Files
stack/dev/vendor/bcda-app/bcda/web/middleware/validation_test.go
kert 7e53d9360a chore(bcda): vendor CMSgov/bcda-app + bcda.cms.gov v3 artefacts sparsely, flatten the v3 data dictionary into crosswalk seeds, weekly drift workflow
dev/vendor/MANIFEST.toml pins bcda-app@7decdcc (v3 handler + tests, request
validation middleware, OperationOutcome writer, v3 Postman collection, gzip
fixtures, sandbox config, LICENSE) and the bcda.cms.gov v3 data dictionary,
v2 dictionary and official v3 sample ndjson. vendor_bcda_app.py --verify |
--check | --sync (stdlib only); the bcda-vendor workflow runs --check weekly
and files an issue on drift — nothing auto-commits, a payload change upstream
is something a human reads before the flattener is re-pointed.

gen_bcda_v3_crosswalk.py writes dev/seeds/bcda_v3_data_dictionary.csv
(fhirPath, IDR sourceColumn, ccwMapping, cclfMapping per v3 field),
bcda_v3_cclf_crosswalk.csv (293 CCLF elements -> v3 field) and
bcda_v3_cclf_absent.csv (52 CCLF fields v3 does not carry); the CCLF sheet
has label/name swapped on data rows and is normalised. Tests assert the
committed seeds match the vendored workbook.

refs #1139
2026-10-25 13:08:05 -04:00

672 lines
22 KiB
Go

