package commands import ( "context" "database/sql" "strings" "testing" "git.tjp.lol/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"}}, } 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 (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 (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 (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 (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) } } }) } }