Files
bookhoard/cmd/server/tests/worker_test.go
T
john-okeefe 3af3fd180f fix: add test files to TestWorker_ConcurrentJobs for scanner
Problem:
TestWorker_ConcurrentJobs was failing because it created empty temporary
directories and submitted scan jobs, but never added any test files for the
scanner to process. The scanner would complete successfully but create no
media items, causing the test to fail with 'Should NOT be empty, but was []'.

Root Cause:
The test was incomplete - it created the directory structure but didn't
populate the directories with test .epub files that the scanner could
process into media items.

Solution:
Added code to create 2 test .epub files in each of the 3 temporary
directories before submitting concurrent scan jobs:
- Directory 1: book0.epub, book1.epub
- Directory 2: book0.epub, book1.epub
- Directory 3: book0.epub, book1.epub
- Total: 6 test files to be scanned concurrently

This matches the pattern used in TestWorker_DirectoryScanJob which creates
test files before scanning.

Files changed:
- cmd/server/tests/worker_test.go: Added test file creation loop
2026-03-24 21:13:43 -04:00

325 lines
9.2 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)
// Create test files in each directory
for j := 0; j < 2; j++ {
filePath := filepath.Join(tmpDir, fmt.Sprintf("book%d.epub", j))
err = os.WriteFile(filePath, []byte(fmt.Sprintf("test content %d", j)), 0644)
require.NoError(t, err, "Failed to create test file")
}
}
// 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
}