package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestFSNotify_BulkFileDetection(t *testing.T) { setup := setupTestServer(t) t.Run("Detects multiple files added simultaneously", func(t *testing.T) { token := setup.Token tmpDir := t.TempDir() // Create library createLibReq := map[string]interface{}{ "name": "bulk-test-library", "type": "ebooks", } libBody, _ := json.Marshal(createLibReq) libReq, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries", bytes.NewBuffer(libBody)) libReq.Header.Set("Content-Type", "application/json") libReq.Header.Set("Authorization", "Bearer "+token) client := &http.Client{} libResp, err := client.Do(libReq) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(libResp.Body) require.Equal(t, http.StatusCreated, libResp.StatusCode) var libResult map[string]interface{} err = json.NewDecoder(libResp.Body).Decode(&libResult) require.NoError(t, err) libraryID := libResult["id"].(string) // Add folder to library folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", setup.Server.URL, libraryID) folderReq := map[string]interface{}{ "folder_path": tmpDir, } folderBody, _ := json.Marshal(folderReq) folderHTTPReq, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(folderBody)) folderHTTPReq.Header.Set("Content-Type", "application/json") folderHTTPReq.Header.Set("Authorization", "Bearer "+token) folderResp, err := client.Do(folderHTTPReq) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(folderResp.Body) require.Equal(t, http.StatusCreated, folderResp.StatusCode, "Folder should be added to library") // Create 20 test files simultaneously for i := 0; i < 20; i++ { fileName := filepath.Join(tmpDir, fmt.Sprintf("book%d.epub", i)) err := os.WriteFile(fileName, []byte(fmt.Sprintf("test %d", i)), 0644) require.NoError(t, err) } // Trigger library scan scanURL := fmt.Sprintf("%s/api/libraries/%s/scan", setup.Server.URL, libraryID) scanHTTPReq, _ := http.NewRequest("POST", scanURL, nil) scanHTTPReq.Header.Set("Authorization", "Bearer "+token) scanResp, err := client.Do(scanHTTPReq) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(scanResp.Body) require.Equal(t, http.StatusAccepted, scanResp.StatusCode, "Scan should be accepted") var scanResponse map[string]interface{} err = json.NewDecoder(scanResp.Body).Decode(&scanResponse) require.NoError(t, err) 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") // Wait for scan job to complete time.Sleep(3 * time.Second) for i := 0; i < 30; i++ { if i > 0 { time.Sleep(1 * time.Second) } statusURL := fmt.Sprintf("%s/api/scanner/status/%s", setup.Server.URL, jobID) statusReq, _ := http.NewRequest("GET", statusURL, nil) statusReq.Header.Set("Authorization", "Bearer "+token) statusResp, err := client.Do(statusReq) require.NoError(t, err) if statusResp.StatusCode == http.StatusNotFound { _ = statusResp.Body.Close() break // Job completed } var status map[string]interface{} err = json.NewDecoder(statusResp.Body).Decode(&status) require.NoError(t, err) _ = statusResp.Body.Close() if status["status"] == "completed" || status["status"] == "failed" { _ = statusResp.Body.Close() break } } // Check items req, _ := http.NewRequest("GET", setup.Server.URL+"/api/libraries/"+libraryID+"/media-items", nil) req.Header.Set("Authorization", "Bearer "+token) itemsResp, err := client.Do(req) require.NoError(t, err) defer func(Body io.ReadCloser) { _ = Body.Close() }(itemsResp.Body) var itemsResult map[string]interface{} err = json.NewDecoder(itemsResp.Body).Decode(&itemsResult) require.NoError(t, err) items, ok := itemsResult["data"].([]interface{}) if !ok || items == nil { items = []interface{}{} // Handle nil or wrong type } assert.GreaterOrEqual(t, len(items), 20, "Should detect all 20 files") // Cleanup deleteReq, _ := http.NewRequest("DELETE", setup.Server.URL+"/api/libraries/"+libraryID, nil) deleteReq.Header.Set("Authorization", "Bearer "+token) client.Do(deleteReq) }) }