Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assetlibraryhistory: fix some errors and add test to show the errors #193

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@awssolutions/cdf-assetlibrary-history",
"comment": "fixed some errors with undefined data and added tests",
"type": "none"
}
],
"packageName": "@awssolutions/cdf-assetlibrary-history"
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ export class CreateAction implements EventAction {

// save the updated job info (1 record for the version, 1 to represent the latest)
await this.eventsDao.create(toSave);
toSave.time = 'latest';
await this.eventsDao.create(toSave);
const toSave2: StateHistoryModel = {
...toSave,
time: 'latest',
};
await this.eventsDao.create(toSave2);

logger.debug('eventaction.create execute: exit:true');
return event;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ export class DeleteAction implements EventAction {
};

await this.eventsDao.create(toSave);
toSave.time = 'latest';
await this.eventsDao.update(toSave);
const toUpdate: StateHistoryModel = {
...toSave,
time: 'latest',
};
await this.eventsDao.update(toUpdate);

return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ export class PublishTemplateAction implements EventAction {
};

await this.eventsDao.create(toSave);
toSave.time = 'latest';
await this.eventsDao.update(toSave);
const toUpdate: StateHistoryModel = {
...toSave,
time: 'latest',
};
await this.eventsDao.update(toUpdate);

return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,18 @@ export class UpdateAction implements EventAction {
event.attributes['attachedToGroup']
);
} else if (event.attributes['detachedFromGroup'] !== undefined) {
const newRelationship = mergedState['groups']['out'][
event.attributes['relationship']
].filter((value: string) => {
return value !== event.attributes['detachedFromGroup'];
});
if (mergedState['groups']['out'] === undefined) {
mergedState['groups']['out'] = {};
} else {
const newRelationship = mergedState['groups']['out'][
event.attributes['relationship']
]?.filter((value: string) => {
return value !== event.attributes['detachedFromGroup'];
});

mergedState['groups']['out'][event.attributes['relationship']] = newRelationship;
mergedState['groups']['out'][event.attributes['relationship']] =
newRelationship;
}
} else if (event.attributes['attachedToDevice'] !== undefined) {
if (mergedState['devices'] === undefined) {
mergedState['devices'] = {};
Expand All @@ -78,23 +83,29 @@ export class UpdateAction implements EventAction {
event.attributes['attachedToDevice']
);
} else if (event.attributes['detachedFromDevice'] !== undefined) {
const newRelationship = mergedState['devices']['out'][
event.attributes['relationship']
].filter((value: string) => {
return value !== event.attributes['detachedFromDevice'];
});

mergedState['groups']['out'][event.attributes['relationship']] = newRelationship;
if (mergedState['devices']['out'] === undefined) {
mergedState['devices']['out'] = {};
} else {
const newRelationship = mergedState['devices']['out'][
event.attributes['relationship']
]?.filter((value: string) => {
return value !== event.attributes['detachedFromDevice'];
});
mergedState['devices']['out'][event.attributes['relationship']] =
newRelationship;
}
}
}

