#!/usr/bin/env python3 """Miniprogram API comprehensive test - 6 endpoints.""" import json, urllib.request, urllib.error, sys BASE = 'http://localhost:3000/api/v1' # Read fresh token from file with open('g:/hms/.test_token_fresh.txt') as f: TOKEN = f.read().strip() PATIENT_ID = '019dcd34-bc4d-72c1-8c19-77ce1f4839d6' TENANT_ID = '019d80da-7a2c-7820-b0a3-3d5266a3a324' headers = { 'Authorization': f'Bearer {TOKEN}', 'X-Tenant-Id': TENANT_ID, 'X-Patient-Id': PATIENT_ID, 'Content-Type': 'application/json' } def api_call(method, path, data=None): url = f'{BASE}{path}' if data: req = urllib.request.Request(url, data=json.dumps(data).encode(), headers=headers, method=method) else: req = urllib.request.Request(url, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=10) as resp: body = json.loads(resp.read().decode()) return resp.status, body except urllib.error.HTTPError as e: body = e.read().decode() try: return e.code, json.loads(body) except Exception: return e.code, body except Exception as e: return 0, str(e) def get_keys(obj, prefix=''): """Recursively extract field names from a dict.""" fields = [] if isinstance(obj, dict): for k, v in obj.items(): full = f'{prefix}{k}' if not prefix else f'{prefix}.{k}' if isinstance(v, dict): fields.extend(get_keys(v, full)) else: fields.append(full) return fields def print_header(title, api_path): print() print('=' * 70) print(f'{title}') print(f'Endpoint: {api_path}') print('=' * 70) def analyze_fields(name, data, expected_fields): """Compare actual vs expected fields.""" actual_keys = set(data.keys()) if isinstance(data, dict) else set() expected_set = set(expected_fields) matched = actual_keys & expected_set missing = expected_set - actual_keys extra = actual_keys - expected_set match_status = "MATCH" if not missing else "MISMATCH" print(f'[{name}] status=200 | {match_status}') print(f' Actual fields: {sorted(actual_keys)}') print(f' Expected fields: {sorted(expected_set)}') if matched: print(f' Matched: {sorted(matched)}') if missing: print(f' ** MISSING: {sorted(missing)} **') if extra: print(f' Extra: {sorted(extra)}') return missing # ===================================================== # API 1: GET /health/vital-signs/today # ===================================================== print_header('API 1: Today Summary', f'GET /health/vital-signs/today?patient_id={PATIENT_ID}') status, body = api_call('GET', f'/health/vital-signs/today?patient_id={PATIENT_ID}') print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') if status == 200: data = body.get('data', body) expected = ['blood_pressure', 'heart_rate', 'blood_sugar', 'weight'] missing = analyze_fields('Today Summary', data, expected) # Check nested structure for key in ['blood_pressure', 'heart_rate', 'blood_sugar', 'weight']: if key in data and isinstance(data[key], dict): print(f' {key} sub-fields: {sorted(data[key].keys())}') else: print(f' FAILED: {status}') # Try without patient_id print(' Retrying without patient_id param...') status2, body2 = api_call('GET', '/health/vital-signs/today') print(f' status={status2}') print(f' Response: {json.dumps(body2, indent=2, ensure_ascii=False)}') # ===================================================== # API 2: POST /health/patients/{id}/vital-signs # ===================================================== print_header('API 2: Create Vital Signs', f'POST /health/patients/{PATIENT_ID}/vital-signs') create_data = { 'record_date': '2026-04-27', 'systolic_bp_morning': 130, 'diastolic_bp_morning': 85, 'heart_rate': 75, 'weight': 69.0, 'blood_sugar': 5.5, } status, body = api_call('POST', f'/health/patients/{PATIENT_ID}/vital-signs', create_data) print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') if status in (200, 201): data = body.get('data', body) actual_keys = set(data.keys()) if isinstance(data, dict) else set() expected_keys = {'id', 'patient_id', 'record_date', 'systolic_bp_morning', 'diastolic_bp_morning', 'heart_rate', 'weight', 'blood_sugar', 'created_at', 'updated_at', 'version', 'tenant_id'} analyze_fields('Create Vital Signs', data, expected_keys) # ===================================================== # API 3: GET /health/patients/{id}/vital-signs (paginated) # ===================================================== print_header('API 3: Vital Signs History', f'GET /health/patients/{PATIENT_ID}/vital-signs?page=1&page_size=5') status, body = api_call('GET', f'/health/patients/{PATIENT_ID}/vital-signs?page=1&page_size=5') print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') if status == 200: data = body.get('data', body) # Check pagination structure if isinstance(body.get('data'), dict) and 'data' in body['data']: # wrapped: {data: {data: [...], total: N}} paginated = body['data'] print(f' Pagination structure: data={type(paginated.get("data")).__name__}, total={paginated.get("total")}') items = paginated.get('data', []) elif isinstance(body.get('data'), list): items = body['data'] print(f' Response is direct array, len={len(items)}') elif isinstance(body.get('data'), dict) and 'items' in body.get('data', {}): paginated = body['data'] items = paginated.get('items', []) print(f' Pagination via items: total={paginated.get("total")}') else: items = [] print(f' Unknown pagination structure') if items and isinstance(items[0], dict): actual_keys = set(items[0].keys()) expected_keys = {'id', 'record_date', 'systolic_bp_morning', 'diastolic_bp_morning', 'heart_rate', 'weight', 'blood_sugar', 'created_at', 'updated_at', 'patient_id'} analyze_fields('Vital Signs Item', items[0], expected_keys) # ===================================================== # API 4: GET /health/vital-signs/trend (mini trend) # ===================================================== print_header('API 4: Vital Signs Trend', 'GET /health/vital-signs/trend?indicator=blood_pressure&range=7d') status, body = api_call('GET', '/health/vital-signs/trend?indicator=blood_pressure&range=7d') print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') # Also try the task-specified path print() print(' Also testing: GET /health/patients/{id}/vital-signs/trend') status2, body2 = api_call('GET', f'/health/patients/{PATIENT_ID}/vital-signs/trend?start_date=2026-04-20&end_date=2026-04-27') print(f' status={status2}') print(f' Response: {json.dumps(body2, indent=2, ensure_ascii=False)}') if status == 200: data = body.get('data', body) print(f' Top-level type: {type(data).__name__}') if isinstance(data, dict): print(f' Keys: {sorted(data.keys())}') elif isinstance(data, list): print(f' Array length: {len(data)}') if data: print(f' First item keys: {sorted(data[0].keys()) if isinstance(data[0], dict) else data[0]}') # ===================================================== # API 5: GET /health/appointments # ===================================================== print_header('API 5: Appointments List', f'GET /health/appointments?patient_id={PATIENT_ID}&page=1&page_size=5') status, body = api_call('GET', f'/health/appointments?patient_id={PATIENT_ID}&page=1&page_size=5') print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') if status == 200: data = body.get('data', body) if isinstance(data, dict) and 'data' in data: items = data.get('data', []) total = data.get('total', 'N/A') print(f' Pagination: total={total}, items={len(items)}') elif isinstance(data, list): items = data else: items = [] if items and isinstance(items[0], dict): actual_keys = set(items[0].keys()) # From miniprogram appointment.ts Appointment interface expected_keys = {'id', 'patient_name', 'doctor_name', 'department', 'appointment_date', 'start_time', 'end_time', 'status', 'version'} analyze_fields('Appointment', items[0], expected_keys) # ===================================================== # API 6: GET /health/follow-up-tasks # ===================================================== print_header('API 6: Follow-Up Tasks', f'GET /health/follow-up-tasks?patient_id={PATIENT_ID}&status=pending&page=1&page_size=5') status, body = api_call('GET', f'/health/follow-up-tasks?patient_id={PATIENT_ID}&status=pending&page=1&page_size=5') print(f'status={status}') print(f'Response: {json.dumps(body, indent=2, ensure_ascii=False)}') if status == 200: data = body.get('data', body) if isinstance(data, dict) and 'data' in data: items = data.get('data', []) total = data.get('total', 'N/A') print(f' Pagination: total={total}, items={len(items)}') elif isinstance(data, list): items = data else: items = [] if items and isinstance(items[0], dict): actual_keys = set(items[0].keys()) # From miniprogram followup.ts FollowUpTask interface expected_keys = {'id', 'patient_id', 'patient_name', 'follow_up_type', 'content_template', 'status', 'planned_date', 'version'} analyze_fields('FollowUpTask', items[0], expected_keys) elif not items: print(' No pending tasks. Retrying without status filter...') status2, body2 = api_call('GET', f'/health/follow-up-tasks?patient_id={PATIENT_ID}&page=1&page_size=5') print(f' status={status2}') data2 = body2.get('data', body2) if status2 == 200 else {} if isinstance(data2, dict) and 'data' in data2: items2 = data2.get('data', []) print(f' Items without status filter: {len(items2)}') if items2 and isinstance(items2[0], dict): actual_keys = set(items2[0].keys()) expected_keys = {'id', 'patient_id', 'patient_name', 'follow_up_type', 'content_template', 'status', 'planned_date', 'version'} analyze_fields('FollowUpTask (all)', items2[0], expected_keys) print() print('=' * 70) print('TEST COMPLETE') print('=' * 70)