ad7b21f3a2
fix cascading delete issue * test passing
224 lines
6.7 KiB
Go
224 lines
6.7 KiB
Go
package folder_test
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"queryorchestration/internal/database/repository"
|
|
"queryorchestration/internal/folder"
|
|
"queryorchestration/internal/harddelete"
|
|
"queryorchestration/internal/test"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createDocWithEntry creates a document with an S3 entry in the given folder.
|
|
// Returns the document ID.
|
|
func createDocWithEntry(t *testing.T, cfg *TestConfig, clientID string, folderID *uuid.UUID, hash string) uuid.UUID {
|
|
t.Helper()
|
|
ctx := t.Context()
|
|
q := cfg.GetDBQueries()
|
|
|
|
filename := hash + ".pdf"
|
|
docID, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{
|
|
Clientid: clientID,
|
|
Hash: hash,
|
|
Filename: &filename,
|
|
Folderid: folderID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
err = q.AddDocumentEntry(ctx, &repository.AddDocumentEntryParams{
|
|
Documentid: docID,
|
|
Bucket: "test-bucket",
|
|
Key: "test-key/" + hash,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return docID
|
|
}
|
|
|
|
func TestHardDeleteFolder(t *testing.T) {
|
|
ctx := t.Context()
|
|
cfg := &TestConfig{}
|
|
test.CreateDB(t, cfg)
|
|
|
|
clientID := "test-folder-delete"
|
|
test.CreateTestClient(t, cfg, clientID, "Test Folder Delete")
|
|
|
|
svc := folder.New(cfg)
|
|
q := cfg.GetDBQueries()
|
|
|
|
// Get root folder for this client
|
|
rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{
|
|
Clientid: clientID,
|
|
Path: "/",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("rejects deleting root folder", func(t *testing.T) {
|
|
_, err := svc.HardDelete(ctx, rootFolder.ID, false)
|
|
require.Error(t, err)
|
|
|
|
var conflictErr *harddelete.ConflictError
|
|
assert.True(t, errors.As(err, &conflictErr))
|
|
assert.Contains(t, conflictErr.Message, "root folder")
|
|
})
|
|
|
|
t.Run("returns NotFoundError for non-existent folder", func(t *testing.T) {
|
|
fakeID := uuid.New()
|
|
_, err := svc.HardDelete(ctx, fakeID, false)
|
|
require.Error(t, err)
|
|
|
|
var notFoundErr *harddelete.NotFoundError
|
|
assert.True(t, errors.As(err, ¬FoundErr))
|
|
})
|
|
|
|
t.Run("deletes empty folder tree", func(t *testing.T) {
|
|
parent, err := svc.CreateFolder(ctx, "/del-empty", &rootFolder.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
child, err := svc.CreateFolder(ctx, "/del-empty/child", &parent.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
s3Paths, err := svc.HardDelete(ctx, parent.ID, false)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, s3Paths)
|
|
|
|
// Verify both folders are gone
|
|
_, err = q.GetFolderByID(ctx, parent.ID)
|
|
require.Error(t, err)
|
|
_, err = q.GetFolderByID(ctx, child.ID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("conflicts when folder has documents and include_documents is false", func(t *testing.T) {
|
|
fdr, err := svc.CreateFolder(ctx, "/del-has-docs", &rootFolder.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
createDocWithEntry(t, cfg, clientID, &fdr.ID, "folder-doc-conflict")
|
|
|
|
_, err = svc.HardDelete(ctx, fdr.ID, false)
|
|
require.Error(t, err)
|
|
|
|
var conflictErr *harddelete.ConflictError
|
|
assert.True(t, errors.As(err, &conflictErr))
|
|
assert.Contains(t, conflictErr.Message, "documents")
|
|
})
|
|
|
|
t.Run("deletes folder tree with documents when include_documents is true", func(t *testing.T) {
|
|
parent, err := svc.CreateFolder(ctx, "/del-with-docs", &rootFolder.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
child, err := svc.CreateFolder(ctx, "/del-with-docs/child", &parent.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
doc1ID := createDocWithEntry(t, cfg, clientID, &parent.ID, "parent-doc-1")
|
|
doc2ID := createDocWithEntry(t, cfg, clientID, &child.ID, "child-doc-1")
|
|
|
|
s3Paths, err := svc.HardDelete(ctx, parent.ID, true)
|
|
require.NoError(t, err)
|
|
|
|
// Verify S3 paths returned for both documents
|
|
assert.Len(t, s3Paths, 2)
|
|
|
|
// Verify both documents are gone
|
|
_, err = q.GetDocumentSummary(ctx, doc1ID)
|
|
require.Error(t, err)
|
|
_, err = q.GetDocumentSummary(ctx, doc2ID)
|
|
require.Error(t, err)
|
|
|
|
// Verify folders are gone
|
|
_, err = q.GetFolderByID(ctx, parent.ID)
|
|
require.Error(t, err)
|
|
_, err = q.GetFolderByID(ctx, child.ID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("deletes folder with batch-backed documents and outcomes", func(t *testing.T) {
|
|
// Use a unique client to avoid collisions
|
|
batchClientID := "test-folder-batch-del"
|
|
test.CreateTestClient(t, cfg, batchClientID, "Test Folder Batch Del")
|
|
|
|
batchRootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{
|
|
Clientid: batchClientID,
|
|
Path: "/",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
batchSvc := folder.New(cfg)
|
|
|
|
// Create a subfolder under root
|
|
subFolder, err := batchSvc.CreateFolder(ctx, "/batch-del-sub", &batchRootFolder.ID, batchClientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create a batch upload for the client
|
|
batchID, err := q.CreateBatchUpload(ctx, &repository.CreateBatchUploadParams{
|
|
ClientID: batchClientID,
|
|
OriginalFilename: "test.zip",
|
|
TotalDocuments: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Create a document in the subfolder with batch_id
|
|
filename := "batch-folder-doc.pdf"
|
|
docID, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{
|
|
Clientid: batchClientID,
|
|
Hash: "batch-folder-doc-hash",
|
|
Filename: &filename,
|
|
Folderid: &subFolder.ID,
|
|
BatchID: &batchID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Add a document entry (S3 ref)
|
|
err = q.AddDocumentEntry(ctx, &repository.AddDocumentEntryParams{
|
|
Documentid: docID,
|
|
Bucket: "test-bucket",
|
|
Key: "test-key/batch-folder-doc-hash",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Insert a resolved outcome with document_id set
|
|
err = q.InsertBatchDocumentOutcome(ctx, &repository.InsertBatchDocumentOutcomeParams{
|
|
BatchID: batchID,
|
|
Filename: "batch-folder-doc.pdf",
|
|
Column3: repository.BatchOutcomeStatusInitComplete,
|
|
DocumentID: &docID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Hard delete the folder with includeDocuments=true -- should fail before FK fix
|
|
_, err = batchSvc.HardDelete(ctx, subFolder.ID, true)
|
|
require.NoError(t, err)
|
|
|
|
// Verify folder is gone
|
|
_, err = q.GetFolderByID(ctx, subFolder.ID)
|
|
require.Error(t, err)
|
|
|
|
// Verify document is gone
|
|
_, err = q.GetDocumentSummary(ctx, docID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("counts documents in nested folder tree correctly", func(t *testing.T) {
|
|
parent, err := svc.CreateFolder(ctx, "/del-count", &rootFolder.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
child, err := svc.CreateFolder(ctx, "/del-count/sub", &parent.ID, clientID, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Document in child, not parent
|
|
createDocWithEntry(t, cfg, clientID, &child.ID, "nested-doc-count")
|
|
|
|
// Should conflict because the tree has a document (in the child)
|
|
_, err = svc.HardDelete(ctx, parent.ID, false)
|
|
require.Error(t, err)
|
|
|
|
var conflictErr *harddelete.ConflictError
|
|
assert.True(t, errors.As(err, &conflictErr))
|
|
})
|
|
}
|