package actions import ( "context" "database/sql" "fmt" "testing" "git.tjp.lol/punchcard/internal/database" "git.tjp.lol/punchcard/internal/queries" ) func setupTestDB(t *testing.T) (*queries.Queries, func()) { db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatalf("Failed to open in-memory sqlite db: %v", err) } if err := database.InitializeDB(db); err != nil { t.Fatalf("Failed to initialize in-memory sqlite db: %v", err) } q := queries.New(db) cleanup := func() { if err := q.DBTX().(*sql.DB).Close(); err != nil { t.Logf("error closing database: %v", err) } } return q, cleanup } func TestArchiveClient(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) int64 expectError bool }{ { name: "archive existing client", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) return client.ID }, expectError: false, }, { name: "archive already archived client", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "AlreadyArchived", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID }, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) clientID := tt.setupData(q) err := a.ArchiveClient(context.Background(), clientID) if tt.expectError && err == nil { t.Errorf("Expected error but got none") } if !tt.expectError && err != nil { t.Errorf("Unexpected error: %v", err) } if !tt.expectError { client, err := a.FindClient(context.Background(), fmt.Sprintf("%d", clientID)) if err != nil { t.Fatalf("Failed to find client: %v", err) } if client.Archived == 0 { t.Errorf("Expected client to be archived") } } }) } } func TestUnarchiveClient(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) int64 expectError bool }{ { name: "unarchive archived client", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.ID }, expectError: false, }, { name: "unarchive already active client", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ActiveClient", }) return client.ID }, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) clientID := tt.setupData(q) err := a.UnarchiveClient(context.Background(), clientID) if tt.expectError && err == nil { t.Errorf("Expected error but got none") } if !tt.expectError && err != nil { t.Errorf("Unexpected error: %v", err) } if !tt.expectError { client, err := a.FindClient(context.Background(), fmt.Sprintf("%d", clientID)) if err != nil { t.Fatalf("Failed to find client: %v", err) } if client.Archived != 0 { t.Errorf("Expected client to not be archived") } } }) } } func TestArchiveProject(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) int64 expectError bool }{ { name: "archive existing project", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "TestProject", ClientID: client.ID, }) return project.ID }, expectError: false, }, { name: "archive already archived project", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "AlreadyArchived", ClientID: client.ID, }) _ = q.ArchiveProject(context.Background(), project.ID) return project.ID }, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) projectID := tt.setupData(q) err := a.ArchiveProject(context.Background(), projectID) if tt.expectError && err == nil { t.Errorf("Expected error but got none") } if !tt.expectError && err != nil { t.Errorf("Unexpected error: %v", err) } if !tt.expectError { project, err := a.FindProject(context.Background(), fmt.Sprintf("%d", projectID)) if err != nil { t.Fatalf("Failed to find project: %v", err) } if project.Archived == 0 { t.Errorf("Expected project to be archived") } } }) } } func TestUnarchiveProject(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) int64 expectError bool }{ { name: "unarchive archived project", setupData: func(q *queries.Queries) 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 project.ID }, expectError: false, }, { name: "unarchive already active project", setupData: func(q *queries.Queries) int64 { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ActiveProject", ClientID: client.ID, }) return project.ID }, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) projectID := tt.setupData(q) err := a.UnarchiveProject(context.Background(), projectID) if tt.expectError && err == nil { t.Errorf("Expected error but got none") } if !tt.expectError && err != nil { t.Errorf("Unexpected error: %v", err) } if !tt.expectError { project, err := a.FindProject(context.Background(), fmt.Sprintf("%d", projectID)) if err != nil { t.Fatalf("Failed to find project: %v", err) } if project.Archived != 0 { t.Errorf("Expected project to not be archived") } } }) } } func TestPunchInWithArchivedClient(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) (clientName string) autoUnarchive bool expectError bool errorType error }{ { name: "punch in on archived client without auto-unarchive returns error", setupData: func(q *queries.Queries) string { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.Name }, autoUnarchive: false, expectError: true, errorType: ErrArchivedClient, }, { name: "punch in on archived client with auto-unarchive succeeds", setupData: func(q *queries.Queries) string { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _ = q.ArchiveClient(context.Background(), client.ID) return client.Name }, autoUnarchive: true, expectError: false, }, { name: "punch in on active client succeeds", setupData: func(q *queries.Queries) string { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ActiveClient", }) return client.Name }, autoUnarchive: false, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) clientName := tt.setupData(q) session, err := a.PunchIn(context.Background(), clientName, "", "", nil, tt.autoUnarchive) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorType != nil && err != tt.errorType { t.Errorf("Expected error %v, got %v", tt.errorType, err) } return } if err != nil { t.Errorf("Unexpected error: %v", err) } if session == nil { t.Fatalf("Expected session but got nil") } if tt.autoUnarchive { client, err := a.FindClient(context.Background(), clientName) if err != nil { t.Fatalf("Failed to find client: %v", err) } if client.Archived != 0 { t.Errorf("Expected client to be unarchived after auto-unarchive") } } }) } } func TestPunchInWithArchivedProject(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) (projectName string) autoUnarchive bool expectError bool errorType error }{ { name: "punch in on archived project without auto-unarchive returns error", setupData: func(q *queries.Queries) string { 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.Name }, autoUnarchive: false, expectError: true, errorType: ErrArchivedProject, }, { name: "punch in on archived project with auto-unarchive succeeds", setupData: func(q *queries.Queries) string { 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.Name }, autoUnarchive: true, expectError: false, }, { name: "punch in on active project succeeds", setupData: func(q *queries.Queries) string { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ActiveProject", ClientID: client.ID, }) return project.Name }, autoUnarchive: false, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) projectName := tt.setupData(q) session, err := a.PunchIn(context.Background(), "", projectName, "", nil, tt.autoUnarchive) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorType != nil && err != tt.errorType { t.Errorf("Expected error %v, got %v", tt.errorType, err) } return } if err != nil { t.Errorf("Unexpected error: %v", err) } if session == nil { t.Fatalf("Expected session but got nil") } if tt.autoUnarchive { project, err := a.FindProject(context.Background(), projectName) if err != nil { t.Fatalf("Failed to find project: %v", err) } if project.Archived != 0 { t.Errorf("Expected project to be unarchived after auto-unarchive") } } }) } } func TestPunchInMostRecentWithArchivedClient(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) autoUnarchive bool expectError bool errorType error }{ { name: "punch in most recent with archived client without auto-unarchive returns error", setupData: func(q *queries.Queries) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _, _ = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ ClientID: client.ID, }) _, _ = q.StopTimeEntry(context.Background()) _ = q.ArchiveClient(context.Background(), client.ID) }, autoUnarchive: false, expectError: true, errorType: ErrArchivedClient, }, { name: "punch in most recent with archived client with auto-unarchive succeeds", setupData: func(q *queries.Queries) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "ArchivedClient", }) _, _ = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ ClientID: client.ID, }) _, _ = q.StopTimeEntry(context.Background()) _ = q.ArchiveClient(context.Background(), client.ID) }, autoUnarchive: true, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) tt.setupData(q) session, err := a.PunchInMostRecent(context.Background(), "", nil, tt.autoUnarchive) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorType != nil && err != tt.errorType { t.Errorf("Expected error %v, got %v", tt.errorType, err) } return } if err != nil { t.Errorf("Unexpected error: %v", err) } if session == nil { t.Fatalf("Expected session but got nil") } }) } } func TestPunchInMostRecentWithArchivedProject(t *testing.T) { tests := []struct { name string setupData func(*queries.Queries) autoUnarchive bool expectError bool errorType error }{ { name: "punch in most recent with archived project without auto-unarchive returns error", setupData: func(q *queries.Queries) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ArchivedProject", ClientID: client.ID, }) _, _ = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ ClientID: client.ID, ProjectID: sql.NullInt64{Int64: project.ID, Valid: true}, }) _, _ = q.StopTimeEntry(context.Background()) _ = q.ArchiveProject(context.Background(), project.ID) }, autoUnarchive: false, expectError: true, errorType: ErrArchivedProject, }, { name: "punch in most recent with archived project with auto-unarchive succeeds", setupData: func(q *queries.Queries) { client, _ := q.CreateClient(context.Background(), queries.CreateClientParams{ Name: "TestClient", }) project, _ := q.CreateProject(context.Background(), queries.CreateProjectParams{ Name: "ArchivedProject", ClientID: client.ID, }) _, _ = q.CreateTimeEntry(context.Background(), queries.CreateTimeEntryParams{ ClientID: client.ID, ProjectID: sql.NullInt64{Int64: project.ID, Valid: true}, }) _, _ = q.StopTimeEntry(context.Background()) _ = q.ArchiveProject(context.Background(), project.ID) }, autoUnarchive: true, expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q, cleanup := setupTestDB(t) defer cleanup() a := New(q) tt.setupData(q) session, err := a.PunchInMostRecent(context.Background(), "", nil, tt.autoUnarchive) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } else if tt.errorType != nil && err != tt.errorType { t.Errorf("Expected error %v, got %v", tt.errorType, err) } return } if err != nil { t.Errorf("Unexpected error: %v", err) } if session == nil { t.Fatalf("Expected session but got nil") } }) } }