338 lines
11 KiB
Go
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)
|
|
}
|