mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-07 05:44:30 +00:00
33dd0d3d7c
Part of RND-13222
284 lines
9.4 KiB
TypeScript
284 lines
9.4 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
|
|
import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
|
|
|
import {
|
|
extractNonNullTypes,
|
|
getEffectiveArrayType,
|
|
getSchemaTitle,
|
|
normalizeNullableUnion,
|
|
} from './utils';
|
|
|
|
describe('getSchemaTitle', () => {
|
|
it('should handle OpenAPI 3.1 nullable array with reference items', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
$ref: '#/components/schemas/ProductPayoutSplitDto',
|
|
},
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems, so type should not include | null
|
|
expect(getSchemaTitle(schema)).toBe('ProductPayoutSplitDto[]');
|
|
});
|
|
|
|
it('should handle OpenAPI 3.1 nullable array with object items', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems, so type should not include | null
|
|
expect(getSchemaTitle(schema)).toBe('string[]');
|
|
});
|
|
|
|
it('should handle OpenAPI 3.0 nullable array (backward compatibility)', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: 'array',
|
|
nullable: true,
|
|
items: {
|
|
$ref: '#/components/schemas/ProductPayoutSplitDto',
|
|
},
|
|
};
|
|
|
|
expect(getSchemaTitle(schema)).toBe('ProductPayoutSplitDto[]');
|
|
});
|
|
|
|
it('should handle non-nullable array with reference items', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: 'array',
|
|
items: {
|
|
$ref: '#/components/schemas/ProductPayoutSplitDto',
|
|
},
|
|
};
|
|
|
|
expect(getSchemaTitle(schema)).toBe('ProductPayoutSplitDto[]');
|
|
});
|
|
|
|
it('should handle nullable union types (non-array)', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'string'],
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems, so type should not include | null
|
|
expect(getSchemaTitle(schema)).toBe('string');
|
|
});
|
|
|
|
it('should handle multiple union types', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['string', 'number', 'null'],
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems, so type should show non-null types only
|
|
expect(getSchemaTitle(schema)).toBe('string | number');
|
|
});
|
|
|
|
it('should handle array with nested object items', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems
|
|
expect(getSchemaTitle(schema)).toBe('object[]');
|
|
});
|
|
|
|
it('should handle array with primitive items', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
type: 'integer',
|
|
},
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems
|
|
expect(getSchemaTitle(schema)).toBe('integer[]');
|
|
});
|
|
|
|
it('should handle the exact payout_splits use case', () => {
|
|
// This matches the exact schema from the user's query
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
$ref: '#/components/schemas/ProductPayoutSplitDto',
|
|
},
|
|
description: 'The payout splits for revenue distribution.\nRequires special approval.',
|
|
};
|
|
|
|
// Nullable is handled separately in getAdditionalItems, so type should be ProductPayoutSplitDto[]
|
|
expect(getSchemaTitle(schema)).toBe('ProductPayoutSplitDto[]');
|
|
});
|
|
});
|
|
|
|
describe('getEffectiveArrayType', () => {
|
|
it('should detect array in OpenAPI 3.1 union type', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['null', 'array'],
|
|
items: {
|
|
$ref: '#/components/schemas/ProductPayoutSplitDto',
|
|
},
|
|
};
|
|
|
|
const result = getEffectiveArrayType(schema);
|
|
expect(result.isArray).toBe(true);
|
|
expect(result.hasNull).toBe(true);
|
|
expect(result.items).toBeDefined();
|
|
});
|
|
|
|
it('should detect array without null', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
};
|
|
|
|
const result = getEffectiveArrayType(schema);
|
|
expect(result.isArray).toBe(true);
|
|
expect(result.hasNull).toBe(false);
|
|
expect(result.items).toBeDefined();
|
|
});
|
|
|
|
it('should return false for non-array types', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: 'string',
|
|
};
|
|
|
|
const result = getEffectiveArrayType(schema);
|
|
expect(result.isArray).toBe(false);
|
|
expect(result.hasNull).toBe(false);
|
|
});
|
|
|
|
it('should handle union type without array', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
type: ['string', 'number'],
|
|
};
|
|
|
|
const result = getEffectiveArrayType(schema);
|
|
expect(result.isArray).toBe(false);
|
|
expect(result.hasNull).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('extractNonNullTypes', () => {
|
|
it('should extract non-null types and detect null', () => {
|
|
const result = extractNonNullTypes(['null', 'array', 'string']);
|
|
expect(result.nonNullTypes).toEqual(['array', 'string']);
|
|
expect(result.hasNull).toBe(true);
|
|
});
|
|
|
|
it('should handle types without null', () => {
|
|
const result = extractNonNullTypes(['string', 'number']);
|
|
expect(result.nonNullTypes).toEqual(['string', 'number']);
|
|
expect(result.hasNull).toBe(false);
|
|
});
|
|
|
|
it('should handle only null', () => {
|
|
const result = extractNonNullTypes(['null']);
|
|
expect(result.nonNullTypes).toEqual([]);
|
|
expect(result.hasNull).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('normalizeNullableUnion', () => {
|
|
it('should return the same object for the same input', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'null' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toBe(normalizeNullableUnion(schema));
|
|
});
|
|
|
|
it('should collapse anyOf with a single non-null member into a nullable schema', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
anyOf: [{ type: 'string' }, { type: 'null' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({ type: 'string', nullable: true });
|
|
});
|
|
|
|
it('should collapse oneOf with a single object member into a nullable object schema', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
oneOf: [
|
|
{
|
|
type: 'object',
|
|
properties: { id: { type: 'string' } },
|
|
},
|
|
{ type: 'null' },
|
|
],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({
|
|
type: 'object',
|
|
properties: { id: { type: 'string' } },
|
|
nullable: true,
|
|
});
|
|
});
|
|
|
|
it('should handle a null member expressed as type: ["null"]', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
anyOf: [{ type: 'integer' }, { type: ['null'] }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({ type: 'integer', nullable: true });
|
|
});
|
|
|
|
it('should keep the union (without null) and flag nullable for multiple non-null members', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'null' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({
|
|
anyOf: [{ type: 'string' }, { type: 'number' }],
|
|
nullable: true,
|
|
});
|
|
});
|
|
|
|
it('should preserve outer-level metadata over the collapsed member', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
description: 'Outer description',
|
|
anyOf: [{ type: 'string', description: 'Member description' }, { type: 'null' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({
|
|
type: 'string',
|
|
description: 'Outer description',
|
|
nullable: true,
|
|
});
|
|
});
|
|
|
|
it('should return the schema unchanged when there is no null member', () => {
|
|
const schema: OpenAPIV3.SchemaObject = {
|
|
anyOf: [{ type: 'string' }, { type: 'number' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toBe(schema);
|
|
});
|
|
|
|
it('should return the schema unchanged when there is no union', () => {
|
|
const schema: OpenAPIV3.SchemaObject = { type: 'string' };
|
|
|
|
expect(normalizeNullableUnion(schema)).toBe(schema);
|
|
});
|
|
|
|
it('should keep a $ref member as a nullable union rather than inlining it', () => {
|
|
const schema: OpenAPIV3_1.SchemaObject = {
|
|
anyOf: [{ $ref: '#/components/schemas/Foo' }, { type: 'null' }],
|
|
};
|
|
|
|
expect(normalizeNullableUnion(schema)).toEqual({
|
|
anyOf: [{ $ref: '#/components/schemas/Foo' }],
|
|
nullable: true,
|
|
});
|
|
});
|
|
});
|