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

338 lines
11 KiB
Go

package domain
import (
"context"
"os"
"path/filepath"
"testing"
"time"
archivalStore "git.nakama.town/fmartingr/hako/internal/archival/store"
authDomain "git.nakama.town/fmartingr/hako/internal/auth/domain"
authStore "git.nakama.town/fmartingr/hako/internal/auth/store"
"git.nakama.town/fmartingr/hako/internal/database"
"git.nakama.town/fmartingr/hako/internal/model"
"git.nakama.town/fmartingr/hako/internal/testutil"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupTestLinkService(t *testing.T) (*LinkDomain, *archivalStore.ArchiveStore, *archivalStore.ArchiveFileStore, *archivalStore.LinkCategoryStore, *database.Connections, string, string, func()) {
t.Helper()
ctx := context.Background()
testDeps := testutil.GetTestConfigurationAndDependencies(t, ctx)
// Initialize domains (must be done in test to avoid import cycle)
testDeps.Dependencies.Domains().SetLinks(NewLinkDomain(testDeps.Dependencies))
testDeps.Dependencies.Domains().SetArchives(NewArchiveDomain(testDeps.Dependencies))
testDeps.Dependencies.Domains().SetCategories(NewCategoryDomain(testDeps.Dependencies))
testDeps.Dependencies.Domains().SetAuth(authDomain.NewAuthDomain(testDeps.Dependencies))
// Create a test user
userUUID := uuid.New()
user := &authStore.User{
ID: userUUID,
Email: "test@example.com",
PasswordHash: "test-hash",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err := testDeps.Dependencies.UserStore.Create(user)
require.NoError(t, err)
linkService := testDeps.Dependencies.Domains().Links().(*LinkDomain)
return linkService, testDeps.ArchiveStore, testDeps.ArchiveFileStore, testDeps.LinkCategoryStore, testDeps.DBConnections, userUUID.String(), testDeps.Config.ArchiveStoragePath, func() {}
}
func TestLinkService_DeleteLink_CascadeDelete(t *testing.T) {
ctx := context.Background()
linkService, archiveStore, archiveFileStore, linkCategoryStore, conns, userID, storageDir, cleanup := setupTestLinkService(t)
defer cleanup()
// Create a link
link, err := linkService.CreateLink(ctx, "https://example.com/test", userID)
require.NoError(t, err)
// Wait a bit for the archive job to be created, then manually create a completed archive
// For testing, let's create a completed archive manually
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: link.ID,
UserID: userID,
Status: model.ArchiveStatusCompleted,
Title: "Test Archive",
CreatedAt: time.Now(),
}
err = archiveStore.Create(ctx, archive)
require.NoError(t, err)
// Create archive files
file1 := &model.ArchiveFile{
ID: uuid.New().String(),
ArchiveID: archive.ID,
ArchiverKey: "direct_download",
Filename: "test1.txt",
MimeType: "text/plain",
FileSize: 100,
StoragePath: model.GetPathForLinkFile(link.ID, "test1.txt"),
HashSha256: "abc123",
CreatedAt: time.Now(),
}
err = archiveFileStore.Create(ctx, file1)
require.NoError(t, err)
file2 := &model.ArchiveFile{
ID: uuid.New().String(),
ArchiveID: archive.ID,
ArchiverKey: "direct_download",
Filename: "test2.txt",
MimeType: "text/plain",
FileSize: 200,
StoragePath: model.GetPathForLinkFile(link.ID, "test2.txt"),
HashSha256: "def456",
CreatedAt: time.Now(),
}
err = archiveFileStore.Create(ctx, file2)
require.NoError(t, err)
// Create storage files
file1Path := filepath.Join(storageDir, file1.StoragePath)
file2Path := filepath.Join(storageDir, file2.StoragePath)
err = os.MkdirAll(filepath.Dir(file1Path), 0755)
require.NoError(t, err)
err = os.WriteFile(file1Path, []byte("test content 1"), 0644)
require.NoError(t, err)
err = os.WriteFile(file2Path, []byte("test content 2"), 0644)
require.NoError(t, err)
// Create a category and link it (insert directly into database)
categoryID := uuid.New().String()
now := time.Now().Format("2006-01-02 15:04:05")
_, err = conns.Write.ExecContext(ctx, `
INSERT INTO categories (id, name, icon, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
`, categoryID, "Test Category", "test-icon", now, now)
require.NoError(t, err)
err = linkCategoryStore.AddCategory(ctx, link.ID, categoryID)
require.NoError(t, err)
// Verify everything exists before deletion
// Note: CreateLink creates an archive automatically, so we have 2 archives total
archives, err := archiveStore.ListByLinkID(ctx, archivalStore.ArchiveListOptions{LinkID: link.ID})
require.NoError(t, err)
assert.Len(t, archives, 2) // One from CreateLink + one we created manually
files, err := archiveFileStore.ListByArchiveID(ctx, archivalStore.ArchiveFileListOptions{ArchiveID: archive.ID})
require.NoError(t, err)
assert.Len(t, files, 2)
categories, err := linkCategoryStore.GetCategoriesByLinkID(ctx, link.ID)
require.NoError(t, err)
assert.Len(t, categories, 1)
// Verify storage files exist
_, err = os.Stat(file1Path)
require.NoError(t, err)
_, err = os.Stat(file2Path)
require.NoError(t, err)
// Delete the link
err = linkService.DeleteLink(ctx, link.ID)
require.NoError(t, err)
// Verify link is deleted
_, err = linkService.GetLink(ctx, link.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
// Verify archives are deleted
archives, err = archiveStore.ListByLinkID(ctx, archivalStore.ArchiveListOptions{LinkID: link.ID})
require.NoError(t, err)
assert.Len(t, archives, 0)
// Verify archive files are deleted
files, err = archiveFileStore.ListByArchiveID(ctx, archivalStore.ArchiveFileListOptions{ArchiveID: archive.ID})
require.NoError(t, err)
assert.Len(t, files, 0)
// Verify link categories are deleted
categories, err = linkCategoryStore.GetCategoriesByLinkID(ctx, link.ID)
require.NoError(t, err)
assert.Len(t, categories, 0)
// Verify storage files are deleted
_, err = os.Stat(file1Path)
assert.True(t, os.IsNotExist(err))
_, err = os.Stat(file2Path)
assert.True(t, os.IsNotExist(err))
}
func TestLinkService_DeleteLink_MultipleArchives(t *testing.T) {
ctx := context.Background()
linkService, archiveStore, archiveFileStore, _, _, userID, storageDir, cleanup := setupTestLinkService(t)
defer cleanup()
// Create a link
link, err := linkService.CreateLink(ctx, "https://example.com/test", userID)
require.NoError(t, err)
// Create multiple archives
archive1 := &model.Archive{
ID: uuid.New().String(),
LinkID: link.ID,
UserID: userID,
Status: model.ArchiveStatusCompleted,
Title: "Archive 1",
CreatedAt: time.Now(),
}
err = archiveStore.Create(ctx, archive1)
require.NoError(t, err)
archive2 := &model.Archive{
ID: uuid.New().String(),
LinkID: link.ID,
UserID: userID,
Status: model.ArchiveStatusCompleted,
Title: "Archive 2",
CreatedAt: time.Now(),
}
err = archiveStore.Create(ctx, archive2)
require.NoError(t, err)
// Create files for each archive
file1 := &model.ArchiveFile{
ID: uuid.New().String(),
ArchiveID: archive1.ID,
ArchiverKey: "direct_download",
Filename: "file1.txt",
MimeType: "text/plain",
FileSize: 100,
StoragePath: model.GetPathForLinkFile(link.ID, "file1.txt"),
HashSha256: "hash1",
CreatedAt: time.Now(),
}
err = archiveFileStore.Create(ctx, file1)
require.NoError(t, err)
file2 := &model.ArchiveFile{
ID: uuid.New().String(),
ArchiveID: archive2.ID,
ArchiverKey: "direct_download",
Filename: "file2.txt",
MimeType: "text/plain",
FileSize: 200,
StoragePath: model.GetPathForLinkFile(link.ID, "file2.txt"),
HashSha256: "hash2",
CreatedAt: time.Now(),
}
err = archiveFileStore.Create(ctx, file2)
require.NoError(t, err)
// Create storage files
file1Path := filepath.Join(storageDir, file1.StoragePath)
file2Path := filepath.Join(storageDir, file2.StoragePath)
err = os.MkdirAll(filepath.Dir(file1Path), 0755)
require.NoError(t, err)
err = os.WriteFile(file1Path, []byte("content 1"), 0644)
require.NoError(t, err)
err = os.WriteFile(file2Path, []byte("content 2"), 0644)
require.NoError(t, err)
// Verify both archives exist
// Note: CreateLink creates an archive automatically, so we have 3 archives total
archives, err := archiveStore.ListByLinkID(ctx, archivalStore.ArchiveListOptions{LinkID: link.ID})
require.NoError(t, err)
assert.Len(t, archives, 3) // One from CreateLink + two we created manually
// Delete the link
err = linkService.DeleteLink(ctx, link.ID)
require.NoError(t, err)
// Verify all archives are deleted
archives, err = archiveStore.ListByLinkID(ctx, archivalStore.ArchiveListOptions{LinkID: link.ID})
require.NoError(t, err)
assert.Len(t, archives, 0)
// Verify all files are deleted
files1, err := archiveFileStore.ListByArchiveID(ctx, archivalStore.ArchiveFileListOptions{ArchiveID: archive1.ID})
require.NoError(t, err)
assert.Len(t, files1, 0)
files2, err := archiveFileStore.ListByArchiveID(ctx, archivalStore.ArchiveFileListOptions{ArchiveID: archive2.ID})
require.NoError(t, err)
assert.Len(t, files2, 0)
// Verify storage files are deleted
_, err = os.Stat(file1Path)
assert.True(t, os.IsNotExist(err))
_, err = os.Stat(file2Path)
assert.True(t, os.IsNotExist(err))
}
func TestLinkService_DeleteLink_NoArchives(t *testing.T) {
ctx := context.Background()
linkService, _, _, _, _, userID, _, cleanup := setupTestLinkService(t)
defer cleanup()
// Create a link
link, err := linkService.CreateLink(ctx, "https://example.com/test", userID)
require.NoError(t, err)
// Delete the link (should work even with no archives)
err = linkService.DeleteLink(ctx, link.ID)
require.NoError(t, err)
// Verify link is deleted
_, err = linkService.GetLink(ctx, link.ID)
assert.Error(t, err)
}
func TestLinkService_DeleteLink_StorageFileNotFound(t *testing.T) {
ctx := context.Background()
linkService, archiveStore, archiveFileStore, _, _, userID, _, cleanup := setupTestLinkService(t)
defer cleanup()
// Create a link
link, err := linkService.CreateLink(ctx, "https://example.com/test", userID)
require.NoError(t, err)
// Create an archive
archive := &model.Archive{
ID: uuid.New().String(),
LinkID: link.ID,
UserID: userID,
Status: model.ArchiveStatusCompleted,
Title: "Test Archive",
CreatedAt: time.Now(),
}
err = archiveStore.Create(ctx, archive)
require.NoError(t, err)
// Create archive file with storage path, but don't create the actual file
file := &model.ArchiveFile{
ID: uuid.New().String(),
ArchiveID: archive.ID,
ArchiverKey: "direct_download",
Filename: "missing.txt",
MimeType: "text/plain",
FileSize: 100,
StoragePath: model.GetPathForLinkFile(link.ID, "missing.txt"),
HashSha256: "hash",
CreatedAt: time.Now(),
}
err = archiveFileStore.Create(ctx, file)
require.NoError(t, err)
// Delete the link - should succeed even if storage file doesn't exist
err = linkService.DeleteLink(ctx, link.ID)
require.NoError(t, err)
// Verify everything is deleted
archives, err := archiveStore.ListByLinkID(ctx, archivalStore.ArchiveListOptions{LinkID: link.ID})
require.NoError(t, err)
assert.Len(t, archives, 0)
}