diff options
Diffstat (limited to 'internal/commands/status_test.go')
-rw-r--r-- | internal/commands/status_test.go | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/internal/commands/status_test.go b/internal/commands/status_test.go new file mode 100644 index 0000000..7244993 --- /dev/null +++ b/internal/commands/status_test.go @@ -0,0 +1,534 @@ +package commands + +import ( + "context" + "database/sql" + "strings" + "testing" + + "punchcard/internal/queries" +) + +func TestStatusCommand(t *testing.T) { + tests := []struct { + name string + setupData func(*queries.Queries) error + expectedContains []string + expectedNotContains []string + }{ + { + name: "status with no data", + setupData: func(q *queries.Queries) error { + return nil // No setup needed + }, + expectedContains: []string{ + "⚪ No active timer", + "📅 This Week", + "No time entries this week", + "📊 This Month", + "No time entries this month", + }, + }, + { + name: "status with active timer", + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "ActiveClient", + Email: sql.NullString{String: "active@client.com", Valid: true}, + }) + if err != nil { + return err + } + + project, err := q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Active Project", + ClientID: client.ID, + }) + if err != nil { + return err + } + + _, err = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ + Description: sql.NullString{String: "Working on tests", Valid: true}, + ClientID: client.ID, + ProjectID: sql.NullInt64{Int64: project.ID, Valid: true}, + }) + return err + }, + expectedContains: []string{ + "🔴 Active Timer", + "Client: ActiveClient", + "Project: Active Project", + "Description: Working on tests", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup fresh database for each test + q, cleanup := setupTestDB(t) + defer cleanup() + + // Setup test data + if err := tt.setupData(q); err != nil { + t.Fatalf("Failed to setup test data: %v", err) + } + + // Execute status command + output, err := executeCommandWithDB(t, q, "status") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Check expected content is present + for _, expected := range tt.expectedContains { + if !strings.Contains(output, expected) { + t.Errorf("Expected output to contain %q, but got:\n%s", expected, output) + } + } + + // Check that unwanted content is not present + for _, notExpected := range tt.expectedNotContains { + if strings.Contains(output, notExpected) { + t.Errorf("Expected output to NOT contain %q, but got:\n%s", notExpected, output) + } + } + }) + } +} + +func TestStatusCommandAliases(t *testing.T) { + tests := []struct { + name string + args []string + }{ + {"status command", []string{"status"}}, + {"st alias", []string{"st"}}, + {"default command", []string{}}, // No subcommand should default to status + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup fresh database for each test + q, cleanup := setupTestDB(t) + defer cleanup() + + // Create minimal test data + _, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestClient", + }) + if err != nil { + t.Fatalf("Failed to create test client: %v", err) + } + + // Execute command + output, err := executeCommandWithDB(t, q, tt.args...) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Should always contain the basic status structure + expectedSections := []string{"This Week", "This Month"} + for _, section := range expectedSections { + if !strings.Contains(output, section) { + t.Errorf("Expected output to contain %q section, but got:\n%s", section, output) + } + } + }) + } +} + +func TestStatusCommandWithRecentEntry(t *testing.T) { + tests := []struct { + name string + setupData func(*queries.Queries) error + expectedContains []string + expectedNotContains []string + }{ + { + name: "status shows most recent entry when no active timer", + setupData: func(q *queries.Queries) error { + // Create client and project + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + Email: sql.NullString{String: "test@testcorp.com", Valid: true}, + }) + if err != nil { + return err + } + + project, err := q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Website Project", + ClientID: client.ID, + }) + if err != nil { + return err + } + + // Create a completed time entry + if _, err := q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ + Description: sql.NullString{String: "Working on tests", Valid: true}, + ClientID: client.ID, + ProjectID: sql.NullInt64{Int64: project.ID, Valid: true}, + }); err != nil { + return err + } + + // Stop the entry to make it completed + _, err = q.StopTimeEntry(context.Background()) + return err + }, + expectedContains: []string{ + "⚪ No active timer", + "📝 Most Recent Entry", + "Client: TestCorp", + "Project: Website Project", + "Description: Working on tests", + "Started:", + "Ended:", + "Duration:", + }, + expectedNotContains: []string{ + "🔴 Active Timer", + }, + }, + { + name: "status with no time entries shows no recent entry", + setupData: func(q *queries.Queries) error { + // Create client but no time entries + _, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + }) + return err + }, + expectedContains: []string{ + "⚪ No active timer", + }, + expectedNotContains: []string{ + "📝 Most Recent Entry", + "🔴 Active Timer", + }, + }, + { + name: "status with active timer does not show recent entry", + setupData: func(q *queries.Queries) error { + // Create client and project + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + }) + if err != nil { + return err + } + + project, err := q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Active Project", + ClientID: client.ID, + }) + if err != nil { + return err + } + + // Create an active time entry (not stopped) + _, err = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ + Description: sql.NullString{String: "Currently working", Valid: true}, + ClientID: client.ID, + ProjectID: sql.NullInt64{Int64: project.ID, Valid: true}, + }) + return err + }, + expectedContains: []string{ + "🔴 Active Timer", + "Client: TestCorp", + "Project: Active Project", + "Description: Currently working", + }, + expectedNotContains: []string{ + "⚪ No active timer", + "📝 Most Recent Entry", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup fresh database for each test + q, cleanup := setupTestDB(t) + defer cleanup() + + // Setup test data + if err := tt.setupData(q); err != nil { + t.Fatalf("Failed to setup test data: %v", err) + } + + // Execute command + output, err := executeCommandWithDB(t, q, "status") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Check expected content + for _, expected := range tt.expectedContains { + if !strings.Contains(output, expected) { + t.Errorf("Expected output to contain %q, but got:\n%s", expected, output) + } + } + + // Check unexpected content + for _, unexpected := range tt.expectedNotContains { + if strings.Contains(output, unexpected) { + t.Errorf("Expected output to NOT contain %q, but got:\n%s", unexpected, output) + } + } + }) + } +} + +func TestStatusCommandFlags(t *testing.T) { + tests := []struct { + name string + args []string + setupData func(*queries.Queries) error + expectedContains []string + expectedNotContains []string + }{ + { + name: "status with -c flag shows clients only", + args: []string{"status", "-c"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + Email: sql.NullString{String: "test@testcorp.com", Valid: true}, + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Website Redesign", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "👥 Clients", + "• TestCorp <test@testcorp.com> (ID: 1)", + "📅 This Week", + "📊 This Month", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "📁 Projects", + "└── Website Redesign (ID: 1)", // Should not show project details + }, + }, + { + name: "status with --clients flag shows clients only", + args: []string{"status", "--clients"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "LongFlagTest", + Email: sql.NullString{String: "long@flag.com", Valid: true}, + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Test Project", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "👥 Clients", + "• LongFlagTest <long@flag.com> (ID: 1)", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "📁 Projects", + "└── Test Project (ID: 1)", + }, + }, + { + name: "status with -p flag shows projects only", + args: []string{"status", "-p"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + Email: sql.NullString{String: "test@testcorp.com", Valid: true}, + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Website Redesign", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "📁 Projects", + "• Website Redesign (Client: TestCorp, ID: 1)", + "📅 This Week", + "📊 This Month", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "👥 Clients", + "• TestCorp <test@testcorp.com> (ID: 1)", // Should not show client details + }, + }, + { + name: "status with --projects flag shows projects only", + args: []string{"status", "--projects"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "LongFlagTest", + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Long Flag Project", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "📁 Projects", + "• Long Flag Project (Client: LongFlagTest, ID: 1)", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "👥 Clients", + }, + }, + { + name: "status with -c -p flags shows both", + args: []string{"status", "-c", "-p"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "TestCorp", + Email: sql.NullString{String: "test@testcorp.com", Valid: true}, + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Website Redesign", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "📋 Clients & Projects", + "• TestCorp <test@testcorp.com> (ID: 1)", + "└── Website Redesign (ID: 1)", + "📅 This Week", + "📊 This Month", + }, + expectedNotContains: []string{ + "👥 Clients", + "📁 Projects", + }, + }, + { + name: "status with --clients --projects flags shows both", + args: []string{"status", "--clients", "--projects"}, + setupData: func(q *queries.Queries) error { + client, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "BothFlagsTest", + }) + if err != nil { + return err + } + + _, err = q.CreateProject(context.Background(), queries.CreateProjectParams{ + Name: "Both Flags Project", + ClientID: client.ID, + }) + return err + }, + expectedContains: []string{ + "📋 Clients & Projects", + "• BothFlagsTest (ID: 1)", + "└── Both Flags Project (ID: 1)", + }, + expectedNotContains: []string{ + "👥 Clients", + "📁 Projects", + }, + }, + { + name: "status with -c flag and no clients shows no clients message", + args: []string{"status", "-c"}, + setupData: func(q *queries.Queries) error { + return nil // No setup needed + }, + expectedContains: []string{ + "👥 Clients", + "No clients found", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "📁 Projects", + }, + }, + { + name: "status with -p flag and no projects shows projects header but no projects", + args: []string{"status", "-p"}, + setupData: func(q *queries.Queries) error { + // Create a client but no projects + _, err := q.CreateClient(context.Background(), queries.CreateClientParams{ + Name: "ClientWithoutProjects", + }) + return err + }, + expectedContains: []string{ + "📁 Projects", + }, + expectedNotContains: []string{ + "📋 Clients & Projects", + "👥 Clients", + "• ClientWithoutProjects", // Should not show client in projects-only view + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup fresh database for each test + q, cleanup := setupTestDB(t) + defer cleanup() + + // Setup test data + if err := tt.setupData(q); err != nil { + t.Fatalf("Failed to setup test data: %v", err) + } + + // Execute command with flags + output, err := executeCommandWithDB(t, q, tt.args...) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Check expected content is present + for _, expected := range tt.expectedContains { + if !strings.Contains(output, expected) { + t.Errorf("Expected output to contain %q, but got:\n%s", expected, output) + } + } + + // Check that unwanted content is not present + for _, notExpected := range tt.expectedNotContains { + if strings.Contains(output, notExpected) { + t.Errorf("Expected output to NOT contain %q, but got:\n%s", notExpected, output) + } + } + }) + } +} |