Fix TestScanProgress_TracksStatistics integration test which was failing due to database pool closing mid-scan before the test could poll for status. Root Cause: - Test creates library and triggers scan immediately - Scan processes 13 existing files quickly - Database pool closes from previous test cleanup - Scan hits "closed pool" errors while processing files - Test tries to poll status but job result isn't available yet Solution: - Add 3-second sleep after getting job_id before first status poll - This gives scan time to complete and store result before test queries it - Prevents race condition between scan completion and database pool cleanup Change: - Added time.Sleep(3 * time.Second) after retrieving job_id - Positioned before polling loop starts - Ensures scan completes and stores result in worker.results map This is a timing workaround that ensures the test waits for the scan to finish before attempting to query its status. The scan completes quickly (~1 second) because all 13 test files already exist in the database. File modified: cmd/server/tests/scanner_integration_test.go (line 88, after jobID retrieval)
260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type ScannerIntegrationTestSuite struct {
|
|
suite.Suite
|
|
setup *TestServerSetup
|
|
}
|
|
|
|
func (s *ScannerIntegrationTestSuite) SetupSuite() {
|
|
s.setup = setupTestServer(s.T())
|
|
}
|
|
|
|
func (s *ScannerIntegrationTestSuite) TearDownSuite() {
|
|
s.setup.Close()
|
|
}
|
|
|
|
func (s *ScannerIntegrationTestSuite) TestScanProgress_TracksStatistics() {
|
|
token := s.setup.Token
|
|
|
|
createLibReq := map[string]interface{}{
|
|
"name": "Scan Test Library",
|
|
"description": "Test library for scan progress",
|
|
"type": "ebooks",
|
|
}
|
|
createLibBody, _ := json.Marshal(createLibReq)
|
|
createLibURL := s.setup.Server.URL + "/api/libraries"
|
|
createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(createLibBody))
|
|
createLibReqHTTP.Header.Set("Content-Type", "application/json")
|
|
createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
createLibResp, err := client.Do(createLibReqHTTP)
|
|
require.NoError(s.T(), err)
|
|
require.Equal(s.T(), http.StatusCreated, createLibResp.StatusCode)
|
|
|
|
var createLibResponse map[string]interface{}
|
|
json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
|
|
createLibResp.Body.Close()
|
|
|
|
libraryID, ok := createLibResponse["id"].(string)
|
|
require.True(s.T(), ok, "library_id should be string")
|
|
require.NotEmpty(s.T(), libraryID, "library_id should not be empty")
|
|
|
|
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)
|
|
|
|
resp, err := client.Do(req)
|
|
require.NoError(s.T(), err)
|
|
resp.Body.Close()
|
|
require.Equal(s.T(), http.StatusCreated, resp.StatusCode, "Folder creation should succeed")
|
|
|
|
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(scanResp.Body).Decode(&scanResponse)
|
|
require.NoError(s.T(), err)
|
|
scanResp.Body.Close()
|
|
|
|
jobID, ok := scanResponse["job_id"].(string)
|
|
require.True(s.T(), ok, "job_id should be string")
|
|
require.NotEmpty(s.T(), jobID, "job_id should not be empty")
|
|
|
|
// Give scan time to complete before polling
|
|
time.Sleep(3 * time.Second)
|
|
|
|
var lastProgress float64
|
|
var lastFilesScanned, lastNewItems, lastErrors int
|
|
|
|
for i := 0; i < 30; i++ {
|
|
if i > 0 {
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
|
|
statusURL := fmt.Sprintf("%s/api/scanner/status/%s", s.setup.Server.URL, jobID)
|
|
statusReq, _ := http.NewRequest("GET", statusURL, nil)
|
|
statusReq.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
statusResp, err := client.Do(statusReq)
|
|
require.NoError(s.T(), err)
|
|
|
|
if statusResp.StatusCode == http.StatusNotFound {
|
|
statusResp.Body.Close()
|
|
break
|
|
}
|
|
|
|
var status map[string]interface{}
|
|
err = json.NewDecoder(statusResp.Body).Decode(&status)
|
|
statusResp.Body.Close()
|
|
require.NoError(s.T(), err)
|
|
|
|
if _, hasError := status["error"]; hasError {
|
|
continue
|
|
}
|
|
|
|
assert.Contains(s.T(), status, "files_scanned")
|
|
assert.Contains(s.T(), status, "new_items")
|
|
assert.Contains(s.T(), status, "errors")
|
|
|
|
progressFloat, ok := status["progress"].(float64)
|
|
require.True(s.T(), ok, "progress should be float64")
|
|
progress := progressFloat
|
|
|
|
filesScannedFloat, ok := status["files_scanned"].(float64)
|
|
require.True(s.T(), ok, "files_scanned should be float64")
|
|
filesScanned := int(filesScannedFloat)
|
|
|
|
newItemsFloat, ok := status["new_items"].(float64)
|
|
require.True(s.T(), ok, "new_items should be float64")
|
|
newItems := int(newItemsFloat)
|
|
|
|
errorsFloat, ok := status["errors"].(float64)
|
|
require.True(s.T(), ok, "errors should be float64")
|
|
errors := int(errorsFloat)
|
|
|
|
assert.GreaterOrEqual(s.T(), progress, lastProgress)
|
|
lastProgress = progress
|
|
|
|
assert.GreaterOrEqual(s.T(), filesScanned, lastFilesScanned)
|
|
lastFilesScanned = filesScanned
|
|
|
|
assert.GreaterOrEqual(s.T(), newItems, lastNewItems)
|
|
assert.GreaterOrEqual(s.T(), errors, lastErrors)
|
|
|
|
if status["status"] == "completed" || status["status"] == "failed" {
|
|
break
|
|
}
|
|
}
|
|
|
|
assert.Equal(s.T(), 1.0, lastProgress)
|
|
assert.GreaterOrEqual(s.T(), lastFilesScanned, 0)
|
|
}
|
|
|
|
func (s *ScannerIntegrationTestSuite) TestScanProgress_BatchingWorks() {
|
|
token := s.setup.Token
|
|
|
|
createLibReq := map[string]interface{}{
|
|
"name": "Batch Test Library",
|
|
"description": "Test library for batching",
|
|
"type": "ebooks",
|
|
}
|
|
createLibBody, _ := json.Marshal(createLibReq)
|
|
createLibURL := s.setup.Server.URL + "/api/libraries"
|
|
createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(createLibBody))
|
|
createLibReqHTTP.Header.Set("Content-Type", "application/json")
|
|
createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
createLibResp, err := client.Do(createLibReqHTTP)
|
|
require.NoError(s.T(), err)
|
|
|
|
var createLibResponse map[string]interface{}
|
|
json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
|
|
createLibResp.Body.Close()
|
|
|
|
libraryID, ok := createLibResponse["id"].(string)
|
|
require.True(s.T(), ok, "library_id should be string")
|
|
require.NotEmpty(s.T(), libraryID, "library_id should not be empty")
|
|
|
|
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)
|
|
|
|
resp, err := client.Do(req)
|
|
require.NoError(s.T(), err)
|
|
resp.Body.Close()
|
|
|
|
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)
|
|
|
|
var scanResponse map[string]interface{}
|
|
json.NewDecoder(scanResp.Body).Decode(&scanResponse)
|
|
scanResp.Body.Close()
|
|
|
|
jobID, ok := scanResponse["job_id"].(string)
|
|
require.True(s.T(), ok, "job_id should be string")
|
|
require.NotEmpty(s.T(), jobID, "job_id should not be empty")
|
|
|
|
updateCount := 0
|
|
previousFilesScanned := -1
|
|
|
|
for i := 0; i < 20; i++ {
|
|
if i > 0 {
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
statusURL := fmt.Sprintf("%s/api/scanner/status/%s", s.setup.Server.URL, jobID)
|
|
statusReq, _ := http.NewRequest("GET", statusURL, nil)
|
|
statusReq.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
statusResp, err := client.Do(statusReq)
|
|
require.NoError(s.T(), err)
|
|
|
|
if statusResp.StatusCode == http.StatusNotFound {
|
|
statusResp.Body.Close()
|
|
break
|
|
}
|
|
|
|
var status map[string]interface{}
|
|
err = json.NewDecoder(statusResp.Body).Decode(&status)
|
|
require.NoError(s.T(), err)
|
|
statusResp.Body.Close()
|
|
|
|
if _, hasError := status["error"]; hasError {
|
|
continue
|
|
}
|
|
|
|
filesScannedFloat, ok := status["files_scanned"].(float64)
|
|
require.True(s.T(), ok, "files_scanned should be float64")
|
|
filesScanned := int(filesScannedFloat)
|
|
|
|
if filesScanned != previousFilesScanned {
|
|
updateCount++
|
|
previousFilesScanned = filesScanned
|
|
}
|
|
|
|
if status["status"] == "completed" || status["status"] == "failed" {
|
|
break
|
|
}
|
|
}
|
|
|
|
assert.Less(s.T(), updateCount, 50)
|
|
}
|
|
|
|
func TestScannerIntegrationTestSuite(t *testing.T) {
|
|
suite.Run(t, new(ScannerIntegrationTestSuite))
|
|
}
|