package commands import ( "context" "strings" "testing" "git.tjp.lol/punchcard/internal/queries" ) func TestArchiveCommand(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) (clientID, projectID int64) args []string expectedOutputs []string expectError bool errorContains string }{ { name: "archive client by name", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) return client.ID, 0 }, args: []string{"archive", "--client", "TestClient"}, expectedOutputs: []string{"Archived client: TestClient"}, expectError: false, }, { name: "archive client by ID", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ClientByID", }) return client.ID, 0 }, args: []string{"archive", "--client", "1"}, expectedOutputs: []string{"Archived client: ClientByID"}, expectError: false, }, { name: "archive project by name", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "TestProject", ClientID: client.ID, }) return client.ID, project.ID }, args: []string{"archive", "--project", "TestProject"}, expectedOutputs: []string{"Archived project: TestProject"}, expectError: false, }, { name: "archive project by ID", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ProjectByID", ClientID: client.ID, }) return client.ID, project.ID }, args: []string{"archive", "--project", "1"}, expectedOutputs: []string{"Archived project: ProjectByID"}, expectError: false, }, { name: "archive without flags returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"archive"}, expectError: true, errorContains: "either --client or --project must be specified", }, { name: "archive with both client and project flags returns error", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "TestProject", ClientID: client.ID, }) return client.ID, project.ID }, args: []string{"archive", "--client", "TestClient", "--project", "TestProject"}, expectError: true, errorContains: "cannot specify both --client and --project", }, { name: "archive nonexistent client returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"archive", "--client", "NonexistentClient"}, expectError: true, errorContains: "failed to find client", }, { name: "archive nonexistent project returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"archive", "--project", "NonexistentProject"}, expectError: true, errorContains: "failed to find project", }, { name: "archive already archived client succeeds", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "AlreadyArchived", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID, 0 }, args: []string{"archive", "--client", "AlreadyArchived"}, expectedOutputs: []string{"Archived client: AlreadyArchived"}, expectError: false, }, { name: "archive client using short flag", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ShortFlagClient", }) return client.ID, 0 }, args: []string{"archive", "-c", "ShortFlagClient"}, expectedOutputs: []string{"Archived client: ShortFlagClient"}, expectError: false, }, { name: "archive project using short flag", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ShortFlagProject", ClientID: client.ID, }) return client.ID, project.ID }, args: []string{"archive", "-p", "ShortFlagProject"}, expectedOutputs: []string{"Archived project: ShortFlagProject"}, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() tt.setupData(q) output, err := executeCommandWithDB(t, q, tt.args...) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { t.Errorf("Expected error to contain %q, got %q", tt.errorContains, err.Error()) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } found := false for _, expectedOutput := range tt.expectedOutputs { if strings.Contains(output, expectedOutput) { found = true break } } if !found { t.Errorf("Expected output to contain one of %v, got %q", tt.expectedOutputs, output) } }) } } func TestUnarchiveCommand(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) (clientID, projectID int64) args []string expectedOutputs []string expectError bool errorContains string }{ { name: "unarchive client by name", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID, 0 }, args: []string{"unarchive", "--client", "ArchivedClient"}, expectedOutputs: []string{"Unarchived client: ArchivedClient"}, expectError: false, }, { name: "unarchive client by ID", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ClientByID", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID, 0 }, args: []string{"unarchive", "--client", "1"}, expectedOutputs: []string{"Unarchived client: ClientByID"}, expectError: false, }, { name: "unarchive project by name", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ArchivedProject", ClientID: client.ID, }) _ = q.ArchiveProject(context.Background(), project.ID) return client.ID, project.ID }, args: []string{"unarchive", "--project", "ArchivedProject"}, expectedOutputs: []string{"Unarchived project: ArchivedProject"}, expectError: false, }, { name: "unarchive project by ID", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ProjectByID", ClientID: client.ID, }) _ = q.ArchiveProject(context.Background(), project.ID) return client.ID, project.ID }, args: []string{"unarchive", "--project", "1"}, expectedOutputs: []string{"Unarchived project: ProjectByID"}, expectError: false, }, { name: "unarchive without flags returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"unarchive"}, expectError: true, errorContains: "either --client or --project must be specified", }, { name: "unarchive with both client and project flags returns error", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "TestProject", ClientID: client.ID, }) _ = q.ArchiveClient(context.Background(), client.ID) _ = q.ArchiveProject(context.Background(), project.ID) return client.ID, project.ID }, args: []string{"unarchive", "--client", "TestClient", "--project", "TestProject"}, expectError: true, errorContains: "cannot specify both --client and --project", }, { name: "unarchive nonexistent client returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"unarchive", "--client", "NonexistentClient"}, expectError: true, errorContains: "failed to find client", }, { name: "unarchive nonexistent project returns error", setupData: func(q *queries.Queries) (int64, int64) { return 0, 0 }, args: []string{"unarchive", "--project", "NonexistentProject"}, expectError: true, errorContains: "failed to find project", }, { name: "unarchive already active client succeeds", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ActiveClient", }) return client.ID, 0 }, args: []string{"unarchive", "--client", "ActiveClient"}, expectedOutputs: []string{"Unarchived client: ActiveClient"}, expectError: false, }, { name: "unarchive client using short flag", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ShortFlagClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID, 0 }, args: []string{"unarchive", "-c", "ShortFlagClient"}, expectedOutputs: []string{"Unarchived client: ShortFlagClient"}, expectError: false, }, { name: "unarchive project using short flag", setupData: func(q *queries.Queries) (int64, int64) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ShortFlagProject", ClientID: client.ID, }) _ = q.ArchiveProject(context.Background(), project.ID) return client.ID, project.ID }, args: []string{"unarchive", "-p", "ShortFlagProject"}, expectedOutputs: []string{"Unarchived project: ShortFlagProject"}, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() tt.setupData(q) output, err := executeCommandWithDB(t, q, tt.args...) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) { t.Errorf("Expected error to contain %q, got %q", tt.errorContains, err.Error()) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } found := false for _, expectedOutput := range tt.expectedOutputs { if strings.Contains(output, expectedOutput) { found = true break } } if !found { t.Errorf("Expected output to contain one of %v, got %q", tt.expectedOutputs, output) } }) } } func TestArchiveStateVerification(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) (entityID int64, isClient bool) archiveAction func(*queries.Queries, int64) verifyFunc func(*testing.T, *queries.Queries, int64, bool) }{ { name: "client is archived after archive command", setupData: func(q *queries.Queries) (int64, bool) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) return client.ID, true }, archiveAction: func(q *queries.Queries, id int64) { _ = q.ArchiveClient(context.Background(), id) }, verifyFunc: func(t *testing.T, q *queries.Queries, id int64, isClient bool) { clients, err := q.FindClient(context.Background(), queries.FindClientParams{ ID: id, }) if err != nil { t.Fatalf("Failed to find client: %v", err) } if len(clients) != 1 { t.Fatalf("Expected 1 client, got %d", len(clients)) } if clients[0].Archived == 0 { t.Errorf("Expected client to be archived") } }, }, { name: "project is archived after archive command", setupData: func(q *queries.Queries) (int64, bool) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "TestProject", ClientID: client.ID, }) return project.ID, false }, archiveAction: func(q *queries.Queries, id int64) { _ = q.ArchiveProject(context.Background(), id) }, verifyFunc: func(t *testing.T, q *queries.Queries, id int64, isClient bool) { projects, err := q.FindProject(context.Background(), queries.FindProjectParams{ ID: id, }) if err != nil { t.Fatalf("Failed to find project: %v", err) } if len(projects) != 1 { t.Fatalf("Expected 1 project, got %d", len(projects)) } if projects[0].Archived == 0 { t.Errorf("Expected project to be archived") } }, }, { name: "client is unarchived after unarchive command", setupData: func(q *queries.Queries) (int64, bool) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID, true }, archiveAction: func(q *queries.Queries, id int64) { _ = q.UnarchiveClient(context.Background(), id) }, verifyFunc: func(t *testing.T, q *queries.Queries, id int64, isClient bool) { clients, err := q.FindClient(context.Background(), queries.FindClientParams{ ID: id, }) if err != nil { t.Fatalf("Failed to find client: %v", err) } if len(clients) != 1 { t.Fatalf("Expected 1 client, got %d", len(clients)) } if clients[0].Archived != 0 { t.Errorf("Expected client to not be archived") } }, }, { name: "project is unarchived after unarchive command", setupData: func(q *queries.Queries) (int64, bool) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ArchivedProject", ClientID: client.ID, }) _ = q.ArchiveProject(context.Background(), project.ID) return project.ID, false }, archiveAction: func(q *queries.Queries, id int64) { _ = q.UnarchiveProject(context.Background(), id) }, verifyFunc: func(t *testing.T, q *queries.Queries, id int64, isClient bool) { projects, err := q.FindProject(context.Background(), queries.FindProjectParams{ ID: id, }) if err != nil { t.Fatalf("Failed to find project: %v", err) } if len(projects) != 1 { t.Fatalf("Expected 1 project, got %d", len(projects)) } if projects[0].Archived != 0 { t.Errorf("Expected project to not be archived") } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() entityID, isClient := tt.setupData(q) tt.archiveAction(q, entityID) tt.verifyFunc(t, q, entityID, isClient) }) } }