Merged in feature/add-deletes (pull request #214)

support delete for client, document and folder

* support delete

for client, document and folder

* remove batch cancel conflict

not used


Approved-by: Jacob Mathison
This commit is contained in:
Jay Brown
2026-03-04 18:30:13 +00:00
parent 62b5de5722
commit 09c61ea9b4
28 changed files with 3438 additions and 632 deletions
+421 -300
View File
@@ -534,6 +534,18 @@ type CollectorSet struct {
MinimumCleanerVersion *CodeVersion `json:"minimum_cleaner_version,omitempty"`
}
// DeleteResponse Response for hard-delete operations when verbose=true
type DeleteResponse struct {
// DeletedDocuments S3 paths of orphaned source documents (only populated when verbose=true)
DeletedDocuments []struct {
// DocumentId ID of the deleted document
DocumentId openapi_types.UUID `json:"documentId"`
// S3Path Full S3 path (s3://bucket/key) of the orphaned source file
S3Path string `json:"s3Path"`
} `json:"deletedDocuments"`
}
// DocClient The properties of a client.
type DocClient struct {
// CanSync If the client is allowing active syncs
@@ -1293,6 +1305,15 @@ type DeleteAdminUserParams struct {
Confirm bool `form:"confirm" json:"confirm"`
}
// DeleteClientParams defines parameters for DeleteClient.
type DeleteClientParams struct {
// Confirm Must be set to true to confirm deletion
Confirm bool `form:"confirm" json:"confirm"`
// Verbose When true, returns S3 paths of orphaned source documents in the response body
Verbose *bool `form:"verbose,omitempty" json:"verbose,omitempty"`
}
// UploadDocumentMultipartBody defines parameters for UploadDocument.
type UploadDocumentMultipartBody struct {
// File The file to upload
@@ -1324,6 +1345,12 @@ type ListClientFoldersParams struct {
Metrics *bool `form:"metrics,omitempty" json:"metrics,omitempty"`
}
// DeleteDocumentParams defines parameters for DeleteDocument.
type DeleteDocumentParams struct {
// Verbose When true, returns S3 paths of orphaned source documents in the response body
Verbose *bool `form:"verbose,omitempty" json:"verbose,omitempty"`
}
// GetDocumentParams defines parameters for GetDocument.
type GetDocumentParams struct {
// TextRecord When true, includes the full text extraction record in the response
@@ -1351,6 +1378,15 @@ type GetFieldExtractionByVersionParams struct {
Version int64 `form:"version" json:"version"`
}
// DeleteFolderParams defines parameters for DeleteFolder.
type DeleteFolderParams struct {
// IncludeDocuments When true, also deletes all documents in the folder tree
IncludeDocuments *bool `form:"include_documents,omitempty" json:"include_documents,omitempty"`
// Verbose When true, returns S3 paths of orphaned source documents in the response body
Verbose *bool `form:"verbose,omitempty" json:"verbose,omitempty"`
}
// GetFolderDocumentsParams defines parameters for GetFolderDocuments.
type GetFolderDocumentsParams struct {
// TextRecord When true, includes the full text extraction record for each document
@@ -1464,6 +1500,9 @@ type ServerInterface interface {
// Create a new client
// (POST /client)
CreateClient(ctx echo.Context) error
// Hard-delete a client and all its data
// (DELETE /client/{id})
DeleteClient(ctx echo.Context, id ClientID, params DeleteClientParams) error
// Get a client by ID
// (GET /client/{id})
GetClient(ctx echo.Context, id ClientID) error
@@ -1488,9 +1527,6 @@ type ServerInterface interface {
// Upload multiple PDF documents as ZIP archive
// (POST /client/{id}/document/batch)
UploadDocumentBatch(ctx echo.Context, id ClientID) error
// Cancel a batch upload
// (DELETE /client/{id}/document/batch/{batch_id})
CancelDocumentBatch(ctx echo.Context, id ClientID, batchId BatchID) error
// Get batch upload status
// (GET /client/{id}/document/batch/{batch_id})
GetDocumentBatch(ctx echo.Context, id ClientID, batchId BatchID) error
@@ -1506,6 +1542,9 @@ type ServerInterface interface {
// List all clients
// (GET /clients)
ListClients(ctx echo.Context) error
// Hard-delete a document
// (DELETE /document/{id})
DeleteDocument(ctx echo.Context, id DocumentID, params DeleteDocumentParams) error
// Get document details by its id
// (GET /document/{id})
GetDocument(ctx echo.Context, id DocumentID, params GetDocumentParams) error
@@ -1542,6 +1581,9 @@ type ServerInterface interface {
// Create a new folder
// (POST /folders)
CreateFolder(ctx echo.Context) error
// Hard-delete a folder tree
// (DELETE /folders/{folderId})
DeleteFolder(ctx echo.Context, folderId openapi_types.UUID, params DeleteFolderParams) error
// Rename a folder
// (PATCH /folders/{folderId})
RenameFolder(ctx echo.Context, folderId openapi_types.UUID) error
@@ -1944,6 +1986,40 @@ func (w *ServerInterfaceWrapper) CreateClient(ctx echo.Context) error {
return err
}
// DeleteClient converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteClient(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id ClientID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Parameter object where we will unmarshal all parameters from the context
var params DeleteClientParams
// ------------- Required query parameter "confirm" -------------
err = runtime.BindQueryParameter("form", true, true, "confirm", ctx.QueryParams(), &params.Confirm)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter confirm: %s", err))
}
// ------------- Optional query parameter "verbose" -------------
err = runtime.BindQueryParameter("form", true, false, "verbose", ctx.QueryParams(), &params.Verbose)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter verbose: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.DeleteClient(ctx, id, params)
return err
}
// GetClient converts echo context to params.
func (w *ServerInterfaceWrapper) GetClient(ctx echo.Context) error {
var err error
@@ -2104,32 +2180,6 @@ func (w *ServerInterfaceWrapper) UploadDocumentBatch(ctx echo.Context) error {
return err
}
// CancelDocumentBatch converts echo context to params.
func (w *ServerInterfaceWrapper) CancelDocumentBatch(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id ClientID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
// ------------- Path parameter "batch_id" -------------
var batchId BatchID
err = runtime.BindStyledParameterWithOptions("simple", "batch_id", ctx.Param("batch_id"), &batchId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter batch_id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.CancelDocumentBatch(ctx, id, batchId)
return err
}
// GetDocumentBatch converts echo context to params.
func (w *ServerInterfaceWrapper) GetDocumentBatch(ctx echo.Context) error {
var err error
@@ -2230,6 +2280,33 @@ func (w *ServerInterfaceWrapper) ListClients(ctx echo.Context) error {
return err
}
// DeleteDocument converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteDocument(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id DocumentID
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Parameter object where we will unmarshal all parameters from the context
var params DeleteDocumentParams
// ------------- Optional query parameter "verbose" -------------
err = runtime.BindQueryParameter("form", true, false, "verbose", ctx.QueryParams(), &params.Verbose)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter verbose: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.DeleteDocument(ctx, id, params)
return err
}
// GetDocument converts echo context to params.
func (w *ServerInterfaceWrapper) GetDocument(ctx echo.Context) error {
var err error
@@ -2431,6 +2508,40 @@ func (w *ServerInterfaceWrapper) CreateFolder(ctx echo.Context) error {
return err
}
// DeleteFolder converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteFolder(ctx echo.Context) error {
var err error
// ------------- Path parameter "folderId" -------------
var folderId openapi_types.UUID
err = runtime.BindStyledParameterWithOptions("simple", "folderId", ctx.Param("folderId"), &folderId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter folderId: %s", err))
}
ctx.Set(JwtAuthScopes, []string{})
// Parameter object where we will unmarshal all parameters from the context
var params DeleteFolderParams
// ------------- Optional query parameter "include_documents" -------------
err = runtime.BindQueryParameter("form", true, false, "include_documents", ctx.QueryParams(), &params.IncludeDocuments)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter include_documents: %s", err))
}
// ------------- Optional query parameter "verbose" -------------
err = runtime.BindQueryParameter("form", true, false, "verbose", ctx.QueryParams(), &params.Verbose)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter verbose: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.DeleteFolder(ctx, folderId, params)
return err
}
// RenameFolder converts echo context to params.
func (w *ServerInterfaceWrapper) RenameFolder(ctx echo.Context) error {
var err error
@@ -2634,6 +2745,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.POST(baseURL+"/admin/users/:email/disable", wrapper.DisableAdminUser)
router.POST(baseURL+"/admin/users/:email/enable", wrapper.EnableAdminUser)
router.POST(baseURL+"/client", wrapper.CreateClient)
router.DELETE(baseURL+"/client/:id", wrapper.DeleteClient)
router.GET(baseURL+"/client/:id", wrapper.GetClient)
router.PATCH(baseURL+"/client/:id", wrapper.UpdateClient)
router.GET(baseURL+"/client/:id/collector", wrapper.GetCollectorByClientId)
@@ -2642,12 +2754,12 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.POST(baseURL+"/client/:id/document", wrapper.UploadDocument)
router.GET(baseURL+"/client/:id/document/batch", wrapper.ListDocumentBatches)
router.POST(baseURL+"/client/:id/document/batch", wrapper.UploadDocumentBatch)
router.DELETE(baseURL+"/client/:id/document/batch/:batch_id", wrapper.CancelDocumentBatch)
router.GET(baseURL+"/client/:id/document/batch/:batch_id", wrapper.GetDocumentBatch)
router.POST(baseURL+"/client/:id/export", wrapper.TriggerExport)
router.GET(baseURL+"/client/:id/folders", wrapper.ListClientFolders)
router.GET(baseURL+"/client/:id/status", wrapper.GetStatusByClientId)
router.GET(baseURL+"/clients", wrapper.ListClients)
router.DELETE(baseURL+"/document/:id", wrapper.DeleteDocument)
router.GET(baseURL+"/document/:id", wrapper.GetDocument)
router.GET(baseURL+"/documents/:documentId/labels", wrapper.GetDocumentLabels)
router.POST(baseURL+"/documents/:documentId/labels", wrapper.ApplyLabel)
@@ -2660,6 +2772,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/field-extractions/history", wrapper.GetFieldExtractionHistory)
router.GET(baseURL+"/field-extractions/version", wrapper.GetFieldExtractionByVersion)
router.POST(baseURL+"/folders", wrapper.CreateFolder)
router.DELETE(baseURL+"/folders/:folderId", wrapper.DeleteFolder)
router.PATCH(baseURL+"/folders/:folderId", wrapper.RenameFolder)
router.GET(baseURL+"/folders/:folderId/documents", wrapper.GetFolderDocuments)
router.GET(baseURL+"/folders/:folderId/metrics", wrapper.GetFolderMetrics)
@@ -2675,276 +2788,284 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"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",
"sQEA",
"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",
"RzcHwbdP3/7/AAAA///pcQ5AzMEBAA==",
}
// GetSwagger returns the content of the embedded swagger specification file
+176
View File
@@ -0,0 +1,176 @@
// delete.go implements the hard-delete API handlers for client, document, and folder resources.
// These handlers permanently remove data from the database. Source documents in S3 are
// intentionally NOT deleted; their paths are logged and optionally returned in verbose mode.
// Delete operations are restricted to super_admin via Permit.io middleware.
package queryapi
import (
"errors"
"fmt"
"log/slog"
"net/http"
"queryorchestration/internal/harddelete"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
openapi_types "github.com/oapi-codegen/runtime/types"
)
// DeleteClient permanently deletes a client and all associated data.
// DELETE /client/{id}?confirm=true&verbose=false
// Requires confirm=true query parameter as a safety guard.
// When verbose=true, returns 200 with S3 paths of orphaned source documents.
// When verbose=false (default), returns 204 No Content.
func (s *Controllers) DeleteClient(ctx echo.Context, id ClientID, params DeleteClientParams) error {
if !params.Confirm {
return ctx.JSON(http.StatusBadRequest, ErrorMessage{
Message: "confirm=true query parameter is required to delete a client",
})
}
adminUser, err := getAdminUserForAudit(ctx)
if err != nil {
return ctx.JSON(http.StatusUnauthorized, ErrorMessage{
Message: "Could not identify admin user",
})
}
slog.Info("client hard-delete requested",
"clientId", id,
"requestedBy", adminUser.Email,
)
result, err := s.svc.Client.HardDelete(ctx.Request().Context(), string(id))
if err != nil {
return handleDeleteError(ctx, err, "client", string(id))
}
slog.Info("client hard-deleted",
"clientId", id,
"requestedBy", adminUser.Email,
"orphanedS3Objects", len(result),
)
return respondDelete(ctx, params.Verbose, result)
}
// DeleteDocument permanently deletes a document and all dependent data.
// DELETE /document/{id}?verbose=false
// When verbose=true, returns 200 with S3 paths of orphaned source documents.
// When verbose=false (default), returns 204 No Content.
func (s *Controllers) DeleteDocument(ctx echo.Context, id DocumentID, params DeleteDocumentParams) error {
adminUser, err := getAdminUserForAudit(ctx)
if err != nil {
return ctx.JSON(http.StatusUnauthorized, ErrorMessage{
Message: "Could not identify admin user",
})
}
docUUID := uuid.UUID(id)
slog.Info("document hard-delete requested",
"documentId", docUUID,
"requestedBy", adminUser.Email,
)
result, err := s.svc.Document.HardDelete(ctx.Request().Context(), docUUID)
if err != nil {
return handleDeleteError(ctx, err, "document", docUUID.String())
}
slog.Info("document hard-deleted",
"documentId", docUUID,
"requestedBy", adminUser.Email,
"orphanedS3Objects", len(result),
)
return respondDelete(ctx, params.Verbose, result)
}
// DeleteFolder permanently deletes a folder tree and optionally its documents.
// DELETE /folders/{folderId}?include_documents=false&verbose=false
// The root folder (path '/') cannot be deleted (returns 409).
// If the folder tree contains documents and include_documents is false, returns 409.
// When verbose=true, returns 200 with S3 paths of orphaned source documents.
// When verbose=false (default), returns 204 No Content.
func (s *Controllers) DeleteFolder(ctx echo.Context, folderId openapi_types.UUID, params DeleteFolderParams) error {
adminUser, err := getAdminUserForAudit(ctx)
if err != nil {
return ctx.JSON(http.StatusUnauthorized, ErrorMessage{
Message: "Could not identify admin user",
})
}
includeDocuments := params.IncludeDocuments != nil && *params.IncludeDocuments
slog.Info("folder hard-delete requested",
"folderId", folderId,
"includeDocuments", includeDocuments,
"requestedBy", adminUser.Email,
)
result, err := s.svc.Folder.HardDelete(ctx.Request().Context(), uuid.UUID(folderId), includeDocuments)
if err != nil {
return handleDeleteError(ctx, err, "folder", uuid.UUID(folderId).String())
}
slog.Info("folder hard-deleted",
"folderId", folderId,
"includeDocuments", includeDocuments,
"requestedBy", adminUser.Email,
"orphanedS3Objects", len(result),
)
return respondDelete(ctx, params.Verbose, result)
}
// handleDeleteError maps service-layer errors to appropriate HTTP responses.
// Returns 404 for not-found, 409 for conflict, and 500 for unexpected errors.
func handleDeleteError(ctx echo.Context, err error, resourceType string, resourceID string) error {
var conflictErr *harddelete.ConflictError
if errors.As(err, &conflictErr) {
return ctx.JSON(http.StatusConflict, ErrorMessage{
Message: conflictErr.Error(),
})
}
var notFoundErr *harddelete.NotFoundError
if errors.As(err, &notFoundErr) {
return ctx.JSON(http.StatusNotFound, ErrorMessage{
Message: notFoundErr.Error(),
})
}
slog.Error("hard-delete failed",
"resourceType", resourceType,
"resourceId", resourceID,
"error", err,
)
return ctx.JSON(http.StatusInternalServerError, ErrorMessage{
Message: fmt.Sprintf("failed to delete %s: internal error", resourceType),
})
}
// respondDelete sends the appropriate HTTP response based on the verbose flag.
// When verbose is true, returns 200 with a DeleteResponse containing S3 paths.
// When verbose is false or nil, returns 204 No Content.
func respondDelete(ctx echo.Context, verbose *bool, s3Paths []harddelete.S3PathInfo) error {
if verbose != nil && *verbose {
docs := make([]struct {
DocumentId openapi_types.UUID `json:"documentId"`
S3Path string `json:"s3Path"`
}, len(s3Paths))
for i, p := range s3Paths {
docs[i].DocumentId = openapi_types.UUID(p.DocumentID)
docs[i].S3Path = fmt.Sprintf("s3://%s/%s", p.Bucket, p.Key)
}
return ctx.JSON(http.StatusOK, DeleteResponse{
DeletedDocuments: docs,
})
}
return ctx.NoContent(http.StatusNoContent)
}
+363
View File
@@ -0,0 +1,363 @@
package queryapi_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
queryapi "queryorchestration/api/queryAPI"
"queryorchestration/internal/cognitoauth"
"queryorchestration/internal/database/repository"
"queryorchestration/internal/test"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
openapi_types "github.com/oapi-codegen/runtime/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// createDeleteContext creates an Echo context with DELETE method and admin user info set.
// This simulates an authenticated admin making a delete request.
func createDeleteContext(t testing.TB) (echo.Context, *httptest.ResponseRecorder) {
t.Helper()
e := echo.New()
req := httptest.NewRequest(http.MethodDelete, "/", nil)
rec := httptest.NewRecorder()
ctx := e.NewContext(req, rec)
ctx.Set("user_info", cognitoauth.UserInfo{
Email: "admin@test.local",
Username: "admin",
Groups: []string{"admin"},
})
return ctx, rec
}
// seedClientWithDocument creates a client, a root folder (via CreateTestClient), a sub-folder,
// and a document with an S3 entry. Returns the document ID and sub-folder ID.
func seedClientWithDocument(t *testing.T, cfg *ControllerConfig, clientID string) (uuid.UUID, uuid.UUID) {
t.Helper()
ctx := t.Context()
q := cfg.GetDBQueries()
test.CreateTestClient(t, cfg, clientID, "Test Client "+clientID)
// Get root folder created by CreateTestClient
rootFolder, err := q.GetFolderByPath(ctx, &repository.GetFolderByPathParams{
Clientid: clientID,
Path: "/",
})
require.NoError(t, err)
// Create a sub-folder
subFolder, err := q.CreateFolder(ctx, &repository.CreateFolderParams{
Path: "/test-folder",
Clientid: clientID,
Createdby: "admin@test.local",
Parentid: &rootFolder.ID,
})
require.NoError(t, err)
// Create a document in the sub-folder
filename := "test-doc.pdf"
docID, err := q.CreateDocument(ctx, &repository.CreateDocumentParams{
Clientid: clientID,
Hash: "hash-" + clientID,
Filename: &filename,
Folderid: &subFolder.ID,
})
require.NoError(t, err)
// Add an S3 entry for the document
err = q.AddDocumentEntry(ctx, &repository.AddDocumentEntryParams{
Documentid: docID,
Bucket: "test-bucket",
Key: "test-key/" + clientID + "/doc.pdf",
})
require.NoError(t, err)
return docID, subFolder.ID
}
func TestDeleteDocument_Success(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-doc-success"
docID, _ := seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteDocument(ctx, openapi_types.UUID(docID), queryapi.DeleteDocumentParams{})
require.NoError(t, err)
assert.Equal(t, http.StatusNoContent, rec.Code)
assert.Empty(t, rec.Body.String())
// Verify document is actually gone
_, dbErr := cfg.GetDBQueries().GetDocumentSummary(t.Context(), docID)
require.Error(t, dbErr)
}
// ptrBool returns a pointer to a bool value.
func ptrBool(b bool) *bool {
return &b
}
func TestDeleteDocument_Verbose(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-doc-verbose"
docID, _ := seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteDocument(ctx, openapi_types.UUID(docID), queryapi.DeleteDocumentParams{
Verbose: ptrBool(true),
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var resp queryapi.DeleteResponse
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.DeletedDocuments, 1)
assert.Equal(t, openapi_types.UUID(docID), resp.DeletedDocuments[0].DocumentId)
assert.Contains(t, resp.DeletedDocuments[0].S3Path, "s3://test-bucket/test-key/")
}
func TestDeleteDocument_NotFound(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
ctx, rec := createDeleteContext(t)
fakeID := uuid.New()
err := cons.DeleteDocument(ctx, openapi_types.UUID(fakeID), queryapi.DeleteDocumentParams{})
require.NoError(t, err)
assert.Equal(t, http.StatusNotFound, rec.Code)
var resp queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Contains(t, resp.Message, "not found")
}
func TestDeleteClient_MissingConfirm(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-client-noconfirm"
test.CreateTestClient(t, cfg, clientID, "Test Client")
ctx, rec := createDeleteContext(t)
err := cons.DeleteClient(ctx, queryapi.ClientID(clientID), queryapi.DeleteClientParams{
Confirm: false,
})
require.NoError(t, err)
assert.Equal(t, http.StatusBadRequest, rec.Code)
var resp queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Contains(t, resp.Message, "confirm=true")
}
func TestDeleteClient_Success(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-client-ok"
seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteClient(ctx, queryapi.ClientID(clientID), queryapi.DeleteClientParams{
Confirm: true,
})
require.NoError(t, err)
assert.Equal(t, http.StatusNoContent, rec.Code)
assert.Empty(t, rec.Body.String())
// Verify client is gone
_, dbErr := cfg.GetDBQueries().GetClient(t.Context(), clientID)
require.Error(t, dbErr)
}
func TestDeleteClient_Verbose(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-client-verbose"
docID, _ := seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteClient(ctx, queryapi.ClientID(clientID), queryapi.DeleteClientParams{
Confirm: true,
Verbose: ptrBool(true),
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var resp queryapi.DeleteResponse
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.DeletedDocuments, 1)
assert.Equal(t, openapi_types.UUID(docID), resp.DeletedDocuments[0].DocumentId)
assert.Contains(t, resp.DeletedDocuments[0].S3Path, "s3://test-bucket/")
}
func TestDeleteFolder_Success(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-folder-ok"
test.CreateTestClient(t, cfg, clientID, "Test Client")
q := cfg.GetDBQueries()
rootFolder, err := q.GetFolderByPath(t.Context(), &repository.GetFolderByPathParams{
Clientid: clientID,
Path: "/",
})
require.NoError(t, err)
subFolder, err := q.CreateFolder(t.Context(), &repository.CreateFolderParams{
Path: "/empty-folder",
Clientid: clientID,
Createdby: "admin@test.local",
Parentid: &rootFolder.ID,
})
require.NoError(t, err)
ctx, rec := createDeleteContext(t)
err = cons.DeleteFolder(ctx, openapi_types.UUID(subFolder.ID), queryapi.DeleteFolderParams{})
require.NoError(t, err)
assert.Equal(t, http.StatusNoContent, rec.Code)
assert.Empty(t, rec.Body.String())
// Verify folder is gone
_, dbErr := q.GetFolderByID(t.Context(), subFolder.ID)
require.Error(t, dbErr)
}
func TestDeleteFolder_RootFolderConflict(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-folder-root"
test.CreateTestClient(t, cfg, clientID, "Test Client")
q := cfg.GetDBQueries()
rootFolder, err := q.GetFolderByPath(t.Context(), &repository.GetFolderByPathParams{
Clientid: clientID,
Path: "/",
})
require.NoError(t, err)
ctx, rec := createDeleteContext(t)
err = cons.DeleteFolder(ctx, openapi_types.UUID(rootFolder.ID), queryapi.DeleteFolderParams{})
require.NoError(t, err)
assert.Equal(t, http.StatusConflict, rec.Code)
var resp queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Contains(t, resp.Message, "root folder")
}
func TestDeleteFolder_DocumentsWithoutInclude(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-folder-nodocs"
_, folderID := seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteFolder(ctx, openapi_types.UUID(folderID), queryapi.DeleteFolderParams{})
require.NoError(t, err)
assert.Equal(t, http.StatusConflict, rec.Code)
var resp queryapi.ErrorMessage
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Contains(t, resp.Message, "documents")
}
func TestDeleteFolder_IncludeDocumentsCascade(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
clientID := "del-folder-cascade"
docID, folderID := seedClientWithDocument(t, cfg, clientID)
ctx, rec := createDeleteContext(t)
err := cons.DeleteFolder(ctx, openapi_types.UUID(folderID), queryapi.DeleteFolderParams{
IncludeDocuments: ptrBool(true),
Verbose: ptrBool(true),
})
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
var resp queryapi.DeleteResponse
err = json.Unmarshal(rec.Body.Bytes(), &resp)
require.NoError(t, err)
require.Len(t, resp.DeletedDocuments, 1)
assert.Equal(t, openapi_types.UUID(docID), resp.DeletedDocuments[0].DocumentId)
// Verify both folder and document are gone
q := cfg.GetDBQueries()
_, dbErr := q.GetFolderByID(t.Context(), folderID)
require.Error(t, dbErr)
_, dbErr = q.GetDocumentSummary(t.Context(), docID)
require.Error(t, dbErr)
}
-16
View File
@@ -288,19 +288,3 @@ func (s *Controllers) GetDocumentBatch(ctx echo.Context, clientId ClientID, batc
return ctx.JSON(http.StatusOK, details)
}
// CancelDocumentBatch cancels a batch upload in progress
func (s *Controllers) CancelDocumentBatch(ctx echo.Context, clientId ClientID, batchId BatchID) error {
// BatchID is already a UUID type
batchUUID := uuid.UUID(batchId)
// Cancel the batch
err := s.svc.DocumentBatch.Cancel(ctx.Request().Context(), clientId, batchUUID)
if err != nil {
slog.Error("unable to cancel batch", "error", err, "batchId", batchId)
return echo.NewHTTPError(http.StatusNotFound, "batch not found or cannot be canceled")
}
// Return 204 No Content for successful cancellation
return ctx.NoContent(http.StatusNoContent)
}
-35
View File
@@ -418,41 +418,6 @@ func TestGetDocumentBatch(t *testing.T) {
assert.Equal(t, "test.zip", response.OriginalFilename)
}
func TestCancelDocumentBatch(t *testing.T) {
cfg := &ControllerConfig{}
test.CreateDB(t, cfg)
initializeTestConfig(t, cfg)
svc := createControllerServices(cfg)
cons := queryapi.NewControllers(svc, cfg)
// Create test client with root folder
clientID := "test_client_cancel"
test.CreateTestClient(t, cfg, clientID, "Test Client Batch Cancel")
// Create test batch
batchID, err := cfg.GetDBQueries().CreateBatchUpload(t.Context(), &repository.CreateBatchUploadParams{
ClientID: clientID,
OriginalFilename: "test.zip",
TotalDocuments: 10,
})
require.NoError(t, err)
// Test canceling batch
ctx, rec := createContext(t)
err = cons.CancelDocumentBatch(ctx, clientID, queryapi.BatchID(batchID))
require.NoError(t, err)
assert.Equal(t, http.StatusNoContent, rec.Code)
// Verify batch was canceled
batch, err := cfg.GetDBQueries().GetBatchUpload(t.Context(), &repository.GetBatchUploadParams{
ID: batchID,
ClientID: clientID,
})
require.NoError(t, err)
assert.Equal(t, repository.BatchStatusCancelled, batch.Status)
}
func TestUploadDocumentBatch_Part1_Success(t *testing.T) {
// Setup: Create testcontainers (database + localstack)
cfg := &ControllerConfig{}