/* eslint-disable import/first, import/order */ import { mockAggCompassRequests, mockCreateComponent, mockCreateExternalAlias, mockDeleteExternalAlias, mockDetachDataManager, mockGetAllComponentTypes, mockGetComponent, mockGetComponentByExternalAlias, mockGetComponentsByReferences, mockUpdateComponent, mockUpdateComponentDataManager, } from './helpers/mockCompassRequests'; mockAggCompassRequests(); import yaml from 'js-yaml'; import api from '../../index'; import { CompassComponentType, CompassLinkType, ConfigFileActions, } from '@atlassian/forge-graphql-types'; import { MOCK_ADDITIONAL_EXTERNAL_ALIASES, MOCK_ANOTHER_CLOUD_ID, MOCK_BASE_COMPONENT_WITHOUT_FIELDS, MOCK_BASE_COMPONENT_WITH_ID, MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID, MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID_2, MOCK_CLOUD_ID, MOCK_COMPONENT_ID, MOCK_COMPONENT_ID_2, MOCK_COMPONENT_ID_3, MOCK_COMPONENT_PATH, MOCK_COMPONENT_SLUG_1, MOCK_COMPONENT_SLUG_2, MOCK_COMPONENT_TYPE_DEF, MOCK_COMPONENT_TYPE_ID, MOCK_CUSTOM_COMPONENT_TYPE_DEF, MOCK_CUSTOM_FIELDS_IN_YAML, MOCK_CUSTOM_FIELDS_IN_YAML_2, MOCK_DATA_MANAGER_EXTERNAL_SOURCE_URL, MOCK_DEDUPLICATION_ID, MOCK_EXTERNAL_ID, MOCK_EXTERNAL_SOURCE, MOCK_LINK, MOCK_NEW_PATH, MOCK_RELATIONSHIP, MOCK_RELATIONSHIP_2, MOCK_RELATIONSHIP_BY_SLUGS, MOCK_URL, UPDATED_MOCK_CUSTOM_FIELD_INPUTS, UPDATED_MOCK_CUSTOM_FIELD_INPUTS_WITH_DISPLAY_VALUE, createCompassYamlLink, getManagedComponentAliases, getMockedCompassYaml, getMockedComponent, getMockedConfigFileMetadata, } from '../fixtures/mocks'; import { COMPONENT_NOT_FOUND, HELLO_CLOUD_ID, MAX_LINKS_OF_TYPE, } from '../../helpers/constants'; import { assertRequestMatchedSnapshot } from './helpers/sdk-helper'; import { DEFAULT_UNNAMED_COMPONENT_NAME, EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, } from '../../requests/config-as-code-requests/helpers/createOrUpdateComponent/constants'; import { generateExternalIdWithPrefix } from '../../requests/config-as-code-requests/helpers/createOrUpdateComponent/helpers'; import { mockAggConfigAsCodeRequests } from './helpers/mockConfigAsCodeRequests'; import { UnableToFindComponentDuringUpdateError } from '../../requests/config-as-code-requests/helpers/createOrUpdateComponent/errors'; const configReq = api.compass.configAsCode.asApp(); const compassConfigAsCodeApp = api.compass.configAsCode.asApp(); const mockIncrement = jest.fn(); const mockSetGauge = jest.fn(); const mockStopTimer = jest.fn(); const mockTimer = { stop: mockStopTimer, }; const mockMeasure = jest.fn(); jest.mock('@forge/metrics', () => ({ internalMetrics: { counter: () => ({ incr: mockIncrement, }), timer: () => ({ measure: mockMeasure, }), gauge: () => ({ set: mockSetGauge, }), }, })); const mockUnlinkComponentIfRetargetedByNewIdRequests = () => { mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); }; describe('syncComponentWithFile', () => { const OLD_ENV = process.env; beforeEach(() => { jest.resetAllMocks(); // copy and reset process.env jest.resetModules(); process.env = { ...OLD_ENV }; mockAggCompassRequests(); mockAggConfigAsCodeRequests(compassConfigAsCodeApp); mockMeasure.mockImplementation(() => mockTimer); }); afterEach(() => { process.env = { ...OLD_ENV }; }); test('creates external alias for component in case data manager is not present and external alias was not created previously for the source', async () => { const compassYaml = getMockedCompassYaml(); const component = getMockedComponent({ externalAliases: [ { externalAliasId: 'other_id_that_doesnt_match_MOCK_EXTERNAL_ID', externalSource: MOCK_EXTERNAL_SOURCE, }, ], }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); const expectedParameter = { componentId: compassYaml.id, externalAlias: { externalId: MOCK_EXTERNAL_ID, externalSource: MOCK_EXTERNAL_SOURCE, }, }; expect(mockCreateExternalAlias).toBeCalledWith(expectedParameter); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update customFields in component', async () => { const compassYaml = getMockedCompassYaml({ customFields: MOCK_CUSTOM_FIELDS_IN_YAML, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ customFields: UPDATED_MOCK_CUSTOM_FIELD_INPUTS, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update single / multi select customFields in component when displayValue is given in yaml', async () => { const compassYaml = getMockedCompassYaml({ customFields: MOCK_CUSTOM_FIELDS_IN_YAML_2, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID_2 }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ customFields: UPDATED_MOCK_CUSTOM_FIELD_INPUTS_WITH_DISPLAY_VALUE, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); describe('finds component type ARI from name when updating component', () => { const testCases = [ { description: 'should update component typeId when FF is enabled', enableCustomTypesByName: 'true', yamlTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.name, expectedTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.id, expectedGetAllComponentTypesCalled: true, }, { description: 'should not update component typeId when FF is disabled', enableCustomTypesByName: 'false', yamlTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.name, expectedTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.name, expectedGetAllComponentTypesCalled: false, }, { description: 'should not update component typeId when FF is enabled and typeId is a valid built-in type', enableCustomTypesByName: 'true', yamlTypeId: MOCK_COMPONENT_TYPE_DEF.id, expectedTypeId: MOCK_COMPONENT_TYPE_DEF.id, expectedGetAllComponentTypesCalled: false, }, { description: 'should not update component typeId when FF is enabled and typeId is a valid ARI', enableCustomTypesByName: 'true', yamlTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.id, expectedTypeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.id, expectedGetAllComponentTypesCalled: false, }, ]; testCases.forEach( ({ description, enableCustomTypesByName, yamlTypeId, expectedTypeId, expectedGetAllComponentTypesCalled, }) => { it(description, async () => { process.env.ENABLE_CUSTOM_TYPES_BY_NAME = enableCustomTypesByName; const compassYaml = getMockedCompassYaml({ typeId: yamlTypeId, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID, }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); mockGetAllComponentTypes.mockResolvedValueOnce({ success: true, data: { componentTypes: [ MOCK_COMPONENT_TYPE_DEF, MOCK_CUSTOM_COMPONENT_TYPE_DEF, ], }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, externalSourceURL: MOCK_URL, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ typeId: expectedTypeId, }), ); expect(mockGetAllComponentTypes).toHaveBeenCalledTimes( expectedGetAllComponentTypesCalled ? 1 : 0, ); }); }, ); it('throws an error when FF is enabled, but a matching custom type could not be found', async () => { process.env.ENABLE_CUSTOM_TYPES_BY_NAME = 'true'; const compassYaml = getMockedCompassYaml({ typeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.name, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); // only SERVICE type returned mockGetAllComponentTypes.mockResolvedValueOnce({ success: true, data: { componentTypes: [MOCK_COMPONENT_TYPE_ID] }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, externalSourceURL: MOCK_URL, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).not.toHaveBeenCalled(); expect(mockGetAllComponentTypes).toHaveBeenCalledTimes(1); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: [ 'typeId must be a Built-In component type, or a valid ARI or Name of a custom component type.', ], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should replace the typeId before creating the component, if FF is enabled', async () => { const deduplicationId = MOCK_DEDUPLICATION_ID; const key = 'test-key'; process.env.ENABLE_CUSTOM_TYPES_BY_NAME = 'true'; const compassYaml = getMockedCompassYaml({ id: null, typeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.name, }); const component = getMockedComponent({ dataManager: { externalSourceURL: MOCK_URL }, externalAliases: [ { externalAliasId: `${deduplicationId}:${key}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }, ], }); mockGetComponentByExternalAlias.mockResolvedValue({ success: false, data: { component: null }, errors: [{ message: COMPONENT_NOT_FOUND }], }); mockGetAllComponentTypes.mockResolvedValue({ success: true, data: { componentTypes: [ MOCK_COMPONENT_TYPE_DEF, MOCK_CUSTOM_COMPONENT_TYPE_DEF, ], }, errors: [], }); mockCreateComponent.mockResolvedValue({ success: true, data: { component: { id: component.id, name: component.name } }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(mockGetAllComponentTypes).toHaveBeenCalledTimes(2); expect(mockCreateExternalAlias).toBeCalled(); expect(mockCreateComponent).toBeCalledWith( expect.objectContaining({ typeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.id, }), ); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ typeId: MOCK_CUSTOM_COMPONENT_TYPE_DEF.id, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); }); describe('add relationships using slugs', () => { it('does not update relationships with slugs when the FF is disabled', async () => { process.env.ENABLE_RELATIONSHIPS_BY_SLUGS = 'false'; const compassYaml = getMockedCompassYaml({ relationships: MOCK_RELATIONSHIP_BY_SLUGS, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID, }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockGetComponentsByReferences.mockResolvedValue({ success: true, data: { components: [ getMockedComponent({ id: MOCK_COMPONENT_ID_2, slug: MOCK_COMPONENT_SLUG_1, }), getMockedComponent({ id: MOCK_COMPONENT_ID_3, slug: MOCK_COMPONENT_SLUG_2, }), ], }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ relationships: [], }), ); expect(mockGetComponentsByReferences).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); expect(mockSetGauge).toHaveBeenCalledTimes(0); }); it('updates relationships with slugs when the FF is enabled', async () => { process.env.ENABLE_RELATIONSHIPS_BY_SLUGS = 'true'; process.env.RELATIONSHIPS_BY_SLUGS_CLOUDID_ALLOWLIST = `${MOCK_CLOUD_ID},${MOCK_ANOTHER_CLOUD_ID}`; const compassYaml = getMockedCompassYaml({ relationships: MOCK_RELATIONSHIP_BY_SLUGS, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockGetComponentsByReferences.mockResolvedValue({ success: true, data: { components: [ getMockedComponent({ id: MOCK_COMPONENT_ID_2, slug: MOCK_COMPONENT_SLUG_1, }), getMockedComponent({ id: MOCK_COMPONENT_ID_3, slug: MOCK_COMPONENT_SLUG_2, }), ], }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockGetComponentsByReferences).toBeCalledTimes(1); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ relationships: [MOCK_RELATIONSHIP, MOCK_RELATIONSHIP_2], }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); expect(mockSetGauge).toHaveBeenCalledWith(2); }); it('accepts an empty relationships object', async () => { process.env.ENABLE_RELATIONSHIPS_BY_SLUGS = 'true'; process.env.RELATIONSHIPS_BY_SLUGS_CLOUDID_ALLOWLIST = `${MOCK_CLOUD_ID},${MOCK_ANOTHER_CLOUD_ID}`; const compassYaml = getMockedCompassYaml({ relationships: {}, }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: {}, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockGetComponentsByReferences.mockResolvedValue({ success: true, data: { components: [ getMockedComponent({ id: MOCK_COMPONENT_ID_2, slug: MOCK_COMPONENT_SLUG_1, }), getMockedComponent({ id: MOCK_COMPONENT_ID_3, slug: MOCK_COMPONENT_SLUG_2, }), ], }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockGetComponentsByReferences).toBeCalledTimes(0); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ relationships: [], }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); }); it('should create external alias even for a component that has a data manager', async () => { const compassYaml = getMockedCompassYaml(); const component = getMockedComponent({ dataManager: { externalSourceURL: 'url' }, }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockCreateExternalAlias).toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should not create external alias for component in case the same external alias was created previously', async () => { const compassYaml = getMockedCompassYaml(); const component = getMockedComponent({ externalAliases: [ { externalAliasId: MOCK_EXTERNAL_ID, externalSource: MOCK_EXTERNAL_SOURCE, }, ], }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should not create external alias if no component present', async () => { const compassYaml = getMockedCompassYaml({ id: undefined }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update component without changing yaml links if it is at MAX_LINKS_OF_TYPE', async () => { const repoLinks = Array(MAX_LINKS_OF_TYPE).fill( createCompassYamlLink(CompassLinkType.Repository), ); const compassYaml = getMockedCompassYaml({ links: [...repoLinks, createCompassYamlLink(CompassLinkType.Project)], }); const component = getMockedComponent({ dataManager: { externalSourceURL: 'url' }, }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: 'https://test', configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ links: compassYaml.links, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should ignore component links with type Document', async () => { const compassYaml = getMockedCompassYaml({ links: [ createCompassYamlLink(CompassLinkType.Document), createCompassYamlLink(CompassLinkType.Project), createCompassYamlLink(CompassLinkType.Dashboard), ], }); const component = getMockedComponent({ dataManager: { externalSourceURL: 'url' }, }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: 'https://test', configFileMetadata: getMockedConfigFileMetadata(), }); const expectedLinks = compassYaml.links?.filter( (link) => link.type !== CompassLinkType.Document, ); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ links: expectedLinks, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should add an error to the datamanager when a file with an invalid property is updated and the feature flag is on, and there is an id', async () => { const compassYaml = getMockedCompassYaml({ fields: { tier: 6 } }); const component = getMockedComponent({ externalAliases: getManagedComponentAliases({}), }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"tier" must have a value of: 1, 2, 3, 4'], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should add an error to the datamanager when a file with an invalid property is updated and the feature flag is on, and there is no id', async () => { const compassYaml = getMockedCompassYaml({ id: null, fields: { tier: 6 } }); const component = getMockedComponent({ externalAliases: getManagedComponentAliases({ path: MOCK_COMPONENT_PATH, }), }); mockGetComponentByExternalAlias.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ oldPath: MOCK_COMPONENT_PATH, newPath: MOCK_COMPONENT_PATH, }), }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"tier" must have a value of: 1, 2, 3, 4'], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should add an error to the datamanager when a file with an invalid property is created and the feature flag is on, and there is no id', async () => { const compassYaml = getMockedCompassYaml({ id: null, fields: { tier: 6 } }); const component = getMockedComponent({ externalAliases: getManagedComponentAliases({}), }); mockGetComponentByExternalAlias .mockResolvedValueOnce({ // Get the name alias, does not exist success: true, data: { component: null }, errors: [], }) .mockResolvedValueOnce({ // get the new path Alias, does not exist, indicating that this is a new component success: true, data: { component: null }, errors: [], }); mockCreateComponent.mockResolvedValue({ success: true, errors: [], data: { component: { id: component.id, name: component.name } }, }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(mockCreateComponent).toBeCalled(); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"tier" must have a value of: 1, 2, 3, 4'], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should create and update component if compass.yml has valid name and no id and action is create', async () => { const deduplicationId = MOCK_DEDUPLICATION_ID; const componentName = 'name'; const compassYaml = getMockedCompassYaml({ id: null, name: componentName, }); const component = getMockedComponent({ dataManager: { externalSourceURL: MOCK_URL }, externalAliases: [ { externalAliasId: `${deduplicationId}:${componentName}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }, ], }); mockGetComponentByExternalAlias.mockResolvedValue({ success: false, data: { component: null }, errors: [], }); mockCreateComponent.mockResolvedValue({ success: true, data: { component: { id: component.id, name: component.name } }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockUpdateComponent.mockResolvedValue({ success: true, errors: [], data: { component }, }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(mockCreateExternalAlias).toBeCalled(); expect(mockUpdateComponent).toBeCalled(); expect(mockGetComponentByExternalAlias).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalId: `${deduplicationId}:${componentName}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, options: { includeCustomFieldOptions: true, includeCustomFields: true, includeLinks: true, }, }); expect(mockUpdateComponentDataManager).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should create component if compass.yml has valid name and no id but the action was an update and there was no component', async () => { const deduplicationId = MOCK_DEDUPLICATION_ID; const key = 'test-key'; const compassYaml = getMockedCompassYaml({ id: null, }); const component = getMockedComponent({ dataManager: { externalSourceURL: MOCK_URL }, externalAliases: [ { externalAliasId: `${deduplicationId}:${key}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }, ], }); mockGetComponentByExternalAlias.mockResolvedValue({ success: false, data: { component: null }, errors: [{ message: COMPONENT_NOT_FOUND }], }); mockCreateComponent.mockResolvedValue({ success: true, data: { component: { id: component.id, name: component.name } }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.UPDATE, }), }); expect(mockCreateExternalAlias).toBeCalled(); expect(mockUpdateComponent).toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should not create or update component if compass.yml has valid name and no id but the action was an UPDATE and get component returned unexpected errors', async () => { const compassYaml = getMockedCompassYaml({ id: null, }); mockGetComponentByExternalAlias.mockResolvedValue({ success: false, data: { component: null }, errors: [{ message: 'Unexpected error' }], }); const result = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.UPDATE, }), }); expect(mockCreateComponent).not.toBeCalled(); expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockUpdateComponent).not.toBeCalled(); expect(result).toEqual({ errors: [ new UnableToFindComponentDuringUpdateError([ { message: 'Unexpected error' }, ]), ], success: false, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should create externalAlias and update component if feature flag turned on and yaml file has id', async () => { const compassYaml = getMockedCompassYaml(); const component = getMockedComponent({ externalAliases: [ { externalAliasId: 'other_id_that_doesnt_match_MOCK_EXTERNAL_ID', externalSource: MOCK_EXTERNAL_SOURCE, }, ], }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); const resp = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); const expectedParameter = { componentId: compassYaml.id, externalAlias: { externalId: MOCK_EXTERNAL_ID, externalSource: MOCK_EXTERNAL_SOURCE, }, }; const getExpectedParameter = { componentId: compassYaml.id, options: { includeLinks: true, includeCustomFields: true, includeCustomFieldOptions: true, }, }; expect(resp.success).toBeTruthy(); expect(mockCreateExternalAlias).toBeCalledWith(expectedParameter); expect(mockUpdateComponent).toBeCalled(); expect(mockGetComponent).toBeCalledWith(getExpectedParameter); expect(mockUpdateComponentDataManager).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); describe('should create a new component with an error if the name does not exist in the yaml file', () => { beforeEach(() => { const pathAlias = { externalAliasId: `${MOCK_DEDUPLICATION_ID}:${MOCK_COMPONENT_PATH}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }; const component = getMockedComponent({ externalAliases: [pathAlias] }); mockGetComponentByExternalAlias.mockResolvedValue({ // Get the name and path alias, does not exist success: true, data: { component: null }, errors: [], }); mockCreateComponent.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponentDataManager.mockResolvedValue({ success: true, errors: [], data: {}, }); }); it('throws error if name is null', async () => { const compassYaml = getMockedCompassYaml({ name: null, id: null }); const resp = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(resp.success).toBeFalsy(); expect(mockCreateComponent).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalAlias: { externalId: generateExternalIdWithPrefix( MOCK_DEDUPLICATION_ID, MOCK_NEW_PATH, ), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }, name: DEFAULT_UNNAMED_COMPONENT_NAME, options: { createdFromFile: true, }, typeId: 'SERVICE', }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"name" must be of type "string"'], status: 'USER_ERROR', }, }); // Assert the name alias was not added since there was no user provided name. expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('throws error if name is invalid', async () => { const invalidName = { name: 'test' } as unknown as string; const compassYaml = getMockedCompassYaml({ name: invalidName, id: null }); const resp = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(resp.success).toBeFalsy(); expect(mockCreateComponent).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalAlias: { externalId: generateExternalIdWithPrefix( MOCK_DEDUPLICATION_ID, MOCK_NEW_PATH, ), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }, name: DEFAULT_UNNAMED_COMPONENT_NAME, options: { createdFromFile: true, }, typeId: 'SERVICE', }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"name" must be of type "string"'], status: 'USER_ERROR', }, }); // Assert the name alias was not added since there was no user provided name. expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('throws error if name is omitted from the file', async () => { const compassYaml = { description: 'description' }; const resp = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata({ configFileAction: ConfigFileActions.CREATE, }), }); expect(resp.success).toBeFalsy(); expect(mockCreateComponent).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalAlias: { externalId: generateExternalIdWithPrefix( MOCK_DEDUPLICATION_ID, MOCK_NEW_PATH, ), externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, }, name: DEFAULT_UNNAMED_COMPONENT_NAME, options: { createdFromFile: true, }, typeId: 'SERVICE', }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: ['"name" must be included in the configuration file'], status: 'USER_ERROR', }, }); // Assert the name alias was not added since there was no user provided name. expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); }); it('should update component datamanager with correct externalSourceURL', async () => { const compassYaml = getMockedCompassYaml(); const component = getMockedComponent({ dataManager: { externalSourceURL: MOCK_URL }, }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ dataManager: { externalSourceURL: MOCK_URL, }, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should catch error when update component fails', async () => { const ERROR_MESSAGE = 'test'; const compassYaml = getMockedCompassYaml(); const component = getMockedComponent(); const error = new Error(ERROR_MESSAGE); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockRejectedValue(error); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, externalSourceURL: MOCK_URL, configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, externalSourceURL: MOCK_URL, lastSyncEvent: { lastSyncErrors: [ERROR_MESSAGE], status: 'SERVER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update a v1 component without optional fields the same way', async () => { const compassYaml = { id: MOCK_COMPONENT_ID, name: 'name', }; mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: { ...MOCK_BASE_COMPONENT_WITHOUT_FIELDS, id: MOCK_COMPONENT_ID, }, }, errors: [], }); const expectedParameters = { componentId: compassYaml.id, options: { includeLinks: true, includeCustomFields: true, includeCustomFieldOptions: true, }, }; await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); assertRequestMatchedSnapshot(mockUpdateComponent); expect(mockGetComponent).toBeCalledWith(expectedParameters); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update a v1 component with many optional fields the same way', async () => { const compassYaml = getMockedCompassYaml({ customFields: MOCK_CUSTOM_FIELDS_IN_YAML, labels: ['test'], fields: { tier: 1, lifecycle: 'ACTIVE' }, typeId: 'SERVICE', links: [MOCK_LINK], }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_GET_COMPONENT_WITH_CUSTOM_FIELDS_WITH_ID }, errors: [], }); mockCreateExternalAlias.mockResolvedValue({ success: true, errors: [], data: {}, }); mockUpdateComponent.mockResolvedValueOnce({ success: true, data: { component: getMockedComponent() }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), additionalExternalAliases: MOCK_ADDITIONAL_EXTERNAL_ALIASES, configFileMetadata: getMockedConfigFileMetadata(), }); assertRequestMatchedSnapshot(mockUpdateComponent); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update labels if they were in yaml', async () => { const labels = ['test', 'test2']; const compassYaml = getMockedCompassYaml({ labels, }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_COMPONENT_WITH_ID }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ labels, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should set labels to an empty array if it was empty in yaml', async () => { const compassYaml = getMockedCompassYaml({ labels: [] }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_COMPONENT_WITH_ID }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ labels: [], }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should set labels to null if they were defined as null in yaml', async () => { const compassYaml = getMockedCompassYaml({ labels: null }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_COMPONENT_WITH_ID }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ labels: null, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should not update labels if they were not defined in yaml', async () => { const compassYaml = getMockedCompassYaml({}); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: MOCK_BASE_COMPONENT_WITH_ID }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).not.toBeCalledWith( expect.objectContaining({ labels: null, }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should add an invalid file error if the create from yaml file was updated to be empty', async () => { const component = getMockedComponent({ externalAliases: getManagedComponentAliases({}), }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ // get the new path Alias. No call for name alias since it doesnt exist success: true, data: { component }, errors: [], }); mockGetComponent.mockResolvedValue({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump({}), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, lastSyncEvent: { lastSyncErrors: [ 'Invalid YAML format. Try again with a valid YAML file', ], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should do nothing when the file update contains nothing and no aliases exist', async () => { const errMsg = 'testErr'; const component = getMockedComponent({ externalAliases: [], }); mockGetComponentByExternalAlias.mockResolvedValue({ // get the new path Alias. No call for name alias since it doesnt exist success: true, data: { component: null }, errors: [{ message: errMsg }], }); mockGetComponent.mockResolvedValue({ success: true, data: { component: null }, errors: [], }); const resp = await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFile: yaml.dump({}), configFileMetadata: getMockedConfigFileMetadata(), }); expect(resp.errors[0].message).toBe( `Unable to find component to update. Check file contents for errors. Error: [{"message":"${errMsg}"}]`, ); expect(mockUpdateComponent).not.toBeCalled(); expect(mockUpdateComponentDataManager).not.toBeCalled(); expect(mockDetachDataManager).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); describe('for hello cloudId', () => { it('should update dataManager when the file update is for hello for a service type component', async () => { const compassYaml = getMockedCompassYaml({ labels: [], typeId: 'SERVICE', }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: { ...MOCK_BASE_COMPONENT_WITH_ID, typeId: CompassComponentType.Service, dataManager: { externalSourceURL: MOCK_DATA_MANAGER_EXTERNAL_SOURCE_URL, }, }, }, errors: [], }); const resp = await configReq.syncComponentWithFile({ cloudId: HELLO_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), externalSourceURL: MOCK_DATA_MANAGER_EXTERNAL_SOURCE_URL, }); expect(resp.errors[0].message).toBe( `Unable to sync that component type on this site`, ); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: compassYaml.id, externalSourceURL: MOCK_DATA_MANAGER_EXTERNAL_SOURCE_URL, lastSyncEvent: { lastSyncErrors: [ 'Unable to sync SERVICE on this cloudId: a436116f-02ce-4520-8fbb-7301462a1674. Please use the UI to create or update SERVICE components.', ], status: 'USER_ERROR', }, }); expect(mockUpdateComponent).not.toBeCalled(); expect(mockDetachDataManager).not.toBeCalled(); expect(mockCreateComponent).not.toBeCalled(); expect(mockStopTimer).toHaveBeenCalledTimes(0); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('should update when the file update is for hello for a library type component', async () => { const compassYaml = getMockedCompassYaml({ labels: [], typeId: 'LIBRARY', }); mockUnlinkComponentIfRetargetedByNewIdRequests(); mockGetComponent.mockResolvedValue({ success: true, data: { component: { ...MOCK_BASE_COMPONENT_WITH_ID, typeId: CompassComponentType.Other, }, }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: HELLO_CLOUD_ID, configFile: yaml.dump(compassYaml), configFileMetadata: getMockedConfigFileMetadata(), }); expect(mockUpdateComponent).toBeCalledWith( expect.objectContaining({ labels: [], }), ); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); }); it('In create from yaml, it should add an error to the component when it is changed to have no name and remove the old name alias, but not add a new one', async () => { const component = getMockedComponent({ externalAliases: getManagedComponentAliases({}), }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFileMetadata: { configFileAction: ConfigFileActions.UPDATE, oldPath: MOCK_COMPONENT_PATH, newPath: MOCK_COMPONENT_PATH, deduplicationId: MOCK_DEDUPLICATION_ID, }, configFile: yaml.dump({ id: null, description: 'test' }), }); expect(mockGetComponentByExternalAlias).toBeCalledTimes(1); expect(mockGetComponentByExternalAlias).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalId: `${MOCK_DEDUPLICATION_ID}:${MOCK_COMPONENT_PATH}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, options: { includeCustomFields: true, includeLinks: true, }, }); expect(mockDeleteExternalAlias).toBeCalledWith({ componentId: component.id, externalAlias: { externalId: component.externalAliases[0].externalAliasId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }, }); expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, lastSyncEvent: { lastSyncErrors: ['"name" must be included in the configuration file'], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); it('In create from yaml, it should add an error to the component when it is changed to have an invalid name and remove the old name alias, but not add a new one', async () => { const component = getMockedComponent({ externalAliases: getManagedComponentAliases({}), }); mockGetComponentByExternalAlias.mockResolvedValueOnce({ success: true, data: { component }, errors: [], }); await configReq.syncComponentWithFile({ cloudId: MOCK_CLOUD_ID, configFileMetadata: { configFileAction: ConfigFileActions.UPDATE, oldPath: MOCK_COMPONENT_PATH, newPath: MOCK_COMPONENT_PATH, deduplicationId: MOCK_DEDUPLICATION_ID, }, configFile: yaml.dump({ id: null, name: 12345, description: 'test' }), }); expect(mockGetComponentByExternalAlias).toBeCalledTimes(1); expect(mockGetComponentByExternalAlias).toBeCalledWith({ cloudId: MOCK_CLOUD_ID, externalId: `${MOCK_DEDUPLICATION_ID}:${MOCK_COMPONENT_PATH}`, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_PATH_TO_FILE, options: { includeCustomFields: true, includeLinks: true, }, }); expect(mockDeleteExternalAlias).toBeCalledWith({ componentId: component.id, externalAlias: { externalId: component.externalAliases[0].externalAliasId, externalSource: EXTERNAL_ALIAS_SOURCE_COMPONENT_NAME, }, }); expect(mockCreateExternalAlias).not.toBeCalled(); expect(mockUpdateComponentDataManager).toBeCalledWith({ componentId: MOCK_COMPONENT_ID, lastSyncEvent: { lastSyncErrors: ['"name" must be of type "string"'], status: 'USER_ERROR', }, }); expect(mockStopTimer).toHaveBeenCalledTimes(1); expect(mockIncrement).toHaveBeenCalledTimes(1); }); });