cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
at main 267 lines 6.9 kB view raw
1package handlers 2 3import ( 4 "context" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/stormlightlabs/noteleaf/internal/repo" 10 "github.com/stormlightlabs/noteleaf/internal/store" 11) 12 13// HandlerTestSuite provides reusable test infrastructure for handlers 14type HandlerTestSuite struct { 15 t *testing.T 16 tempDir string 17 ctx context.Context 18 cleanup func() 19} 20 21// NewHandlerTestSuite creates a new test suite with isolated environment 22func NewHandlerTestSuite(t *testing.T) *HandlerTestSuite { 23 t.Helper() 24 25 tempDir, err := os.MkdirTemp("", "noteleaf-handler-suite-*") 26 if err != nil { 27 t.Fatalf("Failed to create temp dir: %v", err) 28 } 29 30 oldNoteleafConfig := os.Getenv("NOTELEAF_CONFIG") 31 oldNoteleafDataDir := os.Getenv("NOTELEAF_DATA_DIR") 32 os.Setenv("NOTELEAF_CONFIG", filepath.Join(tempDir, ".noteleaf.conf.toml")) 33 os.Setenv("NOTELEAF_DATA_DIR", tempDir) 34 35 cleanup := func() { 36 os.Setenv("NOTELEAF_CONFIG", oldNoteleafConfig) 37 os.Setenv("NOTELEAF_DATA_DIR", oldNoteleafDataDir) 38 os.RemoveAll(tempDir) 39 } 40 41 ctx := context.Background() 42 if err := Setup(ctx, []string{}); err != nil { 43 cleanup() 44 t.Fatalf("Failed to setup database: %v", err) 45 } 46 47 suite := &HandlerTestSuite{ 48 t: t, 49 tempDir: tempDir, 50 ctx: ctx, 51 cleanup: cleanup, 52 } 53 54 t.Cleanup(suite.Cleanup) 55 56 return suite 57} 58 59// Context returns the test context 60func (s *HandlerTestSuite) Context() context.Context { 61 return s.ctx 62} 63 64// TempDir returns the temporary directory for this test 65func (s *HandlerTestSuite) TempDir() string { 66 return s.tempDir 67} 68 69// Cleanup performs test cleanup 70func (s *HandlerTestSuite) Cleanup() { 71 if s.cleanup != nil { 72 s.cleanup() 73 } 74} 75 76// AssertNoError fails the test if err is not nil 77func (s *HandlerTestSuite) AssertNoError(err error, msg string) { 78 s.t.Helper() 79 if err != nil { 80 s.t.Fatalf("%s: unexpected error: %v", msg, err) 81 } 82} 83 84// AssertError fails the test if err is nil 85func (s *HandlerTestSuite) AssertError(err error, msg string) { 86 s.t.Helper() 87 if err == nil { 88 s.t.Fatalf("%s: expected error but got nil", msg) 89 } 90} 91 92// CreateTestDatabase creates an isolated test database 93func (s *HandlerTestSuite) CreateTestDatabase() (*store.Database, *repo.Repositories, error) { 94 db, err := store.NewDatabase() 95 if err != nil { 96 return nil, nil, err 97 } 98 99 repos := repo.NewRepositories(db.DB) 100 return db, repos, nil 101} 102 103// HandlerLifecycleTest tests basic handler lifecycle (create, close) 104// 105// This is a reusable test pattern for any handler implementing Closeable 106func HandlerLifecycleTest[H Closeable](t *testing.T, createHandler func() (H, error)) { 107 t.Helper() 108 109 t.Run("creates handler successfully", func(t *testing.T) { 110 handler, err := createHandler() 111 if err != nil { 112 t.Fatalf("Failed to create handler: %v", err) 113 } 114 115 if err := handler.Close(); err != nil { 116 t.Errorf("Close failed: %v", err) 117 } 118 }) 119 120 t.Run("handles close gracefully", func(t *testing.T) { 121 handler, err := createHandler() 122 if err != nil { 123 t.Fatalf("Failed to create handler: %v", err) 124 } 125 126 if err := handler.Close(); err != nil { 127 t.Errorf("First close should succeed: %v", err) 128 } 129 130 _ = handler.Close() 131 }) 132} 133 134// ViewableTest tests View functionality for handlers 135func ViewableTest[H Viewable](t *testing.T, handler H, validID, invalidID int64) { 136 t.Helper() 137 ctx := context.Background() 138 139 t.Run("views valid item", func(t *testing.T) { 140 err := handler.View(ctx, validID) 141 if err != nil { 142 t.Errorf("View with valid ID should succeed: %v", err) 143 } 144 }) 145 146 t.Run("fails with invalid ID", func(t *testing.T) { 147 err := handler.View(ctx, invalidID) 148 if err == nil { 149 t.Error("View with invalid ID should fail") 150 } 151 }) 152} 153 154// InputReaderTest tests SetInputReader functionality 155func InputReaderTest[H InputReader](t *testing.T, handler H) { 156 t.Helper() 157 158 t.Run("sets input reader", func(t *testing.T) { 159 sim := NewInputSimulator("test") 160 handler.SetInputReader(sim) 161 // If we get here without panic, the test passes 162 }) 163 164 t.Run("handles nil reader", func(t *testing.T) { 165 handler.SetInputReader(nil) 166 // If we get here without panic, the test passes 167 }) 168} 169 170// MediaHandlerTestSuite provides specialized test utilities for media handlers 171type MediaHandlerTestSuite struct { 172 *HandlerTestSuite 173} 174 175// NewMediaHandlerTestSuite creates a test suite for media handlers 176func NewMediaHandlerTestSuite(t *testing.T) *MediaHandlerTestSuite { 177 return &MediaHandlerTestSuite{ 178 HandlerTestSuite: NewHandlerTestSuite(t), 179 } 180} 181 182// TestSearchAndAdd is a reusable test pattern for SearchAndAdd operations 183func (s *MediaHandlerTestSuite) TestSearchAndAdd(handler MediaHandler, query string, shouldSucceed bool) { 184 s.t.Helper() 185 186 err := handler.SearchAndAdd(s.ctx, query, false) 187 if shouldSucceed && err != nil { 188 s.t.Errorf("SearchAndAdd should succeed: %v", err) 189 } 190 if !shouldSucceed && err == nil { 191 s.t.Error("SearchAndAdd should fail") 192 } 193} 194 195// TestList is a reusable test pattern for List operations 196func (s *MediaHandlerTestSuite) TestList(handler MediaHandler, status string) { 197 s.t.Helper() 198 199 err := handler.List(s.ctx, status) 200 if err != nil { 201 s.t.Errorf("List should succeed: %v", err) 202 } 203} 204 205// TestUpdateStatus is a reusable test pattern for UpdateStatus operations 206func (s *MediaHandlerTestSuite) TestUpdateStatus(handler MediaHandler, id, status string, shouldSucceed bool) { 207 s.t.Helper() 208 209 err := handler.UpdateStatus(s.ctx, id, status) 210 if shouldSucceed && err != nil { 211 s.t.Errorf("UpdateStatus should succeed: %v", err) 212 } 213 if !shouldSucceed && err == nil { 214 s.t.Error("UpdateStatus should fail") 215 } 216} 217 218// TestRemove is a reusable test pattern for Remove operations 219func (s *MediaHandlerTestSuite) TestRemove(handler MediaHandler, id string, shouldSucceed bool) { 220 s.t.Helper() 221 222 err := handler.Remove(s.ctx, id) 223 if shouldSucceed && err != nil { 224 s.t.Errorf("Remove should succeed: %v", err) 225 } 226 if !shouldSucceed && err == nil { 227 s.t.Error("Remove should fail") 228 } 229} 230 231// CreateHandler creates a handler with automatic cleanup 232func CreateHandler[H Closeable](t *testing.T, factory func() (H, error)) H { 233 t.Helper() 234 235 handler, err := factory() 236 if err != nil { 237 t.Fatalf("Failed to create handler: %v", err) 238 } 239 240 t.Cleanup(func() { 241 handler.Close() 242 }) 243 244 return handler 245} 246 247// AssertExists verifies that an item exists using a getter 248func AssertExists[T any](t *testing.T, getter func(context.Context, int64) (*T, error), id int64, itemType string) { 249 t.Helper() 250 251 ctx := context.Background() 252 _, err := getter(ctx, id) 253 if err != nil { 254 t.Errorf("%s %d should exist but got error: %v", itemType, id, err) 255 } 256} 257 258// AssertNotExists verifies that an item does not exist using a getter 259func AssertNotExists[T any](t *testing.T, getter func(context.Context, int64) (*T, error), id int64, itemType string) { 260 t.Helper() 261 262 ctx := context.Background() 263 _, err := getter(ctx, id) 264 if err == nil { 265 t.Errorf("%s %d should not exist but was found", itemType, id) 266 } 267}