You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
12 KiB
269 lines
12 KiB
import requests
|
|
import json
|
|
import time
|
|
import threading
|
|
import pymysql
|
|
import redis
|
|
import os
|
|
|
|
BASE = "http://127.0.0.1:8080/api/v1"
|
|
HEADERS = {"Content-Type": "application/json"}
|
|
DB = {
|
|
"host": os.environ.get("DB_HOST", "127.0.0.1"),
|
|
"port": int(os.environ.get("DB_PORT", "3307")),
|
|
"user": os.environ.get("DB_USERNAME", "mydreamjob"),
|
|
"password": os.environ["DB_PASSWORD"],
|
|
"database": os.environ.get("DB_NAME", "mydreamjob")
|
|
}
|
|
|
|
def db_conn():
|
|
return pymysql.connect(**DB, autocommit=True)
|
|
|
|
def get_code(phone, purpose="login"):
|
|
conn = db_conn()
|
|
with conn.cursor() as c:
|
|
c.execute("SELECT code FROM sms_verification_codes WHERE phone=%s AND purpose=%s AND used=0 ORDER BY id DESC LIMIT 1",
|
|
(phone, purpose))
|
|
row = c.fetchone()
|
|
conn.close()
|
|
return row[0] if row else None
|
|
|
|
def auth_header(token):
|
|
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
|
|
def login(phone, role):
|
|
r = redis.Redis(host='127.0.0.1', port=6379)
|
|
r.delete(f'sms:rate:{phone}')
|
|
r.delete(f'sms:lockout:{phone}')
|
|
requests.post(f"{BASE}/auth/sms/send", params={"phone": phone, "purpose": "login"}, headers=HEADERS)
|
|
time.sleep(2)
|
|
code = get_code(phone)
|
|
if not code: raise Exception(f"No code for {phone}")
|
|
r = requests.post(f"{BASE}/auth/login", json={"phone": phone, "code": code, "purpose": "login"}, headers=HEADERS)
|
|
if r.status_code != 200: raise Exception(f"Login failed: {r.status_code} {r.text}")
|
|
return r.json()["data"]["accessToken"]
|
|
|
|
results = []
|
|
|
|
def test(name, method, url, headers, expected_status, body=None, check_fn=None):
|
|
full_url = f"{BASE}{url}" if not url.startswith("http") else url
|
|
if body:
|
|
r = requests.request(method, full_url, json=body, headers=headers)
|
|
else:
|
|
r = requests.request(method, full_url, headers=headers)
|
|
status = r.status_code
|
|
passed = status == expected_status
|
|
detail = ""
|
|
try:
|
|
resp = r.json()
|
|
if check_fn: passed = passed and check_fn(resp)
|
|
detail = resp.get("message", "") or str(resp)[:200]
|
|
except:
|
|
detail = r.text[:200]
|
|
results.append((name, method, url, status, expected_status, "PASS" if passed else "FAIL", detail))
|
|
if not passed:
|
|
print(f" FAIL: {name} - expected {expected_status}, got {status}, detail={detail}")
|
|
return r
|
|
|
|
print("=== Login ===")
|
|
company_token = login("13886593964", "COMPANY")
|
|
print(f" Company token: {company_token[:20]}...")
|
|
admin_token = login("19986593964", "ADMIN")
|
|
print(f" Admin token: {admin_token[:20]}...")
|
|
other_company_token = login("15186593964", "COMPANY")
|
|
print(f" Other company token: {other_company_token[:20]}...")
|
|
student_token = login("13986593964", "STUDENT")
|
|
print(f" Student token: {student_token[:20]}...")
|
|
|
|
# 1. Create company profile
|
|
print("\n=== Test 1: Create company profile ===")
|
|
company_body = {"name": "黑盒测试企业", "description": "test", "industry": "IT", "address": "北京"}
|
|
r = requests.get(f"{BASE}/companies/me", headers=auth_header(company_token))
|
|
if r.status_code == 200:
|
|
body = r.json()
|
|
if body.get("data") is not None:
|
|
test("1. Company exists", "GET", "/companies/me", auth_header(company_token), 200, None,
|
|
lambda j: j.get("data", {}).get("id") is not None)
|
|
else:
|
|
test("1. Create company", "POST", "/companies/me", auth_header(company_token), 200, company_body,
|
|
lambda j: j.get("data", {}).get("id") is not None)
|
|
elif r.status_code == 404:
|
|
test("1. Create company", "POST", "/companies/me", auth_header(company_token), 200, company_body,
|
|
lambda j: j.get("data", {}).get("id") is not None)
|
|
else:
|
|
test("1. Create company failed", "GET", "/companies/me", auth_header(company_token), r.status_code, None)
|
|
|
|
# 2. Submit verification
|
|
print("\n=== Test 2: Submit verification ===")
|
|
r = test("2. Submit verification", "POST", f"{BASE}/companies/me/verification",
|
|
auth_header(company_token), 200,
|
|
{"materialsJson": "{\"license\":\"http://example.com/license.jpg\"}"},
|
|
lambda j: j.get("data", {}).get("status") == "PENDING")
|
|
verification_id = r.json().get("data", {}).get("id")
|
|
print(f" Verification ID: {verification_id}")
|
|
|
|
# 3. Query own verification
|
|
print("\n=== Test 3: Query own verification ===")
|
|
test("3. Query own verification", "GET", f"{BASE}/companies/me/verification",
|
|
auth_header(company_token), 200, None,
|
|
lambda j: j.get("data", {}).get("status") == "PENDING")
|
|
|
|
# 4. Non-admin accesses admin list -> 403
|
|
print("\n=== Test 4: Non-admin -> 403 ===")
|
|
test("4. Company access admin list", "GET", f"{BASE}/admin/companies/verifications",
|
|
auth_header(company_token), 403)
|
|
test("4b. Student access admin list", "GET", f"{BASE}/admin/companies/verifications",
|
|
auth_header(student_token), 403)
|
|
|
|
# 5. Admin query pending list
|
|
print("\n=== Test 5: Admin query pending list ===")
|
|
test("5. Admin list pending", "GET", f"{BASE}/admin/companies/verifications?status=PENDING",
|
|
auth_header(admin_token), 200,
|
|
None, lambda j: j.get("data", {}).get("total", 0) > 0)
|
|
|
|
# 6. Admin query detail
|
|
print("\n=== Test 6: Admin query detail ===")
|
|
test("6. Admin query detail", "GET", f"{BASE}/admin/companies/verifications/{verification_id}",
|
|
auth_header(admin_token), 200,
|
|
None, lambda j: j.get("data", {}).get("status") == "PENDING")
|
|
|
|
# 7. REJECT without reason -> 400
|
|
print("\n=== Test 7: REJECT without reason ===")
|
|
test("7. REJECT without reason", "POST", f"{BASE}/admin/companies/verifications/{verification_id}/decision",
|
|
auth_header(admin_token), 400, {"decision": "REJECTED"})
|
|
|
|
# 8. Admin REJECT -> 200
|
|
print("\n=== Test 8: Admin REJECT ===")
|
|
test("8. Admin REJECT", "POST", f"{BASE}/admin/companies/verifications/{verification_id}/decision",
|
|
auth_header(admin_token), 200,
|
|
{"decision": "REJECTED", "reason": "营业执照不清晰,请重新上传"},
|
|
lambda j: j.get("data", {}).get("status") == "REJECTED")
|
|
|
|
# 9. Company sees REJECTED
|
|
print("\n=== Test 9: Company sees REJECTED ===")
|
|
test("9. Company sees REJECTED", "GET", f"{BASE}/companies/me/verification",
|
|
auth_header(company_token), 200,
|
|
None, lambda j: j.get("data", {}).get("status") == "REJECTED")
|
|
|
|
# 10. Company resubmit -> RESUBMITTED
|
|
print("\n=== Test 10: Resubmit ===")
|
|
r = test("10. Resubmit", "POST", f"{BASE}/companies/me/verification",
|
|
auth_header(company_token), 200,
|
|
{"materialsJson": "{\"license\":\"http://example.com/license_v2.jpg\"}"},
|
|
lambda j: j.get("data", {}).get("status") == "RESUBMITTED")
|
|
verification_id2 = r.json().get("data", {}).get("id")
|
|
print(f" New Verification ID: {verification_id2}")
|
|
|
|
# 11. Admin APPROVE -> 200
|
|
print("\n=== Test 11: Admin APPROVE ===")
|
|
test("11. Admin APPROVE", "POST", f"{BASE}/admin/companies/verifications/{verification_id2}/decision",
|
|
auth_header(admin_token), 200,
|
|
{"decision": "APPROVED", "reason": "材料齐全,通过"},
|
|
lambda j: j.get("data", {}).get("status") == "APPROVED")
|
|
|
|
# 12. Double APPROVE -> 409
|
|
print("\n=== Test 12: Double APPROVE -> 409 ===")
|
|
test("12. Double APPROVE", "POST", f"{BASE}/admin/companies/verifications/{verification_id2}/decision",
|
|
auth_header(admin_token), 409, {"decision": "APPROVED"})
|
|
|
|
# 13. Approved company resubmit -> 409
|
|
print("\n=== Test 13: Approved resubmit -> 409 ===")
|
|
test("13. Approved resubmit", "POST", f"{BASE}/companies/me/verification",
|
|
auth_header(company_token), 409, {"materialsJson": "{}"})
|
|
|
|
# 14. Other company views detail -> 403
|
|
print("\n=== Test 14: Other company -> 403 ===")
|
|
test("14. Other company views detail", "GET", f"{BASE}/admin/companies/verifications/{verification_id2}",
|
|
auth_header(other_company_token), 403)
|
|
|
|
# 15. Query history
|
|
print("\n=== Test 15: Query history ===")
|
|
test("15. Query history", "GET", f"{BASE}/admin/companies/verifications/history?verificationId={verification_id2}",
|
|
auth_header(admin_token), 200,
|
|
None, lambda j: len(j.get("data", [])) >= 2)
|
|
|
|
# 16. Verify companies.verified updated
|
|
print("\n=== Test 16: companies.verified ===")
|
|
conn = db_conn()
|
|
with conn.cursor() as c:
|
|
c.execute("SELECT verified FROM companies WHERE name=%s", ("黑盒测试企业",))
|
|
row = c.fetchone()
|
|
conn.close()
|
|
verified = row[0] if row else 0
|
|
print(f" verified={verified}")
|
|
results.append(("16. companies.verified", "DB", "", 0, 0, "PASS" if verified == 1 else "FAIL", f"verified={verified}"))
|
|
|
|
# 17. Verify notification sent
|
|
print("\n=== Test 17: Notification ===")
|
|
conn = db_conn()
|
|
with conn.cursor() as c:
|
|
c.execute("SELECT COUNT(*) FROM notifications WHERE user_id=(SELECT id FROM users WHERE phone=%s) AND type LIKE %s",
|
|
("13886593964", "VERIFICATION_%"))
|
|
count = c.fetchone()[0]
|
|
conn.close()
|
|
print(f" Notification count: {count}")
|
|
results.append(("17. Notification", "DB", "", 0, 0, "PASS" if count >= 2 else "FAIL", f"count={count}"))
|
|
|
|
# 18. Concurrent verification
|
|
print("\n=== Test 18: Concurrent ===")
|
|
# Ensure other company has a company profile and no pending verifications
|
|
conc_body = {"name": "并发测试企业", "description": "test", "industry": "IT", "address": "上海"}
|
|
r_other_get = requests.get(f"{BASE}/companies/me", headers=auth_header(other_company_token))
|
|
company_id = None
|
|
if r_other_get.status_code == 200 and r_other_get.json().get("data") is not None:
|
|
company_id = r_other_get.json()["data"]["id"]
|
|
elif r_other_get.status_code == 404 or (r_other_get.status_code == 200 and r_other_get.json().get("data") is None):
|
|
r_create = requests.post(f"{BASE}/companies/me", json=conc_body, headers=auth_header(other_company_token))
|
|
if r_create.status_code == 200:
|
|
company_id = r_create.json()["data"]["id"]
|
|
elif r_create.status_code == 409:
|
|
# company exists, get it
|
|
r_get = requests.get(f"{BASE}/companies/me", headers=auth_header(other_company_token))
|
|
if r_get.status_code == 200 and r_get.json().get("data"):
|
|
company_id = r_get.json()["data"]["id"]
|
|
|
|
if company_id is None:
|
|
results.append(("18. Concurrent", "DB", "", 0, 0, "FAIL", "no company for other user"))
|
|
else:
|
|
# Clean up any existing pending verifications for this company
|
|
conn = db_conn()
|
|
with conn.cursor() as c:
|
|
c.execute("DELETE FROM company_verification_history WHERE verification_id IN (SELECT id FROM company_verifications WHERE company_id=%s)", (company_id,))
|
|
c.execute("DELETE FROM company_verifications WHERE company_id=%s", (company_id,))
|
|
conn.close()
|
|
|
|
r_sub = requests.post(f"{BASE}/companies/me/verification",
|
|
json={"materialsJson": "{}"}, headers=auth_header(other_company_token))
|
|
if r_sub.status_code == 200:
|
|
cid = r_sub.json().get("data", {}).get("id")
|
|
if cid:
|
|
def approve():
|
|
requests.post(f"{BASE}/admin/companies/verifications/{cid}/decision",
|
|
{"decision": "APPROVED", "reason": "test"}, headers=auth_header(admin_token))
|
|
t1 = threading.Thread(target=approve)
|
|
t2 = threading.Thread(target=approve)
|
|
t1.start(); t2.start(); t1.join(); t2.join()
|
|
# Verify only one history entry for APPROVED
|
|
conn = db_conn()
|
|
with conn.cursor() as c:
|
|
c.execute("SELECT COUNT(*) FROM company_verification_history WHERE verification_id=%s AND to_status='APPROVED'", (cid,))
|
|
cnt = c.fetchone()[0]
|
|
conn.close()
|
|
print(f" Concurrent APPROVED history count: {cnt}")
|
|
results.append(("18. Concurrent", "DB", "", 0, 0, "PASS" if cnt <= 1 else "FAIL", f"concurrent count={cnt}"))
|
|
else:
|
|
results.append(("18. Concurrent", "DB", "", 0, 0, "FAIL", "no verification id"))
|
|
else:
|
|
results.append(("18. Concurrent", "DB", "", 0, 0, "FAIL", f"submit failed: {r_sub.status_code} {r_sub.text[:200]}"))
|
|
|
|
# Summary
|
|
print("\n" + "="*60)
|
|
print("RESULTS SUMMARY")
|
|
print("="*60)
|
|
passed = 0
|
|
failed = 0
|
|
for name, method, url, status, expected, result, detail in results:
|
|
print(f" [{result}] {name} | {method} {url} | {status}/{expected} {detail}")
|
|
if result == "PASS": passed += 1
|
|
else: failed += 1
|
|
print(f"\nTOTAL: {len(results)} tests, {passed} PASS, {failed} FAIL")
|
|
|