Add server components
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-26 13:28:09 +08:00
parent 7ecc6a8923
commit 079ee4eaeb
168 changed files with 37475 additions and 0 deletions
@@ -0,0 +1,132 @@
package web
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"ymhut-box/server/unified-management/internal/config"
)
func TestSetupRouterServesBuiltAssetsAndBlocksBusinessAPI(t *testing.T) {
root := t.TempDir()
setupDist := filepath.Join(root, "setup")
if err := os.MkdirAll(filepath.Join(setupDist, "assets"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(setupDist, "index.html"), []byte(`<!doctype html><script type="module" src="/setup/assets/setup.js"></script><link rel="stylesheet" href="/setup/assets/setup.css">`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(setupDist, "assets", "setup.css"), []byte(`body{}`), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(setupDist, "assets", "setup.js"), []byte(`console.log("setup")`), 0o644); err != nil {
t.Fatal(err)
}
handler := NewSetupRouter(setupConfig(root, setupDist))
for _, item := range []struct {
path string
want int
typ string
}{
{"/setup", http.StatusOK, "text/html"},
{"/setup/assets/setup.css", http.StatusOK, "text/css"},
{"/setup/assets/setup.js", http.StatusOK, "javascript"},
{"/api/client/bootstrap", http.StatusServiceUnavailable, "application/json"},
} {
req := httptest.NewRequest(http.MethodGet, item.path, nil)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != item.want {
t.Fatalf("%s returned %d: %s", item.path, res.Code, res.Body.String())
}
if got := res.Header().Get("Content-Type"); !strings.Contains(got, item.typ) {
t.Fatalf("%s content-type = %q, want %q", item.path, got, item.typ)
}
}
}
func TestSetupSQLiteCompleteCreatesConfigAndDefaultAdmin(t *testing.T) {
root := t.TempDir()
handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))
body := bytes.NewBufferString(`{"provider":"sqlite","baseUrl":"https://update.ymhut.cn","sqlitePath":"storage/unified.sqlite"}`)
req := httptest.NewRequest(http.MethodPost, "/api/setup/complete", body)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("complete returned %d: %s", res.Code, res.Body.String())
}
data, err := os.ReadFile(filepath.Join(root, "config.json"))
if err != nil {
t.Fatal(err)
}
var cfg config.Config
if err := json.Unmarshal(data, &cfg); err != nil {
t.Fatal(err)
}
if !cfg.Initialized || cfg.Database.Provider != "sqlite" {
t.Fatalf("unexpected config: %#v", cfg)
}
if _, err := os.Stat(filepath.Join(root, "storage", "unified.sqlite")); err != nil {
t.Fatal(err)
}
}
func TestSetupSQLiteIgnoresStructuredMySQLDefaults(t *testing.T) {
root := t.TempDir()
handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))
body := bytes.NewBufferString(`{"provider":"sqlite","baseUrl":"https://update.ymhut.cn","sqlitePath":"storage/unified.sqlite","mysql":{"host":"127.0.0.1","port":3306,"database":"ymhut_unified","username":"","password":"","charset":"utf8mb4","parseTime":true,"tls":"false"}}`)
req := httptest.NewRequest(http.MethodPost, "/api/setup/database/test", body)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusOK {
t.Fatalf("sqlite test returned %d: %s", res.Code, res.Body.String())
}
if strings.Contains(res.Body.String(), "mysql username is required") {
t.Fatalf("sqlite test should ignore structured mysql defaults: %s", res.Body.String())
}
}
func TestSetupStructuredMySQLValidationReturnsFailureWithoutSaving(t *testing.T) {
root := t.TempDir()
handler := NewSetupRouter(setupConfig(root, filepath.Join(root, "missing-setup-dist")))
body := bytes.NewBufferString(`{"provider":"mysql","mysql":{"host":"127.0.0.1","port":1,"database":"ymhut","username":"root","password":"secret","charset":"utf8mb4","parseTime":true,"tls":"false"}}`)
req := httptest.NewRequest(http.MethodPost, "/api/setup/database/test", body)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
if res.Code != http.StatusBadGateway {
t.Fatalf("mysql test returned %d, want 502: %s", res.Code, res.Body.String())
}
if _, err := os.Stat(filepath.Join(root, "config.json")); !os.IsNotExist(err) {
t.Fatalf("config should not be written on failed test: %v", err)
}
if strings.Contains(res.Body.String(), "secret") {
t.Fatalf("response leaked password: %s", res.Body.String())
}
}
func setupConfig(root, setupDist string) *config.Config {
return &config.Config{
BaseDir: root,
ConfigPath: filepath.Join(root, "config.json"),
BaseURL: "https://update.ymhut.cn",
StorageDir: filepath.Join(root, "storage"),
SetupWebDir: setupDist,
Database: config.DatabaseConfig{
Provider: "sqlite",
SQLitePath: filepath.Join(root, "storage", "unified.sqlite"),
HealthIntervalSec: 30,
MaxOpenConns: 1,
MaxIdleConns: 1,
},
}
}