Files
query-orchestration/internal/document/delete_test.go
T
Jay Brown ad7b21f3a2 Merged in bugfix/delete-client-cascade (pull request #220)
fix cascading delete issue

* test passing
2026-04-08 10:54:32 +00:00

220 lines
6.2 KiB
Go

package document_test
import (
"errors"
"testing"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/document"
"queryorchestration/internal/fieldextraction"
"queryorchestration/internal/harddelete"
"queryorchestration/internal/test"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// createTestDocumentWithDeps creates a document with entries, cleans, and field extractions.
// Returns the document ID for use in delete tests.
func createTestDocumentWithDeps(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)
// Add a document entry (S3 reference)
err = q.AddDocumentEntry(ctx, &repository.AddDocumentEntryParams{
Documentid: docID,
Bucket: "test-bucket",
Key: "test-key/" + hash,
})
require.NoError(t, err)
// Add a document clean (must satisfy location_xor_fail constraint)
cleanBucket := "clean-bucket"
cleanKey := "clean-key/" + hash
cleanHash := "cleanhash-" + hash
cleanID, err := q.AddDocumentClean(ctx, &repository.AddDocumentCleanParams{
Documentid: docID,
Bucket: &cleanBucket,
Key: &cleanKey,
Hash: &cleanHash,
Mimetype: repository.NullCleanmimetype{
Cleanmimetype: repository.CleanmimetypeApplicationPdf,
Valid: true,
},
})
require.NoError(t, err)
// Add a clean entry
err = q.AddDocumentCleanEntry(ctx, &repository.AddDocumentCleanEntryParams{
Cleanid: cleanID,
Version: 1,
})
require.NoError(t, err)
// Add field extraction
fieldSvc := fieldextraction.New(cfg)
contractTitle := "Test Contract " + hash
input := &fieldextraction.CreateFieldExtractionInput{
DocumentID: docID,
SingleFields: &repository.AddFieldExtractionParams{
Documentid: docID,
Filename: &filename,
Contracttitle: &contractTitle,
Createdby: "testuser",
},
ArrayFields: []*repository.AddFieldExtractionArrayFieldParams{},
}
_, err = fieldSvc.CreateFieldExtraction(ctx, input)
require.NoError(t, err)
return docID
}
func TestHardDeleteDocument(t *testing.T) {
ctx := t.Context()
cfg := &TestConfig{}
test.CreateDB(t, cfg)
clientID := "test-doc-delete"
test.CreateTestClient(t, cfg, clientID, "Test Doc Delete")
svc := document.New(cfg)
t.Run("deletes document and all dependent rows", func(t *testing.T) {
q := cfg.GetDBQueries()
rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{
Clientid: clientID,
Path: "/",
})
require.NoError(t, err)
docID := createTestDocumentWithDeps(t, cfg, clientID, &rootFolder.ID, "delete-cascade-test")
// Verify document and deps exist before delete
_, err = q.GetDocumentSummary(ctx, docID)
require.NoError(t, err)
s3Rows, err := q.CollectDocumentS3Paths(ctx, docID)
require.NoError(t, err)
assert.Len(t, s3Rows, 1)
// Delete the document
s3Paths, err := svc.HardDelete(ctx, docID)
require.NoError(t, err)
// Verify S3 paths returned
assert.Len(t, s3Paths, 1)
assert.Equal(t, "test-bucket", s3Paths[0].Bucket)
assert.Equal(t, "test-key/delete-cascade-test", s3Paths[0].Key)
assert.Equal(t, docID, s3Paths[0].DocumentID)
// Verify document is gone
_, err = q.GetDocumentSummary(ctx, docID)
require.Error(t, err)
// Verify document entries are gone
s3Rows, err = q.CollectDocumentS3Paths(ctx, docID)
require.NoError(t, err)
assert.Empty(t, s3Rows)
// Verify field extractions are gone
extractions, err := q.GetFieldExtractionsByDocumentID(ctx, docID)
require.NoError(t, err)
assert.Empty(t, extractions)
})
t.Run("returns NotFoundError for non-existent document", func(t *testing.T) {
fakeID := uuid.New()
_, err := svc.HardDelete(ctx, fakeID)
require.Error(t, err)
var notFoundErr *harddelete.NotFoundError
assert.True(t, errors.As(err, &notFoundErr))
})
t.Run("deletes document that has batch outcome rows", func(t *testing.T) {
q := cfg.GetDBQueries()
// Use a unique client to avoid collisions with other subtests
batchClientID := "test-doc-batch-del"
test.CreateTestClient(t, cfg, batchClientID, "Test Doc Batch Del")
rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{
Clientid: batchClientID,
Path: "/",
})
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 linked to the batch
filename := "test.pdf"
docID, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{
Clientid: batchClientID,
Hash: "batch-doc-del-test",
Filename: &filename,
Folderid: &rootFolder.ID,
BatchID: &batchID,
})
require.NoError(t, err)
// Insert a submitted outcome referencing this document
err = q.InsertBatchDocumentOutcome(ctx, &repository.InsertBatchDocumentOutcomeParams{
BatchID: batchID,
Filename: "test.pdf",
Column3: repository.BatchOutcomeStatusSubmitted,
DocumentID: &docID,
})
require.NoError(t, err)
// Hard delete the document -- should fail before FK fix
s3Paths, err := svc.HardDelete(ctx, docID)
require.NoError(t, err)
// Verify document is gone
_ = s3Paths
_, err = q.GetDocumentSummary(ctx, docID)
require.Error(t, err)
// Verify the batch outcome row still exists but document_id is NULL
// (history preserved via SET NULL on FK)
rows, err := q.ListBatchDocumentOutcomes(ctx, batchID)
require.NoError(t, err)
require.Len(t, rows, 1)
assert.Nil(t, rows[0].DocumentID)
})
t.Run("returns empty S3 paths when document has no entries", func(t *testing.T) {
q := cfg.GetDBQueries()
// Create a document with no entries
docID, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{
Clientid: clientID,
Hash: "no-entries-test",
})
require.NoError(t, err)
s3Paths, err := svc.HardDelete(ctx, docID)
require.NoError(t, err)
assert.Empty(t, s3Paths)
})
}