Skip to content

Commit 5913a8e

Browse files
committed
fix: extract content type from matchers
Fixes failures when consumers use regex matchers for JSON Content-Type headers with optional charset parameters. Adds regression coverage for the v3 bug and confirms the same scenario already works in v4. Ref: PACT-6546 Signed-off-by: JP-Ellis <josh@jpellis.me>
1 parent d607973 commit 5913a8e

3 files changed

Lines changed: 78 additions & 4 deletions

File tree

src/pact.integration.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import path = require('path');
99

1010
// eslint-disable-next-line import/first
1111
import { PactV4 } from './v4';
12+
import { MatchersV3 } from './v3';
1213

1314
const { expect } = chai;
1415

@@ -157,6 +158,55 @@ describe('V4 Pact', () => {
157158
'http metadata test name'
158159
);
159160
});
161+
162+
it('supports regex matcher for response content-type with optional charset', () =>
163+
pact
164+
.addInteraction()
165+
.uponReceiving('a response with regex content-type header matcher')
166+
.withRequest('GET', '/')
167+
.willRespondWith(200, (builder) => {
168+
builder
169+
.headers({
170+
'Content-Type': MatchersV3.regex(
171+
/^application\/json(;\s?charset=[\w-]+)?$/i,
172+
'application/json'
173+
),
174+
})
175+
.jsonBody({
176+
foo: 'bar',
177+
});
178+
})
179+
.executeTest(async (server) => {
180+
const response = await axios.get(server.url);
181+
expect(response.data).to.deep.equal({ foo: 'bar' });
182+
}));
183+
184+
it('supports regex matcher for request content-type with optional charset', () =>
185+
pact
186+
.addInteraction()
187+
.uponReceiving('a request with regex content-type header matcher')
188+
.withRequest('POST', '/', (builder) => {
189+
builder
190+
.headers({
191+
'Content-Type': MatchersV3.regex(
192+
/^application\/json(;\s?charset=[\w-]+)?$/i,
193+
'application/json'
194+
),
195+
})
196+
.jsonBody({
197+
foo: 'bar',
198+
});
199+
})
200+
.willRespondWith(200, (builder) => {
201+
builder.jsonBody({
202+
ok: true,
203+
});
204+
})
205+
.executeTest((server) =>
206+
axios.post(server.url, {
207+
foo: 'bar',
208+
})
209+
));
160210
});
161211

162212
describe('Asynchronous message contract', () => {

src/v3/ffi.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as chai from 'chai';
2+
import { contentTypeFromHeaders } from './ffi';
3+
import { regex } from './matchers';
4+
5+
const { expect } = chai;
6+
7+
describe('V3 Pact FFI', () => {
8+
describe('#contentTypeFromHeaders', () => {
9+
it('uses matcher example value when content-type header is a matcher', () => {
10+
const headers = {
11+
'Content-Type': regex(
12+
/^application\/json(;\s?charset=[\w-]+)?$/i,
13+
'application/json'
14+
),
15+
};
16+
17+
expect(contentTypeFromHeaders(headers, 'application/json')).to.eq(
18+
'application/json'
19+
);
20+
});
21+
});
22+
});

src/v3/ffi.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { forEachObjIndexed } from 'ramda';
2-
import { ConsumerInteraction } from '@pact-foundation/pact-core';
3-
import { Matcher, TemplateHeaders, V3Request, V3Response } from './types';
2+
import type { ConsumerInteraction } from '@pact-foundation/pact-core';
3+
import type { Matcher, TemplateHeaders, V3Request, V3Response } from './types';
44
import * as MatchersV3 from './matchers';
55

66
type TemplateHeaderArrayValue = string[] | Matcher<string>[];
@@ -59,15 +59,17 @@ export const setResponseDetails = (
5959
}, res.headers);
6060
};
6161

62-
// TODO: this might need to consider an array of values
6362
export const contentTypeFromHeaders = (
6463
headers: TemplateHeaders | undefined,
6564
defaultContentType: string
6665
): string => {
6766
let contentType: string | Matcher<string> = defaultContentType;
6867
forEachObjIndexed((v, k) => {
6968
if (`${k}`.toLowerCase() === 'content-type') {
70-
contentType = MatchersV3.matcherValueOrString(v);
69+
const headerValue = Array.isArray(v) ? v[0] : v;
70+
contentType = MatchersV3.isMatcher(headerValue)
71+
? MatchersV3.matcherValueOrString(headerValue.value)
72+
: MatchersV3.matcherValueOrString(headerValue);
7173
}
7274
}, headers || {});
7375

0 commit comments

Comments
 (0)