Skip to content

Commit

Permalink
Merge pull request #64 from adobe/CI-5884
Browse files Browse the repository at this point in the history
[WIP] Force sync registrations on --force-events flag
  • Loading branch information
sangeetha5491 authored Jul 14, 2023
2 parents b63559b + e89ca41 commit 32d36ed
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 37 deletions.
4 changes: 2 additions & 2 deletions src/hooks/post-deploy-event-reg.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ const {
WEBHOOK, deployRegistration
} = require('./utils/hook-utils')

module.exports = async function ({ appConfig }) {
await deployRegistration({ appConfig }, WEBHOOK, 'post-deploy-event-reg')
module.exports = async function ({ appConfig, force }) {
await deployRegistration({ appConfig }, WEBHOOK, 'post-deploy-event-reg', force)
}
94 changes: 70 additions & 24 deletions src/hooks/utils/hook-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function getDeliveryType (registration) {
}

/**
*
* @private
* @param {object} registration - registration object
* @param {object} providerMetadataToProviderIdMapping - mapping of provider metadata to provider id
* @returns {Array} of events of interest {provider_id, event_code}
Expand All @@ -58,7 +58,7 @@ function getEventsOfInterestForRegistration (registration,
}

/**
*
* @private
* @param {object} projectConfig - project config object
* @returns {object} Object containing orgId, X_API_KEY, eventsClient
*/
Expand All @@ -75,6 +75,7 @@ async function initEventsSdk (projectConfig) {
}

/**
* @private
* @returns {object} Object containing mapping of provider metadata to provider id
*/
function getProviderMetadataToProviderIdMapping () {
Expand All @@ -91,20 +92,30 @@ function getProviderMetadataToProviderIdMapping () {
}

/**
* @private
* @param {object} eventRegistrations - registrations from the .aio config file
* @returns {object} Object containing mapping of registration name to registration object
*/
function getRegistrationsFromAioConfig (eventRegistrations) {
function getRegistrationNameToRegistrationsMap (eventRegistrations) {
const registrationNameToRegistrations = {}
if (eventRegistrations) {
for (const registration of eventRegistrations) {
registrationNameToRegistrations[registration.name] = registration
}
for (const registration of eventRegistrations) {
registrationNameToRegistrations[registration.name] = registration
}
return registrationNameToRegistrations
}

/**
* @private
* @param {Array.<string>} workspaceRegistrationNames Registration names from the Console workspace
* @param {Array.<string>} appConfigRegistrationNames Registration names defined in the app.config.yaml file
* @returns {Array.<string>} Registrations that are part of the workspace, but not part of the app.config.yaml
*/
function getWorkspaceRegistrationsToBeDeleted (workspaceRegistrationNames, appConfigRegistrationNames) {
return workspaceRegistrationNames.filter(wsRegistration => !appConfigRegistrationNames.includes(wsRegistration))
}

/**
* @private
* @param {object} body - Registration Create/Update Model
* @param {object} eventsSDK - eventsSDK object containing eventsClient and orgId
* @param {object} existingRegistration - existing registration obtained from .aio config if exists
Expand All @@ -114,23 +125,49 @@ async function createOrUpdateRegistration (body, eventsSDK, existingRegistration
if (existingRegistration) {
const response = await eventsSDK.eventsClient.updateRegistration(eventsSDK.orgId, project.id, project.workspace.id,
existingRegistration.registration_id, body)
console.log('Updated registration:' + JSON.stringify(response))
console.log('Updated registration with name:' + response.name + ' and id:' + response.registration_id)
} else {
const response = await eventsSDK.eventsClient.createRegistration(eventsSDK.orgId, project.id,
project.workspace.id, body)
console.log('Created registration:' + JSON.stringify(response))
console.log('Created registration:' + response.name + ' and id:' + response.registration_id)
}
}

/**
* @private
* @param {object} eventsSDK - eventsSDK object containing eventsClient and orgId
* @param {object} existingRegistration - existing registration obtained from .aio config if exists
* @param {object} project - project details from .aio config file
*/
async function deleteRegistration (eventsSDK, existingRegistration, project) {
await eventsSDK.eventsClient.deleteRegistration(eventsSDK.orgId, project.id, project.workspace.id,
existingRegistration.registration_id)
console.log('Deleted registration with name:' + existingRegistration.name + ' and id:' + existingRegistration.registration_id)
}

/**
* @private
* @param {object} eventsSDK - eventsSDK object containing eventsClient and orgId
* @param {object} project - project details from .aio config file
* @returns {object} Object containing all registrations for the workspace
*/
async function getAllRegistrationsForWorkspace (eventsSDK, project) {
const registrationsForWorkspace = await eventsSDK.eventsClient.getAllRegistrationsForWorkspace(eventsSDK.orgId, project.id,
project.workspace.id)
if (!registrationsForWorkspace) { return {} }
return getRegistrationNameToRegistrationsMap(registrationsForWorkspace._embedded.registrations)
}

/**
* @param {object} appConfigRoot - Root object containing events and project details
* @param {object} appConfigRoot.appConfig - Object containing events and project details
* @param {object} appConfigRoot.appConfig.project - Project details from the .aio file
* @param {object} appConfigRoot.appConfig.events - Events registrations that are part of the app.config.yaml file
* @param {string} expectedDeliveryType - Delivery type based on the hook that is calling. Expected delivery type can be webhook or journal
* @param {string} hookType - pre-deploy-event-reg or post-deploy-event-reg hook values
* @param {boolean} forceEventsFlag - determines if registrations that are part of the workspace but not part of the app.config.yaml will be deleted or not
*/
async function deployRegistration ({ appConfig: { events, project } }, expectedDeliveryType, hookType) {
async function deployRegistration ({ appConfig: { events, project } }, expectedDeliveryType, hookType, forceEventsFlag) {
if (!project) {
throw new Error(
`No project found, skipping event registration in ${hookType} hook`)
Expand All @@ -145,30 +182,39 @@ async function deployRegistration ({ appConfig: { events, project } }, expectedD
throw new Error(
`Events SDK could not be initialised correctly. Skipping event registration in ${hookType} hook`)
}
const registrations = events.registrations
const registrationsFromConfig = events.registrations
const providerMetadataToProviderIdMapping = getProviderMetadataToProviderIdMapping()
let existingRegistrations
if (project.workspace.details.events) {
existingRegistrations = getRegistrationsFromAioConfig(
project.workspace.details.events.registrations)
}

for (const registrationName in registrations) {
const deliveryType = getDeliveryType(registrations[registrationName])
const registrationsFromWorkspace = await getAllRegistrationsForWorkspace(eventsSDK, project)
for (const registrationName in registrationsFromConfig) {
const deliveryType = getDeliveryType(registrationsFromConfig[registrationName])
if (deliveryType === expectedDeliveryType) {
const body = {
name: registrationName,
client_id: eventsSDK.X_API_KEY,
description: registrations[registrationName].description,
description: registrationsFromConfig[registrationName].description,
delivery_type: deliveryType,
runtime_action: registrationsFromConfig[registrationName].runtime_action,
events_of_interest: getEventsOfInterestForRegistration(
registrations[registrationName], providerMetadataToProviderIdMapping)
registrationsFromConfig[registrationName], providerMetadataToProviderIdMapping)
}
try {
let existingRegistration
if (existingRegistrations && existingRegistrations[registrationName]) { existingRegistration = existingRegistrations[registrationName] }
await createOrUpdateRegistration(body, eventsSDK,
existingRegistration, project)
if (registrationsFromWorkspace && registrationsFromWorkspace[registrationName]) { existingRegistration = registrationsFromWorkspace[registrationName] }
await createOrUpdateRegistration(body, eventsSDK, existingRegistration, project)
} catch (e) {
throw new Error(
e + '\ncode:' + e.code + '\nDetails:' + JSON.stringify(
e.sdkDetails))
}
}
}

if (forceEventsFlag) {
const registrationsToDeleted = getWorkspaceRegistrationsToBeDeleted(Object.keys(registrationsFromWorkspace), Object.keys(registrationsFromConfig))
console.log('The following registrations will be deleted: ', registrationsToDeleted)
for (const registrationName of registrationsToDeleted) {
try {
await deleteRegistration(eventsSDK, registrationsFromWorkspace[registrationName], project)
} catch (e) {
throw new Error(
e + '\ncode:' + e.code + '\nDetails:' + JSON.stringify(
Expand Down
4 changes: 2 additions & 2 deletions test/__fixtures__/registration/list.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
}
},
"id": 11111,
"name": "bowling 1",
"name": "Event Registration 1",
"description": "let me know when we can go play bowling!",
"client_id": "1234654902189324798",
"webhook_url": "https://send-me-a-bowling-event.com/right-now",
Expand Down Expand Up @@ -48,7 +48,7 @@
}
},
"id": 22222,
"name": "table tenis 2",
"name": "Event Registration 2",
"description": "registration for table tennis events",
"client_id": "1234654902189324798",
"webhook_url": "https://send-me-a-table-tennis-event.com/please",
Expand Down
4 changes: 2 additions & 2 deletions test/__fixtures__/registration/list.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ID NAME ENABLED DELIVERY_TYPE WEBHOOK_STATUS
───────────────────────────────────── ──────────────────────── ───────── ───────────── ──────────────
REGID1 bowling 1 true webhook verified
REGID2 table tenis 2 true webhook verified
REGID1 Event Registration 1 true webhook verified
REGID2 Event Registration 2 true webhook verified
4 changes: 2 additions & 2 deletions test/__fixtures__/registration/list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ _embedded:
href: >-
https://api.adobe.io/events/consumerOrgId/projectId/workspaceId/registrations/REGID1
id: 11111
name: bowling 1
name: Event Registration 1
description: let me know when we can go play bowling!
client_id: '1234654902189324798'
webhook_url: 'https://send-me-a-bowling-event.com/right-now'
Expand Down Expand Up @@ -39,7 +39,7 @@ _embedded:
href: >-
https://api.adobe.io/events/consumerOrgId/projectId/workspaceId/registrations/REGID2
id: 22222
name: table tenis 2
name: Event Registration 2
description: registration for table tennis events
client_id: '1234654902189324798'
webhook_url: 'https://send-me-a-table-tennis-event.com/please'
Expand Down
86 changes: 83 additions & 3 deletions test/hooks/post-deploy-event-reg.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ jest.mock('@adobe/aio-lib-events')
const eventsSdk = require('@adobe/aio-lib-events')
const mockEventsSdkInstance = {
createRegistration: jest.fn(),
updateRegistration: jest.fn()
updateRegistration: jest.fn(),
getAllRegistrationsForWorkspace: jest.fn(),
deleteRegistration: jest.fn()
}
jest.mock('@adobe/aio-lib-ims')
const { getToken } = require('@adobe/aio-lib-ims')
Expand All @@ -32,6 +34,10 @@ describe('post deploy event registration hook interfaces', () => {
eventsSdk.init.mockResolvedValue(mockEventsSdkInstance)
})

afterEach(() => {
eventsSdk.init.mockRestore()
})

test('hook interface', async () => {
const hook = require('../../src/hooks/post-deploy-event-reg')
expect(typeof hook).toBe('function')
Expand Down Expand Up @@ -126,6 +132,7 @@ describe('post deploy event registration hook interfaces', () => {
getToken.mockReturnValue('accessToken')
const events = mock.data.sampleEvents
events.registrations['Event Registration 1'].delivery_type = 'webhook'
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsWithEmptyResponse)
mockEventsSdkInstance.createRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
const projectWithEmptyEvents = mock.data.sampleProjectWithoutEvents
projectWithEmptyEvents.workspace.details.events = {}
Expand All @@ -141,6 +148,7 @@ describe('post deploy event registration hook interfaces', () => {
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsResponse)
mockEventsSdkInstance.updateRegistration.mockRejectedValueOnce(JSON.stringify({
code: 500,
errorDetails: {
Expand All @@ -149,7 +157,7 @@ describe('post deploy event registration hook interfaces', () => {
}))
await expect(hook({ appConfig: { project: mock.data.sampleProject, events: mock.data.sampleEvents } })).rejects.toThrowError()
expect(mockEventsSdkInstance.updateRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.updateRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'registrationId1',
expect(mockEventsSdkInstance.updateRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'REGID1',
mock.data.hookDecodedEventRegistration1
)
})
Expand All @@ -159,11 +167,83 @@ describe('post deploy event registration hook interfaces', () => {
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsResponse)
mockEventsSdkInstance.updateRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
await expect(hook({ appConfig: { project: mock.data.sampleProject, events: mock.data.sampleEvents } })).resolves.not.toThrowError()
expect(mockEventsSdkInstance.updateRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.updateRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'registrationId1',
expect(mockEventsSdkInstance.updateRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'REGID1',
mock.data.hookDecodedEventRegistration1
)
})

test('successfully delete registrations not part of the config', async () => {
const hook = require('../../src/hooks/post-deploy-event-reg')
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
const events = {
registrations: {
'Event Registration 1': mock.data.sampleEvents.registrations['Event Registration 1']
}
}
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsResponse)
mockEventsSdkInstance.updateRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
const projectWithEmptyEvents = mock.data.sampleProjectWithoutEvents
await expect(hook({ appConfig: { project: projectWithEmptyEvents, events }, force: true })).resolves.not.toThrowError()
expect(mockEventsSdkInstance.updateRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.deleteRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'REGID2')
})

test('test delete registrations not part of the config, with no registrations to delete', async () => {
const hook = require('../../src/hooks/post-deploy-event-reg')
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsResponse)
mockEventsSdkInstance.updateRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
await expect(hook({ appConfig: { project: mock.data.sampleProject, events: mock.data.sampleEvents }, force: true })).resolves.not.toThrowError()
expect(mockEventsSdkInstance.updateRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.updateRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'REGID1',
mock.data.hookDecodedEventRegistration1)
expect(mockEventsSdkInstance.deleteRegistration).toHaveBeenCalledTimes(0)
})

test('test delete registrations not part of the config, with no registrations in workspace', async () => {
const hook = require('../../src/hooks/post-deploy-event-reg')
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsWithEmptyResponse)

mockEventsSdkInstance.createRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
await expect(hook({ appConfig: { project: mock.data.sampleProjectWithoutEvents, events: mock.data.sampleEvents }, force: true })).resolves.not.toThrowError()
expect(mockEventsSdkInstance.createRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.createRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID,
mock.data.hookDecodedEventRegistration1)
expect(mockEventsSdkInstance.deleteRegistration).toHaveBeenCalledTimes(0)
})

test('test error on delete registrations not part of the config', async () => {
const hook = require('../../src/hooks/post-deploy-event-reg')
expect(typeof hook).toBe('function')
process.env = mock.data.dotEnv
getToken.mockReturnValue('accessToken')
const events = {
registrations: {
'Event Registration 1': mock.data.sampleEvents.registrations['Event Registration 1']
}
}
mockEventsSdkInstance.getAllRegistrationsForWorkspace.mockResolvedValue(mock.data.getAllWebhookRegistrationsResponse)
mockEventsSdkInstance.updateRegistration.mockReturnValue(mock.data.createWebhookRegistrationResponse)
mockEventsSdkInstance.deleteRegistration.mockRejectedValueOnce({
code: 500,
errorDetails: {
message: 'Internal Server Error'
}
})
const projectWithEmptyEvents = mock.data.sampleProjectWithoutEvents
await expect(hook({ appConfig: { project: projectWithEmptyEvents, events }, force: true })).rejects.toThrowError()
expect(mockEventsSdkInstance.updateRegistration).toBeCalledTimes(1)
expect(mockEventsSdkInstance.deleteRegistration).toHaveBeenCalledWith(CONSUMER_ID, PROJECT_ID, WORKSPACE_ID, 'REGID2')
})
})
Loading

0 comments on commit 32d36ed

Please sign in to comment.