summaryrefslogtreecommitdiff
path: root/internal/commands/status_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/commands/status_test.go')
-rw-r--r--internal/commands/status_test.go534
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)
+ }
+ }
+ })
+ }
+}