Skip to content

Commit

Permalink
SALTO-4991: Fix account/adapter name replace for container types (#5056)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ori-moisis authored Nov 6, 2023
1 parent fa4f18c commit 4ddca71
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 111 deletions.
19 changes: 19 additions & 0 deletions packages/workspace/src/element_adapter_rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContainerTypeName, (innerType: TypeReference) => 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
}
Expand Down
251 changes: 140 additions & 111 deletions packages/workspace/test/workspace/element_adapter_rename.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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<List<foo.foo>>')
})
})
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()
})
})
})

0 comments on commit 4ddca71

Please sign in to comment.