Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates. Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
163 lines
6.5 KiB
Go
163 lines
6.5 KiB
Go
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",
|
|
"remainingHydrogenPercentSource", "hydrogenFullCapacityKg", "hydrogenTankCapacityL",
|
|
"hydrogenFullPressureMPa", "hydrogenReferenceTemperatureC", "hydrogenEstimatePressureMPa",
|
|
"hydrogenEstimateTemperatureC", "hydrogenPressureTemperatureSource", "hydrogenCalculationVersion",
|
|
"hydrogenCapacitySource", "hydrogenPercentReason",
|
|
} {
|
|
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", "ESTIMATED", "MAX_SENSOR_AGGREGATE", "EXCEEDS_NOMINAL_FULL_CAPACITY", "REAL_GAS_35MPA_15C_V1", "共同快照", "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")
|
|
}
|
|
}
|
|
|
|
func TestHistoricalHydrogenDocumentationDefinesEvidenceAndFailureBoundaries(t *testing.T) {
|
|
for name, doc := range map[string]string{"OpenAPI": string(openAPISpec), "HTML": string(simpleDocsHTML)} {
|
|
for _, want := range []string{
|
|
"/api/v1/vehicles/hydrogen-remaining/history/query",
|
|
"requestId", "maxTimeDifferenceSeconds", "sourceRecordId",
|
|
"hydrogenCapacityVersion", "sourceDataVersion", "reasonCode", "2026-08-01", "FORBIDDEN", "ERROR",
|
|
"STALE", "429", "Retry-After", "event_time", "历史容量", "非原子",
|
|
"20个不同点", "去重",
|
|
} {
|
|
if !strings.Contains(doc, want) {
|
|
t.Errorf("%s missing history contract evidence/failure boundary %q", name, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOperatingDataDocumentationAndCatalog(t *testing.T) {
|
|
for _, text := range []string{OperatingDataQueryPath, "queryStatus", "dailyDate", "partialSuccessCount", "CARRIED_FORWARD", "Retry-After", "MISSING"} {
|
|
if !strings.Contains(string(openAPISpec), text) {
|
|
t.Errorf("OpenAPI missing %s", text)
|
|
}
|
|
if !strings.Contains(string(simpleDocsHTML), text) {
|
|
t.Errorf("HTML missing %s", text)
|
|
}
|
|
}
|
|
found := false
|
|
for _, product := range dataProducts() {
|
|
if product.Path == OperatingDataQueryPath {
|
|
found = product.Code == "operating_data"
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("aggregate API missing from catalog")
|
|
}
|
|
}
|
|
|
|
func TestOperatingSimpleModeDocumentation(t *testing.T) {
|
|
for _, field := range []string{"responseMode", "SIMPLE", "reportTime", "offline", "dailyHydrogenConsumptionKg", "hydrogenConsumptionPer100KmKg"} {
|
|
if !strings.Contains(string(openAPISpec), field) || !strings.Contains(string(simpleDocsHTML), field) {
|
|
t.Errorf("SIMPLE contract missing %s", field)
|
|
}
|
|
}
|
|
}
|