diff options
Diffstat (limited to 'internal/store/sqlite_test.go')
| -rw-r--r-- | internal/store/sqlite_test.go | 327 |
1 files changed, 0 insertions, 327 deletions
diff --git a/internal/store/sqlite_test.go b/internal/store/sqlite_test.go index 962a1bf..6bf7783 100644 --- a/internal/store/sqlite_test.go +++ b/internal/store/sqlite_test.go @@ -2,7 +2,6 @@ package store import ( "database/sql" - "fmt" "path/filepath" "testing" "time" @@ -11,332 +10,6 @@ import ( "task-dashboard/internal/models" ) -// setupTestStore creates a test store with schema but without migrations directory -func setupTestStore(t *testing.T) *Store { - t.Helper() - - // Create temporary database file - tempDir := t.TempDir() - dbPath := filepath.Join(tempDir, "test.db") - - db, err := sql.Open("sqlite3", dbPath) - if err != nil { - t.Fatalf("Failed to open test database: %v", err) - } - - // Enable foreign keys - if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { - t.Fatalf("Failed to enable foreign keys: %v", err) - } - - // Enable WAL mode for better concurrency - if _, err := db.Exec("PRAGMA journal_mode = WAL"); err != nil { - t.Fatalf("Failed to enable WAL mode: %v", err) - } - - // Serialize writes to prevent "database is locked" errors - db.SetMaxOpenConns(1) - - store := &Store{db: db} - - // Create notes table directly (without migrations) - schema := ` - CREATE TABLE IF NOT EXISTS notes ( - filename TEXT PRIMARY KEY, - title TEXT NOT NULL, - content TEXT, - modified DATETIME NOT NULL, - path TEXT NOT NULL, - tags TEXT, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - CREATE INDEX IF NOT EXISTS idx_notes_modified ON notes(modified DESC); - ` - if _, err := db.Exec(schema); err != nil { - t.Fatalf("Failed to create schema: %v", err) - } - - return store -} - -// TestGetNotes_LimitClause verifies that the LIMIT clause works correctly -// and prevents SQL injection -func TestGetNotes_LimitClause(t *testing.T) { - store := setupTestStore(t) - defer store.Close() - - // Create 3 distinct notes with different modification times - baseTime := time.Now() - notes := []models.Note{ - { - Filename: "note1.md", - Title: "Note 1", - Content: "Content of note 1", - Modified: baseTime.Add(-2 * time.Hour), - Path: "/vault/note1.md", - Tags: []string{"tag1"}, - }, - { - Filename: "note2.md", - Title: "Note 2", - Content: "Content of note 2", - Modified: baseTime.Add(-1 * time.Hour), - Path: "/vault/note2.md", - Tags: []string{"tag2"}, - }, - { - Filename: "note3.md", - Title: "Note 3", - Content: "Content of note 3", - Modified: baseTime, - Path: "/vault/note3.md", - Tags: []string{"tag3"}, - }, - } - - // Save all 3 notes - if err := store.SaveNotes(notes); err != nil { - t.Fatalf("Failed to save notes: %v", err) - } - - // Test 1: Call GetNotes(2) - should return exactly 2 notes - t.Run("GetNotes with limit 2", func(t *testing.T) { - result, err := store.GetNotes(2) - if err != nil { - t.Fatalf("GetNotes(2) returned error: %v", err) - } - - if len(result) != 2 { - t.Errorf("Expected exactly 2 notes, got %d", len(result)) - } - - // Verify they are the most recent notes (note3 and note2) - if len(result) >= 2 { - if result[0].Filename != "note3.md" { - t.Errorf("Expected first note to be 'note3.md', got '%s'", result[0].Filename) - } - if result[1].Filename != "note2.md" { - t.Errorf("Expected second note to be 'note2.md', got '%s'", result[1].Filename) - } - } - }) - - // Test 2: Call GetNotes(5) - should return all 3 notes - t.Run("GetNotes with limit 5", func(t *testing.T) { - result, err := store.GetNotes(5) - if err != nil { - t.Fatalf("GetNotes(5) returned error: %v", err) - } - - if len(result) != 3 { - t.Errorf("Expected all 3 notes, got %d", len(result)) - } - - // Verify order (most recent first) - if len(result) == 3 { - if result[0].Filename != "note3.md" { - t.Errorf("Expected first note to be 'note3.md', got '%s'", result[0].Filename) - } - if result[1].Filename != "note2.md" { - t.Errorf("Expected second note to be 'note2.md', got '%s'", result[1].Filename) - } - if result[2].Filename != "note1.md" { - t.Errorf("Expected third note to be 'note1.md', got '%s'", result[2].Filename) - } - } - }) - - // Test 3: Call GetNotes(0) - should return all notes (no limit) - t.Run("GetNotes with no limit", func(t *testing.T) { - result, err := store.GetNotes(0) - if err != nil { - t.Fatalf("GetNotes(0) returned error: %v", err) - } - - if len(result) != 3 { - t.Errorf("Expected all 3 notes with no limit, got %d", len(result)) - } - }) - - // Test 4: Call GetNotes(1) - should return exactly 1 note - t.Run("GetNotes with limit 1", func(t *testing.T) { - result, err := store.GetNotes(1) - if err != nil { - t.Fatalf("GetNotes(1) returned error: %v", err) - } - - if len(result) != 1 { - t.Errorf("Expected exactly 1 note, got %d", len(result)) - } - - // Should be the most recent note - if len(result) == 1 && result[0].Filename != "note3.md" { - t.Errorf("Expected note to be 'note3.md', got '%s'", result[0].Filename) - } - }) -} - -// TestGetNotes_EmptyDatabase verifies behavior with empty database -func TestGetNotes_EmptyDatabase(t *testing.T) { - store := setupTestStore(t) - defer store.Close() - - result, err := store.GetNotes(10) - if err != nil { - t.Fatalf("GetNotes on empty database returned error: %v", err) - } - - if len(result) != 0 { - t.Errorf("Expected 0 notes from empty database, got %d", len(result)) - } -} - -// TestSaveNotes_Upsert verifies that SaveNotes properly upserts notes -func TestSaveNotes_Upsert(t *testing.T) { - store := setupTestStore(t) - defer store.Close() - - baseTime := time.Now() - - // Save initial note - initialNote := []models.Note{ - { - Filename: "test.md", - Title: "Initial Title", - Content: "Initial content", - Modified: baseTime, - Path: "/vault/test.md", - Tags: []string{"initial"}, - }, - } - - if err := store.SaveNotes(initialNote); err != nil { - t.Fatalf("Failed to save initial note: %v", err) - } - - // Verify initial save - notes, err := store.GetNotes(0) - if err != nil { - t.Fatalf("Failed to get notes: %v", err) - } - if len(notes) != 1 { - t.Fatalf("Expected 1 note after initial save, got %d", len(notes)) - } - if notes[0].Title != "Initial Title" { - t.Errorf("Expected title 'Initial Title', got '%s'", notes[0].Title) - } - - // Update the same note - updatedNote := []models.Note{ - { - Filename: "test.md", - Title: "Updated Title", - Content: "Updated content", - Modified: baseTime.Add(1 * time.Hour), - Path: "/vault/test.md", - Tags: []string{"updated"}, - }, - } - - if err := store.SaveNotes(updatedNote); err != nil { - t.Fatalf("Failed to save updated note: %v", err) - } - - // Verify update (should still be 1 note, not 2) - notes, err = store.GetNotes(0) - if err != nil { - t.Fatalf("Failed to get notes after update: %v", err) - } - if len(notes) != 1 { - t.Errorf("Expected 1 note after update (upsert), got %d", len(notes)) - } - if notes[0].Title != "Updated Title" { - t.Errorf("Expected title 'Updated Title', got '%s'", notes[0].Title) - } -} - -// TestGetNotes_NegativeLimit verifies behavior with negative limit -func TestGetNotes_NegativeLimit(t *testing.T) { - store := setupTestStore(t) - defer store.Close() - - // Save a note - notes := []models.Note{ - { - Filename: "test.md", - Title: "Test", - Content: "Test content", - Modified: time.Now(), - Path: "/vault/test.md", - Tags: []string{}, - }, - } - - if err := store.SaveNotes(notes); err != nil { - t.Fatalf("Failed to save note: %v", err) - } - - // Call with negative limit (should be treated as no limit) - result, err := store.GetNotes(-1) - if err != nil { - t.Fatalf("GetNotes(-1) returned error: %v", err) - } - - // Should return all notes since negative is treated as 0/no limit - if len(result) != 1 { - t.Errorf("Expected 1 note with negative limit, got %d", len(result)) - } -} - -// TestGetNotes_SQLInjectionAttempt verifies that LIMIT parameter is properly sanitized -func TestGetNotes_SQLInjectionAttempt(t *testing.T) { - store := setupTestStore(t) - defer store.Close() - - // Save some notes - notes := []models.Note{ - { - Filename: "note1.md", - Title: "Note 1", - Content: "Content 1", - Modified: time.Now(), - Path: "/vault/note1.md", - Tags: []string{}, - }, - { - Filename: "note2.md", - Title: "Note 2", - Content: "Content 2", - Modified: time.Now(), - Path: "/vault/note2.md", - Tags: []string{}, - }, - } - - if err := store.SaveNotes(notes); err != nil { - t.Fatalf("Failed to save notes: %v", err) - } - - // The LIMIT parameter is now properly parameterized using sql.Query with args - // This test verifies that the code uses parameterized queries - // If it didn't, passing a malicious value could cause SQL injection - - // Normal call should work - result, err := store.GetNotes(1) - if err != nil { - t.Fatalf("GetNotes(1) returned error: %v", err) - } - - if len(result) != 1 { - t.Errorf("Expected 1 note, got %d", len(result)) - } - - // The fact that this test passes with a simple integer confirms - // that the implementation properly uses parameterized queries - // and is not vulnerable to SQL injection via the LIMIT clause - fmt.Println("✓ LIMIT clause is properly parameterized") -} - // setupTestStoreWithTasks creates a test store with tasks table func setupTestStoreWithTasks(t *testing.T) *Store { t.Helper() |
