From 3417c956d772bedf438a6176c9f8a41ae64e2be0 Mon Sep 17 00:00:00 2001 From: Oprysk Date: Tue, 23 Nov 2021 17:39:44 +0200 Subject: [PATCH] cover all number range cases & add unit tests --- src/utils/__tests__/openapi.test.ts | 91 ++++++++++++++++++++++++----- src/utils/openapi.ts | 63 +++++++++++--------- 2 files changed, 110 insertions(+), 44 deletions(-) diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts index 6f40d974..059220d0 100644 --- a/src/utils/__tests__/openapi.test.ts +++ b/src/utils/__tests__/openapi.test.ts @@ -10,6 +10,7 @@ import { pluralizeType, serializeParameterValue, sortByRequired, + humanizeNumberRange, } from '../'; import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services'; @@ -103,7 +104,7 @@ describe('Utils', () => { it('Should return pathName if no summary, operationId, description', () => { const operation = { - pathName: '/sandbox/test' + pathName: '/sandbox/test', }; expect(getOperationSummary(operation as any)).toBe('/sandbox/test'); }); @@ -141,9 +142,9 @@ describe('Utils', () => { object: ['maxProperties', 'minProperties', 'required', 'additionalProperties', 'properties'], }; - Object.keys(tests).forEach(name => { + Object.keys(tests).forEach((name) => { it(`Should detect ${name} if ${name} properties are present`, () => { - tests[name].forEach(propName => { + tests[name].forEach((propName) => { expect( detectType({ [propName]: 0, @@ -174,7 +175,7 @@ describe('Utils', () => { expect(isPrimitiveType(schema)).toEqual(false); }); - it('should return true for array contains object and schema hasn\'t properties', () => { + it("should return true for array contains object and schema hasn't properties", () => { const schema = { type: ['object', 'string'], }; @@ -231,10 +232,10 @@ describe('Utils', () => { const schema = { type: 'array', items: { - type: 'array', - items: { - type: 'string' - }, + type: 'array', + items: { + type: 'string', + }, }, }; expect(isPrimitiveType(schema)).toEqual(true); @@ -410,12 +411,72 @@ describe('Utils', () => { }); }); + describe('openapi humanizeNumberRange', () => { + it('should return `>=` when only minimum value present or exclusiveMinimum = false', () => { + const expected = '>= 0'; + expect(humanizeNumberRange({ minimum: 0 })).toEqual(expected); + expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: false })).toEqual(expected); + }); + + it('should return `>` when minimum value present and exclusiveMinimum set to true', () => { + expect(humanizeNumberRange({ minimum: 0, exclusiveMinimum: true })).toEqual('> 0'); + }); + + it('should return `<=` when only maximum value present or exclusiveMinimum = false', () => { + const expected = '<= 10'; + expect(humanizeNumberRange({ maximum: 10 })).toEqual(expected); + expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: false })).toEqual(expected); + }); + + it('should return `<` when maximum value present and exclusiveMaximum set to true', () => { + expect(humanizeNumberRange({ maximum: 10, exclusiveMaximum: true })).toEqual('< 10'); + }); + + it('should return correct range for minimum and maximum values and with different exclusive set', () => { + expect(humanizeNumberRange({ minimum: 0, maximum: 10 })).toEqual('[ 0 .. 10 ]'); + expect( + humanizeNumberRange({ + minimum: 0, + exclusiveMinimum: true, + maximum: 10, + exclusiveMaximum: true, + }), + ).toEqual('( 0 .. 10 )'); + expect( + humanizeNumberRange({ + minimum: 0, + maximum: 10, + exclusiveMaximum: true, + }), + ).toEqual('[ 0 .. 10 )'); + expect( + humanizeNumberRange({ + minimum: 0, + exclusiveMinimum: true, + maximum: 10, + }), + ).toEqual('( 0 .. 10 ]'); + }); + + it('should return correct range exclusive values only', () => { + expect(humanizeNumberRange({ exclusiveMinimum: 0 })).toEqual('> 0'); + expect(humanizeNumberRange({ exclusiveMaximum: 10 })).toEqual('< 10'); + expect(humanizeNumberRange({ exclusiveMinimum: 0, exclusiveMaximum: 10 })).toEqual( + '( 0 .. 10 )', + ); + }); + + it('should return undefined', () => { + expect(humanizeNumberRange({})).toEqual(undefined); + }); + }); + describe('openapi humanizeConstraints', () => { const itemConstraintSchema = ( min?: number, max?: number, multipleOf?: number, - uniqueItems?: boolean + uniqueItems?: boolean, ) => ({ type: 'array', minItems: min, maxItems: max, multipleOf, uniqueItems }); it('should not have a humanized constraint without schema constraints', () => { @@ -455,9 +516,9 @@ describe('Utils', () => { }); it('should have a humanized constraint when uniqueItems is set', () => { - expect(humanizeConstraints(itemConstraintSchema(undefined, undefined, undefined, true))).toContain( - 'unique', - ); + expect( + humanizeConstraints(itemConstraintSchema(undefined, undefined, undefined, true)), + ).toContain('unique'); }); }); @@ -656,11 +717,11 @@ describe('Utils', () => { }, ]; - testCases.forEach(locationTestGroup => { + testCases.forEach((locationTestGroup) => { describe(locationTestGroup.description, () => { - locationTestGroup.cases.forEach(valueTypeTestGroup => { + locationTestGroup.cases.forEach((valueTypeTestGroup) => { describe(valueTypeTestGroup.description, () => { - valueTypeTestGroup.cases.forEach(testCase => { + valueTypeTestGroup.cases.forEach((testCase) => { it(`should serialize correctly when style is ${testCase.style} and explode is ${testCase.explode}`, () => { const parameter: OpenAPIParameter = { name: locationTestGroup.name, diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index e1bd97c2..f69fb2ad 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -419,6 +419,39 @@ function humanizeRangeConstraint( return stringRange; } +export function humanizeNumberRange(schema: OpenAPISchema): string | undefined { + let result; + + const minimum = + typeof schema.exclusiveMinimum === 'number' + ? Math.min(schema.exclusiveMinimum, schema.minimum ?? Infinity) + : schema.minimum; + const maximum = + typeof schema.exclusiveMaximum === 'number' + ? Math.min(schema.exclusiveMaximum, schema.maximum ?? Infinity) + : schema.maximum; + const exclusiveMinimum = + typeof schema.exclusiveMinimum === 'number' ? true : schema.exclusiveMinimum; + const exclusiveMaximum = + typeof schema.exclusiveMaximum === 'number' ? true : schema.exclusiveMaximum; + + if (minimum !== undefined && maximum !== undefined) { + result = exclusiveMinimum ? '( ' : '[ '; + result += minimum; + result += ' .. '; + result += maximum; + result += exclusiveMaximum ? ' )' : ' ]'; + } else if (maximum !== undefined) { + result = exclusiveMaximum ? '< ' : '<= '; + result += maximum; + } else if (minimum !== undefined) { + result = exclusiveMinimum ? '> ' : '>= '; + result += minimum; + } + + return result; +} + export function humanizeConstraints(schema: OpenAPISchema): string[] { const res: string[] = []; @@ -437,35 +470,7 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] { res.push(multipleOfConstraint); } - let numberRange; - if (schema.minimum !== undefined && schema.maximum !== undefined) { - numberRange = schema.exclusiveMinimum ? '( ' : '[ '; - numberRange += schema.minimum; - numberRange += ' .. '; - numberRange += schema.maximum; - numberRange += schema.exclusiveMaximum ? ' )' : ' ]'; - } else if (schema.maximum !== undefined) { - numberRange = schema.exclusiveMaximum ? '< ' : '<= '; - numberRange += schema.maximum; - } else if (schema.minimum !== undefined) { - numberRange = schema.exclusiveMinimum ? '> ' : '>= '; - numberRange += schema.minimum; - } - - if (typeof schema.exclusiveMinimum === 'number' && typeof schema.exclusiveMaximum === 'number') { - numberRange = '['; - numberRange += schema.exclusiveMinimum; - numberRange += ' .. '; - numberRange += schema.exclusiveMaximum; - numberRange += ']'; - } else if (typeof schema.exclusiveMinimum === 'number') { - numberRange = '> '; - numberRange += schema.exclusiveMinimum; - } else if (typeof schema.exclusiveMaximum === 'number') { - numberRange = '< '; - numberRange += schema.exclusiveMaximum; - } - + const numberRange = humanizeNumberRange(schema); if (numberRange !== undefined) { res.push(numberRange); }