Files
QWQLwToo 079ee4eaeb
build-winui / winui (push) Has been cancelled
Add server components
2026-06-26 13:28:09 +08:00

317 lines
11 KiB
Go

package config
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"software-download-center/utils"
"github.com/gin-gonic/gin"
)
func TestUpdateInfoContainsOnlyFullInstallerAndMSIX(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTestFile(t, downloadsDir, "YMhut_Box_WinUI_Setup_2.0.7.0.exe", "installer")
writeTestFile(t, downloadsDir, "YMhut_Box_WinUI_2.0.7.0_x64.msix", "msix")
writeTestFile(t, downloadsDir, "winui.appinstaller", "appinstaller")
writeTestFile(t, downloadsDir, "YMhut_Box_WinUI_Setup_2.0.7.0_Light.exe", "light")
writeTestFile(t, filepath.Join(downloadsDir, "incremental"), "YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip", "incremental")
writeTestFile(t, publicDir, "update-info.json", `{
"baselineVersion": "2.0.6.0",
"minIncrementalVersion": "2.0.6.0",
"lightInstaller": {"fileName":"light.exe"},
"packages": [{"id":"external-tools"}],
"incrementals": [{"id":"delta"}],
"messages": {"static":"kept"}
}`)
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", "/update-info.json", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("expected update-info 200, got %d: %s", recorder.Code, recorder.Body.String())
}
var body map[string]interface{}
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
assertAbsent(t, body, "baselineVersion", "minIncrementalVersion", "lightInstaller", "packages", "incrementals", "requiredForFull", "modules")
if body["latestVersion"] != "2.0.7.0" {
t.Fatalf("expected latestVersion 2.0.7.0, got %#v", body["latestVersion"])
}
latest := body["latest"].(map[string]interface{})
assertAbsent(t, latest, "lightInstaller", "incrementals", "packages")
fullInstaller := latest["fullInstaller"].(map[string]interface{})
if fullInstaller["fileName"] != "YMhut_Box_WinUI_Setup_2.0.7.0.exe" {
t.Fatalf("unexpected full installer: %#v", fullInstaller)
}
msix := latest["msix"].(map[string]interface{})
if msix["fileName"] != "YMhut_Box_WinUI_2.0.7.0_x64.msix" {
t.Fatalf("unexpected msix: %#v", msix)
}
appInstaller := latest["appInstaller"].(map[string]interface{})
if appInstaller["fileName"] != "winui.appinstaller" {
t.Fatalf("unexpected appinstaller: %#v", appInstaller)
}
text := recorder.Body.String()
for _, forbidden := range []string{"_Light.exe", "YMhut_Box_Update_", "external-tools", "baselineVersion", "incrementals"} {
if strings.Contains(text, forbidden) {
t.Fatalf("update-info leaked removed field/content %q: %s", forbidden, text)
}
}
}
func TestRemovedDistributionRoutesReturnGone(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
for _, route := range []string{
"/modules.json",
"/api/modules",
"/package-manifest.json",
"/incremental-manifest.json",
"/api/packages/manifest",
"/api/incrementals",
"/api/incrementals/manifest",
"/api/packages/download/YMhut_Box_Tools_2.0.6.0.zip",
"/api/incrementals/download/YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip",
"/packages/YMhut_Box_Tools_2.0.6.0.zip",
"/tool-packages/YMhut_Box_Tools_2.0.6.0.zip",
} {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", route, nil))
if recorder.Code != http.StatusGone {
t.Fatalf("expected %s to return 410, got %d: %s", route, recorder.Code, recorder.Body.String())
}
}
}
func TestCanonicalUpdateInfoRoutesServeDirectlyAndLegacyRoutesRedirect(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
for _, route := range []string{"/update-info.json", "/update-info", "/api/update-info"} {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", route, nil))
if recorder.Code != http.StatusOK {
t.Fatalf("expected %s to return 200, got %d: %s", route, recorder.Code, recorder.Body.String())
}
}
manifestRecorder := httptest.NewRecorder()
router.ServeHTTP(manifestRecorder, httptest.NewRequest("GET", "/manifest.json", nil))
if manifestRecorder.Code != http.StatusOK {
t.Fatalf("expected /manifest.json compatibility response 200, got %d: %s", manifestRecorder.Code, manifestRecorder.Body.String())
}
if manifestRecorder.Header().Get("Deprecation") != "true" {
t.Fatalf("expected /manifest.json to include Deprecation header")
}
if manifestRecorder.Header().Get("X-YMhut-Canonical-Manifest") != "/update-info.json" {
t.Fatalf("expected /manifest.json to point at /update-info.json")
}
for _, route := range []string{"/latest-version.json", "/api/releases/latest", "/latest.json"} {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", route, nil))
if recorder.Code != http.StatusMovedPermanently {
t.Fatalf("expected %s to redirect with 301, got %d", route, recorder.Code)
}
if location := recorder.Header().Get("Location"); location != "/update-info.json" {
t.Fatalf("expected %s Location /update-info.json, got %q", route, location)
}
}
}
func TestDownloadsRouteAllowsOnlySingleInstallerArtifact(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTestFile(t, downloadsDir, "YMhut_Box_WinUI_Setup_2.0.7.0.exe", "installer")
writeTestFile(t, downloadsDir, "YMhut_Box_WinUI_Setup_2.0.7.0_Light.exe", "light")
writeTestFile(t, filepath.Join(downloadsDir, "incremental"), "YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip", "incremental")
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
okRecorder := httptest.NewRecorder()
router.ServeHTTP(okRecorder, httptest.NewRequest("GET", "/downloads/YMhut_Box_WinUI_Setup_2.0.7.0.exe", nil))
if okRecorder.Code != http.StatusOK {
t.Fatalf("expected full installer download 200, got %d: %s", okRecorder.Code, okRecorder.Body.String())
}
if okRecorder.Body.String() != "installer" {
t.Fatalf("unexpected installer body: %q", okRecorder.Body.String())
}
for _, route := range []string{
"/downloads/YMhut_Box_WinUI_Setup_2.0.7.0_Light.exe",
"/downloads/incremental/YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip",
"/downloads/..%2Fsecret.exe",
"/downloads/subdir/file.exe",
} {
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", route, nil))
if recorder.Code != http.StatusForbidden {
t.Fatalf("expected %s to be forbidden, got %d: %s", route, recorder.Code, recorder.Body.String())
}
}
}
func TestAdminAPIRequiresAuthentication(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", "/api/admin/releases/files", nil))
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("expected 401 for unauthenticated admin API, got %d: %s", recorder.Code, recorder.Body.String())
}
if !strings.Contains(recorder.Body.String(), "UNAUTHORIZED") {
t.Fatalf("expected structured unauthorized response, got %s", recorder.Body.String())
}
}
func TestAdminPageShowsUnauthorizedShellWithoutAuth(t *testing.T) {
gin.SetMode(gin.TestMode)
rootDir := t.TempDir()
publicDir := filepath.Join(rootDir, "public")
downloadsDir := filepath.Join(publicDir, "downloads")
viewsDir := filepath.Join(rootDir, "views")
mustMkdirAll(t, downloadsDir)
mustMkdirAll(t, viewsDir)
writeTemplateFiles(t, viewsDir)
router := buildTestRouter(t, rootDir)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest("GET", "/admin/", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("expected admin shell 200, got %d: %s", recorder.Code, recorder.Body.String())
}
if !strings.Contains(recorder.Body.String(), "未授权") {
t.Fatalf("expected admin shell fallback to show unauthorized copy, got %s", recorder.Body.String())
}
}
func TestComparePackageFileNamesSupportsNewAndLegacyInstallerNames(t *testing.T) {
if comparePackageFileNames("YMhut_Box_WinUI_Setup_2.0.7.0.exe", "YMhut_Box_Setup_2.0.6.0.exe") <= 0 {
t.Fatal("expected WinUI 2.0.7.0 installer to be newer than legacy 2.0.6.0 installer")
}
if comparePackageFileNames("YMhut_Box_Setup_2.0.7.0.exe", "YMhut_Box_WinUI_Setup_2.0.7.0.exe") == 0 {
t.Fatal("expected stable tie-breaker for same-version installer names")
}
}
func TestProductsInfoRecognizesMSIXAndSkipsIncrementalSubdirectory(t *testing.T) {
dir := t.TempDir()
writeTestFile(t, dir, "YMhut_Box_WinUI_2.0.7.0_x64.msix", "msix")
writeTestFile(t, filepath.Join(dir, "incremental"), "YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip", "incremental")
products := utils.GetProductsInfo(dir, utils.NewLogger())
if len(products) == 0 {
t.Fatalf("expected MSIX package to be detected")
}
for _, releases := range products {
for _, release := range releases {
if release.FileName == "YMhut_Box_Update_2.0.6.0_to_2.0.7.0.zip" {
t.Fatalf("incremental package from subdirectory should not be listed as product: %#v", products)
}
}
}
}
func buildTestRouter(t *testing.T, rootDir string) *gin.Engine {
t.Helper()
oldWorkingDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(rootDir); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = os.Chdir(oldWorkingDir)
})
router := gin.New()
RegisterRoutes(router, utils.NewLogger())
return router
}
func writeTemplateFiles(t *testing.T, dir string) {
t.Helper()
writeTestFile(t, dir, "index.html", "{{.pageTitle}}")
writeTestFile(t, dir, "404.html", "{{.title}}")
writeTestFile(t, dir, "500.html", "{{.title}}")
}
func writeTestFile(t *testing.T, dir string, name string, content string) {
t.Helper()
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o600); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
func mustMkdirAll(t *testing.T, dir string) {
t.Helper()
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
}
func assertAbsent(t *testing.T, body map[string]interface{}, keys ...string) {
t.Helper()
for _, key := range keys {
if _, ok := body[key]; ok {
t.Fatalf("expected field %s to be absent in %#v", key, body)
}
}
}