Files
lingniu-vehicle-ingest/vehicle-data-platform/apps/api/internal/openplatform/standalone_test.go
2026-07-27 16:46:15 +08:00

122 lines
3.9 KiB
Go

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(`<div id="root"></div>`), 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
}{
{"/", `<div id="root"></div>`, "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)
}
}