feat: expand vehicle data platform capabilities
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user