Files
Containr/app/backend/internal/api/databases_test.go
T
2026-04-10 12:02:36 +02:00

202 lines
6.7 KiB
Go

package api
import (
"strings"
"testing"
"github.com/docker/go-connections/nat"
)
func TestNormalizeDatabaseType(t *testing.T) {
tests := []struct {
input string
want string
}{
{input: "postgres", want: "postgresql"},
{input: "postgresql", want: "postgresql"},
{input: "pg", want: "postgresql"},
{input: "redis", want: "redis"},
{input: "dragonflydb", want: "dragonfly"},
{input: "dragonfly", want: "dragonfly"},
{input: "mysql", want: "mysql"},
{input: "mariadb", want: "mariadb"},
{input: "mongo", want: "mongodb"},
{input: "mongodb", want: "mongodb"},
{input: "clickhouse", want: "clickhouse"},
}
for _, tt := range tests {
got := normalizeDatabaseType(tt.input)
if got != tt.want {
t.Fatalf("normalizeDatabaseType(%q) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestDatabaseConnectionURLAndDefaultVersion(t *testing.T) {
handler := &DatabaseHandler{}
cases := []struct {
dbType string
urlContains string
version string
}{
{dbType: "postgresql", urlContains: "postgresql://", version: "16.2"},
{dbType: "redis", urlContains: "redis://", version: "7.2"},
{dbType: "dragonfly", urlContains: "redis://", version: "1.24"},
{dbType: "mysql", urlContains: "mysql://", version: "8.4"},
{dbType: "mariadb", urlContains: "mysql://", version: "11.4"},
{dbType: "mongodb", urlContains: "mongodb://", version: "7.0"},
{dbType: "clickhouse", urlContains: "http://", version: "24.8"},
}
for _, tc := range cases {
url := handler.generateConnectionURL(DatabaseService{
Type: tc.dbType,
Name: "example",
})
if url == "" {
t.Fatalf("generateConnectionURL returned empty for type %q", tc.dbType)
}
if len(tc.urlContains) > 0 && !strings.HasPrefix(url, tc.urlContains) {
t.Fatalf("generateConnectionURL(%q) = %q, expected prefix %q", tc.dbType, url, tc.urlContains)
}
gotVersion := handler.getDefaultVersion(tc.dbType)
if gotVersion != tc.version {
t.Fatalf("getDefaultVersion(%q) = %q, want %q", tc.dbType, gotVersion, tc.version)
}
}
}
func TestBuildDatabaseRuntimePlanSupportsAllTypes(t *testing.T) {
cases := []struct {
dbType string
expectedImage string
expectedPort nat.Port
urlPrefix string
}{
{dbType: "postgresql", expectedImage: "postgres:16-alpine", expectedPort: nat.Port("5432/tcp"), urlPrefix: "postgresql://"},
{dbType: "redis", expectedImage: "redis:7-alpine", expectedPort: nat.Port("6379/tcp"), urlPrefix: "redis://"},
{dbType: "dragonfly", expectedImage: "docker.dragonflydb.io/dragonflydb/dragonfly:latest", expectedPort: nat.Port("6379/tcp"), urlPrefix: "redis://"},
{dbType: "mysql", expectedImage: "mysql:8.4", expectedPort: nat.Port("3306/tcp"), urlPrefix: "mysql://"},
{dbType: "mariadb", expectedImage: "mariadb:11", expectedPort: nat.Port("3306/tcp"), urlPrefix: "mysql://"},
{dbType: "mongodb", expectedImage: "mongo:7", expectedPort: nat.Port("27017/tcp"), urlPrefix: "mongodb://"},
{dbType: "clickhouse", expectedImage: "clickhouse/clickhouse-server:24.8", expectedPort: nat.Port("8123/tcp"), urlPrefix: "http://"},
}
for _, tc := range cases {
plan, err := buildDatabaseRuntimePlan(tc.dbType, "My DB", nil)
if err != nil {
t.Fatalf("buildDatabaseRuntimePlan(%q) returned error: %v", tc.dbType, err)
}
if plan.Image != tc.expectedImage {
t.Fatalf("buildDatabaseRuntimePlan(%q) image=%q want=%q", tc.dbType, plan.Image, tc.expectedImage)
}
if plan.Port != tc.expectedPort {
t.Fatalf("buildDatabaseRuntimePlan(%q) port=%q want=%q", tc.dbType, plan.Port, tc.expectedPort)
}
conn := plan.ConnectionURL("12345")
if !strings.HasPrefix(conn, tc.urlPrefix) {
t.Fatalf("buildDatabaseRuntimePlan(%q) connectionURL=%q expected prefix=%q", tc.dbType, conn, tc.urlPrefix)
}
}
}
func TestBuildDatabaseRuntimePlanHonorsRuntimeVariables(t *testing.T) {
plan, err := buildDatabaseRuntimePlan("postgresql", "mydb", map[string]string{
"POSTGRES_USER": "template_user",
"POSTGRES_PASSWORD": "template_pass",
"POSTGRES_DB": "template_db",
})
if err != nil {
t.Fatalf("buildDatabaseRuntimePlan returned error: %v", err)
}
envJoined := strings.Join(plan.Env, " ")
if !strings.Contains(envJoined, "POSTGRES_USER=template_user") {
t.Fatalf("expected POSTGRES_USER override in env, got: %s", envJoined)
}
if !strings.Contains(envJoined, "POSTGRES_PASSWORD=template_pass") {
t.Fatalf("expected POSTGRES_PASSWORD override in env, got: %s", envJoined)
}
if !strings.Contains(envJoined, "POSTGRES_DB=template_db") {
t.Fatalf("expected POSTGRES_DB override in env, got: %s", envJoined)
}
url := plan.ConnectionURL("54321")
if !strings.Contains(url, "template_user:template_pass") || !strings.Contains(url, "/template_db") {
t.Fatalf("expected connection URL to reflect runtime overrides, got: %s", url)
}
}
func TestGenerateDatabaseIDIncludesRandomSuffix(t *testing.T) {
id1 := generateDatabaseID("Main DB")
id2 := generateDatabaseID("Main DB")
if id1 == id2 {
t.Fatalf("expected generateDatabaseID to produce unique values, got identical id %q", id1)
}
if !strings.HasPrefix(id1, "db_") || !strings.Contains(id1, "_main_db_") {
t.Fatalf("unexpected database id format: %s", id1)
}
}
func TestManagedDatabaseNamesAreBoundedAndStable(t *testing.T) {
id := "db_1234567890_this-is-a-very-very-very-very-very-long-name"
containerName := managedDatabaseContainerName(id)
volumeName := managedDatabaseVolumeName(id)
if containerName == "" || volumeName == "" {
t.Fatal("expected non-empty managed runtime names")
}
if len(containerName) > 63 {
t.Fatalf("container name too long: %d", len(containerName))
}
if len(volumeName) > 63 {
t.Fatalf("volume name too long: %d", len(volumeName))
}
if !strings.HasPrefix(containerName, "containr-db-") {
t.Fatalf("unexpected container prefix: %s", containerName)
}
if !strings.HasPrefix(volumeName, "containr-db-vol-") {
t.Fatalf("unexpected volume prefix: %s", volumeName)
}
}
func TestSanitizeBackupArchivePath(t *testing.T) {
tests := []struct {
input string
want string
}{
{input: "backup_abc.tar.gz", want: "backup_abc.tar.gz"},
{input: "/tmp/../../danger", want: "tmp_danger.tar.gz"},
{input: " weird name ", want: "weird_name.tar.gz"},
{input: "", want: "backup.tar.gz"},
}
for _, tt := range tests {
got := sanitizeBackupArchivePath(tt.input)
if got != tt.want {
t.Fatalf("sanitizeBackupArchivePath(%q) = %q, want %q", tt.input, got, tt.want)
}
}
}
func TestHumanReadableBytes(t *testing.T) {
tests := []struct {
size int64
want string
}{
{size: 0, want: "0 B"},
{size: 1024, want: "1.00 KB"},
{size: 1048576, want: "1.00 MB"},
}
for _, tt := range tests {
got := humanReadableBytes(tt.size)
if got != tt.want {
t.Fatalf("humanReadableBytes(%d) = %q, want %q", tt.size, got, tt.want)
}
}
}