Problem: Tests were calling `defer setup.Close()` which was interfering with the library cleanup added in the previous commit. The execution order was: 1. setupTestServer() registers t.Cleanup() with library deletion code 2. Test calls defer setup.Close() 3. Test finishes: - defer setup.Close() runs FIRST → closes DB pool - t.Cleanup() runs SECOND → tries to delete libraries but DB is closed! This prevented "Job Status Test Library" and other test libraries from being cleaned up, leaving residual data in the database after tests. Root Cause: The setupTestServer() function already handles cleanup via t.Cleanup(), which calls setup.Close() at the end. The explicit defer calls were redundant and caused the database pool to close before library cleanup could execute. Solution: Removed all 17 occurrences of `defer setup.Close()` from test files: - worker_test.go: 4 tests - jobs_test.go: 7 tests - scan_settings_integration_test.go: 3 tests - library_browse_test.go: 1 test - goroutine_leak_test.go: 1 test - fsnotify_integration_test.go: 1 test Now setupTestServer()'s t.Cleanup() function properly: 1. Deletes "test" libraries (while DB is still connected) 2. Then calls setup.Close() to close connections This ensures all test libraries are cleaned up, leaving a clean database after `make test-integration` completes. Files changed: - cmd/server/tests/worker_test.go: Removed 4 defer calls - cmd/server/tests/jobs_test.go: Removed 7 defer calls - cmd/server/tests/scan_settings_integration_test.go: Removed 3 defer calls - cmd/server/tests/library_browse_test.go: Removed 1 defer call - cmd/server/tests/goroutine_leak_test.go: Removed 1 defer call - cmd/server/tests/fsnotify_integration_test.go: Removed 1 defer call
318 lines
8.9 KiB
Go
318 lines
8.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"bookhoard/internal/services"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestWorker_DirectoryScanJob(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := setup.Token
|
|
|
|
// Create library
|
|
createLibReq := map[string]interface{}{
|
|
"name": "Worker Test Library",
|
|
"description": "Test library for worker",
|
|
"type": "ebooks",
|
|
}
|
|
createLibURL := setup.Server.URL + "/api/libraries"
|
|
createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(jsonMarshal(createLibReq)))
|
|
createLibReqHTTP.Header.Set("Content-Type", "application/json")
|
|
createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
createLibResp, err := client.Do(createLibReqHTTP)
|
|
require.NoError(t, err)
|
|
require.Equal(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(t, ok)
|
|
require.NotEmpty(t, libraryID)
|
|
|
|
// Create a temporary directory for testing
|
|
tmpDir := t.TempDir()
|
|
|
|
// Add folder to library
|
|
folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", setup.Server.URL, libraryID)
|
|
folderReq := map[string]interface{}{
|
|
"folder_path": tmpDir,
|
|
}
|
|
req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(jsonMarshal(folderReq)))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
resp.Body.Close()
|
|
require.Equal(t, http.StatusCreated, resp.StatusCode)
|
|
|
|
// Create test files in the directory
|
|
for i := 0; i < 3; i++ {
|
|
filePath := filepath.Join(tmpDir, fmt.Sprintf("book%d.epub", i))
|
|
err := os.WriteFile(filePath, []byte(fmt.Sprintf("test content %d", i)), 0644)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Submit directory scan job through WorkerInstance
|
|
job := &services.Job{
|
|
ID: uuid.New().String(),
|
|
Type: services.JobTypeDirectoryScan,
|
|
Params: map[string]interface{}{
|
|
"directory": tmpDir,
|
|
"db": setup.DB,
|
|
},
|
|
Status: services.JobStatusPending,
|
|
}
|
|
|
|
// Use EnqueueJob which returns errors
|
|
err = services.WorkerInstance.EnqueueJob(job)
|
|
require.NoError(t, err, "Failed to enqueue job")
|
|
// Poll for job completion
|
|
timeout := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(timeout) {
|
|
if status, exists := services.WorkerInstance.GetJobStatus(job.ID); exists {
|
|
if status.Status == services.JobStatusCompleted || status.Status == services.JobStatusFailed {
|
|
break
|
|
}
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
// Debug: Check job status after polling
|
|
if finalStatus, exists := services.WorkerInstance.GetJobStatus(job.ID); exists {
|
|
t.Logf("Job %s final status: %s, error: %s", job.ID, finalStatus.Status, finalStatus.Error)
|
|
} else {
|
|
t.Logf("Job %s not found in results", job.ID)
|
|
}
|
|
|
|
// Check that items were created in database
|
|
ctx := context.Background()
|
|
items, err := setup.DB.ListMediaItems(ctx, database.ListMediaItemsParams{
|
|
Limit: 1000,
|
|
Offset: 0,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Should have at least 3 items (our test files)
|
|
greaterOrEqual := func(a, b int) bool {
|
|
return a >= b
|
|
}
|
|
assert.True(t, greaterOrEqual(len(items), 3), fmt.Sprintf("Expected at least 3 items, got %d", len(items)))
|
|
}
|
|
|
|
func TestWorker_SetFoldersJob(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
token := setup.Token
|
|
|
|
// Create library
|
|
createLibReq := map[string]interface{}{
|
|
"name": "Set Folders Test Library",
|
|
"description": "Test library for set folders",
|
|
"type": "ebooks",
|
|
}
|
|
createLibURL := setup.Server.URL + "/api/libraries"
|
|
createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(jsonMarshal(createLibReq)))
|
|
createLibReqHTTP.Header.Set("Content-Type", "application/json")
|
|
createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
client := &http.Client{}
|
|
createLibResp, err := client.Do(createLibReqHTTP)
|
|
require.NoError(t, err)
|
|
require.Equal(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(t, ok)
|
|
require.NotEmpty(t, libraryID)
|
|
|
|
// Create temporary directory
|
|
tmpDir := t.TempDir()
|
|
|
|
// Add folder to library via HTTP API
|
|
addFolderToLibrary(t, setup, libraryID, tmpDir)
|
|
|
|
// Submit set folders job through WorkerInstance
|
|
job := &services.Job{
|
|
ID: uuid.New().String(),
|
|
Type: services.JobTypeSetFolders,
|
|
Params: map[string]interface{}{
|
|
"folders": []string{tmpDir},
|
|
"db": setup.DB,
|
|
},
|
|
Status: services.JobStatusPending,
|
|
}
|
|
|
|
err = services.WorkerInstance.EnqueueJob(job)
|
|
require.NoError(t, err, "Failed to enqueue set folders job")
|
|
|
|
// Wait for job to process
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// Verify folder was added to library
|
|
ctx := context.Background()
|
|
folders, err := setup.DB.GetLibraryFolders(ctx, pgtype.UUID{Bytes: [16]byte(uuid.MustParse(libraryID)), Valid: true})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, folders, "Library should have folders")
|
|
|
|
// Find our folder
|
|
found := false
|
|
for _, folder := range folders {
|
|
if folder.FolderPath == tmpDir {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Folder should be in library")
|
|
}
|
|
|
|
func TestWorker_ConcurrentJobs(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create a library and get admin user
|
|
adminID := getTestUserID(t, setup.DB)
|
|
|
|
libraryType, err := setup.DB.GetLibraryTypeByName(ctx, "ebooks")
|
|
require.NoError(t, err)
|
|
|
|
library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{
|
|
Name: "Concurrent Job Test Library",
|
|
Description: pgtype.Text{String: "Test library", Valid: true},
|
|
LibraryTypeID: libraryType.ID,
|
|
CreatedByAdminID: pgtype.UUID{Bytes: [16]byte(adminID), Valid: true},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Create multiple temporary directories
|
|
var tmpDirs []string
|
|
for i := 0; i < 3; i++ {
|
|
tmpDir := t.TempDir()
|
|
tmpDirs = append(tmpDirs, tmpDir)
|
|
|
|
// Add folder to library
|
|
_, err = setup.DB.AddLibraryFolder(ctx, database.AddLibraryFolderParams{
|
|
LibraryID: library.ID,
|
|
FolderPath: tmpDir,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Submit multiple concurrent jobs
|
|
for _, tmpDir := range tmpDirs {
|
|
job := &services.Job{
|
|
ID: uuid.New().String(),
|
|
Type: services.JobTypeDirectoryScan,
|
|
Params: map[string]interface{}{
|
|
"directory": tmpDir,
|
|
"db": setup.DB,
|
|
},
|
|
Status: services.JobStatusPending,
|
|
}
|
|
err = services.WorkerInstance.EnqueueJob(job)
|
|
require.NoError(t, err, "Failed to enqueue set folders job")
|
|
}
|
|
|
|
// Wait for all jobs to process
|
|
time.Sleep(3 * time.Second)
|
|
|
|
// Verify all directories were processed
|
|
allItems, err := setup.DB.ListMediaItems(ctx, database.ListMediaItemsParams{
|
|
Limit: 1000,
|
|
Offset: 0,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, allItems, "Should have media items from concurrent jobs")
|
|
}
|
|
|
|
func TestWorker_JobStatusTracking(t *testing.T) {
|
|
setup := setupTestServer(t)
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create test library
|
|
adminID := getTestUserID(t, setup.DB)
|
|
libraryType, err := setup.DB.GetLibraryTypeByName(ctx, "ebooks")
|
|
require.NoError(t, err)
|
|
|
|
library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{
|
|
Name: "Job Status Test Library",
|
|
Description: pgtype.Text{String: "Test library", Valid: true},
|
|
LibraryTypeID: libraryType.ID,
|
|
CreatedByAdminID: pgtype.UUID{Bytes: [16]byte(adminID), Valid: true},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tmpDir := t.TempDir()
|
|
_, err = setup.DB.AddLibraryFolder(ctx, database.AddLibraryFolderParams{
|
|
LibraryID: library.ID,
|
|
FolderPath: tmpDir,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Submit job and track status
|
|
jobID := uuid.New().String()
|
|
job := &services.Job{
|
|
ID: jobID,
|
|
Type: services.JobTypeDirectoryScan,
|
|
Params: map[string]interface{}{
|
|
"directory": tmpDir,
|
|
"db": setup.DB,
|
|
},
|
|
Status: services.JobStatusPending,
|
|
}
|
|
|
|
err = services.WorkerInstance.EnqueueJob(job)
|
|
require.NoError(t, err, "Failed to enqueue set folders job")
|
|
|
|
// Poll job status
|
|
var finalStatus string
|
|
for i := 0; i < 10; i++ {
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
result, exists := services.WorkerInstance.GetJobStatus(jobID)
|
|
if exists {
|
|
finalStatus = string(result.Status)
|
|
if result.Status == services.JobStatusCompleted || result.Status == services.JobStatusFailed {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Job should complete (success or failure)
|
|
assert.True(t, finalStatus == "completed" || finalStatus == "failed",
|
|
fmt.Sprintf("Job should complete, got status: %s", finalStatus))
|
|
}
|
|
|
|
// Helper function to marshal JSON
|
|
func jsonMarshal(v interface{}) []byte {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|