package openplatform import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestDocsRoutesServeOpenAPIAndBothDocumentationViews(t *testing.T) { handler := WithDocs(http.NotFoundHandler()) tests := []struct { path string contentType string want string }{ {OpenAPISpecPath, "application/yaml", "openapi: 3.0.3"}, {SwaggerUIPath, "text/html", "swagger-ui-bundle.js"}, {SimpleDocsPath, "text/html", "车辆数据开放平台"}, {swaggerInitJSPath, "application/javascript", `persistAuthorization: false`}, } for _, test := range tests { t.Run(test.path, func(t *testing.T) { recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, test.path, nil)) if recorder.Code != http.StatusOK { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } if !strings.HasPrefix(recorder.Header().Get("Content-Type"), test.contentType) { t.Fatalf("content-type=%q", recorder.Header().Get("Content-Type")) } if !strings.Contains(recorder.Body.String(), test.want) { t.Fatalf("body does not contain %q", test.want) } if recorder.Header().Get("X-Content-Type-Options") != "nosniff" { t.Fatal("documentation asset must set nosniff") } if test.path == SimpleDocsPath && !strings.Contains(recorder.Header().Get("Content-Security-Policy"), "script-src 'unsafe-inline'") { t.Fatal("simple documentation must allow its bundled interactive field renderer") } }) } } func TestDocsRedirectsAndRejectsMutatingMethods(t *testing.T) { handler := WithDocs(http.NotFoundHandler()) redirect := httptest.NewRecorder() handler.ServeHTTP(redirect, httptest.NewRequest(http.MethodGet, "/open-api/docs", nil)) if redirect.Code != http.StatusPermanentRedirect || redirect.Header().Get("Location") != SimpleDocsPath { t.Fatalf("status=%d location=%q", redirect.Code, redirect.Header().Get("Location")) } post := httptest.NewRecorder() handler.ServeHTTP(post, httptest.NewRequest(http.MethodPost, OpenAPISpecPath, nil)) if post.Code != http.StatusMethodNotAllowed || post.Header().Get("Allow") != "GET, HEAD" { t.Fatalf("status=%d allow=%q", post.Code, post.Header().Get("Allow")) } } func TestOpenAPISpecCoversPublicAndManagementEndpoints(t *testing.T) { spec := string(openAPISpec) for _, want := range []string{ "openapi: 3.0.3", HydrogenQueryPath + ":", MileageQueryPath + ":", MileageRangeQueryPath + ":", StationaryVehicleQueryPath + ":", "/api/v2/open-platform/apps:", "AppKeyAuth:", "AdminBearer:", "省略或传空数组时", "protocolPriority:", "sourceProtocol:", StationaryVehicleQueryPath, "enum: [GB32960, MQTT, JT808]", } { if !strings.Contains(spec, want) { t.Fatalf("OpenAPI spec missing %q", want) } } } func TestLiveContractDocumentationCoversSerializedFieldsAndQuality(t *testing.T) { // Public additions must be discoverable in both the machine contract and the // human-readable documentation, including their null/quality companions. for _, field := range []string{ "remainingHydrogenKg", "remainingHydrogenPercent", "hydrogenRecordTime", "hydrogenDataStatus", "remainingHydrogenKgStatus", "remainingHydrogenPercentStatus", "hydrogenValueSource", "hydrogenSourceProtocol", "hydrogenStaleAfterSeconds", "hydrogenExpectedIntervalSeconds", "gpsFixStatus", "locationRecordTime", "coordinateSystem", "statisticsStartTime", "statisticsEndTime", "updatedAt", "calculationPhase", "qualityStatus", "algorithmVersion", } { if !strings.Contains(string(openAPISpec), field+":") { t.Errorf("OpenAPI missing live contract field %s", field) } if !strings.Contains(string(simpleDocsHTML), field) { t.Errorf("HTML missing live contract field %s", field) } } for name, doc := range map[string]string{"OpenAPI": string(openAPISpec), "HTML": string(simpleDocsHTML)} { for _, boundary := range []string{"PARTIAL", "UNSUPPORTED", "UNKNOWN", "PRELIMINARY", "FINAL", "SUSPECT", "REPORTED", "共同快照", "null"} { if !strings.Contains(doc, boundary) { t.Errorf("%s missing availability/comparability boundary %q", name, boundary) } } } if strings.Contains(string(openAPISpec), "MEASURED") || strings.Contains(string(simpleDocsHTML), "MEASURED") { t.Error("reported hydrogen must not promise a measured terminal value") } if strings.Contains(string(simpleDocsHTML), "新接入请使用 sourceProtocol") { t.Error("HTML must not advertise sourceProtocol as realtime field") } }