hako/internal/archival/store/archive_store_test.go
2026-01-12 19:35:18 +01:00

272 lines
7.6 KiB
Go

package store
import (
"context"
"os"
"testing"
"time"
authStore "git.nakama.town/fmartingr/hako/internal/auth/store"
"git.nakama.town/fmartingr/hako/internal/database"
"git.nakama.town/fmartingr/hako/internal/model"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func setupTestArchiveStore(t *testing.T) (*ArchiveStore, string, string, func()) {
t.Helper()
// Create a temporary database file
tmpDB, err := os.CreateTemp("", "test_archive_store_*.db")
require.NoError(t, err)
_ = tmpDB.Close()
dbURL := "sqlite:" + tmpDB.Name()
// Create database connections
conns, err := database.NewConnections(dbURL, dbURL)
require.NoError(t, err)
// Run migrations
err = database.InitSchema(conns)
require.NoError(t, err)
// Create stores
userStore := authStore.NewUserStore(conns.Read, conns.Write)
linkStore := NewLinkStore(conns.Read, conns.Write)
archiveStore := NewArchiveStore(conns.Read, conns.Write)
// Create a test user
ctx := context.Background()
userUUID := uuid.New()
user := &authStore.User{
ID: userUUID,
Email: "test@example.com",
PasswordHash: "test-hash",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = userStore.Create(user)
require.NoError(t, err)
// Create a test link
userID := userUUID.String()
linkID := uuid.New().String()
link := &model.Link{
ID: linkID,
URL: "https://example.com",
UserID: userID,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = linkStore.Create(ctx, link)
require.NoError(t, err)
cleanup := func() {
_ = conns.Close()
_ = os.Remove(tmpDB.Name())
_ = os.Remove(tmpDB.Name() + "-wal")
_ = os.Remove(tmpDB.Name() + "-shm")
}
return archiveStore, userID, linkID, cleanup
}
func TestArchiveStore_Create(t *testing.T) {
store, userID, linkID, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
now := time.Now()
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusPending,
ErrorMessage: "",
CreatedAt: now,
}
err := store.Create(ctx, archive)
require.NoError(t, err)
// Verify it was created
retrieved, err := store.GetByID(ctx, archive.ID)
require.NoError(t, err)
require.Equal(t, archive.ID, retrieved.ID)
require.Equal(t, archive.LinkID, retrieved.LinkID)
require.Equal(t, archive.UserID, retrieved.UserID)
require.Equal(t, archive.Status, retrieved.Status)
}
func TestArchiveStore_UpdateStatus_ToCompleted(t *testing.T) {
store, userID, linkID, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
now := time.Now()
// Create an archive in pending status
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusPending,
ErrorMessage: "",
CreatedAt: now,
}
err := store.Create(ctx, archive)
require.NoError(t, err)
// Update to processing
err = store.UpdateStatus(ctx, archive.ID, model.ArchiveStatusProcessing, "")
require.NoError(t, err)
// Verify processing status
retrieved, err := store.GetByID(ctx, archive.ID)
require.NoError(t, err)
require.Equal(t, model.ArchiveStatusProcessing, retrieved.Status)
require.Empty(t, retrieved.ErrorMessage)
require.Nil(t, retrieved.CompletedAt, "CompletedAt should be nil for processing status")
// Update to completed
err = store.UpdateStatus(ctx, archive.ID, model.ArchiveStatusCompleted, "")
require.NoError(t, err)
// Verify completed status - THIS IS THE KEY TEST THAT WOULD HAVE CAUGHT THE BUG
retrieved, err = store.GetByID(ctx, archive.ID)
require.NoError(t, err)
require.Equal(t, model.ArchiveStatusCompleted, retrieved.Status, "Status should be completed")
require.Empty(t, retrieved.ErrorMessage, "Error message should be empty")
require.NotNil(t, retrieved.CompletedAt, "CompletedAt should be set for completed status")
// Verify the completed_at timestamp is recent (within last 2 hours to account for timezone differences)
// The key point is that it's set at all, not the exact timestamp
require.WithinDuration(t, time.Now().UTC(), retrieved.CompletedAt.UTC(), 2*time.Hour)
}
func TestArchiveStore_UpdateStatus_ToFailed(t *testing.T) {
store, userID, linkID, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
now := time.Now()
// Create an archive
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusPending,
ErrorMessage: "",
CreatedAt: now,
}
err := store.Create(ctx, archive)
require.NoError(t, err)
// Update to failed with error message
errorMsg := "extractor not found: test_extractor"
err = store.UpdateStatus(ctx, archive.ID, model.ArchiveStatusFailed, errorMsg)
require.NoError(t, err)
// Verify failed status
retrieved, err := store.GetByID(ctx, archive.ID)
require.NoError(t, err)
require.Equal(t, model.ArchiveStatusFailed, retrieved.Status, "Status should be failed")
require.Equal(t, errorMsg, retrieved.ErrorMessage, "Error message should be set")
require.NotNil(t, retrieved.CompletedAt, "CompletedAt should be set for failed status")
}
func TestArchiveStore_UpdateStatus_ClearsErrorMessage(t *testing.T) {
store, userID, linkID, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
now := time.Now()
// Create an archive with an error
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusFailed,
ErrorMessage: "previous error",
CreatedAt: now,
}
err := store.Create(ctx, archive)
require.NoError(t, err)
// Update to completed (should clear error message)
err = store.UpdateStatus(ctx, archive.ID, model.ArchiveStatusCompleted, "")
require.NoError(t, err)
// Verify error message was cleared
retrieved, err := store.GetByID(ctx, archive.ID)
require.NoError(t, err)
require.Equal(t, model.ArchiveStatusCompleted, retrieved.Status)
require.Empty(t, retrieved.ErrorMessage, "Error message should be cleared when updating to completed")
}
func TestArchiveStore_ListByLinkID(t *testing.T) {
store, userID, linkID, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
// Create multiple archives for the same link
archive1 := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusCompleted,
CreatedAt: time.Now().Add(-2 * time.Hour),
}
archive2 := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusPending,
CreatedAt: time.Now().Add(-1 * time.Hour),
}
archive3 := &model.Archive{
ID: uuid.New().String(),
LinkID: linkID,
UserID: userID,
Status: model.ArchiveStatusProcessing,
CreatedAt: time.Now(),
}
err := store.Create(ctx, archive1)
require.NoError(t, err)
err = store.Create(ctx, archive2)
require.NoError(t, err)
err = store.Create(ctx, archive3)
require.NoError(t, err)
// List archives
opts := ArchiveListOptions{LinkID: linkID}
archives, err := store.ListByLinkID(ctx, opts)
require.NoError(t, err)
require.Len(t, archives, 3)
// Should be ordered by created_at DESC (newest first)
require.Equal(t, archive3.ID, archives[0].ID)
require.Equal(t, archive2.ID, archives[1].ID)
require.Equal(t, archive1.ID, archives[2].ID)
}
func TestArchiveStore_GetByID_NotFound(t *testing.T) {
store, _, _, cleanup := setupTestArchiveStore(t)
defer cleanup()
ctx := context.Background()
_, err := store.GetByID(ctx, "non-existent-id")
require.Error(t, err)
require.Contains(t, err.Error(), "archive not found")
}