package realtime import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestOpenAPIHandlerDocumentsRealtimeSnapshotAndLocation(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/openapi.json", nil) response := httptest.NewRecorder() NewOpenAPIHandler().ServeHTTP(response, request) if response.Code != http.StatusOK { t.Fatalf("status = %d body=%s", response.Code, response.Body.String()) } body := response.Body.String() for _, want := range []string{ `"/api/realtime/snapshots"`, `"/api/realtime/locations"`, `"/api/stats/daily-metrics"`, `"/api/stats/daily-metrics/sources"`, `"/api/stats/daily-metrics/sources/quality"`, `"/api/stats/daily-metrics/sources/selection"`, `"/api/stats/daily-metrics/diagnostics"`, `"/api/stats/daily-metrics/diagnostics/summary"`, `"/api/stats/daily-metrics/diagnostics/reasons"`, `"/api/stats/daily-metrics/diagnostics/field-status"`, `"/api/stats/data-sources"`, `"/api/stats/data-sources/diagnostics"`, `"/api/stats/data-sources/kind-suggestions"`, `"/api/stats/data-sources/jt808-identity-gaps"`, `"/api/stats/data-sources/jt808-mapping-gaps"`, `"/api/stats/data-sources/{id}"`, `"vehicle_realtime_snapshot"`, `"vehicle_realtime_location"`, `vehicle_daily_mileage`, `vehicle_daily_mileage_source`, `"vehicle_data_source"`, `"Stats API"`, `"Data Source API"`, } { if !strings.Contains(body, want) { t.Fatalf("openapi missing %s: %s", want, body) } } for _, want := range []string{`"daily_mileage_km"`, `"latest_total_mileage_km"`, `"source_key"`, `"source_count"`, `"vehicle_count"`, `"selected_count"`, `"sample_count"`, `"quality_status"`, `"quality_reason"`, `"is_selected"`, `"selection_status"`, `"selection_reason"`, `"selection_action"`, `"lower_trust_priority_or_sample_count"`, `"platform_name"`, `"source_code"`, `"source_kind"`, `"sourceKind"`, `"trust_priority"`, `"enabled"`, `"latest_seen_at"`, `"candidate_source_code"`, `"recommended_operator_action"`, `"suggested_source_kind"`, `"suggestion_confidence"`, `"NO_SOURCE_SAMPLE"`, `"NO_TOTAL_MILEAGE"`, `"source_sample_count"`, `"realtime_total_mileage_km"`, `"total_mileage_event_time"`, `"realtime_total_mileage_event_time"`, `"active_count"`, `"actionable_issue_count"`, `"vehicle_total"`, `"pipeline_issue_total"`, `"source_data_issue_total"`, `"severity"`, `"field_status_severity"`, `"realtime_total_mileage_not_reported_on_stat_date"`, `"realtime_mileage_field_status"`, `"field_status_action"`, `"candidate_field_unmapped"`, `"standard_field_stale"`, `"missing_phone_and_plate_binding"`, `"missing_source_phone_identifier"`, `"configured_source_code_platform_name"`, `"source_platform_name_mismatch"`, `"suggested_identifier_value"`, `"vehicle_identifier_example"`, `"raw_frame_query_path"`, `"data_source_query_path"`} { if !strings.Contains(body, want) { t.Fatalf("openapi should document data source field %s: %s", want, body) } } for _, removed := range []string{`"/api/realtime/kv"`, `"vehicle_realtime_kv"`, `"Realtime KV API"`} { if strings.Contains(body, removed) { t.Fatalf("openapi should not expose removed MySQL realtime kv API %s: %s", removed, body) } } for _, want := range []string{`"includeTotal"`, `"sourceCodeMissing"`, `默认不执行 COUNT(*)`, `total 默认表示本页返回条数`} { if !strings.Contains(body, want) { t.Fatalf("openapi should document lightweight total semantics, missing %s: %s", want, body) } } }