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:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user