package commands import ( "context" "testing" "git.tjp.lol/punchcard/internal/queries" ) func TestAddClientCommand(t *testing.T) { tests := []struct { name string args []string expectedOutput string expectError bool }{ { name: "add client with name only", args: []string{"add", "client", "TestCorp"}, expectedOutput: "Created client: TestCorp (ID: 1)\n", expectError: false, }, { name: "add client with name and email", args: []string{"add", "client", "TechSolutions", "contact@techsolutions.com"}, expectedOutput: "Created client: TechSolutions (ID: 1)\n", expectError: false, }, { name: "add client with embedded email in name", args: []string{"add", "client", "StartupXYZ "}, expectedOutput: "Created client: StartupXYZ (ID: 1)\n", expectError: false, }, { name: "add client with both embedded and separate email (prefer separate)", args: []string{"add", "client", "GlobalInc ", "new@global.com"}, expectedOutput: "Created client: GlobalInc (ID: 1)\n", expectError: false, }, { name: "add client with email format in email arg", args: []string{"add", "client", "BigCorp", "Contact Person "}, expectedOutput: "Created client: BigCorp (ID: 1)\n", expectError: false, }, { name: "add client with no arguments", args: []string{"add", "client"}, expectError: true, }, { name: "add client with too many arguments", args: []string{"add", "client", "name", "email", "extra"}, expectError: true, }, { name: "add client with billable rate", args: []string{"add", "client", "BillableClient", "--hourly-rate", "150.50"}, expectedOutput: "Created client: BillableClient (ID: 1)\n", expectError: false, }, { name: "add client with email and billable rate", args: []string{"add", "client", "PremiumClient", "premium@example.com", "--hourly-rate", "200.75"}, expectedOutput: "Created client: PremiumClient (ID: 1)\n", expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() // Execute command output, err := executeCommandWithDB(t, q, tt.args...) // Check error expectation if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if tt.expectedOutput != "" && output != tt.expectedOutput { t.Errorf("expected output %q, got %q", tt.expectedOutput, output) } }) } } func ptrval(i int64) *int64 { return &i } func TestAddClientBillableRateStorage(t *testing.T) { tests := []struct { name string args []string expectedRate *int64 // nil means NULL in database, values in cents expectError bool }{ { name: "client without billable rate stores NULL", args: []string{"add", "client", "NoRateClient"}, expectedRate: nil, expectError: false, }, { name: "client with zero billable rate stores NULL", args: []string{"add", "client", "ZeroRateClient", "--hourly-rate", "0"}, expectedRate: nil, expectError: false, }, { name: "client with billable rate stores value", args: []string{"add", "client", "RateClient", "--hourly-rate", "125.75"}, expectedRate: ptrval(12575), // $125.75 = 12575 cents expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() _, err := executeCommandWithDB(t, q, tt.args...) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } clientName := tt.args[2] // "add", "client", "" clients, err := q.FindClient(context.Background(), queries.FindClientParams{ ID: 0, Name: clientName, }) if err != nil { t.Fatalf("Failed to query created client: %v", err) } if len(clients) != 1 { t.Fatalf("Expected 1 client, got %d", len(clients)) } client := clients[0] if tt.expectedRate == nil { if client.BillableRate.Valid { t.Errorf("Expected NULL billable_rate, got %d", client.BillableRate.Int64) } } else { if !client.BillableRate.Valid { t.Errorf("Expected billable_rate %d, got NULL", *tt.expectedRate) } else if client.BillableRate.Int64 != *tt.expectedRate { t.Errorf("Expected billable_rate %d, got %d", *tt.expectedRate, client.BillableRate.Int64) } } }) } }