package middleware
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/CMSgov/bcda-app/bcda/auth"
"github.com/CMSgov/bcda-app/bcda/constants"
"github.com/CMSgov/bcda-app/bcda/fhir"
"github.com/CMSgov/bcda-app/bcda/responseutils"
"github.com/CMSgov/bcda-app/log"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
var noop http.HandlerFunc = func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(http.StatusOK) }
func TestValidRequestURL(t *testing.T) {
// Allow us to retrieve the RequestParameters by grabbing the updated context.
// When we call *http.Request.WithContext(ctx), a new request is created.
// So we cannot leverage the context associated with the original request
var ctx context.Context
handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ctx = r.Context()
})
now := time.Now().Add(-24 * time.Hour).Round(time.Millisecond)
req, err := http.NewRequest("GET",
fmt.Sprintf("/api/v1/Patient/$export?_type=Patient&_since=%s&_outputFormat=ndjson&_typeFilter=ExplanationOfBenefit%%3Fservice-date%%3Dgt2001-04-01",
now.Format(time.RFC3339Nano)),
nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
ValidateRequestURL(handler).ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
// Verify we have the context as expected
rp, ok := GetRequestParamsFromCtx(ctx)
assert.True(t, ok)
// assert.True(t, now.Equal(rp.Since), "Since parameter does not match")
assert.Equal(t, rp.ResourceTypes, []string{"Patient"})
assert.Equal(t, rp.Version, "v1")
}
func TestInvalidRequestURL(t *testing.T) {
base := "/api/v1/Patient/$export?"
baseV3 := constants.V3Path + "Patient/$export?"
tests := []struct {
name string
url string
errMsg string
}{
{
"invalidOutputFormat",
fmt.Sprintf("%s_outputFormat=invalid", base),
"_outputFormat parameter must be one of",
},
{
"elementsNotSupported",
fmt.Sprintf("%s_elements=invalid", base),
"does not support the _elements parameter",
},
{
"contains?",
fmt.Sprintf("%s?_type=Patient", base),
"query parameters cannot start with ?",
},
{
"invalidSince",
fmt.Sprintf("%s_since=05-25-1977", base),
"Date must be in FHIR Instant format",
},
{
"futureSince",
fmt.Sprintf("%s_since=%s", base, time.Now().Add(24*time.Hour).Format(time.RFC3339Nano)),
"Date must be a date that has already passed",
},
{
"repeatedType",
fmt.Sprintf("%s_type=Patient,Patient", base),
"Repeated resource type Patient",
},
{
"noVersion",
"/api/Patient$export",
"cannot retrieve version",
},
{
"invalidTypeFilterResourceType",
fmt.Sprintf("%s_typeFilter=MedicationRequest%%3Fstatus%%3Dactive", baseV3),
"invalid _typeFilter Resource Type (Only EOBs valid): MedicationRequest",
},
{
"invalidTypeFilterSubquery",
fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Fservice-dateactive", baseV3),
"_typeFilter value missing equals sign",
},
{
"invalidTypeFilterSubqueryParam",
fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Fstatus%%3Dactive", baseV3),
"invalid _typeFilter subquery parameter: status",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ctx = log.NewStructuredLoggerEntry(logrus.New(), ctx)
req, err := http.NewRequest("GET", tt.url, nil)
assert.NoError(t, err)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
ValidateRequestURL(noop).ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), tt.errMsg)
})
}
}
func TestParseHeaderValues(t *testing.T) {
tests := []struct {
name string
headers http.Header
headerName string
expected []string
}{
{
name: "singleValue",
headers: http.Header{"Prefer": []string{constants.TestRespondAsync}},
headerName: "Prefer",
expected: []string{constants.TestRespondAsync},
},
{
name: "commaSeparatedValues",
headers: http.Header{"Prefer": []string{constants.TestRespondAsync + ",handling=lenient"}},
headerName: "Prefer",
expected: []string{constants.TestRespondAsync, "handling=lenient"},
},
{
name: "whitespaceTrimmingAndEmptyTokens",
headers: http.Header{"Prefer": []string{" " + constants.TestRespondAsync + " , , handling=lenient "}},
headerName: "Prefer",
expected: []string{constants.TestRespondAsync, "handling=lenient"},
},
{
name: "multiLineHeaders",
headers: http.Header{"Prefer": []string{constants.TestRespondAsync, "handling=lenient"}},
headerName: "Prefer",
expected: []string{constants.TestRespondAsync, "handling=lenient"},
},
{
name: "missingHeader",
headers: http.Header{},
headerName: "Prefer",
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
results := parseHeaderValues(tt.headers, tt.headerName)
assert.Equal(t, tt.expected, results)
})
}
}
func TestHasHeaderToken(t *testing.T) {
tests := []struct {
name string
values []string
targetToken string
expected bool
}{
{
name: "exactMatch",
values: []string{"respond-async"},
targetToken: "respond-async",
expected: true,
},
{
name: "caseInsensitiveMatch",
values: []string{"RESPOND-ASYNC"},
targetToken: "respond-async",
expected: true,
},
{
name: "withParameters",
values: []string{"respond-async; wait=10"},
targetToken: "respond-async",
expected: true,
},
{
name: "mediaTypeWithParameters",
values: []string{"application/fhir+json; charset=utf-8"},
targetToken: "application/fhir+json",
expected: true,
},
{
name: "multipleValuesMatch",
values: []string{"handling=lenient", "respond-async"},
targetToken: "respond-async",
expected: true,
},
{
name: "noMatch",
values: []string{"handling=lenient"},
targetToken: "respond-async",
expected: false,
},
{
name: "emptySlice",
values: nil,
targetToken: "respond-async",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, hasHeaderToken(tt.values, tt.targetToken))
})
}
}
func TestValidRequestHeaders(t *testing.T) {
tests := []struct {
name string
setupReq func() *http.Request
}{
{
name: "StandardHeaders",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync)
return req
},
},
{
name: "PreferCommaSeparatedDirectives",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync+",handling=lenient")
return req
},
},
{
name: "PreferWithParameters",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync+"; wait=10")
return req
},
},
{
name: "CaseInsensitiveHeaders",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "APPLICATION/FHIR+JSON")
req.Header.Set("Prefer", "RESPOND-ASYNC")
return req
},
},
{
name: "PreferReversedDirectives",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", "handling=lenient, "+constants.TestRespondAsync)
return req
},
},
{
name: "PreferWhitespaceTolerance",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync+" , handling=lenient")
return req
},
},
{
name: "PreferMultiLineHeaders",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Add("Prefer", "handling=lenient")
req.Header.Add("Prefer", constants.TestRespondAsync)
return req
},
},
{
name: "AcceptCommaSeparatedMediaTypes",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json, application/json")
req.Header.Set("Prefer", constants.TestRespondAsync)
return req
},
},
{
name: "AcceptWithParameters",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Set("Accept", "application/fhir+json; charset=utf-8")
req.Header.Set("Prefer", constants.TestRespondAsync)
return req
},
},
{
name: "AcceptMultiLineHeaders",
setupReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
req.Header.Add("Accept", "application/json")
req.Header.Add("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync)
return req
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ctx = log.NewStructuredLoggerEntry(logrus.New(), ctx)
req := tt.setupReq().WithContext(ctx)
rr := httptest.NewRecorder()
ValidateRequestHeaders(noop).ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
})
}
}
func TestInvalidRequestHeaders(t *testing.T) {
tests := []struct {
name string
acceptHeader string
preferHeader string
errMsg string
}{
{"NoAcceptHeader", "", constants.TestRespondAsync, "Accept header is required"},
{"InvalidAcceptHeader", "invalid", constants.TestRespondAsync, "application/fhir+json is the only supported response format"},
{"UnsupportedAcceptHeader", "application/xml", constants.TestRespondAsync, "application/fhir+json is the only supported response format"},
{"NoPreferHeader", "application/fhir+json", "", "Prefer header is required"},
{"InvalidPreferHeader", "application/fhir+json", "invalid", "Only asynchronous responses are supported"},
{"PreferMissingRespondAsync", "application/fhir+json", "handling=lenient", "Only asynchronous responses are supported"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
ctx = log.NewStructuredLoggerEntry(logrus.New(), ctx)
req, err := http.NewRequest("GET", "/api/v1/Patient/$export", nil)
assert.NoError(t, err)
req = req.WithContext(ctx)
if tt.acceptHeader != "" {
req.Header.Set("Accept", tt.acceptHeader)
}
if tt.preferHeader != "" {
req.Header.Set("Prefer", tt.preferHeader)
}
rr := httptest.NewRecorder()
ValidateRequestHeaders(noop).ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), tt.errMsg)
})
}
}
func TestValidateTypeFilterTagCodes(t *testing.T) {
baseV3 := constants.V3Path + "Patient/$export?"
ctx := context.Background()
ctx = log.NewStructuredLoggerEntry(logrus.New(), ctx)
tests := []struct {
name string
url string
shouldFail bool
expectedErr error
description string
expectedTypeFilter fhir.TypeFilterSubquery // when non-nil, we assert the parsed TypeFilter in context equals this making sure params are not dropped.
}{
{
name: "validTagSharedSystem",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CSharedSystem", baseV3),
shouldFail: false,
description: "Valid tag in URL format should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|SharedSystem",
},
},
},
},
{
name: "validTagNationalClaimsHistory",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CNationalClaimsHistory", baseV3),
shouldFail: false,
description: "Valid NCH tag should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|NationalClaimsHistory",
},
},
},
},
{
name: "validTagSharedSystemComma",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CSharedSystem,https%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CFinalAction", baseV3),
shouldFail: false,
description: "Valid comma-separated tags should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|SharedSystem," + constants.BFDFinalActionURL + "|FinalAction",
},
},
},
},
{
name: "validOutcomeComma",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Foutcome%%3Dpartial,complete", baseV3),
shouldFail: false,
description: "Valid comma-separated outcome should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "outcome",
Value: "partial,complete",
},
},
},
},
{
name: "invalidOutcome",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Foutcome%%3Dinvalid_status", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Outcome must be complete or partial",
},
{
name: "validTagDDPS",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CDDPS", baseV3),
shouldFail: false,
description: "Valid DDPS tag should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|DDPS",
},
},
},
},
{
name: "validTagFinalAction",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CFinalAction", baseV3),
shouldFail: false,
description: "Valid FinalAction tag should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDFinalActionURL + "|FinalAction",
},
},
},
},
{
name: "validTagNotFinalAction",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CNotFinalAction", baseV3),
shouldFail: false,
description: "Valid NotFinalAction tag should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDFinalActionURL + "|NotFinalAction",
},
},
},
},
{
name: "invalidTagPartiallyAdjudicated",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3DPartiallyAdjudicated", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Old PartiallyAdjudicated tag should be rejected",
},
{
name: "invalidTagSharedSystem",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3DSharedSystem", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Only code, no system should be rejected. even with valid code",
},
{
name: "invalidTagRandomValue",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3DInvalidTag", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Random invalid tag should be rejected",
},
{
name: "multipleValidTags",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CNotFinalAction%%26_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CSharedSystem", baseV3),
shouldFail: false,
description: "Multiple valid tags should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "_tag",
Value: constants.BFDFinalActionURL + "|NotFinalAction",
},
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|SharedSystem",
},
},
},
},
{
name: "validTypeFilterServiceDateAndTag",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Fservice-date%%3Dlt2021-02-15%%26_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CFinalAction", baseV3),
shouldFail: false,
description: "Subquery with service-date and _tag (FinalAction) should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "service-date",
Value: "lt2021-02-15",
},
{
Name: "_tag",
Value: constants.BFDFinalActionURL + "|FinalAction",
},
},
},
},
{
name: "validTypeFilterServiceDateAndTagAlternate",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3Fservice-date%%3Dgt2001-04-01%%26_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FSystem-Type%%7CNationalClaimsHistory", baseV3),
shouldFail: false,
description: "Subquery with service-date and _tag (NationalClaimsHistory) should pass",
expectedTypeFilter: fhir.TypeFilterSubquery{
ResourceType: "ExplanationOfBenefit",
QueryParameters: []fhir.TypeFilterSubqueryParam{
{
Name: "service-date",
Value: "gt2001-04-01",
},
{
Name: "_tag",
Value: constants.BFDSystemTypeURL + "|NationalClaimsHistory",
},
},
},
},
{
name: "multipleTagsOneInvalid",
url: fmt.Sprintf("%s_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CNotFinalAction%%26_tag%%3DPartiallyAdjudicated", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Multiple tags with one invalid should fail",
},
{
name: "v2ShouldIgnoreTypeFilter",
url: fmt.Sprintf("/api/v2/Patient/$export?_typeFilter=ExplanationOfBenefit%%3F_tag%%3DPartiallyAdjudicated"),
shouldFail: false,
description: "v2 should ignore _typeFilter validation (old behavior preserved)",
},
{
name: "v1ShouldIgnoreTypeFilter",
url: fmt.Sprintf("/api/v1/Patient/$export?_typeFilter=ExplanationOfBenefit%%3F_tag%%3DPartiallyAdjudicated"),
shouldFail: false,
description: "v1 should ignore _typeFilter validation (old behavior preserved)",
},
{
name: "multipleTypeFilterSubqueries",
url: fmt.Sprintf("%s_type=ExplanationOfBenefit&_typeFilter=ExplanationOfBenefit%%3Fservice-date%%3Dlt2021-02-15%%26_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CFinalAction&_typeFilter=ExplanationOfBenefit%%3F_tag%%3Dhttps%%3A%%2F%%2Fbluebutton.cms.gov%%2Ffhir%%2FCodeSystem%%2FFinal-Action%%7CNotFinalAction", baseV3),
shouldFail: true,
expectedErr: fhir.ParameterValidationError{},
description: "Multiple _typeFilter params. Bulk IG supports this but we do not",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", tt.url, nil)
assert.NoError(t, err)
req = req.WithContext(ctx)
req.Header.Set("Accept", "application/fhir+json")
req.Header.Set("Prefer", constants.TestRespondAsync)
var capturedCtx context.Context
handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
capturedCtx = r.Context()
rw.WriteHeader(http.StatusOK)
})
rr := httptest.NewRecorder()
ValidateRequestURL(handler).ServeHTTP(rr, req)
if tt.shouldFail {
assert.Equal(t, http.StatusBadRequest, rr.Code, tt.description)
assert.Contains(t, rr.Body.String(), tt.expectedErr.Error(), tt.description)
} else {
assert.Equal(t, http.StatusOK, rr.Code, tt.description)
if tt.expectedTypeFilter.QueryParameters != nil {
rp, ok := GetRequestParamsFromCtx(capturedCtx)
assert.True(t, ok, "request params should be in context")
assert.Equal(t, tt.expectedTypeFilter, rp.TypeFilter, "parsed _typeFilter params should match request")
}
}
})
}
}
func TestGetRespWriter(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
path string
}{
{"v1", constants.V1Path},
{"v2", constants.V2Path},
{"v3", constants.V3Path},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rr := httptest.NewRecorder()
rw := auth.GetRespWriter(tt.path)
rw.OpOutcome(ctx, rr, http.StatusUnauthorized, responseutils.TokenErr, responseutils.TokenErr)
resp := rr.Body.String()
switch tt.name {
case "v1":
assert.NotContains(t, resp, "coding")
case "v2":
assert.Contains(t, resp, "coding")
case "v3":
assert.NotContains(t, resp, "coding")
}
})
}
}