Files
3x-ui/internal/web/service/backup_filename_test.go
T
MHSanaei a7e959ff49 feat(backup): name DB backup files after the server address
Panel downloads and Telegram backups were always named x-ui.db / x-ui.dump, so backups from different servers were indistinguishable. Name them after the panel address instead: the configured web domain, or the public IP (IPv4 before IPv6) when no domain is set, falling back to x-ui.

Centralized in ServerService.BackupFilename(); host is sanitized to the getDb filename charset (IPv6 colons become hyphens) and read from the mutex-guarded LastStatus to avoid racing the status goroutine.
2026-06-22 21:55:58 +02:00

39 lines
1.1 KiB
Go

package service
import (
"regexp"
"testing"
)
// getDb (controller) only accepts a Content-Disposition filename matching this
// pattern, so every sanitizeBackupHost output must satisfy it.
var backupFilenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
func TestSanitizeBackupHost(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{"domain", "panel.example.com", "panel.example.com"},
{"ipv4", "203.0.113.5", "203.0.113.5"},
{"ipv6", "2001:db8::1", "2001-db8--1"},
{"ipv6 bracketed", "[fe80::1]", "fe80--1"},
{"domain with port", "example.com:8443", "example.com-8443"},
{"trims edge dots and dashes", "-.example.com.-", "example.com"},
{"empty falls back", "", "x-ui"},
{"all invalid falls back", ":::", "x-ui"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := sanitizeBackupHost(tc.in)
if got != tc.want {
t.Errorf("sanitizeBackupHost(%q) = %q, want %q", tc.in, got, tc.want)
}
if !backupFilenameRegex.MatchString(got) {
t.Errorf("sanitizeBackupHost(%q) = %q, not a valid download filename", tc.in, got)
}
})
}
}