72de690894
Chatbot functionality * baseline working * missing test file * more tests
499 lines
19 KiB
Go
499 lines
19 KiB
Go
// Package queryapi_test — Milestone 5 client-scoped read-only document
|
|
// endpoints (plan §10 M5).
|
|
//
|
|
// Routes covered here:
|
|
// - GET /client/{clientId}/documents/{documentId}/schema
|
|
// - GET /client/{clientId}/documents/{documentId}/all-metadata
|
|
//
|
|
// These endpoints expose the M1-shipped client-scoped repo queries
|
|
// (GetDocumentEnrichedForClient, GetDocumentLabelsForClient,
|
|
// GetDocumentCustomSchemaIdForClient, GetCurrentDocumentCustomMetadataForClient)
|
|
// behind two HTTP routes plan §3 calls "user routes". Plan §3 mandates
|
|
// the singular `/client/...` form; the OpenAPI spec must register only
|
|
// the singular path so Permit.io's existing `client_user` policy applies.
|
|
//
|
|
// Compile contract: this file references queryapi.GetDocumentSchema,
|
|
// queryapi.GetDocumentAllMetadata controller methods plus
|
|
// queryapi.DocumentSchemaResponse and queryapi.DocumentAllMetadataResponse
|
|
// types. Backend-eng adds the OpenAPI spec entries, regenerates, and
|
|
// the file compiles. Until then it fails to compile — the failing
|
|
// state plan §10 M5 endorses.
|
|
//
|
|
// All tests use:
|
|
// - real Postgres via internal/test.CreateDB
|
|
// - the M2 helpers (newBotContext, newBotContextAs, resetClientForBotTests)
|
|
// for actor-injection and per-test cleanup
|
|
// - testActorSubject as the default Cognito sub
|
|
//
|
|
// No mocks. No t.Skip beyond testing.Short. No commented-out tests.
|
|
package queryapi_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
queryapi "queryorchestration/api/queryAPI"
|
|
"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"
|
|
)
|
|
|
|
// trivialDocSchemaJSON is a minimal JSON Schema that passes the
|
|
// schema-validator but carries enough fields for the M5 schema endpoint
|
|
// to demonstrate it serializes the schema body verbatim. Not the literal
|
|
// from sample.response.json — that is the real Aaria payload; here we
|
|
// just need a recognizable test fixture.
|
|
const trivialDocSchemaJSON = `{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"type": "object",
|
|
"properties": {
|
|
"contract_title": {"type": "string"},
|
|
"effective_date": {"type": "string"}
|
|
},
|
|
"additionalProperties": false
|
|
}`
|
|
|
|
// botDocumentsContext bundles the per-test fixture: ControllerConfig,
|
|
// Controllers, and a clientID. Each test owns its own client so the
|
|
// shared testcontainer Postgres reuse is safe.
|
|
type botDocumentsContext struct {
|
|
cfg *ControllerConfig
|
|
cons *queryapi.Controllers
|
|
clientID string
|
|
}
|
|
|
|
// newBotDocumentsContext is the standard fixture for M5 tests. It
|
|
// stands up a real Postgres, runs initializeTestConfig (which sets the
|
|
// CHATBOT_* env placeholders), builds the full controller stack, and
|
|
// resets the test client. Mirrors newBotTurnsContext from M4 but
|
|
// without the FastAPI httptest server because M5 endpoints do not
|
|
// touch FastAPI.
|
|
func newBotDocumentsContext(t *testing.T, clientID string) *botDocumentsContext {
|
|
t.Helper()
|
|
cfg := &ControllerConfig{}
|
|
test.CreateDB(t, cfg)
|
|
initializeTestConfig(t, cfg)
|
|
|
|
svc := createControllerServices(cfg)
|
|
cons := queryapi.NewControllers(svc, cfg)
|
|
|
|
resetClientForBotTests(t, cfg, clientID)
|
|
test.CreateTestClient(t, cfg, clientID, "bot-m5-"+clientID)
|
|
|
|
return &botDocumentsContext{
|
|
cfg: cfg,
|
|
cons: cons,
|
|
clientID: clientID,
|
|
}
|
|
}
|
|
|
|
// seedDocumentForClient creates a documents row owned by clientID and
|
|
// returns its uuid. Tests that need a cross-client document call this
|
|
// helper with the other clientID.
|
|
func seedDocumentForClient(t *testing.T, ctx *botDocumentsContext, clientID, hash string) uuid.UUID {
|
|
t.Helper()
|
|
docID, err := ctx.cfg.GetDBQueries().CreateDocument(t.Context(), &repository.CreateDocumentParams{
|
|
Clientid: clientID,
|
|
Hash: hash,
|
|
})
|
|
require.NoError(t, err)
|
|
return docID
|
|
}
|
|
|
|
// seedSchemaForClient inserts an active client_metadata_schemas row
|
|
// and returns its id. The schema body is trivialDocSchemaJSON; tests
|
|
// only need the row to exist for the binding/rejection cases.
|
|
func seedSchemaForClient(t *testing.T, ctx *botDocumentsContext, clientID, name string) uuid.UUID {
|
|
t.Helper()
|
|
desc := "m5 test schema"
|
|
row, err := ctx.cfg.GetDBQueries().CreateClientMetadataSchema(t.Context(), &repository.CreateClientMetadataSchemaParams{
|
|
ClientID: clientID,
|
|
Name: name,
|
|
Description: &desc,
|
|
SchemaDef: []byte(trivialDocSchemaJSON),
|
|
Version: 1,
|
|
Status: repository.SchemaStatusTypeActive,
|
|
CreatedBy: "tester",
|
|
})
|
|
require.NoError(t, err)
|
|
return row.ID
|
|
}
|
|
|
|
// bindSchemaToDocument writes documents.custom_schema_id via the sqlc
|
|
// generated query. Same-client constraint comes from the composite FK
|
|
// (migration 128); cross-client binding is rejected at DB level.
|
|
func bindSchemaToDocument(t *testing.T, ctx *botDocumentsContext, docID, schemaID uuid.UUID) {
|
|
t.Helper()
|
|
require.NoError(t, ctx.cfg.GetDBQueries().SetDocumentCustomSchemaId(t.Context(), &repository.SetDocumentCustomSchemaIdParams{
|
|
CustomSchemaID: &schemaID,
|
|
ID: docID,
|
|
}))
|
|
}
|
|
|
|
// applyLabel writes a documentLabels row. The label string must exist
|
|
// in the labels seed (migration 110 seeds: Ingested, OCR_Processed,
|
|
// GenAI_Processed, Doczy_AI_Completed, Dashboard_Ready).
|
|
func applyLabel(t *testing.T, ctx *botDocumentsContext, docID uuid.UUID, label string) {
|
|
t.Helper()
|
|
_, err := ctx.cfg.GetDBQueries().ApplyLabel(t.Context(), &repository.ApplyLabelParams{
|
|
Documentid: docID,
|
|
Label: label,
|
|
Appliedby: "tester",
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// applyCustomMetadata writes one document_custom_metadata row at the
|
|
// supplied version. Caller is responsible for monotonic version values
|
|
// because the table enforces (document_id, version) uniqueness.
|
|
func applyCustomMetadata(t *testing.T, ctx *botDocumentsContext, docID uuid.UUID, version int32, metadata string) {
|
|
t.Helper()
|
|
_, err := ctx.cfg.GetDBQueries().CreateDocumentCustomMetadata(t.Context(), &repository.CreateDocumentCustomMetadataParams{
|
|
DocumentID: docID,
|
|
Metadata: []byte(metadata),
|
|
Version: version,
|
|
CreatedBy: "tester",
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// assertHTTPErrorM5 asserts the returned err is *echo.HTTPError with
|
|
// the expected code. Distinct from the M4 assertHTTPError so the M5
|
|
// file does not depend on M4 helper symbols.
|
|
func assertHTTPErrorM5(t *testing.T, err error, wantCode int) {
|
|
t.Helper()
|
|
require.Error(t, err)
|
|
httpErr, ok := err.(*echo.HTTPError)
|
|
require.Truef(t, ok, "expected *echo.HTTPError, got %T: %v", err, err)
|
|
assert.Equal(t, wantCode, httpErr.Code)
|
|
}
|
|
|
|
// ---------- GET /client/{clientId}/documents/{documentId}/schema ----------
|
|
|
|
// TestGetDocumentSchema_OwnerSuccess: client_user authenticated for
|
|
// clientA fetches a document owned by clientA whose schema is bound to
|
|
// schemaS; response is 200 with the schema body serialized verbatim.
|
|
func TestGetDocumentSchema_OwnerSuccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_SCHEMA_OK"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-schema-ok-doc")
|
|
schemaID := seedSchemaForClient(t, bdc, clientID, "m5-schema-ok")
|
|
bindSchemaToDocument(t, bdc, docID, schemaID)
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/schema", clientID, docID)
|
|
ctx, rec := newBotContext(t, http.MethodGet, path, nil)
|
|
|
|
require.NoError(t, bdc.cons.GetDocumentSchema(ctx, clientID, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp queryapi.DocumentSchemaResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
require.NotNil(t, resp.Schema, "bound document must return a non-nil schema body")
|
|
// The schema body round-trips through the response. Asserting on a
|
|
// known property name keeps the test resilient to whichever exact
|
|
// field name backend-eng picks (Schema, schemaDef, etc.); the
|
|
// JSON-tag-driven serialization will surface "properties" verbatim.
|
|
props, ok := (*resp.Schema)["properties"].(map[string]interface{})
|
|
require.Truef(t, ok, "schema body must include 'properties' map; got %v", *resp.Schema)
|
|
require.Contains(t, props, "contract_title")
|
|
}
|
|
|
|
// TestGetDocumentSchema_CrossClient_404: client_user authenticated for
|
|
// clientA requests a document owned by clientB. Plan §9: cross-client
|
|
// reads must miss with 404.
|
|
func TestGetDocumentSchema_CrossClient_404(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const (
|
|
clientA = "BOT_M5_SCHEMA_X_A"
|
|
clientB = "BOT_M5_SCHEMA_X_B"
|
|
)
|
|
bdc := newBotDocumentsContext(t, clientA)
|
|
resetClientForBotTests(t, bdc.cfg, clientB)
|
|
test.CreateTestClient(t, bdc.cfg, clientB, "bot-m5-x-b")
|
|
|
|
// docB belongs to clientB.
|
|
docB := seedDocumentForClient(t, bdc, clientB, "m5-schema-x-doc-b")
|
|
schemaB := seedSchemaForClient(t, bdc, clientB, "m5-schema-x-b")
|
|
bindSchemaToDocument(t, bdc, docB, schemaB)
|
|
|
|
// Caller authenticates for clientA.
|
|
path := fmt.Sprintf("/client/%s/documents/%s/schema", clientA, docB)
|
|
ctx, _ := newBotContext(t, http.MethodGet, path, nil)
|
|
err := bdc.cons.GetDocumentSchema(ctx, clientA, openapi_types.UUID(docB))
|
|
assertHTTPErrorM5(t, err, http.StatusNotFound)
|
|
}
|
|
|
|
// TestGetDocumentSchema_UnboundDocument_NullSchema: document exists but
|
|
// has NULL custom_schema_id. Per the dispatch authoritative answer, the
|
|
// endpoint returns 200 with schema=null in the body (the document
|
|
// exists; only the optional binding is absent).
|
|
func TestGetDocumentSchema_UnboundDocument_NullSchema(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_SCHEMA_UB"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
// Document with no schema bound.
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-schema-unbound-doc")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/schema", clientID, docID)
|
|
ctx, rec := newBotContext(t, http.MethodGet, path, nil)
|
|
require.NoError(t, bdc.cons.GetDocumentSchema(ctx, clientID, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code,
|
|
"plan §10 M5: unbound document returns 200 with schema=null, not 404")
|
|
|
|
var resp queryapi.DocumentSchemaResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
assert.Nil(t, resp.Schema,
|
|
"unbound document must return schema=null (the document exists; only the binding is absent)")
|
|
}
|
|
|
|
// TestGetDocumentSchema_NoSubject_401: handler returns 401 when no
|
|
// Cognito subject is present.
|
|
func TestGetDocumentSchema_NoSubject_401(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_SCHEMA_401"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-schema-401-doc")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/schema", clientID, docID)
|
|
ctx, _ := newBotContextAs(t, http.MethodGet, path, nil, "")
|
|
err := bdc.cons.GetDocumentSchema(ctx, clientID, openapi_types.UUID(docID))
|
|
assertHTTPErrorM5(t, err, http.StatusUnauthorized)
|
|
}
|
|
|
|
// TestGetDocumentSchema_DocumentNotFound_404: the supplied document id
|
|
// does not exist in the documents table.
|
|
func TestGetDocumentSchema_DocumentNotFound_404(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_SCHEMA_NF"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
missingID := uuid.New()
|
|
path := fmt.Sprintf("/client/%s/documents/%s/schema", clientID, missingID)
|
|
ctx, _ := newBotContext(t, http.MethodGet, path, nil)
|
|
err := bdc.cons.GetDocumentSchema(ctx, clientID, openapi_types.UUID(missingID))
|
|
assertHTTPErrorM5(t, err, http.StatusNotFound)
|
|
}
|
|
|
|
// ---------- GET /client/{clientId}/documents/{documentId}/all-metadata ----------
|
|
|
|
// TestGetDocumentAllMetadata_OwnerSuccess: docA belongs to clientA,
|
|
// has 2 labels and 3 custom-metadata key/value pairs. GET returns 200
|
|
// with document, labels, and customMetadata populated.
|
|
func TestGetDocumentAllMetadata_OwnerSuccess(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_ALL_OK"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-all-ok-doc")
|
|
schemaID := seedSchemaForClient(t, bdc, clientID, "m5-all-ok-schema")
|
|
bindSchemaToDocument(t, bdc, docID, schemaID)
|
|
|
|
// Two labels.
|
|
applyLabel(t, bdc, docID, "Ingested")
|
|
applyLabel(t, bdc, docID, "OCR_Processed")
|
|
|
|
// One metadata version with three keys.
|
|
applyCustomMetadata(t, bdc, docID, 1,
|
|
`{"contract_title":"AAA","effective_date":"2026-01-01","payer_name":"X Health"}`)
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientID, docID)
|
|
ctx, rec := newBotContext(t, http.MethodGet, path, nil)
|
|
require.NoError(t, bdc.cons.GetDocumentAllMetadata(ctx, clientID, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp queryapi.DocumentAllMetadataResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
// Document fields.
|
|
assert.Equal(t, openapi_types.UUID(docID), resp.Document.Id)
|
|
assert.Equal(t, clientID, string(resp.Document.ClientId))
|
|
|
|
// Labels: must contain both seeded labels (order is implementation-
|
|
// defined; the labels SQL ORDERs by appliedAt DESC).
|
|
require.Len(t, resp.Labels, 2,
|
|
"both seeded labels must appear in the response")
|
|
labelStrings := []string{string(resp.Labels[0].Label), string(resp.Labels[1].Label)}
|
|
assert.Contains(t, labelStrings, "Ingested")
|
|
assert.Contains(t, labelStrings, "OCR_Processed")
|
|
|
|
// Custom metadata: three keys round-trip with their values.
|
|
require.NotNil(t, resp.CustomMetadata)
|
|
assert.Equal(t, "AAA", (*resp.CustomMetadata)["contract_title"])
|
|
assert.Equal(t, "2026-01-01", (*resp.CustomMetadata)["effective_date"])
|
|
assert.Equal(t, "X Health", (*resp.CustomMetadata)["payer_name"])
|
|
}
|
|
|
|
// TestGetDocumentAllMetadata_CrossClient_404: clientA's document,
|
|
// requested as clientB. Plan §9: cross-client reads miss with 404.
|
|
func TestGetDocumentAllMetadata_CrossClient_404(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const (
|
|
clientA = "BOT_M5_ALL_X_A"
|
|
clientB = "BOT_M5_ALL_X_B"
|
|
)
|
|
bdc := newBotDocumentsContext(t, clientA)
|
|
resetClientForBotTests(t, bdc.cfg, clientB)
|
|
test.CreateTestClient(t, bdc.cfg, clientB, "bot-m5-all-x-b")
|
|
|
|
docB := seedDocumentForClient(t, bdc, clientB, "m5-all-x-doc-b")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientA, docB)
|
|
ctx, _ := newBotContext(t, http.MethodGet, path, nil)
|
|
err := bdc.cons.GetDocumentAllMetadata(ctx, clientA, openapi_types.UUID(docB))
|
|
assertHTTPErrorM5(t, err, http.StatusNotFound)
|
|
}
|
|
|
|
// TestAllMetadata_UnboundSchemaEmptyMetadata: document has labels but no
|
|
// custom-metadata schema bound (no metadata rows possible without a
|
|
// schema). Response shows labels populated and customMetadata as an
|
|
// empty map / null.
|
|
//
|
|
// Renamed from TestGetDocumentAllMetadata_UnboundCustomSchema_EmptyMetadata
|
|
// because the original 60-char Go name produced a 66-char Postgres
|
|
// alias when prefixed with `postgrestest`, exceeding NAMEDATALEN=63.
|
|
// Same trap M1 hit on TestGetCurrentDocumentCustomMetadataForClient.
|
|
func TestAllMetadata_UnboundSchemaEmptyMetadata(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_ALL_UB"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-all-unbound-doc")
|
|
applyLabel(t, bdc, docID, "Ingested")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientID, docID)
|
|
ctx, rec := newBotContext(t, http.MethodGet, path, nil)
|
|
require.NoError(t, bdc.cons.GetDocumentAllMetadata(ctx, clientID, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp queryapi.DocumentAllMetadataResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
require.Len(t, resp.Labels, 1, "the one applied label must appear")
|
|
assert.Equal(t, "Ingested", string(resp.Labels[0].Label))
|
|
|
|
// CustomMetadata is either nil or an empty map. Both shapes match the
|
|
// "no schema bound -> no metadata" semantics; assert that nothing
|
|
// surfaces a non-trivial entry.
|
|
if resp.CustomMetadata != nil {
|
|
assert.Empty(t, *resp.CustomMetadata,
|
|
"unbound document must have empty/null customMetadata")
|
|
}
|
|
}
|
|
|
|
// TestGetDocumentAllMetadata_NoLabels_EmptyArray: document has no
|
|
// labels and no custom metadata. Response is well-formed with empty
|
|
// arrays/maps.
|
|
func TestGetDocumentAllMetadata_NoLabels_EmptyArray(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_ALL_EMPTY"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-all-empty-doc")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientID, docID)
|
|
ctx, rec := newBotContext(t, http.MethodGet, path, nil)
|
|
require.NoError(t, bdc.cons.GetDocumentAllMetadata(ctx, clientID, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
|
|
var resp queryapi.DocumentAllMetadataResponse
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
|
|
|
assert.NotNil(t, resp.Labels,
|
|
"labels must be a non-nil slice so json.Marshal emits []")
|
|
assert.Empty(t, resp.Labels)
|
|
if resp.CustomMetadata != nil {
|
|
assert.Empty(t, *resp.CustomMetadata)
|
|
}
|
|
}
|
|
|
|
// TestGetDocumentAllMetadata_NoSubject_401: handler returns 401 when no
|
|
// Cognito subject is present.
|
|
func TestGetDocumentAllMetadata_NoSubject_401(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_ALL_401"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
docID := seedDocumentForClient(t, bdc, clientID, "m5-all-401-doc")
|
|
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientID, docID)
|
|
ctx, _ := newBotContextAs(t, http.MethodGet, path, nil, "")
|
|
err := bdc.cons.GetDocumentAllMetadata(ctx, clientID, openapi_types.UUID(docID))
|
|
assertHTTPErrorM5(t, err, http.StatusUnauthorized)
|
|
}
|
|
|
|
// TestGetDocumentAllMetadata_DocumentNotFound_404: the supplied
|
|
// document id does not exist.
|
|
func TestGetDocumentAllMetadata_DocumentNotFound_404(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "BOT_M5_ALL_NF"
|
|
bdc := newBotDocumentsContext(t, clientID)
|
|
|
|
missingID := uuid.New()
|
|
path := fmt.Sprintf("/client/%s/documents/%s/all-metadata", clientID, missingID)
|
|
ctx, _ := newBotContext(t, http.MethodGet, path, nil)
|
|
err := bdc.cons.GetDocumentAllMetadata(ctx, clientID, openapi_types.UUID(missingID))
|
|
assertHTTPErrorM5(t, err, http.StatusNotFound)
|
|
}
|
|
|
|
// ---------- Authorization smoke ----------
|
|
|
|
// TestGetDocumentEndpoints_PluralRouteAbsent: assert via OpenAPI grep
|
|
// that /clients/{clientId}/documents/.../{schema,all-metadata} (plural)
|
|
// is NOT registered. Plan §3 mandates the singular form so Permit.io's
|
|
// existing client_user policy applies.
|
|
func TestGetDocumentEndpoints_PluralRouteAbsent(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
swagger, err := queryapi.GetSwagger()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, swagger.Paths)
|
|
|
|
for _, p := range []string{
|
|
"/clients/{clientId}/documents/{documentId}/schema",
|
|
"/clients/{clientId}/documents/{documentId}/all-metadata",
|
|
} {
|
|
assert.Nil(t, swagger.Paths.Find(p),
|
|
"plural route %s must not be registered (plan §3 mandates singular `client`)", p)
|
|
}
|
|
|
|
// Positive control: the singular routes must be declared.
|
|
for _, p := range []string{
|
|
"/client/{clientId}/documents/{documentId}/schema",
|
|
"/client/{clientId}/documents/{documentId}/all-metadata",
|
|
} {
|
|
assert.NotNilf(t, swagger.Paths.Find(p),
|
|
"singular route %s must be registered", p)
|
|
}
|
|
}
|