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,250 @@
package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestLoadJSONConfig(t *testing.T) {
dir := t.TempDir()
content := `{
"listen": ":9090",
"admin_password": "secret",
"client_signature_key": "client-key",
"package_encryption_key": "package-key",
"timestamp_window_seconds": 90,
"max_request_bytes": 123,
"max_package_bytes": 456,
"storage_dir": "./data",
"database_path": "./data/feedback.sqlite",
"mail": {
"host": "smtp.example.com",
"port": 587,
"secure": "starttls",
"username": "u",
"password": "p",
"from_address": "from@example.com",
"from_name": "Feedback",
"developer_address": "dev@example.com",
"timeout_seconds": 7
}
}`
if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if cfg.Listen != ":9090" || cfg.AdminPassword != "secret" || cfg.ClientSignatureKey != "client-key" {
t.Fatalf("unexpected top-level config: %+v", cfg)
}
if cfg.StorageDir != filepath.Join(dir, "data") {
t.Fatalf("storage dir was not normalized: %q", cfg.StorageDir)
}
if cfg.Mail.Host != "smtp.example.com" || cfg.Mail.Port != 587 || cfg.Mail.Secure != "starttls" {
t.Fatalf("unexpected mail config: %+v", cfg.Mail)
}
}
func TestConfigTxtTakesPriorityOverJSON(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(`{"admin_password":"json-secret"}`), 0o600); err != nil {
t.Fatal(err)
}
content := `<?php
$adminPassword = 'txt-secret';
return [
'listen' => ':9191',
'admin_password' => $adminPassword,
'storage_dir' => __DIR__ . '/storage',
'database_path' => __DIR__ . '/storage/feedback.sqlite',
];`
if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if cfg.AdminPassword != "txt-secret" || cfg.Listen != ":9191" {
t.Fatalf("config.txt should win over config.json, got password=%q listen=%q", cfg.AdminPassword, cfg.Listen)
}
}
func TestLoadConfigTxtLegacyArrayConfig(t *testing.T) {
dir := t.TempDir()
content := `<?php
$adminPassword = 'legacy-secret';
return [
'listen' => ':7070',
'admin_password_hash' => '',
'admin_password' => $adminPassword,
'client_signature_key' => 'legacy-client',
'package_encryption_key' => 'legacy-package',
'timestamp_window_seconds' => 120,
'max_request_bytes' => 12 * 1024 * 1024,
'max_package_bytes' => 10 * 1024 * 1024,
'storage_dir' => __DIR__ . '/storage',
'database_path' => __DIR__ . '/storage/feedback.sqlite',
'mail' => [
'host' => 'mail.example.com',
'port' => 465,
'secure' => 'ssl',
'username' => 'sender@example.com',
'password' => 'mail-secret',
'from_address' => 'sender@example.com',
'from_name' => 'YMhut Box Feedback',
'developer_address' => 'developer@example.com',
'timeout_seconds' => 20,
],
];`
if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if cfg.Listen != ":7070" || cfg.AdminPassword != "legacy-secret" || cfg.ClientSignatureKey != "legacy-client" || cfg.PackageEncryptionKey != "legacy-package" {
t.Fatalf("unexpected legacy config: %+v", cfg)
}
if cfg.MaxRequestBytes != 12*1024*1024 || cfg.MaxPackageBytes != 10*1024*1024 {
t.Fatalf("legacy integer expressions were not evaluated: %+v", cfg)
}
if cfg.StorageDir != filepath.Join(dir, "storage") || cfg.DatabasePath != filepath.Join(dir, "storage", "feedback.sqlite") {
t.Fatalf("legacy __DIR__ paths were not normalized: %q %q", cfg.StorageDir, cfg.DatabasePath)
}
if cfg.Mail.Password != "mail-secret" || cfg.Mail.DeveloperAddress != "developer@example.com" {
t.Fatalf("unexpected legacy mail config: %+v", cfg.Mail)
}
}
func TestLoadConfigTxtEnhancedSettings(t *testing.T) {
dir := t.TempDir()
content := `<?php
return [
'listen' => ':7071',
'admin_password' => 'secret',
'storage_dir' => __DIR__ . '/storage',
'database_path' => __DIR__ . '/storage/feedback.sqlite',
'submission_per_minute' => 9,
'max_zip_files' => 12,
'max_decompressed_bytes' => 1024 * 1024,
'backup_dir' => __DIR__ . '/storage/backups',
'webhooks' => [
[
'name' => 'ops',
'url' => 'https://example.com/hook',
'secret' => 'hook-secret',
'enabled' => true,
'events' => ['feedback.created', 'mail.failed'],
'timeout_seconds' => 3,
'max_retries' => 1,
],
],
];`
if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if cfg.RateLimit.SubmissionPerMinute != 9 || cfg.UploadGuard.MaxZipFiles != 12 || cfg.UploadGuard.MaxDecompressedBytes != 1024*1024 {
t.Fatalf("enhanced settings were not parsed: %+v", cfg)
}
if cfg.Backup.Dir != filepath.Join(dir, "storage", "backups") {
t.Fatalf("backup dir was not normalized: %q", cfg.Backup.Dir)
}
if len(cfg.Webhooks) != 1 || cfg.Webhooks[0].Name != "ops" || cfg.Webhooks[0].Events[1] != "mail.failed" || cfg.Webhooks[0].MaxRetries != 1 {
t.Fatalf("webhooks were not parsed: %+v", cfg.Webhooks)
}
}
func TestLoadConfigTxtDatabaseSettings(t *testing.T) {
dir := t.TempDir()
content := `<?php
return [
'storage_dir' => __DIR__ . '/storage',
'database_path' => __DIR__ . '/storage/feedback.sqlite',
'database_provider' => 'postgres',
'database_host' => 'db.example.com',
'database_port' => 5433,
'database_name' => 'feedback',
'database_user' => 'feedback_user',
'database_password' => 'db-secret',
'database_ssl_mode' => 'require',
'database_failover_enabled' => true,
'database_sync_enabled' => true,
'database_sync_interval_seconds' => 60,
'database_sync_batch_size' => 25,
];`
if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
t.Fatal(err)
}
cfg, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if cfg.Database.Provider != "postgres" || cfg.Database.Host != "db.example.com" || cfg.Database.Port != 5433 || cfg.Database.Password != "db-secret" {
t.Fatalf("database config was not parsed: %+v", cfg.Database)
}
if cfg.Database.SQLitePath != filepath.Join(dir, "storage", "feedback.sqlite") || cfg.DatabasePath != cfg.Database.SQLitePath {
t.Fatalf("sqlite path compatibility failed: %q %q", cfg.Database.SQLitePath, cfg.DatabasePath)
}
if !cfg.Database.FailoverEnabled || !cfg.Database.Sync.Enabled || cfg.Database.Sync.IntervalSeconds != 60 || cfg.Database.Sync.BatchSize != 25 {
t.Fatalf("database sync settings were not parsed: %+v", cfg.Database)
}
}
func TestSaveConfigCreatesReloadableConfigTxt(t *testing.T) {
dir := t.TempDir()
cfg := Default(dir)
cfg.Listen = ":9099"
cfg.Database.Provider = "mysql"
cfg.Database.Host = "mysql.example.com"
cfg.Database.Name = "feedback"
cfg.Database.User = "feedback_user"
cfg.Database.Password = "mysql-secret"
cfg.Webhooks = []WebhookConfig{{Name: "ops", URL: "https://example.com/hook", Secret: "hook-secret", Enabled: true, Events: []string{"feedback.created"}, TimeoutSeconds: 4, MaxRetries: 2}}
if err := Save(cfg); err != nil {
t.Fatal(err)
}
saved, err := os.ReadFile(filepath.Join(dir, "config.txt"))
if err != nil {
t.Fatal(err)
}
text := string(saved)
if strings.Contains(text, dir) {
t.Fatalf("saved config should not contain deploy-root absolute paths: %s", text)
}
for _, expected := range []string{
"'storage_dir' => 'storage'",
"'database_path' => 'storage/feedback.sqlite'",
"'database_sqlite_path' => 'storage/feedback.sqlite'",
"'backup_dir' => 'storage/backups'",
} {
if !strings.Contains(text, expected) {
t.Fatalf("saved config is missing portable path %q: %s", expected, text)
}
}
loaded, err := Load(dir)
if err != nil {
t.Fatal(err)
}
if loaded.Listen != ":9099" || loaded.Database.Provider != "mysql" || loaded.Database.Password != "mysql-secret" {
t.Fatalf("saved config did not reload: %+v", loaded.Database)
}
if len(loaded.Webhooks) != 1 || loaded.Webhooks[0].Secret != "hook-secret" {
t.Fatalf("saved webhooks did not reload: %+v", loaded.Webhooks)
}
}