最初版本

This commit is contained in:
sa19840531
2026-08-15 14:51:51 +08:00
parent 4e01df4dce
commit ea47ea4449
176 changed files with 21009 additions and 0 deletions
+746
View File
@@ -0,0 +1,746 @@
from __future__ import annotations
import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from typing import Any
DEFAULT_BASE_URL = "http://www.pubuli.com:7788/"
DEFAULT_ACTIVITY_ID = "ad-x1234"
@dataclass
class TestResult:
name: str
passed: bool
status_code: int | None
message: str
response: Any = None
def build_url(
base_url: str,
path: str,
query: dict[str, Any] | None = None,
) -> str:
url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
if query:
filtered = {
key: value
for key, value in query.items()
if value is not None and value != ""
}
if filtered:
url += "?" + urllib.parse.urlencode(filtered)
return url
def request_json(
*,
base_url: str,
path: str,
method: str = "GET",
query: dict[str, Any] | None = None,
body: dict[str, Any] | None = None,
timeout: int = 15,
) -> tuple[int, Any]:
url = build_url(
base_url=base_url,
path=path,
query=query,
)
headers = {
"Accept": "application/json",
"User-Agent": "API-System-Smoke-Test/2.0",
}
data = None
if body is not None:
data = json.dumps(
body,
ensure_ascii=False,
).encode("utf-8")
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
url=url,
data=data,
headers=headers,
method=method.upper(),
)
try:
with urllib.request.urlopen(
request,
timeout=timeout,
) as response:
status_code = int(response.status)
raw_text = response.read().decode(
"utf-8",
errors="replace",
)
except urllib.error.HTTPError as exc:
status_code = int(exc.code)
raw_text = exc.read().decode(
"utf-8",
errors="replace",
)
try:
parsed = (
json.loads(raw_text)
if raw_text
else None
)
except json.JSONDecodeError:
parsed = raw_text
return status_code, parsed
def request_text(
*,
base_url: str,
path: str,
timeout: int = 15,
) -> tuple[int, str, str]:
url = build_url(
base_url=base_url,
path=path,
)
request = urllib.request.Request(
url=url,
method="GET",
headers={
"Accept": "text/html,application/xhtml+xml",
"User-Agent": "API-System-Smoke-Test/2.0",
},
)
try:
with urllib.request.urlopen(
request,
timeout=timeout,
) as response:
return (
int(response.status),
response.geturl(),
response.read().decode(
"utf-8",
errors="replace",
),
)
except urllib.error.HTTPError as exc:
return (
int(exc.code),
exc.geturl(),
exc.read().decode(
"utf-8",
errors="replace",
),
)
def add_result(
results: list[TestResult],
*,
name: str,
passed: bool,
status_code: int | None,
message: str,
response: Any = None,
) -> None:
results.append(
TestResult(
name=name,
passed=passed,
status_code=status_code,
message=message,
response=response,
)
)
def ask_username() -> str:
"""
互動式要求輸入測試會員名。
使用者直接按 Enter 時會持續要求輸入,
避免傳送空白會員名給 API。
"""
while True:
username = input(
"請輸入要測試的會員名:"
).strip()
if username:
return username
print("會員名不可為空,請重新輸入。")
def run_tests(
args: argparse.Namespace,
) -> list[TestResult]:
results: list[TestResult] = []
# 1. 主域名
try:
status, final_url, html = request_text(
base_url=args.base_url,
path="/",
timeout=args.timeout,
)
passed = status == 200
add_result(
results,
name="主域名訪問",
passed=passed,
status_code=status,
message=(
f"可訪問,最終地址:{final_url}"
if passed
else f"主域名返回異常,最終地址:{final_url}"
),
)
except Exception as exc:
add_result(
results,
name="主域名訪問",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
return results
# 2. 登入頁
try:
status, final_url, html = request_text(
base_url=args.base_url,
path="/login",
timeout=args.timeout,
)
passed = (
status == 200
and (
"用戶名" in html
or "username" in html.lower()
)
)
add_result(
results,
name="登入頁",
passed=passed,
status_code=status,
message=(
f"登入頁正常:{final_url}"
if passed
else "登入頁內容不符合預期"
),
)
except Exception as exc:
add_result(
results,
name="登入頁",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 3. 健康檢查
try:
status, data = request_json(
base_url=args.base_url,
path="/health",
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, dict)
and data.get("status") == "ok"
)
add_result(
results,
name="健康檢查",
passed=passed,
status_code=status,
message=(
"服務狀態正常"
if passed
else "健康接口回應不符合預期"
),
response=data,
)
except Exception as exc:
add_result(
results,
name="健康檢查",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 4. 帳號查詢
account_data: Any = None
try:
status, account_data = request_json(
base_url=args.base_url,
path="/platform/get-user-account",
query={
"username": args.username,
},
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(account_data, dict)
and account_data.get("is_exist") is True
)
add_result(
results,
name="帳號查詢",
passed=passed,
status_code=status,
message=(
f"找到會員,狀態={account_data.get('status')}"
if passed
else "找不到會員或帳號接口格式錯誤"
),
response=account_data,
)
except Exception as exc:
add_result(
results,
name="帳號查詢",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
query_username = args.username
if isinstance(account_data, dict):
query_username = (
account_data.get("username_clean")
or account_data.get("username")
or args.username
)
# 5. 提款記錄
try:
status, data = request_json(
base_url=args.base_url,
path="/platform/withdraw-orders",
query={
"username": query_username,
},
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, list)
)
add_result(
results,
name="提款記錄查詢",
passed=passed,
status_code=status,
message=(
f"返回 {len(data)} 筆提款記錄"
if passed
else "提款接口回應格式錯誤"
),
response=(
data[:5]
if isinstance(data, list)
else data
),
)
except Exception as exc:
add_result(
results,
name="提款記錄查詢",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 6. 未完成提款
try:
status, data = request_json(
base_url=args.base_url,
path="/platform/withdraw-orders-unfinish",
query={
"username": query_username,
},
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, list)
)
add_result(
results,
name="未完成提款查詢",
passed=passed,
status_code=status,
message=(
f"返回 {len(data)} 筆未完成提款"
if passed
else "未完成提款接口回應格式錯誤"
),
response=(
data[:5]
if isinstance(data, list)
else data
),
)
except Exception as exc:
add_result(
results,
name="未完成提款查詢",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 7. 充值記錄
deposit_query: dict[str, Any] = {
"username": query_username,
}
if args.amount:
deposit_query["amount"] = args.amount
if args.created_at:
deposit_query["created_at"] = args.created_at
try:
status, data = request_json(
base_url=args.base_url,
path="/platform/deposit-not-received",
query=deposit_query,
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, list)
)
add_result(
results,
name="充值記錄查詢",
passed=passed,
status_code=status,
message=(
f"返回 {len(data)} 筆充值記錄"
if passed
else "充值接口回應格式錯誤"
),
response=(
data[:5]
if isinstance(data, list)
else data
),
)
except Exception as exc:
add_result(
results,
name="充值記錄查詢",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 8. 活動申請模擬
try:
status, data = request_json(
base_url=args.base_url,
path="/platform/apply-activity",
method="POST",
body={
"username": query_username,
"activity_id": args.activity_id,
},
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, dict)
and "activity_found" in data
and "qualified" in data
)
add_result(
results,
name="活動申請模擬",
passed=passed,
status_code=status,
message=(
"活動模擬接口正常"
if passed
else "活動接口回應格式錯誤"
),
response=data,
)
except Exception as exc:
add_result(
results,
name="活動申請模擬",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
# 9. 餘額查詢模擬
try:
status, data = request_json(
base_url=args.base_url,
path="/platform/fix-balance",
method="PUT",
body={
"username": query_username,
},
timeout=args.timeout,
)
passed = (
status == 200
and isinstance(data, dict)
and "balance" in data
)
add_result(
results,
name="餘額查詢模擬",
passed=passed,
status_code=status,
message=(
f"餘額={data.get('balance')}"
if passed
else "餘額接口回應格式錯誤"
),
response=data,
)
except Exception as exc:
add_result(
results,
name="餘額查詢模擬",
passed=False,
status_code=None,
message=f"{type(exc).__name__}: {exc}",
)
return results
def print_results(
results: list[TestResult],
verbose: bool,
) -> None:
print()
print("API接口系統測試結果")
print("=" * 76)
for index, result in enumerate(
results,
start=1,
):
mark = (
"PASS"
if result.passed
else "FAIL"
)
status_text = (
str(result.status_code)
if result.status_code is not None
else "-"
)
print(
f"[{mark}] {index:02d}. "
f"{result.name} "
f"(HTTP {status_text})"
)
print(f" {result.message}")
if (
verbose
and result.response is not None
):
print(
json.dumps(
result.response,
ensure_ascii=False,
indent=2,
default=str,
)
)
passed_count = sum(
1
for result in results
if result.passed
)
failed_count = (
len(results)
- passed_count
)
print("-" * 76)
print(
f"總計:{len(results)},"
f"通過:{passed_count},"
f"失敗:{failed_count}"
)
print(
"測試結果:全部通過"
if failed_count == 0
else "測試結果:存在失敗項目"
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"pubuli.com API接口系統"
"可輸入會員名測試腳本"
)
)
parser.add_argument(
"--base-url",
default=DEFAULT_BASE_URL,
help=(
"主域名,預設:"
f"{DEFAULT_BASE_URL}"
),
)
parser.add_argument(
"--username",
default="",
help=(
"測試會員名。"
"未指定時,程式會提示輸入。"
),
)
parser.add_argument(
"--amount",
default="",
help="可選:指定充值金額",
)
parser.add_argument(
"--created-at",
default="",
help=(
"可選:指定充值時間,"
"格式為 ISO-8601"
),
)
parser.add_argument(
"--activity-id",
default=DEFAULT_ACTIVITY_ID,
help="測試活動 ID",
)
parser.add_argument(
"--timeout",
type=int,
default=15,
help="單個請求超時秒數",
)
parser.add_argument(
"--verbose",
action="store_true",
help="顯示完整 JSON 回應",
)
args = parser.parse_args()
# 沒有透過命令列指定會員名時,進入互動輸入模式。
if not args.username.strip():
args.username = ask_username()
else:
args.username = args.username.strip()
return args
def main() -> int:
args = parse_args()
print()
print("pubuli.com API接口系統冒煙測試")
print("=" * 76)
print(f"主域名:{args.base_url}")
print(f"測試會員:{args.username}")
print("API Key:目前不使用")
print(f"請求超時:{args.timeout} 秒")
results = run_tests(args)
print_results(
results=results,
verbose=args.verbose,
)
return (
0
if all(
result.passed
for result in results
)
else 1
)
if __name__ == "__main__":
sys.exit(main())