diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-18 07:52:29 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-18 07:52:29 -0800 |
| commit | f66114a6159e3830f5c6ea772f76c988ae65623a (patch) | |
| tree | 5bff187b7e89b6e36c4c4948deab4b377e030b0b /api/api_stress_test.go | |
| parent | 804e55984ba88a768c6518904ce45f30ea27da9f (diff) | |
| parent | 269e44da41f9feed32214bbab6fc16ec88fffd85 (diff) | |
| download | neko-f66114a6159e3830f5c6ea772f76c988ae65623a.tar.gz neko-f66114a6159e3830f5c6ea772f76c988ae65623a.tar.bz2 neko-f66114a6159e3830f5c6ea772f76c988ae65623a.zip | |
Merge pull request #18 from adammathes/claude/improve-test-coverage-iBkwc
Add comprehensive test coverage for security and import features
Diffstat (limited to 'api/api_stress_test.go')
| -rw-r--r-- | api/api_stress_test.go | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/api/api_stress_test.go b/api/api_stress_test.go index a846f75..4fcbf5e 100644 --- a/api/api_stress_test.go +++ b/api/api_stress_test.go @@ -194,6 +194,140 @@ func TestStress_LargeDataset(t *testing.T) { } } +func TestStress_ConcurrentMixedOperations(t *testing.T) { + if testing.Short() { + t.Skip("skipping stress test in short mode") + } + + setupTestDB(t) + + // Create multiple feeds with items across categories + categories := []string{"tech", "news", "science", "art"} + for i, cat := range categories { + f := &feed.Feed{ + Url: fmt.Sprintf("http://example.com/mixed/%d", i), + Title: fmt.Sprintf("Mixed Feed %d", i), + Category: cat, + } + f.Create() + for j := 0; j < 25; j++ { + it := &item.Item{ + Title: fmt.Sprintf("Mixed Item %d-%d", i, j), + Url: fmt.Sprintf("http://example.com/mixed/%d/%d", i, j), + Description: fmt.Sprintf("<p>Mixed content %d-%d</p>", i, j), + PublishDate: "2024-01-01 00:00:00", + FeedId: f.Id, + } + _ = it.Create() + } + } + + server := newTestServer() + + const goroutines = 40 + var wg sync.WaitGroup + errors := make(chan error, goroutines*2) + + start := time.Now() + + // Mix of reads, filtered reads, updates, and exports + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + + var req *http.Request + switch idx % 4 { + case 0: + // Stream with category filter + req = httptest.NewRequest("GET", "/stream?tag="+categories[idx%len(categories)], nil) + case 1: + // Stream with search + req = httptest.NewRequest("GET", "/stream?q=Mixed", nil) + case 2: + // Feed list + req = httptest.NewRequest("GET", "/feed", nil) + case 3: + // Export + req = httptest.NewRequest("GET", "/export/json", nil) + } + + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + errors <- fmt.Errorf("op %d (type %d) got status %d", idx, idx%4, rr.Code) + } + }(i) + } + wg.Wait() + close(errors) + elapsed := time.Since(start) + + for err := range errors { + t.Errorf("concurrent mixed operation error: %v", err) + } + + t.Logf("40 concurrent mixed operations completed in %v", elapsed) + if elapsed > 10*time.Second { + t.Errorf("concurrent mixed operations took too long: %v (threshold: 10s)", elapsed) + } +} + +func TestStress_RapidReadMarkUnmark(t *testing.T) { + if testing.Short() { + t.Skip("skipping stress test in short mode") + } + + setupTestDB(t) + + f := &feed.Feed{Url: "http://example.com/rapid", Title: "Rapid Feed"} + f.Create() + it := &item.Item{ + Title: "Rapid Toggle", + Url: "http://example.com/rapid/1", + FeedId: f.Id, + } + _ = it.Create() + + server := newTestServer() + + // Rapidly toggle read state on the same item + const iterations = 100 + var wg sync.WaitGroup + errors := make(chan error, iterations) + + start := time.Now() + for i := 0; i < iterations; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + body, _ := json.Marshal(item.Item{ + Id: it.Id, + ReadState: idx%2 == 0, + Starred: idx%3 == 0, + }) + req := httptest.NewRequest("PUT", "/item/"+strconv.FormatInt(it.Id, 10), bytes.NewBuffer(body)) + rr := httptest.NewRecorder() + server.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + errors <- fmt.Errorf("rapid update %d got status %d", idx, rr.Code) + } + }(i) + } + wg.Wait() + close(errors) + elapsed := time.Since(start) + + for err := range errors { + t.Errorf("rapid toggle error: %v", err) + } + + t.Logf("100 concurrent read-state toggles completed in %v", elapsed) + if elapsed > 10*time.Second { + t.Errorf("rapid toggles took too long: %v (threshold: 10s)", elapsed) + } +} + func seedStressData(t *testing.T, count int) { t.Helper() f := &feed.Feed{Url: "http://example.com/bench", Title: "Bench Feed", Category: "tech"} |
