feat: complete 2.0.7.12 platform overhaul

This commit is contained in:
2026-08-16 19:33:03 +08:00
parent 73555cd04c
commit c9fa6f7a88
159 changed files with 13243 additions and 2539 deletions
@@ -11,6 +11,7 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"image/color"
"image/png"
"io"
@@ -26,6 +27,7 @@ import (
"testing"
"time"
"ymhut-box/server/unified-management/internal/adminassets"
"ymhut-box/server/unified-management/internal/auth"
"ymhut-box/server/unified-management/internal/config"
"ymhut-box/server/unified-management/internal/db"
@@ -36,6 +38,74 @@ import (
"ymhut-box/server/unified-management/internal/sources"
)
func TestRequestBaseURLUsesTrustedForwardedValues(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://internal:33550/api/client/bootstrap", nil)
req.Header.Set("X-Forwarded-Proto", "HTTPS, http")
req.Header.Set("X-Forwarded-Host", "updates.example.com:8443, internal:33550")
if got := requestBaseURL(req, "https://fallback.example.com/"); got != "https://updates.example.com:8443" {
t.Fatalf("requestBaseURL() = %q", got)
}
}
func TestRequestBaseURLRejectsInvalidForwardedValues(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "https://internal:33550/api/client/bootstrap", nil)
req.Header.Set("X-Forwarded-Proto", "file")
req.Header.Set("X-Forwarded-Host", "user@evil.example")
if got := requestBaseURL(req, "https://fallback.example.com/"); got != "https://internal:33550" {
t.Fatalf("requestBaseURL() = %q", got)
}
req.Host = "bad host"
req.Header.Set("X-Forwarded-Host", "updates.example.com:99999")
if got := requestBaseURL(req, "https://fallback.example.com/"); got != "https://fallback.example.com" {
t.Fatalf("requestBaseURL() with invalid hosts = %q", got)
}
}
func TestPublicSnapshotCacheBoundsHostVariants(t *testing.T) {
service := newPublicSnapshotService(time.Hour)
for index := 0; index < maxPublicSnapshotEntries+20; index++ {
key := fmt.Sprintf("bootstrap|https://host-%d.example.com", index)
service.Get(key, func(time.Time) any { return map[string]any{"ok": true} })
}
if got := len(service.entries); got != maxPublicSnapshotEntries {
t.Fatalf("snapshot cache has %d entries, want %d", got, maxPublicSnapshotEntries)
}
}
func TestPublicSnapshotCacheRemovesExpiredEntries(t *testing.T) {
service := newPublicSnapshotService(time.Millisecond)
service.Get("expired", func(time.Time) any { return map[string]any{"value": 1} })
time.Sleep(2 * time.Millisecond)
service.Get("current", func(time.Time) any { return map[string]any{"value": 2} })
if _, exists := service.entries["expired"]; exists {
t.Fatal("expired snapshot was not removed")
}
}
func TestClientAddressTrustsForwardedIPOnlyFromLoopbackProxy(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://internal/", nil)
req.RemoteAddr = "127.0.0.1:44000"
req.Header.Set("X-Real-IP", "203.0.113.8")
req.Header.Set("X-Forwarded-For", "198.51.100.2, 203.0.113.9")
if got := clientAddress(req); got != "203.0.113.8" {
t.Fatalf("clientAddress() = %q", got)
}
req.Header.Set("X-Real-IP", "not-an-ip")
if got := clientAddress(req); got != "203.0.113.9" {
t.Fatalf("clientAddress() XFF fallback = %q", got)
}
req.RemoteAddr = "192.0.2.25:44000"
req.Header.Set("X-Real-IP", "203.0.113.10")
if got := clientAddress(req); got != "192.0.2.25" {
t.Fatalf("clientAddress() trusted a non-loopback peer: %q", got)
}
}
func TestCompatibilityRoutes(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
@@ -54,6 +124,29 @@ func TestCompatibilityRoutes(t *testing.T) {
}
}
func TestPublicAuthBootstrapDoesNotExposeAdministratorCredentials(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
req := httptest.NewRequest(http.MethodGet, "/api/admin/auth/bootstrap", nil)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("bootstrap returned %d: %s", res.Code, res.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(res.Body.Bytes(), &payload); err != nil {
t.Fatal(err)
}
for _, field := range []string{"defaultUsername", "defaultPassword", "isDefaultPassword"} {
if _, exists := payload[field]; exists {
t.Fatalf("public bootstrap exposed %s: %#v", field, payload)
}
}
if cacheControl := res.Header().Get("Cache-Control"); cacheControl != "no-store" {
t.Fatalf("bootstrap cache control = %q, want no-store", cacheControl)
}
}
func TestClientBootstrapSupportsConditionalCaching(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
@@ -246,6 +339,48 @@ func TestClientBootstrapAndEndpointsShape(t *testing.T) {
}
}
func TestClientReferenceDataSupportsCachingAndBootstrapDiscovery(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
request := httptest.NewRequest(http.MethodGet, "/api/client/reference-data/gpu", nil)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("reference data returned %d: %s", response.Code, response.Body.String())
}
etag := response.Header().Get("ETag")
if etag == "" {
t.Fatal("reference data did not include an ETag")
}
var ranking map[string]any
if err := json.Unmarshal(response.Body.Bytes(), &ranking); err != nil {
t.Fatal(err)
}
if ranking["kind"] != "gpu" || ranking["items"] == nil {
t.Fatalf("unexpected reference data: %#v", ranking)
}
conditional := httptest.NewRequest(http.MethodGet, "/api/client/reference-data/gpu", nil)
conditional.Header.Set("If-None-Match", etag)
conditionalResponse := httptest.NewRecorder()
handler.ServeHTTP(conditionalResponse, conditional)
if conditionalResponse.Code != http.StatusNotModified {
t.Fatalf("conditional reference data returned %d", conditionalResponse.Code)
}
bootstrapRequest := httptest.NewRequest(http.MethodGet, "/api/client/bootstrap", nil)
bootstrapResponse := httptest.NewRecorder()
handler.ServeHTTP(bootstrapResponse, bootstrapRequest)
var bootstrap map[string]any
if err := json.Unmarshal(bootstrapResponse.Body.Bytes(), &bootstrap); err != nil {
t.Fatal(err)
}
if bootstrap["referenceData"] == nil {
t.Fatalf("bootstrap does not advertise reference data: %#v", bootstrap)
}
}
func TestAdminDeleteSourcePublishesCompatibilityJSON(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
@@ -653,7 +788,7 @@ func TestAdminReleasePackageUploadRejectsMissingAndOversizedFile(t *testing.T) {
{name: "missing", include: false, wantStatus: http.StatusBadRequest, wantCode: "FILE_REQUIRED"},
{name: "empty", include: true, data: []byte{}, wantStatus: http.StatusBadRequest, wantCode: "PACKAGE_EMPTY"},
{name: "oversized", include: true, data: bytes.Repeat([]byte{'x'}, (1<<20)+1), wantStatus: http.StatusRequestEntityTooLarge, wantCode: "PACKAGE_TOO_LARGE"},
{name: "unsafe-name", include: true, data: []byte("package"), wantStatus: http.StatusBadRequest, wantCode: "PACKAGE_UPLOAD_FAILED"},
{name: "unsafe-name", include: true, data: []byte("package"), wantStatus: http.StatusBadRequest, wantCode: "PACKAGE_NAME_INVALID"},
} {
t.Run(tc.name, func(t *testing.T) {
handler, cleanup := testRouter(t)
@@ -737,8 +872,10 @@ func TestValidateAdminDiskBuildRejectsMissingReferencedAsset(t *testing.T) {
if err := os.WriteFile(filepath.Join(dir, "index.html"), []byte(`<script type="module" src="/admin/assets/missing.js"></script>`), 0o644); err != nil {
t.Fatal(err)
}
if err := validateAdminDiskBuild(dir); err == nil || !strings.Contains(err.Error(), "assets/missing.js") {
t.Fatalf("validateAdminDiskBuild returned %v, want missing asset error", err)
writeAdminBuildMetadata(t, dir, "assets/missing.js")
status := adminassets.ValidateDisk(dir, "dev")
if status.Ready || !strings.Contains(status.ValidationError, "assets/missing.js") {
t.Fatalf("ValidateDisk returned %#v, want missing asset error", status)
}
}
@@ -753,7 +890,8 @@ func TestIncompleteAdminDiskBuildDoesNotServeDiskAssets(t *testing.T) {
if err := os.WriteFile(filepath.Join(dir, "assets", "stale.js"), []byte(`stale disk asset`), 0o644); err != nil {
t.Fatal(err)
}
r := &router{cfg: &config.Config{AdminWebDir: dir}}
writeAdminBuildMetadata(t, dir, "assets/missing.js")
r := &router{cfg: &config.Config{AdminWebDir: dir, AdminAssetMode: adminassets.ModeDisk}, adminAssets: adminassets.New(adminassets.ModeDisk, dir, "dev")}
req := httptest.NewRequest(http.MethodGet, "/admin/assets/stale.js", nil)
res := httptest.NewRecorder()
r.serveAdminAsset(res, req, "assets/stale.js")
@@ -768,6 +906,17 @@ func TestIncompleteAdminDiskBuildDoesNotServeDiskAssets(t *testing.T) {
}
}
func writeAdminBuildMetadata(t *testing.T, dir, output string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, "admin-build.json"), []byte(`{"buildId":"dev"}`), 0o644); err != nil {
t.Fatal(err)
}
manifest := fmt.Sprintf(`{"src/main.ts":{"file":%q,"isEntry":true}}`, output)
if err := os.WriteFile(filepath.Join(dir, "asset-manifest.json"), []byte(manifest), 0o644); err != nil {
t.Fatal(err)
}
}
func containsAny(value string, needles []string) bool {
for _, needle := range needles {
if strings.Contains(value, needle) {
@@ -913,6 +1062,111 @@ func TestAdminWriteRequiresCSRF(t *testing.T) {
}
}
func TestAdminPageReadRoutesNeverReturnGatewayOrServerErrors(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, _, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
paths := []string{
"/api/admin/dashboard/overview?window=24h",
"/api/admin/feedbacks?page=1&pageSize=20",
"/api/admin/releases",
"/api/admin/releases/notices",
"/api/admin/legacy/update-info",
"/api/admin/legacy/media-types",
"/api/admin/sources",
"/api/admin/sources/check/status",
"/api/admin/endpoints",
"/api/admin/database/status",
"/api/admin/database/sync/jobs/latest",
"/api/admin/system/migration",
"/api/admin/system/branding",
"/api/admin/system/mail/config",
"/api/admin/sync/legacy/preview",
"/api/admin/system/health",
"/api/admin/system/audit?page=1&pageSize=20",
"/api/admin/system/logs?page=1&pageSize=20",
}
for _, path := range paths {
t.Run(path, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, path, nil)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code >= http.StatusInternalServerError {
t.Fatalf("admin page request returned %d: %s", res.Code, res.Body.String())
}
if res.Code == http.StatusUnauthorized || res.Code == http.StatusForbidden {
t.Fatalf("admin page request returned %d: %s", res.Code, res.Body.String())
}
})
}
}
func TestAdminDashboardSnapshotAndHealthExposeDiagnostics(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, _, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
request := func(path string) (*httptest.ResponseRecorder, map[string]any) {
req := httptest.NewRequest(http.MethodGet, path, nil)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("%s returned %d: %s", path, res.Code, res.Body.String())
}
var payload map[string]any
if err := json.Unmarshal(res.Body.Bytes(), &payload); err != nil {
t.Fatal(err)
}
return res, payload
}
first, dashboard := request("/api/admin/dashboard/overview?window=6h")
second, _ := request("/api/admin/dashboard/overview?window=6h")
for _, key := range []string{"sourceRows", "sourceCheckJobs", "generatedAt", "warnings", "heartbeats", "averageLatency", "clientCalls"} {
if _, ok := dashboard[key]; !ok {
t.Fatalf("dashboard missing %s: %#v", key, dashboard)
}
}
if first.Header().Get("X-Admin-Cache") != "miss" || second.Header().Get("X-Admin-Cache") != "hit" {
t.Fatalf("cache headers first=%q second=%q", first.Header().Get("X-Admin-Cache"), second.Header().Get("X-Admin-Cache"))
}
_, healthPayload := request("/api/admin/system/health")
assets, ok := healthPayload["adminAssets"].(map[string]any)
if !ok || assets["manifestStatus"] != "valid" || assets["buildId"] != "dev" {
t.Fatalf("admin asset diagnostics missing: %#v", healthPayload["adminAssets"])
}
}
func TestSecurityMiddlewareRecoversAPIPanicAsJSON(t *testing.T) {
handler := withSecurity(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
panic("simulated handler failure")
}))
req := httptest.NewRequest(http.MethodGet, "/api/admin/system/health", nil)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusInternalServerError {
t.Fatalf("status = %d, want %d: %s", res.Code, http.StatusInternalServerError, res.Body.String())
}
if contentType := res.Header().Get("Content-Type"); !strings.Contains(contentType, "application/json") {
t.Fatalf("content type = %q, want JSON", contentType)
}
if !strings.Contains(res.Body.String(), "INTERNAL_SERVER_ERROR") {
t.Fatalf("response does not contain stable error code: %s", res.Body.String())
}
}
func loginForTest(handler http.Handler) (string, string, error) {
captchaReq := httptest.NewRequest(http.MethodGet, "/api/admin/auth/captcha", nil)
captchaRes := httptest.NewRecorder()
@@ -1091,6 +1345,12 @@ func testRouter(t *testing.T) (http.Handler, func()) {
if err := os.WriteFile(filepath.Join(adminDist, "assets", "admin.js"), []byte(`console.log("admin")`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(adminDist, "admin-build.json"), []byte(`{"buildId":"dev"}`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(adminDist, "asset-manifest.json"), []byte(`{"src/main.ts":{"file":"assets/admin.js","css":["assets/admin.css"],"isEntry":true}}`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(noticeDir, 0o755); err != nil {
t.Fatal(err)
}
@@ -1125,6 +1385,7 @@ func testRouter(t *testing.T) (http.Handler, func()) {
UpdateNoticeDir: noticeDir,
DownloadsDir: filepath.Join(public, "downloads"),
AdminWebDir: adminDist,
AdminAssetMode: adminassets.ModeDisk,
PortalWebDir: portalDist,
SourceCheckSeconds: 3600,
ClientSignatureKey: "ymhut-box-feedback-client-v1",