cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
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}