package api import ( "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" "drivervault/apiserver/internal/config" "drivervault/apiserver/internal/pb" ) // The server name is applied at runtime and persisted to .env, and /api/health // reports it without a restart — the three things the panel's API Server tab // relies on. func TestServerConfigRenameAppliesAndPersists(t *testing.T) { t.Chdir(t.TempDir()) // UpdateEnvFile writes ./.env s := New(config.Config{ServerName: config.DefaultServerName}, pb.New("", "", "")) rec := httptest.NewRecorder() s.handleUpdateServerConfig(rec, httptest.NewRequest(http.MethodPut, "/api/admin/server-config", strings.NewReader(`{"name":" Home Garage "}`))) if rec.Code != http.StatusOK { t.Fatalf("rename: got %d, want 200: %s", rec.Code, rec.Body) } var out struct { Config serverConfigView `json:"config"` Warning string `json:"warning"` } if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { t.Fatalf("decode: %v", err) } if out.Warning != "" { t.Fatalf("unexpected warning: %s", out.Warning) } if out.Config.Name != "Home Garage" { t.Errorf("name = %q, want %q (surrounding space trimmed)", out.Config.Name, "Home Garage") } if got := s.serverName(); got != "Home Garage" { t.Errorf("runtime name = %q, want %q", got, "Home Garage") } env, err := os.ReadFile(config.EnvFile) if err != nil { t.Fatalf("read .env: %v", err) } if !strings.Contains(string(env), "SERVER_NAME=Home Garage") { t.Errorf(".env missing the rename, got:\n%s", env) } // The name reaches clients through the public liveness probe. rec = httptest.NewRecorder() s.handleHealth(rec, httptest.NewRequest(http.MethodGet, "/api/health", nil)) var health struct { Name string `json:"name"` } if err := json.Unmarshal(rec.Body.Bytes(), &health); err != nil { t.Fatalf("decode health: %v", err) } if health.Name != "Home Garage" { t.Errorf("health name = %q, want %q", health.Name, "Home Garage") } } func TestServerConfigRejectsUnusableNames(t *testing.T) { t.Chdir(t.TempDir()) cases := map[string]string{ "empty": `{"name":""}`, "blank": `{"name":" "}`, "too long": `{"name":"` + strings.Repeat("x", maxServerNameLen+1) + `"}`, "bad json": `{`, "no name": `{}`, } for name, body := range cases { t.Run(name, func(t *testing.T) { s := New(config.Config{ServerName: config.DefaultServerName}, pb.New("", "", "")) rec := httptest.NewRecorder() s.handleUpdateServerConfig(rec, httptest.NewRequest(http.MethodPut, "/api/admin/server-config", strings.NewReader(body))) if rec.Code != http.StatusBadRequest { t.Fatalf("got %d, want 400: %s", rec.Code, rec.Body) } if got := s.serverName(); got != config.DefaultServerName { t.Errorf("name changed to %q on a rejected request", got) } if _, err := os.Stat(config.EnvFile); err == nil { t.Error(".env written for a rejected request") } }) } } // A name of exactly the limit is accepted — the bound is inclusive, and it is // counted in runes, so a 64-character non-Latin name fits. func TestServerConfigCountsRunesNotBytes(t *testing.T) { t.Chdir(t.TempDir()) s := New(config.Config{ServerName: config.DefaultServerName}, pb.New("", "", "")) name := strings.Repeat("ł", maxServerNameLen) rec := httptest.NewRecorder() s.handleUpdateServerConfig(rec, httptest.NewRequest(http.MethodPut, "/api/admin/server-config", strings.NewReader(`{"name":"`+name+`"}`))) if rec.Code != http.StatusOK { t.Fatalf("got %d, want 200: %s", rec.Code, rec.Body) } if got := s.serverName(); got != name { t.Errorf("name = %q, want the %d-rune name", got, maxServerNameLen) } }