cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

refactor: test helpers & shared utilities

+1449 -1516
+18 -21
internal/articles/parser.go
··· 34 34 35 35 // ParsingRule represents XPath rules for extracting content from a specific domain 36 36 type ParsingRule struct { 37 - Domain string 38 - Title string 39 - Author string 40 - Date string 41 - Body string 42 - // XPath selectors for elements to remove 43 - Strip []string 37 + Domain string 38 + Title string 39 + Author string 40 + Date string 41 + Body string 42 + Strip []string // XPath selectors for elements to remove 44 43 TestURLs []string 45 44 } 46 45 ··· 152 151 } 153 152 154 153 // ParseURL extracts article content from a given URL 155 - func (p *ArticleParser) ParseURL(urlStr string) (*ParsedContent, error) { 156 - parsedURL, err := url.Parse(urlStr) 154 + func (p *ArticleParser) ParseURL(s string) (*ParsedContent, error) { 155 + parsedURL, err := url.Parse(s) 157 156 if err != nil { 158 157 return nil, fmt.Errorf("invalid URL: %w", err) 159 158 } 160 159 161 160 domain := parsedURL.Hostname() 162 161 163 - resp, err := p.client.Get(urlStr) 162 + resp, err := p.client.Get(s) 164 163 if err != nil { 165 164 return nil, fmt.Errorf("failed to fetch URL: %w", err) 166 165 } ··· 175 174 return nil, fmt.Errorf("failed to read response body: %w", err) 176 175 } 177 176 178 - return p.Parse(string(htmlBytes), domain, urlStr) 177 + return p.Parse(string(htmlBytes), domain, s) 179 178 } 180 179 181 180 // ParseHTML extracts article content from HTML string using domain-specific rules ··· 257 256 } 258 257 259 258 // SaveArticle saves the parsed content to filesystem and returns file paths 260 - func (p *ArticleParser) SaveArticle(content *ParsedContent, storageDir string) (markdownPath, htmlPath string, err error) { 261 - if err := os.MkdirAll(storageDir, 0755); err != nil { 259 + func (p *ArticleParser) SaveArticle(content *ParsedContent, dir string) (markdownPath, htmlPath string, err error) { 260 + if err := os.MkdirAll(dir, 0755); err != nil { 262 261 return "", "", fmt.Errorf("failed to create storage directory: %w", err) 263 262 } 264 263 ··· 267 266 slug = "article" 268 267 } 269 268 270 - baseMarkdownPath := filepath.Join(storageDir, slug+".md") 271 - baseHTMLPath := filepath.Join(storageDir, slug+".html") 269 + baseMarkdownPath := filepath.Join(dir, slug+".md") 270 + baseHTMLPath := filepath.Join(dir, slug+".html") 272 271 273 272 markdownPath = baseMarkdownPath 274 273 htmlPath = baseHTMLPath ··· 280 279 break 281 280 } 282 281 } 283 - markdownPath = filepath.Join(storageDir, fmt.Sprintf("%s_%d.md", slug, counter)) 284 - htmlPath = filepath.Join(storageDir, fmt.Sprintf("%s_%d.html", slug, counter)) 282 + markdownPath = filepath.Join(dir, fmt.Sprintf("%s_%d.md", slug, counter)) 283 + htmlPath = filepath.Join(dir, fmt.Sprintf("%s_%d.html", slug, counter)) 285 284 counter++ 286 285 } 287 286 ··· 385 384 return nil, fmt.Errorf("failed to save article: %w", err) 386 385 } 387 386 388 - article := &models.Article{ 387 + return &models.Article{ 389 388 URL: url, 390 389 Title: content.Title, 391 390 Author: content.Author, ··· 394 393 HTMLPath: htmlPath, 395 394 Created: time.Now(), 396 395 Modified: time.Now(), 397 - } 398 - 399 - return article, nil 396 + }, nil 400 397 }
+7 -15
internal/articles/parser_test.go
··· 161 161 t.Run("slugify", func(t *testing.T) { 162 162 parser := &ArticleParser{} 163 163 164 - testCases := []struct { 164 + tc := []struct { 165 165 input string 166 166 expected string 167 167 }{ ··· 174 174 {strings.Repeat("a", 150), strings.Repeat("a", 100)}, 175 175 } 176 176 177 - for _, tc := range testCases { 178 - t.Run(fmt.Sprintf("slugify '%s'", tc.input), func(t *testing.T) { 179 - result := parser.slugify(tc.input) 180 - if result != tc.expected { 181 - t.Errorf("Expected '%s', got '%s'", tc.expected, result) 177 + for _, tt := range tc { 178 + t.Run(fmt.Sprintf("slugify '%s'", tt.input), func(t *testing.T) { 179 + result := parser.slugify(tt.input) 180 + if result != tt.expected { 181 + t.Errorf("Expected '%s', got '%s'", tt.expected, result) 182 182 } 183 183 }) 184 184 } ··· 536 536 })) 537 537 defer server.Close() 538 538 539 - // Use a direct Wikipedia URL that would be processed by the real function 540 539 _, err := CreateArticleFromURL("https://en.wikipedia.org/wiki/NonExistentPage12345", tempDir) 541 540 if err == nil { 542 541 t.Error("Expected error for HTTP 404") ··· 547 546 }) 548 547 549 548 t.Run("fails with network error", func(t *testing.T) { 550 - // Use a non-existent server to trigger network error 551 549 _, err := CreateArticleFromURL("http://localhost:99999/test", tempDir) 552 550 if err == nil { 553 551 t.Error("Expected error for network failure") ··· 565 563 t.Run("fails with malformed HTML", func(t *testing.T) { 566 564 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 567 565 w.WriteHeader(http.StatusOK) 568 - w.Write([]byte("<html><head><title>Test</head></body>")) // Malformed HTML 566 + w.Write([]byte("<html><head><title>Test</head></body>")) 569 567 })) 570 568 defer server.Close() 571 569 572 - // Create a custom parser with localhost rule for testing 573 570 parser, err := NewArticleParser(server.Client()) 574 571 if err != nil { 575 572 t.Fatalf("Failed to create parser: %v", err) ··· 587 584 if err == nil { 588 585 t.Error("Expected error for malformed HTML") 589 586 } 590 - // Malformed HTML may either fail to parse or fail to extract title 591 587 if !strings.Contains(err.Error(), "failed to parse HTML") && !strings.Contains(err.Error(), "could not extract title") { 592 588 t.Errorf("Expected HTML parsing or title extraction error, got %v", err) 593 589 } ··· 607 603 })) 608 604 defer server.Close() 609 605 610 - // Create a custom parser with localhost rule for testing 611 606 parser, err := NewArticleParser(server.Client()) 612 607 if err != nil { 613 608 t.Fatalf("Failed to create parser: %v", err) ··· 646 641 server := newServerWithHtml(wikipediaHTML) 647 642 defer server.Close() 648 643 649 - // Create a custom parser with localhost rule for testing 650 644 parser, err := NewArticleParser(server.Client()) 651 645 if err != nil { 652 646 t.Fatalf("Failed to create parser: %v", err) ··· 699 693 t.Error("Expected Modified timestamp to be set") 700 694 } 701 695 702 - // Check files exist 703 696 if _, err := os.Stat(article.MarkdownPath); os.IsNotExist(err) { 704 697 t.Error("Expected markdown file to exist") 705 698 } ··· 707 700 t.Error("Expected HTML file to exist") 708 701 } 709 702 710 - // Verify file contents 711 703 mdContent, err := os.ReadFile(article.MarkdownPath) 712 704 if err != nil { 713 705 t.Fatalf("Failed to read markdown file: %v", err)
+37 -36
internal/handlers/articles_test.go
··· 12 12 "github.com/stormlightlabs/noteleaf/internal/articles" 13 13 "github.com/stormlightlabs/noteleaf/internal/models" 14 14 "github.com/stormlightlabs/noteleaf/internal/repo" 15 + "github.com/stormlightlabs/noteleaf/internal/shared" 15 16 ) 16 17 17 18 func TestArticleHandler(t *testing.T) { ··· 49 50 } 50 51 51 52 _, err := NewArticleHandler() 52 - Expect.AssertError(t, err, "failed to initialize database", "NewArticleHandler should fail when database initialization fails") 53 + shared.AssertErrorContains(t, err, "failed to initialize database", "NewArticleHandler should fail when database initialization fails") 53 54 }) 54 55 55 56 }) ··· 85 86 helper.AddTestRule("127.0.0.1", testRule) 86 87 87 88 err := helper.Add(ctx, server.URL+"/test-article") 88 - Expect.AssertNoError(t, err, "Add should succeed with valid URL") 89 + shared.AssertNoError(t, err, "Add should succeed with valid URL") 89 90 90 91 articles, err := helper.repos.Articles.List(ctx, &repo.ArticleListOptions{}) 91 92 if err != nil { ··· 128 129 } 129 130 130 131 err = helper.Add(ctx, duplicateURL) 131 - Expect.AssertNoError(t, err, "Add should succeed with duplicate URL and return existing") 132 + shared.AssertNoError(t, err, "Add should succeed with duplicate URL and return existing") 132 133 }) 133 134 134 135 t.Run("handles unsupported domain", func(t *testing.T) { ··· 142 143 defer server.Close() 143 144 144 145 err := helper.Add(ctx, server.URL+"/unsupported") 145 - Expect.AssertError(t, err, "failed to parse article", "Add should fail with unsupported domain") 146 + shared.AssertErrorContains(t, err, "failed to parse article", "Add should fail with unsupported domain") 146 147 }) 147 148 148 149 t.Run("handles HTTP error", func(t *testing.T) { ··· 155 156 defer server.Close() 156 157 157 158 err := helper.Add(ctx, server.URL+"/404") 158 - Expect.AssertError(t, err, "failed to parse article", "Add should fail with HTTP error") 159 + shared.AssertErrorContains(t, err, "failed to parse article", "Add should fail with HTTP error") 159 160 }) 160 161 161 162 t.Run("handles storage directory error", func(t *testing.T) { ··· 180 181 } 181 182 182 183 err := helper.Add(ctx, "https://example.com/test-article") 183 - Expect.AssertError(t, err, "failed to get article storage dir", "Add should fail when storage directory cannot be determined") 184 + shared.AssertErrorContains(t, err, "failed to get article storage dir", "Add should fail when storage directory cannot be determined") 184 185 }) 185 186 186 187 t.Run("handles database save error", func(t *testing.T) { ··· 209 210 helper.db.Exec("DROP TABLE articles") 210 211 211 212 err := helper.Add(ctx, server.URL+"/test") 212 - Expect.AssertError(t, err, "failed to save article to database", "Add should fail when database save fails") 213 + shared.AssertErrorContains(t, err, "failed to save article to database", "Add should fail when database save fails") 213 214 }) 214 215 }) 215 216 ··· 222 223 id2 := helper.CreateTestArticle(t, "https://example.com/article2", "Second Article", "Jane Smith", "2024-01-02") 223 224 224 225 err := helper.List(ctx, "", "", 0) 225 - Expect.AssertNoError(t, err, "List should succeed") 226 + shared.AssertNoError(t, err, "List should succeed") 226 227 227 228 AssertExists(t, helper.repos.Articles.Get, id1, "article") 228 229 AssertExists(t, helper.repos.Articles.Get, id2, "article") ··· 236 237 helper.CreateTestArticle(t, "https://example.com/second", "Second Article", "Jane", "2024-01-02") 237 238 238 239 err := helper.List(ctx, "First", "", 0) 239 - Expect.AssertNoError(t, err, "List with title filter should succeed") 240 + shared.AssertNoError(t, err, "List with title filter should succeed") 240 241 }) 241 242 242 243 t.Run("lists with author filter", func(t *testing.T) { ··· 247 248 helper.CreateTestArticle(t, "https://example.com/jane1", "Article by Jane", "Jane Smith", "2024-01-02") 248 249 249 250 err := helper.List(ctx, "", "John", 0) 250 - Expect.AssertNoError(t, err, "List with author filter should succeed") 251 + shared.AssertNoError(t, err, "List with author filter should succeed") 251 252 }) 252 253 253 254 t.Run("lists with limit", func(t *testing.T) { ··· 259 260 helper.CreateTestArticle(t, "https://example.com/3", "Article 3", "Author", "2024-01-03") 260 261 261 262 err := helper.List(ctx, "", "", 2) 262 - Expect.AssertNoError(t, err, "List with limit should succeed") 263 + shared.AssertNoError(t, err, "List with limit should succeed") 263 264 }) 264 265 265 266 t.Run("handles empty results", func(t *testing.T) { ··· 267 268 ctx := context.Background() 268 269 269 270 err := helper.List(ctx, "nonexistent", "", 0) 270 - Expect.AssertNoError(t, err, "List with no matches should succeed") 271 + shared.AssertNoError(t, err, "List with no matches should succeed") 271 272 }) 272 273 273 274 t.Run("handles database error", func(t *testing.T) { ··· 277 278 helper.db.Exec("DROP TABLE articles") 278 279 279 280 err := helper.List(ctx, "", "", 0) 280 - Expect.AssertError(t, err, "failed to list articles", "List should fail when database is corrupted") 281 + shared.AssertErrorContains(t, err, "failed to list articles", "List should fail when database is corrupted") 281 282 }) 282 283 }) 283 284 ··· 289 290 id := helper.CreateTestArticle(t, "https://example.com/test", "Test Article", "Test Author", "2024-01-01") 290 291 291 292 err := helper.View(ctx, id) 292 - Expect.AssertNoError(t, err, "View should succeed with valid article ID") 293 + shared.AssertNoError(t, err, "View should succeed with valid article ID") 293 294 }) 294 295 295 296 t.Run("handles non-existent article", func(t *testing.T) { ··· 297 298 ctx := context.Background() 298 299 299 300 err := helper.View(ctx, 99999) 300 - Expect.AssertError(t, err, "failed to get article", "View should fail with non-existent article ID") 301 + shared.AssertErrorContains(t, err, "failed to get article", "View should fail with non-existent article ID") 301 302 }) 302 303 303 304 t.Run("handles missing files gracefully", func(t *testing.T) { ··· 321 322 } 322 323 323 324 err = helper.View(ctx, id) 324 - Expect.AssertNoError(t, err, "View should succeed even when files are missing") 325 + shared.AssertNoError(t, err, "View should succeed even when files are missing") 325 326 }) 326 327 327 328 t.Run("handles database error", func(t *testing.T) { ··· 331 332 helper.db.Exec("DROP TABLE articles") 332 333 333 334 err := helper.View(ctx, 1) 334 - Expect.AssertError(t, err, "failed to get article", "View should fail when database is corrupted") 335 + shared.AssertErrorContains(t, err, "failed to get article", "View should fail when database is corrupted") 335 336 }) 336 337 }) 337 338 ··· 341 342 ctx := context.Background() 342 343 id := helper.CreateTestArticle(t, "https://example.com/read", "Read Test Article", "Test Author", "2024-01-01") 343 344 err := helper.Read(ctx, id) 344 - Expect.AssertNoError(t, err, "Read should succeed with valid article ID") 345 + shared.AssertNoError(t, err, "Read should succeed with valid article ID") 345 346 }) 346 347 347 348 t.Run("handles non-existent article", func(t *testing.T) { 348 349 helper := NewArticleTestHelper(t) 349 350 ctx := context.Background() 350 351 err := helper.Read(ctx, 99999) 351 - Expect.AssertError(t, err, "failed to get article", "Read should fail with non-existent article ID") 352 + shared.AssertErrorContains(t, err, "failed to get article", "Read should fail with non-existent article ID") 352 353 }) 353 354 354 355 t.Run("handles missing markdown file", func(t *testing.T) { ··· 372 373 } 373 374 374 375 err = helper.Read(ctx, id) 375 - Expect.AssertError(t, err, "markdown file not found", "Read should fail when markdown file is missing") 376 + shared.AssertErrorContains(t, err, "markdown file not found", "Read should fail when markdown file is missing") 376 377 }) 377 378 378 379 t.Run("handles database error", func(t *testing.T) { ··· 380 381 ctx := context.Background() 381 382 helper.db.Exec("DROP TABLE articles") 382 383 err := helper.Read(ctx, 1) 383 - Expect.AssertError(t, err, "failed to get article", "Read should fail when database is corrupted") 384 + shared.AssertErrorContains(t, err, "failed to get article", "Read should fail when database is corrupted") 384 385 }) 385 386 }) 386 387 ··· 392 393 AssertExists(t, helper.repos.Articles.Get, id, "article") 393 394 394 395 err := helper.Remove(ctx, id) 395 - Expect.AssertNoError(t, err, "Remove should succeed") 396 + shared.AssertNoError(t, err, "Remove should succeed") 396 397 AssertNotExists(t, helper.repos.Articles.Get, id, "article") 397 398 }) 398 399 ··· 400 401 helper := NewArticleTestHelper(t) 401 402 ctx := context.Background() 402 403 err := helper.Remove(ctx, 99999) 403 - Expect.AssertError(t, err, "failed to get article", "Remove should fail with non-existent article ID") 404 + shared.AssertErrorContains(t, err, "failed to get article", "Remove should fail with non-existent article ID") 404 405 }) 405 406 406 407 t.Run("handles missing files gracefully", func(t *testing.T) { ··· 424 425 } 425 426 426 427 err = helper.Remove(ctx, id) 427 - Expect.AssertNoError(t, err, "Remove should succeed even when files don't exist") 428 + shared.AssertNoError(t, err, "Remove should succeed even when files don't exist") 428 429 }) 429 430 430 431 t.Run("handles database error", func(t *testing.T) { ··· 435 436 helper.db.Exec("DROP TABLE articles") 436 437 437 438 err := helper.Remove(ctx, id) 438 - Expect.AssertError(t, err, "failed to get article", "Remove should fail when database is corrupted") 439 + shared.AssertErrorContains(t, err, "failed to get article", "Remove should fail when database is corrupted") 439 440 }) 440 441 }) 441 442 ··· 443 444 t.Run("shows supported domains", func(t *testing.T) { 444 445 helper := NewArticleTestHelper(t) 445 446 err := helper.Help() 446 - Expect.AssertNoError(t, err, "Help should succeed") 447 + shared.AssertNoError(t, err, "Help should succeed") 447 448 }) 448 449 449 450 t.Run("handles storage directory error", func(t *testing.T) { ··· 467 468 } 468 469 469 470 err := helper.Help() 470 - Expect.AssertError(t, err, "failed to get storage directory", "Help should fail when storage directory cannot be determined") 471 + shared.AssertErrorContains(t, err, "failed to get storage directory", "Help should fail when storage directory cannot be determined") 471 472 }) 472 473 }) 473 474 ··· 475 476 t.Run("closes successfully", func(t *testing.T) { 476 477 helper := NewArticleTestHelper(t) 477 478 err := helper.Close() 478 - Expect.AssertNoError(t, err, "Close should succeed") 479 + shared.AssertNoError(t, err, "Close should succeed") 479 480 }) 480 481 481 482 t.Run("handles nil database gracefully", func(t *testing.T) { 482 483 helper := NewArticleTestHelper(t) 483 484 helper.db = nil 484 485 err := helper.Close() 485 - Expect.AssertNoError(t, err, "Close should succeed with nil database") 486 + shared.AssertNoError(t, err, "Close should succeed with nil database") 486 487 }) 487 488 }) 488 489 ··· 490 491 t.Run("returns storage directory successfully", func(t *testing.T) { 491 492 helper := NewArticleTestHelper(t) 492 493 dir, err := helper.getStorageDirectory() 493 - Expect.AssertNoError(t, err, "getStorageDirectory should succeed") 494 + shared.AssertNoError(t, err, "getStorageDirectory should succeed") 494 495 495 496 if dir == "" { 496 497 t.Error("Storage directory should not be empty") ··· 522 523 } 523 524 524 525 _, err := helper.getStorageDirectory() 525 - Expect.AssertError(t, err, "", "getStorageDirectory should fail when home directory cannot be determined") 526 + shared.AssertErrorContains(t, err, "", "getStorageDirectory should fail when home directory cannot be determined") 526 527 }) 527 528 }) 528 529 } ··· 556 557 helper.AddTestRule("127.0.0.1", testRule) 557 558 558 559 err := helper.Add(ctx, server.URL+"/integration-test") 559 - Expect.AssertNoError(t, err, "Add should succeed in integration test") 560 + shared.AssertNoError(t, err, "Add should succeed in integration test") 560 561 561 562 err = helper.List(ctx, "", "", 0) 562 - Expect.AssertNoError(t, err, "List should succeed in integration test") 563 + shared.AssertNoError(t, err, "List should succeed in integration test") 563 564 564 565 articles, err := helper.repos.Articles.List(ctx, &repo.ArticleListOptions{}) 565 566 if err != nil { ··· 573 574 articleID := articles[0].ID 574 575 575 576 err = helper.View(ctx, articleID) 576 - Expect.AssertNoError(t, err, "View should succeed in integration test") 577 + shared.AssertNoError(t, err, "View should succeed in integration test") 577 578 578 579 err = helper.Help() 579 - Expect.AssertNoError(t, err, "Help should succeed in integration test") 580 + shared.AssertNoError(t, err, "Help should succeed in integration test") 580 581 581 582 err = helper.Remove(ctx, articleID) 582 - Expect.AssertNoError(t, err, "Remove should succeed in integration test") 583 + shared.AssertNoError(t, err, "Remove should succeed in integration test") 583 584 584 585 AssertNotExists(t, helper.repos.Articles.Get, articleID, "article") 585 586 })
+10 -31
internal/handlers/books_test.go
··· 11 11 "github.com/stormlightlabs/noteleaf/internal/models" 12 12 "github.com/stormlightlabs/noteleaf/internal/repo" 13 13 "github.com/stormlightlabs/noteleaf/internal/services" 14 + "github.com/stormlightlabs/noteleaf/internal/shared" 14 15 ) 15 - 16 - // setupBookTest removed - use NewHandlerTestSuite(t) instead 17 16 18 17 func createTestBook(t *testing.T, handler *BookHandler, ctx context.Context) *models.Book { 19 18 t.Helper() ··· 113 112 }) 114 113 115 114 t.Run("handles HTTP error responses", func(t *testing.T) { 116 - mockServer := HTTPErrorMockServer(500, "Internal Server Error") 115 + mockServer := shared.HTTPErrorMockServer(500, "Internal Server Error") 117 116 defer mockServer.Close() 118 117 119 118 handler.service = services.NewBookService(mockServer.URL()) ··· 129 128 }) 130 129 131 130 t.Run("handles malformed JSON response", func(t *testing.T) { 132 - mockServer := InvalidJSONMockServer() 131 + mockServer := shared.InvalidJSONMockServer() 133 132 defer mockServer.Close() 134 133 135 134 handler.service = services.NewBookService(mockServer.URL()) ··· 149 148 NumFound: 0, Start: 0, Docs: []services.OpenLibrarySearchDoc{}, 150 149 } 151 150 152 - mockServer := JSONMockServer(emptyResponse) 151 + mockServer := shared.JSONMockServer(emptyResponse) 153 152 defer mockServer.Close() 154 153 155 154 handler.service = services.NewBookService(mockServer.URL()) ··· 163 162 }) 164 163 165 164 t.Run("handles network timeouts", func(t *testing.T) { 166 - mockServer := TimeoutMockServer(5 * time.Second) 165 + mockServer := shared.TimeoutMockServer(5 * time.Second) 167 166 defer mockServer.Close() 168 167 169 168 handler.service = services.NewBookService(mockServer.URL()) ··· 181 180 {Key: "/works/OL123456W", Title: "Test Book", Authors: []string{"Author"}, Year: 2020}, 182 181 } 183 182 mockResponse := MockOpenLibraryResponse(mockBooks) 184 - mockServer := JSONMockServer(mockResponse) 183 + mockServer := shared.JSONMockServer(mockResponse) 185 184 defer mockServer.Close() 186 185 187 186 handler.service = services.NewBookService(mockServer.URL()) ··· 249 248 {Key: "/works/OL456W", Title: "Test Book 2", Authors: []string{"Author 2"}, Year: 2021, Editions: 3, CoverID: 456}, 250 249 } 251 250 mockResponse := MockOpenLibraryResponse(mockBooks) 252 - mockServer := JSONMockServer(mockResponse) 251 + mockServer := shared.JSONMockServer(mockResponse) 253 252 defer mockServer.Close() 254 253 255 254 handler.service = services.NewBookService(mockServer.URL()) ··· 279 278 {Key: "/works/OL789W", Title: "Another Book", Authors: []string{"Another Author"}, Year: 2022}, 280 279 } 281 280 mockResponse := MockOpenLibraryResponse(mockBooks) 282 - mockServer := JSONMockServer(mockResponse) 281 + mockServer := shared.JSONMockServer(mockResponse) 283 282 defer mockServer.Close() 284 283 285 284 handler.service = services.NewBookService(mockServer.URL()) ··· 307 306 {Key: "/works/OL999W", Title: "Choice Test Book", Authors: []string{"Choice Author"}, Year: 2023}, 308 307 } 309 308 mockResponse := MockOpenLibraryResponse(mockBooks) 310 - mockServer := JSONMockServer(mockResponse) 309 + mockServer := shared.JSONMockServer(mockResponse) 311 310 defer mockServer.Close() 312 311 313 312 handler.service = services.NewBookService(mockServer.URL()) ··· 821 820 }) 822 821 } 823 822 824 - // TestBookHandlerWithSuite demonstrates using HandlerTestSuite for cleaner tests 825 823 func TestBookHandlerWithSuite(t *testing.T) { 826 - // Example: Using HandlerTestSuite for lifecycle testing 827 824 t.Run("Lifecycle", func(t *testing.T) { 828 825 suite := NewMediaHandlerTestSuite(t) 829 826 ··· 831 828 suite.AssertNoError(err, "NewBookHandler") 832 829 defer handler.Close() 833 830 834 - // Test basic behaviors using reusable patterns 835 831 InputReaderTest(t, handler) 836 832 }) 837 833 838 - // Example: Using generic CreateHandler 839 834 t.Run("GenericHandlerCreation", func(t *testing.T) { 840 835 _ = NewHandlerTestSuite(t) 841 - 842 - // Generic CreateHandler replaces CreateBookHandler 843 836 handler := CreateHandler(t, NewBookHandler) 844 - 845 - // Handler automatically cleaned up via t.Cleanup 846 837 _ = handler 847 838 }) 848 839 849 - // Example: Using HandlerTestSuite for media operations 850 840 t.Run("MediaOperations", func(t *testing.T) { 851 841 suite := NewMediaHandlerTestSuite(t) 852 842 ··· 854 844 suite.AssertNoError(err, "NewBookHandler") 855 845 defer handler.Close() 856 846 857 - // Create a test book first 858 847 book := &models.Book{ 859 848 Title: "Suite Test Book", 860 849 Author: "Suite Test Author", ··· 862 851 Added: time.Now(), 863 852 } 864 853 id, err := handler.repos.Books.Create(suite.Context(), book) 865 - suite.AssertNoError(err, "Create test book") 866 854 867 - // Test list operation 855 + suite.AssertNoError(err, "Create test book") 868 856 suite.TestList(handler, "") 869 - 870 - // Test status update 871 857 suite.TestUpdateStatus(handler, strconv.FormatInt(id, 10), "reading", true) 872 - 873 - // Test invalid status update 874 858 suite.TestUpdateStatus(handler, strconv.FormatInt(id, 10), "invalid", false) 875 - 876 - // Test remove operation 877 859 suite.TestRemove(handler, strconv.FormatInt(id, 10), true) 878 860 }) 879 861 880 - // Example: Generic lifecycle test 881 862 t.Run("GenericLifecycle", func(t *testing.T) { 882 863 _ = NewHandlerTestSuite(t) 883 - 884 - // Demonstrates using generic HandlerLifecycleTest 885 864 HandlerLifecycleTest(t, NewBookHandler) 886 865 }) 887 866 }
+239 -261
internal/handlers/config_test.go
··· 8 8 "strings" 9 9 "testing" 10 10 11 + "github.com/stormlightlabs/noteleaf/internal/shared" 11 12 "github.com/stormlightlabs/noteleaf/internal/store" 12 13 ) 13 14 14 - func TestConfigHandlerGet(t *testing.T) { 15 - tempDir, err := os.MkdirTemp("", "noteleaf-config-handler-get-test-*") 16 - if err != nil { 17 - t.Fatalf("Failed to create temp directory: %v", err) 18 - } 19 - defer os.RemoveAll(tempDir) 15 + func TestConfigHandler(t *testing.T) { 16 + t.Run("Get", func(t *testing.T) { 17 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-handler-get-test-*", t) 18 + defer cleanup() 20 19 21 - // Set up environment 22 - customConfigPath := filepath.Join(tempDir, "test-config.toml") 23 - originalEnv := os.Getenv("NOTELEAF_CONFIG") 24 - os.Setenv("NOTELEAF_CONFIG", customConfigPath) 25 - defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 20 + customConfigPath := filepath.Join(tempDir, "test-config.toml") 21 + originalEnv := os.Getenv("NOTELEAF_CONFIG") 22 + os.Setenv("NOTELEAF_CONFIG", customConfigPath) 23 + defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 26 24 27 - // Create a test config 28 - config := store.DefaultConfig() 29 - config.ColorScheme = "test-scheme" 30 - config.Editor = "vim" 31 - if err := store.SaveConfig(config); err != nil { 32 - t.Fatalf("Failed to save config: %v", err) 33 - } 34 - 35 - t.Run("Get all config values", func(t *testing.T) { 36 - handler, err := NewConfigHandler() 37 - if err != nil { 38 - t.Fatalf("Failed to create handler: %v", err) 25 + config := store.DefaultConfig() 26 + config.ColorScheme = "test-scheme" 27 + config.Editor = "vim" 28 + if err := store.SaveConfig(config); err != nil { 29 + t.Fatalf("Failed to save config: %v", err) 39 30 } 40 31 41 - // Capture stdout 42 - oldStdout := os.Stdout 43 - r, w, _ := os.Pipe() 44 - os.Stdout = w 32 + t.Run("Get all config values", func(t *testing.T) { 33 + handler, err := NewConfigHandler() 34 + if err != nil { 35 + t.Fatalf("Failed to create handler: %v", err) 36 + } 45 37 46 - err = handler.Get("") 47 - if err != nil { 48 - t.Fatalf("Get failed: %v", err) 49 - } 38 + oldStdout := os.Stdout 39 + r, w, _ := os.Pipe() 40 + os.Stdout = w 50 41 51 - w.Close() 52 - os.Stdout = oldStdout 42 + err = handler.Get("") 43 + if err != nil { 44 + t.Fatalf("Get failed: %v", err) 45 + } 53 46 54 - var buf bytes.Buffer 55 - io.Copy(&buf, r) 56 - output := buf.String() 47 + w.Close() 48 + os.Stdout = oldStdout 57 49 58 - if !strings.Contains(output, "color_scheme") { 59 - t.Error("Output should contain color_scheme") 60 - } 61 - if !strings.Contains(output, "test-scheme") { 62 - t.Error("Output should contain test-scheme value") 63 - } 64 - }) 50 + var buf bytes.Buffer 51 + io.Copy(&buf, r) 52 + output := buf.String() 65 53 66 - t.Run("Get specific config value", func(t *testing.T) { 67 - handler, err := NewConfigHandler() 68 - if err != nil { 69 - t.Fatalf("Failed to create handler: %v", err) 70 - } 71 - 72 - // Capture stdout 73 - oldStdout := os.Stdout 74 - r, w, _ := os.Pipe() 75 - os.Stdout = w 76 - 77 - err = handler.Get("editor") 78 - if err != nil { 79 - t.Fatalf("Get failed: %v", err) 80 - } 81 - 82 - w.Close() 83 - os.Stdout = oldStdout 84 - 85 - var buf bytes.Buffer 86 - io.Copy(&buf, r) 87 - output := buf.String() 54 + if !strings.Contains(output, "color_scheme") { 55 + t.Error("Output should contain color_scheme") 56 + } 57 + if !strings.Contains(output, "test-scheme") { 58 + t.Error("Output should contain test-scheme value") 59 + } 60 + }) 88 61 89 - if !strings.Contains(output, "editor = vim") { 90 - t.Errorf("Output should contain 'editor = vim', got: %s", output) 91 - } 92 - }) 62 + t.Run("Get specific config value", func(t *testing.T) { 63 + handler, err := NewConfigHandler() 64 + if err != nil { 65 + t.Fatalf("Failed to create handler: %v", err) 66 + } 93 67 94 - t.Run("Get unknown config key", func(t *testing.T) { 95 - handler, err := NewConfigHandler() 96 - if err != nil { 97 - t.Fatalf("Failed to create handler: %v", err) 98 - } 68 + oldStdout := os.Stdout 69 + r, w, _ := os.Pipe() 70 + os.Stdout = w 99 71 100 - err = handler.Get("nonexistent_key") 101 - if err == nil { 102 - t.Error("Get should fail for unknown key") 103 - } 104 - if !strings.Contains(err.Error(), "unknown config key") { 105 - t.Errorf("Error should mention unknown config key, got: %v", err) 106 - } 107 - }) 108 - } 72 + err = handler.Get("editor") 73 + if err != nil { 74 + t.Fatalf("Get failed: %v", err) 75 + } 109 76 110 - func TestConfigHandlerSet(t *testing.T) { 111 - tempDir, err := os.MkdirTemp("", "noteleaf-config-handler-set-test-*") 112 - if err != nil { 113 - t.Fatalf("Failed to create temp directory: %v", err) 114 - } 115 - defer os.RemoveAll(tempDir) 77 + w.Close() 78 + os.Stdout = oldStdout 116 79 117 - // Set up environment 118 - customConfigPath := filepath.Join(tempDir, "test-config.toml") 119 - originalEnv := os.Getenv("NOTELEAF_CONFIG") 120 - os.Setenv("NOTELEAF_CONFIG", customConfigPath) 121 - defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 80 + var buf bytes.Buffer 81 + io.Copy(&buf, r) 82 + output := buf.String() 122 83 123 - t.Run("Set string config value", func(t *testing.T) { 124 - handler, err := NewConfigHandler() 125 - if err != nil { 126 - t.Fatalf("Failed to create handler: %v", err) 127 - } 84 + if !strings.Contains(output, "editor = vim") { 85 + t.Errorf("Output should contain 'editor = vim', got: %s", output) 86 + } 87 + }) 128 88 129 - // Capture stdout 130 - oldStdout := os.Stdout 131 - r, w, _ := os.Pipe() 132 - os.Stdout = w 89 + t.Run("Get unknown config key", func(t *testing.T) { 90 + handler, err := NewConfigHandler() 91 + if err != nil { 92 + t.Fatalf("Failed to create handler: %v", err) 93 + } 133 94 134 - err = handler.Set("editor", "emacs") 135 - if err != nil { 136 - t.Fatalf("Set failed: %v", err) 137 - } 95 + err = handler.Get("nonexistent_key") 96 + if err == nil { 97 + t.Error("Get should fail for unknown key") 98 + } 99 + if !strings.Contains(err.Error(), "unknown config key") { 100 + t.Errorf("Error should mention unknown config key, got: %v", err) 101 + } 102 + }) 103 + }) 138 104 139 - w.Close() 140 - os.Stdout = oldStdout 105 + t.Run("Set", func(t *testing.T) { 106 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-handler-set-test-*", t) 107 + defer cleanup() 141 108 142 - var buf bytes.Buffer 143 - io.Copy(&buf, r) 144 - output := buf.String() 109 + customConfigPath := filepath.Join(tempDir, "test-config.toml") 110 + originalEnv := os.Getenv("NOTELEAF_CONFIG") 111 + os.Setenv("NOTELEAF_CONFIG", customConfigPath) 112 + defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 145 113 146 - if !strings.Contains(output, "Set editor = emacs") { 147 - t.Errorf("Output should confirm setting, got: %s", output) 148 - } 114 + t.Run("Set string config value", func(t *testing.T) { 115 + handler, err := NewConfigHandler() 116 + if err != nil { 117 + t.Fatalf("Failed to create handler: %v", err) 118 + } 149 119 150 - // Verify it was actually saved 151 - loadedConfig, err := store.LoadConfig() 152 - if err != nil { 153 - t.Fatalf("Failed to load config: %v", err) 154 - } 120 + oldStdout := os.Stdout 121 + r, w, _ := os.Pipe() 122 + os.Stdout = w 155 123 156 - if loadedConfig.Editor != "emacs" { 157 - t.Errorf("Expected editor 'emacs', got '%s'", loadedConfig.Editor) 158 - } 159 - }) 124 + err = handler.Set("editor", "emacs") 125 + if err != nil { 126 + t.Fatalf("Set failed: %v", err) 127 + } 160 128 161 - t.Run("Set boolean config value", func(t *testing.T) { 162 - handler, err := NewConfigHandler() 163 - if err != nil { 164 - t.Fatalf("Failed to create handler: %v", err) 165 - } 129 + w.Close() 130 + os.Stdout = oldStdout 166 131 167 - err = handler.Set("auto_archive", "true") 168 - if err != nil { 169 - t.Fatalf("Set failed: %v", err) 170 - } 132 + var buf bytes.Buffer 133 + io.Copy(&buf, r) 134 + output := buf.String() 171 135 172 - // Verify it was actually saved 173 - loadedConfig, err := store.LoadConfig() 174 - if err != nil { 175 - t.Fatalf("Failed to load config: %v", err) 176 - } 136 + if !strings.Contains(output, "Set editor = emacs") { 137 + t.Errorf("Output should confirm setting, got: %s", output) 138 + } 177 139 178 - if !loadedConfig.AutoArchive { 179 - t.Error("Expected auto_archive to be true") 180 - } 181 - }) 140 + loadedConfig, err := store.LoadConfig() 141 + if err != nil { 142 + t.Fatalf("Failed to load config: %v", err) 143 + } 182 144 183 - t.Run("Set boolean config value with various formats", func(t *testing.T) { 184 - testCases := []struct { 185 - value string 186 - expected bool 187 - }{ 188 - {"true", true}, 189 - {"1", true}, 190 - {"yes", true}, 191 - {"false", false}, 192 - {"0", false}, 193 - {"no", false}, 194 - } 145 + if loadedConfig.Editor != "emacs" { 146 + t.Errorf("Expected editor 'emacs', got '%s'", loadedConfig.Editor) 147 + } 148 + }) 195 149 196 - for _, tc := range testCases { 150 + t.Run("Set boolean config value", func(t *testing.T) { 197 151 handler, err := NewConfigHandler() 198 152 if err != nil { 199 153 t.Fatalf("Failed to create handler: %v", err) 200 154 } 201 155 202 - err = handler.Set("sync_enabled", tc.value) 156 + err = handler.Set("auto_archive", "true") 203 157 if err != nil { 204 - t.Fatalf("Set failed for value '%s': %v", tc.value, err) 158 + t.Fatalf("Set failed: %v", err) 205 159 } 206 160 207 161 loadedConfig, err := store.LoadConfig() ··· 209 163 t.Fatalf("Failed to load config: %v", err) 210 164 } 211 165 212 - if loadedConfig.SyncEnabled != tc.expected { 213 - t.Errorf("For value '%s', expected sync_enabled %v, got %v", tc.value, tc.expected, loadedConfig.SyncEnabled) 166 + if !loadedConfig.AutoArchive { 167 + t.Error("Expected auto_archive to be true") 214 168 } 215 - } 216 - }) 169 + }) 217 170 218 - t.Run("Set unknown config key", func(t *testing.T) { 219 - handler, err := NewConfigHandler() 220 - if err != nil { 221 - t.Fatalf("Failed to create handler: %v", err) 222 - } 171 + t.Run("Set boolean config value with various formats", func(t *testing.T) { 172 + tc := []struct { 173 + value string 174 + expected bool 175 + }{ 176 + {"true", true}, 177 + {"1", true}, 178 + {"yes", true}, 179 + {"false", false}, 180 + {"0", false}, 181 + {"no", false}, 182 + } 223 183 224 - err = handler.Set("nonexistent_key", "value") 225 - if err == nil { 226 - t.Error("Set should fail for unknown key") 227 - } 228 - if !strings.Contains(err.Error(), "unknown config key") { 229 - t.Errorf("Error should mention unknown config key, got: %v", err) 230 - } 231 - }) 232 - } 184 + for _, tt := range tc { 185 + handler, err := NewConfigHandler() 186 + if err != nil { 187 + t.Fatalf("Failed to create handler: %v", err) 188 + } 233 189 234 - func TestConfigHandlerPath(t *testing.T) { 235 - tempDir, err := os.MkdirTemp("", "noteleaf-config-handler-path-test-*") 236 - if err != nil { 237 - t.Fatalf("Failed to create temp directory: %v", err) 238 - } 239 - defer os.RemoveAll(tempDir) 190 + err = handler.Set("sync_enabled", tt.value) 191 + if err != nil { 192 + t.Fatalf("Set failed for value '%s': %v", tt.value, err) 193 + } 240 194 241 - customConfigPath := filepath.Join(tempDir, "my-config.toml") 242 - originalEnv := os.Getenv("NOTELEAF_CONFIG") 243 - os.Setenv("NOTELEAF_CONFIG", customConfigPath) 244 - defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 195 + loadedConfig, err := store.LoadConfig() 196 + if err != nil { 197 + t.Fatalf("Failed to load config: %v", err) 198 + } 245 199 246 - t.Run("Path returns correct config file path", func(t *testing.T) { 247 - handler, err := NewConfigHandler() 248 - if err != nil { 249 - t.Fatalf("Failed to create handler: %v", err) 250 - } 200 + if loadedConfig.SyncEnabled != tt.expected { 201 + t.Errorf("For value '%s', expected sync_enabled %v, got %v", tt.value, tt.expected, loadedConfig.SyncEnabled) 202 + } 203 + } 204 + }) 251 205 252 - // Capture stdout 253 - oldStdout := os.Stdout 254 - r, w, _ := os.Pipe() 255 - os.Stdout = w 206 + t.Run("Set unknown config key", func(t *testing.T) { 207 + handler, err := NewConfigHandler() 208 + if err != nil { 209 + t.Fatalf("Failed to create handler: %v", err) 210 + } 256 211 257 - err = handler.Path() 258 - if err != nil { 259 - t.Fatalf("Path failed: %v", err) 260 - } 212 + err = handler.Set("nonexistent_key", "value") 213 + if err == nil { 214 + t.Error("Set should fail for unknown key") 215 + } 216 + if !strings.Contains(err.Error(), "unknown config key") { 217 + t.Errorf("Error should mention unknown config key, got: %v", err) 218 + } 219 + }) 220 + }) 221 + t.Run("Path", func(t *testing.T) { 222 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-handler-path-test-*", t) 223 + defer cleanup() 261 224 262 - w.Close() 263 - os.Stdout = oldStdout 225 + customConfigPath := filepath.Join(tempDir, "my-config.toml") 226 + originalEnv := os.Getenv("NOTELEAF_CONFIG") 227 + os.Setenv("NOTELEAF_CONFIG", customConfigPath) 228 + defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 264 229 265 - var buf bytes.Buffer 266 - io.Copy(&buf, r) 267 - output := strings.TrimSpace(buf.String()) 230 + t.Run("Path returns correct config file path", func(t *testing.T) { 231 + handler, err := NewConfigHandler() 232 + if err != nil { 233 + t.Fatalf("Failed to create handler: %v", err) 234 + } 268 235 269 - if output != customConfigPath { 270 - t.Errorf("Expected path '%s', got '%s'", customConfigPath, output) 271 - } 236 + oldStdout := os.Stdout 237 + r, w, _ := os.Pipe() 238 + os.Stdout = w 239 + 240 + err = handler.Path() 241 + if err != nil { 242 + t.Fatalf("Path failed: %v", err) 243 + } 244 + 245 + w.Close() 246 + os.Stdout = oldStdout 247 + 248 + var buf bytes.Buffer 249 + io.Copy(&buf, r) 250 + output := strings.TrimSpace(buf.String()) 251 + 252 + if output != customConfigPath { 253 + t.Errorf("Expected path '%s', got '%s'", customConfigPath, output) 254 + } 255 + }) 272 256 }) 273 - } 274 257 275 - func TestConfigHandlerReset(t *testing.T) { 276 - tempDir, err := os.MkdirTemp("", "noteleaf-config-handler-reset-test-*") 277 - if err != nil { 278 - t.Fatalf("Failed to create temp directory: %v", err) 279 - } 280 - defer os.RemoveAll(tempDir) 258 + t.Run("Reset", func(t *testing.T) { 259 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-handler-reset-test-*", t) 260 + defer cleanup() 281 261 282 - customConfigPath := filepath.Join(tempDir, "test-config.toml") 283 - originalEnv := os.Getenv("NOTELEAF_CONFIG") 284 - os.Setenv("NOTELEAF_CONFIG", customConfigPath) 285 - defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 262 + customConfigPath := filepath.Join(tempDir, "test-config.toml") 263 + originalEnv := os.Getenv("NOTELEAF_CONFIG") 264 + os.Setenv("NOTELEAF_CONFIG", customConfigPath) 265 + defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 286 266 287 - t.Run("Reset restores default config", func(t *testing.T) { 288 - // First, modify the config 289 - config := store.DefaultConfig() 290 - config.ColorScheme = "custom" 291 - config.AutoArchive = true 292 - config.Editor = "emacs" 293 - if err := store.SaveConfig(config); err != nil { 294 - t.Fatalf("Failed to save config: %v", err) 295 - } 267 + t.Run("Reset restores default config", func(t *testing.T) { 268 + config := store.DefaultConfig() 269 + config.ColorScheme = "custom" 270 + config.AutoArchive = true 271 + config.Editor = "emacs" 272 + if err := store.SaveConfig(config); err != nil { 273 + t.Fatalf("Failed to save config: %v", err) 274 + } 296 275 297 - handler, err := NewConfigHandler() 298 - if err != nil { 299 - t.Fatalf("Failed to create handler: %v", err) 300 - } 276 + handler, err := NewConfigHandler() 277 + if err != nil { 278 + t.Fatalf("Failed to create handler: %v", err) 279 + } 301 280 302 - // Capture stdout 303 - oldStdout := os.Stdout 304 - r, w, _ := os.Pipe() 305 - os.Stdout = w 281 + oldStdout := os.Stdout 282 + r, w, _ := os.Pipe() 283 + os.Stdout = w 306 284 307 - err = handler.Reset() 308 - if err != nil { 309 - t.Fatalf("Reset failed: %v", err) 310 - } 285 + err = handler.Reset() 286 + if err != nil { 287 + t.Fatalf("Reset failed: %v", err) 288 + } 311 289 312 - w.Close() 313 - os.Stdout = oldStdout 290 + w.Close() 291 + os.Stdout = oldStdout 314 292 315 - var buf bytes.Buffer 316 - io.Copy(&buf, r) 317 - output := buf.String() 293 + var buf bytes.Buffer 294 + io.Copy(&buf, r) 295 + output := buf.String() 318 296 319 - if !strings.Contains(output, "reset to defaults") { 320 - t.Errorf("Output should confirm reset, got: %s", output) 321 - } 297 + if !strings.Contains(output, "reset to defaults") { 298 + t.Errorf("Output should confirm reset, got: %s", output) 299 + } 322 300 323 - // Verify config was reset 324 - loadedConfig, err := store.LoadConfig() 325 - if err != nil { 326 - t.Fatalf("Failed to load config: %v", err) 327 - } 301 + loadedConfig, err := store.LoadConfig() 302 + if err != nil { 303 + t.Fatalf("Failed to load config: %v", err) 304 + } 328 305 329 - defaultConfig := store.DefaultConfig() 330 - if loadedConfig.ColorScheme != defaultConfig.ColorScheme { 331 - t.Errorf("ColorScheme should be reset to default '%s', got '%s'", defaultConfig.ColorScheme, loadedConfig.ColorScheme) 332 - } 333 - if loadedConfig.AutoArchive != defaultConfig.AutoArchive { 334 - t.Errorf("AutoArchive should be reset to default %v, got %v", defaultConfig.AutoArchive, loadedConfig.AutoArchive) 335 - } 336 - if loadedConfig.Editor != defaultConfig.Editor { 337 - t.Errorf("Editor should be reset to default '%s', got '%s'", defaultConfig.Editor, loadedConfig.Editor) 338 - } 306 + defaultConfig := store.DefaultConfig() 307 + if loadedConfig.ColorScheme != defaultConfig.ColorScheme { 308 + t.Errorf("ColorScheme should be reset to default '%s', got '%s'", defaultConfig.ColorScheme, loadedConfig.ColorScheme) 309 + } 310 + if loadedConfig.AutoArchive != defaultConfig.AutoArchive { 311 + t.Errorf("AutoArchive should be reset to default %v, got %v", defaultConfig.AutoArchive, loadedConfig.AutoArchive) 312 + } 313 + if loadedConfig.Editor != defaultConfig.Editor { 314 + t.Errorf("Editor should be reset to default '%s', got '%s'", defaultConfig.Editor, loadedConfig.Editor) 315 + } 316 + }) 339 317 }) 340 318 }
+37 -38
internal/handlers/notes_test.go
··· 11 11 "time" 12 12 13 13 "github.com/stormlightlabs/noteleaf/internal/models" 14 + "github.com/stormlightlabs/noteleaf/internal/shared" 14 15 "github.com/stormlightlabs/noteleaf/internal/store" 15 16 ) 16 - 17 - // setupNoteTest removed - use NewHandlerTestSuite(t) instead 18 17 19 18 func createTestMarkdownFile(t *testing.T, dir, filename, content string) string { 20 19 filePath := filepath.Join(dir, filename) ··· 37 36 t.Run("New", func(t *testing.T) { 38 37 t.Run("creates handler successfully", func(t *testing.T) { 39 38 testHandler, err := NewNoteHandler() 40 - Expect.AssertNoError(t, err, "NewNoteHandler should succeed") 39 + shared.AssertNoError(t, err, "NewNoteHandler should succeed") 41 40 if testHandler == nil { 42 41 t.Fatal("Handler should not be nil") 43 42 } ··· 68 67 envHelper.UnsetEnv("NOTELEAF_DATA_DIR") 69 68 70 69 _, err := NewNoteHandler() 71 - Expect.AssertError(t, err, "failed to initialize database", "NewNoteHandler should fail when database initialization fails") 70 + shared.AssertErrorContains(t, err, "failed to initialize database", "NewNoteHandler should fail when database initialization fails") 72 71 }) 73 72 }) 74 73 ··· 77 76 78 77 t.Run("creates note from title only", func(t *testing.T) { 79 78 err := handler.Create(ctx, "Test Note 1", "", "", false) 80 - Expect.AssertNoError(t, err, "Create should succeed") 79 + shared.AssertNoError(t, err, "Create should succeed") 81 80 }) 82 81 83 82 t.Run("creates note from title and content", func(t *testing.T) { 84 83 err := handler.Create(ctx, "Test Note 2", "This is test content", "", false) 85 - Expect.AssertNoError(t, err, "Create should succeed") 84 + shared.AssertNoError(t, err, "Create should succeed") 86 85 }) 87 86 88 87 t.Run("creates note from markdown file", func(t *testing.T) { ··· 93 92 filePath := createTestMarkdownFile(t, suite.TempDir(), "test.md", content) 94 93 95 94 err := handler.Create(ctx, "", "", filePath, false) 96 - Expect.AssertNoError(t, err, "Create from file should succeed") 95 + shared.AssertNoError(t, err, "Create from file should succeed") 97 96 }) 98 97 99 98 t.Run("handles non-existent file", func(t *testing.T) { 100 99 err := handler.Create(ctx, "", "", "/non/existent/file.md", false) 101 - Expect.AssertError(t, err, "", "Create should fail with non-existent file") 100 + shared.AssertErrorContains(t, err, "", "Create should fail with non-existent file") 102 101 }) 103 102 }) 104 103 ··· 107 106 108 107 t.Run("handles non-existent note", func(t *testing.T) { 109 108 err := handler.Edit(ctx, 999) 110 - Expect.AssertError(t, err, "failed to get note", "Edit should fail with non-existent note ID") 109 + shared.AssertErrorContains(t, err, "failed to get note", "Edit should fail with non-existent note ID") 111 110 }) 112 111 113 112 t.Run("handles no editor configured", func(t *testing.T) { ··· 118 117 envHelper.SetEnv("PATH", "") 119 118 120 119 err := handler.Edit(ctx, 1) 121 - Expect.AssertError(t, err, "failed to open editor", "Edit should fail when no editor is configured") 120 + shared.AssertErrorContains(t, err, "failed to open editor", "Edit should fail when no editor is configured") 122 121 }) 123 122 124 123 t.Run("handles database connection error", func(t *testing.T) { ··· 126 125 defer func() { 127 126 var err error 128 127 handler.db, err = store.NewDatabase() 129 - Expect.AssertNoError(t, err, "Failed to reconnect to database") 128 + shared.AssertNoError(t, err, "Failed to reconnect to database") 130 129 }() 131 130 132 131 err := handler.Edit(ctx, 1) 133 - Expect.AssertError(t, err, "failed to get note", "Edit should fail when database is closed") 132 + shared.AssertErrorContains(t, err, "failed to get note", "Edit should fail when database is closed") 134 133 }) 135 134 136 135 t.Run("handles temp file creation error", func(t *testing.T) { 137 136 testHandler, err := NewNoteHandler() 138 - Expect.AssertNoError(t, err, "Failed to create test handler") 137 + shared.AssertNoError(t, err, "Failed to create test handler") 139 138 defer testHandler.Close() 140 139 141 140 err = testHandler.Create(ctx, "Temp File Test Note", "Test content", "", false) 142 - Expect.AssertNoError(t, err, "Failed to create test note") 141 + shared.AssertNoError(t, err, "Failed to create test note") 143 142 144 143 envHelper := NewEnvironmentTestHelper() 145 144 defer envHelper.RestoreEnv() 146 145 envHelper.SetEnv("TMPDIR", "/non/existent/path") 147 146 148 147 err = testHandler.Edit(ctx, 1) 149 - Expect.AssertError(t, err, "failed to create temporary file", "Edit should fail when temp file creation fails") 148 + shared.AssertErrorContains(t, err, "failed to create temporary file", "Edit should fail when temp file creation fails") 150 149 }) 151 150 152 151 t.Run("handles editor failure", func(t *testing.T) { 153 152 testHandler, err := NewNoteHandler() 154 - Expect.AssertNoError(t, err, "Failed to create test handler") 153 + shared.AssertNoError(t, err, "Failed to create test handler") 155 154 defer testHandler.Close() 156 155 157 156 err = testHandler.Create(ctx, "Editor Failure Test Note", "Test content", "", false) 158 - Expect.AssertNoError(t, err, "Failed to create test note") 157 + shared.AssertNoError(t, err, "Failed to create test note") 159 158 160 159 mockEditor := NewMockEditor().WithFailure("editor process failed") 161 160 testHandler.openInEditorFunc = mockEditor.GetEditorFunc() 162 161 163 162 err = testHandler.Edit(ctx, 1) 164 - Expect.AssertError(t, err, "failed to open editor", "Edit should fail when editor fails") 163 + shared.AssertErrorContains(t, err, "failed to open editor", "Edit should fail when editor fails") 165 164 }) 166 165 167 166 t.Run("handles temp file write error", func(t *testing.T) { ··· 172 171 handler.openInEditorFunc = mockEditor.GetEditorFunc() 173 172 174 173 err := handler.Edit(ctx, 1) 175 - Expect.AssertError(t, err, "", "Edit should handle temp file write issues") 174 + shared.AssertErrorContains(t, err, "", "Edit should handle temp file write issues") 176 175 }) 177 176 178 177 t.Run("handles file read error after editing", func(t *testing.T) { 179 178 testHandler, err := NewNoteHandler() 180 - Expect.AssertNoError(t, err, "Failed to create test handler") 179 + shared.AssertNoError(t, err, "Failed to create test handler") 181 180 defer testHandler.Close() 182 181 183 182 err = testHandler.Create(ctx, "File Read Error Test Note", "Test content", "", false) 184 - Expect.AssertNoError(t, err, "Failed to create test note") 183 + shared.AssertNoError(t, err, "Failed to create test note") 185 184 186 185 mockEditor := NewMockEditor().WithFileDeleted() 187 186 testHandler.openInEditorFunc = mockEditor.GetEditorFunc() 188 187 189 188 err = testHandler.Edit(ctx, 1) 190 - Expect.AssertError(t, err, "failed to read edited content", "Edit should fail when temp file is deleted") 189 + shared.AssertErrorContains(t, err, "failed to read edited content", "Edit should fail when temp file is deleted") 191 190 }) 192 191 193 192 t.Run("handles database update error", func(t *testing.T) { ··· 203 202 handler.openInEditorFunc = mockEditor.GetEditorFunc() 204 203 205 204 err := handler.Edit(ctx, id) 206 - Expect.AssertError(t, err, "failed to get note", "Edit should fail when database is corrupted") 205 + shared.AssertErrorContains(t, err, "failed to get note", "Edit should fail when database is corrupted") 207 206 }) 208 207 209 208 t.Run("handles validation error - corrupted note content", func(t *testing.T) { ··· 282 281 handler.openInEditorFunc = mockEditor.GetEditorFunc() 283 282 284 283 err := handler.Edit(ctx, id) 285 - Expect.AssertNoError(t, err, "Edit should succeed") 284 + shared.AssertNoError(t, err, "Edit should succeed") 286 285 }) 287 286 288 287 t.Run("Edit Errors", func(t *testing.T) { ··· 351 350 handler.openInEditorFunc = mockEditor.GetEditorFunc() 352 351 err := handler.Edit(ctx, noteID) 353 352 354 - Expect.AssertNoError(t, err, "Edit should succeed") 353 + shared.AssertNoError(t, err, "Edit should succeed") 355 354 AssertExists(t, handler.repos.Notes.Get, noteID, "note") 356 355 }) 357 356 ··· 369 368 handler.openInEditorFunc = mockEditor.GetEditorFunc() 370 369 371 370 err := handler.Edit(ctx, noteID) 372 - Expect.AssertNoError(t, err, "Edit should succeed even with no changes") 371 + shared.AssertNoError(t, err, "Edit should succeed even with no changes") 373 372 }) 374 373 375 374 t.Run("handles content without title", func(t *testing.T) { ··· 382 381 handler.openInEditorFunc = mockEditor.GetEditorFunc() 383 382 384 383 err := handler.Edit(ctx, noteID) 385 - Expect.AssertNoError(t, err, "Edit should succeed without title") 384 + shared.AssertNoError(t, err, "Edit should succeed without title") 386 385 }) 387 386 }) 388 387 }) ··· 701 700 handler.openInEditorFunc = mockEditor.GetEditorFunc() 702 701 703 702 err := handler.createInteractive(ctx) 704 - Expect.AssertNoError(t, err, "createInteractive should succeed") 703 + shared.AssertNoError(t, err, "createInteractive should succeed") 705 704 }) 706 705 707 706 t.Run("handles cancelled note creation", func(t *testing.T) { ··· 710 709 handler.openInEditorFunc = mockEditor.GetEditorFunc() 711 710 712 711 err := handler.createInteractive(ctx) 713 - Expect.AssertNoError(t, err, "createInteractive should succeed even when cancelled") 712 + shared.AssertNoError(t, err, "createInteractive should succeed even when cancelled") 714 713 }) 715 714 716 715 t.Run("handles editor error", func(t *testing.T) { ··· 719 718 handler.openInEditorFunc = mockEditor.GetEditorFunc() 720 719 721 720 err := handler.createInteractive(ctx) 722 - Expect.AssertError(t, err, "failed to open editor", "createInteractive should fail when editor fails") 721 + shared.AssertErrorContains(t, err, "failed to open editor", "createInteractive should fail when editor fails") 723 722 }) 724 723 725 724 t.Run("handles no editor configured", func(t *testing.T) { ··· 731 730 envHelper.SetEnv("PATH", "") 732 731 733 732 err := handler.createInteractive(ctx) 734 - Expect.AssertError(t, err, "no editor configured", "createInteractive should fail when no editor is configured") 733 + shared.AssertErrorContains(t, err, "no editor configured", "createInteractive should fail when no editor is configured") 735 734 }) 736 735 737 736 t.Run("handles file read error after editing", func(t *testing.T) { ··· 740 739 handler.openInEditorFunc = mockEditor.GetEditorFunc() 741 740 742 741 err := handler.createInteractive(ctx) 743 - Expect.AssertError(t, err, "failed to read edited content", "createInteractive should fail when temp file is deleted") 742 + shared.AssertErrorContains(t, err, "failed to read edited content", "createInteractive should fail when temp file is deleted") 744 743 }) 745 744 }) 746 745 ··· 750 749 t.Run("creates note successfully without editor prompt", func(t *testing.T) { 751 750 handler := NewHandlerTestHelper(t) 752 751 err := handler.CreateWithOptions(ctx, "Test Note", "Test content", "", false, false) 753 - Expect.AssertNoError(t, err, "CreateWithOptions should succeed") 752 + shared.AssertNoError(t, err, "CreateWithOptions should succeed") 754 753 }) 755 754 756 755 t.Run("creates note successfully with editor prompt disabled", func(t *testing.T) { 757 756 handler := NewHandlerTestHelper(t) 758 757 err := handler.CreateWithOptions(ctx, "Another Test Note", "More content", "", false, false) 759 - Expect.AssertNoError(t, err, "CreateWithOptions should succeed") 758 + shared.AssertNoError(t, err, "CreateWithOptions should succeed") 760 759 }) 761 760 762 761 t.Run("handles database error during creation", func(t *testing.T) { ··· 765 764 cancel() 766 765 767 766 err := handler.CreateWithOptions(cancelCtx, "Test Note", "Test content", "", false, false) 768 - Expect.AssertError(t, err, "failed to create note", "CreateWithOptions should fail with cancelled context") 767 + shared.AssertErrorContains(t, err, "failed to create note", "CreateWithOptions should fail with cancelled context") 769 768 }) 770 769 771 770 t.Run("creates note with empty content", func(t *testing.T) { 772 771 handler := NewHandlerTestHelper(t) 773 772 err := handler.CreateWithOptions(ctx, "Empty Content Note", "", "", false, false) 774 - Expect.AssertNoError(t, err, "CreateWithOptions should succeed with empty content") 773 + shared.AssertNoError(t, err, "CreateWithOptions should succeed with empty content") 775 774 }) 776 775 777 776 t.Run("creates note with empty title", func(t *testing.T) { 778 777 handler := NewHandlerTestHelper(t) 779 778 err := handler.CreateWithOptions(ctx, "", "Content without title", "", false, false) 780 - Expect.AssertNoError(t, err, "CreateWithOptions should succeed with empty title") 779 + shared.AssertNoError(t, err, "CreateWithOptions should succeed with empty title") 781 780 }) 782 781 783 782 t.Run("handles editor prompt with no editor available", func(t *testing.T) { ··· 789 788 envHelper.SetEnv("PATH", "") 790 789 791 790 err := handler.CreateWithOptions(ctx, "Test Note", "Test content", "", false, true) 792 - Expect.AssertNoError(t, err, "CreateWithOptions should succeed even when no editor is available") 791 + shared.AssertNoError(t, err, "CreateWithOptions should succeed even when no editor is available") 793 792 }) 794 793 }) 795 794
-2
internal/handlers/seed_test.go
··· 10 10 "github.com/stormlightlabs/noteleaf/internal/store" 11 11 ) 12 12 13 - // setupSeedTest removed - use NewHandlerTestSuite(t) instead 14 - 15 13 func countRecords(t *testing.T, db *store.Database, table string) int { 16 14 t.Helper() 17 15
+5 -5
internal/handlers/tasks_test.go
··· 13 13 14 14 "github.com/google/uuid" 15 15 "github.com/stormlightlabs/noteleaf/internal/models" 16 - "github.com/stormlightlabs/noteleaf/internal/repo" 16 + "github.com/stormlightlabs/noteleaf/internal/shared" 17 17 "github.com/stormlightlabs/noteleaf/internal/ui" 18 18 ) 19 19 ··· 78 78 t.Run("creates task successfully", func(t *testing.T) { 79 79 desc := "Buy groceries and cook dinner" 80 80 err := handler.Create(ctx, desc, "", "", "", "", "", "", "", "", []string{}) 81 - repo.AssertNoError(t, err, "CreateTask should succeed") 81 + shared.AssertNoError(t, err, "CreateTask should succeed") 82 82 83 83 tasks, err := handler.repos.Tasks.GetPending(ctx) 84 - repo.AssertNoError(t, err, "Failed to get pending tasks") 84 + shared.AssertNoError(t, err, "Failed to get pending tasks") 85 85 86 86 if len(tasks) != 1 { 87 87 t.Errorf("Expected 1 task, got %d", len(tasks)) ··· 105 105 t.Run("fails with empty description", func(t *testing.T) { 106 106 desc := "" 107 107 err := handler.Create(ctx, desc, "", "", "", "", "", "", "", "", []string{}) 108 - repo.AssertError(t, err, "Expected error for empty description") 109 - repo.AssertContains(t, err.Error(), "task description required", "Error message should mention required description") 108 + shared.AssertError(t, err, "Expected error for empty description") 109 + shared.AssertContains(t, err.Error(), "task description required", "Error message should mention required description") 110 110 }) 111 111 112 112 t.Run("creates task with flags", func(t *testing.T) {
+7 -206
internal/handlers/test_utilities.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 5 "fmt" 7 6 "io" 8 - "net/http" 9 - "net/http/httptest" 10 7 "os" 11 8 "path/filepath" 12 9 "strconv" ··· 24 21 "github.com/stormlightlabs/noteleaf/internal/ui" 25 22 ) 26 23 27 - // HandlerTestHelper wraps NoteHandler with test-specific functionality 24 + // HandlerTestHelper wraps [NoteHandler] with test-specific functionality 28 25 // 29 - // Uses HandlerTestSuite internally to avoid code duplication 26 + // Uses [HandlerTestSuite] internally to avoid code duplication 30 27 type HandlerTestHelper struct { 31 28 *NoteHandler 32 29 suite *HandlerTestSuite 33 30 } 34 31 35 - // NewHandlerTestHelper creates a NoteHandler with isolated test database 32 + // NewHandlerTestHelper creates a [NoteHandler] with isolated test database 36 33 func NewHandlerTestHelper(t *testing.T) *HandlerTestHelper { 37 34 suite := NewHandlerTestSuite(t) 38 35 ··· 126 123 return me 127 124 } 128 125 129 - // GetEditorFunc returns the editor function for use with NoteHandler 126 + // GetEditorFunc returns the editor function for use with [NoteHandler] 130 127 func (me *MockEditor) GetEditorFunc() editorFunc { 131 128 return func(editor, filePath string) error { 132 129 if me.shouldFail { ··· 192 189 dth.handler.db = db 193 190 } 194 191 195 - // AssertionHelpers provides test assertion utilities 196 - type AssertionHelpers struct{} 197 - 198 - // AssertError checks that an error occurred and optionally contains expected text 199 - func (ah AssertionHelpers) AssertError(t *testing.T, err error, expectedSubstring string, msg string) { 200 - t.Helper() 201 - if err == nil { 202 - t.Errorf("%s: expected error but got none", msg) 203 - return 204 - } 205 - if expectedSubstring != "" && !containsString(err.Error(), expectedSubstring) { 206 - t.Errorf("%s: expected error containing %q, got: %v", msg, expectedSubstring, err) 207 - } 208 - } 209 - 210 - // AssertNoError checks that no error occurred 211 - func (ah AssertionHelpers) AssertNoError(t *testing.T, err error, msg string) { 212 - t.Helper() 213 - if err != nil { 214 - t.Errorf("%s: unexpected error: %v", msg, err) 215 - } 216 - } 217 - 218 192 // EnvironmentTestHelper provides environment manipulation utilities for testing 219 - // 220 - // Use this for tests requiring fine-grained environment control beyond HandlerTestSuite. 221 - // Examples: testing missing EDITOR, invalid PATH, corrupt TMPDIR, etc. 222 193 type EnvironmentTestHelper struct { 223 194 originalVars map[string]string 224 195 } ··· 281 252 return tempDir, nil 282 253 } 283 254 284 - // Helper function to check if string contains substring (case-insensitive) 285 - func containsString(haystack, needle string) bool { 286 - if needle == "" { 287 - return true 288 - } 289 - return len(haystack) >= len(needle) && 290 - haystack[len(haystack)-len(needle):] == needle || 291 - haystack[:len(needle)] == needle || 292 - (len(haystack) > len(needle) && 293 - func() bool { 294 - for i := 1; i <= len(haystack)-len(needle); i++ { 295 - if haystack[i:i+len(needle)] == needle { 296 - return true 297 - } 298 - } 299 - return false 300 - }()) 301 - } 302 - 303 - // ArticleTestHelper wraps ArticleHandler with test-specific functionality 255 + // ArticleTestHelper wraps [ArticleHandler] with test-specific functionality 304 256 type ArticleTestHelper struct { 305 257 *ArticleHandler 306 258 suite *HandlerTestSuite 307 259 } 308 260 309 - // NewArticleTestHelper creates an ArticleHandler with isolated test database 261 + // NewArticleTestHelper creates an [ArticleHandler] with isolated test database 310 262 func NewArticleTestHelper(t *testing.T) *ArticleTestHelper { 311 263 suite := NewHandlerTestSuite(t) 312 264 ··· 373 325 } 374 326 } 375 327 376 - var Expect = AssertionHelpers{} 377 - 378 - // HTTPMockServer provides utilities for mocking HTTP services in tests 379 - type HTTPMockServer struct { 380 - server *httptest.Server 381 - requests []*http.Request 382 - } 383 - 384 - // NewMockServer creates a new mock HTTP server 385 - func NewMockServer() *HTTPMockServer { 386 - mock := &HTTPMockServer{ 387 - requests: make([]*http.Request, 0), 388 - } 389 - return mock 390 - } 391 - 392 - // WithHandler sets up the mock server with a custom handler 393 - func (m *HTTPMockServer) WithHandler(handler http.HandlerFunc) *HTTPMockServer { 394 - m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 395 - m.requests = append(m.requests, r) 396 - handler(w, r) 397 - })) 398 - return m 399 - } 400 - 401 - // URL returns the mock server URL 402 - func (m *HTTPMockServer) URL() string { 403 - if m.server == nil { 404 - panic("mock server not initialized - call WithHandler first") 405 - } 406 - return m.server.URL 407 - } 408 - 409 - // Close closes the mock server 410 - func (m *HTTPMockServer) Close() { 411 - if m.server != nil { 412 - m.server.Close() 413 - } 414 - } 415 - 416 - // GetRequests returns all recorded HTTP requests 417 - func (m *HTTPMockServer) GetRequests() []*http.Request { 418 - return m.requests 419 - } 420 - 421 - // GetLastRequest returns the last recorded HTTP request 422 - func (m *HTTPMockServer) GetLastRequest() *http.Request { 423 - if len(m.requests) == 0 { 424 - return nil 425 - } 426 - return m.requests[len(m.requests)-1] 427 - } 428 - 429 - // MockOpenLibraryResponse creates a mock OpenLibrary search response 328 + // MockOpenLibraryResponse creates a mocked instance of [services.OpenLibrarySearchResponse] 430 329 func MockOpenLibraryResponse(books []MockBook) services.OpenLibrarySearchResponse { 431 330 docs := make([]services.OpenLibrarySearchDoc, len(books)) 432 331 for i, book := range books { ··· 487 386 Link string 488 387 Score string 489 388 Type string 490 - } 491 - 492 - // HTTPErrorMockServer creates a mock server that returns HTTP errors 493 - func HTTPErrorMockServer(statusCode int, message string) *HTTPMockServer { 494 - return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 495 - http.Error(w, message, statusCode) 496 - }) 497 - } 498 - 499 - // JSONMockServer creates a mock server that returns JSON responses 500 - func JSONMockServer(response any) *HTTPMockServer { 501 - return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 502 - w.Header().Set("Content-Type", "application/json") 503 - if err := json.NewEncoder(w).Encode(response); err != nil { 504 - http.Error(w, "Failed to encode response", http.StatusInternalServerError) 505 - } 506 - }) 507 - } 508 - 509 - // TimeoutMockServer creates a mock server that simulates timeouts 510 - func TimeoutMockServer(delay time.Duration) *HTTPMockServer { 511 - return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 512 - time.Sleep(delay) 513 - w.WriteHeader(http.StatusOK) 514 - }) 515 - } 516 - 517 - // InvalidJSONMockServer creates a mock server that returns malformed JSON 518 - func InvalidJSONMockServer() *HTTPMockServer { 519 - return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 520 - w.Header().Set("Content-Type", "application/json") 521 - w.Write([]byte(`{"invalid": json`)) 522 - }) 523 - } 524 - 525 - // EmptyResponseMockServer creates a mock server that returns empty responses 526 - func EmptyResponseMockServer() *HTTPMockServer { 527 - return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 528 - w.WriteHeader(http.StatusOK) 529 - }) 530 - } 531 - 532 - // ServiceTestHelper provides utilities for testing services with HTTP mocks 533 - type ServiceTestHelper struct { 534 - mockServers []*HTTPMockServer 535 - } 536 - 537 - // NewServiceTestHelper creates a new service test helper 538 - func NewServiceTestHelper() *ServiceTestHelper { 539 - return &ServiceTestHelper{ 540 - mockServers: make([]*HTTPMockServer, 0), 541 - } 542 - } 543 - 544 - // AddMockServer adds a mock server and returns its URL 545 - func (sth *ServiceTestHelper) AddMockServer(server *HTTPMockServer) string { 546 - sth.mockServers = append(sth.mockServers, server) 547 - return server.URL() 548 - } 549 - 550 - // Cleanup closes all mock servers 551 - func (sth *ServiceTestHelper) Cleanup() { 552 - for _, server := range sth.mockServers { 553 - server.Close() 554 - } 555 - } 556 - 557 - // AssertRequestMade verifies that a request was made to the mock server 558 - func (sth *ServiceTestHelper) AssertRequestMade(t *testing.T, server *HTTPMockServer, expectedPath string) { 559 - t.Helper() 560 - if len(server.requests) == 0 { 561 - t.Error("Expected HTTP request to be made but none were recorded") 562 - return 563 - } 564 - 565 - lastReq := server.GetLastRequest() 566 - if lastReq.URL.Path != expectedPath { 567 - t.Errorf("Expected request to path %s, got %s", expectedPath, lastReq.URL.Path) 568 - } 569 389 } 570 390 571 391 // MockMediaFetcher provides a test implementation of Fetchable and Searchable interfaces ··· 1013 833 }, 1014 834 } 1015 835 } 1016 - 1017 - // AssertTaskHasUUID verifies that a task has a non-empty UUID 1018 - func AssertTaskHasUUID(t *testing.T, task *models.Task) { 1019 - t.Helper() 1020 - if task.UUID == "" { 1021 - t.Fatal("Task should have a UUID") 1022 - } 1023 - } 1024 - 1025 - // AssertTaskDatesSet verifies that Entry and Modified timestamps are set 1026 - func AssertTaskDatesSet(t *testing.T, task *models.Task) { 1027 - t.Helper() 1028 - if task.Entry.IsZero() { 1029 - t.Error("Task Entry timestamp should be set") 1030 - } 1031 - if task.Modified.IsZero() { 1032 - t.Error("Task Modified timestamp should be set") 1033 - } 1034 - }
+52 -52
internal/models/models_test.go
··· 78 78 79 79 t.Run("Task Model", func(t *testing.T) { 80 80 t.Run("Status Methods", func(t *testing.T) { 81 - testCases := []struct { 81 + tc := []struct { 82 82 status string 83 83 isCompleted bool 84 84 isPending bool ··· 90 90 {"unknown", false, false, false}, 91 91 } 92 92 93 - for _, tc := range testCases { 94 - task := &Task{Status: tc.status} 93 + for _, tt := range tc { 94 + task := &Task{Status: tt.status} 95 95 96 - if task.IsCompleted() != tc.isCompleted { 97 - t.Errorf("Status %s: expected IsCompleted %v, got %v", tc.status, tc.isCompleted, task.IsCompleted()) 96 + if task.IsCompleted() != tt.isCompleted { 97 + t.Errorf("Status %s: expected IsCompleted %v, got %v", tt.status, tt.isCompleted, task.IsCompleted()) 98 98 } 99 - if task.IsPending() != tc.isPending { 100 - t.Errorf("Status %s: expected IsPending %v, got %v", tc.status, tc.isPending, task.IsPending()) 99 + if task.IsPending() != tt.isPending { 100 + t.Errorf("Status %s: expected IsPending %v, got %v", tt.status, tt.isPending, task.IsPending()) 101 101 } 102 - if task.IsDeleted() != tc.isDeleted { 103 - t.Errorf("Status %s: expected IsDeleted %v, got %v", tc.status, tc.isDeleted, task.IsDeleted()) 102 + if task.IsDeleted() != tt.isDeleted { 103 + t.Errorf("Status %s: expected IsDeleted %v, got %v", tt.status, tt.isDeleted, task.IsDeleted()) 104 104 } 105 105 } 106 106 }) 107 107 108 108 t.Run("New Status Tracking Methods", func(t *testing.T) { 109 - testCases := []struct { 109 + tc := []struct { 110 110 status string 111 111 isTodo bool 112 112 isInProgress bool ··· 122 122 {"unknown", false, false, false, false, false}, 123 123 } 124 124 125 - for _, tc := range testCases { 126 - task := &Task{Status: tc.status} 125 + for _, tt := range tc { 126 + task := &Task{Status: tt.status} 127 127 128 - if task.IsTodo() != tc.isTodo { 129 - t.Errorf("Status %s: expected IsTodo %v, got %v", tc.status, tc.isTodo, task.IsTodo()) 128 + if task.IsTodo() != tt.isTodo { 129 + t.Errorf("Status %s: expected IsTodo %v, got %v", tt.status, tt.isTodo, task.IsTodo()) 130 130 } 131 - if task.IsInProgress() != tc.isInProgress { 132 - t.Errorf("Status %s: expected IsInProgress %v, got %v", tc.status, tc.isInProgress, task.IsInProgress()) 131 + if task.IsInProgress() != tt.isInProgress { 132 + t.Errorf("Status %s: expected IsInProgress %v, got %v", tt.status, tt.isInProgress, task.IsInProgress()) 133 133 } 134 - if task.IsBlocked() != tc.isBlocked { 135 - t.Errorf("Status %s: expected IsBlocked %v, got %v", tc.status, tc.isBlocked, task.IsBlocked()) 134 + if task.IsBlocked() != tt.isBlocked { 135 + t.Errorf("Status %s: expected IsBlocked %v, got %v", tt.status, tt.isBlocked, task.IsBlocked()) 136 136 } 137 - if task.IsDone() != tc.isDone { 138 - t.Errorf("Status %s: expected IsDone %v, got %v", tc.status, tc.isDone, task.IsDone()) 137 + if task.IsDone() != tt.isDone { 138 + t.Errorf("Status %s: expected IsDone %v, got %v", tt.status, tt.isDone, task.IsDone()) 139 139 } 140 - if task.IsAbandoned() != tc.isAbandoned { 141 - t.Errorf("Status %s: expected IsAbandoned %v, got %v", tc.status, tc.isAbandoned, task.IsAbandoned()) 140 + if task.IsAbandoned() != tt.isAbandoned { 141 + t.Errorf("Status %s: expected IsAbandoned %v, got %v", tt.status, tt.isAbandoned, task.IsAbandoned()) 142 142 } 143 143 } 144 144 }) ··· 238 238 }) 239 239 240 240 t.Run("Priority Weight Calculation", func(t *testing.T) { 241 - testCases := []struct { 241 + tc := []struct { 242 242 priority string 243 243 weight int 244 244 }{ ··· 258 258 {"invalid", 0}, 259 259 } 260 260 261 - for _, tc := range testCases { 262 - task := &Task{Priority: tc.priority} 261 + for _, tt := range tc { 262 + task := &Task{Priority: tt.priority} 263 263 weight := task.GetPriorityWeight() 264 - if weight != tc.weight { 265 - t.Errorf("Priority %s: expected weight %d, got %d", tc.priority, tc.weight, weight) 264 + if weight != tt.weight { 265 + t.Errorf("Priority %s: expected weight %d, got %d", tt.priority, tt.weight, weight) 266 266 } 267 267 } 268 268 }) ··· 481 481 482 482 t.Run("Movie Model", func(t *testing.T) { 483 483 t.Run("Status Methods", func(t *testing.T) { 484 - testCases := []struct { 484 + tc := []struct { 485 485 status string 486 486 isWatched bool 487 487 isQueued bool ··· 492 492 {"unknown", false, false}, 493 493 } 494 494 495 - for _, tc := range testCases { 496 - movie := &Movie{Status: tc.status} 495 + for _, tt := range tc { 496 + movie := &Movie{Status: tt.status} 497 497 498 - if movie.IsWatched() != tc.isWatched { 499 - t.Errorf("Status %s: expected IsWatched %v, got %v", tc.status, tc.isWatched, movie.IsWatched()) 498 + if movie.IsWatched() != tt.isWatched { 499 + t.Errorf("Status %s: expected IsWatched %v, got %v", tt.status, tt.isWatched, movie.IsWatched()) 500 500 } 501 - if movie.IsQueued() != tc.isQueued { 502 - t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, movie.IsQueued()) 501 + if movie.IsQueued() != tt.isQueued { 502 + t.Errorf("Status %s: expected IsQueued %v, got %v", tt.status, tt.isQueued, movie.IsQueued()) 503 503 } 504 504 } 505 505 }) ··· 507 507 508 508 t.Run("TV Show Model", func(t *testing.T) { 509 509 t.Run("Status Methods", func(t *testing.T) { 510 - testCases := []struct { 510 + tc := []struct { 511 511 status string 512 512 isWatching bool 513 513 isWatched bool ··· 520 520 {"unknown", false, false, false}, 521 521 } 522 522 523 - for _, tc := range testCases { 524 - tvShow := &TVShow{Status: tc.status} 523 + for _, tt := range tc { 524 + tvShow := &TVShow{Status: tt.status} 525 525 526 - if tvShow.IsWatching() != tc.isWatching { 527 - t.Errorf("Status %s: expected IsWatching %v, got %v", tc.status, tc.isWatching, tvShow.IsWatching()) 526 + if tvShow.IsWatching() != tt.isWatching { 527 + t.Errorf("Status %s: expected IsWatching %v, got %v", tt.status, tt.isWatching, tvShow.IsWatching()) 528 528 } 529 - if tvShow.IsWatched() != tc.isWatched { 530 - t.Errorf("Status %s: expected IsWatched %v, got %v", tc.status, tc.isWatched, tvShow.IsWatched()) 529 + if tvShow.IsWatched() != tt.isWatched { 530 + t.Errorf("Status %s: expected IsWatched %v, got %v", tt.status, tt.isWatched, tvShow.IsWatched()) 531 531 } 532 - if tvShow.IsQueued() != tc.isQueued { 533 - t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, tvShow.IsQueued()) 532 + if tvShow.IsQueued() != tt.isQueued { 533 + t.Errorf("Status %s: expected IsQueued %v, got %v", tt.status, tt.isQueued, tvShow.IsQueued()) 534 534 } 535 535 } 536 536 }) ··· 538 538 539 539 t.Run("Book Model", func(t *testing.T) { 540 540 t.Run("Status Methods", func(t *testing.T) { 541 - testCases := []struct { 541 + tc := []struct { 542 542 status string 543 543 isReading bool 544 544 isFinished bool ··· 551 551 {"unknown", false, false, false}, 552 552 } 553 553 554 - for _, tc := range testCases { 555 - book := &Book{Status: tc.status} 554 + for _, tt := range tc { 555 + book := &Book{Status: tt.status} 556 556 557 - if book.IsReading() != tc.isReading { 558 - t.Errorf("Status %s: expected IsReading %v, got %v", tc.status, tc.isReading, book.IsReading()) 557 + if book.IsReading() != tt.isReading { 558 + t.Errorf("Status %s: expected IsReading %v, got %v", tt.status, tt.isReading, book.IsReading()) 559 559 } 560 - if book.IsFinished() != tc.isFinished { 561 - t.Errorf("Status %s: expected IsFinished %v, got %v", tc.status, tc.isFinished, book.IsFinished()) 560 + if book.IsFinished() != tt.isFinished { 561 + t.Errorf("Status %s: expected IsFinished %v, got %v", tt.status, tt.isFinished, book.IsFinished()) 562 562 } 563 - if book.IsQueued() != tc.isQueued { 564 - t.Errorf("Status %s: expected IsQueued %v, got %v", tc.status, tc.isQueued, book.IsQueued()) 563 + if book.IsQueued() != tt.isQueued { 564 + t.Errorf("Status %s: expected IsQueued %v, got %v", tt.status, tt.isQueued, book.IsQueued()) 565 565 } 566 566 } 567 567 })
+113 -112
internal/repo/article_repository_test.go
··· 8 8 9 9 _ "github.com/mattn/go-sqlite3" 10 10 "github.com/stormlightlabs/noteleaf/internal/models" 11 + "github.com/stormlightlabs/noteleaf/internal/shared" 11 12 ) 12 13 13 14 func TestArticleRepository(t *testing.T) { ··· 20 21 21 22 article := CreateSampleArticle() 22 23 id, err := repo.Create(ctx, article) 23 - AssertNoError(t, err, "Failed to create article") 24 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 25 - AssertEqual(t, id, article.ID, "Expected article ID to be set correctly") 26 - AssertFalse(t, article.Created.IsZero(), "Expected Created timestamp to be set") 27 - AssertFalse(t, article.Modified.IsZero(), "Expected Modified timestamp to be set") 24 + shared.AssertNoError(t, err, "Failed to create article") 25 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 26 + shared.AssertEqual(t, id, article.ID, "Expected article ID to be set correctly") 27 + shared.AssertFalse(t, article.Created.IsZero(), "Expected Created timestamp to be set") 28 + shared.AssertFalse(t, article.Modified.IsZero(), "Expected Modified timestamp to be set") 28 29 }) 29 30 30 31 t.Run("Get article", func(t *testing.T) { ··· 33 34 34 35 original := CreateSampleArticle() 35 36 id, err := repo.Create(ctx, original) 36 - AssertNoError(t, err, "Failed to create article") 37 + shared.AssertNoError(t, err, "Failed to create article") 37 38 38 39 retrieved, err := repo.Get(ctx, id) 39 - AssertNoError(t, err, "Failed to get article") 40 - AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 41 - AssertEqual(t, original.URL, retrieved.URL, "URL mismatch") 42 - AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 43 - AssertEqual(t, original.Author, retrieved.Author, "Author mismatch") 44 - AssertEqual(t, original.Date, retrieved.Date, "Date mismatch") 45 - AssertEqual(t, original.MarkdownPath, retrieved.MarkdownPath, "MarkdownPath mismatch") 46 - AssertEqual(t, original.HTMLPath, retrieved.HTMLPath, "HTMLPath mismatch") 40 + shared.AssertNoError(t, err, "Failed to get article") 41 + shared.AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 42 + shared.AssertEqual(t, original.URL, retrieved.URL, "URL mismatch") 43 + shared.AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 44 + shared.AssertEqual(t, original.Author, retrieved.Author, "Author mismatch") 45 + shared.AssertEqual(t, original.Date, retrieved.Date, "Date mismatch") 46 + shared.AssertEqual(t, original.MarkdownPath, retrieved.MarkdownPath, "MarkdownPath mismatch") 47 + shared.AssertEqual(t, original.HTMLPath, retrieved.HTMLPath, "HTMLPath mismatch") 47 48 }) 48 49 49 50 t.Run("Update article", func(t *testing.T) { ··· 52 53 53 54 article := CreateSampleArticle() 54 55 id, err := repo.Create(ctx, article) 55 - AssertNoError(t, err, "Failed to create article") 56 + shared.AssertNoError(t, err, "Failed to create article") 56 57 57 58 originalModified := article.Modified 58 59 article.Title = "Updated Title" ··· 62 63 article.HTMLPath = "/updated/path/article.html" 63 64 64 65 err = repo.Update(ctx, article) 65 - AssertNoError(t, err, "Failed to update article") 66 + shared.AssertNoError(t, err, "Failed to update article") 66 67 67 68 retrieved, err := repo.Get(ctx, id) 68 - AssertNoError(t, err, "Failed to get updated article") 69 - AssertEqual(t, "Updated Title", retrieved.Title, "Expected updated title") 70 - AssertEqual(t, "Updated Author", retrieved.Author, "Expected updated author") 71 - AssertEqual(t, "2024-01-02", retrieved.Date, "Expected updated date") 72 - AssertEqual(t, "/updated/path/article.md", retrieved.MarkdownPath, "Expected updated markdown path") 73 - AssertEqual(t, "/updated/path/article.html", retrieved.HTMLPath, "Expected updated HTML path") 74 - AssertTrue(t, retrieved.Modified.After(originalModified), "Expected Modified timestamp to be updated") 69 + shared.AssertNoError(t, err, "Failed to get updated article") 70 + shared.AssertEqual(t, "Updated Title", retrieved.Title, "Expected updated title") 71 + shared.AssertEqual(t, "Updated Author", retrieved.Author, "Expected updated author") 72 + shared.AssertEqual(t, "2024-01-02", retrieved.Date, "Expected updated date") 73 + shared.AssertEqual(t, "/updated/path/article.md", retrieved.MarkdownPath, "Expected updated markdown path") 74 + shared.AssertEqual(t, "/updated/path/article.html", retrieved.HTMLPath, "Expected updated HTML path") 75 + shared.AssertTrue(t, retrieved.Modified.After(originalModified), "Expected Modified timestamp to be updated") 75 76 }) 76 77 77 78 t.Run("Delete article", func(t *testing.T) { ··· 80 81 81 82 article := CreateSampleArticle() 82 83 id, err := repo.Create(ctx, article) 83 - AssertNoError(t, err, "Failed to create article") 84 + shared.AssertNoError(t, err, "Failed to create article") 84 85 85 86 err = repo.Delete(ctx, id) 86 - AssertNoError(t, err, "Failed to delete article") 87 + shared.AssertNoError(t, err, "Failed to delete article") 87 88 88 89 _, err = repo.Get(ctx, id) 89 - AssertError(t, err, "Expected error when getting deleted article") 90 + shared.AssertError(t, err, "Expected error when getting deleted article") 90 91 }) 91 92 }) 92 93 ··· 99 100 article := CreateSampleArticle() 100 101 article.Title = "" 101 102 _, err := repo.Create(ctx, article) 102 - AssertError(t, err, "Expected error when creating article with empty title") 103 + shared.AssertError(t, err, "Expected error when creating article with empty title") 103 104 }) 104 105 105 106 t.Run("Fails with missing URL", func(t *testing.T) { 106 107 article := CreateSampleArticle() 107 108 article.URL = "" 108 109 _, err := repo.Create(ctx, article) 109 - AssertError(t, err, "Expected error when creating article with empty URL") 110 + shared.AssertError(t, err, "Expected error when creating article with empty URL") 110 111 }) 111 112 112 113 t.Run("Fails with duplicate URL", func(t *testing.T) { 113 114 article1 := CreateSampleArticle() 114 115 _, err := repo.Create(ctx, article1) 115 - AssertNoError(t, err, "Failed to create first article") 116 + shared.AssertNoError(t, err, "Failed to create first article") 116 117 117 118 article2 := CreateSampleArticle() 118 119 article2.URL = article1.URL 119 120 _, err = repo.Create(ctx, article2) 120 - AssertError(t, err, "Expected error when creating article with duplicate URL") 121 + shared.AssertError(t, err, "Expected error when creating article with duplicate URL") 121 122 }) 122 123 123 124 t.Run("Fails with missing markdown path", func(t *testing.T) { 124 125 article := CreateSampleArticle() 125 126 article.MarkdownPath = "" 126 127 _, err := repo.Create(ctx, article) 127 - AssertError(t, err, "Expected error when creating article with empty markdown path") 128 - AssertContains(t, err.Error(), "MarkdownPath", "Expected MarkdownPath validation error") 128 + shared.AssertError(t, err, "Expected error when creating article with empty markdown path") 129 + shared.AssertContains(t, err.Error(), "MarkdownPath", "Expected MarkdownPath validation error") 129 130 }) 130 131 131 132 t.Run("Fails with missing HTML path", func(t *testing.T) { 132 133 article := CreateSampleArticle() 133 134 article.HTMLPath = "" 134 135 _, err := repo.Create(ctx, article) 135 - AssertError(t, err, "Expected error when creating article with empty HTML path") 136 - AssertContains(t, err.Error(), "HTMLPath", "Expected HTMLPath validation error") 136 + shared.AssertError(t, err, "Expected error when creating article with empty HTML path") 137 + shared.AssertContains(t, err.Error(), "HTMLPath", "Expected HTMLPath validation error") 137 138 }) 138 139 139 140 t.Run("Fails with invalid URL format", func(t *testing.T) { 140 141 article := CreateSampleArticle() 141 142 article.URL = "not-a-valid-url" 142 143 _, err := repo.Create(ctx, article) 143 - AssertError(t, err, "Expected error when creating article with invalid URL format") 144 - AssertContains(t, err.Error(), "URL", "Expected URL format validation error") 144 + shared.AssertError(t, err, "Expected error when creating article with invalid URL format") 145 + shared.AssertContains(t, err.Error(), "URL", "Expected URL format validation error") 145 146 }) 146 147 147 148 t.Run("Fails with invalid date format", func(t *testing.T) { 148 149 article := CreateSampleArticle() 149 150 article.Date = "invalid-date" 150 151 _, err := repo.Create(ctx, article) 151 - AssertError(t, err, "Expected error when creating article with invalid date format") 152 - AssertContains(t, err.Error(), "Date", "Expected date validation error") 152 + shared.AssertError(t, err, "Expected error when creating article with invalid date format") 153 + shared.AssertContains(t, err.Error(), "Date", "Expected date validation error") 153 154 }) 154 155 155 156 t.Run("Fails with title too long", func(t *testing.T) { 156 157 article := CreateSampleArticle() 157 158 article.Title = strings.Repeat("a", 501) 158 159 _, err := repo.Create(ctx, article) 159 - AssertError(t, err, "Expected error when creating article with title too long") 160 - AssertContains(t, err.Error(), "Title", "Expected title length validation error") 160 + shared.AssertError(t, err, "Expected error when creating article with title too long") 161 + shared.AssertContains(t, err.Error(), "Title", "Expected title length validation error") 161 162 }) 162 163 163 164 t.Run("Fails with author too long", func(t *testing.T) { 164 165 article := CreateSampleArticle() 165 166 article.Author = strings.Repeat("a", 201) 166 167 _, err := repo.Create(ctx, article) 167 - AssertError(t, err, "Expected error when creating article with author too long") 168 - AssertContains(t, err.Error(), "Author", "Expected author length validation error") 168 + shared.AssertError(t, err, "Expected error when creating article with author too long") 169 + shared.AssertContains(t, err.Error(), "Author", "Expected author length validation error") 169 170 }) 170 171 171 172 t.Run("Validates timestamps", func(t *testing.T) { ··· 174 175 article.Modified = now 175 176 article.Created = now.Add(time.Hour) 176 177 err := repo.Validate(article) 177 - AssertError(t, err, "Expected error when created is after modified") 178 - AssertContains(t, err.Error(), "Created", "Expected timestamp validation error") 178 + shared.AssertError(t, err, "Expected error when created is after modified") 179 + shared.AssertContains(t, err.Error(), "Created", "Expected timestamp validation error") 179 180 }) 180 181 181 182 t.Run("Succeeds when created equals modified", func(t *testing.T) { ··· 184 185 article.Created = now 185 186 article.Modified = now 186 187 err := repo.Validate(article) 187 - AssertNoError(t, err, "Expected no error when created equals modified") 188 + shared.AssertNoError(t, err, "Expected no error when created equals modified") 188 189 }) 189 190 190 191 t.Run("Succeeds when created is before modified", func(t *testing.T) { ··· 193 194 article.Created = now 194 195 article.Modified = now.Add(time.Hour) 195 196 err := repo.Validate(article) 196 - AssertNoError(t, err, "Expected no error when created is before modified") 197 + shared.AssertNoError(t, err, "Expected no error when created is before modified") 197 198 }) 198 199 199 200 t.Run("Succeeds with valid optional fields", func(t *testing.T) { ··· 201 202 article.Date = "2024-01-01" 202 203 article.Author = "Test Author" 203 204 err := repo.Validate(article) 204 - AssertNoError(t, err, "Expected no error with valid optional fields") 205 + shared.AssertNoError(t, err, "Expected no error with valid optional fields") 205 206 }) 206 207 207 208 t.Run("Succeeds with empty optional fields", func(t *testing.T) { ··· 209 210 article.Date = "" 210 211 article.Author = "" 211 212 err := repo.Validate(article) 212 - AssertNoError(t, err, "Expected no error with empty optional fields") 213 + shared.AssertNoError(t, err, "Expected no error with empty optional fields") 213 214 }) 214 215 }) 215 216 ··· 221 222 t.Run("Successfully retrieves article by URL", func(t *testing.T) { 222 223 original := CreateSampleArticle() 223 224 _, err := repo.Create(ctx, original) 224 - AssertNoError(t, err, "Failed to create article") 225 + shared.AssertNoError(t, err, "Failed to create article") 225 226 226 227 retrieved, err := repo.GetByURL(ctx, original.URL) 227 - AssertNoError(t, err, "Failed to get article by URL") 228 - AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 229 - AssertEqual(t, original.URL, retrieved.URL, "URL mismatch") 230 - AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 228 + shared.AssertNoError(t, err, "Failed to get article by URL") 229 + shared.AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 230 + shared.AssertEqual(t, original.URL, retrieved.URL, "URL mismatch") 231 + shared.AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 231 232 }) 232 233 233 234 t.Run("Fails when URL not found", func(t *testing.T) { 234 235 nonexistent := "https://example.com/nonexistent" 235 236 _, err := repo.GetByURL(ctx, nonexistent) 236 - AssertError(t, err, "Expected error when getting article by non-existent URL") 237 - AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 237 + shared.AssertError(t, err, "Expected error when getting article by non-existent URL") 238 + shared.AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 238 239 }) 239 240 }) 240 241 ··· 272 273 273 274 for _, article := range articles { 274 275 _, err := repo.Create(ctx, article) 275 - AssertNoError(t, err, "Failed to create test article") 276 + shared.AssertNoError(t, err, "Failed to create test article") 276 277 } 277 278 278 279 t.Run("List all articles", func(t *testing.T) { 279 280 results, err := repo.List(ctx, nil) 280 - AssertNoError(t, err, "Failed to list all articles") 281 - AssertEqual(t, 3, len(results), "Expected 3 articles") 281 + shared.AssertNoError(t, err, "Failed to list all articles") 282 + shared.AssertEqual(t, 3, len(results), "Expected 3 articles") 282 283 }) 283 284 284 285 t.Run("Filter by title", func(t *testing.T) { 285 286 opts := &ArticleListOptions{Title: "Important"} 286 287 results, err := repo.List(ctx, opts) 287 - AssertNoError(t, err, "Failed to list articles by title") 288 - AssertEqual(t, 1, len(results), "Expected 1 article matching title") 289 - AssertEqual(t, "Important Article", results[0].Title, "Wrong article returned") 288 + shared.AssertNoError(t, err, "Failed to list articles by title") 289 + shared.AssertEqual(t, 1, len(results), "Expected 1 article matching title") 290 + shared.AssertEqual(t, "Important Article", results[0].Title, "Wrong article returned") 290 291 }) 291 292 292 293 t.Run("Filter by author", func(t *testing.T) { 293 294 opts := &ArticleListOptions{Author: "John Doe"} 294 295 results, err := repo.List(ctx, opts) 295 - AssertNoError(t, err, "Failed to list articles by author") 296 - AssertEqual(t, 2, len(results), "Expected 2 articles by John Doe") 296 + shared.AssertNoError(t, err, "Failed to list articles by author") 297 + shared.AssertEqual(t, 2, len(results), "Expected 2 articles by John Doe") 297 298 }) 298 299 299 300 t.Run("Filter by URL", func(t *testing.T) { 300 301 opts := &ArticleListOptions{URL: "different.com"} 301 302 results, err := repo.List(ctx, opts) 302 - AssertNoError(t, err, "Failed to list articles by URL") 303 - AssertEqual(t, 1, len(results), "Expected 1 article from different.com") 303 + shared.AssertNoError(t, err, "Failed to list articles by URL") 304 + shared.AssertEqual(t, 1, len(results), "Expected 1 article from different.com") 304 305 }) 305 306 306 307 t.Run("Filter by date range", func(t *testing.T) { 307 308 opts := &ArticleListOptions{DateFrom: "2024-01-02", DateTo: "2024-01-03"} 308 309 results, err := repo.List(ctx, opts) 309 - AssertNoError(t, err, "Failed to list articles by date range") 310 - AssertEqual(t, 2, len(results), "Expected 2 articles in date range") 310 + shared.AssertNoError(t, err, "Failed to list articles by date range") 311 + shared.AssertEqual(t, 2, len(results), "Expected 2 articles in date range") 311 312 }) 312 313 313 314 t.Run("With limit", func(t *testing.T) { 314 315 opts := &ArticleListOptions{Limit: 2} 315 316 results, err := repo.List(ctx, opts) 316 - AssertNoError(t, err, "Failed to list articles with limit") 317 - AssertEqual(t, 2, len(results), "Expected 2 articles due to limit") 317 + shared.AssertNoError(t, err, "Failed to list articles with limit") 318 + shared.AssertEqual(t, 2, len(results), "Expected 2 articles due to limit") 318 319 }) 319 320 320 321 t.Run("With limit and offset", func(t *testing.T) { 321 322 opts := &ArticleListOptions{Limit: 2, Offset: 1} 322 323 results, err := repo.List(ctx, opts) 323 - AssertNoError(t, err, "Failed to list articles with limit and offset") 324 - AssertEqual(t, 2, len(results), "Expected 2 articles due to limit") 324 + shared.AssertNoError(t, err, "Failed to list articles with limit and offset") 325 + shared.AssertEqual(t, 2, len(results), "Expected 2 articles due to limit") 325 326 }) 326 327 327 328 t.Run("Multiple filters", func(t *testing.T) { 328 329 opts := &ArticleListOptions{Author: "John Doe", DateFrom: "2024-01-02"} 329 330 results, err := repo.List(ctx, opts) 330 - AssertNoError(t, err, "Failed to list articles with multiple filters") 331 - AssertEqual(t, 1, len(results), "Expected 1 article matching all filters") 332 - AssertEqual(t, "Important Article", results[0].Title, "Wrong article returned") 331 + shared.AssertNoError(t, err, "Failed to list articles with multiple filters") 332 + shared.AssertEqual(t, 1, len(results), "Expected 1 article matching all filters") 333 + shared.AssertEqual(t, "Important Article", results[0].Title, "Wrong article returned") 333 334 }) 334 335 335 336 t.Run("No results", func(t *testing.T) { 336 337 opts := &ArticleListOptions{Title: "Nonexistent"} 337 338 results, err := repo.List(ctx, opts) 338 - AssertNoError(t, err, "Failed to list articles") 339 - AssertEqual(t, 0, len(results), "Expected no articles") 339 + shared.AssertNoError(t, err, "Failed to list articles") 340 + shared.AssertEqual(t, 0, len(results), "Expected no articles") 340 341 }) 341 342 }) 342 343 ··· 359 360 360 361 for _, article := range articles { 361 362 _, err := repo.Create(ctx, article) 362 - AssertNoError(t, err, "Failed to create test article") 363 + shared.AssertNoError(t, err, "Failed to create test article") 363 364 } 364 365 365 366 t.Run("Count all articles", func(t *testing.T) { 366 367 count, err := repo.Count(ctx, nil) 367 - AssertNoError(t, err, "Failed to count articles") 368 - AssertEqual(t, int64(2), count, "Expected 2 articles") 368 + shared.AssertNoError(t, err, "Failed to count articles") 369 + shared.AssertEqual(t, int64(2), count, "Expected 2 articles") 369 370 }) 370 371 371 372 t.Run("Count with filter", func(t *testing.T) { 372 373 opts := &ArticleListOptions{Author: "Test Author"} 373 374 count, err := repo.Count(ctx, opts) 374 - AssertNoError(t, err, "Failed to count articles with filter") 375 - AssertEqual(t, int64(1), count, "Expected 1 article by Test Author") 375 + shared.AssertNoError(t, err, "Failed to count articles with filter") 376 + shared.AssertEqual(t, int64(1), count, "Expected 1 article by Test Author") 376 377 }) 377 378 378 379 t.Run("Count with no results", func(t *testing.T) { 379 380 opts := &ArticleListOptions{Title: "Nonexistent"} 380 381 count, err := repo.Count(ctx, opts) 381 - AssertNoError(t, err, "Failed to count articles") 382 - AssertEqual(t, int64(0), count, "Expected 0 articles") 382 + shared.AssertNoError(t, err, "Failed to count articles") 383 + shared.AssertEqual(t, int64(0), count, "Expected 0 articles") 383 384 }) 384 385 }) 385 386 ··· 390 391 391 392 article := CreateSampleArticle() 392 393 id, err := repo.Create(ctx, article) 393 - AssertNoError(t, err, "Failed to create article") 394 + shared.AssertNoError(t, err, "Failed to create article") 394 395 395 396 t.Run("Create with cancelled context", func(t *testing.T) { 396 397 newArticle := CreateSampleArticle() ··· 437 438 438 439 t.Run("Get non-existent article", func(t *testing.T) { 439 440 _, err := repo.Get(ctx, 99999) 440 - AssertError(t, err, "Expected error for non-existent article") 441 - AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 441 + shared.AssertError(t, err, "Expected error for non-existent article") 442 + shared.AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 442 443 }) 443 444 444 445 t.Run("Update non-existent article", func(t *testing.T) { 445 446 article := CreateSampleArticle() 446 447 article.ID = 99999 447 448 err := repo.Update(ctx, article) 448 - AssertError(t, err, "Expected error when updating non-existent article") 449 - AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 449 + shared.AssertError(t, err, "Expected error when updating non-existent article") 450 + shared.AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 450 451 }) 451 452 452 453 t.Run("Delete non-existent article", func(t *testing.T) { 453 454 err := repo.Delete(ctx, 99999) 454 - AssertError(t, err, "Expected error when deleting non-existent article") 455 - AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 455 + shared.AssertError(t, err, "Expected error when deleting non-existent article") 456 + shared.AssertContains(t, err.Error(), "not found", "Expected 'not found' in error message") 456 457 }) 457 458 458 459 t.Run("Update validation - remove required title", func(t *testing.T) { ··· 461 462 462 463 article := CreateSampleArticle() 463 464 _, err := repo.Create(ctx, article) 464 - AssertNoError(t, err, "Failed to create article") 465 + shared.AssertNoError(t, err, "Failed to create article") 465 466 466 467 article.Title = "" 467 468 err = repo.Update(ctx, article) 468 - AssertError(t, err, "Expected error when updating article with empty title") 469 + shared.AssertError(t, err, "Expected error when updating article with empty title") 469 470 }) 470 471 471 472 t.Run("Update validation - invalid URL format", func(t *testing.T) { ··· 474 475 475 476 article := CreateSampleArticle() 476 477 _, err := repo.Create(ctx, article) 477 - AssertNoError(t, err, "Failed to create article") 478 + shared.AssertNoError(t, err, "Failed to create article") 478 479 479 480 article.URL = "not-a-valid-url" 480 481 err = repo.Update(ctx, article) 481 - AssertError(t, err, "Expected error when updating article with invalid URL format") 482 - AssertContains(t, err.Error(), "URL", "Expected URL format validation error") 482 + shared.AssertError(t, err, "Expected error when updating article with invalid URL format") 483 + shared.AssertContains(t, err.Error(), "URL", "Expected URL format validation error") 483 484 }) 484 485 485 486 t.Run("Update validation - invalid date format", func(t *testing.T) { ··· 488 489 489 490 article := CreateSampleArticle() 490 491 _, err := repo.Create(ctx, article) 491 - AssertNoError(t, err, "Failed to create article") 492 + shared.AssertNoError(t, err, "Failed to create article") 492 493 493 494 article.Date = "invalid-date" 494 495 err = repo.Update(ctx, article) 495 - AssertError(t, err, "Expected error when updating article with invalid date format") 496 - AssertContains(t, err.Error(), "Date", "Expected date validation error") 496 + shared.AssertError(t, err, "Expected error when updating article with invalid date format") 497 + shared.AssertContains(t, err.Error(), "Date", "Expected date validation error") 497 498 }) 498 499 499 500 t.Run("Update validation - title too long", func(t *testing.T) { ··· 502 503 503 504 article := CreateSampleArticle() 504 505 _, err := repo.Create(ctx, article) 505 - AssertNoError(t, err, "Failed to create article") 506 + shared.AssertNoError(t, err, "Failed to create article") 506 507 507 508 article.Title = strings.Repeat("a", 501) 508 509 err = repo.Update(ctx, article) 509 - AssertError(t, err, "Expected error when updating article with title too long") 510 - AssertContains(t, err.Error(), "Title", "Expected title length validation error") 510 + shared.AssertError(t, err, "Expected error when updating article with title too long") 511 + shared.AssertContains(t, err.Error(), "Title", "Expected title length validation error") 511 512 }) 512 513 513 514 t.Run("Update validation - author too long", func(t *testing.T) { ··· 516 517 517 518 article := CreateSampleArticle() 518 519 _, err := repo.Create(ctx, article) 519 - AssertNoError(t, err, "Failed to create article") 520 + shared.AssertNoError(t, err, "Failed to create article") 520 521 521 522 article.Author = strings.Repeat("a", 201) 522 523 err = repo.Update(ctx, article) 523 - AssertError(t, err, "Expected error when updating article with author too long") 524 - AssertContains(t, err.Error(), "Author", "Expected author length validation error") 524 + shared.AssertError(t, err, "Expected error when updating article with author too long") 525 + shared.AssertContains(t, err.Error(), "Author", "Expected author length validation error") 525 526 }) 526 527 527 528 t.Run("Update validation - remove markdown path", func(t *testing.T) { ··· 530 531 531 532 article := CreateSampleArticle() 532 533 _, err := repo.Create(ctx, article) 533 - AssertNoError(t, err, "Failed to create article") 534 + shared.AssertNoError(t, err, "Failed to create article") 534 535 535 536 article.MarkdownPath = "" 536 537 err = repo.Update(ctx, article) 537 - AssertError(t, err, "Expected error when updating article with empty markdown path") 538 - AssertContains(t, err.Error(), "MarkdownPath", "Expected MarkdownPath validation error") 538 + shared.AssertError(t, err, "Expected error when updating article with empty markdown path") 539 + shared.AssertContains(t, err.Error(), "MarkdownPath", "Expected MarkdownPath validation error") 539 540 }) 540 541 541 542 t.Run("Update validation - remove HTML path", func(t *testing.T) { ··· 544 545 545 546 article := CreateSampleArticle() 546 547 _, err := repo.Create(ctx, article) 547 - AssertNoError(t, err, "Failed to create article") 548 + shared.AssertNoError(t, err, "Failed to create article") 548 549 549 550 article.HTMLPath = "" 550 551 err = repo.Update(ctx, article) 551 - AssertError(t, err, "Expected error when updating article with empty HTML path") 552 - AssertContains(t, err.Error(), "HTMLPath", "Expected HTMLPath validation error") 552 + shared.AssertError(t, err, "Expected error when updating article with empty HTML path") 553 + shared.AssertContains(t, err.Error(), "HTMLPath", "Expected HTMLPath validation error") 553 554 }) 554 555 555 556 t.Run("List with no results", func(t *testing.T) { 556 557 opts := &ArticleListOptions{Author: "NonExistentAuthor"} 557 558 articles, err := repo.List(ctx, opts) 558 - AssertNoError(t, err, "Should not error when no articles found") 559 - AssertEqual(t, 0, len(articles), "Expected empty result set") 559 + shared.AssertNoError(t, err, "Should not error when no articles found") 560 + shared.AssertEqual(t, 0, len(articles), "Expected empty result set") 560 561 }) 561 562 }) 562 563 }
+54 -53
internal/repo/base_media_repository_test.go
··· 6 6 7 7 _ "github.com/mattn/go-sqlite3" 8 8 "github.com/stormlightlabs/noteleaf/internal/models" 9 + "github.com/stormlightlabs/noteleaf/internal/shared" 9 10 ) 10 11 11 12 func TestBaseMediaRepository(t *testing.T) { ··· 23 24 } 24 25 25 26 id, err := repo.Create(ctx, book) 26 - AssertNoError(t, err, "Failed to create book") 27 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 27 + shared.AssertNoError(t, err, "Failed to create book") 28 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 28 29 29 30 retrieved, err := repo.Get(ctx, id) 30 - AssertNoError(t, err, "Failed to get book") 31 - AssertEqual(t, book.Title, retrieved.Title, "Title mismatch") 32 - AssertEqual(t, book.Author, retrieved.Author, "Author mismatch") 33 - AssertEqual(t, book.Status, retrieved.Status, "Status mismatch") 31 + shared.AssertNoError(t, err, "Failed to get book") 32 + shared.AssertEqual(t, book.Title, retrieved.Title, "Title mismatch") 33 + shared.AssertEqual(t, book.Author, retrieved.Author, "Author mismatch") 34 + shared.AssertEqual(t, book.Status, retrieved.Status, "Status mismatch") 34 35 }) 35 36 36 37 t.Run("Update", func(t *testing.T) { ··· 44 45 } 45 46 46 47 id, err := repo.Create(ctx, book) 47 - AssertNoError(t, err, "Failed to create book") 48 + shared.AssertNoError(t, err, "Failed to create book") 48 49 49 50 book.Title = "Updated Title" 50 51 book.Author = "Updated Author" 51 52 book.Status = "reading" 52 53 53 54 err = repo.Update(ctx, book) 54 - AssertNoError(t, err, "Failed to update book") 55 + shared.AssertNoError(t, err, "Failed to update book") 55 56 56 57 retrieved, err := repo.Get(ctx, id) 57 - AssertNoError(t, err, "Failed to get updated book") 58 - AssertEqual(t, "Updated Title", retrieved.Title, "Title not updated") 59 - AssertEqual(t, "Updated Author", retrieved.Author, "Author not updated") 60 - AssertEqual(t, "reading", retrieved.Status, "Status not updated") 58 + shared.AssertNoError(t, err, "Failed to get updated book") 59 + shared.AssertEqual(t, "Updated Title", retrieved.Title, "Title not updated") 60 + shared.AssertEqual(t, "Updated Author", retrieved.Author, "Author not updated") 61 + shared.AssertEqual(t, "reading", retrieved.Status, "Status not updated") 61 62 }) 62 63 63 64 t.Run("Delete", func(t *testing.T) { ··· 70 71 } 71 72 72 73 id, err := repo.Create(ctx, book) 73 - AssertNoError(t, err, "Failed to create book") 74 + shared.AssertNoError(t, err, "Failed to create book") 74 75 75 76 err = repo.Delete(ctx, id) 76 - AssertNoError(t, err, "Failed to delete book") 77 + shared.AssertNoError(t, err, "Failed to delete book") 77 78 78 79 _, err = repo.Get(ctx, id) 79 - AssertError(t, err, "Expected error when getting deleted book") 80 + shared.AssertError(t, err, "Expected error when getting deleted book") 80 81 }) 81 82 82 83 t.Run("Get non-existent", func(t *testing.T) { ··· 84 85 repo := NewBookRepository(db) 85 86 86 87 _, err := repo.Get(ctx, 9999) 87 - AssertError(t, err, "Expected error for non-existent book") 88 - AssertContains(t, err.Error(), "not found", "Error should mention 'not found'") 88 + shared.AssertError(t, err, "Expected error for non-existent book") 89 + shared.AssertContains(t, err.Error(), "not found", "Error should mention 'not found'") 89 90 }) 90 91 91 92 t.Run("ListQuery with multiple books", func(t *testing.T) { ··· 100 101 101 102 for _, book := range books { 102 103 _, err := repo.Create(ctx, book) 103 - AssertNoError(t, err, "Failed to create book") 104 + shared.AssertNoError(t, err, "Failed to create book") 104 105 } 105 106 106 107 allBooks, err := repo.List(ctx, BookListOptions{}) 107 - AssertNoError(t, err, "Failed to list books") 108 + shared.AssertNoError(t, err, "Failed to list books") 108 109 if len(allBooks) != 3 { 109 110 t.Errorf("Expected 3 books, got %d", len(allBooks)) 110 111 } ··· 120 121 Status: "queued", 121 122 } 122 123 _, err := repo.Create(ctx, book) 123 - AssertNoError(t, err, "Failed to create book") 124 + shared.AssertNoError(t, err, "Failed to create book") 124 125 } 125 126 126 127 count, err := repo.Count(ctx, BookListOptions{}) 127 - AssertNoError(t, err, "Failed to count books") 128 + shared.AssertNoError(t, err, "Failed to count books") 128 129 if count != 5 { 129 130 t.Errorf("Expected count of 5, got %d", count) 130 131 } ··· 143 144 } 144 145 145 146 id, err := repo.Create(ctx, movie) 146 - AssertNoError(t, err, "Failed to create movie") 147 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 147 + shared.AssertNoError(t, err, "Failed to create movie") 148 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 148 149 149 150 retrieved, err := repo.Get(ctx, id) 150 - AssertNoError(t, err, "Failed to get movie") 151 - AssertEqual(t, movie.Title, retrieved.Title, "Title mismatch") 152 - AssertEqual(t, movie.Year, retrieved.Year, "Year mismatch") 153 - AssertEqual(t, movie.Status, retrieved.Status, "Status mismatch") 151 + shared.AssertNoError(t, err, "Failed to get movie") 152 + shared.AssertEqual(t, movie.Title, retrieved.Title, "Title mismatch") 153 + shared.AssertEqual(t, movie.Year, retrieved.Year, "Year mismatch") 154 + shared.AssertEqual(t, movie.Status, retrieved.Status, "Status mismatch") 154 155 }) 155 156 156 157 t.Run("Update", func(t *testing.T) { ··· 164 165 } 165 166 166 167 id, err := repo.Create(ctx, movie) 167 - AssertNoError(t, err, "Failed to create movie") 168 + shared.AssertNoError(t, err, "Failed to create movie") 168 169 169 170 movie.Title = "Updated Movie" 170 171 movie.Year = 2023 171 172 movie.Status = "watched" 172 173 173 174 err = repo.Update(ctx, movie) 174 - AssertNoError(t, err, "Failed to update movie") 175 + shared.AssertNoError(t, err, "Failed to update movie") 175 176 176 177 retrieved, err := repo.Get(ctx, id) 177 - AssertNoError(t, err, "Failed to get updated movie") 178 - AssertEqual(t, "Updated Movie", retrieved.Title, "Title not updated") 179 - AssertEqual(t, 2023, retrieved.Year, "Year not updated") 180 - AssertEqual(t, "watched", retrieved.Status, "Status not updated") 178 + shared.AssertNoError(t, err, "Failed to get updated movie") 179 + shared.AssertEqual(t, "Updated Movie", retrieved.Title, "Title not updated") 180 + shared.AssertEqual(t, 2023, retrieved.Year, "Year not updated") 181 + shared.AssertEqual(t, "watched", retrieved.Status, "Status not updated") 181 182 }) 182 183 183 184 t.Run("Delete", func(t *testing.T) { ··· 190 191 } 191 192 192 193 id, err := repo.Create(ctx, movie) 193 - AssertNoError(t, err, "Failed to create movie") 194 + shared.AssertNoError(t, err, "Failed to create movie") 194 195 195 196 err = repo.Delete(ctx, id) 196 - AssertNoError(t, err, "Failed to delete movie") 197 + shared.AssertNoError(t, err, "Failed to delete movie") 197 198 198 199 _, err = repo.Get(ctx, id) 199 - AssertError(t, err, "Expected error when getting deleted movie") 200 + shared.AssertError(t, err, "Expected error when getting deleted movie") 200 201 }) 201 202 }) 202 203 ··· 213 214 } 214 215 215 216 id, err := repo.Create(ctx, show) 216 - AssertNoError(t, err, "Failed to create TV show") 217 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 217 + shared.AssertNoError(t, err, "Failed to create TV show") 218 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 218 219 219 220 retrieved, err := repo.Get(ctx, id) 220 - AssertNoError(t, err, "Failed to get TV show") 221 - AssertEqual(t, show.Title, retrieved.Title, "Title mismatch") 222 - AssertEqual(t, show.Season, retrieved.Season, "Season mismatch") 223 - AssertEqual(t, show.Episode, retrieved.Episode, "Episode mismatch") 224 - AssertEqual(t, show.Status, retrieved.Status, "Status mismatch") 221 + shared.AssertNoError(t, err, "Failed to get TV show") 222 + shared.AssertEqual(t, show.Title, retrieved.Title, "Title mismatch") 223 + shared.AssertEqual(t, show.Season, retrieved.Season, "Season mismatch") 224 + shared.AssertEqual(t, show.Episode, retrieved.Episode, "Episode mismatch") 225 + shared.AssertEqual(t, show.Status, retrieved.Status, "Status mismatch") 225 226 }) 226 227 227 228 t.Run("Update", func(t *testing.T) { ··· 236 237 } 237 238 238 239 id, err := repo.Create(ctx, show) 239 - AssertNoError(t, err, "Failed to create TV show") 240 + shared.AssertNoError(t, err, "Failed to create TV show") 240 241 241 242 show.Title = "Updated Show" 242 243 show.Season = 2 ··· 244 245 show.Status = "watching" 245 246 246 247 err = repo.Update(ctx, show) 247 - AssertNoError(t, err, "Failed to update TV show") 248 + shared.AssertNoError(t, err, "Failed to update TV show") 248 249 249 250 retrieved, err := repo.Get(ctx, id) 250 - AssertNoError(t, err, "Failed to get updated TV show") 251 - AssertEqual(t, "Updated Show", retrieved.Title, "Title not updated") 252 - AssertEqual(t, 2, retrieved.Season, "Season not updated") 253 - AssertEqual(t, 5, retrieved.Episode, "Episode not updated") 254 - AssertEqual(t, "watching", retrieved.Status, "Status not updated") 251 + shared.AssertNoError(t, err, "Failed to get updated TV show") 252 + shared.AssertEqual(t, "Updated Show", retrieved.Title, "Title not updated") 253 + shared.AssertEqual(t, 2, retrieved.Season, "Season not updated") 254 + shared.AssertEqual(t, 5, retrieved.Episode, "Episode not updated") 255 + shared.AssertEqual(t, "watching", retrieved.Status, "Status not updated") 255 256 }) 256 257 257 258 t.Run("Delete", func(t *testing.T) { ··· 264 265 } 265 266 266 267 id, err := repo.Create(ctx, show) 267 - AssertNoError(t, err, "Failed to create TV show") 268 + shared.AssertNoError(t, err, "Failed to create TV show") 268 269 269 270 err = repo.Delete(ctx, id) 270 - AssertNoError(t, err, "Failed to delete TV show") 271 + shared.AssertNoError(t, err, "Failed to delete TV show") 271 272 272 273 _, err = repo.Get(ctx, id) 273 - AssertError(t, err, "Expected error when getting deleted TV show") 274 + shared.AssertError(t, err, "Expected error when getting deleted TV show") 274 275 }) 275 276 }) 276 277
+97 -96
internal/repo/book_repository_test.go
··· 7 7 8 8 _ "github.com/mattn/go-sqlite3" 9 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 + "github.com/stormlightlabs/noteleaf/internal/shared" 10 11 ) 11 12 12 13 func TestBookRepository(t *testing.T) { ··· 19 20 book := CreateSampleBook() 20 21 21 22 id, err := repo.Create(ctx, book) 22 - AssertNoError(t, err, "Failed to create book") 23 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 24 - AssertEqual(t, id, book.ID, "Expected book ID to be set correctly") 25 - AssertFalse(t, book.Added.IsZero(), "Expected Added timestamp to be set") 23 + shared.AssertNoError(t, err, "Failed to create book") 24 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 25 + shared.AssertEqual(t, id, book.ID, "Expected book ID to be set correctly") 26 + shared.AssertFalse(t, book.Added.IsZero(), "Expected Added timestamp to be set") 26 27 }) 27 28 28 29 t.Run("Get Book", func(t *testing.T) { 29 30 original := CreateSampleBook() 30 31 id, err := repo.Create(ctx, original) 31 - AssertNoError(t, err, "Failed to create book") 32 + shared.AssertNoError(t, err, "Failed to create book") 32 33 33 34 retrieved, err := repo.Get(ctx, id) 34 - AssertNoError(t, err, "Failed to get book") 35 + shared.AssertNoError(t, err, "Failed to get book") 35 36 36 - AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 37 - AssertEqual(t, original.Author, retrieved.Author, "Author mismatch") 38 - AssertEqual(t, original.Status, retrieved.Status, "Status mismatch") 39 - AssertEqual(t, original.Progress, retrieved.Progress, "Progress mismatch") 40 - AssertEqual(t, original.Pages, retrieved.Pages, "Pages mismatch") 41 - AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch") 42 - AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch") 37 + shared.AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 38 + shared.AssertEqual(t, original.Author, retrieved.Author, "Author mismatch") 39 + shared.AssertEqual(t, original.Status, retrieved.Status, "Status mismatch") 40 + shared.AssertEqual(t, original.Progress, retrieved.Progress, "Progress mismatch") 41 + shared.AssertEqual(t, original.Pages, retrieved.Pages, "Pages mismatch") 42 + shared.AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch") 43 + shared.AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch") 43 44 }) 44 45 45 46 t.Run("Update Book", func(t *testing.T) { 46 47 book := CreateSampleBook() 47 48 id, err := repo.Create(ctx, book) 48 - AssertNoError(t, err, "Failed to create book") 49 + shared.AssertNoError(t, err, "Failed to create book") 49 50 50 51 book.Title = "Updated Book" 51 52 book.Status = "reading" ··· 55 56 book.Started = &now 56 57 57 58 err = repo.Update(ctx, book) 58 - AssertNoError(t, err, "Failed to update book") 59 + shared.AssertNoError(t, err, "Failed to update book") 59 60 60 61 updated, err := repo.Get(ctx, id) 61 - AssertNoError(t, err, "Failed to get updated book") 62 + shared.AssertNoError(t, err, "Failed to get updated book") 62 63 63 - AssertEqual(t, "Updated Book", updated.Title, "Expected updated title") 64 - AssertEqual(t, "reading", updated.Status, "Expected reading status") 65 - AssertEqual(t, 50, updated.Progress, "Expected progress 50") 66 - AssertEqual(t, 5.0, updated.Rating, "Expected rating 5.0") 67 - AssertTrue(t, updated.Started != nil, "Expected started time to be set") 64 + shared.AssertEqual(t, "Updated Book", updated.Title, "Expected updated title") 65 + shared.AssertEqual(t, "reading", updated.Status, "Expected reading status") 66 + shared.AssertEqual(t, 50, updated.Progress, "Expected progress 50") 67 + shared.AssertEqual(t, 5.0, updated.Rating, "Expected rating 5.0") 68 + shared.AssertTrue(t, updated.Started != nil, "Expected started time to be set") 68 69 }) 69 70 70 71 t.Run("Delete Book", func(t *testing.T) { 71 72 book := CreateSampleBook() 72 73 id, err := repo.Create(ctx, book) 73 - AssertNoError(t, err, "Failed to create book") 74 + shared.AssertNoError(t, err, "Failed to create book") 74 75 75 76 err = repo.Delete(ctx, id) 76 - AssertNoError(t, err, "Failed to delete book") 77 + shared.AssertNoError(t, err, "Failed to delete book") 77 78 78 79 _, err = repo.Get(ctx, id) 79 - AssertError(t, err, "Expected error when getting deleted book") 80 + shared.AssertError(t, err, "Expected error when getting deleted book") 80 81 }) 81 82 }) 82 83 ··· 94 95 95 96 for _, book := range books { 96 97 _, err := repo.Create(ctx, book) 97 - AssertNoError(t, err, "Failed to create book") 98 + shared.AssertNoError(t, err, "Failed to create book") 98 99 } 99 100 100 101 t.Run("List All Books", func(t *testing.T) { 101 102 results, err := repo.List(ctx, BookListOptions{}) 102 - AssertNoError(t, err, "Failed to list books") 103 - AssertEqual(t, 4, len(results), "Expected 4 books") 103 + shared.AssertNoError(t, err, "Failed to list books") 104 + shared.AssertEqual(t, 4, len(results), "Expected 4 books") 104 105 }) 105 106 106 107 t.Run("List Books with Status Filter", func(t *testing.T) { 107 108 results, err := repo.List(ctx, BookListOptions{Status: "queued"}) 108 - AssertNoError(t, err, "Failed to list books") 109 - AssertEqual(t, 2, len(results), "Expected 2 queued books") 109 + shared.AssertNoError(t, err, "Failed to list books") 110 + shared.AssertEqual(t, 2, len(results), "Expected 2 queued books") 110 111 111 112 for _, book := range results { 112 - AssertEqual(t, "queued", book.Status, "Expected queued status") 113 + shared.AssertEqual(t, "queued", book.Status, "Expected queued status") 113 114 } 114 115 }) 115 116 116 117 t.Run("List Books by Author", func(t *testing.T) { 117 118 results, err := repo.List(ctx, BookListOptions{Author: "Author A"}) 118 - AssertNoError(t, err, "Failed to list books") 119 - AssertEqual(t, 2, len(results), "Expected 2 books by Author A") 119 + shared.AssertNoError(t, err, "Failed to list books") 120 + shared.AssertEqual(t, 2, len(results), "Expected 2 books by Author A") 120 121 121 122 for _, book := range results { 122 - AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 123 + shared.AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 123 124 } 124 125 }) 125 126 126 127 t.Run("List Books with Progress Filter", func(t *testing.T) { 127 128 results, err := repo.List(ctx, BookListOptions{MinProgress: 50}) 128 - AssertNoError(t, err, "Failed to list books") 129 - AssertEqual(t, 2, len(results), "Expected 2 books with progress >= 50") 129 + shared.AssertNoError(t, err, "Failed to list books") 130 + shared.AssertEqual(t, 2, len(results), "Expected 2 books with progress >= 50") 130 131 131 132 for _, book := range results { 132 - AssertTrue(t, book.Progress >= 50, "Expected progress >= 50") 133 + shared.AssertTrue(t, book.Progress >= 50, "Expected progress >= 50") 133 134 } 134 135 }) 135 136 136 137 t.Run("List Books with Rating Filter", func(t *testing.T) { 137 138 results, err := repo.List(ctx, BookListOptions{MinRating: 4.5}) 138 - AssertNoError(t, err, "Failed to list books") 139 - AssertEqual(t, 2, len(results), "Expected 2 books with rating >= 4.5") 139 + shared.AssertNoError(t, err, "Failed to list books") 140 + shared.AssertEqual(t, 2, len(results), "Expected 2 books with rating >= 4.5") 140 141 141 142 for _, book := range results { 142 - AssertTrue(t, book.Rating >= 4.5, "Expected rating >= 4.5") 143 + shared.AssertTrue(t, book.Rating >= 4.5, "Expected rating >= 4.5") 143 144 } 144 145 }) 145 146 146 147 t.Run("List Books with Search", func(t *testing.T) { 147 148 results, err := repo.List(ctx, BookListOptions{Search: "Book 1"}) 148 - AssertNoError(t, err, "Failed to list books") 149 - AssertEqual(t, 1, len(results), "Expected 1 book matching search") 149 + shared.AssertNoError(t, err, "Failed to list books") 150 + shared.AssertEqual(t, 1, len(results), "Expected 1 book matching search") 150 151 151 152 if len(results) > 0 { 152 - AssertEqual(t, "Book 1", results[0].Title, "Expected 'Book 1'") 153 + shared.AssertEqual(t, "Book 1", results[0].Title, "Expected 'Book 1'") 153 154 } 154 155 }) 155 156 156 157 t.Run("List Books with Limit", func(t *testing.T) { 157 158 results, err := repo.List(ctx, BookListOptions{Limit: 2}) 158 - AssertNoError(t, err, "Failed to list books") 159 - AssertEqual(t, 2, len(results), "Expected 2 books due to limit") 159 + shared.AssertNoError(t, err, "Failed to list books") 160 + shared.AssertEqual(t, 2, len(results), "Expected 2 books due to limit") 160 161 }) 161 162 }) 162 163 ··· 173 174 var book1ID int64 174 175 for _, book := range []*models.Book{book1, book2, book3, book4} { 175 176 id, err := repo.Create(ctx, book) 176 - AssertNoError(t, err, "Failed to create book") 177 + shared.AssertNoError(t, err, "Failed to create book") 177 178 if book == book1 { 178 179 book1ID = id 179 180 } ··· 181 182 182 183 t.Run("GetQueued", func(t *testing.T) { 183 184 results, err := repo.GetQueued(ctx) 184 - AssertNoError(t, err, "Failed to get queued books") 185 - AssertEqual(t, 2, len(results), "Expected 2 queued books") 185 + shared.AssertNoError(t, err, "Failed to get queued books") 186 + shared.AssertEqual(t, 2, len(results), "Expected 2 queued books") 186 187 187 188 for _, book := range results { 188 - AssertEqual(t, "queued", book.Status, "Expected queued status") 189 + shared.AssertEqual(t, "queued", book.Status, "Expected queued status") 189 190 } 190 191 }) 191 192 192 193 t.Run("GetReading", func(t *testing.T) { 193 194 results, err := repo.GetReading(ctx) 194 - AssertNoError(t, err, "Failed to get reading books") 195 - AssertEqual(t, 1, len(results), "Expected 1 reading book") 195 + shared.AssertNoError(t, err, "Failed to get reading books") 196 + shared.AssertEqual(t, 1, len(results), "Expected 1 reading book") 196 197 197 198 if len(results) > 0 { 198 - AssertEqual(t, "reading", results[0].Status, "Expected reading status") 199 + shared.AssertEqual(t, "reading", results[0].Status, "Expected reading status") 199 200 } 200 201 }) 201 202 202 203 t.Run("GetFinished", func(t *testing.T) { 203 204 results, err := repo.GetFinished(ctx) 204 - AssertNoError(t, err, "Failed to get finished books") 205 - AssertEqual(t, 1, len(results), "Expected 1 finished book") 205 + shared.AssertNoError(t, err, "Failed to get finished books") 206 + shared.AssertEqual(t, 1, len(results), "Expected 1 finished book") 206 207 207 208 if len(results) > 0 { 208 - AssertEqual(t, "finished", results[0].Status, "Expected finished status") 209 + shared.AssertEqual(t, "finished", results[0].Status, "Expected finished status") 209 210 } 210 211 }) 211 212 212 213 t.Run("GetByAuthor", func(t *testing.T) { 213 214 results, err := repo.GetByAuthor(ctx, "Author A") 214 - AssertNoError(t, err, "Failed to get books by author") 215 - AssertEqual(t, 2, len(results), "Expected 2 books by Author A") 215 + shared.AssertNoError(t, err, "Failed to get books by author") 216 + shared.AssertEqual(t, 2, len(results), "Expected 2 books by Author A") 216 217 217 218 for _, book := range results { 218 - AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 219 + shared.AssertEqual(t, "Author A", book.Author, "Expected author 'Author A'") 219 220 } 220 221 }) 221 222 222 223 t.Run("StartReading", func(t *testing.T) { 223 224 err := repo.StartReading(ctx, book1ID) 224 - AssertNoError(t, err, "Failed to start reading book") 225 + shared.AssertNoError(t, err, "Failed to start reading book") 225 226 226 227 updated, err := repo.Get(ctx, book1ID) 227 - AssertNoError(t, err, "Failed to get updated book") 228 + shared.AssertNoError(t, err, "Failed to get updated book") 228 229 229 - AssertEqual(t, "reading", updated.Status, "Expected status to be reading") 230 - AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set") 230 + shared.AssertEqual(t, "reading", updated.Status, "Expected status to be reading") 231 + shared.AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set") 231 232 }) 232 233 233 234 t.Run("FinishReading", func(t *testing.T) { 234 235 newBook := &models.Book{Title: "New Book", Status: "reading", Progress: 80} 235 236 id, err := repo.Create(ctx, newBook) 236 - AssertNoError(t, err, "Failed to create new book") 237 + shared.AssertNoError(t, err, "Failed to create new book") 237 238 238 239 err = repo.FinishReading(ctx, id) 239 - AssertNoError(t, err, "Failed to finish reading book") 240 + shared.AssertNoError(t, err, "Failed to finish reading book") 240 241 241 242 updated, err := repo.Get(ctx, id) 242 - AssertNoError(t, err, "Failed to get updated book") 243 + shared.AssertNoError(t, err, "Failed to get updated book") 243 244 244 - AssertEqual(t, "finished", updated.Status, "Expected status to be finished") 245 - AssertEqual(t, 100, updated.Progress, "Expected progress to be 100") 246 - AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set") 245 + shared.AssertEqual(t, "finished", updated.Status, "Expected status to be finished") 246 + shared.AssertEqual(t, 100, updated.Progress, "Expected progress to be 100") 247 + shared.AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set") 247 248 }) 248 249 249 250 t.Run("UpdateProgress", func(t *testing.T) { 250 251 newBook := &models.Book{Title: "Progress Book", Status: "queued", Progress: 0} 251 252 id, err := repo.Create(ctx, newBook) 252 - AssertNoError(t, err, "Failed to create new book") 253 + shared.AssertNoError(t, err, "Failed to create new book") 253 254 254 255 err = repo.UpdateProgress(ctx, id, 25) 255 - AssertNoError(t, err, "Failed to update progress") 256 + shared.AssertNoError(t, err, "Failed to update progress") 256 257 257 258 updated, err := repo.Get(ctx, id) 258 - AssertNoError(t, err, "Failed to get updated book") 259 + shared.AssertNoError(t, err, "Failed to get updated book") 259 260 260 - AssertEqual(t, "reading", updated.Status, "Expected status to be reading when progress > 0") 261 - AssertEqual(t, 25, updated.Progress, "Expected progress 25") 262 - AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set when progress > 0") 261 + shared.AssertEqual(t, "reading", updated.Status, "Expected status to be reading when progress > 0") 262 + shared.AssertEqual(t, 25, updated.Progress, "Expected progress 25") 263 + shared.AssertTrue(t, updated.Started != nil, "Expected started timestamp to be set when progress > 0") 263 264 264 265 err = repo.UpdateProgress(ctx, id, 100) 265 - AssertNoError(t, err, "Failed to update progress to 100") 266 + shared.AssertNoError(t, err, "Failed to update progress to 100") 266 267 267 268 updated, err = repo.Get(ctx, id) 268 - AssertNoError(t, err, "Failed to get updated book") 269 + shared.AssertNoError(t, err, "Failed to get updated book") 269 270 270 - AssertEqual(t, "finished", updated.Status, "Expected status to be finished when progress = 100") 271 - AssertEqual(t, 100, updated.Progress, "Expected progress 100") 272 - AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set when progress = 100") 271 + shared.AssertEqual(t, "finished", updated.Status, "Expected status to be finished when progress = 100") 272 + shared.AssertEqual(t, 100, updated.Progress, "Expected progress 100") 273 + shared.AssertTrue(t, updated.Finished != nil, "Expected finished timestamp to be set when progress = 100") 273 274 }) 274 275 }) 275 276 ··· 287 288 288 289 for _, book := range books { 289 290 _, err := repo.Create(ctx, book) 290 - AssertNoError(t, err, "Failed to create book") 291 + shared.AssertNoError(t, err, "Failed to create book") 291 292 } 292 293 293 294 t.Run("Count all books", func(t *testing.T) { 294 295 count, err := repo.Count(ctx, BookListOptions{}) 295 - AssertNoError(t, err, "Failed to count books") 296 - AssertEqual(t, int64(4), count, "Expected 4 books") 296 + shared.AssertNoError(t, err, "Failed to count books") 297 + shared.AssertEqual(t, int64(4), count, "Expected 4 books") 297 298 }) 298 299 299 300 t.Run("Count queued books", func(t *testing.T) { 300 301 count, err := repo.Count(ctx, BookListOptions{Status: "queued"}) 301 - AssertNoError(t, err, "Failed to count queued books") 302 - AssertEqual(t, int64(2), count, "Expected 2 queued books") 302 + shared.AssertNoError(t, err, "Failed to count queued books") 303 + shared.AssertEqual(t, int64(2), count, "Expected 2 queued books") 303 304 }) 304 305 305 306 t.Run("Count books by progress", func(t *testing.T) { 306 307 count, err := repo.Count(ctx, BookListOptions{MinProgress: 50}) 307 - AssertNoError(t, err, "Failed to count books with progress >= 50") 308 - AssertEqual(t, int64(2), count, "Expected 2 books with progress >= 50") 308 + shared.AssertNoError(t, err, "Failed to count books with progress >= 50") 309 + shared.AssertEqual(t, int64(2), count, "Expected 2 books with progress >= 50") 309 310 }) 310 311 311 312 t.Run("Count books by rating", func(t *testing.T) { 312 313 count, err := repo.Count(ctx, BookListOptions{MinRating: 4.0}) 313 - AssertNoError(t, err, "Failed to count high-rated books") 314 - AssertEqual(t, int64(3), count, "Expected 3 books with rating >= 4.0") 314 + shared.AssertNoError(t, err, "Failed to count high-rated books") 315 + shared.AssertEqual(t, int64(3), count, "Expected 3 books with rating >= 4.0") 315 316 }) 316 317 317 318 t.Run("Count with context cancellation", func(t *testing.T) { ··· 327 328 328 329 book := NewBookBuilder().WithTitle("Test Book").WithAuthor("Test Author").Build() 329 330 id, err := repo.Create(ctx, book) 330 - AssertNoError(t, err, "Failed to create book") 331 + shared.AssertNoError(t, err, "Failed to create book") 331 332 332 333 t.Run("Create with cancelled context", func(t *testing.T) { 333 334 newBook := NewBookBuilder().WithTitle("Cancelled").Build() ··· 399 400 400 401 t.Run("Get non-existent book", func(t *testing.T) { 401 402 _, err := repo.Get(ctx, 99999) 402 - AssertError(t, err, "Expected error for non-existent book") 403 + shared.AssertError(t, err, "Expected error for non-existent book") 403 404 }) 404 405 405 406 t.Run("Update non-existent book succeeds with no rows affected", func(t *testing.T) { 406 407 book := NewBookBuilder().WithTitle("Non-existent").Build() 407 408 book.ID = 99999 408 409 err := repo.Update(ctx, book) 409 - AssertNoError(t, err, "Update should not error when no rows affected") 410 + shared.AssertNoError(t, err, "Update should not error when no rows affected") 410 411 }) 411 412 412 413 t.Run("Delete non-existent book succeeds with no rows affected", func(t *testing.T) { 413 414 err := repo.Delete(ctx, 99999) 414 - AssertNoError(t, err, "Delete should not error when no rows affected") 415 + shared.AssertNoError(t, err, "Delete should not error when no rows affected") 415 416 }) 416 417 417 418 t.Run("StartReading non-existent book", func(t *testing.T) { 418 419 err := repo.StartReading(ctx, 99999) 419 - AssertError(t, err, "Expected error for non-existent book") 420 + shared.AssertError(t, err, "Expected error for non-existent book") 420 421 }) 421 422 422 423 t.Run("FinishReading non-existent book", func(t *testing.T) { 423 424 err := repo.FinishReading(ctx, 99999) 424 - AssertError(t, err, "Expected error for non-existent book") 425 + shared.AssertError(t, err, "Expected error for non-existent book") 425 426 }) 426 427 427 428 t.Run("UpdateProgress non-existent book", func(t *testing.T) { 428 429 err := repo.UpdateProgress(ctx, 99999, 50) 429 - AssertError(t, err, "Expected error for non-existent book") 430 + shared.AssertError(t, err, "Expected error for non-existent book") 430 431 }) 431 432 432 433 t.Run("GetByAuthor with no results", func(t *testing.T) { 433 434 books, err := repo.GetByAuthor(ctx, "NonExistentAuthor") 434 - AssertNoError(t, err, "Should not error when no books found") 435 - AssertEqual(t, 0, len(books), "Expected empty result set") 435 + shared.AssertNoError(t, err, "Should not error when no books found") 436 + shared.AssertEqual(t, 0, len(books), "Expected empty result set") 436 437 }) 437 438 }) 438 439 }
+73 -71
internal/repo/find_methods_test.go
··· 3 3 import ( 4 4 "context" 5 5 "testing" 6 + 7 + "github.com/stormlightlabs/noteleaf/internal/shared" 6 8 ) 7 9 8 10 func TestFindMethods(t *testing.T) { ··· 16 18 Status: "pending", 17 19 } 18 20 tasks, err := repos.Tasks.Find(ctx, options) 19 - AssertNoError(t, err, "Find should succeed") 20 - AssertTrue(t, len(tasks) >= 1, "Should find at least one pending task") 21 + shared.AssertNoError(t, err, "Find should succeed") 22 + shared.AssertTrue(t, len(tasks) >= 1, "Should find at least one pending task") 21 23 for _, task := range tasks { 22 - AssertEqual(t, "pending", task.Status, "All returned tasks should be pending") 24 + shared.AssertEqual(t, "pending", task.Status, "All returned tasks should be pending") 23 25 } 24 26 }) 25 27 ··· 28 30 Priority: "high", 29 31 } 30 32 tasks, err := repos.Tasks.Find(ctx, options) 31 - AssertNoError(t, err, "Find should succeed") 32 - AssertTrue(t, len(tasks) >= 1, "Should find at least one high priority task") 33 + shared.AssertNoError(t, err, "Find should succeed") 34 + shared.AssertTrue(t, len(tasks) >= 1, "Should find at least one high priority task") 33 35 for _, task := range tasks { 34 - AssertEqual(t, "high", task.Priority, "All returned tasks should be high priority") 36 + shared.AssertEqual(t, "high", task.Priority, "All returned tasks should be high priority") 35 37 } 36 38 }) 37 39 ··· 40 42 Project: "test-project", 41 43 } 42 44 tasks, err := repos.Tasks.Find(ctx, options) 43 - AssertNoError(t, err, "Find should succeed") 44 - AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-project") 45 + shared.AssertNoError(t, err, "Find should succeed") 46 + shared.AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-project") 45 47 for _, task := range tasks { 46 - AssertEqual(t, "test-project", task.Project, "All returned tasks should be in test-project") 48 + shared.AssertEqual(t, "test-project", task.Project, "All returned tasks should be in test-project") 47 49 } 48 50 }) 49 51 ··· 52 54 Context: "test-context", 53 55 } 54 56 tasks, err := repos.Tasks.Find(ctx, options) 55 - AssertNoError(t, err, "Find should succeed") 56 - AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-context") 57 + shared.AssertNoError(t, err, "Find should succeed") 58 + shared.AssertTrue(t, len(tasks) >= 1, "Should find tasks in test-context") 57 59 for _, task := range tasks { 58 - AssertEqual(t, "test-context", task.Context, "All returned tasks should be in test-context") 60 + shared.AssertEqual(t, "test-context", task.Context, "All returned tasks should be in test-context") 59 61 } 60 62 }) 61 63 ··· 66 68 Project: "test-project", 67 69 } 68 70 tasks, err := repos.Tasks.Find(ctx, options) 69 - AssertNoError(t, err, "Find should succeed") 71 + shared.AssertNoError(t, err, "Find should succeed") 70 72 for _, task := range tasks { 71 - AssertEqual(t, "pending", task.Status, "Task should be pending") 72 - AssertEqual(t, "high", task.Priority, "Task should be high priority") 73 - AssertEqual(t, "test-project", task.Project, "Task should be in test-project") 73 + shared.AssertEqual(t, "pending", task.Status, "Task should be pending") 74 + shared.AssertEqual(t, "high", task.Priority, "Task should be high priority") 75 + shared.AssertEqual(t, "test-project", task.Project, "Task should be in test-project") 74 76 } 75 77 }) 76 78 ··· 79 81 Status: "non-existent-status", 80 82 } 81 83 tasks, err := repos.Tasks.Find(ctx, options) 82 - AssertNoError(t, err, "Find should succeed even with no results") 83 - AssertEqual(t, 0, len(tasks), "Should find no tasks") 84 + shared.AssertNoError(t, err, "Find should succeed even with no results") 85 + shared.AssertEqual(t, 0, len(tasks), "Should find no tasks") 84 86 }) 85 87 86 88 t.Run("returns all tasks with empty options", func(t *testing.T) { 87 89 options := TaskListOptions{} 88 90 tasks, err := repos.Tasks.Find(ctx, options) 89 - AssertNoError(t, err, "Find should succeed with empty options") 90 - AssertTrue(t, len(tasks) >= 2, "Should return all tasks for empty options") 91 + shared.AssertNoError(t, err, "Find should succeed with empty options") 92 + shared.AssertTrue(t, len(tasks) >= 2, "Should return all tasks for empty options") 91 93 }) 92 94 }) 93 95 ··· 97 99 Status: "reading", 98 100 } 99 101 books, err := repos.Books.Find(ctx, options) 100 - AssertNoError(t, err, "Find should succeed") 101 - AssertTrue(t, len(books) >= 1, "Should find at least one book being read") 102 + shared.AssertNoError(t, err, "Find should succeed") 103 + shared.AssertTrue(t, len(books) >= 1, "Should find at least one book being read") 102 104 for _, book := range books { 103 - AssertEqual(t, "reading", book.Status, "All returned books should be reading") 105 + shared.AssertEqual(t, "reading", book.Status, "All returned books should be reading") 104 106 } 105 107 }) 106 108 ··· 109 111 Author: "Test Author", 110 112 } 111 113 books, err := repos.Books.Find(ctx, options) 112 - AssertNoError(t, err, "Find should succeed") 113 - AssertTrue(t, len(books) >= 1, "Should find at least one book by Test Author") 114 + shared.AssertNoError(t, err, "Find should succeed") 115 + shared.AssertTrue(t, len(books) >= 1, "Should find at least one book by Test Author") 114 116 for _, book := range books { 115 - AssertEqual(t, "Test Author", book.Author, "All returned books should be by Test Author") 117 + shared.AssertEqual(t, "Test Author", book.Author, "All returned books should be by Test Author") 116 118 } 117 119 }) 118 120 ··· 121 123 MinProgress: 0, 122 124 } 123 125 books, err := repos.Books.Find(ctx, options) 124 - AssertNoError(t, err, "Find should succeed") 125 - AssertTrue(t, len(books) >= 1, "Should find books with progress >= 0") 126 + shared.AssertNoError(t, err, "Find should succeed") 127 + shared.AssertTrue(t, len(books) >= 1, "Should find books with progress >= 0") 126 128 for _, book := range books { 127 - AssertTrue(t, book.Progress >= 0, "All returned books should have progress >= 0") 129 + shared.AssertTrue(t, book.Progress >= 0, "All returned books should have progress >= 0") 128 130 } 129 131 }) 130 132 ··· 135 137 MinProgress: 0, 136 138 } 137 139 books, err := repos.Books.Find(ctx, options) 138 - AssertNoError(t, err, "Find should succeed") 140 + shared.AssertNoError(t, err, "Find should succeed") 139 141 for _, book := range books { 140 - AssertEqual(t, "reading", book.Status, "Book should be reading") 141 - AssertEqual(t, "Test Author", book.Author, "Book should be by Test Author") 142 - AssertTrue(t, book.Progress >= 0, "Book should have progress >= 0") 142 + shared.AssertEqual(t, "reading", book.Status, "Book should be reading") 143 + shared.AssertEqual(t, "Test Author", book.Author, "Book should be by Test Author") 144 + shared.AssertTrue(t, book.Progress >= 0, "Book should have progress >= 0") 143 145 } 144 146 }) 145 147 ··· 148 150 Status: "non-existent-status", 149 151 } 150 152 books, err := repos.Books.Find(ctx, options) 151 - AssertNoError(t, err, "Find should succeed even with no results") 152 - AssertEqual(t, 0, len(books), "Should find no books") 153 + shared.AssertNoError(t, err, "Find should succeed even with no results") 154 + shared.AssertEqual(t, 0, len(books), "Should find no books") 153 155 }) 154 156 155 157 t.Run("returns all books with empty options", func(t *testing.T) { 156 158 options := BookListOptions{} 157 159 books, err := repos.Books.Find(ctx, options) 158 - AssertNoError(t, err, "Find should succeed with empty options") 159 - AssertTrue(t, len(books) >= 2, "Should return all books for empty options") 160 + shared.AssertNoError(t, err, "Find should succeed with empty options") 161 + shared.AssertTrue(t, len(books) >= 2, "Should return all books for empty options") 160 162 }) 161 163 }) 162 164 ··· 166 168 Status: "watched", 167 169 } 168 170 movies, err := repos.Movies.Find(ctx, options) 169 - AssertNoError(t, err, "Find should succeed") 170 - AssertTrue(t, len(movies) >= 1, "Should find at least one watched movie") 171 + shared.AssertNoError(t, err, "Find should succeed") 172 + shared.AssertTrue(t, len(movies) >= 1, "Should find at least one watched movie") 171 173 for _, movie := range movies { 172 - AssertEqual(t, "watched", movie.Status, "All returned movies should be watched") 174 + shared.AssertEqual(t, "watched", movie.Status, "All returned movies should be watched") 173 175 } 174 176 }) 175 177 ··· 178 180 Year: 2023, 179 181 } 180 182 movies, err := repos.Movies.Find(ctx, options) 181 - AssertNoError(t, err, "Find should succeed") 182 - AssertTrue(t, len(movies) >= 1, "Should find movies from 2023") 183 + shared.AssertNoError(t, err, "Find should succeed") 184 + shared.AssertTrue(t, len(movies) >= 1, "Should find movies from 2023") 183 185 for _, movie := range movies { 184 - AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 186 + shared.AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 185 187 } 186 188 }) 187 189 ··· 190 192 MinRating: 0.0, 191 193 } 192 194 movies, err := repos.Movies.Find(ctx, options) 193 - AssertNoError(t, err, "Find should succeed") 194 - AssertTrue(t, len(movies) >= 1, "Should find movies with rating >= 0") 195 + shared.AssertNoError(t, err, "Find should succeed") 196 + shared.AssertTrue(t, len(movies) >= 1, "Should find movies with rating >= 0") 195 197 for _, movie := range movies { 196 - AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 198 + shared.AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 197 199 } 198 200 }) 199 201 ··· 204 206 MinRating: 0.0, 205 207 } 206 208 movies, err := repos.Movies.Find(ctx, options) 207 - AssertNoError(t, err, "Find should succeed") 209 + shared.AssertNoError(t, err, "Find should succeed") 208 210 for _, movie := range movies { 209 - AssertEqual(t, "watched", movie.Status, "Movie should be watched") 210 - AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 211 - AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 211 + shared.AssertEqual(t, "watched", movie.Status, "Movie should be watched") 212 + shared.AssertEqual(t, 2023, movie.Year, "Movie should be from 2023") 213 + shared.AssertTrue(t, movie.Rating >= 0.0, "Movie rating should be >= 0") 212 214 } 213 215 }) 214 216 ··· 217 219 Status: "non-existent-status", 218 220 } 219 221 movies, err := repos.Movies.Find(ctx, options) 220 - AssertNoError(t, err, "Find should succeed even with no results") 221 - AssertEqual(t, 0, len(movies), "Should find no movies") 222 + shared.AssertNoError(t, err, "Find should succeed even with no results") 223 + shared.AssertEqual(t, 0, len(movies), "Should find no movies") 222 224 }) 223 225 224 226 t.Run("returns all movies with empty options", func(t *testing.T) { 225 227 options := MovieListOptions{} 226 228 movies, err := repos.Movies.Find(ctx, options) 227 - AssertNoError(t, err, "Find should succeed with empty options") 228 - AssertTrue(t, len(movies) >= 2, "Should return all movies for empty options") 229 + shared.AssertNoError(t, err, "Find should succeed with empty options") 230 + shared.AssertTrue(t, len(movies) >= 2, "Should return all movies for empty options") 229 231 }) 230 232 }) 231 233 ··· 235 237 Status: "watching", 236 238 } 237 239 shows, err := repos.TV.Find(ctx, options) 238 - AssertNoError(t, err, "Find should succeed") 239 - AssertTrue(t, len(shows) >= 1, "Should find at least one TV show being watched") 240 + shared.AssertNoError(t, err, "Find should succeed") 241 + shared.AssertTrue(t, len(shows) >= 1, "Should find at least one TV show being watched") 240 242 for _, show := range shows { 241 - AssertEqual(t, "watching", show.Status, "All returned shows should be watching") 243 + shared.AssertEqual(t, "watching", show.Status, "All returned shows should be watching") 242 244 } 243 245 }) 244 246 ··· 247 249 Season: 1, 248 250 } 249 251 shows, err := repos.TV.Find(ctx, options) 250 - AssertNoError(t, err, "Find should succeed") 251 - AssertTrue(t, len(shows) >= 1, "Should find TV shows with season 1") 252 + shared.AssertNoError(t, err, "Find should succeed") 253 + shared.AssertTrue(t, len(shows) >= 1, "Should find TV shows with season 1") 252 254 for _, show := range shows { 253 - AssertEqual(t, 1, show.Season, "All returned shows should be season 1") 255 + shared.AssertEqual(t, 1, show.Season, "All returned shows should be season 1") 254 256 } 255 257 }) 256 258 ··· 259 261 MinRating: 0.0, 260 262 } 261 263 shows, err := repos.TV.Find(ctx, options) 262 - AssertNoError(t, err, "Find should succeed") 263 - AssertTrue(t, len(shows) >= 1, "Should find TV shows with rating >= 0") 264 + shared.AssertNoError(t, err, "Find should succeed") 265 + shared.AssertTrue(t, len(shows) >= 1, "Should find TV shows with rating >= 0") 264 266 for _, show := range shows { 265 - AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 267 + shared.AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 266 268 } 267 269 }) 268 270 ··· 273 275 MinRating: 0.0, 274 276 } 275 277 shows, err := repos.TV.Find(ctx, options) 276 - AssertNoError(t, err, "Find should succeed") 278 + shared.AssertNoError(t, err, "Find should succeed") 277 279 for _, show := range shows { 278 - AssertEqual(t, "watching", show.Status, "Show should be watching") 279 - AssertEqual(t, 1, show.Season, "Show should be season 1") 280 - AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 280 + shared.AssertEqual(t, "watching", show.Status, "Show should be watching") 281 + shared.AssertEqual(t, 1, show.Season, "Show should be season 1") 282 + shared.AssertTrue(t, show.Rating >= 0.0, "Show rating should be >= 0") 281 283 } 282 284 }) 283 285 ··· 286 288 Status: "non-existent-status", 287 289 } 288 290 shows, err := repos.TV.Find(ctx, options) 289 - AssertNoError(t, err, "Find should succeed even with no results") 290 - AssertEqual(t, 0, len(shows), "Should find no TV shows") 291 + shared.AssertNoError(t, err, "Find should succeed even with no results") 292 + shared.AssertEqual(t, 0, len(shows), "Should find no TV shows") 291 293 }) 292 294 293 295 t.Run("returns all TV shows with empty options", func(t *testing.T) { 294 296 options := TVListOptions{} 295 297 shows, err := repos.TV.Find(ctx, options) 296 - AssertNoError(t, err, "Find should succeed with empty options") 297 - AssertTrue(t, len(shows) >= 2, "Should return all TV shows for empty options") 298 + shared.AssertNoError(t, err, "Find should succeed with empty options") 299 + shared.AssertTrue(t, len(shows) >= 2, "Should return all TV shows for empty options") 298 300 }) 299 301 }) 300 302 }
+64 -63
internal/repo/movie_repository_test.go
··· 7 7 8 8 _ "github.com/mattn/go-sqlite3" 9 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 + "github.com/stormlightlabs/noteleaf/internal/shared" 10 11 ) 11 12 12 13 func TestMovieRepository(t *testing.T) { ··· 19 20 movie := CreateSampleMovie() 20 21 21 22 id, err := repo.Create(ctx, movie) 22 - AssertNoError(t, err, "Failed to create movie") 23 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 24 - AssertEqual(t, id, movie.ID, "Expected movie ID to be set correctly") 25 - AssertFalse(t, movie.Added.IsZero(), "Expected Added timestamp to be set") 23 + shared.AssertNoError(t, err, "Failed to create movie") 24 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 25 + shared.AssertEqual(t, id, movie.ID, "Expected movie ID to be set correctly") 26 + shared.AssertFalse(t, movie.Added.IsZero(), "Expected Added timestamp to be set") 26 27 }) 27 28 28 29 t.Run("Get Movie", func(t *testing.T) { 29 30 original := CreateSampleMovie() 30 31 id, err := repo.Create(ctx, original) 31 - AssertNoError(t, err, "Failed to create movie") 32 + shared.AssertNoError(t, err, "Failed to create movie") 32 33 33 34 retrieved, err := repo.Get(ctx, id) 34 - AssertNoError(t, err, "Failed to get movie") 35 + shared.AssertNoError(t, err, "Failed to get movie") 35 36 36 - AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 37 - AssertEqual(t, original.Year, retrieved.Year, "Year mismatch") 38 - AssertEqual(t, original.Status, retrieved.Status, "Status mismatch") 39 - AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch") 40 - AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch") 37 + shared.AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 38 + shared.AssertEqual(t, original.Year, retrieved.Year, "Year mismatch") 39 + shared.AssertEqual(t, original.Status, retrieved.Status, "Status mismatch") 40 + shared.AssertEqual(t, original.Rating, retrieved.Rating, "Rating mismatch") 41 + shared.AssertEqual(t, original.Notes, retrieved.Notes, "Notes mismatch") 41 42 }) 42 43 43 44 t.Run("Update Movie", func(t *testing.T) { 44 45 movie := CreateSampleMovie() 45 46 id, err := repo.Create(ctx, movie) 46 - AssertNoError(t, err, "Failed to create movie") 47 + shared.AssertNoError(t, err, "Failed to create movie") 47 48 48 49 movie.Title = "Updated Movie" 49 50 movie.Status = "watched" ··· 52 53 movie.Watched = &now 53 54 54 55 err = repo.Update(ctx, movie) 55 - AssertNoError(t, err, "Failed to update movie") 56 + shared.AssertNoError(t, err, "Failed to update movie") 56 57 57 58 updated, err := repo.Get(ctx, id) 58 - AssertNoError(t, err, "Failed to get updated movie") 59 + shared.AssertNoError(t, err, "Failed to get updated movie") 59 60 60 - AssertEqual(t, "Updated Movie", updated.Title, "Expected updated title") 61 - AssertEqual(t, "watched", updated.Status, "Expected watched status") 62 - AssertEqual(t, 9.0, updated.Rating, "Expected rating 9.0") 63 - AssertTrue(t, updated.Watched != nil, "Expected watched time to be set") 61 + shared.AssertEqual(t, "Updated Movie", updated.Title, "Expected updated title") 62 + shared.AssertEqual(t, "watched", updated.Status, "Expected watched status") 63 + shared.AssertEqual(t, 9.0, updated.Rating, "Expected rating 9.0") 64 + shared.AssertTrue(t, updated.Watched != nil, "Expected watched time to be set") 64 65 }) 65 66 66 67 t.Run("Delete Movie", func(t *testing.T) { 67 68 movie := CreateSampleMovie() 68 69 id, err := repo.Create(ctx, movie) 69 - AssertNoError(t, err, "Failed to create movie") 70 + shared.AssertNoError(t, err, "Failed to create movie") 70 71 71 72 err = repo.Delete(ctx, id) 72 - AssertNoError(t, err, "Failed to delete movie") 73 + shared.AssertNoError(t, err, "Failed to delete movie") 73 74 74 75 _, err = repo.Get(ctx, id) 75 - AssertError(t, err, "Expected error when getting deleted movie") 76 + shared.AssertError(t, err, "Expected error when getting deleted movie") 76 77 }) 77 78 }) 78 79 ··· 89 90 90 91 for _, movie := range movies { 91 92 _, err := repo.Create(ctx, movie) 92 - AssertNoError(t, err, "Failed to create movie") 93 + shared.AssertNoError(t, err, "Failed to create movie") 93 94 } 94 95 95 96 t.Run("List All Movies", func(t *testing.T) { 96 97 results, err := repo.List(ctx, MovieListOptions{}) 97 - AssertNoError(t, err, "Failed to list movies") 98 - AssertEqual(t, 3, len(results), "Expected 3 movies") 98 + shared.AssertNoError(t, err, "Failed to list movies") 99 + shared.AssertEqual(t, 3, len(results), "Expected 3 movies") 99 100 }) 100 101 101 102 t.Run("List Movies with Status Filter", func(t *testing.T) { 102 103 results, err := repo.List(ctx, MovieListOptions{Status: "queued"}) 103 - AssertNoError(t, err, "Failed to list movies") 104 - AssertEqual(t, 2, len(results), "Expected 2 queued movies") 104 + shared.AssertNoError(t, err, "Failed to list movies") 105 + shared.AssertEqual(t, 2, len(results), "Expected 2 queued movies") 105 106 106 107 for _, movie := range results { 107 - AssertEqual(t, "queued", movie.Status, "Expected queued status") 108 + shared.AssertEqual(t, "queued", movie.Status, "Expected queued status") 108 109 } 109 110 }) 110 111 111 112 t.Run("List Movies with Year Filter", func(t *testing.T) { 112 113 results, err := repo.List(ctx, MovieListOptions{Year: 2021}) 113 - AssertNoError(t, err, "Failed to list movies") 114 - AssertEqual(t, 1, len(results), "Expected 1 movie from 2021") 114 + shared.AssertNoError(t, err, "Failed to list movies") 115 + shared.AssertEqual(t, 1, len(results), "Expected 1 movie from 2021") 115 116 116 117 if len(results) > 0 { 117 - AssertEqual(t, 2021, results[0].Year, "Expected year 2021") 118 + shared.AssertEqual(t, 2021, results[0].Year, "Expected year 2021") 118 119 } 119 120 }) 120 121 121 122 t.Run("List Movies with Rating Filter", func(t *testing.T) { 122 123 results, err := repo.List(ctx, MovieListOptions{MinRating: 8.0}) 123 - AssertNoError(t, err, "Failed to list movies") 124 - AssertEqual(t, 2, len(results), "Expected 2 movies with rating >= 8.0") 124 + shared.AssertNoError(t, err, "Failed to list movies") 125 + shared.AssertEqual(t, 2, len(results), "Expected 2 movies with rating >= 8.0") 125 126 126 127 for _, movie := range results { 127 - AssertTrue(t, movie.Rating >= 8.0, "Expected rating >= 8.0") 128 + shared.AssertTrue(t, movie.Rating >= 8.0, "Expected rating >= 8.0") 128 129 } 129 130 }) 130 131 131 132 t.Run("List Movies with Search", func(t *testing.T) { 132 133 results, err := repo.List(ctx, MovieListOptions{Search: "Movie 1"}) 133 - AssertNoError(t, err, "Failed to list movies") 134 - AssertEqual(t, 1, len(results), "Expected 1 movie matching search") 134 + shared.AssertNoError(t, err, "Failed to list movies") 135 + shared.AssertEqual(t, 1, len(results), "Expected 1 movie matching search") 135 136 136 137 if len(results) > 0 { 137 - AssertEqual(t, "Movie 1", results[0].Title, "Expected 'Movie 1'") 138 + shared.AssertEqual(t, "Movie 1", results[0].Title, "Expected 'Movie 1'") 138 139 } 139 140 }) 140 141 141 142 t.Run("List Movies with Limit", func(t *testing.T) { 142 143 results, err := repo.List(ctx, MovieListOptions{Limit: 2}) 143 - AssertNoError(t, err, "Failed to list movies") 144 - AssertEqual(t, 2, len(results), "Expected 2 movies due to limit") 144 + shared.AssertNoError(t, err, "Failed to list movies") 145 + shared.AssertEqual(t, 2, len(results), "Expected 2 movies due to limit") 145 146 }) 146 147 }) 147 148 ··· 157 158 var movie1ID int64 158 159 for _, movie := range []*models.Movie{movie1, movie2, movie3} { 159 160 id, err := repo.Create(ctx, movie) 160 - AssertNoError(t, err, "Failed to create movie") 161 + shared.AssertNoError(t, err, "Failed to create movie") 161 162 if movie == movie1 { 162 163 movie1ID = id 163 164 } ··· 165 166 166 167 t.Run("GetQueued", func(t *testing.T) { 167 168 results, err := repo.GetQueued(ctx) 168 - AssertNoError(t, err, "Failed to get queued movies") 169 - AssertEqual(t, 2, len(results), "Expected 2 queued movies") 169 + shared.AssertNoError(t, err, "Failed to get queued movies") 170 + shared.AssertEqual(t, 2, len(results), "Expected 2 queued movies") 170 171 171 172 for _, movie := range results { 172 - AssertEqual(t, "queued", movie.Status, "Expected queued status") 173 + shared.AssertEqual(t, "queued", movie.Status, "Expected queued status") 173 174 } 174 175 }) 175 176 176 177 t.Run("GetWatched", func(t *testing.T) { 177 178 results, err := repo.GetWatched(ctx) 178 - AssertNoError(t, err, "Failed to get watched movies") 179 - AssertEqual(t, 1, len(results), "Expected 1 watched movie") 179 + shared.AssertNoError(t, err, "Failed to get watched movies") 180 + shared.AssertEqual(t, 1, len(results), "Expected 1 watched movie") 180 181 181 182 if len(results) > 0 { 182 - AssertEqual(t, "watched", results[0].Status, "Expected watched status") 183 + shared.AssertEqual(t, "watched", results[0].Status, "Expected watched status") 183 184 } 184 185 }) 185 186 186 187 t.Run("MarkWatched", func(t *testing.T) { 187 188 err := repo.MarkWatched(ctx, movie1ID) 188 - AssertNoError(t, err, "Failed to mark movie as watched") 189 + shared.AssertNoError(t, err, "Failed to mark movie as watched") 189 190 190 191 updated, err := repo.Get(ctx, movie1ID) 191 - AssertNoError(t, err, "Failed to get updated movie") 192 + shared.AssertNoError(t, err, "Failed to get updated movie") 192 193 193 - AssertEqual(t, "watched", updated.Status, "Expected status to be watched") 194 - AssertTrue(t, updated.Watched != nil, "Expected watched timestamp to be set") 194 + shared.AssertEqual(t, "watched", updated.Status, "Expected status to be watched") 195 + shared.AssertTrue(t, updated.Watched != nil, "Expected watched timestamp to be set") 195 196 }) 196 197 }) 197 198 ··· 208 209 209 210 for _, movie := range movies { 210 211 _, err := repo.Create(ctx, movie) 211 - AssertNoError(t, err, "Failed to create movie") 212 + shared.AssertNoError(t, err, "Failed to create movie") 212 213 } 213 214 214 215 t.Run("Count all movies", func(t *testing.T) { 215 216 count, err := repo.Count(ctx, MovieListOptions{}) 216 - AssertNoError(t, err, "Failed to count movies") 217 - AssertEqual(t, int64(3), count, "Expected 3 movies") 217 + shared.AssertNoError(t, err, "Failed to count movies") 218 + shared.AssertEqual(t, int64(3), count, "Expected 3 movies") 218 219 }) 219 220 220 221 t.Run("Count queued movies", func(t *testing.T) { 221 222 count, err := repo.Count(ctx, MovieListOptions{Status: "queued"}) 222 - AssertNoError(t, err, "Failed to count queued movies") 223 - AssertEqual(t, int64(2), count, "Expected 2 queued movies") 223 + shared.AssertNoError(t, err, "Failed to count queued movies") 224 + shared.AssertEqual(t, int64(2), count, "Expected 2 queued movies") 224 225 }) 225 226 226 227 t.Run("Count movies by rating", func(t *testing.T) { 227 228 count, err := repo.Count(ctx, MovieListOptions{MinRating: 8.0}) 228 - AssertNoError(t, err, "Failed to count high-rated movies") 229 - AssertEqual(t, int64(2), count, "Expected 2 movies with rating >= 8.0") 229 + shared.AssertNoError(t, err, "Failed to count high-rated movies") 230 + shared.AssertEqual(t, int64(2), count, "Expected 2 movies with rating >= 8.0") 230 231 }) 231 232 232 233 t.Run("Count with context cancellation", func(t *testing.T) { ··· 242 243 243 244 movie := NewMovieBuilder().WithTitle("Test Movie").WithYear(2023).Build() 244 245 id, err := repo.Create(ctx, movie) 245 - AssertNoError(t, err, "Failed to create movie") 246 + shared.AssertNoError(t, err, "Failed to create movie") 246 247 247 248 t.Run("Create with cancelled context", func(t *testing.T) { 248 249 newMovie := NewMovieBuilder().WithTitle("Cancelled").Build() ··· 294 295 295 296 t.Run("Get non-existent movie", func(t *testing.T) { 296 297 _, err := repo.Get(ctx, 99999) 297 - AssertError(t, err, "Expected error for non-existent movie") 298 + shared.AssertError(t, err, "Expected error for non-existent movie") 298 299 }) 299 300 300 301 t.Run("Update non-existent movie succeeds with no rows affected", func(t *testing.T) { 301 302 movie := NewMovieBuilder().WithTitle("Non-existent").Build() 302 303 movie.ID = 99999 303 304 err := repo.Update(ctx, movie) 304 - AssertNoError(t, err, "Update should not error when no rows affected") 305 + shared.AssertNoError(t, err, "Update should not error when no rows affected") 305 306 }) 306 307 307 308 t.Run("Delete non-existent movie succeeds with no rows affected", func(t *testing.T) { 308 309 err := repo.Delete(ctx, 99999) 309 - AssertNoError(t, err, "Delete should not error when no rows affected") 310 + shared.AssertNoError(t, err, "Delete should not error when no rows affected") 310 311 }) 311 312 312 313 t.Run("MarkWatched non-existent movie", func(t *testing.T) { 313 314 err := repo.MarkWatched(ctx, 99999) 314 - AssertError(t, err, "Expected error for non-existent movie") 315 + shared.AssertError(t, err, "Expected error for non-existent movie") 315 316 }) 316 317 317 318 t.Run("List with no results", func(t *testing.T) { 318 319 movies, err := repo.List(ctx, MovieListOptions{Year: 1900}) 319 - AssertNoError(t, err, "Should not error when no movies found") 320 - AssertEqual(t, 0, len(movies), "Expected empty result set") 320 + shared.AssertNoError(t, err, "Should not error when no movies found") 321 + shared.AssertEqual(t, 0, len(movies), "Expected empty result set") 321 322 }) 322 323 }) 323 324 }
+107 -106
internal/repo/note_repository_test.go
··· 6 6 7 7 _ "github.com/mattn/go-sqlite3" 8 8 "github.com/stormlightlabs/noteleaf/internal/models" 9 + "github.com/stormlightlabs/noteleaf/internal/shared" 9 10 ) 10 11 11 12 func TestNoteRepository(t *testing.T) { ··· 18 19 note := CreateSampleNote() 19 20 20 21 id, err := repo.Create(ctx, note) 21 - AssertNoError(t, err, "Failed to create note") 22 - AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 23 - AssertEqual(t, id, note.ID, "Expected note ID to be set correctly") 24 - AssertFalse(t, note.Created.IsZero(), "Expected Created timestamp to be set") 25 - AssertFalse(t, note.Modified.IsZero(), "Expected Modified timestamp to be set") 22 + shared.AssertNoError(t, err, "Failed to create note") 23 + shared.AssertNotEqual(t, int64(0), id, "Expected non-zero ID") 24 + shared.AssertEqual(t, id, note.ID, "Expected note ID to be set correctly") 25 + shared.AssertFalse(t, note.Created.IsZero(), "Expected Created timestamp to be set") 26 + shared.AssertFalse(t, note.Modified.IsZero(), "Expected Modified timestamp to be set") 26 27 }) 27 28 28 29 t.Run("Get Note", func(t *testing.T) { 29 30 original := CreateSampleNote() 30 31 id, err := repo.Create(ctx, original) 31 - AssertNoError(t, err, "Failed to create note") 32 + shared.AssertNoError(t, err, "Failed to create note") 32 33 33 34 retrieved, err := repo.Get(ctx, id) 34 - AssertNoError(t, err, "Failed to get note") 35 + shared.AssertNoError(t, err, "Failed to get note") 35 36 36 - AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 37 - AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 38 - AssertEqual(t, original.Content, retrieved.Content, "Content mismatch") 39 - AssertEqual(t, len(original.Tags), len(retrieved.Tags), "Tags length mismatch") 40 - AssertEqual(t, original.Archived, retrieved.Archived, "Archived mismatch") 41 - AssertEqual(t, original.FilePath, retrieved.FilePath, "FilePath mismatch") 37 + shared.AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 38 + shared.AssertEqual(t, original.Title, retrieved.Title, "Title mismatch") 39 + shared.AssertEqual(t, original.Content, retrieved.Content, "Content mismatch") 40 + shared.AssertEqual(t, len(original.Tags), len(retrieved.Tags), "Tags length mismatch") 41 + shared.AssertEqual(t, original.Archived, retrieved.Archived, "Archived mismatch") 42 + shared.AssertEqual(t, original.FilePath, retrieved.FilePath, "FilePath mismatch") 42 43 }) 43 44 44 45 t.Run("Update Note", func(t *testing.T) { 45 46 note := CreateSampleNote() 46 47 id, err := repo.Create(ctx, note) 47 - AssertNoError(t, err, "Failed to create note") 48 + shared.AssertNoError(t, err, "Failed to create note") 48 49 49 50 originalModified := note.Modified 50 51 ··· 55 56 note.FilePath = "/new/path/note.md" 56 57 57 58 err = repo.Update(ctx, note) 58 - AssertNoError(t, err, "Failed to update note") 59 + shared.AssertNoError(t, err, "Failed to update note") 59 60 60 61 retrieved, err := repo.Get(ctx, id) 61 - AssertNoError(t, err, "Failed to get updated note") 62 + shared.AssertNoError(t, err, "Failed to get updated note") 62 63 63 - AssertEqual(t, "Updated Title", retrieved.Title, "Expected updated title") 64 - AssertEqual(t, "Updated content", retrieved.Content, "Expected updated content") 65 - AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags") 64 + shared.AssertEqual(t, "Updated Title", retrieved.Title, "Expected updated title") 65 + shared.AssertEqual(t, "Updated content", retrieved.Content, "Expected updated content") 66 + shared.AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags") 66 67 if len(retrieved.Tags) >= 2 { 67 - AssertEqual(t, "updated", retrieved.Tags[0], "Expected first tag to be 'updated'") 68 - AssertEqual(t, "test", retrieved.Tags[1], "Expected second tag to be 'test'") 68 + shared.AssertEqual(t, "updated", retrieved.Tags[0], "Expected first tag to be 'updated'") 69 + shared.AssertEqual(t, "test", retrieved.Tags[1], "Expected second tag to be 'test'") 69 70 } 70 - AssertTrue(t, retrieved.Archived, "Expected note to be archived") 71 - AssertEqual(t, "/new/path/note.md", retrieved.FilePath, "Expected updated file path") 72 - AssertTrue(t, retrieved.Modified.After(originalModified), "Expected Modified timestamp to be updated") 71 + shared.AssertTrue(t, retrieved.Archived, "Expected note to be archived") 72 + shared.AssertEqual(t, "/new/path/note.md", retrieved.FilePath, "Expected updated file path") 73 + shared.AssertTrue(t, retrieved.Modified.After(originalModified), "Expected Modified timestamp to be updated") 73 74 }) 74 75 75 76 t.Run("Delete Note", func(t *testing.T) { 76 77 note := CreateSampleNote() 77 78 id, err := repo.Create(ctx, note) 78 - AssertNoError(t, err, "Failed to create note") 79 + shared.AssertNoError(t, err, "Failed to create note") 79 80 80 81 err = repo.Delete(ctx, id) 81 - AssertNoError(t, err, "Failed to delete note") 82 + shared.AssertNoError(t, err, "Failed to delete note") 82 83 83 84 _, err = repo.Get(ctx, id) 84 - AssertError(t, err, "Expected error when getting deleted note") 85 + shared.AssertError(t, err, "Expected error when getting deleted note") 85 86 }) 86 87 }) 87 88 ··· 98 99 99 100 for _, note := range notes { 100 101 _, err := repo.Create(ctx, note) 101 - AssertNoError(t, err, "Failed to create test note") 102 + shared.AssertNoError(t, err, "Failed to create test note") 102 103 } 103 104 104 105 t.Run("List All Notes", func(t *testing.T) { 105 106 results, err := repo.List(ctx, NoteListOptions{}) 106 - AssertNoError(t, err, "Failed to list notes") 107 - AssertEqual(t, 3, len(results), "Expected 3 notes") 107 + shared.AssertNoError(t, err, "Failed to list notes") 108 + shared.AssertEqual(t, 3, len(results), "Expected 3 notes") 108 109 }) 109 110 110 111 t.Run("List Archived Notes Only", func(t *testing.T) { 111 112 archived := true 112 113 results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 113 - AssertNoError(t, err, "Failed to list archived notes") 114 - AssertEqual(t, 1, len(results), "Expected 1 archived note") 114 + shared.AssertNoError(t, err, "Failed to list archived notes") 115 + shared.AssertEqual(t, 1, len(results), "Expected 1 archived note") 115 116 if len(results) > 0 { 116 - AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 117 + shared.AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 117 118 } 118 119 }) 119 120 120 121 t.Run("List Active Notes Only", func(t *testing.T) { 121 122 archived := false 122 123 results, err := repo.List(ctx, NoteListOptions{Archived: &archived}) 123 - AssertNoError(t, err, "Failed to list active notes") 124 - AssertEqual(t, 2, len(results), "Expected 2 active notes") 124 + shared.AssertNoError(t, err, "Failed to list active notes") 125 + shared.AssertEqual(t, 2, len(results), "Expected 2 active notes") 125 126 for _, note := range results { 126 - AssertFalse(t, note.Archived, "Retrieved note should not be archived") 127 + shared.AssertFalse(t, note.Archived, "Retrieved note should not be archived") 127 128 } 128 129 }) 129 130 130 131 t.Run("Search by Title", func(t *testing.T) { 131 132 results, err := repo.List(ctx, NoteListOptions{Title: "First"}) 132 - AssertNoError(t, err, "Failed to search by title") 133 - AssertEqual(t, 1, len(results), "Expected 1 note") 133 + shared.AssertNoError(t, err, "Failed to search by title") 134 + shared.AssertEqual(t, 1, len(results), "Expected 1 note") 134 135 if len(results) > 0 { 135 - AssertEqual(t, "First Note", results[0].Title, "Expected 'First Note'") 136 + shared.AssertEqual(t, "First Note", results[0].Title, "Expected 'First Note'") 136 137 } 137 138 }) 138 139 139 140 t.Run("Search by Content", func(t *testing.T) { 140 141 results, err := repo.List(ctx, NoteListOptions{Content: "Important"}) 141 - AssertNoError(t, err, "Failed to search by content") 142 - AssertEqual(t, 1, len(results), "Expected 1 note") 142 + shared.AssertNoError(t, err, "Failed to search by content") 143 + shared.AssertEqual(t, 1, len(results), "Expected 1 note") 143 144 if len(results) > 0 { 144 - AssertEqual(t, "Third Note", results[0].Title, "Expected 'Third Note'") 145 + shared.AssertEqual(t, "Third Note", results[0].Title, "Expected 'Third Note'") 145 146 } 146 147 }) 147 148 148 149 t.Run("Limit and Offset", func(t *testing.T) { 149 150 results, err := repo.List(ctx, NoteListOptions{Limit: 2}) 150 - AssertNoError(t, err, "Failed to list with limit") 151 - AssertEqual(t, 2, len(results), "Expected 2 notes") 151 + shared.AssertNoError(t, err, "Failed to list with limit") 152 + shared.AssertEqual(t, 2, len(results), "Expected 2 notes") 152 153 153 154 results, err = repo.List(ctx, NoteListOptions{Limit: 2, Offset: 1}) 154 - AssertNoError(t, err, "Failed to list with limit and offset") 155 - AssertEqual(t, 2, len(results), "Expected 2 notes with offset") 155 + shared.AssertNoError(t, err, "Failed to list with limit and offset") 156 + shared.AssertEqual(t, 2, len(results), "Expected 2 notes with offset") 156 157 }) 157 158 }) 158 159 ··· 169 170 170 171 for _, note := range notes { 171 172 _, err := repo.Create(ctx, note) 172 - AssertNoError(t, err, "Failed to create test note") 173 + shared.AssertNoError(t, err, "Failed to create test note") 173 174 } 174 175 175 176 t.Run("GetByTitle", func(t *testing.T) { 176 177 results, err := repo.GetByTitle(ctx, "Work") 177 - AssertNoError(t, err, "Failed to get by title") 178 - AssertEqual(t, 1, len(results), "Expected 1 note") 178 + shared.AssertNoError(t, err, "Failed to get by title") 179 + shared.AssertEqual(t, 1, len(results), "Expected 1 note") 179 180 if len(results) > 0 { 180 - AssertEqual(t, "Work Note", results[0].Title, "Expected 'Work Note'") 181 + shared.AssertEqual(t, "Work Note", results[0].Title, "Expected 'Work Note'") 181 182 } 182 183 }) 183 184 184 185 t.Run("GetArchived", func(t *testing.T) { 185 186 results, err := repo.GetArchived(ctx) 186 - AssertNoError(t, err, "Failed to get archived notes") 187 - AssertEqual(t, 1, len(results), "Expected 1 archived note") 187 + shared.AssertNoError(t, err, "Failed to get archived notes") 188 + shared.AssertEqual(t, 1, len(results), "Expected 1 archived note") 188 189 if len(results) > 0 { 189 - AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 190 + shared.AssertTrue(t, results[0].Archived, "Retrieved note should be archived") 190 191 } 191 192 }) 192 193 193 194 t.Run("GetActive", func(t *testing.T) { 194 195 results, err := repo.GetActive(ctx) 195 - AssertNoError(t, err, "Failed to get active notes") 196 - AssertEqual(t, 2, len(results), "Expected 2 active notes") 196 + shared.AssertNoError(t, err, "Failed to get active notes") 197 + shared.AssertEqual(t, 2, len(results), "Expected 2 active notes") 197 198 for _, note := range results { 198 - AssertFalse(t, note.Archived, "Retrieved note should not be archived") 199 + shared.AssertFalse(t, note.Archived, "Retrieved note should not be archived") 199 200 } 200 201 }) 201 202 ··· 206 207 Archived: false, 207 208 } 208 209 id, err := repo.Create(ctx, note) 209 - AssertNoError(t, err, "Failed to create note") 210 + shared.AssertNoError(t, err, "Failed to create note") 210 211 211 212 err = repo.Archive(ctx, id) 212 - AssertNoError(t, err, "Failed to archive note") 213 + shared.AssertNoError(t, err, "Failed to archive note") 213 214 214 215 retrieved, err := repo.Get(ctx, id) 215 - AssertNoError(t, err, "Failed to get note") 216 - AssertTrue(t, retrieved.Archived, "Note should be archived") 216 + shared.AssertNoError(t, err, "Failed to get note") 217 + shared.AssertTrue(t, retrieved.Archived, "Note should be archived") 217 218 218 219 err = repo.Unarchive(ctx, id) 219 - AssertNoError(t, err, "Failed to unarchive note") 220 + shared.AssertNoError(t, err, "Failed to unarchive note") 220 221 221 222 retrieved, err = repo.Get(ctx, id) 222 - AssertNoError(t, err, "Failed to get note") 223 - AssertFalse(t, retrieved.Archived, "Note should not be archived") 223 + shared.AssertNoError(t, err, "Failed to get note") 224 + shared.AssertFalse(t, retrieved.Archived, "Note should not be archived") 224 225 }) 225 226 226 227 t.Run("SearchContent", func(t *testing.T) { 227 228 results, err := repo.SearchContent(ctx, "Important") 228 - AssertNoError(t, err, "Failed to search content") 229 - AssertEqual(t, 1, len(results), "Expected 1 note") 229 + shared.AssertNoError(t, err, "Failed to search content") 230 + shared.AssertEqual(t, 1, len(results), "Expected 1 note") 230 231 if len(results) > 0 { 231 - AssertEqual(t, "Important Note", results[0].Title, "Expected 'Important Note'") 232 + shared.AssertEqual(t, "Important Note", results[0].Title, "Expected 'Important Note'") 232 233 } 233 234 }) 234 235 235 236 t.Run("GetRecent", func(t *testing.T) { 236 237 results, err := repo.GetRecent(ctx, 2) 237 - AssertNoError(t, err, "Failed to get recent notes") 238 - AssertEqual(t, 2, len(results), "Expected 2 notes") 238 + shared.AssertNoError(t, err, "Failed to get recent notes") 239 + shared.AssertEqual(t, 2, len(results), "Expected 2 notes") 239 240 }) 240 241 }) 241 242 ··· 250 251 Tags: []string{"initial"}, 251 252 } 252 253 id, err := repo.Create(ctx, note) 253 - AssertNoError(t, err, "Failed to create note") 254 + shared.AssertNoError(t, err, "Failed to create note") 254 255 255 256 t.Run("AddTag", func(t *testing.T) { 256 257 err := repo.AddTag(ctx, id, "new-tag") 257 - AssertNoError(t, err, "Failed to add tag") 258 + shared.AssertNoError(t, err, "Failed to add tag") 258 259 259 260 retrieved, err := repo.Get(ctx, id) 260 - AssertNoError(t, err, "Failed to get note") 261 + shared.AssertNoError(t, err, "Failed to get note") 261 262 262 - AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags") 263 + shared.AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags") 263 264 264 265 found := false 265 266 for _, tag := range retrieved.Tags { ··· 268 269 break 269 270 } 270 271 } 271 - AssertTrue(t, found, "New tag not found in note") 272 + shared.AssertTrue(t, found, "New tag not found in note") 272 273 }) 273 274 274 275 t.Run("AddTag Duplicate", func(t *testing.T) { 275 276 err := repo.AddTag(ctx, id, "new-tag") 276 - AssertNoError(t, err, "Failed to add duplicate tag") 277 + shared.AssertNoError(t, err, "Failed to add duplicate tag") 277 278 278 279 retrieved, err := repo.Get(ctx, id) 279 - AssertNoError(t, err, "Failed to get note") 280 + shared.AssertNoError(t, err, "Failed to get note") 280 281 281 - AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags (no duplicate)") 282 + shared.AssertEqual(t, 2, len(retrieved.Tags), "Expected 2 tags (no duplicate)") 282 283 }) 283 284 284 285 t.Run("RemoveTag", func(t *testing.T) { 285 286 err := repo.RemoveTag(ctx, id, "initial") 286 - AssertNoError(t, err, "Failed to remove tag") 287 + shared.AssertNoError(t, err, "Failed to remove tag") 287 288 288 289 retrieved, err := repo.Get(ctx, id) 289 - AssertNoError(t, err, "Failed to get note") 290 + shared.AssertNoError(t, err, "Failed to get note") 290 291 291 - AssertEqual(t, 1, len(retrieved.Tags), "Expected 1 tag after removal") 292 + shared.AssertEqual(t, 1, len(retrieved.Tags), "Expected 1 tag after removal") 292 293 293 294 for _, tag := range retrieved.Tags { 294 - AssertNotEqual(t, "initial", tag, "Removed tag still found in note") 295 + shared.AssertNotEqual(t, "initial", tag, "Removed tag still found in note") 295 296 } 296 297 }) 297 298 ··· 313 314 } 314 315 315 316 _, err := repo.Create(ctx, note1) 316 - AssertNoError(t, err, "Failed to create note1") 317 + shared.AssertNoError(t, err, "Failed to create note1") 317 318 _, err = repo.Create(ctx, note2) 318 - AssertNoError(t, err, "Failed to create note2") 319 + shared.AssertNoError(t, err, "Failed to create note2") 319 320 _, err = repo.Create(ctx, note3) 320 - AssertNoError(t, err, "Failed to create note3") 321 + shared.AssertNoError(t, err, "Failed to create note3") 321 322 322 323 results, err := repo.GetByTags(ctx, []string{"work"}) 323 - AssertNoError(t, err, "Failed to get notes by tag") 324 - AssertTrue(t, len(results) >= 2, "Expected at least 2 notes with 'work' tag") 324 + shared.AssertNoError(t, err, "Failed to get notes by tag") 325 + shared.AssertTrue(t, len(results) >= 2, "Expected at least 2 notes with 'work' tag") 325 326 326 327 results, err = repo.GetByTags(ctx, []string{"nonexistent"}) 327 - AssertNoError(t, err, "Failed to get notes by nonexistent tag") 328 - AssertEqual(t, 0, len(results), "Expected 0 notes with nonexistent tag") 328 + shared.AssertNoError(t, err, "Failed to get notes by nonexistent tag") 329 + shared.AssertEqual(t, 0, len(results), "Expected 0 notes with nonexistent tag") 329 330 330 331 results, err = repo.GetByTags(ctx, []string{}) 331 - AssertNoError(t, err, "Failed to get notes with empty tags") 332 - AssertEqual(t, 0, len(results), "Expected 0 notes with empty tag list") 332 + shared.AssertNoError(t, err, "Failed to get notes with empty tags") 333 + shared.AssertEqual(t, 0, len(results), "Expected 0 notes with empty tag list") 333 334 }) 334 335 }) 335 336 ··· 340 341 341 342 note := NewNoteBuilder().WithTitle("Test Note").WithContent("Test content").Build() 342 343 id, err := repo.Create(ctx, note) 343 - AssertNoError(t, err, "Failed to create note") 344 + shared.AssertNoError(t, err, "Failed to create note") 344 345 345 346 t.Run("Create with cancelled context", func(t *testing.T) { 346 347 newNote := NewNoteBuilder().WithTitle("Cancelled").Build() ··· 427 428 428 429 t.Run("Get non-existent note", func(t *testing.T) { 429 430 _, err := repo.Get(ctx, 99999) 430 - AssertError(t, err, "Expected error for non-existent note") 431 + shared.AssertError(t, err, "Expected error for non-existent note") 431 432 }) 432 433 433 434 t.Run("Update non-existent note", func(t *testing.T) { ··· 438 439 } 439 440 440 441 err := repo.Update(ctx, note) 441 - AssertError(t, err, "Expected error when updating non-existent note") 442 + shared.AssertError(t, err, "Expected error when updating non-existent note") 442 443 }) 443 444 444 445 t.Run("Delete non-existent note", func(t *testing.T) { 445 446 err := repo.Delete(ctx, 99999) 446 - AssertError(t, err, "Expected error when deleting non-existent note") 447 + shared.AssertError(t, err, "Expected error when deleting non-existent note") 447 448 }) 448 449 449 450 t.Run("Archive non-existent note", func(t *testing.T) { 450 451 err := repo.Archive(ctx, 99999) 451 - AssertError(t, err, "Expected error when archiving non-existent note") 452 + shared.AssertError(t, err, "Expected error when archiving non-existent note") 452 453 }) 453 454 454 455 t.Run("AddTag to non-existent note", func(t *testing.T) { 455 456 err := repo.AddTag(ctx, 99999, "tag") 456 - AssertError(t, err, "Expected error when adding tag to non-existent note") 457 + shared.AssertError(t, err, "Expected error when adding tag to non-existent note") 457 458 }) 458 459 459 460 t.Run("Note with empty tags", func(t *testing.T) { ··· 464 465 } 465 466 466 467 id, err := repo.Create(ctx, note) 467 - AssertNoError(t, err, "Failed to create note with empty tags") 468 + shared.AssertNoError(t, err, "Failed to create note with empty tags") 468 469 469 470 retrieved, err := repo.Get(ctx, id) 470 - AssertNoError(t, err, "Failed to get note") 471 + shared.AssertNoError(t, err, "Failed to get note") 471 472 472 - AssertEqual(t, 0, len(retrieved.Tags), "Expected empty tags slice") 473 + shared.AssertEqual(t, 0, len(retrieved.Tags), "Expected empty tags slice") 473 474 }) 474 475 475 476 t.Run("Note with nil tags", func(t *testing.T) { ··· 480 481 } 481 482 482 483 id, err := repo.Create(ctx, note) 483 - AssertNoError(t, err, "Failed to create note with nil tags") 484 + shared.AssertNoError(t, err, "Failed to create note with nil tags") 484 485 485 486 retrieved, err := repo.Get(ctx, id) 486 - AssertNoError(t, err, "Failed to get note") 487 + shared.AssertNoError(t, err, "Failed to get note") 487 488 488 - AssertEqual(t, 0, len(retrieved.Tags), "Expected empty tags") 489 + shared.AssertEqual(t, 0, len(retrieved.Tags), "Expected empty tags") 489 490 }) 490 491 491 492 t.Run("Note with long content", func(t *testing.T) { ··· 500 501 } 501 502 502 503 id, err := repo.Create(ctx, note) 503 - AssertNoError(t, err, "Failed to create note with long content") 504 + shared.AssertNoError(t, err, "Failed to create note with long content") 504 505 505 506 retrieved, err := repo.Get(ctx, id) 506 - AssertNoError(t, err, "Failed to get note") 507 + shared.AssertNoError(t, err, "Failed to get note") 507 508 508 - AssertEqual(t, longContent, retrieved.Content, "Long content was not stored/retrieved correctly") 509 + shared.AssertEqual(t, longContent, retrieved.Content, "Long content was not stored/retrieved correctly") 509 510 }) 510 511 511 512 t.Run("List with no results", func(t *testing.T) { 512 513 notes, err := repo.List(ctx, NoteListOptions{Title: "NonexistentTitle"}) 513 - AssertNoError(t, err, "Should not error when no notes found") 514 - AssertEqual(t, 0, len(notes), "Expected empty result set") 514 + shared.AssertNoError(t, err, "Should not error when no notes found") 515 + shared.AssertEqual(t, 0, len(notes), "Expected empty result set") 515 516 }) 516 517 }) 517 518 }
+36 -35
internal/repo/task_repository_test.go
··· 10 10 "github.com/google/uuid" 11 11 _ "github.com/mattn/go-sqlite3" 12 12 "github.com/stormlightlabs/noteleaf/internal/models" 13 + "github.com/stormlightlabs/noteleaf/internal/shared" 13 14 ) 14 15 15 16 func newUUID() string { ··· 156 157 t.Run("when called with context cancellation", func(t *testing.T) { 157 158 task := CreateSampleTask() 158 159 _, err := repo.Create(ctx, task) 159 - AssertNoError(t, err, "Failed to create task") 160 + shared.AssertNoError(t, err, "Failed to create task") 160 161 161 162 task.Description = "Updated" 162 163 err = repo.Update(NewCanceledContext(), task) ··· 923 924 defer func() { marshalTaskTags = orig }() 924 925 925 926 _, err := repo.Create(ctx, CreateSampleTask()) 926 - AssertError(t, err, "expected MarshalTags error") 927 - AssertContains(t, err.Error(), "failed to marshal tags", "error message") 927 + shared.AssertError(t, err, "expected MarshalTags error") 928 + shared.AssertContains(t, err.Error(), "failed to marshal tags", "error message") 928 929 }) 929 930 930 931 t.Run("Create fails on MarshalAnnotations error", func(t *testing.T) { ··· 935 936 defer func() { marshalTaskAnnotations = orig }() 936 937 937 938 _, err := repo.Create(ctx, CreateSampleTask()) 938 - AssertError(t, err, "expected MarshalAnnotations error") 939 - AssertContains(t, err.Error(), "failed to marshal annotations", "error message") 939 + shared.AssertError(t, err, "expected MarshalAnnotations error") 940 + shared.AssertContains(t, err.Error(), "failed to marshal annotations", "error message") 940 941 }) 941 942 942 943 t.Run("Update fails on MarshalTags error", func(t *testing.T) { 943 944 task := CreateSampleTask() 944 945 id, err := repo.Create(ctx, task) 945 - AssertNoError(t, err, "create should succeed") 946 + shared.AssertNoError(t, err, "create should succeed") 946 947 947 948 orig := marshalTaskTags 948 949 marshalTaskTags = func(t *models.Task) (string, error) { ··· 952 953 953 954 task.ID = id 954 955 err = repo.Update(ctx, task) 955 - AssertError(t, err, "expected MarshalTags error") 956 - AssertContains(t, err.Error(), "failed to marshal tags", "error message") 956 + shared.AssertError(t, err, "expected MarshalTags error") 957 + shared.AssertContains(t, err.Error(), "failed to marshal tags", "error message") 957 958 }) 958 959 959 960 t.Run("Update fails on MarshalAnnotations error", func(t *testing.T) { 960 961 task := CreateSampleTask() 961 962 id, err := repo.Create(ctx, task) 962 - AssertNoError(t, err, "create should succeed") 963 + shared.AssertNoError(t, err, "create should succeed") 963 964 964 965 orig := marshalTaskAnnotations 965 966 marshalTaskAnnotations = func(t *models.Task) (string, error) { ··· 969 970 970 971 task.ID = id 971 972 err = repo.Update(ctx, task) 972 - AssertError(t, err, "expected MarshalAnnotations error") 973 - AssertContains(t, err.Error(), "failed to marshal annotations", "error message") 973 + shared.AssertError(t, err, "expected MarshalAnnotations error") 974 + shared.AssertContains(t, err.Error(), "failed to marshal annotations", "error message") 974 975 }) 975 976 976 977 t.Run("Get fails on UnmarshalTags error", func(t *testing.T) { 977 978 task := CreateSampleTask() 978 979 task.Tags = []string{"test"} 979 980 id, err := repo.Create(ctx, task) 980 - AssertNoError(t, err, "create should succeed") 981 + shared.AssertNoError(t, err, "create should succeed") 981 982 982 983 orig := unmarshalTaskTags 983 984 unmarshalTaskTags = func(t *models.Task, s string) error { ··· 986 987 defer func() { unmarshalTaskTags = orig }() 987 988 988 989 _, err = repo.Get(ctx, id) 989 - AssertError(t, err, "expected UnmarshalTags error") 990 - AssertContains(t, err.Error(), "failed to unmarshal tags", "error message") 990 + shared.AssertError(t, err, "expected UnmarshalTags error") 991 + shared.AssertContains(t, err.Error(), "failed to unmarshal tags", "error message") 991 992 }) 992 993 993 994 t.Run("Get fails on UnmarshalAnnotations error", func(t *testing.T) { 994 995 task := CreateSampleTask() 995 996 task.Annotations = []string{"test"} 996 997 id, err := repo.Create(ctx, task) 997 - AssertNoError(t, err, "create should succeed") 998 + shared.AssertNoError(t, err, "create should succeed") 998 999 999 1000 orig := unmarshalTaskAnnotations 1000 1001 unmarshalTaskAnnotations = func(t *models.Task, s string) error { ··· 1003 1004 defer func() { unmarshalTaskAnnotations = orig }() 1004 1005 1005 1006 _, err = repo.Get(ctx, id) 1006 - AssertError(t, err, "expected UnmarshalAnnotations error") 1007 - AssertContains(t, err.Error(), "failed to unmarshal annotations", "error message") 1007 + shared.AssertError(t, err, "expected UnmarshalAnnotations error") 1008 + shared.AssertContains(t, err.Error(), "failed to unmarshal annotations", "error message") 1008 1009 }) 1009 1010 1010 1011 t.Run("GetByUUID fails on UnmarshalTags error", func(t *testing.T) { 1011 1012 task := CreateSampleTask() 1012 1013 task.Tags = []string{"test"} 1013 1014 _, err := repo.Create(ctx, task) 1014 - AssertNoError(t, err, "create should succeed") 1015 + shared.AssertNoError(t, err, "create should succeed") 1015 1016 1016 1017 orig := unmarshalTaskTags 1017 1018 unmarshalTaskTags = func(t *models.Task, s string) error { ··· 1020 1021 defer func() { unmarshalTaskTags = orig }() 1021 1022 1022 1023 _, err = repo.GetByUUID(ctx, task.UUID) 1023 - AssertError(t, err, "expected UnmarshalTags error") 1024 - AssertContains(t, err.Error(), "failed to unmarshal tags", "error message") 1024 + shared.AssertError(t, err, "expected UnmarshalTags error") 1025 + shared.AssertContains(t, err.Error(), "failed to unmarshal tags", "error message") 1025 1026 }) 1026 1027 1027 1028 t.Run("GetByUUID fails on UnmarshalAnnotations error", func(t *testing.T) { 1028 1029 task := CreateSampleTask() 1029 1030 task.Annotations = []string{"test"} 1030 1031 _, err := repo.Create(ctx, task) 1031 - AssertNoError(t, err, "create should succeed") 1032 + shared.AssertNoError(t, err, "create should succeed") 1032 1033 1033 1034 orig := unmarshalTaskAnnotations 1034 1035 unmarshalTaskAnnotations = func(t *models.Task, s string) error { ··· 1037 1038 defer func() { unmarshalTaskAnnotations = orig }() 1038 1039 1039 1040 _, err = repo.GetByUUID(ctx, task.UUID) 1040 - AssertError(t, err, "expected UnmarshalAnnotations error") 1041 - AssertContains(t, err.Error(), "failed to unmarshal annotations", "error message") 1041 + shared.AssertError(t, err, "expected UnmarshalAnnotations error") 1042 + shared.AssertContains(t, err.Error(), "failed to unmarshal annotations", "error message") 1042 1043 }) 1043 1044 }) 1044 1045 ··· 1101 1102 t.Run("GetByContext", func(t *testing.T) { 1102 1103 task1 := NewTaskBuilder().WithContext("work").WithDescription("Work task 1").Build() 1103 1104 _, err := repo.Create(ctx, task1) 1104 - AssertNoError(t, err, "Failed to create task1") 1105 + shared.AssertNoError(t, err, "Failed to create task1") 1105 1106 1106 1107 task2 := NewTaskBuilder().WithContext("home").WithDescription("Home task 1").Build() 1107 1108 _, err = repo.Create(ctx, task2) 1108 - AssertNoError(t, err, "Failed to create task2") 1109 + shared.AssertNoError(t, err, "Failed to create task2") 1109 1110 1110 1111 task3 := NewTaskBuilder().WithContext("work").WithDescription("Work task 2").Build() 1111 1112 _, err = repo.Create(ctx, task3) 1112 - AssertNoError(t, err, "Failed to create task3") 1113 + shared.AssertNoError(t, err, "Failed to create task3") 1113 1114 1114 1115 workTasks, err := repo.GetByContext(ctx, "work") 1115 1116 if err != nil { ··· 1142 1143 blocker := CreateSampleTask() 1143 1144 blocker.Description = "Blocker task" 1144 1145 _, err := repo.Create(ctx, blocker) 1145 - AssertNoError(t, err, "create blocker should succeed") 1146 + shared.AssertNoError(t, err, "create blocker should succeed") 1146 1147 1147 1148 blocked1 := CreateSampleTask() 1148 1149 blocked1.Description = "Blocked task 1" 1149 1150 blocked1.DependsOn = []string{blocker.UUID} 1150 1151 _, err = repo.Create(ctx, blocked1) 1151 - AssertNoError(t, err, "create blocked1 should succeed") 1152 + shared.AssertNoError(t, err, "create blocked1 should succeed") 1152 1153 1153 1154 blocked2 := CreateSampleTask() 1154 1155 blocked2.Description = "Blocked task 2" 1155 1156 blocked2.DependsOn = []string{blocker.UUID} 1156 1157 _, err = repo.Create(ctx, blocked2) 1157 - AssertNoError(t, err, "create blocked2 should succeed") 1158 + shared.AssertNoError(t, err, "create blocked2 should succeed") 1158 1159 1159 1160 independent := CreateSampleTask() 1160 1161 independent.Description = "Independent task" 1161 1162 _, err = repo.Create(ctx, independent) 1162 - AssertNoError(t, err, "create independent should succeed") 1163 + shared.AssertNoError(t, err, "create independent should succeed") 1163 1164 1164 1165 blockedTasks, err := repo.GetBlockedTasks(ctx, blocker.UUID) 1165 - AssertNoError(t, err, "GetBlockedTasks should succeed") 1166 - AssertEqual(t, 2, len(blockedTasks), "should find 2 blocked tasks") 1166 + shared.AssertNoError(t, err, "GetBlockedTasks should succeed") 1167 + shared.AssertEqual(t, 2, len(blockedTasks), "should find 2 blocked tasks") 1167 1168 1168 1169 for _, task := range blockedTasks { 1169 - AssertTrue(t, slices.Contains(task.DependsOn, blocker.UUID), "task should depend on blocker") 1170 + shared.AssertTrue(t, slices.Contains(task.DependsOn, blocker.UUID), "task should depend on blocker") 1170 1171 } 1171 1172 1172 1173 emptyBlocked, err := repo.GetBlockedTasks(ctx, independent.UUID) 1173 - AssertNoError(t, err, "GetBlockedTasks for independent should succeed") 1174 - AssertEqual(t, 0, len(emptyBlocked), "independent task should not block anything") 1174 + shared.AssertNoError(t, err, "GetBlockedTasks for independent should succeed") 1175 + shared.AssertEqual(t, 0, len(emptyBlocked), "independent task should not block anything") 1175 1176 }) 1176 1177 }
+12 -88
internal/repo/test_utilities.go
··· 12 12 "github.com/jaswdr/faker/v2" 13 13 _ "github.com/mattn/go-sqlite3" 14 14 "github.com/stormlightlabs/noteleaf/internal/models" 15 + "github.com/stormlightlabs/noteleaf/internal/shared" 15 16 "github.com/stormlightlabs/noteleaf/internal/store" 16 17 ) 17 18 ··· 169 170 return articles 170 171 } 171 172 172 - func AssertNoError(t *testing.T, err error, msg string) { 173 - t.Helper() 174 - if err != nil { 175 - t.Fatalf("%s: %v", msg, err) 176 - } 177 - } 178 - 179 - func AssertError(t *testing.T, err error, msg string) { 180 - t.Helper() 181 - if err == nil { 182 - t.Fatalf("%s: expected error but got none", msg) 183 - } 184 - } 185 - 186 173 func AssertCancelledContext(t *testing.T, err error) { 187 - AssertError(t, err, "Expected error with cancelled context") 188 - } 189 - 190 - func AssertEqual[T comparable](t *testing.T, expected, actual T, msg string) { 191 - t.Helper() 192 - if expected != actual { 193 - t.Fatalf("%s: expected %v, got %v", msg, expected, actual) 194 - } 195 - } 196 - 197 - func AssertNotEqual[T comparable](t *testing.T, notExpected, actual T, msg string) { 198 - t.Helper() 199 - if notExpected == actual { 200 - t.Fatalf("%s: expected value to not equal %v", msg, notExpected) 201 - } 202 - } 203 - 204 - func AssertTrue(t *testing.T, condition bool, msg string) { 205 - t.Helper() 206 - if !condition { 207 - t.Fatalf("%s: expected true", msg) 208 - } 209 - } 210 - 211 - func AssertFalse(t *testing.T, condition bool, msg string) { 212 - t.Helper() 213 - if condition { 214 - t.Fatalf("%s: expected false", msg) 215 - } 216 - } 217 - 218 - func AssertContains(t *testing.T, str, substr, msg string) { 219 - t.Helper() 220 - if !strings.Contains(str, substr) { 221 - t.Fatalf("%s: expected string '%s' to contain '%s'", msg, str, substr) 222 - } 223 - } 224 - 225 - func AssertNil(t *testing.T, value any, msg string) { 226 - t.Helper() 227 - if value != nil { 228 - t.Fatalf("%s: expected nil, got %v", msg, value) 229 - } 230 - } 231 - 232 - func AssertNotNil(t *testing.T, value any, msg string) { 233 - t.Helper() 234 - if value == nil { 235 - t.Fatalf("%s: expected non-nil value", msg) 236 - } 237 - } 238 - 239 - func AssertGreaterThan[T interface{ int | int64 | float64 }](t *testing.T, actual, threshold T, msg string) { 240 - t.Helper() 241 - if actual <= threshold { 242 - t.Fatalf("%s: expected %v > %v", msg, actual, threshold) 243 - } 244 - } 245 - 246 - func AssertLessThan[T interface{ int | int64 | float64 }](t *testing.T, actual, threshold T, msg string) { 247 - t.Helper() 248 - if actual >= threshold { 249 - t.Fatalf("%s: expected %v < %v", msg, actual, threshold) 250 - } 174 + shared.AssertError(t, err, "Expected error with cancelled context") 251 175 } 252 176 253 177 // NewCanceledContext returns a pre-canceled context for testing error conditions ··· 565 489 task2.Priority = "low" 566 490 567 491 id1, err := repos.Tasks.Create(ctx, task1) 568 - AssertNoError(t, err, "Failed to create sample task 1") 492 + shared.AssertNoError(t, err, "Failed to create sample task 1") 569 493 task1.ID = id1 570 494 571 495 id2, err := repos.Tasks.Create(ctx, task2) 572 - AssertNoError(t, err, "Failed to create sample task 2") 496 + shared.AssertNoError(t, err, "Failed to create sample task 2") 573 497 task2.ID = id2 574 498 575 499 book1 := CreateSampleBook() ··· 581 505 book2.Status = "finished" 582 506 583 507 bookID1, err := repos.Books.Create(ctx, book1) 584 - AssertNoError(t, err, "Failed to create sample book 1") 508 + shared.AssertNoError(t, err, "Failed to create sample book 1") 585 509 book1.ID = bookID1 586 510 587 511 bookID2, err := repos.Books.Create(ctx, book2) 588 - AssertNoError(t, err, "Failed to create sample book 2") 512 + shared.AssertNoError(t, err, "Failed to create sample book 2") 589 513 book2.ID = bookID2 590 514 591 515 movie1 := CreateSampleMovie() ··· 597 521 movie2.Status = "watched" 598 522 599 523 movieID1, err := repos.Movies.Create(ctx, movie1) 600 - AssertNoError(t, err, "Failed to create sample movie 1") 524 + shared.AssertNoError(t, err, "Failed to create sample movie 1") 601 525 movie1.ID = movieID1 602 526 603 527 movieID2, err := repos.Movies.Create(ctx, movie2) 604 - AssertNoError(t, err, "Failed to create sample movie 2") 528 + shared.AssertNoError(t, err, "Failed to create sample movie 2") 605 529 movie2.ID = movieID2 606 530 607 531 tv1 := CreateSampleTVShow() ··· 613 537 tv2.Status = "watching" 614 538 615 539 tvID1, err := repos.TV.Create(ctx, tv1) 616 - AssertNoError(t, err, "Failed to create sample TV show 1") 540 + shared.AssertNoError(t, err, "Failed to create sample TV show 1") 617 541 tv1.ID = tvID1 618 542 619 543 tvID2, err := repos.TV.Create(ctx, tv2) 620 - AssertNoError(t, err, "Failed to create sample TV show 2") 544 + shared.AssertNoError(t, err, "Failed to create sample TV show 2") 621 545 tv2.ID = tvID2 622 546 623 547 note1 := CreateSampleNote() ··· 630 554 note2.Archived = true 631 555 632 556 noteID1, err := repos.Notes.Create(ctx, note1) 633 - AssertNoError(t, err, "Failed to create sample note 1") 557 + shared.AssertNoError(t, err, "Failed to create sample note 1") 634 558 note1.ID = noteID1 635 559 636 560 noteID2, err := repos.Notes.Create(ctx, note2) 637 - AssertNoError(t, err, "Failed to create sample note 2") 561 + shared.AssertNoError(t, err, "Failed to create sample note 2") 638 562 note2.ID = noteID2 639 563 640 564 return repos
+79 -78
internal/repo/time_entry_repository_test.go
··· 9 9 10 10 _ "github.com/mattn/go-sqlite3" 11 11 "github.com/stormlightlabs/noteleaf/internal/models" 12 + "github.com/stormlightlabs/noteleaf/internal/shared" 12 13 ) 13 14 14 15 func createTestTask(t *testing.T, db *sql.DB) *models.Task { ··· 22 23 } 23 24 24 25 id, err := taskRepo.Create(ctx, task) 25 - AssertNoError(t, err, "Failed to create test task") 26 + shared.AssertNoError(t, err, "Failed to create test task") 26 27 task.ID = id 27 28 return task 28 29 } ··· 38 39 description := "Working on feature" 39 40 entry, err := repo.Start(ctx, task.ID, description) 40 41 41 - AssertNoError(t, err, "Failed to start time tracking") 42 - AssertNotEqual(t, int64(0), entry.ID, "Expected non-zero entry ID") 43 - AssertEqual(t, task.ID, entry.TaskID, "Expected TaskID to match") 44 - AssertEqual(t, description, entry.Description, "Expected description to match") 45 - AssertTrue(t, entry.EndTime == nil, "Expected EndTime to be nil for active entry") 46 - AssertTrue(t, entry.IsActive(), "Expected entry to be active") 42 + shared.AssertNoError(t, err, "Failed to start time tracking") 43 + shared.AssertNotEqual(t, int64(0), entry.ID, "Expected non-zero entry ID") 44 + shared.AssertEqual(t, task.ID, entry.TaskID, "Expected TaskID to match") 45 + shared.AssertEqual(t, description, entry.Description, "Expected description to match") 46 + shared.AssertTrue(t, entry.EndTime == nil, "Expected EndTime to be nil for active entry") 47 + shared.AssertTrue(t, entry.IsActive(), "Expected entry to be active") 47 48 }) 48 49 49 50 t.Run("Prevent starting already active task", func(t *testing.T) { 50 51 _, err := repo.Start(ctx, task.ID, "Another attempt") 51 52 52 - AssertError(t, err, "Expected error when starting already active task") 53 - AssertContains(t, err.Error(), "task already has an active time entry", "Expected specific error message") 53 + shared.AssertError(t, err, "Expected error when starting already active task") 54 + shared.AssertContains(t, err.Error(), "task already has an active time entry", "Expected specific error message") 54 55 }) 55 56 56 57 t.Run("Stop active time entry", func(t *testing.T) { ··· 59 60 task := createTestTask(t, db) 60 61 61 62 entry, err := repo.Start(ctx, task.ID, "Test work") 62 - AssertNoError(t, err, "Failed to start time tracking") 63 + shared.AssertNoError(t, err, "Failed to start time tracking") 63 64 64 65 time.Sleep(1010 * time.Millisecond) 65 66 66 67 stoppedEntry, err := repo.Stop(ctx, entry.ID) 67 - AssertNoError(t, err, "Failed to stop time tracking") 68 - AssertTrue(t, stoppedEntry.EndTime != nil, "Expected EndTime to be set") 69 - AssertGreaterThan(t, stoppedEntry.DurationSeconds, int64(0), "Expected duration > 0") 70 - AssertFalse(t, stoppedEntry.IsActive(), "Expected entry to not be active after stopping") 68 + shared.AssertNoError(t, err, "Failed to stop time tracking") 69 + shared.AssertTrue(t, stoppedEntry.EndTime != nil, "Expected EndTime to be set") 70 + shared.AssertGreaterThan(t, stoppedEntry.DurationSeconds, int64(0), "Expected duration > 0") 71 + shared.AssertFalse(t, stoppedEntry.IsActive(), "Expected entry to not be active after stopping") 71 72 }) 72 73 73 74 t.Run("Fail to stop already stopped entry", func(t *testing.T) { ··· 76 77 task := createTestTask(t, db) 77 78 78 79 entry, err := repo.Start(ctx, task.ID, "Test work") 79 - AssertNoError(t, err, "Failed to start time tracking") 80 + shared.AssertNoError(t, err, "Failed to start time tracking") 80 81 81 82 time.Sleep(1010 * time.Millisecond) 82 83 _, err = repo.Stop(ctx, entry.ID) 83 - AssertNoError(t, err, "Failed to stop time tracking") 84 + shared.AssertNoError(t, err, "Failed to stop time tracking") 84 85 85 86 _, err = repo.Stop(ctx, entry.ID) 86 - AssertError(t, err, "Expected error when stopping already stopped entry") 87 - AssertContains(t, err.Error(), "time entry is not active", "Expected specific error message") 87 + shared.AssertError(t, err, "Expected error when stopping already stopped entry") 88 + shared.AssertContains(t, err.Error(), "time entry is not active", "Expected specific error message") 88 89 }) 89 90 90 91 t.Run("Get time entry", func(t *testing.T) { ··· 93 94 task := createTestTask(t, db) 94 95 95 96 original, err := repo.Start(ctx, task.ID, "Test entry") 96 - AssertNoError(t, err, "Failed to start time tracking") 97 + shared.AssertNoError(t, err, "Failed to start time tracking") 97 98 98 99 retrieved, err := repo.Get(ctx, original.ID) 99 - AssertNoError(t, err, "Failed to get time entry") 100 - AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 101 - AssertEqual(t, original.TaskID, retrieved.TaskID, "TaskID mismatch") 102 - AssertEqual(t, original.Description, retrieved.Description, "Description mismatch") 100 + shared.AssertNoError(t, err, "Failed to get time entry") 101 + shared.AssertEqual(t, original.ID, retrieved.ID, "ID mismatch") 102 + shared.AssertEqual(t, original.TaskID, retrieved.TaskID, "TaskID mismatch") 103 + shared.AssertEqual(t, original.Description, retrieved.Description, "Description mismatch") 103 104 }) 104 105 105 106 t.Run("Delete time entry", func(t *testing.T) { ··· 108 109 task := createTestTask(t, db) 109 110 110 111 entry, err := repo.Start(ctx, task.ID, "To be deleted") 111 - AssertNoError(t, err, "Failed to create entry") 112 + shared.AssertNoError(t, err, "Failed to create entry") 112 113 113 114 err = repo.Delete(ctx, entry.ID) 114 - AssertNoError(t, err, "Failed to delete entry") 115 + shared.AssertNoError(t, err, "Failed to delete entry") 115 116 116 117 _, err = repo.Get(ctx, entry.ID) 117 - AssertError(t, err, "Expected error when getting deleted entry") 118 - AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 118 + shared.AssertError(t, err, "Expected error when getting deleted entry") 119 + shared.AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 119 120 }) 120 121 }) 121 122 ··· 127 128 128 129 t.Run("GetActiveByTaskID returns error when no active entry", func(t *testing.T) { 129 130 _, err := repo.GetActiveByTaskID(ctx, task.ID) 130 - AssertError(t, err, "Expected error when no active entry exists") 131 - AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 131 + shared.AssertError(t, err, "Expected error when no active entry exists") 132 + shared.AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 132 133 }) 133 134 134 135 t.Run("GetActiveByTaskID returns active entry", func(t *testing.T) { 135 136 startedEntry, err := repo.Start(ctx, task.ID, "Test work") 136 - AssertNoError(t, err, "Failed to start time tracking") 137 + shared.AssertNoError(t, err, "Failed to start time tracking") 137 138 138 139 activeEntry, err := repo.GetActiveByTaskID(ctx, task.ID) 139 - AssertNoError(t, err, "Failed to get active entry") 140 - AssertEqual(t, startedEntry.ID, activeEntry.ID, "Expected entry IDs to match") 141 - AssertTrue(t, activeEntry.IsActive(), "Expected entry to be active") 140 + shared.AssertNoError(t, err, "Failed to get active entry") 141 + shared.AssertEqual(t, startedEntry.ID, activeEntry.ID, "Expected entry IDs to match") 142 + shared.AssertTrue(t, activeEntry.IsActive(), "Expected entry to be active") 142 143 }) 143 144 144 145 t.Run("StopActiveByTaskID stops active entry", func(t *testing.T) { ··· 147 148 task := createTestTask(t, db) 148 149 149 150 _, err := repo.Start(ctx, task.ID, "Test work") 150 - AssertNoError(t, err, "Failed to start time tracking") 151 + shared.AssertNoError(t, err, "Failed to start time tracking") 151 152 152 153 stoppedEntry, err := repo.StopActiveByTaskID(ctx, task.ID) 153 - AssertNoError(t, err, "Failed to stop time tracking by task ID") 154 - AssertTrue(t, stoppedEntry.EndTime != nil, "Expected EndTime to be set") 155 - AssertFalse(t, stoppedEntry.IsActive(), "Expected entry to not be active") 154 + shared.AssertNoError(t, err, "Failed to stop time tracking by task ID") 155 + shared.AssertTrue(t, stoppedEntry.EndTime != nil, "Expected EndTime to be set") 156 + shared.AssertFalse(t, stoppedEntry.IsActive(), "Expected entry to not be active") 156 157 }) 157 158 158 159 t.Run("StopActiveByTaskID fails when no active entry", func(t *testing.T) { ··· 161 162 task := createTestTask(t, db) 162 163 163 164 _, err := repo.StopActiveByTaskID(ctx, task.ID) 164 - AssertError(t, err, "Expected error when no active entry exists") 165 - AssertContains(t, err.Error(), "no active time entry found for task", "Expected specific error message") 165 + shared.AssertError(t, err, "Expected error when no active entry exists") 166 + shared.AssertContains(t, err.Error(), "no active time entry found for task", "Expected specific error message") 166 167 }) 167 168 168 169 t.Run("GetByTaskID returns empty when no entries", func(t *testing.T) { ··· 171 172 task := createTestTask(t, db) 172 173 173 174 entries, err := repo.GetByTaskID(ctx, task.ID) 174 - AssertNoError(t, err, "Failed to get entries") 175 - AssertEqual(t, 0, len(entries), "Expected 0 entries") 175 + shared.AssertNoError(t, err, "Failed to get entries") 176 + shared.AssertEqual(t, 0, len(entries), "Expected 0 entries") 176 177 }) 177 178 178 179 t.Run("GetByTaskID returns all entries for task", func(t *testing.T) { ··· 181 182 task := createTestTask(t, db) 182 183 183 184 _, err := repo.Start(ctx, task.ID, "First session") 184 - AssertNoError(t, err, "Failed to start first session") 185 + shared.AssertNoError(t, err, "Failed to start first session") 185 186 186 187 _, err = repo.StopActiveByTaskID(ctx, task.ID) 187 - AssertNoError(t, err, "Failed to stop first session") 188 + shared.AssertNoError(t, err, "Failed to stop first session") 188 189 189 190 _, err = repo.Start(ctx, task.ID, "Second session") 190 - AssertNoError(t, err, "Failed to start second session") 191 + shared.AssertNoError(t, err, "Failed to start second session") 191 192 192 193 entries, err := repo.GetByTaskID(ctx, task.ID) 193 - AssertNoError(t, err, "Failed to get entries") 194 - AssertEqual(t, 2, len(entries), "Expected 2 entries") 195 - AssertEqual(t, "Second session", entries[0].Description, "Expected newest entry first") 196 - AssertEqual(t, "First session", entries[1].Description, "Expected oldest entry second") 194 + shared.AssertNoError(t, err, "Failed to get entries") 195 + shared.AssertEqual(t, 2, len(entries), "Expected 2 entries") 196 + shared.AssertEqual(t, "Second session", entries[0].Description, "Expected newest entry first") 197 + shared.AssertEqual(t, "First session", entries[1].Description, "Expected oldest entry second") 197 198 }) 198 199 199 200 t.Run("GetTotalTimeByTaskID returns zero when no entries", func(t *testing.T) { ··· 202 203 task := createTestTask(t, db) 203 204 204 205 duration, err := repo.GetTotalTimeByTaskID(ctx, task.ID) 205 - AssertNoError(t, err, "Failed to get total time") 206 - AssertEqual(t, time.Duration(0), duration, "Expected 0 duration") 206 + shared.AssertNoError(t, err, "Failed to get total time") 207 + shared.AssertEqual(t, time.Duration(0), duration, "Expected 0 duration") 207 208 }) 208 209 209 210 t.Run("GetTotalTimeByTaskID calculates total including active entries", func(t *testing.T) { ··· 212 213 task := createTestTask(t, db) 213 214 214 215 entry1, err := repo.Start(ctx, task.ID, "Completed work") 215 - AssertNoError(t, err, "Failed to start first entry") 216 + shared.AssertNoError(t, err, "Failed to start first entry") 216 217 217 218 time.Sleep(1010 * time.Millisecond) 218 219 _, err = repo.Stop(ctx, entry1.ID) 219 - AssertNoError(t, err, "Failed to stop first entry") 220 + shared.AssertNoError(t, err, "Failed to stop first entry") 220 221 221 222 _, err = repo.Start(ctx, task.ID, "Active work") 222 - AssertNoError(t, err, "Failed to start second entry") 223 + shared.AssertNoError(t, err, "Failed to start second entry") 223 224 224 225 time.Sleep(1010 * time.Millisecond) 225 226 226 227 totalTime, err := repo.GetTotalTimeByTaskID(ctx, task.ID) 227 - AssertNoError(t, err, "Failed to get total time") 228 - AssertTrue(t, totalTime > 0, "Expected total time > 0") 229 - AssertTrue(t, totalTime >= 2*time.Second, "Expected total time >= 2s") 228 + shared.AssertNoError(t, err, "Failed to get total time") 229 + shared.AssertTrue(t, totalTime > 0, "Expected total time > 0") 230 + shared.AssertTrue(t, totalTime >= 2*time.Second, "Expected total time >= 2s") 230 231 }) 231 232 }) 232 233 ··· 240 241 end := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) 241 242 242 243 entries, err := repo.GetByDateRange(ctx, start, end) 243 - AssertNoError(t, err, "Failed to get entries by date range") 244 - AssertEqual(t, 0, len(entries), "Expected 0 entries") 244 + shared.AssertNoError(t, err, "Failed to get entries by date range") 245 + shared.AssertEqual(t, 0, len(entries), "Expected 0 entries") 245 246 }) 246 247 247 248 t.Run("Returns entries within date range", func(t *testing.T) { 248 249 task := createTestTask(t, db) 249 250 250 251 entry, err := repo.Start(ctx, task.ID, "Test entry") 251 - AssertNoError(t, err, "Failed to start entry") 252 + shared.AssertNoError(t, err, "Failed to start entry") 252 253 253 254 _, err = repo.Stop(ctx, entry.ID) 254 - AssertNoError(t, err, "Failed to stop entry") 255 + shared.AssertNoError(t, err, "Failed to stop entry") 255 256 256 257 now := time.Now() 257 258 start := now.Add(-time.Hour) 258 259 end := now.Add(time.Hour) 259 260 260 261 entries, err := repo.GetByDateRange(ctx, start, end) 261 - AssertNoError(t, err, "Failed to get entries by date range") 262 + shared.AssertNoError(t, err, "Failed to get entries by date range") 262 263 263 264 found := false 264 265 for _, e := range entries { ··· 267 268 break 268 269 } 269 270 } 270 - AssertTrue(t, found, "Expected to find 'Test entry' in results") 271 + shared.AssertTrue(t, found, "Expected to find 'Test entry' in results") 271 272 }) 272 273 273 274 t.Run("Respects date range boundaries", func(t *testing.T) { 274 275 task := createTestTask(t, db) 275 276 276 277 entry, err := repo.Start(ctx, task.ID, "Boundary test") 277 - AssertNoError(t, err, "Failed to start entry") 278 + shared.AssertNoError(t, err, "Failed to start entry") 278 279 279 280 _, err = repo.Stop(ctx, entry.ID) 280 - AssertNoError(t, err, "Failed to stop entry") 281 + shared.AssertNoError(t, err, "Failed to stop entry") 281 282 282 283 start := time.Now().Add(time.Hour) 283 284 end := time.Now().Add(2 * time.Hour) 284 285 285 286 entries, err := repo.GetByDateRange(ctx, start, end) 286 - AssertNoError(t, err, "Failed to get entries by date range") 287 + shared.AssertNoError(t, err, "Failed to get entries by date range") 287 288 288 289 for _, e := range entries { 289 290 if e.Description == "Boundary test" { ··· 297 298 end := time.Now().AddDate(0, 0, -1) 298 299 299 300 entries, err := repo.GetByDateRange(ctx, start, end) 300 - AssertNoError(t, err, "Should not error with invalid date range") 301 - AssertEqual(t, 0, len(entries), "Expected 0 entries with invalid range") 301 + shared.AssertNoError(t, err, "Should not error with invalid date range") 302 + shared.AssertEqual(t, 0, len(entries), "Expected 0 entries with invalid range") 302 303 }) 303 304 }) 304 305 ··· 309 310 task := createTestTask(t, db) 310 311 311 312 entry, err := repo.Start(ctx, task.ID, "Test entry") 312 - AssertNoError(t, err, "Failed to create entry") 313 + shared.AssertNoError(t, err, "Failed to create entry") 313 314 314 315 t.Run("Start with cancelled context", func(t *testing.T) { 315 316 db := CreateTestDB(t) ··· 371 372 372 373 t.Run("Get non-existent entry", func(t *testing.T) { 373 374 _, err := repo.Get(ctx, 99999) 374 - AssertError(t, err, "Expected error for non-existent entry") 375 - AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 375 + shared.AssertError(t, err, "Expected error for non-existent entry") 376 + shared.AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 376 377 }) 377 378 378 379 t.Run("Stop non-existent entry", func(t *testing.T) { 379 380 _, err := repo.Stop(ctx, 99999) 380 - AssertError(t, err, "Expected error for non-existent entry") 381 + shared.AssertError(t, err, "Expected error for non-existent entry") 381 382 }) 382 383 383 384 t.Run("Delete non-existent entry", func(t *testing.T) { 384 385 err := repo.Delete(ctx, 99999) 385 - AssertError(t, err, "Expected error for non-existent entry") 386 - AssertContains(t, err.Error(), "time entry not found", "Expected specific error message") 386 + shared.AssertError(t, err, "Expected error for non-existent entry") 387 + shared.AssertContains(t, err.Error(), "time entry not found", "Expected specific error message") 387 388 }) 388 389 389 390 t.Run("Start with non-existent task", func(t *testing.T) { 390 391 _, err := repo.Start(ctx, 99999, "Test") 391 - AssertError(t, err, "Expected error for non-existent task") 392 + shared.AssertError(t, err, "Expected error for non-existent task") 392 393 }) 393 394 394 395 t.Run("GetActiveByTaskID with no results", func(t *testing.T) { 395 396 task := createTestTask(t, db) 396 397 _, err := repo.GetActiveByTaskID(ctx, task.ID) 397 - AssertError(t, err, "Expected error when no active entry") 398 - AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 398 + shared.AssertError(t, err, "Expected error when no active entry") 399 + shared.AssertEqual(t, sql.ErrNoRows, err, "Expected sql.ErrNoRows") 399 400 }) 400 401 401 402 t.Run("GetByTaskID with no results", func(t *testing.T) { 402 403 task := createTestTask(t, db) 403 404 entries, err := repo.GetByTaskID(ctx, task.ID) 404 - AssertNoError(t, err, "Should not error when no entries found") 405 - AssertEqual(t, 0, len(entries), "Expected empty result set") 405 + shared.AssertNoError(t, err, "Should not error when no entries found") 406 + shared.AssertEqual(t, 0, len(entries), "Expected empty result set") 406 407 }) 407 408 }) 408 409 }
+11 -10
internal/repo/tv_repository_test.go
··· 7 7 8 8 _ "github.com/mattn/go-sqlite3" 9 9 "github.com/stormlightlabs/noteleaf/internal/models" 10 + "github.com/stormlightlabs/noteleaf/internal/shared" 10 11 ) 11 12 12 13 func TestTVRepository(t *testing.T) { ··· 475 476 476 477 tvShow := NewTVShowBuilder().WithTitle("Test Show").WithSeason(1).WithEpisode(1).Build() 477 478 id, err := repo.Create(ctx, tvShow) 478 - AssertNoError(t, err, "Failed to create TV show") 479 + shared.AssertNoError(t, err, "Failed to create TV show") 479 480 480 481 t.Run("Create with cancelled context", func(t *testing.T) { 481 482 newShow := NewTVShowBuilder().WithTitle("Cancelled").Build() ··· 547 548 548 549 t.Run("Get non-existent TV show", func(t *testing.T) { 549 550 _, err := repo.Get(ctx, 99999) 550 - AssertError(t, err, "Expected error for non-existent TV show") 551 + shared.AssertError(t, err, "Expected error for non-existent TV show") 551 552 }) 552 553 553 554 t.Run("Update non-existent TV show succeeds with no rows affected", func(t *testing.T) { 554 555 show := NewTVShowBuilder().WithTitle("Non-existent").Build() 555 556 show.ID = 99999 556 557 err := repo.Update(ctx, show) 557 - AssertNoError(t, err, "Update should not error when no rows affected") 558 + shared.AssertNoError(t, err, "Update should not error when no rows affected") 558 559 }) 559 560 560 561 t.Run("Delete non-existent TV show succeeds with no rows affected", func(t *testing.T) { 561 562 err := repo.Delete(ctx, 99999) 562 - AssertNoError(t, err, "Delete should not error when no rows affected") 563 + shared.AssertNoError(t, err, "Delete should not error when no rows affected") 563 564 }) 564 565 565 566 t.Run("MarkWatched non-existent TV show", func(t *testing.T) { 566 567 err := repo.MarkWatched(ctx, 99999) 567 - AssertError(t, err, "Expected error for non-existent TV show") 568 + shared.AssertError(t, err, "Expected error for non-existent TV show") 568 569 }) 569 570 570 571 t.Run("StartWatching non-existent TV show", func(t *testing.T) { 571 572 err := repo.StartWatching(ctx, 99999) 572 - AssertError(t, err, "Expected error for non-existent TV show") 573 + shared.AssertError(t, err, "Expected error for non-existent TV show") 573 574 }) 574 575 575 576 t.Run("GetByTitle with no results", func(t *testing.T) { 576 577 shows, err := repo.GetByTitle(ctx, "NonExistentShow") 577 - AssertNoError(t, err, "Should not error when no shows found") 578 - AssertEqual(t, 0, len(shows), "Expected empty result set") 578 + shared.AssertNoError(t, err, "Should not error when no shows found") 579 + shared.AssertEqual(t, 0, len(shows), "Expected empty result set") 579 580 }) 580 581 581 582 t.Run("GetBySeason with no results", func(t *testing.T) { 582 583 shows, err := repo.GetBySeason(ctx, "NonExistentShow", 1) 583 - AssertNoError(t, err, "Should not error when no shows found") 584 - AssertEqual(t, 0, len(shows), "Expected empty result set") 584 + shared.AssertNoError(t, err, "Should not error when no shows found") 585 + shared.AssertEqual(t, 0, len(shows), "Expected empty result set") 585 586 }) 586 587 }) 587 588 }
+7 -7
internal/services/http_test.go
··· 21 21 ParseSearch = origSearch 22 22 }() 23 23 24 - tests := []struct { 24 + tc := []struct { 25 25 name string 26 26 setup func() 27 27 call func() error ··· 165 165 }, 166 166 } 167 167 168 - for _, tc := range tests { 169 - t.Run(tc.name, func(t *testing.T) { 168 + for _, tt := range tc { 169 + t.Run(tt.name, func(t *testing.T) { 170 170 FetchHTML = origFetch 171 171 ExtractMovieMetadata = origMovie 172 172 ExtractTVSeriesMetadata = origTV 173 173 ExtractTVSeasonMetadata = origSeason 174 174 ParseSearch = origSearch 175 175 176 - tc.setup() 177 - err := tc.call() 178 - if tc.expectErr && err == nil { 176 + tt.setup() 177 + err := tt.call() 178 + if tt.expectErr && err == nil { 179 179 t.Fatalf("expected error, got nil") 180 180 } 181 - if !tc.expectErr && err != nil { 181 + if !tt.expectErr && err != nil { 182 182 t.Fatalf("unexpected error: %v", err) 183 183 } 184 184 })
+19
internal/shared/shared.go
··· 1 + // package shared contains constants used across the codebase 2 + package shared 3 + 4 + import ( 5 + "errors" 6 + "fmt" 7 + ) 8 + 9 + var ( 10 + ErrConfig error = fmt.Errorf("configuration error") 11 + ) 12 + 13 + func ConfigError(m string, err error) error { 14 + return errors.Join(ErrConfig, fmt.Errorf("%s: %w", m, err)) 15 + } 16 + 17 + func IsConfigError(err error) bool { 18 + return errors.Is(err, ErrConfig) 19 + }
+70
internal/shared/shared_test.go
··· 1 + package shared 2 + 3 + import ( 4 + "errors" 5 + "testing" 6 + ) 7 + 8 + func TestErrors(t *testing.T) { 9 + t.Run("ConfigError", func(t *testing.T) { 10 + t.Run("creates joined error with message", func(t *testing.T) { 11 + baseErr := errors.New("invalid format") 12 + err := ConfigError("database connection failed", baseErr) 13 + 14 + AssertError(t, err, "ConfigError should create an error") 15 + AssertContains(t, err.Error(), "configuration error", "error should contain config error marker") 16 + AssertContains(t, err.Error(), "database connection failed", "error should contain custom message") 17 + AssertContains(t, err.Error(), "invalid format", "error should contain base error") 18 + }) 19 + 20 + t.Run("preserves both error chains", func(t *testing.T) { 21 + baseErr := errors.New("connection timeout") 22 + err := ConfigError("failed to connect", baseErr) 23 + 24 + AssertTrue(t, errors.Is(err, ErrConfig), "should identify as config error") 25 + AssertTrue(t, errors.Is(err, baseErr), "should preserve original error in chain") 26 + }) 27 + 28 + t.Run("wraps multiple errors with Join", func(t *testing.T) { 29 + baseErr := errors.New("parse error") 30 + err := ConfigError("invalid config file", baseErr) 31 + 32 + AssertTrue(t, errors.Is(err, ErrConfig), "joined error should contain ErrConfig") 33 + AssertTrue(t, errors.Is(err, baseErr), "joined error should contain base error") 34 + }) 35 + }) 36 + 37 + t.Run("IsConfigError", func(t *testing.T) { 38 + t.Run("identifies config errors", func(t *testing.T) { 39 + baseErr := errors.New("test error") 40 + err := ConfigError("test message", baseErr) 41 + 42 + AssertTrue(t, IsConfigError(err), "should identify config error") 43 + }) 44 + 45 + t.Run("returns false for regular errors", func(t *testing.T) { 46 + err := errors.New("regular error") 47 + 48 + AssertFalse(t, IsConfigError(err), "should not identify regular error as config error") 49 + }) 50 + 51 + t.Run("returns false for nil error", func(t *testing.T) { 52 + AssertFalse(t, IsConfigError(nil), "should return false for nil error") 53 + }) 54 + 55 + t.Run("returns false for wrapped non-config errors", func(t *testing.T) { 56 + baseErr := errors.New("base error") 57 + wrappedErr := errors.New("wrapped: " + baseErr.Error()) 58 + 59 + AssertFalse(t, IsConfigError(wrappedErr), "should not identify wrapped non-config error") 60 + }) 61 + 62 + t.Run("identifies wrapped config errors", func(t *testing.T) { 63 + baseErr := errors.New("original error") 64 + configErr := ConfigError("config issue", baseErr) 65 + wrappedAgain := errors.Join(errors.New("outer error"), configErr) 66 + 67 + AssertTrue(t, IsConfigError(wrappedAgain), "should identify config error in join chain") 68 + }) 69 + }) 70 + }
+238
internal/shared/test_utilities.go
··· 1 + // shared test utilities & helpers 2 + package shared 3 + 4 + import ( 5 + "encoding/json" 6 + "net/http" 7 + "net/http/httptest" 8 + "os" 9 + "strings" 10 + "testing" 11 + "time" 12 + ) 13 + 14 + func CreateTempDir(p string, t *testing.T) (string, func()) { 15 + t.Helper() 16 + tempDir, err := os.MkdirTemp("", p) 17 + if err != nil { 18 + t.Fatalf("Failed to create temp directory: %v", err) 19 + } 20 + return tempDir, func() { os.RemoveAll(tempDir) } 21 + } 22 + 23 + func AssertNoError(t *testing.T, err error, msg string) { 24 + t.Helper() 25 + if err != nil { 26 + t.Fatalf("%s: %v", msg, err) 27 + } 28 + } 29 + 30 + func AssertError(t *testing.T, err error, msg string) { 31 + t.Helper() 32 + if err == nil { 33 + t.Fatalf("%s: expected error but got none", msg) 34 + } 35 + } 36 + 37 + // AssertErrorContains checks that an error occurred and optionally contains expected text 38 + func AssertErrorContains(t *testing.T, err error, expected, msg string) { 39 + t.Helper() 40 + if err == nil { 41 + t.Errorf("%s: expected error but got none", msg) 42 + return 43 + } 44 + if expected != "" && !ContainsString(err.Error(), expected) { 45 + t.Errorf("%s: expected error containing %q, got: %v", msg, expected, err) 46 + } 47 + } 48 + 49 + func AssertTrue(t *testing.T, condition bool, msg string) { 50 + t.Helper() 51 + if !condition { 52 + t.Fatalf("%s: expected true", msg) 53 + } 54 + } 55 + 56 + func AssertFalse(t *testing.T, condition bool, msg string) { 57 + t.Helper() 58 + if condition { 59 + t.Fatalf("%s: expected false", msg) 60 + } 61 + } 62 + 63 + func AssertContains(t *testing.T, str, substr, msg string) { 64 + t.Helper() 65 + if !strings.Contains(str, substr) { 66 + t.Fatalf("%s: expected string '%s' to contain '%s'", msg, str, substr) 67 + } 68 + } 69 + 70 + func AssertEqual[T comparable](t *testing.T, expected, actual T, msg string) { 71 + t.Helper() 72 + if expected != actual { 73 + t.Fatalf("%s: expected %v, got %v", msg, expected, actual) 74 + } 75 + } 76 + 77 + func AssertNotEqual[T comparable](t *testing.T, not, actual T, msg string) { 78 + t.Helper() 79 + if not == actual { 80 + t.Fatalf("%s: expected value to not equal %v", msg, not) 81 + } 82 + } 83 + 84 + func AssertNil(t *testing.T, value any, msg string) { 85 + t.Helper() 86 + if value != nil { 87 + t.Fatalf("%s: expected nil, got %v", msg, value) 88 + } 89 + } 90 + 91 + func AssertNotNil(t *testing.T, value any, msg string) { 92 + t.Helper() 93 + if value == nil { 94 + t.Fatalf("%s: expected non-nil value", msg) 95 + } 96 + } 97 + 98 + func AssertGreaterThan[T interface{ int | int64 | float64 }](t *testing.T, actual, threshold T, msg string) { 99 + t.Helper() 100 + if actual <= threshold { 101 + t.Fatalf("%s: expected %v > %v", msg, actual, threshold) 102 + } 103 + } 104 + 105 + func AssertLessThan[T interface{ int | int64 | float64 }](t *testing.T, actual, threshold T, msg string) { 106 + t.Helper() 107 + if actual >= threshold { 108 + t.Fatalf("%s: expected %v < %v", msg, actual, threshold) 109 + } 110 + } 111 + 112 + // Helper function to check if string contains substring (case-insensitive) 113 + func ContainsString(haystack, needle string) bool { 114 + if needle == "" { 115 + return true 116 + } 117 + return len(haystack) >= len(needle) && 118 + haystack[len(haystack)-len(needle):] == needle || 119 + haystack[:len(needle)] == needle || 120 + (len(haystack) > len(needle) && 121 + func() bool { 122 + for i := 1; i <= len(haystack)-len(needle); i++ { 123 + if haystack[i:i+len(needle)] == needle { 124 + return true 125 + } 126 + } 127 + return false 128 + }()) 129 + } 130 + 131 + // HTTPMockServer provides utilities for mocking HTTP services in tests 132 + type HTTPMockServer struct { 133 + server *httptest.Server 134 + requests []*http.Request 135 + } 136 + 137 + // NewMockServer creates a new mock HTTP server 138 + func NewMockServer() *HTTPMockServer { 139 + mock := &HTTPMockServer{ 140 + requests: make([]*http.Request, 0), 141 + } 142 + return mock 143 + } 144 + 145 + // WithHandler sets up the mock server with a custom handler 146 + func (m *HTTPMockServer) WithHandler(handler http.HandlerFunc) *HTTPMockServer { 147 + m.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 148 + m.requests = append(m.requests, r) 149 + handler(w, r) 150 + })) 151 + return m 152 + } 153 + 154 + // URL returns the mock server URL 155 + func (m *HTTPMockServer) URL() string { 156 + if m.server == nil { 157 + panic("mock server not initialized - call WithHandler first") 158 + } 159 + return m.server.URL 160 + } 161 + 162 + // Close closes the mock server 163 + func (m *HTTPMockServer) Close() { 164 + if m.server != nil { 165 + m.server.Close() 166 + } 167 + } 168 + 169 + // GetRequests returns all recorded HTTP requests 170 + func (m *HTTPMockServer) GetRequests() []*http.Request { 171 + return m.requests 172 + } 173 + 174 + // GetLastRequest returns the last recorded HTTP request 175 + func (m *HTTPMockServer) GetLastRequest() *http.Request { 176 + if len(m.requests) == 0 { 177 + return nil 178 + } 179 + return m.requests[len(m.requests)-1] 180 + } 181 + 182 + func (m HTTPMockServer) Requests() []*http.Request { 183 + return m.requests 184 + } 185 + 186 + // HTTPErrorMockServer creates a mock server that returns HTTP errors 187 + func HTTPErrorMockServer(statusCode int, message string) *HTTPMockServer { 188 + return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 189 + http.Error(w, message, statusCode) 190 + }) 191 + } 192 + 193 + // JSONMockServer creates a mock server that returns JSON responses 194 + func JSONMockServer(response any) *HTTPMockServer { 195 + return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 196 + w.Header().Set("Content-Type", "application/json") 197 + if err := json.NewEncoder(w).Encode(response); err != nil { 198 + http.Error(w, "Failed to encode response", http.StatusInternalServerError) 199 + } 200 + }) 201 + } 202 + 203 + // TimeoutMockServer creates a mock server that simulates timeouts 204 + func TimeoutMockServer(delay time.Duration) *HTTPMockServer { 205 + return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 206 + time.Sleep(delay) 207 + w.WriteHeader(http.StatusOK) 208 + }) 209 + } 210 + 211 + // InvalidJSONMockServer creates a mock server that returns malformed JSON 212 + func InvalidJSONMockServer() *HTTPMockServer { 213 + return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 214 + w.Header().Set("Content-Type", "application/json") 215 + w.Write([]byte(`{"invalid": json`)) 216 + }) 217 + } 218 + 219 + // EmptyResponseMockServer creates a mock server that returns empty responses 220 + func EmptyResponseMockServer() *HTTPMockServer { 221 + return NewMockServer().WithHandler(func(w http.ResponseWriter, r *http.Request) { 222 + w.WriteHeader(http.StatusOK) 223 + }) 224 + } 225 + 226 + // AssertRequestMade verifies that a request was made to the mock server 227 + func AssertRequestMade(t *testing.T, server *HTTPMockServer, expected string) { 228 + t.Helper() 229 + if len(server.requests) == 0 { 230 + t.Error("Expected HTTP request to be made but none were recorded") 231 + return 232 + } 233 + 234 + lastReq := server.GetLastRequest() 235 + if lastReq.URL.Path != expected { 236 + t.Errorf("Expected request to path %s, got %s", expected, lastReq.URL.Path) 237 + } 238 + }
+9 -12
internal/store/config.go
··· 1 1 package store 2 2 3 3 import ( 4 - "fmt" 5 4 "os" 6 5 "path/filepath" 7 6 8 7 "github.com/BurntSushi/toml" 8 + "github.com/stormlightlabs/noteleaf/internal/shared" 9 9 ) 10 10 11 11 // Config holds application configuration ··· 49 49 } else { 50 50 configDir, err := GetConfigDir() 51 51 if err != nil { 52 - return nil, fmt.Errorf("failed to get config directory: %w", err) 52 + return nil, shared.ConfigError("failed to get config directory", err) 53 53 } 54 54 configPath = filepath.Join(configDir, ".noteleaf.conf.toml") 55 55 } ··· 57 57 if _, err := os.Stat(configPath); os.IsNotExist(err) { 58 58 config := DefaultConfig() 59 59 if err := SaveConfig(config); err != nil { 60 - return nil, fmt.Errorf("failed to create default config: %w", err) 60 + return nil, shared.ConfigError("failed to create default config", err) 61 61 } 62 62 return config, nil 63 63 } 64 64 65 65 data, err := os.ReadFile(configPath) 66 66 if err != nil { 67 - return nil, fmt.Errorf("failed to read config file: %w", err) 67 + return nil, shared.ConfigError("failed to read config file", err) 68 68 } 69 69 70 70 config := DefaultConfig() 71 71 if err := toml.Unmarshal(data, config); err != nil { 72 - return nil, fmt.Errorf("failed to parse config file: %w", err) 72 + return nil, shared.ConfigError("failed to parse config file", err) 73 73 } 74 74 75 75 return config, nil ··· 79 79 func SaveConfig(config *Config) error { 80 80 var configPath string 81 81 82 - // Check for NOTELEAF_CONFIG environment variable 83 82 if envConfigPath := os.Getenv("NOTELEAF_CONFIG"); envConfigPath != "" { 84 83 configPath = envConfigPath 85 - // Ensure the directory exists for custom config path 86 84 configDir := filepath.Dir(configPath) 87 85 if err := os.MkdirAll(configDir, 0755); err != nil { 88 - return fmt.Errorf("failed to create config directory: %w", err) 86 + return shared.ConfigError("failed to create config directory", err) 89 87 } 90 88 } else { 91 89 configDir, err := GetConfigDir() 92 90 if err != nil { 93 - return fmt.Errorf("failed to get config directory: %w", err) 91 + return shared.ConfigError("failed to get config directory", err) 94 92 } 95 93 configPath = filepath.Join(configDir, ".noteleaf.conf.toml") 96 94 } 97 95 98 96 data, err := toml.Marshal(config) 99 97 if err != nil { 100 - return fmt.Errorf("failed to marshal config: %w", err) 98 + return shared.ConfigError("failed to marshal config", err) 101 99 } 102 100 103 101 if err := os.WriteFile(configPath, data, 0644); err != nil { 104 - return fmt.Errorf("failed to write config file: %w", err) 102 + return shared.ConfigError("failed to write config file", err) 105 103 } 106 104 107 105 return nil ··· 109 107 110 108 // GetConfigPath returns the path to the configuration file 111 109 func GetConfigPath() (string, error) { 112 - // Check for NOTELEAF_CONFIG environment variable 113 110 if envConfigPath := os.Getenv("NOTELEAF_CONFIG"); envConfigPath != "" { 114 111 return envConfigPath, nil 115 112 }
+36 -93
internal/store/config_test.go
··· 8 8 "testing" 9 9 10 10 "github.com/BurntSushi/toml" 11 + "github.com/stormlightlabs/noteleaf/internal/shared" 11 12 ) 12 13 13 14 func TestDefaultConfig(t *testing.T) { ··· 47 48 } 48 49 49 50 func TestConfigOperations(t *testing.T) { 50 - tempDir, err := os.MkdirTemp("", "noteleaf-config-test-*") 51 - if err != nil { 52 - t.Fatalf("Failed to create temp directory: %v", err) 53 - } 54 - defer os.RemoveAll(tempDir) 51 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-test-*", t) 52 + defer cleanup() 55 53 56 54 originalGetConfigDir := GetConfigDir 57 55 GetConfigDir = func() (string, error) { ··· 124 122 } 125 123 126 124 func TestConfigPersistence(t *testing.T) { 127 - tempDir, err := os.MkdirTemp("", "noteleaf-config-persist-test-*") 128 - if err != nil { 129 - t.Fatalf("Failed to create temp directory: %v", err) 130 - } 131 - defer os.RemoveAll(tempDir) 125 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-persist-test-*", t) 126 + defer cleanup() 132 127 133 128 originalGetConfigDir := GetConfigDir 134 129 GetConfigDir = func() (string, error) { ··· 199 194 200 195 func TestConfigErrorHandling(t *testing.T) { 201 196 t.Run("LoadConfig handles invalid TOML", func(t *testing.T) { 202 - tempDir, err := os.MkdirTemp("", "noteleaf-config-error-test-*") 203 - if err != nil { 204 - t.Fatalf("Failed to create temp directory: %v", err) 205 - } 206 - defer os.RemoveAll(tempDir) 197 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-error-test-*", t) 198 + defer cleanup() 207 199 208 200 originalGetConfigDir := GetConfigDir 209 201 GetConfigDir = func() (string, error) { ··· 213 205 214 206 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml") 215 207 invalidTOML := `[invalid toml content` 216 - err = os.WriteFile(configPath, []byte(invalidTOML), 0644) 217 - if err != nil { 208 + if err := os.WriteFile(configPath, []byte(invalidTOML), 0644); err != nil { 218 209 t.Fatalf("Failed to write invalid TOML: %v", err) 219 210 } 220 211 221 - _, err = LoadConfig() 222 - if err == nil { 212 + if _, err := LoadConfig(); err == nil { 223 213 t.Error("LoadConfig should fail with invalid TOML") 224 214 } 225 215 }) ··· 229 219 t.Skip("Permission test not reliable on Windows") 230 220 } 231 221 232 - tempDir, err := os.MkdirTemp("", "noteleaf-config-perm-test-*") 233 - if err != nil { 234 - t.Fatalf("Failed to create temp directory: %v", err) 235 - } 236 - defer os.RemoveAll(tempDir) 222 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-perm-test-*", t) 223 + defer cleanup() 237 224 238 225 originalGetConfigDir := GetConfigDir 239 226 GetConfigDir = func() (string, error) { ··· 243 230 244 231 configPath := filepath.Join(tempDir, ".noteleaf.conf.toml") 245 232 validTOML := `color_scheme = "dark"` 246 - err = os.WriteFile(configPath, []byte(validTOML), 0644) 247 - if err != nil { 233 + if err := os.WriteFile(configPath, []byte(validTOML), 0644); err != nil { 248 234 t.Fatalf("Failed to write config file: %v", err) 249 235 } 250 236 251 - err = os.Chmod(configPath, 0000) 252 - if err != nil { 237 + if err := os.Chmod(configPath, 0000); err != nil { 253 238 t.Fatalf("Failed to change file permissions: %v", err) 254 239 } 255 240 defer os.Chmod(configPath, 0644) 256 241 257 - _, err = LoadConfig() 258 - if err == nil { 242 + if _, err := LoadConfig(); err == nil { 259 243 t.Error("LoadConfig should fail when config file is not readable") 260 244 } 261 245 }) ··· 274 258 }) 275 259 276 260 t.Run("LoadConfig handles SaveConfig failure when creating default", func(t *testing.T) { 277 - tempDir, err := os.MkdirTemp("", "noteleaf-config-save-fail-test-*") 278 - if err != nil { 279 - t.Fatalf("Failed to create temp directory: %v", err) 280 - } 281 - defer os.RemoveAll(tempDir) 261 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-save-fail-test-*", t) 262 + defer cleanup() 282 263 283 264 _ = filepath.Join(tempDir, ".noteleaf.conf.toml") 284 265 ··· 293 274 } 294 275 defer func() { GetConfigDir = originalGetConfigDir }() 295 276 296 - _, err = LoadConfig() 297 - if err == nil { 277 + if _, err := LoadConfig(); err == nil { 298 278 t.Error("LoadConfig should fail when SaveConfig fails during default config creation") 299 279 } 300 280 }) ··· 307 287 defer func() { GetConfigDir = originalGetConfigDir }() 308 288 309 289 config := DefaultConfig() 310 - err := SaveConfig(config) 311 - if err == nil { 290 + if err := SaveConfig(config); err == nil { 312 291 t.Error("SaveConfig should fail when config directory cannot be accessed") 313 292 } 314 293 }) ··· 318 297 t.Skip("Permission test not reliable on Windows") 319 298 } 320 299 321 - tempDir, err := os.MkdirTemp("", "noteleaf-config-write-perm-test-*") 322 - if err != nil { 323 - t.Fatalf("Failed to create temp directory: %v", err) 324 - } 325 - defer os.RemoveAll(tempDir) 300 + tempDir, cleanup := shared.CreateTempDir("noteleaf-config-write-perm-test-*", t) 301 + defer cleanup() 326 302 327 303 originalGetConfigDir := GetConfigDir 328 304 GetConfigDir = func() (string, error) { ··· 330 306 } 331 307 defer func() { GetConfigDir = originalGetConfigDir }() 332 308 333 - err = os.Chmod(tempDir, 0555) 334 - if err != nil { 309 + if err := os.Chmod(tempDir, 0555); err != nil { 335 310 t.Fatalf("Failed to change directory permissions: %v", err) 336 311 } 337 312 defer os.Chmod(tempDir, 0755) 338 313 339 314 config := DefaultConfig() 340 - err = SaveConfig(config) 341 - if err == nil { 315 + if err := SaveConfig(config); err == nil { 342 316 t.Error("SaveConfig should fail when directory is not writable") 343 317 } 344 318 }) ··· 378 352 }) 379 353 380 354 t.Run("creates directory if it doesn't exist", func(t *testing.T) { 381 - tempDir, err := os.MkdirTemp("", "noteleaf-test-*") 382 - if err != nil { 383 - t.Fatalf("Failed to create temp directory: %v", err) 384 - } 385 - defer os.RemoveAll(tempDir) 355 + tempDir, cleanup := shared.CreateTempDir("noteleaf-test-*", t) 356 + defer cleanup() 386 357 387 358 var originalEnv string 388 359 var envVar string ··· 537 508 538 509 func TestEnvironmentVariableOverrides(t *testing.T) { 539 510 t.Run("NOTELEAF_CONFIG overrides default config path for LoadConfig", func(t *testing.T) { 540 - tempDir, err := os.MkdirTemp("", "noteleaf-env-config-test-*") 541 - if err != nil { 542 - t.Fatalf("Failed to create temp directory: %v", err) 543 - } 544 - defer os.RemoveAll(tempDir) 511 + tempDir, cleanup := shared.CreateTempDir("noteleaf-env-config-test-*", t) 512 + defer cleanup() 545 513 546 514 customConfigPath := filepath.Join(tempDir, "custom-config.toml") 547 515 originalEnv := os.Getenv("NOTELEAF_CONFIG") 548 516 os.Setenv("NOTELEAF_CONFIG", customConfigPath) 549 517 defer os.Setenv("NOTELEAF_CONFIG", originalEnv) 550 518 551 - // Create a custom config 552 519 customConfig := DefaultConfig() 553 520 customConfig.ColorScheme = "custom-env-test" 554 521 if err := SaveConfig(customConfig); err != nil { 555 522 t.Fatalf("Failed to save custom config: %v", err) 556 523 } 557 524 558 - // Load config should use the custom path 559 525 loadedConfig, err := LoadConfig() 560 526 if err != nil { 561 527 t.Fatalf("LoadConfig failed: %v", err) ··· 567 533 }) 568 534 569 535 t.Run("NOTELEAF_CONFIG overrides default config path for SaveConfig", func(t *testing.T) { 570 - tempDir, err := os.MkdirTemp("", "noteleaf-env-save-test-*") 571 - if err != nil { 572 - t.Fatalf("Failed to create temp directory: %v", err) 573 - } 574 - defer os.RemoveAll(tempDir) 536 + tempDir, cleanup := shared.CreateTempDir("noteleaf-env-save-test-*", t) 537 + defer cleanup() 575 538 576 539 customConfigPath := filepath.Join(tempDir, "subdir", "config.toml") 577 540 originalEnv := os.Getenv("NOTELEAF_CONFIG") ··· 584 547 t.Fatalf("SaveConfig failed: %v", err) 585 548 } 586 549 587 - // Verify the file was created at the custom path 588 550 if _, err := os.Stat(customConfigPath); os.IsNotExist(err) { 589 551 t.Error("Config file should be created at custom NOTELEAF_CONFIG path") 590 552 } 591 553 592 - // Verify the content 593 554 data, err := os.ReadFile(customConfigPath) 594 555 if err != nil { 595 556 t.Fatalf("Failed to read config file: %v", err) ··· 606 567 }) 607 568 608 569 t.Run("NOTELEAF_CONFIG overrides default config path for GetConfigPath", func(t *testing.T) { 609 - tempDir, err := os.MkdirTemp("", "noteleaf-env-path-test-*") 610 - if err != nil { 611 - t.Fatalf("Failed to create temp directory: %v", err) 612 - } 613 - defer os.RemoveAll(tempDir) 570 + tempDir, cleanup := shared.CreateTempDir("noteleaf-env-path-test-*", t) 571 + defer cleanup() 614 572 615 573 customConfigPath := filepath.Join(tempDir, "my-config.toml") 616 574 originalEnv := os.Getenv("NOTELEAF_CONFIG") ··· 628 586 }) 629 587 630 588 t.Run("NOTELEAF_CONFIG creates parent directories if needed", func(t *testing.T) { 631 - tempDir, err := os.MkdirTemp("", "noteleaf-env-mkdir-test-*") 632 - if err != nil { 633 - t.Fatalf("Failed to create temp directory: %v", err) 634 - } 635 - defer os.RemoveAll(tempDir) 589 + tempDir, cleanup := shared.CreateTempDir("noteleaf-env-mkdir-test-*", t) 590 + defer cleanup() 636 591 637 592 customConfigPath := filepath.Join(tempDir, "nested", "deep", "config.toml") 638 593 originalEnv := os.Getenv("NOTELEAF_CONFIG") ··· 652 607 653 608 func TestGetDataDir(t *testing.T) { 654 609 t.Run("NOTELEAF_DATA_DIR overrides default data directory", func(t *testing.T) { 655 - tempDir, err := os.MkdirTemp("", "noteleaf-data-dir-test-*") 656 - if err != nil { 657 - t.Fatalf("Failed to create temp directory: %v", err) 658 - } 659 - defer os.RemoveAll(tempDir) 610 + tempDir, cleanup := shared.CreateTempDir("noteleaf-data-dir-test-*", t) 611 + defer cleanup() 660 612 661 613 customDataDir := filepath.Join(tempDir, "my-data") 662 614 originalEnv := os.Getenv("NOTELEAF_DATA_DIR") ··· 672 624 t.Errorf("Expected data dir '%s', got '%s'", customDataDir, dataDir) 673 625 } 674 626 675 - // Verify directory was created 676 627 if _, err := os.Stat(customDataDir); os.IsNotExist(err) { 677 628 t.Error("Data directory should be created") 678 629 } 679 630 }) 680 631 681 632 t.Run("GetDataDir returns correct directory based on OS", func(t *testing.T) { 682 - // Temporarily unset NOTELEAF_DATA_DIR 683 633 originalEnv := os.Getenv("NOTELEAF_DATA_DIR") 684 634 os.Unsetenv("NOTELEAF_DATA_DIR") 685 635 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv) ··· 699 649 }) 700 650 701 651 t.Run("GetDataDir handles NOTELEAF_DATA_DIR with nested path", func(t *testing.T) { 702 - tempDir, err := os.MkdirTemp("", "noteleaf-nested-data-test-*") 703 - if err != nil { 704 - t.Fatalf("Failed to create temp directory: %v", err) 705 - } 706 - defer os.RemoveAll(tempDir) 652 + tempDir, cleanup := shared.CreateTempDir("noteleaf-nested-data-test-*", t) 653 + defer cleanup() 707 654 708 655 customDataDir := filepath.Join(tempDir, "level1", "level2", "data") 709 656 originalEnv := os.Getenv("NOTELEAF_DATA_DIR") ··· 719 666 t.Errorf("Expected data dir '%s', got '%s'", customDataDir, dataDir) 720 667 } 721 668 722 - // Verify nested directories were created 723 669 if _, err := os.Stat(customDataDir); os.IsNotExist(err) { 724 670 t.Error("Nested data directories should be created") 725 671 } 726 672 }) 727 673 728 674 t.Run("GetDataDir uses platform-specific defaults", func(t *testing.T) { 729 - // Temporarily unset NOTELEAF_DATA_DIR 730 675 originalEnv := os.Getenv("NOTELEAF_DATA_DIR") 731 676 os.Unsetenv("NOTELEAF_DATA_DIR") 732 677 defer os.Setenv("NOTELEAF_DATA_DIR", originalEnv) 733 678 734 - // Create temporary environment for testing 735 679 tempHome, err := os.MkdirTemp("", "noteleaf-home-test-*") 736 680 if err != nil { 737 681 t.Fatalf("Failed to create temp home: %v", err) ··· 760 704 t.Fatalf("GetDataDir failed: %v", err) 761 705 } 762 706 763 - // Verify the path contains our temp directory 764 707 if !strings.Contains(dataDir, tempHome) { 765 708 t.Errorf("Data directory should be under temp home, got: %s", dataDir) 766 709 }
+4 -5
internal/store/database_test.go
··· 8 8 "runtime" 9 9 "strings" 10 10 "testing" 11 + 12 + "github.com/stormlightlabs/noteleaf/internal/shared" 11 13 ) 12 14 13 15 func withTempDirs(t *testing.T) string { 14 16 t.Helper() 15 - tempDir, err := os.MkdirTemp("", "noteleaf-db-test-*") 16 - if err != nil { 17 - t.Fatalf("Failed to create temp directory: %v", err) 18 - } 19 - t.Cleanup(func() { os.RemoveAll(tempDir) }) 17 + tempDir, cleanup := shared.CreateTempDir("noteleaf-db-test-*", t) 18 + t.Cleanup(func() { cleanup() }) 20 19 21 20 origConfig, origData := GetConfigDir, GetDataDir 22 21 GetConfigDir = func() (string, error) { return tempDir, nil }
+2 -1
internal/ui/data_list_tui_test.go
··· 5 5 "time" 6 6 7 7 tea "github.com/charmbracelet/bubbletea" 8 + "github.com/stormlightlabs/noteleaf/internal/shared" 8 9 ) 9 10 10 11 type mockListModel struct { ··· 67 68 68 69 if err := suite.WaitFor(func(m tea.Model) bool { 69 70 view := m.View() 70 - return !containsString(view, "help") 71 + return !shared.ContainsString(view, "help") 71 72 }, 1*time.Second); err != nil { 72 73 t.Errorf("Help should have been hidden: %v", err) 73 74 }
-1
internal/ui/project_list_adapter.go
··· 14 14 GetProjects(ctx context.Context) ([]repo.ProjectSummary, error) 15 15 } 16 16 17 - 18 17 func pluralizeCount(count int) string { 19 18 if count == 1 { 20 19 return ""
-1
internal/ui/tag_list_adapter.go
··· 14 14 GetTags(ctx context.Context) ([]repo.TagSummary, error) 15 15 } 16 16 17 - 18 17 // TagSummaryRecord adapts repo.TagSummary to work with DataTable 19 18 type TagSummaryRecord struct { 20 19 summary repo.TagSummary
+2 -1
internal/ui/task_edit_interactive_test.go
··· 6 6 7 7 tea "github.com/charmbracelet/bubbletea" 8 8 "github.com/stormlightlabs/noteleaf/internal/models" 9 + "github.com/stormlightlabs/noteleaf/internal/shared" 9 10 ) 10 11 11 12 func TestInteractiveTUIBehavior(t *testing.T) { ··· 195 196 t.Error("View should not be empty") 196 197 } 197 198 198 - if !containsString(view, "Test Output") { 199 + if !shared.ContainsString(view, "Test Output") { 199 200 t.Error("View should contain task description") 200 201 } 201 202 })
+4 -16
internal/ui/test_utilities.go
··· 10 10 11 11 tea "github.com/charmbracelet/bubbletea" 12 12 "github.com/stormlightlabs/noteleaf/internal/models" 13 + "github.com/stormlightlabs/noteleaf/internal/shared" 13 14 ) 14 15 15 16 type AssertionHelpers struct{} ··· 212 213 func (suite *TUITestSuite) WaitForView(contains string, timeout time.Duration) error { 213 214 return suite.WaitFor(func(model tea.Model) bool { 214 215 view := model.View() 215 - return len(view) > 0 && containsString(view, contains) 216 + return len(view) > 0 && shared.ContainsString(view, contains) 216 217 }, timeout) 217 218 } 218 219 ··· 314 315 func (ah *AssertionHelpers) AssertViewContains(t *testing.T, suite *TUITestSuite, expected string, msg string) { 315 316 t.Helper() 316 317 view := suite.GetCurrentView() 317 - if !containsString(view, expected) { 318 + if !shared.ContainsString(view, expected) { 318 319 t.Errorf("View assertion failed: %s\nView content: %s\nExpected to contain: %s", msg, view, expected) 319 320 } 320 321 } ··· 322 323 func (ah *AssertionHelpers) AssertViewNotContains(t *testing.T, suite *TUITestSuite, unexpected string, msg string) { 323 324 t.Helper() 324 325 view := suite.GetCurrentView() 325 - if containsString(view, unexpected) { 326 + if shared.ContainsString(view, unexpected) { 326 327 t.Errorf("View assertion failed: %s\nView content: %s\nShould not contain: %s", msg, view, unexpected) 327 328 } 328 329 } ··· 335 336 } 336 337 337 338 var Expect = AssertionHelpers{} 338 - 339 - func containsString(haystack, needle string) bool { 340 - if needle == "" { 341 - return true 342 - } 343 - 344 - for i := 0; i <= len(haystack)-len(needle); i++ { 345 - if haystack[i:i+len(needle)] == needle { 346 - return true 347 - } 348 - } 349 - return false 350 - } 351 339 352 340 // Test generators for switch case coverage 353 341 type SwitchCaseTest struct {