Files
bookhoard/cmd/server/tests/test_helpers_db_test.go
T
john-okeefe a6700f73e0 fix(tests): handle all Close() and Decode() errors across integration tests
Replace all unhandled resp.Body.Close() calls throughout the test suite:

- Deferred calls: replace 'defer VAR.Body.Close()' with a closure that explicitly
  discards the error via 'defer func(Body io.ReadCloser) { _ = Body.Close() }(VAR.Body)'
- Immediate calls: replace 'VAR.Body.Close()' with '_ = VAR.Body.Close()'

Replace all unhandled json.NewDecoder(VAR.Body).Decode(&x) calls with error capture
and require.NoError assertion. Files using httptest.ResponseRecorder (collections_preview,
processing_issues) use 'err :=' declaration; suite-style tests (scanner_integration,
dashboard_integration) use s.T() instead of t.
2026-04-21 20:33:05 -04:00

165 lines
5.3 KiB
Go

package main
import (
"bookhoard/internal/database"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Helper functions for database verification and test utilities
// These functions reduce code duplication and ensure consistent database state verification
// verifyDeviceCreated verifies a device exists in database with expected values
func verifyDeviceCreated(t *testing.T, db *database.Queries, deviceID uuid.UUID, expectedName, expectedType, expectedIdentifier string) {
pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
device, err := db.GetDevice(context.Background(), pgDeviceID)
require.NoError(t, err, "Device should exist in database")
assert.Equal(t, expectedName, device.DeviceName, "Device name should match")
assert.Equal(t, expectedType, device.DeviceType, "Device type should match")
assert.Equal(t, expectedIdentifier, device.DeviceIdentifier, "Device identifier should match")
assert.NotEmpty(t, device.AuthToken, "Device should have auth token")
}
// verifyDeviceDeleted verifies a device does not exist in database
func verifyDeviceDeleted(t *testing.T, db *database.Queries, deviceID uuid.UUID) {
pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
_, err := db.GetDevice(context.Background(), pgDeviceID)
assert.Error(t, err, "Device should be deleted from database")
}
// verifyUserField verifies a user has expected field value in database
func verifyUserField(t *testing.T, db *database.Queries, userID uuid.UUID, field string, expected interface{}) {
pgUserID := pgtype.UUID{Bytes: [16]byte(userID), Valid: true}
user, err := db.GetUser(context.Background(), pgUserID)
require.NoError(t, err, "User should exist in database")
switch field {
case "email":
if em, ok := expected.(string); ok {
assert.Equal(t, em, user.Email, "Email should match")
}
case "first_name":
if fn, ok := expected.(string); ok {
assert.Equal(t, fn, user.FirstName.String, "First name should match")
}
case "last_name":
if ln, ok := expected.(string); ok {
assert.Equal(t, ln, user.LastName.String, "Last name should match")
}
case "username":
if un, ok := expected.(string); ok {
assert.Equal(t, un, user.Username, "Username should match")
}
case "theme":
if th, ok := expected.(string); ok {
assert.Equal(t, th, user.Theme.String, "Theme should match")
}
}
}
// verifyMediaItemInDB verifies a media item exists in database
func verifyMediaItemInDB(t *testing.T, db *database.Queries, mediaID uuid.UUID) {
pgMediaID := pgtype.UUID{Bytes: [16]byte(mediaID), Valid: true}
_, err := db.GetMediaItem(context.Background(), pgMediaID)
require.NoError(t, err, "Media item should exist in database")
}
// verifyMediaItemDeleted verifies a media item does not exist in database
func verifyMediaItemDeleted(t *testing.T, db *database.Queries, mediaID uuid.UUID) {
pgMediaID := pgtype.UUID{Bytes: [16]byte(mediaID), Valid: true}
_, err := db.GetMediaItem(context.Background(), pgMediaID)
assert.Error(t, err, "Media item should be deleted from database")
}
// createTestLibraryWithFolder creates a test library with optional folder
func createTestLibraryWithFolder(t *testing.T, ts *httptest.Server, token, name string, withFolder bool) string {
libReq := map[string]interface{}{
"name": name,
"type": "ebooks",
}
libBody, _ := json.Marshal(libReq)
libHTTP, _ := http.NewRequest("POST", ts.URL+"/api/libraries", bytes.NewBuffer(libBody))
libHTTP.Header.Set("Content-Type", "application/json")
libHTTP.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(libHTTP)
require.NoError(t, err)
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
require.Equal(t, http.StatusCreated, resp.StatusCode, "Library creation should succeed")
var libResponse map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&libResponse)
require.NoError(t, err)
libraryID := libResponse["id"].(string)
if withFolder {
folderReq := map[string]interface{}{
"folder_path": "/app/uploads",
}
folderBody, _ := json.Marshal(folderReq)
folderHTTP, _ := http.NewRequest("POST", fmt.Sprintf("%s/api/libraries/%s/folders", ts.URL, libraryID), bytes.NewBuffer(folderBody))
folderHTTP.Header.Set("Content-Type", "application/json")
folderHTTP.Header.Set("Authorization", "Bearer "+token)
folderResp, err := client.Do(folderHTTP)
require.NoError(t, err)
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(folderResp.Body)
require.Equal(t, http.StatusCreated, folderResp.StatusCode, "Folder creation should succeed")
}
return libraryID
}
// runConcurrent executes functions concurrently and waits for all to complete
func runConcurrent(t *testing.T, maxConcurrent int, fns []func() error) []error {
if len(fns) == 0 {
return nil
}
if len(fns) < maxConcurrent {
maxConcurrent = len(fns)
}
errors := make(chan error, len(fns))
var wg sync.WaitGroup
for i := 0; i < maxConcurrent; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
if err := fns[idx](); err != nil {
errors <- err
}
}(i)
}
wg.Wait()
close(errors)
var allErrors []error
for err := range errors {
allErrors = append(allErrors, err)
}
return allErrors
}