import json from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] p = PROJECT_ROOT / 'docs' / 'openapi-rc.json' d = json.load(open(p, encoding='utf-8')) print('paths:', len(d['paths'])) assert len(d['paths']) == 129, f"Expected 129 paths, got {len(d['paths'])}" paths = d['paths'] required = [ '/review-weight-templates', '/profile/me/review-weights', '/profile/me/privacy', '/jobs/{jobId}/personalized-review-score', '/company/candidates', '/company/candidates/{applicationId}', '/profile/me/candidate-scoring', '/profile/me/candidate-scoring/dimensions/{dimension}', '/profile/me/candidate-scoring/trend', '/company/candidates/{applicationId}/scoring', '/profile/me/candidate-scoring/appeals', '/profile/me/candidate-scoring/appeals/{appealId}', '/profile/me/candidate-scoring/appeals/{appealId}/cancel', '/admin/candidate-scoring/appeals', '/admin/candidate-scoring/appeals/{appealId}/decision', '/admin/candidate-scoring/appeals/{appealId}/rollback', '/admin/candidate-scoring/appeals/{appealId}/history', '/admin/candidate-scoring/snapshots/{candidateUserId}/{dimension}/{period}/history', # 企业评分运营看板(7 个接口) '/company/scoring/dashboard/overview', '/company/scoring/dashboard/job-rankings', '/company/scoring/dashboard/job-rankings/{jobId}', '/company/scoring/dashboard/candidate-rankings', '/company/scoring/dashboard/candidate-rankings/{applicationId}', '/company/scoring/dashboard/trend', '/admin/scoring/dashboard/overview', # Offer 进阶(5 个 path / 6 个 operation) '/offers/{id}/counter-offers', '/offers/{id}/counter-offers/{coId}/accept', '/offers/{id}/counter-offers/{coId}/reject', '/offers/compare', '/offers/statistics', # 账号注销(2 个 path / 3 个 operation) '/profile/me/deletion', '/profile/me/deletion/cancel', # 个人数据导出(3 个 path / 4 个 operation;详情已覆盖状态查询,无需 /status) '/profile/me/data-export-tasks', '/profile/me/data-export-tasks/{taskId}', '/profile/me/data-export-tasks/{taskId}/download', # 结构化面试评分(3 个 path / 4 个 operation) '/interviews/{interviewId}/score', '/interviews/{interviewId}/score/submit', '/interviews/{interviewId}/score/history', # 同岗位竞争力排名(1 个 path / 1 个 operation) '/jobs/{jobId}/competition-ranking', # V34 统一求职进度聚合(1 个 path / 1 个 operation) '/profile/me/career-progress', # V33 举报与屏蔽(8 个 path / 9 个 operation) '/reports', '/reports/me', '/reports/{id}', '/blocks/{blockedUserId}', '/blocks/me', '/admin/reports', '/admin/reports/{id}', '/admin/reports/{id}/decision', '/skills/word-cloud', '/skills/match', '/skills/learning-path', ] for path in required: assert path in paths, f"Missing path: {path}" print(f" Found: {path}") assert 'get' in paths['/review-weight-templates'], "Missing GET /review-weight-templates" assert 'get' in paths['/profile/me/review-weights'], "Missing GET /profile/me/review-weights" assert 'put' in paths['/profile/me/review-weights'], "Missing PUT /profile/me/review-weights" assert 'get' in paths['/profile/me/privacy'], "Missing GET /profile/me/privacy" assert 'put' in paths['/profile/me/privacy'], "Missing PUT /profile/me/privacy" assert 'get' in paths['/jobs/{jobId}/personalized-review-score'], "Missing GET /jobs/{jobId}/personalized-review-score" assert 'get' in paths['/company/candidates'], "Missing GET /company/candidates" assert 'get' in paths['/company/candidates/{applicationId}'], "Missing GET /company/candidates/{applicationId}" assert 'get' in paths['/profile/me/candidate-scoring'], "Missing GET /profile/me/candidate-scoring" assert 'get' in paths['/profile/me/candidate-scoring/dimensions/{dimension}'], "Missing GET /profile/me/candidate-scoring/dimensions/{dimension}" assert 'get' in paths['/profile/me/candidate-scoring/trend'], "Missing GET /profile/me/candidate-scoring/trend" assert 'get' in paths['/company/candidates/{applicationId}/scoring'], "Missing GET /company/candidates/{applicationId}/scoring" # 评分治理(9 个接口落在 8 个路径:POST+GET 共享 /appeals) assert 'post' in paths['/profile/me/candidate-scoring/appeals'], "Missing POST /profile/me/candidate-scoring/appeals" assert 'get' in paths['/profile/me/candidate-scoring/appeals'], "Missing GET /profile/me/candidate-scoring/appeals" assert 'get' in paths['/profile/me/candidate-scoring/appeals/{appealId}'], "Missing GET .../appeals/{appealId}" assert 'post' in paths['/profile/me/candidate-scoring/appeals/{appealId}/cancel'], "Missing POST .../cancel" assert 'get' in paths['/admin/candidate-scoring/appeals'], "Missing GET /admin/candidate-scoring/appeals" assert 'post' in paths['/admin/candidate-scoring/appeals/{appealId}/decision'], "Missing POST .../decision" assert 'post' in paths['/admin/candidate-scoring/appeals/{appealId}/rollback'], "Missing POST .../rollback" assert 'get' in paths['/admin/candidate-scoring/appeals/{appealId}/history'], "Missing GET .../history" assert 'get' in paths['/admin/candidate-scoring/snapshots/{candidateUserId}/{dimension}/{period}/history'], "Missing GET snapshots history" # 企业评分运营看板(7 个 GET) assert 'get' in paths['/company/scoring/dashboard/overview'], "Missing GET /company/scoring/dashboard/overview" assert 'get' in paths['/company/scoring/dashboard/job-rankings'], "Missing GET /company/scoring/dashboard/job-rankings" assert 'get' in paths['/company/scoring/dashboard/job-rankings/{jobId}'], "Missing GET /company/scoring/dashboard/job-rankings/{jobId}" assert 'get' in paths['/company/scoring/dashboard/candidate-rankings'], "Missing GET /company/scoring/dashboard/candidate-rankings" assert 'get' in paths['/company/scoring/dashboard/candidate-rankings/{applicationId}'], "Missing GET /company/scoring/dashboard/candidate-rankings/{applicationId}" assert 'get' in paths['/company/scoring/dashboard/trend'], "Missing GET /company/scoring/dashboard/trend" assert 'get' in paths['/admin/scoring/dashboard/overview'], "Missing GET /admin/scoring/dashboard/overview" # Offer 进阶(6 个 operation / 5 个 path) assert 'post' in paths['/offers/{id}/counter-offers'], "Missing POST /offers/{id}/counter-offers" assert 'get' in paths['/offers/{id}/counter-offers'], "Missing GET /offers/{id}/counter-offers" assert 'patch' in paths['/offers/{id}/counter-offers/{coId}/accept'], "Missing PATCH .../accept" assert 'patch' in paths['/offers/{id}/counter-offers/{coId}/reject'], "Missing PATCH .../reject" assert 'get' in paths['/offers/compare'], "Missing GET /offers/compare" assert 'get' in paths['/offers/statistics'], "Missing GET /offers/statistics" # 账号注销(3 个 operation) assert 'get' in paths['/profile/me/deletion'], "Missing GET /profile/me/deletion" assert 'post' in paths['/profile/me/deletion'], "Missing POST /profile/me/deletion" assert 'post' in paths['/profile/me/deletion/cancel'], "Missing POST /profile/me/deletion/cancel" # 个人数据导出(4 个 operation / 3 个 path) assert 'post' in paths['/profile/me/data-export-tasks'], "Missing POST /profile/me/data-export-tasks" assert 'get' in paths['/profile/me/data-export-tasks'], "Missing GET /profile/me/data-export-tasks" assert 'get' in paths['/profile/me/data-export-tasks/{taskId}'], "Missing GET /profile/me/data-export-tasks/{taskId}" assert 'get' in paths['/profile/me/data-export-tasks/{taskId}/download'], "Missing GET .../download" print(" Personal data export endpoints present (4 operations / 3 paths)") # 同岗位竞争力排名(1 个 GET operation) assert 'get' in paths['/jobs/{jobId}/competition-ranking'], "Missing GET /jobs/{jobId}/competition-ranking" print(" Competition ranking endpoint present (1 GET operation)") # V34 统一求职进度聚合(1 个 GET operation) assert 'get' in paths['/profile/me/career-progress'], "Missing GET /profile/me/career-progress" print(" Career progress endpoint present (1 GET operation)") # V33 举报与屏蔽(8 个 path / 9 个 operation;PUT+DELETE 共享 /blocks/{blockedUserId}) assert 'post' in paths['/reports'], "Missing POST /reports" assert 'get' in paths['/reports/me'], "Missing GET /reports/me" assert 'get' in paths['/reports/{id}'], "Missing GET /reports/{id}" assert 'put' in paths['/blocks/{blockedUserId}'], "Missing PUT /blocks/{blockedUserId}" assert 'delete' in paths['/blocks/{blockedUserId}'], "Missing DELETE /blocks/{blockedUserId}" assert 'get' in paths['/blocks/me'], "Missing GET /blocks/me" assert 'get' in paths['/admin/reports'], "Missing GET /admin/reports" assert 'get' in paths['/admin/reports/{id}'], "Missing GET /admin/reports/{id}" assert 'post' in paths['/admin/reports/{id}/decision'], "Missing POST /admin/reports/{id}/decision" print(" V33 report/block endpoints present (9 operations / 8 paths)") # Skill cloud endpoints (3 paths / 3 GET operations) for skill_path in ('/skills/word-cloud', '/skills/match', '/skills/learning-path'): assert 'get' in paths[skill_path], f"Missing GET {skill_path}" assert paths['/skills/word-cloud']['get'].get('operationId') == 'getWordCloud' assert paths['/skills/match']['get'].get('operationId') == 'getMatch' assert paths['/skills/learning-path']['get'].get('operationId') == 'getLearningPath' assert paths['/skills/word-cloud']['get']['responses']['200']['content']['application/json']['schema']['$ref'] == '#/components/schemas/ApiResponseSkillCloudResponse' assert paths['/skills/match']['get']['responses']['200']['content']['application/json']['schema']['$ref'] == '#/components/schemas/ApiResponseSkillMatchResponse' assert paths['/skills/learning-path']['get']['responses']['200']['content']['application/json']['schema']['$ref'] == '#/components/schemas/ApiResponseSkillLearningPathResponse' schemas = d['components']['schemas'] skill_schema_fields = { 'SkillCloudNode': {'id', 'code', 'name'}, 'SkillCloudItem': {'rank', 'skillId', 'skillCode', 'nameZh', 'nameEn', 'sourceType', 'sourceCode', 'categoryId', 'categoryName', 'heat', 'heatSourceCode', 'heatElementId', 'heatProxy'}, 'SkillCloudResponse': {'industry', 'occupation', 'category', 'total', 'items'}, 'SkillMatchResponse': {'matchScore', 'matchedCount', 'totalCount', 'matchedSkills', 'missingSkills'}, 'SkillLearningPathItem': {'priority', 'skillId', 'skillName', 'categoryId', 'categoryName', 'suggestion'}, 'SkillLearningPathResponse': {'total', 'items'}, } for schema_name, expected_fields in skill_schema_fields.items(): schema = schemas.get(schema_name) assert schema is not None, f'Missing schema: {schema_name}' assert expected_fields <= set(schema.get('properties', {})), f'Schema fields missing: {schema_name}' word_cloud_example = paths['/skills/word-cloud']['get']['responses']['200']['content']['application/json']['example']['data'] assert word_cloud_example['total'] == len(word_cloud_example['items']) match_example = paths['/skills/match']['get']['responses']['200']['content']['application/json']['example']['data'] assert match_example['matchedCount'] == len(match_example['matchedSkills']) assert match_example['totalCount'] == len(match_example['matchedSkills']) + len(match_example['missingSkills']) assert match_example['matchScore'] == round(match_example['matchedCount'] * 100 / match_example['totalCount']) learning_path_example = paths['/skills/learning-path']['get']['responses']['200']['content']['application/json']['example']['data'] assert learning_path_example['total'] == len(learning_path_example['items']) print(" Skill cloud endpoints and schemas present (3 GET operations)") print(" Offer advance endpoints all present") print(" 26 operations all present") print(" Account deletion endpoints present (3 operations)") data = json.dumps(d) assert '"" : "#/components/schemas/' not in data, "Found empty ref key" print("No empty ref keys found") print("Valid JSON: True") print("All checks passed!")