@eyevinn/player-analytics-shared 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-test.yml +19 -0
- package/.github/workflows/release-gh-package.yml +24 -0
- package/.github/workflows/release-npmjs-package.yml +21 -0
- package/README.md +2 -0
- package/adapters/db/DynamoDBAdapter.ts +147 -0
- package/adapters/db/MongoDBAdapter.ts +79 -0
- package/adapters/queue/BeanstalkdAdapter.ts +41 -0
- package/adapters/queue/RedisAdapter.ts +34 -0
- package/adapters/queue/SqsQueueAdapter.ts +127 -0
- package/build/adapters/db/DynamoDBAdapter.d.ts +14 -0
- package/build/adapters/db/DynamoDBAdapter.js +138 -0
- package/build/adapters/db/DynamoDBAdapter.js.map +1 -0
- package/build/adapters/db/MongoDBAdapter.d.ts +16 -0
- package/build/adapters/db/MongoDBAdapter.js +98 -0
- package/build/adapters/db/MongoDBAdapter.js.map +1 -0
- package/build/adapters/queue/BeanstalkdAdapter.d.ts +13 -0
- package/build/adapters/queue/BeanstalkdAdapter.js +55 -0
- package/build/adapters/queue/BeanstalkdAdapter.js.map +1 -0
- package/build/adapters/queue/RedisAdapter.d.ts +12 -0
- package/build/adapters/queue/RedisAdapter.js +44 -0
- package/build/adapters/queue/RedisAdapter.js.map +1 -0
- package/build/adapters/queue/SqsQueueAdapter.d.ts +12 -0
- package/build/adapters/queue/SqsQueueAdapter.js +122 -0
- package/build/adapters/queue/SqsQueueAdapter.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.js +19 -0
- package/build/index.js.map +1 -0
- package/build/types/db.d.ts +32 -0
- package/build/types/db.js +12 -0
- package/build/types/db.js.map +1 -0
- package/build/types/interfaces.d.ts +2 -0
- package/build/types/interfaces.js +15 -0
- package/build/types/interfaces.js.map +1 -0
- package/build/types/queue.d.ts +15 -0
- package/build/types/queue.js +7 -0
- package/build/types/queue.js.map +1 -0
- package/build/util/constants.d.ts +1 -0
- package/build/util/constants.js +5 -0
- package/build/util/constants.js.map +1 -0
- package/build/util/logger.d.ts +3 -0
- package/build/util/logger.js +37 -0
- package/build/util/logger.js.map +1 -0
- package/index.ts +8 -0
- package/package.json +43 -0
- package/spec/adapters/DynamoDBAdapter.spec.ts +275 -0
- package/spec/adapters/MongoDBAdapter.spec.ts +170 -0
- package/spec/adapters/SqsQueueAdapter.spec.ts +149 -0
- package/spec/support/jasmine.json +11 -0
- package/tsconfig-build.json +6 -0
- package/tsconfig.json +21 -0
- package/tslint.json +9 -0
- package/types/db.ts +39 -0
- package/types/interfaces.ts +2 -0
- package/types/queue.ts +17 -0
- package/util/constants.ts +1 -0
- package/util/logger.ts +45 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateTableCommand,
|
|
3
|
+
ListTablesCommand,
|
|
4
|
+
PutItemCommand,
|
|
5
|
+
DynamoDBClient,
|
|
6
|
+
GetItemCommand,
|
|
7
|
+
DeleteItemCommand,
|
|
8
|
+
CreateTableCommandOutput,
|
|
9
|
+
PutItemCommandOutput,
|
|
10
|
+
GetItemCommandOutput,
|
|
11
|
+
DeleteItemCommandOutput,
|
|
12
|
+
QueryCommand,
|
|
13
|
+
QueryCommandOutput,
|
|
14
|
+
DescribeTableCommand,
|
|
15
|
+
DescribeTableCommandOutput,
|
|
16
|
+
} from '@aws-sdk/client-dynamodb';
|
|
17
|
+
import { AwsError, mockClient } from 'aws-sdk-client-mock';
|
|
18
|
+
import { DynamoDBAdapter } from '../../adapters/db/DynamoDBAdapter';
|
|
19
|
+
import { ErrorType } from '../../types/interfaces';
|
|
20
|
+
import Logger from '../../util/logger';
|
|
21
|
+
|
|
22
|
+
const ddbMock = mockClient(DynamoDBClient);
|
|
23
|
+
|
|
24
|
+
describe('Dynamo DB Adapter', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
process.env.AWS_REGION = 'us-east-1';
|
|
27
|
+
ddbMock.reset();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return true if table exists in database', async () => {
|
|
31
|
+
const DDBReply: DescribeTableCommandOutput = {
|
|
32
|
+
Table: {
|
|
33
|
+
TableArn: 'arn:aws:dynamodb:us-west-2:123456789012:table/mock_table_1',
|
|
34
|
+
AttributeDefinitions: [
|
|
35
|
+
{
|
|
36
|
+
AttributeName: 'sessionId',
|
|
37
|
+
AttributeType: 'S',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
AttributeName: 'timestamp',
|
|
41
|
+
AttributeType: 'N',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
KeySchema: [
|
|
45
|
+
{
|
|
46
|
+
AttributeName: 'sessionId',
|
|
47
|
+
KeyType: 'HASH',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
AttributeName: 'timestamp',
|
|
51
|
+
KeyType: 'RANGE',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
LocalSecondaryIndexes: [],
|
|
55
|
+
ProvisionedThroughput: {
|
|
56
|
+
NumberOfDecreasesToday: 0,
|
|
57
|
+
ReadCapacityUnits: 5,
|
|
58
|
+
WriteCapacityUnits: 5,
|
|
59
|
+
},
|
|
60
|
+
TableName: 'mock_table_1',
|
|
61
|
+
TableSizeBytes: 0,
|
|
62
|
+
TableStatus: 'ACTIVE',
|
|
63
|
+
},
|
|
64
|
+
$metadata: {
|
|
65
|
+
httpStatusCode: 200,
|
|
66
|
+
requestId: 'df840ab9-e68b-5c0e-b4a0-5094f2dfaee8',
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
70
|
+
ddbMock.on(DescribeTableCommand).resolves(DDBReply);
|
|
71
|
+
const result = await adapter.tableExists('mock_table_1');
|
|
72
|
+
expect(result).toEqual(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return false if table does not exists in database', async () => {
|
|
76
|
+
const DDBReply: AwsError = {
|
|
77
|
+
Type: 'Sender',
|
|
78
|
+
Code: 'ResourceNotFoundException',
|
|
79
|
+
name: 'ResourceNotFoundException',
|
|
80
|
+
$fault: 'client',
|
|
81
|
+
$metadata: {
|
|
82
|
+
httpStatusCode: 400,
|
|
83
|
+
requestId: 'df840ab9-e68b-5c0e-b4a0-5094f2dfaee8',
|
|
84
|
+
attempts: 1,
|
|
85
|
+
totalRetryDelay: 0,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
89
|
+
ddbMock.on(DescribeTableCommand).resolves(DDBReply);
|
|
90
|
+
const result = await adapter.tableExists('mock_table_1');
|
|
91
|
+
expect(result).toEqual(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should put item to database', async () => {
|
|
95
|
+
const DDBReply: PutItemCommandOutput = {
|
|
96
|
+
$metadata: {
|
|
97
|
+
httpStatusCode: 200,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
101
|
+
const mockEvent = {
|
|
102
|
+
event: 'loading',
|
|
103
|
+
timestamp: 0,
|
|
104
|
+
playhead: 0,
|
|
105
|
+
duration: 0,
|
|
106
|
+
host: 'mock.tenant.mock',
|
|
107
|
+
};
|
|
108
|
+
ddbMock.on(PutItemCommand).resolves(DDBReply);
|
|
109
|
+
const result = await adapter.putItem({
|
|
110
|
+
tableName: 'table_1',
|
|
111
|
+
data: mockEvent,
|
|
112
|
+
});
|
|
113
|
+
expect(result).toBeTrue();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should label errorType with "continue" when allowed error occurs', async () => {
|
|
117
|
+
const DDBReply: AwsError = {
|
|
118
|
+
Type: 'Sender',
|
|
119
|
+
Code: 'ResourceNotFoundException',
|
|
120
|
+
name: 'ResourceNotFoundException',
|
|
121
|
+
$fault: 'client',
|
|
122
|
+
$metadata: {
|
|
123
|
+
httpStatusCode: 400,
|
|
124
|
+
requestId: 'df840ab9-e68b-5c0e-b4a0-5094f2dfaee8',
|
|
125
|
+
attempts: 1,
|
|
126
|
+
totalRetryDelay: 0,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
130
|
+
const mockEvent = {
|
|
131
|
+
event: 'loading',
|
|
132
|
+
timestamp: 0,
|
|
133
|
+
playhead: 0,
|
|
134
|
+
duration: 0,
|
|
135
|
+
host: 'mock.tenant.mock',
|
|
136
|
+
};
|
|
137
|
+
ddbMock.on(PutItemCommand).rejects(DDBReply);
|
|
138
|
+
try {
|
|
139
|
+
const result = await adapter.putItem({
|
|
140
|
+
tableName: 'table_1',
|
|
141
|
+
data: mockEvent,
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
expect(err.errorType).toEqual(ErrorType.CONTINUE);
|
|
145
|
+
expect(err.error.Code).toEqual('ResourceNotFoundException');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should label errorType with "abort" when non-allowed error occurs', async () => {
|
|
150
|
+
const DDBReply: AwsError = {
|
|
151
|
+
Type: 'Sender',
|
|
152
|
+
Code: 'RequestLimitExceeded',
|
|
153
|
+
name: 'RequestLimitExceeded',
|
|
154
|
+
$fault: 'client',
|
|
155
|
+
$metadata: {
|
|
156
|
+
httpStatusCode: 400,
|
|
157
|
+
requestId: 'df840ab9-e68b-5c0e-b4a0-5094f2dfaee8',
|
|
158
|
+
attempts: 1,
|
|
159
|
+
totalRetryDelay: 0,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
163
|
+
const mockEvent = {
|
|
164
|
+
event: 'loading',
|
|
165
|
+
timestamp: 0,
|
|
166
|
+
playhead: 0,
|
|
167
|
+
duration: 0,
|
|
168
|
+
host: 'mock.tenant.mock',
|
|
169
|
+
};
|
|
170
|
+
ddbMock.on(PutItemCommand).rejects(DDBReply);
|
|
171
|
+
try {
|
|
172
|
+
const result = await adapter.putItem({
|
|
173
|
+
tableName: 'table_1',
|
|
174
|
+
data: mockEvent,
|
|
175
|
+
});
|
|
176
|
+
} catch (err) {
|
|
177
|
+
expect(err.errorType).toEqual(ErrorType.ABORT);
|
|
178
|
+
expect(err.error.Code).toEqual('RequestLimitExceeded');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should get item from database', async () => {
|
|
183
|
+
const DDBReply: GetItemCommandOutput = {
|
|
184
|
+
$metadata: {},
|
|
185
|
+
Item: { eventId: { S: '123-123-123-123' } },
|
|
186
|
+
};
|
|
187
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
188
|
+
const mockId = '123-123-123-123';
|
|
189
|
+
ddbMock.on(GetItemCommand).resolves(DDBReply);
|
|
190
|
+
const result = await adapter.getItem({
|
|
191
|
+
tableName: 'table_1',
|
|
192
|
+
sessionId: mockId,
|
|
193
|
+
timestamp: 0,
|
|
194
|
+
});
|
|
195
|
+
expect(result).toEqual({
|
|
196
|
+
$metadata: {},
|
|
197
|
+
Item: { eventId: { S: '123-123-123-123' } },
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should delete item in database', async () => {
|
|
202
|
+
const DDBReply: DeleteItemCommandOutput = {
|
|
203
|
+
$metadata: {
|
|
204
|
+
httpStatusCode: 200,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
208
|
+
const mockId = '123-123-123-123';
|
|
209
|
+
ddbMock.on(DeleteItemCommand).resolves(DDBReply);
|
|
210
|
+
const result = await adapter.deleteItem({
|
|
211
|
+
tableName: 'table_1',
|
|
212
|
+
sessionId: mockId,
|
|
213
|
+
timestamp: 0,
|
|
214
|
+
});
|
|
215
|
+
expect(result).toBeTrue();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should get items from db with a specific sessionId and convert them to valid event objects', async () => {
|
|
219
|
+
const DDBReply: QueryCommandOutput = {
|
|
220
|
+
$metadata: {},
|
|
221
|
+
Items: [
|
|
222
|
+
{
|
|
223
|
+
event: { S: 'playing' },
|
|
224
|
+
sessionId: { S: '123-214-234' },
|
|
225
|
+
timestamp: { N: '1640191099' },
|
|
226
|
+
playhead: { N: '1' },
|
|
227
|
+
duration: { N: '0' },
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
event: { S: 'playing' },
|
|
231
|
+
sessionId: { S: '123-214-234' },
|
|
232
|
+
timestamp: { N: '1640193099' },
|
|
233
|
+
playhead: { N: '3' },
|
|
234
|
+
duration: { N: '0' },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
event: { S: 'paused' },
|
|
238
|
+
sessionId: { S: '123-214-234' },
|
|
239
|
+
timestamp: { N: '1640192099' },
|
|
240
|
+
playhead: { N: '2' },
|
|
241
|
+
duration: { N: '0' },
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
const adapter = new DynamoDBAdapter(Logger);
|
|
246
|
+
ddbMock.on(QueryCommand).resolves(DDBReply);
|
|
247
|
+
const result = await adapter.getItemsBySession({
|
|
248
|
+
tableName: 'table_1',
|
|
249
|
+
sessionId: '123-214-234',
|
|
250
|
+
});
|
|
251
|
+
expect(result).toEqual([
|
|
252
|
+
{
|
|
253
|
+
event: 'playing',
|
|
254
|
+
sessionId: '123-214-234',
|
|
255
|
+
timestamp: 1640191099,
|
|
256
|
+
duration: 0,
|
|
257
|
+
playhead: 1,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
event: 'playing',
|
|
261
|
+
sessionId: '123-214-234',
|
|
262
|
+
timestamp: 1640193099,
|
|
263
|
+
duration: 0,
|
|
264
|
+
playhead: 3,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
event: 'paused',
|
|
268
|
+
sessionId: '123-214-234',
|
|
269
|
+
timestamp: 1640192099,
|
|
270
|
+
duration: 0,
|
|
271
|
+
playhead: 2,
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
2
|
+
import { MongoDBAdapter } from '../../adapters/db/MongoDBAdapter';
|
|
3
|
+
import Logger from '../../util/logger';
|
|
4
|
+
|
|
5
|
+
describe('Mongo DB Adapter', () => {
|
|
6
|
+
let adapter: MongoDBAdapter;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
const instance = await MongoMemoryServer.create();
|
|
9
|
+
const uri = instance.getUri();
|
|
10
|
+
(global as any).__MONGOINSTANCE = instance;
|
|
11
|
+
process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf('/'));
|
|
12
|
+
adapter = new MongoDBAdapter(Logger);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
const collections = await adapter.dbClient.db().collections();
|
|
17
|
+
for (const collection of collections) {
|
|
18
|
+
const c = await adapter.dbClient
|
|
19
|
+
.db()
|
|
20
|
+
.collection(collection.collectionName);
|
|
21
|
+
await c.drop();
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
const collections = [];
|
|
27
|
+
for (const collection of collections) {
|
|
28
|
+
const c = await adapter.dbClient.db().collection(collection);
|
|
29
|
+
await c.drop();
|
|
30
|
+
}
|
|
31
|
+
await adapter.dbClient.close();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return true if table exists in database', async () => {
|
|
35
|
+
const res = await adapter.dbClient.db().createCollection('test_table_1');
|
|
36
|
+
setTimeout(async () => {
|
|
37
|
+
const result = await adapter.tableExists('test_table_1');
|
|
38
|
+
expect(result).toEqual(true);
|
|
39
|
+
}, 1000);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return false if table does not exists in database', async () => {
|
|
43
|
+
const result = await adapter.tableExists('test_table_1');
|
|
44
|
+
expect(result).toEqual(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should put item to database', async () => {
|
|
48
|
+
const mockEvent = {
|
|
49
|
+
event: 'loading',
|
|
50
|
+
timestamp: 0,
|
|
51
|
+
playhead: 0,
|
|
52
|
+
duration: 0,
|
|
53
|
+
host: 'mock.tenant.mock',
|
|
54
|
+
};
|
|
55
|
+
const result = await adapter.putItem({
|
|
56
|
+
tableName: 'test_table_1',
|
|
57
|
+
data: mockEvent,
|
|
58
|
+
});
|
|
59
|
+
expect(result).toBeTrue();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should get item from database', async () => {
|
|
63
|
+
const mockId = '123-123-123-123';
|
|
64
|
+
await adapter.putItem({
|
|
65
|
+
tableName: 'test_table_1',
|
|
66
|
+
data: {
|
|
67
|
+
event: 'loading',
|
|
68
|
+
timestamp: 0,
|
|
69
|
+
playhead: 0,
|
|
70
|
+
duration: 0,
|
|
71
|
+
host: 'mock.tenant.mock',
|
|
72
|
+
sessionId: mockId,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const result = await adapter.getItem({
|
|
76
|
+
tableName: 'test_table_1',
|
|
77
|
+
sessionId: mockId,
|
|
78
|
+
timestamp: 0,
|
|
79
|
+
});
|
|
80
|
+
expect(result).toBeDefined();
|
|
81
|
+
expect(result.sessionId).toEqual(mockId);
|
|
82
|
+
expect(result.host).toEqual('mock.tenant.mock');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should delete item in database', async () => {
|
|
86
|
+
const mockId = '123-123-123-123';
|
|
87
|
+
await adapter.putItem({
|
|
88
|
+
tableName: 'test_table_1',
|
|
89
|
+
data: {
|
|
90
|
+
event: 'loading',
|
|
91
|
+
timestamp: 0,
|
|
92
|
+
playhead: 0,
|
|
93
|
+
duration: 0,
|
|
94
|
+
host: 'mock.tenant.mock',
|
|
95
|
+
sessionId: mockId,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const result = await adapter.deleteItem({
|
|
99
|
+
tableName: 'test_table_1',
|
|
100
|
+
sessionId: mockId,
|
|
101
|
+
timestamp: 0,
|
|
102
|
+
});
|
|
103
|
+
expect(result).toBeTrue();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should get items from db with a specific sessionId and convert them to valid event objects', async () => {
|
|
107
|
+
const mockItems = [
|
|
108
|
+
{
|
|
109
|
+
event: 'playing',
|
|
110
|
+
sessionId: '123-214-234',
|
|
111
|
+
timestamp: 1640191099,
|
|
112
|
+
playhead: 1,
|
|
113
|
+
duration: 0,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
event: 'playing',
|
|
117
|
+
sessionId: '123-214-234',
|
|
118
|
+
timestamp: 1640193099,
|
|
119
|
+
playhead: 3,
|
|
120
|
+
duration: 0,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
event: 'paused',
|
|
124
|
+
sessionId: '123-214-234',
|
|
125
|
+
timestamp: 1640192099,
|
|
126
|
+
playhead: 2,
|
|
127
|
+
duration: 0,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
// for each mock items, put them to db
|
|
131
|
+
for (const mockItem of mockItems) {
|
|
132
|
+
await adapter.putItem({
|
|
133
|
+
tableName: 'test_table_1',
|
|
134
|
+
data: mockItem,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const result = await adapter.getItemsBySession({
|
|
138
|
+
tableName: 'test_table_1',
|
|
139
|
+
sessionId: '123-214-234',
|
|
140
|
+
});
|
|
141
|
+
// remove the mongodb id to be able to compare by equality
|
|
142
|
+
const cleanedResult = result.map((item) => {
|
|
143
|
+
delete item._id;
|
|
144
|
+
return item;
|
|
145
|
+
});
|
|
146
|
+
expect(cleanedResult).toEqual([
|
|
147
|
+
{
|
|
148
|
+
event: 'playing',
|
|
149
|
+
sessionId: '123-214-234',
|
|
150
|
+
timestamp: 1640191099,
|
|
151
|
+
duration: 0,
|
|
152
|
+
playhead: 1,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
event: 'playing',
|
|
156
|
+
sessionId: '123-214-234',
|
|
157
|
+
timestamp: 1640193099,
|
|
158
|
+
duration: 0,
|
|
159
|
+
playhead: 3,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
event: 'paused',
|
|
163
|
+
sessionId: '123-214-234',
|
|
164
|
+
timestamp: 1640192099,
|
|
165
|
+
duration: 0,
|
|
166
|
+
playhead: 2,
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeleteMessageCommand,
|
|
3
|
+
Message,
|
|
4
|
+
ReceiveMessageCommand,
|
|
5
|
+
SendMessageCommand,
|
|
6
|
+
SQSClient,
|
|
7
|
+
} from '@aws-sdk/client-sqs';
|
|
8
|
+
import { AwsError, mockClient } from 'aws-sdk-client-mock';
|
|
9
|
+
import { SqsQueueAdapter } from '../../adapters/queue/SqsQueueAdapter';
|
|
10
|
+
import Logger from '../../util/logger';
|
|
11
|
+
|
|
12
|
+
const sqsMock = mockClient(SQSClient);
|
|
13
|
+
|
|
14
|
+
describe('SQS Queue Adapter', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
process.env.AWS_REGION = 'us-east-1';
|
|
17
|
+
process.env.QUEUE_TYPE = 'SQS';
|
|
18
|
+
process.env.SQS_QUEUE_URL =
|
|
19
|
+
'https://sqs.us-east-1.amazonaws.com/1234/test-queue';
|
|
20
|
+
sqsMock.reset();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
delete process.env.AWS_REGION;
|
|
25
|
+
delete process.env.QUEUE_TYPE;
|
|
26
|
+
delete process.env.QUEUE_REGION;
|
|
27
|
+
delete process.env.SQS_QUEUE_URL;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should push to queue if default env is set', async () => {
|
|
31
|
+
const sqsResp = { MessageId: '12345678-4444-5555-6666-111122223333' };
|
|
32
|
+
const adapter = new SqsQueueAdapter(Logger);
|
|
33
|
+
const event = {
|
|
34
|
+
event: 'loading',
|
|
35
|
+
timestamp: 0,
|
|
36
|
+
playhead: 0,
|
|
37
|
+
duration: 0,
|
|
38
|
+
};
|
|
39
|
+
sqsMock.on(SendMessageCommand).resolves(sqsResp);
|
|
40
|
+
const result = await adapter.pushToQueue(event);
|
|
41
|
+
expect(result).toEqual(sqsResp);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should push to queue if QUEUE_REGION env is set and AWS_REGION is undefined', async () => {
|
|
45
|
+
process.env.QUEUE_REGION = 'eu-north-1';
|
|
46
|
+
process.env.AWS_REGION = undefined;
|
|
47
|
+
const sqsResp = { MessageId: '12345678-4444-5555-6666-111122223333' };
|
|
48
|
+
const adapter = new SqsQueueAdapter(Logger);
|
|
49
|
+
const event = {
|
|
50
|
+
event: 'loading',
|
|
51
|
+
timestamp: 0,
|
|
52
|
+
playhead: 0,
|
|
53
|
+
duration: 0,
|
|
54
|
+
};
|
|
55
|
+
sqsMock.on(SendMessageCommand).resolves(sqsResp);
|
|
56
|
+
const result = await adapter.pushToQueue(event);
|
|
57
|
+
expect(result).toEqual(sqsResp);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should not push to queue if sqs queue env is not set', async () => {
|
|
61
|
+
process.env.SQS_QUEUE_URL = undefined;
|
|
62
|
+
const queueAdapter = new SqsQueueAdapter(Logger);
|
|
63
|
+
const mockEvent = {
|
|
64
|
+
event: 'loading',
|
|
65
|
+
timestamp: 0,
|
|
66
|
+
playhead: 0,
|
|
67
|
+
duration: 0,
|
|
68
|
+
};
|
|
69
|
+
let result = await queueAdapter.pushToQueue(mockEvent);
|
|
70
|
+
expect(result).toEqual({ message: 'SQS_QUEUE_URL is undefined' });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should not read from queue if sqs queue env is not set', async () => {
|
|
74
|
+
process.env.SQS_QUEUE_URL = undefined;
|
|
75
|
+
const queueAdapter = new SqsQueueAdapter(Logger);
|
|
76
|
+
let result = await queueAdapter.pullFromQueue();
|
|
77
|
+
expect(result).toEqual({ message: 'SQS_QUEUE_URL is undefined' });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should not remove from queue if sqs queue env is not set', async () => {
|
|
81
|
+
process.env.SQS_QUEUE_URL = undefined;
|
|
82
|
+
const queueAdapter = new SqsQueueAdapter(Logger);
|
|
83
|
+
const mockSQSMessage: Message = {
|
|
84
|
+
MessageId: '62686810-05ba-4b43-62730ff3156g7jd3',
|
|
85
|
+
ReceiptHandle:
|
|
86
|
+
'MbZj6wDWli+JvwwJaBV+3dcjk2YW2vA3' +
|
|
87
|
+
'+STFFljTM8tJJg6HRG6PYSasuWXPJB+C' +
|
|
88
|
+
'wLj1FjgXUv1uSj1gUPAWV66FU/WeR4mq' +
|
|
89
|
+
'2OKpEGYWbnLmpRCJVAyeMjeU5ZBdtcQ+' +
|
|
90
|
+
'QEauMZc8ZRv37sIW2iJKq3M9MFx1YvV11A2x/KSbkJ0=',
|
|
91
|
+
MD5OfBody: 'fafb00f5732ab283681e124bf8747ed1',
|
|
92
|
+
Body: JSON.stringify({
|
|
93
|
+
event: 'loading',
|
|
94
|
+
timestamp: 0,
|
|
95
|
+
playhead: 0,
|
|
96
|
+
duration: 0,
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
let result = await queueAdapter.removeFromQueue(mockSQSMessage);
|
|
100
|
+
expect(result).toEqual({ message: 'SQS_QUEUE_URL is undefined' });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should catch error when sending message fails', async () => {
|
|
104
|
+
const errMsg: AwsError = {
|
|
105
|
+
Type: 'Sender',
|
|
106
|
+
Code: 'AWS.SimpleQueueService.NonExistentQueue',
|
|
107
|
+
name: 'AWS.SimpleQueueService.NonExistentQueue',
|
|
108
|
+
$fault: 'client',
|
|
109
|
+
$metadata: {
|
|
110
|
+
httpStatusCode: 400,
|
|
111
|
+
requestId: 'df840ab9-e68b-5c0e-b4a0-5094f2dfaee8',
|
|
112
|
+
attempts: 1,
|
|
113
|
+
totalRetryDelay: 0,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const mockSQSMessage: Message = {
|
|
117
|
+
MessageId: '62686810-05ba-4b43-62730ff3156g7jd3',
|
|
118
|
+
ReceiptHandle:
|
|
119
|
+
'MbZj6wDWli+JvwwJaBV+3dcjk2YW2vA3' +
|
|
120
|
+
'+STFFljTM8tJJg6HRG6PYSasuWXPJB+C' +
|
|
121
|
+
'wLj1FjgXUv1uSj1gUPAWV66FU/WeR4mq' +
|
|
122
|
+
'2OKpEGYWbnLmpRCJVAyeMjeU5ZBdtcQ+' +
|
|
123
|
+
'QEauMZc8ZRv37sIW2iJKq3M9MFx1YvV11A2x/KSbkJ0=',
|
|
124
|
+
MD5OfBody: 'fafb00f5732ab283681e124bf8747ed1',
|
|
125
|
+
Body: JSON.stringify({
|
|
126
|
+
event: 'loading',
|
|
127
|
+
timestamp: 0,
|
|
128
|
+
playhead: 0,
|
|
129
|
+
duration: 0,
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
const mockEvent = {
|
|
133
|
+
event: 'loading',
|
|
134
|
+
timestamp: 0,
|
|
135
|
+
playhead: 0,
|
|
136
|
+
duration: 0,
|
|
137
|
+
};
|
|
138
|
+
sqsMock.on(SendMessageCommand).rejects(errMsg);
|
|
139
|
+
sqsMock.on(ReceiveMessageCommand).rejects(errMsg);
|
|
140
|
+
sqsMock.on(DeleteMessageCommand).rejects(errMsg);
|
|
141
|
+
const queueAdapter = new SqsQueueAdapter(Logger);
|
|
142
|
+
let pushResult = await queueAdapter.pushToQueue(mockEvent);
|
|
143
|
+
let readResult = await queueAdapter.pullFromQueue();
|
|
144
|
+
let removeResult = await queueAdapter.removeFromQueue(mockSQSMessage);
|
|
145
|
+
expect(pushResult.toString()).toEqual(errMsg.Code);
|
|
146
|
+
expect(readResult.toString()).toEqual(errMsg.Code);
|
|
147
|
+
expect(removeResult.toString()).toEqual(errMsg.Code);
|
|
148
|
+
});
|
|
149
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"target": "es6",
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"outDir": "build",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"strictNullChecks": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationDir": "./build"
|
|
13
|
+
},
|
|
14
|
+
"lib": ["es2015"],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"../node_modules"
|
|
17
|
+
],
|
|
18
|
+
"include": [
|
|
19
|
+
"./**/*.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|
package/tslint.json
ADDED
package/types/db.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
export enum ErrorType {
|
|
4
|
+
'ABORT' = 0,
|
|
5
|
+
'CONTINUE' = 1,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IHandleErrorOutput {
|
|
9
|
+
errorType: ErrorType;
|
|
10
|
+
error: Object;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IPutItemInput {
|
|
14
|
+
tableName: string;
|
|
15
|
+
data: Object;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IGetItems {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
tableName: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IGetItemInput {
|
|
24
|
+
tableName: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export abstract class AbstractDBAdapter {
|
|
30
|
+
logger: winston.Logger;
|
|
31
|
+
dbClient: any;
|
|
32
|
+
|
|
33
|
+
abstract tableExists(name: string): Promise<boolean>;
|
|
34
|
+
abstract putItem(params: IPutItemInput): Promise<boolean>;
|
|
35
|
+
abstract getItem(params: IGetItemInput): Promise<any>;
|
|
36
|
+
abstract deleteItem(params: IGetItemInput): Promise<boolean>;
|
|
37
|
+
abstract getItemsBySession(params: IGetItems): Promise<any[]>;
|
|
38
|
+
abstract handleError(error: any): IHandleErrorOutput;
|
|
39
|
+
}
|
package/types/queue.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
export interface EventValidator {
|
|
4
|
+
logger: winston.Logger;
|
|
5
|
+
eventSchema: any;
|
|
6
|
+
validateEvent(event: Object): any;
|
|
7
|
+
validateEventList(eventList: Array<Object>): any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export abstract class AbstractQueueAdapter {
|
|
11
|
+
logger: winston.Logger;
|
|
12
|
+
client: any;
|
|
13
|
+
abstract pushToQueue(body: Object): Promise<Object>;
|
|
14
|
+
abstract pullFromQueue(): Promise<Object>;
|
|
15
|
+
abstract removeFromQueue(body: Object): Promise<Object>;
|
|
16
|
+
abstract getEventJSONsFromMessages(body: any[]): Object[];
|
|
17
|
+
}
|