Fix integration test code in backend progress tracking plan

Fixed 4 issues identified during PROJECT_GUIDELINES.md compliance review:

1. Fixed test assertions to use s.T() instead of t in test suite methods
2. Replaced non-existent createTestLibraryWithFolder() helper with:
   - Existing setup.CreateLibrary() method from test_helpers.go
   - Manual folder creation via POST /api/libraries/{id}/folders API
3. Replaced weak unit test with comprehensive tests:
   - TestWorker_JobResult_HasStatsFields: Verifies stats fields are stored
   - TestWorker_ProgressCallback_UpdatesJobResult: Verifies real-time updates
4. Documented database pool configuration (setupTestServer already uses max_conns=1)

Changes ensure integration tests will work correctly when implemented:
- Use TestServerSetup.CreateLibrary() for library creation
- Create folders via API call before triggering scans
- Use proper s.T() test reference in all assertions
- Include both unit and integration tests for full coverage

Verified against PROJECT_GUIDELINES.md:
- Uses setupTestServer() from test_helpers.go ✓
- Database pool uses max_conns=1 ✓
- Follows service layer pattern ✓
- No database schema changes ✓
- All assertions use correct test reference ✓
This commit is contained in:
2026-02-25 10:45:13 -05:00
parent bd9715c272
commit 4a436f414c
+135 -40
View File
@@ -473,27 +473,42 @@ func (s *ScannerIntegrationTestSuite) TearDownSuite() {
func (s *ScannerIntegrationTestSuite) TestScanProgress_TracksStatistics() {
token := s.setup.Token
// Create test library with folder
libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Scan Test Library", false)
// Start scan
url := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
req, _ := http.NewRequest("POST", url, nil)
// Create test library
libraryID := s.setup.CreateLibrary(s.T(), "Scan Test Library", "ebooks")
// Add folder to library
folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", s.setup.Server.URL, libraryID)
folderReq := map[string]interface{}{
"folder_path": "/app/uploads",
}
folderBody, _ := json.Marshal(folderReq)
req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(folderBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
require.NoError(s.T(), err)
require.Equal(s.T(), http.StatusAccepted, resp.StatusCode)
resp.Body.Close()
require.Equal(s.T(), http.StatusCreated, resp.StatusCode, "Folder creation should succeed")
// Start scan
scanURL := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
scanReq, _ := http.NewRequest("POST", scanURL, nil)
scanReq.Header.Set("Authorization", "Bearer "+token)
scanResp, err := client.Do(scanReq)
require.NoError(s.T(), err)
require.Equal(s.T(), http.StatusAccepted, scanResp.StatusCode)
var scanResponse map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&scanResponse)
err = json.NewDecoder(scanResp.Body).Decode(&scanResponse)
require.NoError(s.T(), err)
resp.Body.Close()
scanResp.Body.Close()
jobID, ok := scanResponse["job_id"].(string)
require.True(t, ok, "job_id should be string")
require.NotEmpty(t, jobID, "job_id should not be empty")
require.True(s.T(), ok, "job_id should be string")
require.NotEmpty(s.T(), jobID, "job_id should not be empty")
// Poll for progress updates
var lastProgress float64
@@ -521,19 +536,19 @@ func (s *ScannerIntegrationTestSuite) TestScanProgress_TracksStatistics() {
// Track progress with safe type assertions
progressFloat, ok := status["progress"].(float64)
require.True(t, ok, "progress should be float64")
require.True(s.T(), ok, "progress should be float64")
progress := progressFloat
filesScannedFloat, ok := status["files_scanned"].(float64)
require.True(t, ok, "files_scanned should be float64")
require.True(s.T(), ok, "files_scanned should be float64")
filesScanned := int(filesScannedFloat)
newItemsFloat, ok := status["new_items"].(float64)
require.True(t, ok, "new_items should be float64")
require.True(s.T(), ok, "new_items should be float64")
newItems := int(newItemsFloat)
errorsFloat, ok := status["errors"].(float64)
require.True(t, ok, "errors should be float64")
require.True(s.T(), ok, "errors should be float64")
errors := int(errorsFloat)
// Progress should be non-decreasing
@@ -562,12 +577,16 @@ func (s *ScannerIntegrationTestSuite) TestScanProgress_TracksStatistics() {
func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
token := s.setup.Token
// Create library
libraryID := createTestLibraryWithFolder(s.T(), s.setup.Server, token, "Batch Test Library", false)
// Create library with folder
libraryID := s.setup.CreateLibrary(s.T(), "Batch Test Library", "ebooks")
// Start scan
url := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
req, _ := http.NewRequest("POST", url, nil)
folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", s.setup.Server.URL, libraryID)
folderReq := map[string]interface{}{
"folder_path": "/app/uploads",
}
folderBody, _ := json.Marshal(folderReq)
req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(folderBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
@@ -575,12 +594,21 @@ func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
require.NoError(s.T(), err)
resp.Body.Close()
// Start scan
scanURL := fmt.Sprintf("%s/api/libraries/%s/scan", s.setup.Server.URL, libraryID)
scanReq, _ := http.NewRequest("POST", scanURL, nil)
scanReq.Header.Set("Authorization", "Bearer "+token)
scanResp, err := client.Do(scanReq)
require.NoError(s.T(), err)
scanResp.Body.Close()
var scanResponse map[string]interface{}
json.NewDecoder(resp.Body).Decode(&scanResponse)
json.NewDecoder(scanResp.Body).Decode(&scanResponse)
jobID, ok := scanResponse["job_id"].(string)
require.True(t, ok, "job_id should be string")
require.NotEmpty(t, jobID, "job_id should not be empty")
require.True(s.T(), ok, "job_id should be string")
require.NotEmpty(s.T(), jobID, "job_id should not be empty")
// Poll and verify we don't get updates on EVERY file
updateCount := 0
@@ -601,7 +629,7 @@ func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
statusResp.Body.Close()
filesScannedFloat, ok := status["files_scanned"].(float64)
require.True(t, ok, "files_scanned should be float64")
require.True(s.T(), ok, "files_scanned should be float64")
filesScanned := int(filesScannedFloat)
// Only count as update if files_scanned changed
@@ -618,7 +646,7 @@ func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
// With batching every 10 files, we should have FEWER updates than files
// This is a weak assertion, but verifies batching is working
// Threshold of 50 assumes test library has < 500 files - adjust based on actual test data
assert.Less(t, updateCount, 50)
assert.Less(s.T(), updateCount, 50)
}
func TestScannerIntegrationTestSuite(t *testing.T) {
@@ -632,28 +660,90 @@ func TestScannerIntegrationTestSuite(t *testing.T) {
**Add test for new JobResult fields:**
```go
func TestWorker_ProcessScanJob_ReturnsStats(t *testing.T) {
// Test that processScanJob returns proper stat structure
func TestWorker_JobResult_HasStatsFields(t *testing.T) {
// Test that JobResult properly stores scan statistics
worker := NewWorker(1)
defer worker.Shutdown()
jobID := "test-job-stats"
// Simulate job completion with stats
worker.mu.Lock()
worker.results[jobID] = &JobResult{
JobID: jobID,
Status: JobStatusCompleted,
Progress: 1.0,
FilesScanned: 42,
NewItems: 5,
Errors: 1,
}
worker.mu.Unlock()
// Verify stats are retrievable
result, exists := worker.GetJobStatus(jobID)
require.True(t, exists, "Job result should exist")
require.NotNil(t, result, "Result should not be nil")
assert.Equal(t, jobID, result.JobID)
assert.Equal(t, JobStatusCompleted, result.Status)
assert.Equal(t, 1.0, result.Progress)
assert.Equal(t, 42, result.FilesScanned, "FilesScanned should be 42")
assert.Equal(t, 5, result.NewItems, "NewItems should be 5")
assert.Equal(t, 1, result.Errors, "Errors should be 1")
}
func TestWorker_ProgressCallback_UpdatesJobResult(t *testing.T) {
// Test that progress callback updates JobResult in real-time
worker := NewWorker(1)
defer worker.Shutdown()
job := &Job{
ID: "test-job-stats",
Type: JobTypeScan,
Params: map[string]interface{}{
"library_id": "test-lib",
"folders": []string{"/test"},
"admin_id": "test-admin",
"db": nil, // Will fail but we can test return structure
},
Status: JobStatusPending,
ID: "test-progress",
Type: JobTypeScan,
Status: JobStatusInProgress,
Context: context.Background(),
}
result, err := worker.processScanJob(job)
// Set up progress callback
job.ProgressCallback = func(progress float64, filesScanned, newItems, errors int) {
worker.mu.Lock()
defer worker.mu.Unlock()
if result, exists := worker.results[job.ID]; exists {
result.Progress = progress
result.FilesScanned = filesScanned
result.NewItems = newItems
result.Errors = errors
}
}
// Should fail (nil db), but we can test the error handling
assert.Error(t, err)
assert.Nil(t, result)
// Initialize result
worker.mu.Lock()
worker.results[job.ID] = &JobResult{
JobID: job.ID,
Status: JobStatusInProgress,
}
worker.mu.Unlock()
// Simulate progress updates
job.UpdateProgress(0.5, 10, 2, 0)
result, exists := worker.GetJobStatus(job.ID)
require.True(t, exists)
assert.Equal(t, 0.5, result.Progress)
assert.Equal(t, 10, result.FilesScanned)
assert.Equal(t, 2, result.NewItems)
assert.Equal(t, 0, result.Errors)
// Simulate completion
job.UpdateProgress(1.0, 20, 5, 1)
result, exists = worker.GetJobStatus(job.ID)
require.True(t, exists)
assert.Equal(t, 1.0, result.Progress)
assert.Equal(t, 20, result.FilesScanned)
assert.Equal(t, 5, result.NewItems)
assert.Equal(t, 1, result.Errors)
}
```
@@ -664,7 +754,11 @@ func TestWorker_ProcessScanJob_ReturnsStats(t *testing.T) {
After implementation:
- [ ] Unit tests pass: `go test ./internal/services/...`
- [ ] TestWorker_JobResult_HasStatsFields verifies stats are stored
- [ ] TestWorker_ProgressCallback_UpdatesJobResult verifies real-time updates
- [ ] Integration tests pass: `go test ./cmd/server/tests/...`
- [ ] TestScanProgress_TracksStatistics verifies all new fields exist and increment
- [ ] TestScanProgress_BatchingWorks verifies batching reduces update frequency
- [ ] Scan a library with multiple files
- [ ] Poll `/api/scanner/status/{jobId}` during scan
- [ ] Verify `progress` increases from 0% to 100% gradually
@@ -686,6 +780,7 @@ After implementation:
- **Memory:** Stats tracking uses 3 int fields (24 bytes) per MediaScanner instance
- **Backwards Compatibility:** Frontend already uses `|| 0` fallbacks, so safe to deploy
- **Testing:** Integration tests use `setupTestServer()` from `test_helpers.go`
- **Database Pool Configuration:** `setupTestServer()` already sets `max_conns=1` (test_helpers.go:428), preventing connection pool exhaustion during test runs
---