Phase 0: Fix test infrastructure
- Fix critical bug in createTestUserOnce() (dead code, wrong return type) - Add test_helpers_db.go with 6 new helper functions - Impact: All tests can now create users reliably
This commit is contained in:
@@ -183,3 +183,50 @@ func TestSyncIntegration_QueueProcessor_EnqueueProgress(t *testing.T) {
|
|||||||
assert.Equal(t, sync.SyncStatusPending, item.Status.String)
|
assert.Equal(t, sync.SyncStatusPending, item.Status.String)
|
||||||
assert.Equal(t, int32(sync.PriorityPageTurn), item.Priority.Int32)
|
assert.Equal(t, int32(sync.PriorityPageTurn), item.Priority.Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PHASE 2: Concurrency Protection
|
||||||
|
// TestSyncConcurrent_ProgressUpdates tests multiple devices updating same book simultaneously
|
||||||
|
func TestSyncConcurrent_ProgressUpdates(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
db := setupSyncTestDB(t)
|
||||||
|
userID := createSyncTestUser(t, db)
|
||||||
|
deviceID := createSyncTestDevice(t, db, userID)
|
||||||
|
mediaItemID := createSyncTestMedia(t, db)
|
||||||
|
|
||||||
|
// Progress values that will be updated concurrently
|
||||||
|
progressValues := []float64{25.0, 50.0, 75.0}
|
||||||
|
|
||||||
|
// Define update operations
|
||||||
|
var updateOps []func() error
|
||||||
|
for _, progress := range progressValues {
|
||||||
|
p := progress
|
||||||
|
updateOps = append(updateOps, func() error {
|
||||||
|
update := &sync.ProgressUpdate{
|
||||||
|
DeviceID: pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true},
|
||||||
|
MediaItemID: pgtype.UUID{Bytes: [16]byte(mediaItemID), Valid: true},
|
||||||
|
UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
|
||||||
|
Percentage: p,
|
||||||
|
Source: "koreader",
|
||||||
|
}
|
||||||
|
processor := sync.NewSyncQueueProcessor(db)
|
||||||
|
return processor.EnqueueProgress(update)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute updates concurrently
|
||||||
|
errors := runConcurrent(t, len(updateOps), updateOps)
|
||||||
|
for err := range errors {
|
||||||
|
t.Logf("Concurrent update error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 2: Database verification
|
||||||
|
// Verify final database state is consistent
|
||||||
|
// With concurrent updates, one should win - verify database has one value
|
||||||
|
progress, err := db.GetReadingProgress(ctx, database.GetReadingProgressParams{
|
||||||
|
MediaItemID: pgtype.UUID{Bytes: [16]byte(mediaItemID), Valid: true},
|
||||||
|
UserID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "should retrieve final reading progress")
|
||||||
|
assert.True(t, progress.Percentage.Valid, "percentage should be set")
|
||||||
|
assert.Contains(t, progressValues, progress.Percentage.Float64, "final percentage should match one of the concurrent updates")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bookhoard/internal/database"
|
"bookhoard/internal/database"
|
||||||
|
"bookhoard/internal/handlers"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -122,3 +124,36 @@ func createTestLibraryWithFolder(t *testing.T, ts *httptest.Server, token, name
|
|||||||
|
|
||||||
return libraryID
|
return libraryID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runConcurrent executes functions concurrently and waits for all to complete
|
||||||
|
func runConcurrent(t *testing.T, maxConcurrent int, fns []func() error) []error {
|
||||||
|
if len(fns) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(fns) < maxConcurrent {
|
||||||
|
maxConcurrent = len(fns)
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := make(chan error, len(fns))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for i := 0; i < maxConcurrent; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := fns[idx](); err != nil {
|
||||||
|
errors <- err
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errors)
|
||||||
|
|
||||||
|
var allErrors []error
|
||||||
|
for err := range errors {
|
||||||
|
allErrors = append(allErrors, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrors
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user