更新 update 门户站点界面和后台功能
build-winui / winui (push) Waiting to run

This commit is contained in:
QWQLwToo
2026-06-27 18:09:11 +08:00
parent 2513eb2903
commit 962a2f2143
56 changed files with 4564 additions and 714 deletions
@@ -130,12 +130,111 @@ func TestClientBootstrapAndEndpointsShape(t *testing.T) {
}
continue
}
if path == "/api/client/bootstrap" {
branding, ok := payload["branding"].(map[string]any)
if !ok {
t.Fatalf("bootstrap missing branding: %#v", payload)
}
if branding["developerName"] != "YMhut" || branding["feedbackEmail"] != "support@ymhut.cn" {
t.Fatalf("unexpected branding defaults: %#v", branding)
}
}
if payload["ok"] != true {
t.Fatalf("%s missing ok=true: %#v", path, payload)
}
}
}
func TestAdminDeleteSourcePublishesCompatibilityJSON(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, csrf, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodDelete, "/api/admin/sources/demo", nil)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
req.Header.Set("X-CSRF-Token", csrf)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("delete source returned %d: %s", res.Code, res.Body.String())
}
mediaReq := httptest.NewRequest(http.MethodGet, "/media-types.json", nil)
mediaRes := httptest.NewRecorder()
handler.ServeHTTP(mediaRes, mediaReq)
if mediaRes.Code != http.StatusOK {
t.Fatalf("media-types returned %d: %s", mediaRes.Code, mediaRes.Body.String())
}
if strings.Contains(mediaRes.Body.String(), `"demo"`) {
t.Fatalf("deleted source leaked into media-types.json: %s", mediaRes.Body.String())
}
updateReq := httptest.NewRequest(http.MethodGet, "/update-info.json", nil)
updateRes := httptest.NewRecorder()
handler.ServeHTTP(updateRes, updateReq)
if updateRes.Code != http.StatusOK {
t.Fatalf("update-info returned %d: %s", updateRes.Code, updateRes.Body.String())
}
var updatePayload map[string]any
if err := json.Unmarshal(updateRes.Body.Bytes(), &updatePayload); err != nil {
t.Fatal(err)
}
assertJSONKeys(t, "update-info after source delete", updatePayload, []string{"app_version", "manifest_version", "packages", "modules"})
}
func TestAdminAuditPaginationAndBranding(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, csrf, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 40; i++ {
body := strings.NewReader(`{"developerName":"YMhut","feedbackEmail":"support@ymhut.cn"}`)
req := httptest.NewRequest(http.MethodPost, "/api/admin/system/branding", body)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
req.Header.Set("X-CSRF-Token", csrf)
req.Header.Set("Content-Type", "application/json")
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("branding save %d returned %d: %s", i, res.Code, res.Body.String())
}
}
req := httptest.NewRequest(http.MethodGet, "/api/admin/system/audit?page=1&perPage=35&type=system.branding.saved", nil)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("audit returned %d: %s", res.Code, res.Body.String())
}
var payload struct {
Items []any `json:"items"`
Page struct {
Total int `json:"total"`
Page int `json:"page"`
PerPage int `json:"perPage"`
} `json:"page"`
}
if err := json.Unmarshal(res.Body.Bytes(), &payload); err != nil {
t.Fatal(err)
}
if payload.Page.Page != 1 || payload.Page.PerPage != 35 {
t.Fatalf("unexpected audit page metadata: %#v", payload.Page)
}
if payload.Page.Total < 40 {
t.Fatalf("expected at least 40 branding audit records, got %d", payload.Page.Total)
}
if len(payload.Items) > 35 {
t.Fatalf("expected at most 35 audit items, got %d", len(payload.Items))
}
}
func TestLegacyFeedbackStatusDTOContract(t *testing.T) {
payload := legacyFeedbackStatus(db.Feedback{
Code: "FB-20260626-ABCDEF",
@@ -419,6 +518,80 @@ func TestAdminLegacyRequiresAuth(t *testing.T) {
}
}
func TestAdminLegacyUpdateInfoSyncsReleaseNotice(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, csrf, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
body, _ := json.Marshal(map[string]string{
"raw": `{"app_version":"2.0.7.5","title":"YMhut Box 2.0.7.5","message":"随机放映室优化","release_notes":"修复图片源和全屏预览"}`,
})
req := httptest.NewRequest(http.MethodPut, "/api/admin/legacy/update-info", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-CSRF-Token", csrf)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("save update-info returned %d: %s", res.Code, res.Body.String())
}
listReq := httptest.NewRequest(http.MethodGet, "/api/admin/releases/notices", nil)
listReq.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
listRes := httptest.NewRecorder()
handler.ServeHTTP(listRes, listReq)
if listRes.Code != http.StatusOK {
t.Fatalf("notice list returned %d: %s", listRes.Code, listRes.Body.String())
}
var payload struct {
Items []struct {
Version string `json:"version"`
Title string `json:"title"`
} `json:"items"`
}
if err := json.Unmarshal(listRes.Body.Bytes(), &payload); err != nil {
t.Fatal(err)
}
found := false
for _, item := range payload.Items {
if item.Version == "2.0.7.5" && item.Title == "YMhut Box 2.0.7.5" {
found = true
}
}
if !found {
t.Fatalf("synced release notice not found: %#v", payload.Items)
}
}
func TestAdminLegacyValidationErrorIsChinese(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
session, csrf, err := loginForTest(handler)
if err != nil {
t.Fatal(err)
}
body, _ := json.Marshal(map[string]string{"raw": `{"message":"missing version and title"}`})
req := httptest.NewRequest(http.MethodPost, "/api/admin/legacy/update-info/validate", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-CSRF-Token", csrf)
req.AddCookie(&http.Cookie{Name: auth.SessionCookie, Value: session})
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusBadRequest {
t.Fatalf("expected validation failure, got %d: %s", res.Code, res.Body.String())
}
if strings.Contains(res.Body.String(), "update-info requires app_version or title") {
t.Fatalf("english validation leaked: %s", res.Body.String())
}
if !strings.Contains(res.Body.String(), "更新 JSON 需要填写 app_version 或 title") {
t.Fatalf("missing chinese validation message: %s", res.Body.String())
}
}
func TestAdminWriteRequiresCSRF(t *testing.T) {
handler, cleanup := testRouter(t)
defer cleanup()
@@ -599,9 +772,12 @@ func testRouter(t *testing.T) (http.Handler, func()) {
})
mustWriteJSON(t, filepath.Join(noticeDir, "2.0.0.json"), map[string]any{"app_version": "2.0.0", "title": "YMhut Box 2.0.0", "release_notes": "Initial release", "release_notes_md": "## Initial"})
cfg := &config.Config{
BaseDir: root,
ConfigPath: filepath.Join(root, "config.json"),
Listen: ":0",
BaseURL: "https://update.ymhut.cn",
StorageDir: filepath.Join(root, "storage"),
DataDir: filepath.Join(root, "data"),
UpdatePublicDir: public,
UpdateNoticeDir: noticeDir,
DownloadsDir: filepath.Join(public, "downloads"),