Move database-dependent sync tests from internal/sync/ to cmd/server/tests/ where they belong: - TestSyncIntegration_OfflineDetector_* tests - TestSyncIntegration_QueueProcessor_EnqueueProgress test - Helper functions: setupSyncTestDB, createSyncTestUser/Device These tests require a running PostgreSQL database and are properly categorized as integration tests now. Unit tests that remain in internal/sync/: - TestOfflineDetector_ConstantValues (no DB needed) - TestCalculateNextRetry (logic only) - TestSyncTypeConstants, TestSyncStatusConstants (constants) - TestPriorityConstants (constants) All unit tests now pass with `make test` (no DB required).
182 lines
5.4 KiB
Go
182 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bookhoard/internal/database"
|
|
"bookhoard/internal/sync"
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// setupSyncTestDB creates a database connection for sync integration tests
|
|
func setupSyncTestDB(t *testing.T) *database.Queries {
|
|
ctx := context.Background()
|
|
|
|
dbURL := "postgresql://postgres:postgres@db:5432/bookhoard?sslmode=disable"
|
|
dbPool, err := pgxpool.New(ctx, dbURL)
|
|
require.NoError(t, err, "Failed to connect to test database")
|
|
|
|
db := database.New(dbPool)
|
|
|
|
t.Cleanup(func() {
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM sync_queue WHERE true")
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM reading_progress WHERE true")
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM media_items WHERE title LIKE 'Test %'")
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM libraries WHERE name LIKE 'Test %'")
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM devices WHERE device_name LIKE 'Test %'")
|
|
_, _ = dbPool.Exec(ctx, "DELETE FROM users WHERE email LIKE 'test%'")
|
|
dbPool.Close()
|
|
})
|
|
|
|
return db
|
|
}
|
|
|
|
func createSyncTestUser(t *testing.T, db *database.Queries) pgtype.UUID {
|
|
ctx := context.Background()
|
|
|
|
userID := uuid.New()
|
|
hashedPassword := "$2a$10$rKvZ.HZx3lLJ6IQCpH1lOukQ/xU8j5cH8mYhPY5YGfXllq5hG8y0Ou"
|
|
|
|
_, err := db.CreateUser(ctx, database.CreateUserParams{
|
|
Email: "test-sync@example.com",
|
|
Username: "testsyncuser",
|
|
PasswordHash: hashedPassword,
|
|
FirstName: pgtype.Text{String: "Test", Valid: true},
|
|
LastName: pgtype.Text{String: "Sync", Valid: true},
|
|
Role: "user",
|
|
})
|
|
require.NoError(t, err)
|
|
return pgtype.UUID{Bytes: userID, Valid: true}
|
|
}
|
|
|
|
func createSyncTestDevice(t *testing.T, db *database.Queries, userID pgtype.UUID) pgtype.UUID {
|
|
ctx := context.Background()
|
|
|
|
deviceID := uuid.New()
|
|
authToken := "test-sync-token-" + deviceID.String()
|
|
|
|
_, err := db.CreateDevice(ctx, database.CreateDeviceParams{
|
|
UserID: userID,
|
|
DeviceName: "Test Sync Device",
|
|
DeviceType: "koreader",
|
|
DeviceIdentifier: deviceID.String(),
|
|
AuthToken: authToken,
|
|
SyncEnabled: pgtype.Bool{Bool: true, Valid: true},
|
|
})
|
|
require.NoError(t, err)
|
|
return pgtype.UUID{Bytes: deviceID, Valid: true}
|
|
}
|
|
|
|
func TestSyncIntegration_OfflineDetector_DeviceStatusDetection(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := setupSyncTestDB(t)
|
|
|
|
userID := createSyncTestUser(t, db)
|
|
deviceID := createSyncTestDevice(t, db, userID)
|
|
|
|
detector := sync.NewOfflineDetector(db, nil)
|
|
|
|
status, err := detector.GetDeviceStatus(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
assert.True(t, status.IsOnline, "device should be online initially")
|
|
assert.Equal(t, "Test Sync Device", status.DeviceName)
|
|
assert.Equal(t, "koreader", status.DeviceType)
|
|
}
|
|
|
|
func TestSyncIntegration_OfflineDetector_OfflineThreshold(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := setupSyncTestDB(t)
|
|
|
|
userID := createSyncTestUser(t, db)
|
|
deviceID := createSyncTestDevice(t, db, userID)
|
|
|
|
_, err := db.UpdateDeviceLastSeen(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
|
|
detector := sync.NewOfflineDetector(db, nil)
|
|
|
|
// Get device status
|
|
status, err := detector.GetDeviceStatus(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
assert.True(t, status.IsOnline, "device should be online initially")
|
|
}
|
|
|
|
func TestSyncIntegration_OfflineDetector_GetDeviceStatus(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := setupSyncTestDB(t)
|
|
|
|
userID := createSyncTestUser(t, db)
|
|
deviceID := createSyncTestDevice(t, db, userID)
|
|
|
|
detector := sync.NewOfflineDetector(db, nil)
|
|
|
|
status, err := detector.GetDeviceStatus(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, status)
|
|
assert.Equal(t, "Test Sync Device", status.DeviceName)
|
|
assert.Equal(t, "koreader", status.DeviceType)
|
|
}
|
|
|
|
func TestSyncIntegration_OfflineDetector_ForceReconnectDevice(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := setupSyncTestDB(t)
|
|
|
|
userID := createSyncTestUser(t, db)
|
|
deviceID := createSyncTestDevice(t, db, userID)
|
|
|
|
_, err := db.UpdateDeviceLastSeen(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
|
|
detector := sync.NewOfflineDetector(db, nil)
|
|
|
|
err = detector.ForceReconnectDevice(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
|
|
device, err := db.GetDevice(ctx, deviceID)
|
|
require.NoError(t, err)
|
|
assert.True(t, device.SyncEnabled.Bool, "device should be re-enabled after force reconnect")
|
|
}
|
|
|
|
func TestSyncIntegration_QueueProcessor_EnqueueProgress(t *testing.T) {
|
|
ctx := context.Background()
|
|
db := setupSyncTestDB(t)
|
|
|
|
processor := sync.NewSyncQueueProcessor(db)
|
|
|
|
userID := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
|
deviceID := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
|
mediaItemID := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
|
|
|
percentage := 0.45
|
|
chapter := 3
|
|
update := &sync.ProgressUpdate{
|
|
DeviceID: deviceID,
|
|
MediaItemID: mediaItemID,
|
|
UserID: userID,
|
|
Percentage: percentage,
|
|
Chapter: &chapter,
|
|
Source: "koreader",
|
|
SyncMode: "immediate",
|
|
}
|
|
|
|
err := processor.EnqueueProgress(update)
|
|
require.NoError(t, err, "should enqueue progress update")
|
|
|
|
items, err := db.ListPendingSyncQueueItems(ctx, database.ListPendingSyncQueueItemsParams{
|
|
DeviceID: deviceID,
|
|
Limit: 10,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Len(t, items, 1, "should have one queue item")
|
|
|
|
item := items[0]
|
|
assert.Equal(t, "progress", item.SyncType)
|
|
assert.Equal(t, sync.SyncStatusPending, item.Status.String)
|
|
assert.Equal(t, int32(sync.PriorityPageTurn), item.Priority.Int32)
|
|
}
|