package depot_test import ( "context" "os" "testing" "time" "github.com/flowy-live/llink/internal/depot" "github.com/flowy-live/llink/internal/testhelper" "github.com/jackc/pgx/v5/pgxpool" "github.com/stretchr/testify/assert" ) var dbPool *pgxpool.Pool func TestMain(m *testing.M) { dbPool = testhelper.SetupTestDB() defer testhelper.TeardownTestDB() ret := m.Run() os.Exit(ret) } // TestDepotRepository tests the repository layer directly // These tests can run without GCS since they only test database operations func TestDepotRepository_CreateAndGet(t *testing.T) { ctx := context.Background() // Create a depot object directly in the database for testing var id string err := dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_test123", "test-file.txt", "text/plain", int64(1024), "test-bucket", "test-key-123", false, ).Scan(&id) assert.NoError(t, err) assert.Equal(t, "dpo_test123", id) // Query the object back var obj struct { ID string Name string ContentType string ContentLength int64 BucketName string ObjectKey string ContainsContent bool CreatedAt time.Time } err = dbPool.QueryRow(ctx, `SELECT id, name, content_type, content_length, bucket_name, object_key, contains_content, created_at FROM depot_objects WHERE id = $1`, id, ).Scan(&obj.ID, &obj.Name, &obj.ContentType, &obj.ContentLength, &obj.BucketName, &obj.ObjectKey, &obj.ContainsContent, &obj.CreatedAt) assert.NoError(t, err) assert.Equal(t, "test-file.txt", obj.Name) assert.Equal(t, "text/plain", obj.ContentType) assert.Equal(t, int64(1024), obj.ContentLength) assert.Equal(t, "test-bucket", obj.BucketName) assert.Equal(t, "test-key-123", obj.ObjectKey) assert.False(t, obj.ContainsContent) assert.False(t, obj.CreatedAt.IsZero()) // Clean up _, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id) assert.NoError(t, err) } func TestDepotRepository_ConfirmUpload(t *testing.T) { ctx := context.Background() // Create a depot object var id string err := dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_confirm123", "confirm-file.txt", "text/plain", int64(2048), "test-bucket", "confirm-key", false, ).Scan(&id) assert.NoError(t, err) // Verify it starts with contains_content = false var containsContent bool err = dbPool.QueryRow(ctx, `SELECT contains_content FROM depot_objects WHERE id = $1`, id, ).Scan(&containsContent) assert.NoError(t, err) assert.False(t, containsContent) // Confirm upload _, err = dbPool.Exec(ctx, `UPDATE depot_objects SET contains_content = TRUE WHERE id = $1`, id, ) assert.NoError(t, err) // Verify it's now true err = dbPool.QueryRow(ctx, `SELECT contains_content FROM depot_objects WHERE id = $1`, id, ).Scan(&containsContent) assert.NoError(t, err) assert.True(t, containsContent) // Clean up _, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id) assert.NoError(t, err) } func TestDepotRepository_Delete(t *testing.T) { ctx := context.Background() // Create a depot object var id string err := dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_delete123", "delete-file.txt", "text/plain", int64(512), "test-bucket", "delete-key", false, ).Scan(&id) assert.NoError(t, err) // Delete it result, err := dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id) assert.NoError(t, err) assert.Equal(t, int64(1), result.RowsAffected()) // Verify it's gone var count int err = dbPool.QueryRow(ctx, `SELECT COUNT(*) FROM depot_objects WHERE id = $1`, id, ).Scan(&count) assert.NoError(t, err) assert.Equal(t, 0, count) } func TestDepotRepository_Exists(t *testing.T) { ctx := context.Background() // Create a depot object var id string err := dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_exists123", "exists-file.txt", "text/plain", int64(256), "test-bucket", "exists-key", true, ).Scan(&id) assert.NoError(t, err) // Check exists var exists bool err = dbPool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM depot_objects WHERE id = $1)`, id, ).Scan(&exists) assert.NoError(t, err) assert.True(t, exists) // Check non-existent err = dbPool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM depot_objects WHERE id = $1)`, "dpo_nonexistent", ).Scan(&exists) assert.NoError(t, err) assert.False(t, exists) // Clean up _, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id = $1`, id) assert.NoError(t, err) } func TestDepotRepository_OrphanedIndex(t *testing.T) { ctx := context.Background() // Create an orphaned object (contains_content = false) var orphanID string err := dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_orphan123", "orphan-file.txt", "text/plain", int64(128), "test-bucket", "orphan-key", false, ).Scan(&orphanID) assert.NoError(t, err) // Create a confirmed object (contains_content = true) var confirmedID string err = dbPool.QueryRow(ctx, `INSERT INTO depot_objects (id, name, content_type, content_length, bucket_name, object_key, contains_content) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, "dpo_confirmed123", "confirmed-file.txt", "text/plain", int64(128), "test-bucket", "confirmed-key", true, ).Scan(&confirmedID) assert.NoError(t, err) // Query orphaned objects using the index rows, err := dbPool.Query(ctx, `SELECT id FROM depot_objects WHERE contains_content = FALSE`) assert.NoError(t, err) defer rows.Close() var orphanedIDs []string for rows.Next() { var id string err := rows.Scan(&id) assert.NoError(t, err) orphanedIDs = append(orphanedIDs, id) } // Our orphan should be in the list assert.Contains(t, orphanedIDs, orphanID) assert.NotContains(t, orphanedIDs, confirmedID) // Clean up _, err = dbPool.Exec(ctx, `DELETE FROM depot_objects WHERE id IN ($1, $2)`, orphanID, confirmedID) assert.NoError(t, err) } // TestDepotModels tests the model structures func TestDepotModels(t *testing.T) { // Test Config defaults config := depot.Config{ BucketName: "test-bucket", } assert.Equal(t, "test-bucket", config.BucketName) assert.Equal(t, time.Duration(0), config.UploadURLExpiry) assert.Equal(t, time.Duration(0), config.DownloadURLExpiry) // Test with explicit values config = depot.Config{ BucketName: "custom-bucket", UploadURLExpiry: 10 * time.Minute, DownloadURLExpiry: 12 * time.Hour, } assert.Equal(t, "custom-bucket", config.BucketName) assert.Equal(t, 10*time.Minute, config.UploadURLExpiry) assert.Equal(t, 12*time.Hour, config.DownloadURLExpiry) } // TestDepotErrors tests the error definitions func TestDepotErrors(t *testing.T) { assert.Error(t, depot.ErrNotFound) assert.Error(t, depot.ErrInvalidInput) assert.Equal(t, "object not found", depot.ErrNotFound.Error()) assert.Equal(t, "invalid input", depot.ErrInvalidInput.Error()) }