package main import ( "reflect" "strings" "testing" ) func TestParseEnvLine(t *testing.T) { t.Parallel() tests := []struct { name string line string wantKey string wantValue string wantOK bool }{ {name: "simple", line: "FOO=bar", wantKey: "FOO", wantValue: "bar", wantOK: true}, {name: "trims", line: " FOO = bar ", wantKey: "FOO", wantValue: "bar", wantOK: true}, {name: "comment", line: "# comment", wantOK: false}, {name: "empty", line: "", wantOK: false}, {name: "no equals", line: "FOO", wantOK: false}, {name: "invalid key digit", line: "1FOO=bar", wantOK: false}, {name: "invalid key dash", line: "FOO-BAR=1", wantOK: false}, {name: "underscore ok", line: "_FOO=bar", wantKey: "_FOO", wantValue: "bar", wantOK: true}, {name: "digit ok after", line: "FOO1=2", wantKey: "FOO1", wantValue: "2", wantOK: true}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { key, value, ok := parseEnvLine(tt.line) if ok != tt.wantOK { t.Fatalf("ok=%v want %v", ok, tt.wantOK) } if key != tt.wantKey || value != tt.wantValue { t.Fatalf("got key=%q value=%q want key=%q value=%q", key, value, tt.wantKey, tt.wantValue) } }) } } func TestIsEnvKey(t *testing.T) { t.Parallel() tests := []struct { key string want bool }{ {key: "A", want: true}, {key: "_A", want: true}, {key: "A1", want: true}, {key: "A_B2", want: true}, {key: "", want: false}, {key: "1A", want: false}, {key: "A-B", want: false}, {key: "A B", want: false}, {key: "A.B", want: false}, } for _, tt := range tests { tt := tt t.Run(tt.key, func(t *testing.T) { if got := isEnvKey(tt.key); got != tt.want { t.Fatalf("got %v want %v", got, tt.want) } }) } } func TestIsEnvLine(t *testing.T) { t.Parallel() tests := []struct { line string want bool }{ {line: "", want: true}, {line: " ", want: true}, {line: "FOO=bar", want: true}, {line: "FOO = bar", want: false}, {line: "1FOO=bar", want: false}, {line: "NO_EQUALS", want: false}, } for _, tt := range tests { tt := tt t.Run(tt.line, func(t *testing.T) { if got := isEnvLine(tt.line); got != tt.want { t.Fatalf("got %v want %v", got, tt.want) } }) } } func TestApplyEnvAssignments(t *testing.T) { t.Parallel() lines := []string{ "# comment", "FOO=old", "BAR=keep", "INVALID LINE", "BAZ=gone", "FOO=duplicate", } assignments := []envAssignment{ {key: "FOO", value: "new"}, {key: "BAZ", value: "zzz"}, {key: "NEWKEY", value: "123"}, } want := []string{ "# comment", "FOO=new", "BAR=keep", "INVALID LINE", "BAZ=zzz", "NEWKEY=123", } got := applyEnvAssignments(lines, assignments) if !reflect.DeepEqual(got, want) { t.Fatalf("got %#v want %#v", got, want) } } func TestRemoveEnvKeys(t *testing.T) { t.Parallel() lines := []string{ "KEEP=1", "DROP=2", "# comment", "INVALID LINE", "DROP=3", "ALSO=4", } keys := []string{"DROP", "MISSING"} want := []string{ "KEEP=1", "# comment", "INVALID LINE", "ALSO=4", } got := removeEnvKeys(lines, keys) if !reflect.DeepEqual(got, want) { t.Fatalf("got %#v want %#v", got, want) } } func TestShellQuote(t *testing.T) { t.Parallel() tests := []struct { name string input string want string }{ {name: "empty", input: "", want: "''"}, {name: "simple", input: "abc", want: "'abc'"}, {name: "spaces", input: "a b", want: "'a b'"}, {name: "single quote", input: "a'b", want: "'a'\"'\"'b'"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { if got := shellQuote(tt.input); got != tt.want { t.Fatalf("got %q want %q", got, tt.want) } }) } } func TestResolveVolumePath(t *testing.T) { t.Parallel() tests := []struct { name string template string baseDir string service string wantResult string }{ {name: "empty uses base", template: "", baseDir: "/srv/pb", service: "svc", wantResult: "/srv/pb"}, {name: "absolute path", template: "/data/{service}", baseDir: "/srv/pb", service: "svc", wantResult: "/data/svc"}, {name: "relative path", template: "data/{service}", baseDir: "/srv/pb", service: "svc", wantResult: "/srv/pb/data/svc"}, {name: "simple relative", template: "custom", baseDir: "/srv/pb", service: "svc", wantResult: "/srv/pb/custom"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { if got := resolveVolumePath(tt.template, tt.baseDir, tt.service); got != tt.wantResult { t.Fatalf("got %q want %q", got, tt.wantResult) } }) } } func TestTranslateMachineArch(t *testing.T) { t.Parallel() tests := []struct { name string input string want string wantErr bool }{ {name: "x86_64", input: " x86_64 ", want: "amd64"}, {name: "amd64", input: "amd64", want: "amd64"}, {name: "i686", input: "i686", want: "386"}, {name: "armv7l", input: "armv7l", want: "armv7"}, {name: "armv6l", input: "armv6l", want: "arm"}, {name: "aarch64", input: "aarch64", want: "arm64"}, {name: "arm64", input: "arm64", want: "arm64"}, {name: "unknown", input: "mips", wantErr: true}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { got, err := translateMachineArch(tt.input) if tt.wantErr { if err == nil { t.Fatalf("expected error") } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got != tt.want { t.Fatalf("got %q want %q", got, tt.want) } }) } } func TestSystemdScript(t *testing.T) { t.Parallel() serviceDir := "/srv/pb" envFile := "/etc/pb/env" serviceName := "demo" script := systemdScript(serviceDir, envFile, serviceName) required := []string{ "/etc/systemd/system/pb@.service", "StandardOutput = append:" + serviceDir + "/%i.log", "StandardError = append:" + serviceDir + "/%i.log", "WorkingDirectory = " + serviceDir, "EnvironmentFile = " + envFile, "ExecStart = " + serviceDir + "/pocketbase serve --http=127.0.0.1:${PORT} --hooksWatch=false --dir=${DATA_DIR} --hooksDir=" + serviceDir + "/pb_hooks --migrationsDir=" + serviceDir + "/pb_migrations --publicDir=" + serviceDir + "/pb_public", "systemctl --no-block enable pb@" + serviceName, } for _, snippet := range required { if !strings.Contains(script, snippet) { t.Fatalf("script missing %q", snippet) } } } func TestSystemdOverrideScript(t *testing.T) { t.Parallel() serviceName := "demo" port := 8090 volume := "/srv/pb/data" script := systemdOverrideScript(serviceName, port, volume) required := []string{ "/etc/systemd/system/pb@" + serviceName + ".service.d", "Environment=PORT=8090", "Environment=DATA_DIR=" + volume, } for _, snippet := range required { if !strings.Contains(script, snippet) { t.Fatalf("script missing %q", snippet) } } } func TestSystemdRestartScript(t *testing.T) { t.Parallel() serviceName := "demo" script := systemdRestartScript(serviceName) expected := "systemctl --no-block restart pb@" + serviceName if !strings.Contains(script, expected) { t.Fatalf("script missing %q", expected) } }