···11# Testing Documentation
2233-This document outlines the testing patterns and practices used in the noteleaf application.
33+This document outlines the testing patterns and practices used in the `noteleaf` application.
4455-## Testing Principles
55+## Overview
6677-The codebase follows Go's standard testing practices without external libraries. Tests use only the standard library `testing` package and avoid mock frameworks or assertion libraries. This keeps dependencies minimal and tests readable using standard Go patterns.
77+The codebase follows Go's standard testing practices without external libraries. Tests use only the standard library package and avoid mock frameworks or assertion libraries. This is to keep dependencies minimal and tests readable using standard Go patterns.
8899-## Test File Organization
99+### Organization
10101111-Test files follow the standard Go convention of `*_test.go` naming. Each package contains its own test files alongside the source code. Test files are organized by functionality and mirror the structure of the source code they test.
1111+Each package contains its own test files alongside the source code. Test files are organized by functionality and mirror the structure of the source code they test.
12121313-## Testing Patterns
1313+## Patterns
14141515### Handler Creation Pattern
1616···30303131Tests use `t.Fatal` for setup errors that prevent test execution and `t.Error` for test assertion failures. Fatal errors stop test execution while errors allow tests to continue checking other conditions.
32323333+### Context Cancellation Testing Pattern
3434+3535+Error case testing frequently uses context cancellation to simulate database and network failures. The pattern creates a context, immediately cancels it, then calls the function under test to verify error handling. This provides a reliable way to test error paths without requiring complex mock setups or external failure injection.
3636+3337### Command Structure Testing
34383539Command group tests verify cobra command structure including use strings, aliases, short descriptions, and subcommand presence. Tests check that commands are properly configured without executing their logic.
···38423943Tests verify interface compliance using compile-time checks with blank identifier assignments. This ensures structs implement expected interfaces without runtime overhead.
40444141-```go
4242-var _ CommandGroup = NewTaskCommands(handler)
4343-```
4444-4545## Test Organization Patterns
46464747-### Single Root Test Pattern
4747+### Single Root Test
48484949-The preferred test organization pattern uses a single root test function with nested subtests using `t.Run`. This provides clear hierarchical organization and allows running specific test sections while maintaining shared setup and context.
4949+The preferred test organization pattern uses a single root test function with nested subtests using `t.Run`. This provides clear hierarchical organization and allows running specific test sections while maintaining shared setup and context. This pattern offers several advantages: clear test hierarchy with logical grouping, ability to run specific test sections, consistent test structure across the codebase, and shared setup that can be inherited by subtests.
50505151-```go
5252-func TestCommandGroup(t *testing.T) {
5353- t.Run("Interface Implementations", func(t *testing.T) {
5454- // Test interface compliance
5555- })
5151+### Integration vs Unit Testing
56525757- t.Run("Create", func(t *testing.T) {
5858- t.Run("TaskCommand", func(t *testing.T) {
5959- // Test task command creation
6060- })
6161- t.Run("MovieCommand", func(t *testing.T) {
6262- // Test movie command creation
6363- })
6464- })
6565-}
6666-```
5353+The codebase emphasizes integration testing over heavy mocking by using real handlers and services to verify actual behavior rather than mocked interactions. The goal is to catch integration issues while maintaining test reliability.
67546868-This pattern offers several advantages: clear test hierarchy with logical grouping, ability to run specific test sections with `go test -run TestCommandGroup/Create/TaskCommand`, consistent test structure across the codebase, and shared setup that can be inherited by subtests.
5555+### Static Output
69567070-### Integration vs Unit Testing
5757+UI components support static output modes for testing. Tests capture output using bytes.Buffer and verify content using string contains checks rather than exact string matching for better test maintainability.
71587272-The codebase emphasizes integration testing over heavy mocking. Tests use real handlers and services to verify actual behavior rather than mocked interactions. This approach catches integration issues while maintaining test reliability.
7373-7474-### Static Output Testing
5959+### Standard Output Redirection
75607676-UI components support static output modes for testing. Tests capture output using bytes.Buffer and verify content using string contains checks rather than exact string matching for better test maintainability.
6161+For testing functions that write to stdout, tests use a pipe redirection pattern with goroutines to capture output. The pattern saves the original stdout, redirects to a pipe, captures output in a separate goroutine, and restores stdout after the test. This ensures clean output capture without interfering with the testing framework.
77627878-## Test Utilities
6363+## Utilities
79648080-### Helper Functions
6565+### Helpers
81668267Test files include helper functions for creating test data and finding elements in collections. These utilities reduce code duplication and improve test readability.
83688484-### Mock Data Creation
6969+### Mock Data
85708686-Tests create realistic mock data using factory functions that return properly initialized structs with sensible defaults. This approach provides consistent test data across different test cases.
7171+Tests create realistic mock data using factory functions (powered by faker) that return properly initialized structs with sensible defaults.
87728873## Testing CLI Commands
8974···1018610287The single root test pattern allows for efficient resource management where setup costs can be amortized across multiple related test cases.
10388104104-## Best Practices Summary
8989+## Errors
9090+9191+Error coverage follows a systematic approach to identify and test failure scenarios:
10592106106-Use factory functions for test handler creation with proper cleanup patterns. Organize tests using single root test functions with nested subtests for clear hierarchy. Manage resources with cleanup functions returned by factory methods. Prefer integration testing over mocking for real-world behavior validation. Verify interface compliance at compile time within dedicated subtests. Focus command tests on structure verification rather than execution testing. Leverage the single root test pattern for logical grouping and selective test execution. Use realistic test data with factory functions for consistent test scenarios.
9393+1. **Context Cancellation** - Primary method for testing database and network timeout scenarios
9494+2. **Invalid Input** - Malformed data, empty inputs, boundary conditions
9595+3. **Resource Exhaustion** - Database connection failures, memory limits
9696+4. **Constraint Violations** - Duplicate keys, foreign key failures
9797+5. **State Validation** - Testing functions with invalid system states
+25
internal/handlers/articles.go
···267267 return nil
268268}
269269270270+// Read displays an article's content with formatted markdown rendering
271271+func (h *ArticleHandler) Read(ctx context.Context, id int64) error {
272272+ article, err := h.repos.Articles.Get(ctx, id)
273273+ if err != nil {
274274+ return fmt.Errorf("failed to get article: %w", err)
275275+ }
276276+277277+ if _, err := os.Stat(article.MarkdownPath); os.IsNotExist(err) {
278278+ return fmt.Errorf("markdown file not found: %s", article.MarkdownPath)
279279+ }
280280+281281+ content, err := os.ReadFile(article.MarkdownPath)
282282+ if err != nil {
283283+ return fmt.Errorf("failed to read markdown file: %w", err)
284284+ }
285285+286286+ if rendered, err := renderMarkdown(string(content)); err != nil {
287287+ return err
288288+ } else {
289289+ fmt.Print(rendered)
290290+ return nil
291291+ }
292292+293293+}
294294+270295// TODO: Try to get from config first (could be added later)
271296// For now, use default ~/Documents/Leaf/
272297func (h *ArticleHandler) getStorageDirectory() (string, error) {