轻松聘AI开发
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

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")