Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions packages/mock/src/faker/resolvers/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,81 @@ export function resolveMockValue({
: { nullable: schemaReference.nullable }),
} as MockSchemaObject;

// When a discriminator parent ($ref-loaded schema with both `discriminator`
// and `oneOf`) is being expanded inside an `allOf` chain AND the chain
// is rooted at one of that parent's mapping targets (i.e. the current
// schema *is* a variant via `allOf: [parent, ...extras]`), the parent's
// `oneOf` is descriptive of the union, not additive to this specific
// variant. Re-expanding it inlines sibling factory calls into the derived
// variant's mock body (#2155). Drop the `oneOf` side here; the parent
// still contributes its own `properties` and other base attributes
// through the remaining schema fields.
//
// The mapping-target check guards against cases like
// `someField: allOf: [<discriminator parent>]` (e.g. #one-of-nested
// `Example2.expiry`), where the surrounding schema is NOT a variant of
// the parent and we still need the full union to randomize over.
//
// Symmetrically with the oneOf-side fix in `combineSchemasMock` (#3429),
// also drop the discriminator key from the parent's `properties`: each
// variant already carries a constrained discriminator value via
// `resolveDiscriminators`, so leaving the parent's free-choice enum in
// would just emit dead code (immediately shadowed by the variant's
// constrained value through spread merge).
if (
combine?.separator === 'allOf' &&
newSchema.discriminator &&
newSchema.oneOf
) {
const parentDiscriminator = newSchema.discriminator as {
propertyName?: string;
mapping?: Record<string, string>;
};
const mappingTargetNames = parentDiscriminator.mapping
? Object.values(parentDiscriminator.mapping).map((ref) =>
pascal(ref.split('/').pop() ?? ''),
)
: [];
const expandingAsVariant = existingReferencedProperties.some((refName) =>
mappingTargetNames.includes(refName),
);

if (expandingAsVariant) {
const mutableSchema = newSchema as Record<string, unknown>;
delete mutableSchema.oneOf;
const parentProperties = newSchema.properties as
| Record<string, unknown>
| undefined;
if (
parentDiscriminator.propertyName &&
parentProperties &&
parentDiscriminator.propertyName in parentProperties
) {
const remainingProperties = Object.fromEntries(
Object.entries(parentProperties).filter(
([key]) => key !== parentDiscriminator.propertyName,
),
);
if (Object.keys(remainingProperties).length === 0) {
delete mutableSchema.properties;
} else {
mutableSchema.properties = remainingProperties;
}
const parentRequired = newSchema.required as string[] | undefined;
if (Array.isArray(parentRequired)) {
const filteredRequired = parentRequired.filter(
(key) => key !== parentDiscriminator.propertyName,
);
if (filteredRequired.length === 0) {
delete mutableSchema.required;
} else {
mutableSchema.required = filteredRequired;
}
}
}
}
}

const newSeparator = newSchema.allOf
? 'allOf'
: newSchema.oneOf
Expand Down
109 changes: 109 additions & 0 deletions tests/__snapshots__/mock/discriminator-oneof-allof/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/
import axios from 'axios';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';

import type { DiscriminatorTest } from './model';

import { faker } from '@faker-js/faker';

import { HttpResponse, http } from 'msw';
import type { RequestHandlerOptions } from 'msw';

import type { Item1, Item2, Item3 } from './model';

export const getTest = (
options?: AxiosRequestConfig,
): Promise<AxiosResponse<DiscriminatorTest>> => {
return axios.get(`/test`, options);
};

export type GetTestResult = AxiosResponse<DiscriminatorTest>;

export const getGetTestResponseItem1Mock = (
overrideResponse: Partial<Item1> = {},
): Item1 => ({
...{
...{
...{
property1: faker.helpers.arrayElement([
faker.string.alpha({ length: { min: 10, max: 20 } }),
undefined,
]),
},
},
type: faker.helpers.arrayElement(['item1'] as const),
},
...overrideResponse,
});

export const getGetTestResponseItem2Mock = (
overrideResponse: Partial<Item2> = {},
): Item2 => ({
...{
...{
...{
property2: faker.helpers.arrayElement([
faker.string.alpha({ length: { min: 10, max: 20 } }),
undefined,
]),
},
},
type: faker.helpers.arrayElement(['item2'] as const),
},
...overrideResponse,
});

export const getGetTestResponseItem3Mock = (
overrideResponse: Partial<Item3> = {},
): Item3 => ({
...{
...{
...{
property3: faker.helpers.arrayElement([
faker.string.alpha({ length: { min: 10, max: 20 } }),
undefined,
]),
},
},
type: faker.helpers.arrayElement(['item3'] as const),
},
...overrideResponse,
});

export const getGetTestResponseMock = (): DiscriminatorTest =>
faker.helpers.arrayElement([
{ ...getGetTestResponseItem1Mock() },
{ ...getGetTestResponseItem2Mock() },
{ ...getGetTestResponseItem3Mock() },
]);

export const getGetTestMockHandler = (
overrideResponse?:
| DiscriminatorTest
| ((
info: Parameters<Parameters<typeof http.get>[1]>[0],
) => Promise<DiscriminatorTest> | DiscriminatorTest),
options?: RequestHandlerOptions,
) => {
return http.get(
'*/test',
async (info: Parameters<Parameters<typeof http.get>[1]>[0]) => {
return HttpResponse.json(
overrideResponse !== undefined
? typeof overrideResponse === 'function'
? await overrideResponse(info)
: overrideResponse
: getGetTestResponseMock(),
{ status: 200 },
);
},
options,
);
};
export const getDiscriminatorWithOneOfUnionAndAllOfInheritedVariantsMock =
() => [getGetTestMockHandler()];
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/
import type { DiscriminatorTestType } from './discriminatorTestType';
import type { Item1 } from './item1';
import type { Item2 } from './item2';
import type { Item3 } from './item3';

