package openplatform import ( "encoding/json" "errors" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "github.com/DATA-DOG/go-sqlmock" ) func TestPortalDecodeDetail(t *testing.T) { tests := []struct { name string err error want string }{ {name: "empty", err: io.EOF, want: "请求内容不能为空"}, {name: "syntax", err: &json.SyntaxError{Offset: 2}, want: "请求内容不是有效的 JSON"}, {name: "type", err: &json.UnmarshalTypeError{Field: "validFrom"}, want: "字段 validFrom 的数据类型不正确"}, {name: "unknown", err: errors.New(`json: unknown field "appId"`), want: `请求包含未支持的字段 "appId"`}, {name: "other", err: errors.New("unexpected"), want: "请求内容无法解析"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := portalDecodeDetail(test.err); got != test.want { t.Fatalf("portalDecodeDetail() = %q, want %q", got, test.want) } }) } } func TestStandaloneServerServesPortalDocsAndCatalog(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } defer db.Close() staticDir := t.TempDir() if err := os.WriteFile(filepath.Join(staticDir, "index.html"), []byte(`
`), 0o600); err != nil { t.Fatal(err) } handler := NewStandaloneServer(db, StandaloneConfig{ StaticDir: staticDir, SessionTTL: time.Hour, RequestTimeout: time.Second, Release: "test-release", }) tests := []struct { path string content string contentType string }{ {"/", ``, "text/html"}, {"/healthz", `"service":"open-platform-api"`, "application/json"}, {"/portal-api/catalog", `"daily_hydrogen"`, "application/json"}, {"/open-api/openapi.yaml", "openapi: 3.0.3", "application/yaml"}, } for _, item := range tests { recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, item.path, nil)) if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), item.content) { t.Fatalf("path=%s status=%d body=%s", item.path, recorder.Code, recorder.Body.String()) } if value := recorder.Header().Get("Content-Type"); !strings.Contains(value, item.contentType) { t.Fatalf("path=%s content-type=%s", item.path, value) } if recorder.Header().Get("X-Frame-Options") != "DENY" { t.Fatalf("path=%s missing security headers", item.path) } } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestStandalonePortalSessionRequiresBearerToken(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatal(err) } defer db.Close() handler := NewStandaloneServer(db, StandaloneConfig{RequestTimeout: time.Second}) recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/portal-api/session", nil)) if recorder.Code != http.StatusUnauthorized || !strings.Contains(recorder.Body.String(), "UNAUTHORIZED") { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestPortalCredentialValidation(t *testing.T) { input := PortalUserInput{ Username: "partner.dev", DisplayName: "合作伙伴开发者", Password: "StrongPass2026", Status: "enabled", ValidFrom: "2026-07-01T00:00:00+08:00", ValidTo: "2027-07-01T00:00:00+08:00", } if _, _, err := validatePortalUserInput(&input, true); err != nil { t.Fatalf("valid portal user rejected: %v", err) } if err := validatePortalPassword("weak-password"); err == nil { t.Fatal("weak password should be rejected") } if !validPortalRole("owner") || !validPortalRole("developer") || !validPortalRole("viewer") || validPortalRole("admin") { t.Fatal("unexpected portal role validation") } raw, hash, err := newPortalSessionToken() if err != nil || len(raw) != 64 || hash == [32]byte{} { t.Fatalf("invalid session token raw=%d err=%v", len(raw), err) } }