From 4ddca71ce9d5aa33657ef9617fb7d3b0bdb5aafa Mon Sep 17 00:00:00 2001 From: ori-moisis Date: Mon, 6 Nov 2023 13:12:20 +0200 Subject: [PATCH] SALTO-4991: Fix account/adapter name replace for container types (#5056) The code did not handle the case where there was an unresolved type referece to a container type, it only worked for resolved type references. --- .../workspace/src/element_adapter_rename.ts | 19 ++ .../workspace/element_adapter_rename.test.ts | 251 ++++++++++-------- 2 files changed, 159 insertions(+), 111 deletions(-) diff --git a/packages/workspace/src/element_adapter_rename.ts b/packages/workspace/src/element_adapter_rename.ts index 68efcb60f34..8819e6d3167 100644 --- a/packages/workspace/src/element_adapter_rename.ts +++ b/packages/workspace/src/element_adapter_rename.ts @@ -37,16 +37,35 @@ import { isStaticFile, StaticFile, UnresolvedReference, + ContainerTypeName, } from '@salto-io/adapter-api' import { transformElement, TransformFunc } from '@salto-io/adapter-utils' const { awu } = collections.asynciterable +export const buildContainerTypeId = ( + prefix: ContainerTypeName, + innerTypeId: ElemID, +): ElemID => { + const typeElemIdCreator: Record ElemID> = { + List: ListType.createElemID, + Map: MapType.createElemID, + } + return typeElemIdCreator[prefix](new TypeReference(innerTypeId)) +} + // Changing element ids is a practice we'd like to discourage, which is why this // function is not generic, but aimed at a specific need - to create a copy of an elemID // with a modified adapter. This is used when an account has a different name than the // service it represents. Created for the multiple accounts per service features (SALTO-1264). export const createAdapterReplacedID = (elemID: ElemID, adapter: string): ElemID => { + const containerInfo = elemID.getContainerPrefixAndInnerType() + if (containerInfo !== undefined) { + return buildContainerTypeId( + containerInfo.prefix, + createAdapterReplacedID(ElemID.fromFullName(containerInfo.innerTypeName), adapter), + ) + } if (elemID.adapter === GLOBAL_ADAPTER || elemID.adapter === ElemID.VARIABLES_NAMESPACE) { return elemID } diff --git a/packages/workspace/test/workspace/element_adapter_rename.test.ts b/packages/workspace/test/workspace/element_adapter_rename.test.ts index 9ef321857c7..391747d34a2 100644 --- a/packages/workspace/test/workspace/element_adapter_rename.test.ts +++ b/packages/workspace/test/workspace/element_adapter_rename.test.ts @@ -13,9 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BuiltinTypes, ElemID, InstanceElement, ListType, MapType, ObjectType, ReferenceExpression, StaticFile, TemplateExpression, TypeReference, UnresolvedReference } from '@salto-io/adapter-api' +import { BuiltinTypes, ElemID, InstanceElement, ListType, MapType, ObjectType, ReferenceExpression, StaticFile, TemplateExpression, TypeElement, TypeReference, UnresolvedReference } from '@salto-io/adapter-api' import { createInMemoryElementSource } from '../../src/workspace/elements_source' -import { createAdapterReplacedID, updateElementsWithAlternativeAccount } from '../../src/element_adapter_rename' +import { buildContainerTypeId, createAdapterReplacedID, updateElementsWithAlternativeAccount } from '../../src/element_adapter_rename' + +describe('buildContainerTypeId', () => { + it.each([['list', ListType], ['map', MapType]])('with %s prefix', (_name, containerType) => { + const id = containerType.createElemID(BuiltinTypes.STRING) + const containerInfo = id.getContainerPrefixAndInnerType() + if (containerInfo !== undefined) { + expect(buildContainerTypeId(containerInfo.prefix, BuiltinTypes.STRING.elemID)).toEqual(id) + } + }) +}) describe('when replacing id adapter', () => { it('creates a new elemID with different id', () => { @@ -30,119 +40,138 @@ describe('when replacing id adapter', () => { const origID = new ElemID('var', '2', 'var') expect(createAdapterReplacedID(origID, '5')).toEqual(origID) }) + it('changes container type inner type ids', () => { + const origID = MapType.createElemID( + new TypeReference(ListType.createElemID( + new TypeReference(new ElemID('bla', 'foo')) + )) + ) + expect(createAdapterReplacedID(origID, 'foo').getFullName()).toEqual('Map>') + }) }) describe('rename adapter in elements', () => { - const serviceName = 'salesforce' - const newServiceName = 's1' - const innerRefType = new ObjectType({ elemID: new ElemID(serviceName, 'inner') }) - const innerType = new ObjectType({ - elemID: new ElemID(serviceName, 'typeInContainers'), - fields: { - value: { refType: BuiltinTypes.STRING }, - }, - annotationRefsOrTypes: { - someRef: innerRefType, - }, - }) - const objectToChange = new ObjectType({ - elemID: new ElemID(serviceName, 'objectType'), - path: [serviceName, 'somepath'], - fields: { - field: { refType: innerType }, - templateField: { refType: innerType }, - listField: { refType: new ListType(innerType) }, - listOfListField: { refType: new ListType(new ListType(innerType)) }, - mapField: { refType: new MapType(innerType) }, - mapOfMapField: { refType: new MapType(new MapType(innerType)) }, - }, - }) - const staticFileToChange = new StaticFile({ - filepath: `static-resources/${serviceName}/test${serviceName}.txt`, - content: Buffer.from('test'), - }) - const instanceToChange = new InstanceElement('InstanceElement', objectToChange, { - field: new ReferenceExpression(innerType.elemID), - templateField: new TemplateExpression({ - parts: [ - 'prefix', - new ReferenceExpression(innerType.elemID), - 'middle', - new ReferenceExpression(innerRefType.elemID), - ], - }), - innerRefField: innerType, - staticFileField: staticFileToChange, - }) - // the adapter change is supposed to set this value to undefined if it finds unresolved reference. - instanceToChange.value.field.value = new UnresolvedReference(innerType.elemID) - const unresolvedReferenceInstanceToChange = new InstanceElement('InstanceElement', - new TypeReference(objectToChange.elemID)) - const changedInnerRefType = new ObjectType({ elemID: new ElemID(newServiceName, 'inner') }) - const changedInnerType = new ObjectType({ - elemID: new ElemID(newServiceName, 'typeInContainers'), - fields: { - value: { refType: BuiltinTypes.STRING }, - }, - annotationRefsOrTypes: { - someRef: changedInnerRefType, - }, - }) - const changedObject = new ObjectType({ - path: [newServiceName, 'somepath'], - elemID: new ElemID(newServiceName, 'objectType'), - fields: { - field: { refType: changedInnerType }, - templateField: { refType: changedInnerType }, - listField: { refType: new ListType(changedInnerType) }, - listOfListField: { refType: new ListType(new ListType(changedInnerType)) }, - mapField: { refType: new MapType(changedInnerType) }, - mapOfMapField: { refType: new MapType(new MapType(changedInnerType)) }, - }, - }) - const changedStaticFile = new StaticFile({ - filepath: `static-resources/${newServiceName}/test${serviceName}.txt`, - content: Buffer.from('test'), - }) - const changedInstance = new InstanceElement('InstanceElement', changedObject, { - field: new ReferenceExpression(changedInnerType.elemID), - templateField: new TemplateExpression({ - parts: [ - 'prefix', - new ReferenceExpression(changedInnerType.elemID), - 'middle', - new ReferenceExpression(changedInnerRefType.elemID), - ], - }), - innerRefField: changedInnerType, - staticFileField: changedStaticFile, - }) - const changedUnresolvedReferenceInstanceToChange = new InstanceElement('InstanceElement', - new TypeReference(changedObject.elemID)) - beforeEach(async () => { - await updateElementsWithAlternativeAccount([ - objectToChange, - instanceToChange, - unresolvedReferenceInstanceToChange, - ], newServiceName, serviceName, createInMemoryElementSource()) - }) + describe.each([ + ['resolved inner ref types', true], + ['unresolved inner ref types', false], + ])('with %s', (_testName, resolvedInnerRefs) => { + const createRefType = (type: TypeElement): TypeReference => ( + new TypeReference(type.elemID, resolvedInnerRefs ? type : undefined) + ) - it('updates objectType with new id', () => { - expect(objectToChange.isEqual(changedObject)).toBeTruthy() - }) + const serviceName = 'salesforce' + const newServiceName = 's1' + const innerRefType = new ObjectType({ elemID: new ElemID(serviceName, 'inner') }) + const innerType = new ObjectType({ + elemID: new ElemID(serviceName, 'typeInContainers'), + fields: { + value: { refType: BuiltinTypes.STRING }, + }, + annotationRefsOrTypes: { + someRef: createRefType(innerRefType), + }, + }) + const objectToChange = new ObjectType({ + elemID: new ElemID(serviceName, 'objectType'), + path: [serviceName, 'somepath'], + fields: { + field: { refType: createRefType(innerType) }, + templateField: { refType: createRefType(innerType) }, + listField: { refType: createRefType(new ListType(createRefType(innerType))) }, + listOfListField: { + refType: createRefType(new ListType(createRefType(new ListType(createRefType(innerType))))), + }, + mapField: { refType: createRefType(new MapType(createRefType(innerType))) }, + mapOfMapField: { refType: createRefType(new MapType(createRefType(new MapType(createRefType(innerType))))) }, + }, + }) + const staticFileToChange = new StaticFile({ + filepath: `static-resources/${serviceName}/test${serviceName}.txt`, + content: Buffer.from('test'), + }) + const instanceToChange = new InstanceElement('InstanceElement', objectToChange, { + field: new ReferenceExpression(innerType.elemID), + templateField: new TemplateExpression({ + parts: [ + 'prefix', + new ReferenceExpression(innerType.elemID), + 'middle', + new ReferenceExpression(innerRefType.elemID), + ], + }), + innerRefField: innerType, + staticFileField: staticFileToChange, + }) + // the adapter change is supposed to set this value to undefined if it finds unresolved reference. + instanceToChange.value.field.value = new UnresolvedReference(innerType.elemID) + const unresolvedReferenceInstanceToChange = new InstanceElement('InstanceElement', + new TypeReference(objectToChange.elemID)) + const changedInnerRefType = new ObjectType({ elemID: new ElemID(newServiceName, 'inner') }) + const changedInnerType = new ObjectType({ + elemID: new ElemID(newServiceName, 'typeInContainers'), + fields: { + value: { refType: BuiltinTypes.STRING }, + }, + annotationRefsOrTypes: { + someRef: changedInnerRefType, + }, + }) + const changedObject = new ObjectType({ + path: [newServiceName, 'somepath'], + elemID: new ElemID(newServiceName, 'objectType'), + fields: { + field: { refType: changedInnerType }, + templateField: { refType: changedInnerType }, + listField: { refType: new ListType(changedInnerType) }, + listOfListField: { refType: new ListType(new ListType(changedInnerType)) }, + mapField: { refType: new MapType(changedInnerType) }, + mapOfMapField: { refType: new MapType(new MapType(changedInnerType)) }, + }, + }) + const changedStaticFile = new StaticFile({ + filepath: `static-resources/${newServiceName}/test${serviceName}.txt`, + content: Buffer.from('test'), + }) + const changedInstance = new InstanceElement('InstanceElement', changedObject, { + field: new ReferenceExpression(changedInnerType.elemID), + templateField: new TemplateExpression({ + parts: [ + 'prefix', + new ReferenceExpression(changedInnerType.elemID), + 'middle', + new ReferenceExpression(changedInnerRefType.elemID), + ], + }), + innerRefField: changedInnerType, + staticFileField: changedStaticFile, + }) + const changedUnresolvedReferenceInstanceToChange = new InstanceElement('InstanceElement', + new TypeReference(changedObject.elemID)) + beforeEach(async () => { + await updateElementsWithAlternativeAccount([ + objectToChange, + instanceToChange, + unresolvedReferenceInstanceToChange, + ], newServiceName, serviceName, createInMemoryElementSource()) + }) - it('updates InstanceElement with new id', () => { - // These fields represent a resolved value that is not supposed to be manipulated by - // adapter manipulation - changedInstance.value.innerRefField.annotationRefTypes.someRef.type = instanceToChange - .value.innerRefField.annotationRefTypes.someRef.type - changedInstance.value.innerRefField.fields.value = instanceToChange - .value.innerRefField.fields.value - expect(instanceToChange.value.staticFileField).toEqual(changedInstance.value.staticFileField) - expect(instanceToChange.isEqual(changedInstance)).toBeTruthy() - }) + it('updates objectType with new id', () => { + expect(objectToChange.isEqual(changedObject)).toBeTruthy() + }) + + it('updates InstanceElement with new id', () => { + // These fields represent a resolved value that is not supposed to be manipulated by + // adapter manipulation + changedInstance.value.innerRefField.annotationRefTypes.someRef.type = instanceToChange + .value.innerRefField.annotationRefTypes.someRef.type + changedInstance.value.innerRefField.fields.value = instanceToChange + .value.innerRefField.fields.value + expect(instanceToChange.value.staticFileField).toEqual(changedInstance.value.staticFileField) + expect(instanceToChange.isEqual(changedInstance)).toBeTruthy() + }) - it('updates InstanceElement with unresolved type', () => { - expect(changedUnresolvedReferenceInstanceToChange - .isEqual(unresolvedReferenceInstanceToChange)).toBeTruthy() + it('updates InstanceElement with unresolved type', () => { + expect(changedUnresolvedReferenceInstanceToChange + .isEqual(unresolvedReferenceInstanceToChange)).toBeTruthy() + }) }) })