export type DiscriminatorTest =
| (Item1 & {
type: DiscriminatorTestType;
})
| (Item2 & {
type: DiscriminatorTestType;
})
| (Item3 & {
type: DiscriminatorTestType;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/

export type DiscriminatorTestType =
(typeof DiscriminatorTestType)[keyof typeof DiscriminatorTestType];

export const DiscriminatorTestType = {
item1: 'item1',
item2: 'item2',
item3: 'item3',
} as const;
15 changes: 15 additions & 0 deletions tests/__snapshots__/mock/discriminator-oneof-allof/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/

export * from './discriminatorTest';
export * from './discriminatorTestType';
export * from './item1';
export * from './item1Type';
export * from './item2';
export * from './item2Type';
export * from './item3';
export * from './item3Type';
13 changes: 13 additions & 0 deletions tests/__snapshots__/mock/discriminator-oneof-allof/model/item1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/
import type { DiscriminatorTest } from './discriminatorTest';
import type { Item1Type } from './item1Type';

export type Item1 = Omit<DiscriminatorTest, 'type'> & {
type: Item1Type;
property1?: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/

export type Item1Type = (typeof Item1Type)[keyof typeof Item1Type];

export const Item1Type = {
item1: 'item1',
} as const;
13 changes: 13 additions & 0 deletions tests/__snapshots__/mock/discriminator-oneof-allof/model/item2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/
import type { DiscriminatorTest } from './discriminatorTest';
import type { Item2Type } from './item2Type';

export type Item2 = Omit<DiscriminatorTest, 'type'> & {
type: Item2Type;
property2?: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/

export type Item2Type = (typeof Item2Type)[keyof typeof Item2Type];

export const Item2Type = {
item2: 'item2',
} as const;
13 changes: 13 additions & 0 deletions tests/__snapshots__/mock/discriminator-oneof-allof/model/item3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/
import type { DiscriminatorTest } from './discriminatorTest';
import type { Item3Type } from './item3Type';

export type Item3 = Omit<DiscriminatorTest, 'type'> & {
type: Item3Type;
property3?: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Generated by orval v8.12.3 🍺
* Do not edit manually.
* Discriminator with oneOf union and allOf-inherited variants
* OpenAPI spec version: 1.0
*/

export type Item3Type = (typeof Item3Type)[keyof typeof Item3Type];

export const Item3Type = {
item3: 'item3',
} as const;
36 changes: 36 additions & 0 deletions tests/api-generation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,39 @@ test('default issue-1775 preserves boolean enum literals across allOf+oneOf', as
expect(endpoints).toContain('arrayElement([false] as const)');
expect(endpoints).not.toMatch(/success: faker\.datatype\.boolean\(\)/);
});

test('mock issue-2155 keeps allOf-inherited variant mocks free of sibling factories', async () => {
// Regression for the second half of #2155: when a variant is shaped as
// `Item N = allOf:[<discriminator parent>, ...]`, resolveMockValue used to
// re-expand the parent's `oneOf` inside the allOf chain, inlining sibling
// factory calls into the derived variant's body. The fix in
// `packages/mock/src/faker/resolvers/value.ts` strips `oneOf` from the
// referenced parent when it is being expanded under an allOf separator,
// because the current schema is by construction a specific variant — the
// union side of the parent is descriptive, not additive.
const endpoints = await readFile(
generated('mock', 'discriminator-oneof-allof', 'endpoints.ts'),
'utf8',
);

// Each variant's factory body must only describe its own properties and the
// mapping-constrained discriminator value — no cross-variant factory calls.
const variantBlocks = [
['getGetTestResponseItem1Mock', /getGetTestResponseItem[23]Mock/],
['getGetTestResponseItem2Mock', /getGetTestResponseItem[13]Mock/],
['getGetTestResponseItem3Mock', /getGetTestResponseItem[12]Mock/],
] as const;
for (const [funcName, siblingPattern] of variantBlocks) {
const start = endpoints.indexOf(`export const ${funcName}`);
expect(start, `${funcName} should be generated`).toBeGreaterThan(-1);

const nextExport = endpoints.indexOf('export const ', start + 1);
const end = nextExport === -1 ? endpoints.length : nextExport;

const block = endpoints.slice(start, end);
expect(
block,
`${funcName} must not reference sibling factories`,
).not.toMatch(siblingPattern);
}
});
12 changes: 12 additions & 0 deletions tests/configs/mock.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,18 @@ export default defineConfig({
target: '../specifications/discriminator-oneof-union.yaml',
},
},
discriminatorOneofAllof: {
output: {
target: '../generated/mock/discriminator-oneof-allof/endpoints.ts',
schemas: '../generated/mock/discriminator-oneof-allof/model',
mock: true,
clean: true,
formatter: 'prettier',
},
input: {
target: '../specifications/discriminator-oneof-allof.yaml',
},
},
mswMixedContentUnion: {
output: {
target: '../generated/mock/msw-mixed-content-union/endpoints.ts',
Expand Down
Loading
Loading