17fc813823
M1, M2 and M3 complete * M1, M2 and M3 complete * review changes * docs * docs
490 lines
18 KiB
Go
490 lines
18 KiB
Go
// Package queryapi_test — Milestone 2 §2.8 end-to-end integration tests.
|
|
//
|
|
// These tests compose the Milestone 2 HTTP handlers, the customschema
|
|
// service, the fieldextraction service, and the real Postgres testcontainer
|
|
// produced by internal/test.CreateDB to prove the mutual-exclusivity
|
|
// invariant, the full happy-path round-trip, and the edge cases the plan
|
|
// calls out in §2.8.
|
|
//
|
|
// No mocks. Every request goes through a real handler receiver. The
|
|
// helpers below reuse the Milestone 1 testutils (newCustomSchemaContext,
|
|
// seedCustomSchema, resetClientForSchemaTests) so the two files share the
|
|
// same fixture shape.
|
|
package queryapi_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
queryapi "queryorchestration/api/queryAPI"
|
|
"queryorchestration/internal/cognitoauth"
|
|
"queryorchestration/internal/database/repository"
|
|
"queryorchestration/internal/fieldextraction"
|
|
"queryorchestration/internal/test"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/oapi-codegen/nullable"
|
|
openapi_types "github.com/oapi-codegen/runtime/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// newCustomMetadataContext is the Milestone 2 sibling of
|
|
// newCustomSchemaContext — it exists only so the tests in this file can
|
|
// read clearly at a glance; the implementation is identical.
|
|
func newCustomMetadataContext(t testing.TB, method string, body interface{}) (echo.Context, *httptest.ResponseRecorder) {
|
|
t.Helper()
|
|
e := echo.New()
|
|
var reqBody []byte
|
|
if body != nil {
|
|
var err error
|
|
reqBody, err = json.Marshal(body)
|
|
require.NoError(t, err)
|
|
}
|
|
req := httptest.NewRequest(method, "/", bytes.NewReader(reqBody))
|
|
if reqBody != nil {
|
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
ctx := e.NewContext(req, rec)
|
|
ctx.Set("user_claims", map[string]interface{}{"sub": testActorSubject})
|
|
ctx.Set("user_info", cognitoauth.UserInfo{Username: "admin"})
|
|
return ctx, rec
|
|
}
|
|
|
|
// setupM2Fixture boots a fresh DB, wipes any rows belonging to clientID,
|
|
// creates the client and returns the controllers. Mirrors the Milestone 1
|
|
// pattern but in one helper so the §2.8 tests stay short.
|
|
func setupM2Fixture(t *testing.T, clientID string) (*queryapi.Controllers, *ControllerConfig) {
|
|
t.Helper()
|
|
cfg := &ControllerConfig{}
|
|
test.CreateDB(t, cfg)
|
|
initializeTestConfig(t, cfg)
|
|
svc := createControllerServices(cfg)
|
|
cons := queryapi.NewControllers(svc, cfg)
|
|
|
|
resetClientForSchemaTests(t, cfg, clientID)
|
|
test.CreateTestClient(t, cfg, clientID, "M2 integration "+clientID)
|
|
return cons, cfg
|
|
}
|
|
|
|
// seedDoc creates a document for clientID with a unique hash.
|
|
func seedDoc(t *testing.T, cfg *ControllerConfig, clientID, hash string) uuid.UUID {
|
|
t.Helper()
|
|
id, err := cfg.GetDBQueries().CreateDocument(t.Context(), &repository.CreateDocumentParams{
|
|
Clientid: clientID,
|
|
Hash: hash,
|
|
})
|
|
require.NoError(t, err)
|
|
return id
|
|
}
|
|
|
|
// assignSchemaForTest wraps AssignDocumentSchema against a freshly built
|
|
// echo context so the §2.8 tests can bind a document in one line.
|
|
func assignSchemaForTest(t *testing.T, cons *queryapi.Controllers, docID, schemaID uuid.UUID) {
|
|
t.Helper()
|
|
body := queryapi.DocumentSchemaAssignRequest{
|
|
CustomSchemaId: nullable.NewNullableWithValue(openapi_types.UUID(schemaID)),
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPatch, body)
|
|
require.NoError(t, cons.AssignDocumentSchema(ctx, openapi_types.UUID(docID)))
|
|
require.Equal(t, http.StatusOK, rec.Code, "assign must return 200")
|
|
}
|
|
|
|
// ---------- end-to-end happy round-trip ----------
|
|
|
|
// TestM2_HappyRoundTrip covers the §2.8 full round-trip:
|
|
// create schema → assign to document → POST valid metadata → GET returns
|
|
// it with populated schemaId / schemaName / schemaVersion / createdBy.
|
|
func TestM2_HappyRoundTrip(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_HAPPY"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-happy")
|
|
docID := seedDoc(t, cfg, clientID, "m2-happy-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
// POST valid metadata.
|
|
postBody := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: map[string]interface{}{"name": "contract-42"},
|
|
}
|
|
ctxPost, recPost := newCustomMetadataContext(t, http.MethodPost, postBody)
|
|
require.NoError(t, cons.SetCustomMetadata(ctxPost))
|
|
require.Equal(t, http.StatusCreated, recPost.Code)
|
|
|
|
var posted queryapi.CustomMetadataResponse
|
|
require.NoError(t, json.Unmarshal(recPost.Body.Bytes(), &posted))
|
|
assert.Equal(t, int32(1), posted.Version)
|
|
assert.Equal(t, testActorSubject, posted.CreatedBy)
|
|
|
|
// GET /custom-metadata?documentId=...
|
|
ctxGet, recGet := newCustomMetadataContext(t, http.MethodGet, nil)
|
|
err := cons.GetCurrentCustomMetadata(ctxGet, queryapi.GetCurrentCustomMetadataParams{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, recGet.Code)
|
|
|
|
var got queryapi.CustomMetadataResponse
|
|
require.NoError(t, json.Unmarshal(recGet.Body.Bytes(), &got))
|
|
assert.Equal(t, posted.Version, got.Version)
|
|
assert.Equal(t, posted.Id, got.Id)
|
|
require.True(t, got.SchemaId.IsSpecified() && !got.SchemaId.IsNull())
|
|
sid, _ := got.SchemaId.Get()
|
|
assert.Equal(t, schema.Id, sid)
|
|
schemaName, _ := got.SchemaName.Get()
|
|
assert.Equal(t, "m2-happy", schemaName)
|
|
schemaVersion, _ := got.SchemaVersion.Get()
|
|
assert.Equal(t, int32(1), schemaVersion)
|
|
}
|
|
|
|
// TestM2_PostWithoutSchemaReturns400 proves POST /custom-metadata refuses
|
|
// writes to documents that have no schema binding.
|
|
func TestM2_PostWithoutSchemaReturns400(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_NOSCH"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
docID := seedDoc(t, cfg, clientID, "m2-nosch-doc")
|
|
|
|
postBody := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: map[string]interface{}{"name": "nope"},
|
|
}
|
|
ctxPost, recPost := newCustomMetadataContext(t, http.MethodPost, postBody)
|
|
err := cons.SetCustomMetadata(ctxPost)
|
|
// The handler propagates echo.HTTPError for mapped sentinels; the
|
|
// real HTTP server flushes this as a response. For the handler unit
|
|
// test we inspect the error directly.
|
|
requireHTTPStatus(t, err, recPost, http.StatusBadRequest)
|
|
}
|
|
|
|
// TestM2_LegacyWriteOnCustomDoc covers the §2.8 mutual-exclusivity end:
|
|
// schema bound → legacy field extraction write → 409.
|
|
func TestM2_LegacyWriteOnCustomDoc(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_LEGACY"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-legacy")
|
|
docID := seedDoc(t, cfg, clientID, "m2-legacy-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
// Attempt legacy write via the fieldextraction service directly
|
|
// (the REST handler would do the same mapping — we test service +
|
|
// handler mapping separately in the fieldextraction package).
|
|
svc := createControllerServices(cfg)
|
|
_, err := svc.FieldExtraction.CreateFieldExtraction(t.Context(), &fieldextraction.CreateFieldExtractionInput{
|
|
DocumentID: docID,
|
|
SingleFields: &repository.AddFieldExtractionParams{
|
|
Documentid: docID,
|
|
Createdby: "tester",
|
|
},
|
|
ArrayFields: nil,
|
|
})
|
|
require.Error(t, err)
|
|
assert.ErrorIs(t, err, fieldextraction.ErrMutualExclusivityViolation)
|
|
}
|
|
|
|
// TestM2_GetByVersion covers §2.8 GET /custom-metadata/version happy
|
|
// path: post 3 versions, retrieve v2 exactly.
|
|
func TestM2_GetByVersion(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_VERSION"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-version")
|
|
docID := seedDoc(t, cfg, clientID, "m2-version-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
for _, payload := range []string{`{"name":"v1"}`, `{"name":"v2"}`, `{"name":"v3"}`} {
|
|
var m map[string]interface{}
|
|
require.NoError(t, json.Unmarshal([]byte(payload), &m))
|
|
body := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: m,
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPost, body)
|
|
require.NoError(t, cons.SetCustomMetadata(ctx))
|
|
require.Equal(t, http.StatusCreated, rec.Code)
|
|
}
|
|
|
|
ctxVer, recVer := newCustomMetadataContext(t, http.MethodGet, nil)
|
|
err := cons.GetCustomMetadataByVersion(ctxVer, queryapi.GetCustomMetadataByVersionParams{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Version: 2,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, recVer.Code)
|
|
|
|
var got queryapi.CustomMetadataResponse
|
|
require.NoError(t, json.Unmarshal(recVer.Body.Bytes(), &got))
|
|
assert.Equal(t, int32(2), got.Version)
|
|
// metadata must be the v2 payload, not v1 or v3
|
|
v, ok := got.Metadata["name"]
|
|
require.True(t, ok)
|
|
assert.Equal(t, "v2", v)
|
|
}
|
|
|
|
// TestM2_History covers the §2.8 history GET happy path with pagination.
|
|
func TestM2_History(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_HIST"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-hist")
|
|
docID := seedDoc(t, cfg, clientID, "m2-hist-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
for i := 0; i < 5; i++ {
|
|
body := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: map[string]interface{}{"name": "x"},
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPost, body)
|
|
require.NoError(t, cons.SetCustomMetadata(ctx))
|
|
require.Equal(t, http.StatusCreated, rec.Code)
|
|
}
|
|
|
|
limit := int32(2)
|
|
offset := int32(0)
|
|
ctx1, rec1 := newCustomMetadataContext(t, http.MethodGet, nil)
|
|
require.NoError(t, cons.GetCustomMetadataHistory(ctx1, queryapi.GetCustomMetadataHistoryParams{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Limit: &limit,
|
|
Offset: &offset,
|
|
}))
|
|
require.Equal(t, http.StatusOK, rec1.Code)
|
|
var page1 queryapi.CustomMetadataHistoryResponse
|
|
require.NoError(t, json.Unmarshal(rec1.Body.Bytes(), &page1))
|
|
require.Len(t, page1.Versions, 2)
|
|
assert.Equal(t, int32(5), page1.Versions[0].Version)
|
|
assert.Equal(t, int32(4), page1.Versions[1].Version)
|
|
|
|
offset2 := int32(2)
|
|
ctx2, rec2 := newCustomMetadataContext(t, http.MethodGet, nil)
|
|
require.NoError(t, cons.GetCustomMetadataHistory(ctx2, queryapi.GetCustomMetadataHistoryParams{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Limit: &limit,
|
|
Offset: &offset2,
|
|
}))
|
|
var page2 queryapi.CustomMetadataHistoryResponse
|
|
require.NoError(t, json.Unmarshal(rec2.Body.Bytes(), &page2))
|
|
require.Len(t, page2.Versions, 2)
|
|
assert.Equal(t, int32(3), page2.Versions[0].Version)
|
|
assert.Equal(t, int32(2), page2.Versions[1].Version)
|
|
}
|
|
|
|
// TestM2_AssignToDocWithLegacyExtractionsReturns409 covers the reverse
|
|
// direction of the mutual-exclusivity invariant: a doc with legacy
|
|
// extractions cannot be bound to a schema.
|
|
func TestM2_AssignToDocWithLegacyExtractionsReturns409(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_LEGACYASSIGN"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
// Create a document and seed a legacy extraction via the service.
|
|
svc := createControllerServices(cfg)
|
|
docID := seedDoc(t, cfg, clientID, "m2-legacy-assign-doc")
|
|
_, err := svc.FieldExtraction.CreateFieldExtraction(t.Context(), &fieldextraction.CreateFieldExtractionInput{
|
|
DocumentID: docID,
|
|
SingleFields: &repository.AddFieldExtractionParams{
|
|
Documentid: docID,
|
|
Createdby: "tester",
|
|
},
|
|
ArrayFields: nil,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-legacy-assign")
|
|
|
|
// Attempt to bind — must 409.
|
|
body := queryapi.DocumentSchemaAssignRequest{
|
|
CustomSchemaId: nullable.NewNullableWithValue(schema.Id),
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPatch, body)
|
|
err = cons.AssignDocumentSchema(ctx, openapi_types.UUID(docID))
|
|
requireHTTPStatus(t, err, rec, http.StatusConflict)
|
|
}
|
|
|
|
// TestM2_FKCascadeDeletesMetadata proves the FK cascade from documents
|
|
// removes document_custom_metadata rows on document delete, covering the
|
|
// §2.8 "FK-cascade delete" row.
|
|
func TestM2_FKCascadeDeletesMetadata(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_FKCASCADE"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-fkcascade")
|
|
docID := seedDoc(t, cfg, clientID, "m2-fkcascade-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
// Write two metadata versions via the handler.
|
|
for i := 0; i < 2; i++ {
|
|
body := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: map[string]interface{}{"name": "x"},
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPost, body)
|
|
require.NoError(t, cons.SetCustomMetadata(ctx))
|
|
require.Equal(t, http.StatusCreated, rec.Code)
|
|
}
|
|
|
|
var before int
|
|
require.NoError(t, cfg.GetDBPool().QueryRow(t.Context(),
|
|
`SELECT COUNT(*) FROM document_custom_metadata WHERE document_id = $1`,
|
|
docID).Scan(&before))
|
|
require.Equal(t, 2, before)
|
|
|
|
// Delete the document row directly (the feature relies on the FK
|
|
// cascade, not on an application-level delete path).
|
|
_, err := cfg.GetDBPool().Exec(t.Context(),
|
|
`DELETE FROM documents WHERE id = $1`, docID)
|
|
require.NoError(t, err)
|
|
|
|
var after int
|
|
require.NoError(t, cfg.GetDBPool().QueryRow(t.Context(),
|
|
`SELECT COUNT(*) FROM document_custom_metadata WHERE document_id = $1`,
|
|
docID).Scan(&after))
|
|
assert.Equal(t, 0, after, "FK cascade must remove every metadata row")
|
|
}
|
|
|
|
// TestM2_GetByVersionNotFound exercises the ErrMetadataVersionNotFound (404)
|
|
// branch in mapCustomMetadataError by requesting a version that does not exist.
|
|
func TestM2_GetByVersionNotFound(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_VERNOTFOUND"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-ver-notfound")
|
|
docID := seedDoc(t, cfg, clientID, "m2-ver-notfound-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
// POST one version so the document exists and has metadata.
|
|
body := queryapi.CustomMetadataRequest{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Metadata: map[string]interface{}{"name": "x"},
|
|
}
|
|
ctx, rec := newCustomMetadataContext(t, http.MethodPost, body)
|
|
require.NoError(t, cons.SetCustomMetadata(ctx))
|
|
require.Equal(t, http.StatusCreated, rec.Code)
|
|
|
|
// Request a version that does not exist → 404.
|
|
ctxVer, recVer := newCustomMetadataContext(t, http.MethodGet, nil)
|
|
err := cons.GetCustomMetadataByVersion(ctxVer, queryapi.GetCustomMetadataByVersionParams{
|
|
DocumentId: openapi_types.UUID(docID),
|
|
Version: 99,
|
|
})
|
|
requireHTTPStatus(t, err, recVer, http.StatusNotFound)
|
|
}
|
|
|
|
// TestM2_CreateFieldExtractionOnCustomDocReturns409 exercises the
|
|
// ErrMutualExclusivityViolation branch in the CreateFieldExtraction handler.
|
|
// A document bound to a custom schema must reject legacy field extraction writes
|
|
// with 409.
|
|
func TestM2_CreateFieldExtractionOnCustomDocReturns409(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_LEGFX409"
|
|
cons, cfg := setupM2Fixture(t, clientID)
|
|
|
|
schema := seedCustomSchema(t, cons, clientID, "m2-legfx-409")
|
|
docID := seedDoc(t, cfg, clientID, "m2-legfx-409-doc")
|
|
assignSchemaForTest(t, cons, docID, uuid.UUID(schema.Id))
|
|
|
|
// Attempt a legacy field extraction write — must 409.
|
|
reqBody := queryapi.FieldExtractionRequest{
|
|
DocumentId: queryapi.DocumentID(docID),
|
|
CreatedBy: "test@example.com",
|
|
SingleFields: queryapi.SingleFields{
|
|
FileName: func() *string { s := "test.pdf"; return &s }(),
|
|
},
|
|
}
|
|
b, err := json.Marshal(reqBody)
|
|
require.NoError(t, err)
|
|
|
|
e := echo.New()
|
|
req := httptest.NewRequest(http.MethodPost, "/field-extractions", bytes.NewReader(b))
|
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
|
rec := httptest.NewRecorder()
|
|
ctx := e.NewContext(req, rec)
|
|
ctx.Set("user_claims", map[string]interface{}{"sub": testActorSubject})
|
|
|
|
fxErr := cons.CreateFieldExtraction(ctx)
|
|
requireHTTPStatus(t, fxErr, rec, http.StatusConflict)
|
|
}
|
|
|
|
// TestM2_CreateFieldExtractionDocNotFound exercises the ErrDocumentNotFound (404)
|
|
// branch in the CreateFieldExtraction handler when the document does not exist.
|
|
func TestM2_CreateFieldExtractionDocNotFound(t *testing.T) {
|
|
if testing.Short() {
|
|
t.SkipNow()
|
|
}
|
|
const clientID = "M2_FXDOCNOTFOUND"
|
|
cons, _ := setupM2Fixture(t, clientID)
|
|
|
|
// Use a UUID that will not match any document.
|
|
nonExistentDocID := uuid.New()
|
|
reqBody := queryapi.FieldExtractionRequest{
|
|
DocumentId: queryapi.DocumentID(nonExistentDocID),
|
|
CreatedBy: "test@example.com",
|
|
SingleFields: queryapi.SingleFields{
|
|
FileName: func() *string { s := "test.pdf"; return &s }(),
|
|
},
|
|
}
|
|
b, err := json.Marshal(reqBody)
|
|
require.NoError(t, err)
|
|
|
|
e := echo.New()
|
|
req := httptest.NewRequest(http.MethodPost, "/field-extractions", bytes.NewReader(b))
|
|
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
|
rec := httptest.NewRecorder()
|
|
ctx := e.NewContext(req, rec)
|
|
ctx.Set("user_claims", map[string]interface{}{"sub": testActorSubject})
|
|
|
|
fxErr := cons.CreateFieldExtraction(ctx)
|
|
requireHTTPStatus(t, fxErr, rec, http.StatusNotFound)
|
|
}
|
|
|
|
// requireHTTPStatus asserts the handler produced the expected HTTP code,
|
|
// handling both the "echo.HTTPError returned" and "recorder has code
|
|
// already" shapes the handlers can emit. Milestone 2 handlers always
|
|
// return an echo.HTTPError for mapped sentinels rather than writing to
|
|
// the recorder, so the error branch is the common path.
|
|
func requireHTTPStatus(t *testing.T, err error, rec *httptest.ResponseRecorder, want int) {
|
|
t.Helper()
|
|
if err != nil {
|
|
var httpErr *echo.HTTPError
|
|
if errors.As(err, &httpErr) {
|
|
assert.Equalf(t, want, httpErr.Code, "expected status %d, got %d: %v", want, httpErr.Code, httpErr.Message)
|
|
return
|
|
}
|
|
t.Fatalf("expected echo.HTTPError with status %d, got %T: %v", want, err, err)
|
|
}
|
|
assert.Equal(t, want, rec.Code)
|
|
}
|