From 7eaeb789d85fc2db7eef9e886daaa81279f77f55 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 26 May 2022 09:21:56 -0400 Subject: [PATCH] fix: added test for mapInputHelper #none Signed-off-by: James --- .../Launch/LaunchForm/__mocks__/utils.ts | 28 ++++++ .../Launch/LaunchForm/inputHelpers/map.ts | 30 +++++- .../inputHelpers/test/inputHelpers.test.ts | 91 +++++++++++++++++++ .../LaunchForm/inputHelpers/test/testCases.ts | 8 +- .../Launch/LaunchForm/inputHelpers/utils.ts | 6 +- .../console/src/components/common/strings.ts | 9 ++ 6 files changed, 162 insertions(+), 10 deletions(-) diff --git a/packages/zapp/console/src/components/Launch/LaunchForm/__mocks__/utils.ts b/packages/zapp/console/src/components/Launch/LaunchForm/__mocks__/utils.ts index b6ccd6281..73f1356ea 100644 --- a/packages/zapp/console/src/components/Launch/LaunchForm/__mocks__/utils.ts +++ b/packages/zapp/console/src/components/Launch/LaunchForm/__mocks__/utils.ts @@ -51,3 +51,31 @@ export function nestedCollectionInputTypeDefinition( }, }; } + +export function mapInputTypeDefinition(typeDefinition: InputTypeDefinition): InputTypeDefinition { + return { + literalType: { + mapValueType: typeDefinition.literalType, + }, + type: InputType.Map, + subtype: typeDefinition, + }; +} + +export function nestedMapInputTypeDefinition( + typeDefinition: InputTypeDefinition, +): InputTypeDefinition { + return { + literalType: { + mapValueType: { + mapValueType: typeDefinition.literalType, + }, + }, + type: InputType.Map, + subtype: { + literalType: { mapValueType: typeDefinition.literalType }, + type: InputType.Map, + subtype: typeDefinition, + }, + }; +} diff --git a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/map.ts b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/map.ts index 75e9c3d42..fe2834639 100644 --- a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/map.ts +++ b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/map.ts @@ -4,6 +4,16 @@ import { InputTypeDefinition, InputValue } from '../types'; import { getHelperForInput } from './getHelperForInput'; import { ConverterInput, InputHelper, InputValidatorParams } from './types'; import t from '../../../common/strings'; +import { parseJSON } from './parseJson'; +import { literalNone } from './constants'; + +function parseMap(map: string) { + const parsed = parseJSON(map); + if (typeof parsed !== 'object') { + throw new Error(t('valueNotParse')); + } + return parsed; +} function fromLiteral(literal: Core.ILiteral, { subtype }: InputTypeDefinition): InputValue { if (!subtype) { @@ -33,13 +43,23 @@ function toLiteral({ value, typeDefinition: { subtype } }: ConverterInput): Core if (!subtype) { throw new Error(t('missingMapSubType')); } - const obj = JSON.parse(value.toString()); - const key = Object.keys(obj)?.[0]; + let parsed: { [key: string]: any }; + // If we're processing a nested map, it may already have been parsed + if (typeof value === 'object') { + parsed = value; + } else { + const stringValue = typeof value === 'string' ? value : value.toString(); + if (!stringValue.length) { + return literalNone(); + } + parsed = parseMap(stringValue); + } + const key = Object.keys(parsed)?.[0]; const helper = getHelperForInput(subtype.type); return { - map: { literals: { [key]: helper.toLiteral({ value: obj[key], typeDefinition: subtype }) } }, + map: { literals: { [key]: helper.toLiteral({ value: parsed[key], typeDefinition: subtype }) } }, }; } @@ -54,11 +74,11 @@ function validate({ value, typeDefinition: { subtype } }: InputValidatorParams) throw new Error(t('valueRequired')); } try { - JSON.parse(value.toString()); + parseMap(value); } catch (e) { throw new Error(t('valueNotParse')); } - const obj = JSON.parse(value.toString()); + const obj = parseJSON(value); if (!Object.keys(obj).length || !Object.keys(obj)[0].trim().length) { throw new Error(t('valueKeyRequired')); } diff --git a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts index a6a2c47bf..d84d83318 100644 --- a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts +++ b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/inputHelpers.test.ts @@ -5,7 +5,9 @@ import { BlobDimensionality, SimpleType } from 'models/Common/types'; import { InputProps, InputType, InputTypeDefinition } from '../../types'; import { collectionInputTypeDefinition, + mapInputTypeDefinition, nestedCollectionInputTypeDefinition, + nestedMapInputTypeDefinition, primitiveLiteral, } from '../../__mocks__/utils'; import { literalNone } from '../constants'; @@ -34,6 +36,22 @@ function makeSimpleInput(typeDefinition: InputTypeDefinition, value: any): Input return { ...baseInputProps, value, typeDefinition }; } +function makeMapInput(typeDefinition: InputTypeDefinition, value: string): InputProps { + return { + ...baseInputProps, + value, + typeDefinition: mapInputTypeDefinition(typeDefinition), + }; +} + +function makeNestedMapInput(typeDefinition: InputTypeDefinition, value: string): InputProps { + return { + ...baseInputProps, + value, + typeDefinition: nestedMapInputTypeDefinition(typeDefinition), + }; +} + function makeCollectionInput(typeDefinition: InputTypeDefinition, value: string): InputProps { return { ...baseInputProps, @@ -66,6 +84,36 @@ describe('literalToInputValue', () => { expect(literalToInputValue(inputTypes.none, literalNone())).toEqual(undefined)); }); + describe('Map', () => { + literalToInputTestCases.map(([typeDefinition, input, output]) => { + it(`should correctly convert map of ${typeDefinition.type}: ${stringifyValue(input)}`, () => { + const map: Core.ILiteral = { + map: { + literals: { a: input }, + }, + }; + const expectedString = stringifyValue({ a: output }); + const result = literalToInputValue(mapInputTypeDefinition(typeDefinition), map); + expect(result).toEqual(expectedString); + }); + }); + + it('should return empty for noneType literals', () => { + const map: Core.ILiteral = { + map: { + literals: { a: literalNone() }, + }, + }; + + const typeDefinition: InputTypeDefinition = { + literalType: { simple: Core.SimpleType.NONE }, + type: InputType.None, + }; + + expect(literalToInputValue(mapInputTypeDefinition(typeDefinition), map)).toEqual('{}'); + }); + }); + describe('Collections', () => { literalToInputTestCases.map(([typeDefinition, input, output]) => { it(`should correctly convert collection of ${typeDefinition.type}: ${stringifyValue( @@ -128,6 +176,49 @@ describe('inputToLiteral', () => { }); }); + describe('Map', () => { + literalTestCases.map(([typeDefinition, input, output]) => { + let singleMapValue: any; + let nestedMapValue: any; + if (typeDefinition.type === InputType.Struct) { + const objValue = JSON.parse(input); + singleMapValue = stringifyValue({ a: objValue }); + nestedMapValue = stringifyValue({ a: { b: objValue } }); + } else if (['boolean', 'number'].includes(typeof input)) { + singleMapValue = `{"a":${input}}`; + nestedMapValue = `{"a":{"b":${input}}}`; + } else if (input == null) { + singleMapValue = '{"a":null}'; + nestedMapValue = '{"a":{"b":null}}'; + } else if (typeof input === 'string' || Long.isLong(input)) { + singleMapValue = `{"a":"${input}"}`; + nestedMapValue = `{"a":{"b":"${input}"}}`; + } else if (input instanceof Date) { + const dateString = input.toISOString(); + singleMapValue = `{"a":"${dateString}"}`; + nestedMapValue = `{"a":{"b":"${dateString}"}}`; + } else { + const stringValue = stringifyValue(input); + singleMapValue = `{"a":${stringValue}}`; + nestedMapValue = `{"a":{"b":${stringValue}}}`; + } + + it(`should correctly convert map of type ${ + typeDefinition.type + }: ${singleMapValue} (${typeof input})`, () => { + const result = inputToLiteral(makeMapInput(typeDefinition, singleMapValue)); + expect(result.map!.literals!.a).toEqual(output); + }); + + it(`should correctly convert nested map of type ${ + typeDefinition.type + }: ${nestedMapValue} (${typeof input})`, () => { + const result = inputToLiteral(makeNestedMapInput(typeDefinition, nestedMapValue)); + expect(result.map!.literals!.a.map!.literals!.b).toEqual(output); + }); + }); + }); + describe('Collections', () => { literalTestCases.map(([typeDefinition, input, output]) => { let singleCollectionValue: any; diff --git a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts index 8f708d24e..0cb038323 100644 --- a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts +++ b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/test/testCases.ts @@ -67,6 +67,10 @@ export const inputTypes: Record = { literalType: { mapValueType: { simple: Core.SimpleType.STRING }, }, + subtype: { + literalType: { simple: Core.SimpleType.NONE }, + type: InputType.None, + }, type: InputType.Map, }, none: { @@ -194,10 +198,6 @@ export const validityTestCases = { Long.MIN_VALUE, ], }, - map: { - invalid: ['a', {}, true, new Date(), 1.1, 0 / 0, '1.1', '1a'], - valid: [{ a: '1' }, { a: [1, 2, 3] }, { a: { b: 'c' } }], - }, // schema is just a specialized string input, so it has the same validity cases as string schema: { invalid: [123, true, new Date(), {}], valid: ['', 'abcdefg'] }, string: { invalid: [123, true, new Date(), {}], valid: ['', 'abcdefg'] }, diff --git a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/utils.ts b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/utils.ts index 3064bec47..2f989dba3 100644 --- a/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/utils.ts +++ b/packages/zapp/console/src/components/Launch/LaunchForm/inputHelpers/utils.ts @@ -46,8 +46,12 @@ export function typeIsSupported(typeDefinition: InputTypeDefinition): boolean { case InputType.Schema: case InputType.String: case InputType.Struct: - case InputType.Map: return true; + case InputType.Map: + if (!subtype) { + return false; + } + return typeIsSupported(subtype); case InputType.Collection: { if (!subtype) { console.error('Unexpected missing subtype for collection input', typeDefinition); diff --git a/packages/zapp/console/src/components/common/strings.ts b/packages/zapp/console/src/components/common/strings.ts index 8cf4c5945..97b6cad04 100644 --- a/packages/zapp/console/src/components/common/strings.ts +++ b/packages/zapp/console/src/components/common/strings.ts @@ -13,6 +13,15 @@ const str = { serviceAccountHeader: 'Service Account', noMatchingResults: 'No matching results', missingMapSubType: 'Unexpected missing subtype for map', + mapMissingMapProperty: 'Map literal missing `map` property', + mapMissingMapLiteralsProperty: 'Map literal missing `map.literals` property', + mapLiternalNotObject: 'Map literal is not an object', + mapLiternalObjectEmpty: 'Map literal object is empty', + valueNotString: 'Value is not a string', + valueRequired: 'Value is required', + valueNotParse: 'Value did not parse to an object', + valueKeyRequired: "Value's key is required", + valueValueInvalid: "Value's value is invalid", }; export { patternKey } from '@flyteconsole/locale';