package model import ( "strings" "testing" ) func intPtr(v int) *int { return &v } func TestCreatePodRequest_Validate(t *testing.T) { tests := []struct { name string req CreatePodRequest wantErr bool errMsg string }{ { name: "valid request", req: CreatePodRequest{User: "alice", Pod: "main", Tools: "go,rust"}, wantErr: false, }, { name: "minimal valid request", req: CreatePodRequest{User: "bob", Pod: "dev1"}, wantErr: false, }, { name: "missing user", req: CreatePodRequest{Pod: "main"}, wantErr: true, errMsg: "user is required", }, { name: "missing pod name", req: CreatePodRequest{User: "alice"}, wantErr: true, errMsg: "pod name is required", }, { name: "invalid user - uppercase", req: CreatePodRequest{User: "Alice", Pod: "main"}, wantErr: true, errMsg: "user must be lowercase alphanumeric with hyphens", }, { name: "invalid user - starts with hyphen", req: CreatePodRequest{User: "-alice", Pod: "main"}, wantErr: true, errMsg: "user must be lowercase alphanumeric with hyphens", }, { name: "invalid user - ends with hyphen", req: CreatePodRequest{User: "alice-", Pod: "main"}, wantErr: true, errMsg: "user must be lowercase alphanumeric with hyphens", }, { name: "invalid user - special chars", req: CreatePodRequest{User: "al!ce", Pod: "main"}, wantErr: true, errMsg: "user must be lowercase alphanumeric with hyphens", }, { name: "invalid pod name - spaces", req: CreatePodRequest{User: "alice", Pod: "my pod"}, wantErr: true, errMsg: "pod name must be lowercase alphanumeric with hyphens", }, { name: "valid user with hyphens", req: CreatePodRequest{User: "alice-dev", Pod: "my-pod"}, wantErr: false, }, { name: "user name too long for namespace", req: CreatePodRequest{User: strings.Repeat("a", 60), Pod: "main"}, wantErr: true, errMsg: "user name too long", }, { name: "pod name too long for service", req: CreatePodRequest{User: "alice", Pod: strings.Repeat("a", 52)}, wantErr: true, errMsg: "pod name too long", }, { name: "max valid user name (59 chars)", req: CreatePodRequest{User: strings.Repeat("a", 59), Pod: "main"}, wantErr: false, }, { name: "max valid pod name (51 chars)", req: CreatePodRequest{User: "alice", Pod: strings.Repeat("a", 51)}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.req.Validate() if tt.wantErr { if err == nil { t.Fatal("expected error, got nil") } if tt.errMsg != "" && !strings.HasPrefix(err.Error(), tt.errMsg) { t.Errorf("error = %q, want prefix %q", err.Error(), tt.errMsg) } } else { if err != nil { t.Fatalf("unexpected error: %v", err) } } }) } } func TestCreateUserRequest_Validate(t *testing.T) { tests := []struct { name string req CreateUserRequest wantErr bool }{ { name: "valid request", req: CreateUserRequest{User: "alice"}, wantErr: false, }, { name: "valid with quotas", req: CreateUserRequest{User: "bob", Quotas: &UpdateQuotasRequest{MaxConcurrentPods: intPtr(5)}}, wantErr: false, }, { name: "missing user", req: CreateUserRequest{}, wantErr: true, }, { name: "invalid user name", req: CreateUserRequest{User: "ALICE"}, wantErr: true, }, { name: "user name too long for namespace", req: CreateUserRequest{User: strings.Repeat("a", 60)}, wantErr: true, }, { name: "max valid user name (59 chars)", req: CreateUserRequest{User: strings.Repeat("a", 59)}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.req.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestUpdateQuotasRequest_Validate(t *testing.T) { tests := []struct { name string req UpdateQuotasRequest wantErr bool }{ { name: "valid - all fields", req: UpdateQuotasRequest{MaxConcurrentPods: intPtr(5), MaxCPUPerPod: intPtr(16)}, wantErr: false, }, { name: "valid - partial update", req: UpdateQuotasRequest{MaxConcurrentPods: intPtr(2)}, wantErr: false, }, { name: "valid - empty (no-op)", req: UpdateQuotasRequest{}, wantErr: false, }, { name: "invalid - zero max pods", req: UpdateQuotasRequest{MaxConcurrentPods: intPtr(0)}, wantErr: true, }, { name: "invalid - negative cpu", req: UpdateQuotasRequest{MaxCPUPerPod: intPtr(-1)}, wantErr: true, }, { name: "invalid - zero ram", req: UpdateQuotasRequest{MaxRAMGBPerPod: intPtr(0)}, wantErr: true, }, { name: "invalid - zero monthly hours", req: UpdateQuotasRequest{MonthlyPodHours: intPtr(0)}, wantErr: true, }, { name: "invalid - negative ai requests", req: UpdateQuotasRequest{MonthlyAIRequests: intPtr(-1)}, wantErr: true, }, { name: "valid - zero ai requests allowed", req: UpdateQuotasRequest{MonthlyAIRequests: intPtr(0)}, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.req.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestIsValidName(t *testing.T) { tests := []struct { name string input string want bool }{ {"simple lowercase", "alice", true}, {"with numbers", "alice123", true}, {"with hyphens", "alice-dev", true}, {"single char", "a", true}, {"empty", "", false}, {"uppercase", "Alice", false}, {"starts with hyphen", "-alice", false}, {"ends with hyphen", "alice-", false}, {"underscore", "alice_dev", false}, {"dot", "alice.dev", false}, {"space", "alice dev", false}, {"64 chars (too long)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", false}, {"63 chars (max)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := isValidName(tt.input) if got != tt.want { t.Errorf("isValidName(%q) = %v, want %v", tt.input, got, tt.want) } }) } } func TestDefaultQuota(t *testing.T) { q := DefaultQuota() if q.MaxConcurrentPods != 3 { t.Errorf("MaxConcurrentPods = %d, want 3", q.MaxConcurrentPods) } if q.MaxCPUPerPod != 8 { t.Errorf("MaxCPUPerPod = %d, want 8", q.MaxCPUPerPod) } if q.MaxRAMGBPerPod != 16 { t.Errorf("MaxRAMGBPerPod = %d, want 16", q.MaxRAMGBPerPod) } if q.MonthlyPodHours != 500 { t.Errorf("MonthlyPodHours = %d, want 500", q.MonthlyPodHours) } if q.MonthlyAIRequests != 10000 { t.Errorf("MonthlyAIRequests = %d, want 10000", q.MonthlyAIRequests) } } func TestNamespaceName(t *testing.T) { if got := NamespaceName("alice"); got != "dev-alice" { t.Errorf("NamespaceName(alice) = %q, want %q", got, "dev-alice") } } func TestPodFullName(t *testing.T) { if got := PodFullName("main"); got != "dev-pod-main" { t.Errorf("PodFullName(main) = %q, want %q", got, "dev-pod-main") } } func TestServiceName(t *testing.T) { if got := ServiceName("main"); got != "dev-pod-main-svc" { t.Errorf("ServiceName(main) = %q, want %q", got, "dev-pod-main-svc") } } func TestPodURL(t *testing.T) { got := PodURL("spinoff.dev", "alice", "main") want := "https://spinoff.dev/@alice/main/" if got != want { t.Errorf("PodURL() = %q, want %q", got, want) } }