if (event.event === 'modify' && event.type === 'devices') {
const state = JSON.parse(existingEvent.state);
if (state['groups'] !== undefined) {
mergedState['groups'] = state['groups'];
}
if (state['devices'] !== undefined) {
mergedState['devices'] = state['devices'];
if (existingEvent !== undefined) {
if (event.event === 'modify' && event.type === 'devices') {
const state = JSON.parse(existingEvent.state);
if (state['groups'] !== undefined) {
mergedState['groups'] = state['groups'];
}
if (state['devices'] !== undefined) {
mergedState['devices'] = state['devices'];
}
}
}

Expand All @@ -111,9 +122,19 @@ export class UpdateAction implements EventAction {
state: event.payload,
};

const toUpdate: StateHistoryModel = {
...toSave,
time: 'latest',
};
await this.eventsDao.create(toSave);
toSave.time = 'latest';
await this.eventsDao.update(toSave);
// If there is no "latest" event, then we need to create it...not update it
// This is probably an error getting here (like when the item was initially created, it didn't get the latest entry),
// but we should recover gracefully from it
if (existingEvent === undefined) {
await this.eventsDao.create(toUpdate);
} else {
await this.eventsDao.update(toUpdate);
}

return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,11 @@ export class UpdateComponentParentAction implements EventAction {
};

await this.eventsDao.create(toSave);
toSave.time = 'latest';
await this.eventsDao.update(toSave);
const toUpdate: StateHistoryModel = {
...toSave,
time: 'latest',
};
await this.eventsDao.update(toUpdate);

return event;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export interface EventModel {
type: Category;
time: string;
event: EventType;
user: string;
payload: string;
user?: string;
payload?: string;
attributes: { [key: string]: string };
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*********************************************************************************************************************
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. *
* *
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
* with the License. A copy of the License is located at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
* and limitations under the License. *
*********************************************************************************************************************/
import 'reflect-metadata';

import { createMockInstance } from 'jest-create-mock-instance';
import { CreateAction } from './actions/eventaction.create';
import { EventActionFactory } from './actions/eventaction.factory';
import { UnsupportedAction } from './actions/eventaction.unsupported';
import { UpdateAction } from './actions/eventaction.update';
import { EventsDao } from './events.dao';
import { Category, EventModel, EventType } from './events.models';
import { EventsService } from './events.service';
const TEST_USER_STATE =
'{"groups":{},"attributes":{"email":"[email protected]","lastUserCaptureTime":"1698712913","country":"US","language":"en-us","firstName":"Test","lastName":"Tester"},"name":"test_user_id","templateId":"user","parentPath":"/user","groupPath":"/user/test_user_id","category":"group"}';

describe('EventsService', () => {
let instance: EventsService;
let mockedEventActionFactory: jest.Mocked<EventActionFactory>;
let mockedEventDao: jest.Mocked<EventsDao>;
let updateAction: UpdateAction;
let unsupportedAction: UnsupportedAction;
let createAction: CreateAction;

beforeEach(() => {
mockedEventActionFactory = createMockInstance(EventActionFactory);
mockedEventDao = createMockInstance(EventsDao);
updateAction = new UpdateAction(mockedEventDao);
unsupportedAction = new UnsupportedAction();
createAction = new CreateAction(mockedEventDao);
instance = new EventsService(mockedEventActionFactory);

mockedEventDao.getLatest = jest.fn().mockImplementation((objectId) => {
if (objectId === '/user/test_user_id') {
return Promise.resolve({
objectId: '/user/test_user_id',
time: 'latest',
event: 'modify',
state: TEST_USER_STATE,
type: 'groups',
});
} else if (objectId === 'test_device_id') {
return Promise.resolve({
objectId: 'test_device_id',
time: 'latest',
event: 'modify',
state: '{"attributes":{},"groups":{"out":{"manufactured_by":["/supplier/supplier1"],"has_firmware":["/firmware/firmwareId1"],"is_model":["/device/robot/testDevice"]}},"devices":{},"templateId":"testDevice","deviceId":"test_device_id","state":"unprovisioned","category":"device","connected":true}',
type: 'devices',
});
} else if ((objectId = 'test_undefined_object')) {
return Promise.resolve(undefined);
} else {
return Promise.resolve(undefined);
}
});

mockedEventDao.update = jest.fn().mockImplementation(() => {
return Promise.resolve();
});
mockedEventDao.create = jest.fn().mockImplementation(() => {
return Promise.resolve();
});

mockedEventActionFactory.getAction = jest.fn().mockImplementation((event) => {
if (event.event === EventType.modify) {
return [updateAction];
} else if (event.event === EventType.create) {
return [createAction];
}
return [unsupportedAction];
});
});

it('happy path update a pre-existing group', async () => {
let testEvent = {
objectId: '/user/test_user_id',
type: Category.groups,
event: EventType.modify,
attributes: {
sourceGroupPath: '/user/test_user_id',
detachedFromGroup: '/client_device/test_device_id',
relationship: 'uses',
},
time: '2024-02-23T21:00:00.000Z',
};
await instance.create(testEvent);

expect(mockedEventDao.create).toBeCalledTimes(1);
// Calling `create` will add the `out: {}` to the user state in dynamo
let userState = JSON.parse(TEST_USER_STATE);
userState.groups = { out: {} };
expect(mockedEventDao.create).toHaveBeenCalledWith({
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: JSON.stringify(userState),
time: testEvent.time,
user: undefined,
});
expect(mockedEventDao.update).toBeCalledTimes(1);
expect(mockedEventDao.update).toHaveBeenCalledWith({
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: JSON.stringify(userState),
time: 'latest',
user: undefined,
});
});

it('happy path update a pre-existing device', async () => {
let testEvent: EventModel = {
objectId: 'test_device_id',
type: Category.devices,
event: EventType.modify,
payload:
'{"attributes":{},"groups":{},"devices":{},"deviceId":"test_device_id","connected":false,"templateId":"testDevice","category":"device"}',
time: '2024-02-24T20:00:00.000Z',
attributes: undefined,
};
await instance.create(testEvent);

expect(mockedEventDao.create).toBeCalledTimes(1);
expect(mockedEventDao.create).toHaveBeenCalledWith({
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: testEvent.time,
user: undefined,
});
expect(mockedEventDao.update).toBeCalledTimes(1);
expect(mockedEventDao.update).toHaveBeenCalledWith({
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: 'latest',
user: undefined,
});
});

it('happy path create a new device', async () => {
let testEvent: EventModel = {
objectId: 'test_device_id',
type: Category.devices,
event: EventType.create,
payload:
'{"attributes":{},"groups":{},"devices":{},"deviceId":"test_device_id","connected":false,"templateId":"testDevice","category":"device"}',
time: '2024-02-24T20:00:00.000Z',
attributes: undefined,
};
await instance.create(testEvent);

expect(mockedEventDao.create).toBeCalledTimes(2);
expect(mockedEventDao.create).toHaveBeenNthCalledWith(1, {
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: testEvent.time,
user: undefined,
});
expect(mockedEventDao.create).toHaveBeenNthCalledWith(2, {
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: 'latest',
user: undefined,
});
expect(mockedEventDao.update).toBeCalledTimes(0);
});

it('getLatest returns an undefined object on a "modify" call', async () => {
let testEvent: EventModel = {
objectId: 'test_undefined_object',
type: Category.devices,
event: EventType.modify,
payload:
'{"attributes":{},"groups":{},"devices":{},"deviceId":"test_undefined_object","connected":false,"templateId":"testDevice","category":"device"}',
time: '2024-02-24T20:00:00.000Z',
attributes: undefined,
};
await instance.create(testEvent);

expect(mockedEventDao.create).toBeCalledTimes(2);
expect(mockedEventDao.create).toHaveBeenNthCalledWith(1, {
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: testEvent.time,
user: undefined,
});
expect(mockedEventDao.create).toHaveBeenNthCalledWith(2, {
objectId: testEvent.objectId,
type: testEvent.type,
event: testEvent.event,
state: testEvent.payload,
time: 'latest',
user: undefined,
});
expect(mockedEventDao.update).toBeCalledTimes(0);
});
});
Loading