Skip to content

Commit c5ff7bc

Browse files
authored
fix(mock): stop leaking sibling variants into allOf-inherited mocks (#3431)
1 parent 61d5dfc commit c5ff7bc

15 files changed

Lines changed: 436 additions & 1 deletion

File tree

packages/mock/src/faker/resolvers/value.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,81 @@ export function resolveMockValue({
193193
: { nullable: schemaReference.nullable }),
194194
} as MockSchemaObject;
195195

196+
// When a discriminator parent ($ref-loaded schema with both `discriminator`
197+
// and `oneOf`) is being expanded inside an `allOf` chain AND the chain
198+
// is rooted at one of that parent's mapping targets (i.e. the current
199+
// schema *is* a variant via `allOf: [parent, ...extras]`), the parent's
200+
// `oneOf` is descriptive of the union, not additive to this specific
201+
// variant. Re-expanding it inlines sibling factory calls into the derived
202+
// variant's mock body (#2155). Drop the `oneOf` side here; the parent
203+
// still contributes its own `properties` and other base attributes
204+
// through the remaining schema fields.
205+
//
206+
// The mapping-target check guards against cases like
207+
// `someField: allOf: [<discriminator parent>]` (e.g. #one-of-nested
208+
// `Example2.expiry`), where the surrounding schema is NOT a variant of
209+
// the parent and we still need the full union to randomize over.
210+
//
211+
// Symmetrically with the oneOf-side fix in `combineSchemasMock` (#3429),
212+
// also drop the discriminator key from the parent's `properties`: each
213+
// variant already carries a constrained discriminator value via
214+
// `resolveDiscriminators`, so leaving the parent's free-choice enum in
215+
// would just emit dead code (immediately shadowed by the variant's
216+
// constrained value through spread merge).
217+
if (
218+
combine?.separator === 'allOf' &&
219+
newSchema.discriminator &&
220+
newSchema.oneOf
221+
) {
222+
const parentDiscriminator = newSchema.discriminator as {
223+
propertyName?: string;
224+
mapping?: Record<string, string>;
225+
};
226+
const mappingTargetNames = parentDiscriminator.mapping
227+
? Object.values(parentDiscriminator.mapping).map((ref) =>
228+
pascal(ref.split('/').pop() ?? ''),
229+
)
230+
: [];
231+
const expandingAsVariant = existingReferencedProperties.some((refName) =>
232+
mappingTargetNames.includes(refName),
233+
);
234+
235+
if (expandingAsVariant) {
236+
const mutableSchema = newSchema as Record<string, unknown>;
237+
delete mutableSchema.oneOf;
238+
const parentProperties = newSchema.properties as
239+
| Record<string, unknown>
240+
| undefined;
241+
if (
242+
parentDiscriminator.propertyName &&
243+
parentProperties &&
244+
parentDiscriminator.propertyName in parentProperties
245+
) {
246+
const remainingProperties = Object.fromEntries(
247+
Object.entries(parentProperties).filter(
248+
([key]) => key !== parentDiscriminator.propertyName,
249+
),
250+
);
251+
if (Object.keys(remainingProperties).length === 0) {
252+
delete mutableSchema.properties;
253+
} else {
254+
mutableSchema.properties = remainingProperties;
255+
}
256+
const parentRequired = newSchema.required as string[] | undefined;
257+
if (Array.isArray(parentRequired)) {
258+
const filteredRequired = parentRequired.filter(
259+
(key) => key !== parentDiscriminator.propertyName,
260+
);
261+
if (filteredRequired.length === 0) {
262+
delete mutableSchema.required;
263+
} else {
264+
mutableSchema.required = filteredRequired;
265+
}
266+
}
267+
}
268+
}
269+
}
270+
196271
const newSeparator = newSchema.allOf
197272
? 'allOf'
198273
: newSchema.oneOf
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
import axios from 'axios';
8+
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
9+
10+
import type { DiscriminatorTest } from './model';
11+
12+
import { faker } from '@faker-js/faker';
13+
14+
import { HttpResponse, http } from 'msw';
15+
import type { RequestHandlerOptions } from 'msw';
16+
17+
import type { Item1, Item2, Item3 } from './model';
18+
19+
export const getTest = (
20+
options?: AxiosRequestConfig,
21+
): Promise<AxiosResponse<DiscriminatorTest>> => {
22+
return axios.get(`/test`, options);
23+
};
24+
25+
export type GetTestResult = AxiosResponse<DiscriminatorTest>;
26+
27+
export const getGetTestResponseItem1Mock = (
28+
overrideResponse: Partial<Item1> = {},
29+
): Item1 => ({
30+
...{
31+
...{
32+
...{
33+
property1: faker.helpers.arrayElement([
34+
faker.string.alpha({ length: { min: 10, max: 20 } }),
35+
undefined,
36+
]),
37+
},
38+
},
39+
type: faker.helpers.arrayElement(['item1'] as const),
40+
},
41+
...overrideResponse,
42+
});
43+
44+
export const getGetTestResponseItem2Mock = (
45+
overrideResponse: Partial<Item2> = {},
46+
): Item2 => ({
47+
...{
48+
...{
49+
...{
50+
property2: faker.helpers.arrayElement([
51+
faker.string.alpha({ length: { min: 10, max: 20 } }),
52+
undefined,
53+
]),
54+
},
55+
},
56+
type: faker.helpers.arrayElement(['item2'] as const),
57+
},
58+
...overrideResponse,
59+
});
60+
61+
export const getGetTestResponseItem3Mock = (
62+
overrideResponse: Partial<Item3> = {},
63+
): Item3 => ({
64+
...{
65+
...{
66+
...{
67+
property3: faker.helpers.arrayElement([
68+
faker.string.alpha({ length: { min: 10, max: 20 } }),
69+
undefined,
70+
]),
71+
},
72+
},
73+
type: faker.helpers.arrayElement(['item3'] as const),
74+
},
75+
...overrideResponse,
76+
});
77+
78+
export const getGetTestResponseMock = (): DiscriminatorTest =>
79+
faker.helpers.arrayElement([
80+
{ ...getGetTestResponseItem1Mock() },
81+
{ ...getGetTestResponseItem2Mock() },
82+
{ ...getGetTestResponseItem3Mock() },
83+
]);
84+
85+
export const getGetTestMockHandler = (
86+
overrideResponse?:
87+
| DiscriminatorTest
88+
| ((
89+
info: Parameters<Parameters<typeof http.get>[1]>[0],
90+
) => Promise<DiscriminatorTest> | DiscriminatorTest),
91+
options?: RequestHandlerOptions,
92+
) => {
93+
return http.get(
94+
'*/test',
95+
async (info: Parameters<Parameters<typeof http.get>[1]>[0]) => {
96+
return HttpResponse.json(
97+
overrideResponse !== undefined
98+
? typeof overrideResponse === 'function'
99+
? await overrideResponse(info)
100+
: overrideResponse
101+
: getGetTestResponseMock(),
102+
{ status: 200 },
103+
);
104+
},
105+
options,
106+
);
107+
};
108+
export const getDiscriminatorWithOneOfUnionAndAllOfInheritedVariantsMock =
109+
() => [getGetTestMockHandler()];
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
import type { DiscriminatorTestType } from './discriminatorTestType';
8+
import type { Item1 } from './item1';
9+
import type { Item2 } from './item2';
10+
import type { Item3 } from './item3';
11+
12+
export type DiscriminatorTest =
13+
| (Item1 & {
14+
type: DiscriminatorTestType;
15+
})
16+
| (Item2 & {
17+
type: DiscriminatorTestType;
18+
})
19+
| (Item3 & {
20+
type: DiscriminatorTestType;
21+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
8+
export type DiscriminatorTestType =
9+
(typeof DiscriminatorTestType)[keyof typeof DiscriminatorTestType];
10+
11+
export const DiscriminatorTestType = {
12+
item1: 'item1',
13+
item2: 'item2',
14+
item3: 'item3',
15+
} as const;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
8+
export * from './discriminatorTest';
9+
export * from './discriminatorTestType';
10+
export * from './item1';
11+
export * from './item1Type';
12+
export * from './item2';
13+
export * from './item2Type';
14+
export * from './item3';
15+
export * from './item3Type';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
import type { DiscriminatorTest } from './discriminatorTest';
8+
import type { Item1Type } from './item1Type';
9+
10+
export type Item1 = Omit<DiscriminatorTest, 'type'> & {
11+
type: Item1Type;
12+
property1?: string;
13+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
8+
export type Item1Type = (typeof Item1Type)[keyof typeof Item1Type];
9+
10+
export const Item1Type = {
11+
item1: 'item1',
12+
} as const;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
import type { DiscriminatorTest } from './discriminatorTest';
8+
import type { Item2Type } from './item2Type';
9+
10+
export type Item2 = Omit<DiscriminatorTest, 'type'> & {
11+
type: Item2Type;
12+
property2?: string;
13+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
8+
export type Item2Type = (typeof Item2Type)[keyof typeof Item2Type];
9+
10+
export const Item2Type = {
11+
item2: 'item2',
12+
} as const;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Generated by orval v8.12.3 🍺
3+
* Do not edit manually.
4+
* Discriminator with oneOf union and allOf-inherited variants
5+
* OpenAPI spec version: 1.0
6+
*/
7+
import type { DiscriminatorTest } from './discriminatorTest';
8+
import type { Item3Type } from './item3Type';
9+
10+
export type Item3 = Omit<DiscriminatorTest, 'type'> & {
11+
type: Item3Type;
12+
property3?: string;
13+
};

0 commit comments

Comments
 (0)