Files
Jay Brown 09c61ea9b4 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
2026-03-04 18:30:13 +00:00

364 lines
10 KiB
Go

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)
}