package client_test import ( "errors" "testing" "queryorchestration/internal/client" "queryorchestration/internal/database/repository" "queryorchestration/internal/harddelete" "queryorchestration/internal/test" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // createDocWithEntryForClient creates a document with an S3 entry in the given folder for a client. func createDocWithEntryForClient(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: "clients/" + clientID + "/" + hash, }) require.NoError(t, err) return docID } func TestHardDeleteClient(t *testing.T) { ctx := t.Context() cfg := &TestConfig{} test.CreateDB(t, cfg) svc := client.New(cfg) q := cfg.GetDBQueries() t.Run("deletes client and all associated data", func(t *testing.T) { clientID := "test-client-hard-delete" test.CreateTestClient(t, cfg, clientID, "Test Client Hard Delete") // Get root folder rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{ Clientid: clientID, Path: "/", }) require.NoError(t, err) // Create documents with S3 entries doc1ID := createDocWithEntryForClient(t, cfg, clientID, &rootFolder.ID, "client-del-doc1") doc2ID := createDocWithEntryForClient(t, cfg, clientID, &rootFolder.ID, "client-del-doc2") // Create collector data versionID, err := q.AddLatestCollectorVersion(ctx, clientID) require.NoError(t, err) err = q.SetActiveCollectorVersion(ctx, &repository.SetActiveCollectorVersionParams{ Clientid: clientID, Versionid: versionID, }) require.NoError(t, err) err = q.SetCollectorCleanVersion(ctx, &repository.SetCollectorCleanVersionParams{ Clientid: clientID, Addedversion: versionID, Versionid: 1, }) require.NoError(t, err) // Create clientCanSync err = q.AddClientCanSync(ctx, &repository.AddClientCanSyncParams{ Cansync: true, Clientid: clientID, }) require.NoError(t, err) // Delete the client s3Paths, err := svc.HardDelete(ctx, clientID) require.NoError(t, err) // Verify S3 paths returned for both documents assert.Len(t, s3Paths, 2) // Verify documents are gone _, err = q.GetDocumentSummary(ctx, doc1ID) require.Error(t, err) _, err = q.GetDocumentSummary(ctx, doc2ID) require.Error(t, err) // Verify client is gone _, err = q.GetClient(ctx, clientID) require.Error(t, err) }) t.Run("deletes client with batch outcomes", func(t *testing.T) { clientID := "test-client-batch-del" test.CreateTestClient(t, cfg, clientID, "Test Client Batch Del") // Get root folder rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{ Clientid: clientID, Path: "/", }) require.NoError(t, err) // Create batch upload for client batchID, err := q.CreateBatchUpload(ctx, &repository.CreateBatchUploadParams{ ClientID: clientID, OriginalFilename: "test.zip", TotalDocuments: 1, }) require.NoError(t, err) // Create a document linked to the batch filename := "resolved.pdf" docID := createDocWithEntryForClient(t, cfg, clientID, &rootFolder.ID, "batch-client-doc") // Update the document to have the batch_id by creating via params // (the helper doesn't set batch_id, so create a second doc with batch_id) docID2, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{ Clientid: clientID, Hash: "batch-client-doc2", Filename: &filename, Folderid: &rootFolder.ID, BatchID: &batchID, }) require.NoError(t, err) _ = docID // Insert resolved outcome (with document_id set) err = q.InsertBatchDocumentOutcome(ctx, &repository.InsertBatchDocumentOutcomeParams{ BatchID: batchID, Filename: "resolved.pdf", Column3: repository.BatchOutcomeStatusInitComplete, DocumentID: &docID2, }) require.NoError(t, err) // Insert unresolved outcome (document_id NULL) err = q.InsertBatchDocumentOutcome(ctx, &repository.InsertBatchDocumentOutcomeParams{ BatchID: batchID, Filename: "unresolved.pdf", Column3: repository.BatchOutcomeStatusSubmitted, }) require.NoError(t, err) // Hard delete the client -- should fail before FK fix _, err = svc.HardDelete(ctx, clientID) require.NoError(t, err) // Verify client is gone _, err = q.GetClient(ctx, clientID) require.Error(t, err) // Verify batch_document_outcomes are gone (cascade from batch_uploads deletion) rows, err := q.ListBatchDocumentOutcomes(ctx, batchID) require.NoError(t, err) assert.Empty(t, rows) }) t.Run("returns NotFoundError for non-existent client", func(t *testing.T) { _, err := svc.HardDelete(ctx, "non-existent-client") require.Error(t, err) var notFoundErr *harddelete.NotFoundError assert.True(t, errors.As(err, ¬FoundErr)) }) t.Run("deletes client with no documents", func(t *testing.T) { clientID := "test-client-no-docs-del" test.CreateTestClient(t, cfg, clientID, "Test Client No Docs Del") s3Paths, err := svc.HardDelete(ctx, clientID) require.NoError(t, err) assert.Empty(t, s3Paths) // Verify client and root folder are gone _, err = q.GetClient(ctx, clientID) require.Error(t, err) }) t.Run("deletes client with nested folder structure", func(t *testing.T) { clientID := "test-client-nested-del" test.CreateTestClient(t, cfg, clientID, "Test Client Nested Del") rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{ Clientid: clientID, Path: "/", }) require.NoError(t, err) // Create subfolder subFolder, err := q.CreateFolder(ctx, &repository.CreateFolderParams{ Path: "/contracts", Parentid: &rootFolder.ID, Clientid: clientID, Createdby: "testuser", }) require.NoError(t, err) // Create document in subfolder docID := createDocWithEntryForClient(t, cfg, clientID, &subFolder.ID, "nested-del-doc") s3Paths, err := svc.HardDelete(ctx, clientID) require.NoError(t, err) assert.Len(t, s3Paths, 1) // Verify all gone _, err = q.GetDocumentSummary(ctx, docID) require.Error(t, err) _, err = q.GetFolderByID(ctx, subFolder.ID) require.Error(t, err) _, err = q.GetFolderByID(ctx, rootFolder.ID) require.Error(t, err) _, err = q.GetClient(ctx, clientID) require.Error(t, err) }) }