@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user