-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintegration_test.go
More file actions
325 lines (273 loc) · 8.73 KB
/
integration_test.go
File metadata and controls
325 lines (273 loc) · 8.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
package main
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"path/filepath"
"strings"
"testing"
"time"
"staticsend/pkg/api"
"staticsend/pkg/database"
"staticsend/pkg/email"
"staticsend/pkg/middleware"
"staticsend/pkg/models"
"github.com/go-chi/chi/v5"
)
// IntegrationTestSuite holds the test server and dependencies
type IntegrationTestSuite struct {
Server *httptest.Server
DB *database.Database
EmailService *email.EmailService
Router *chi.Mux
TestUser *models.User
TestForm *models.Form
}
// SetupIntegrationTest creates a complete test environment
func SetupIntegrationTest(t *testing.T) *IntegrationTestSuite {
// Create test database
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "integration_test.db")
err := database.Init(dbPath)
if err != nil {
// If migration files don't exist, create a minimal schema
db, err := database.DB.Begin()
if err != nil {
t.Fatalf("Failed to create test database: %v", err)
}
// Create minimal schema for testing
schema := `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS forms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
form_key TEXT UNIQUE NOT NULL,
domain TEXT NOT NULL,
turnstile_public_key TEXT,
turnstile_secret_key TEXT,
notification_email TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS submissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
form_id INTEGER NOT NULL,
data TEXT NOT NULL,
ip_address TEXT,
user_agent TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (form_id) REFERENCES forms(id)
);
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
_, err = db.Exec(schema)
if err != nil {
t.Fatalf("Failed to create test schema: %v", err)
}
err = db.Commit()
if err != nil {
t.Fatalf("Failed to commit test schema: %v", err)
}
}
dbWrapper := &database.Database{Connection: database.DB}
// Create email service
emailConfig := email.EmailConfig{
Host: "localhost",
Port: 587,
Username: "test@example.com",
Password: "password",
From: "test@example.com",
UseTLS: false,
}
emailService := email.NewEmailService(emailConfig, 10, 1, 1)
// Create handlers
apiHandler := api.NewSubmissionHandler(database.DB, emailService)
// Create router
r := chi.NewRouter()
// Add middleware
r.Use(middleware.IPRateLimit(time.Minute, 100)) // High limit for testing
// API routes only (avoid template complications)
r.Route("/api/v1", func(r chi.Router) {
r.Post("/submit/{formKey}", apiHandler.SubmitForm)
})
// Simple health check endpoint
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// Create test server
server := httptest.NewServer(r)
// Create test user
testUser, err := models.CreateUser(database.DB, "test@example.com", "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj/VcSAg/9qm") // "password123"
if err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create test form
testForm, err := models.CreateForm(database.DB, testUser.ID, "Test Form", "example.com", "test-secret", "admin@example.com", "https://example.com/callback", "formkey-int-001")
if err != nil {
t.Fatalf("Failed to create test form: %v", err)
}
// Enable registration for tests
models.UpdateAppSetting(database.DB, "registration_enabled", "true")
return &IntegrationTestSuite{
Server: server,
DB: dbWrapper,
EmailService: emailService,
Router: r,
TestUser: testUser,
TestForm: testForm,
}
}
// Cleanup closes the test environment
func (suite *IntegrationTestSuite) Cleanup() {
suite.Server.Close()
suite.EmailService.Shutdown()
database.Close()
}
// TestFormSubmissionFlow tests the complete form submission workflow
func TestFormSubmissionFlow(t *testing.T) {
suite := SetupIntegrationTest(t)
defer suite.Cleanup()
t.Run("successful form submission", func(t *testing.T) {
// Prepare form data
formData := url.Values{}
formData.Set("name", "John Doe")
formData.Set("email", "john@example.com")
formData.Set("message", "Test message")
formData.Set("cf-turnstile-response", "fake-token-for-testing")
// Submit form
resp, err := http.Post(
suite.Server.URL+"/api/v1/submit/"+suite.TestForm.FormKey,
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
t.Fatalf("Failed to submit form: %v", err)
}
defer resp.Body.Close()
// Note: This will fail with Turnstile validation, but we can check the error
body, _ := io.ReadAll(resp.Body)
// Should get Turnstile validation error (expected in test environment)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Expected status 400 (Turnstile validation failure), got %d", resp.StatusCode)
}
if !strings.Contains(string(body), "Invalid Turnstile token") {
t.Errorf("Expected Turnstile validation error, got: %s", string(body))
}
})
t.Run("form not found", func(t *testing.T) {
formData := url.Values{}
formData.Set("name", "John Doe")
formData.Set("cf-turnstile-response", "fake-token")
resp, err := http.Post(
suite.Server.URL+"/api/v1/submit/nonexistent",
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
t.Fatalf("Failed to submit to nonexistent form: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", resp.StatusCode)
}
})
t.Run("missing turnstile token", func(t *testing.T) {
formData := url.Values{}
formData.Set("name", "John Doe")
// No Turnstile token
resp, err := http.Post(
suite.Server.URL+"/api/v1/submit/"+suite.TestForm.FormKey,
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
t.Fatalf("Failed to submit form: %v", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Expected status 400, got %d", resp.StatusCode)
}
if !strings.Contains(string(body), "Turnstile verification required") {
t.Errorf("Expected Turnstile required error, got: %s", string(body))
}
})
}
// TestHealthCheck tests the health check endpoint
func TestHealthCheck(t *testing.T) {
suite := SetupIntegrationTest(t)
defer suite.Cleanup()
t.Run("health endpoint responds", func(t *testing.T) {
resp, err := http.Get(suite.Server.URL + "/health")
if err != nil {
t.Fatalf("Failed to get health endpoint: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Errorf("Expected 'OK', got '%s'", string(body))
}
})
}
// TestRateLimiting tests the rate limiting functionality
func TestRateLimiting(t *testing.T) {
suite := SetupIntegrationTest(t)
defer suite.Cleanup()
t.Run("rate limit enforcement", func(t *testing.T) {
// This test would need a lower rate limit to be practical
// For now, just test that rate limiting middleware is active
formData := url.Values{}
formData.Set("name", "John Doe")
formData.Set("cf-turnstile-response", "fake-token")
// Make a request
resp, err := http.Post(
suite.Server.URL+"/api/v1/submit/"+suite.TestForm.FormKey,
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
t.Fatalf("Failed to submit form: %v", err)
}
defer resp.Body.Close()
// Should get some response (rate limiting is configured with high limit for testing)
if resp.StatusCode == 0 {
t.Error("Expected some HTTP response")
}
})
}
// TestAPIEndpoints tests API endpoint availability
func TestAPIEndpoints(t *testing.T) {
suite := SetupIntegrationTest(t)
defer suite.Cleanup()
t.Run("submit endpoint exists", func(t *testing.T) {
// Test with empty form data to verify endpoint exists
formData := url.Values{}
resp, err := http.Post(
suite.Server.URL+"/api/v1/submit/test-form",
"application/x-www-form-urlencoded",
strings.NewReader(formData.Encode()),
)
if err != nil {
t.Fatalf("Failed to post to submit endpoint: %v", err)
}
defer resp.Body.Close()
// Should return 400 due to missing Turnstile token, not 404
if resp.StatusCode == http.StatusNotFound {
t.Errorf("Submit endpoint not found")
}
})
}