Skip to content

Commit 60451f5

Browse files
feat(Statements): Add flexibility for bypassing queue (LLC-1560) (#849)
1 parent 052bbce commit 60451f5

File tree

21 files changed

+313
-17
lines changed

21 files changed

+313
-17
lines changed

docker-compose.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@ version: '3.6'
22
services:
33
mongo:
44
container_name: ll_mongo
5-
image: mongo:4.0
5+
image: mongo:4.4
6+
command: --bind_ip_all --replSet rs0
67
ports:
7-
- 27017:27017
8+
- 27017:27017
89
volumes:
9-
- mongo-config:/data/configdb:rw
10-
- mongo-data:/data/db:rw
10+
- mongo-config:/data/configdb:rw
11+
- mongo-data:/data/db:rw
1112
redis:
1213
# activate persistency
1314
command: redis-server --appendonly yes
1415
container_name: ll_redis
1516
image: redis:4.0
1617
ports:
17-
- 6379:6379
18+
- 6379:6379
1819
volumes:
19-
- redis-data:/data:rw
20+
- redis-data:/data:rw
2021
volumes:
2122
mongo-config: {}
2223
mongo-data: {}
23-
redis-data: {}
24+
redis-data: {}

src/apps/statements/expressPresenter/postStatements/alternateRequest.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import getUrlPath from '../utils/getUrlPath';
1515
import storeStatement from '../utils/storeStatement';
1616
import validateVersionHeader from '../utils/validateHeaderVersion';
1717
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
18+
import { validateStatementBypassQueues } from '../utils/validateStatementBypassQueues';
1819
import storeStatements from './storeStatements';
1920

2021
export interface Options {
@@ -52,9 +53,13 @@ export default async ({ config, method, req, res }: Options) => {
5253
checkUnknownParams(req.query, ['method']);
5354

5455
validateStatementProcessingPriority(req.query.priority as string | undefined);
56+
validateStatementBypassQueues(req.query.bypassQueues as string | undefined);
5557
const priority =
5658
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
57-
59+
const bypassQueues =
60+
req.query.bypassQueues && (req.query.bypassQueues as string).trim() !== ''
61+
? (req.query.bypassQueues as string).split(',')
62+
: [];
5863
if (method === 'POST' || (method === undefined && config.allowUndefinedMethod)) {
5964
const bodyParams = await getBodyParams(req);
6065

@@ -68,7 +73,7 @@ export default async ({ config, method, req, res }: Options) => {
6873

6974
const body = getBodyContent(bodyParams);
7075

71-
return storeStatements({ config, client, priority, body, attachments: [], res });
76+
return storeStatements({ config, client, priority, bypassQueues, body, attachments: [], res });
7277
}
7378

7479
if (method === 'GET') {
@@ -100,7 +105,16 @@ export default async ({ config, method, req, res }: Options) => {
100105
const body = getBodyContent(bodyParams);
101106
const statementId = bodyParams.statementId as string | undefined;
102107

103-
return storeStatement({ config, client, body, priority, attachments: [], statementId, res });
108+
return storeStatement({
109+
config,
110+
client,
111+
body,
112+
priority,
113+
bypassQueues,
114+
attachments: [],
115+
statementId,
116+
res,
117+
});
104118
}
105119

106120
throw new InvalidMethod(method);

src/apps/statements/expressPresenter/postStatements/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import getClient from '../utils/getClient';
1616
import validateVersionHeader from '../utils/validateHeaderVersion';
1717
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
18+
import { validateStatementBypassQueues } from '../utils/validateStatementBypassQueues';
1819
import alternateRequest from './alternateRequest';
1920
import storeStatements from './storeStatements';
2021
import storeWithAttachments from './storeWithAttachments';
@@ -38,9 +39,14 @@ export default (config: Config) => {
3839
const method = req.query.method as string | undefined;
3940

4041
validateStatementProcessingPriority(req.query.priority as string | undefined);
42+
validateStatementBypassQueues(req.query.bypassQueues as string | undefined);
4143

4244
const priority =
4345
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
46+
const bypassQueues =
47+
req.query.bypassQueues && (req.query.bypassQueues as string).trim() !== ''
48+
? (req.query.bypassQueues as string).split(',')
49+
: [];
4450
const contentType = defaultTo(req.header('Content-Type'), '');
4551

4652
if (method === undefined && multipartContentTypePattern.test(contentType)) {
@@ -53,7 +59,7 @@ export default (config: Config) => {
5359

5460
const body = await parseJsonBody(config, req);
5561
const attachments: any[] = [];
56-
return storeStatements({ config, client, priority, body, attachments, res });
62+
return storeStatements({ config, client, priority, bypassQueues, body, attachments, res });
5763
}
5864

5965
if (method !== undefined || alternateContentTypePattern.test(contentType)) {

src/apps/statements/expressPresenter/postStatements/storeStatements.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,29 @@ export interface Options {
1010
readonly config: Config;
1111
readonly client: ClientModel;
1212
readonly priority: StatementProcessingPriority;
13+
readonly bypassQueues: string[];
1314
readonly body: any;
1415
readonly attachments: any[];
1516
readonly res: Response;
1617
}
1718

18-
export default async ({ config, client, priority, body, attachments, res }: Options) => {
19+
export default async ({
20+
config,
21+
client,
22+
priority,
23+
bypassQueues,
24+
body,
25+
attachments,
26+
res,
27+
}: Options) => {
1928
const models = isArray(body) ? body : [body];
20-
const ids = await config.service.storeStatements({ priority, models, attachments, client });
29+
const ids = await config.service.storeStatements({
30+
priority,
31+
bypassQueues,
32+
models,
33+
attachments,
34+
client,
35+
});
2136
res.setHeader('X-Experience-API-Version', xapiHeaderVersion);
2237
res.status(StatusCodes.OK);
2338
res.json(ids);

src/apps/statements/expressPresenter/postStatements/storeWithAttachments.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Config from '../Config';
55
import getClient from '../utils/getClient';
66
import getMultipartStatements from '../utils/getMultipartStatements';
77
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
8+
import { validateStatementBypassQueues } from '../utils/validateStatementBypassQueues';
89
import storeStatements from './storeStatements';
910

1011
export interface Options {
@@ -17,10 +18,15 @@ export default async ({ config, req, res }: Options) => {
1718
const client = await getClient(config, defaultTo(req.header('Authorization'), ''));
1819

1920
validateStatementProcessingPriority(req.query.priority as string | undefined);
21+
validateStatementBypassQueues(req.query.bypassQueues as string | undefined);
2022

2123
const priority =
2224
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
25+
const bypassQueues =
26+
req.query.bypassQueues && (req.query.bypassQueues as string).trim() !== ''
27+
? (req.query.bypassQueues as string).split(',')
28+
: [];
2329
const { body, attachments } = await getMultipartStatements(req);
2430

25-
return storeStatements({ config, client, priority, body, attachments, res });
31+
return storeStatements({ config, client, priority, bypassQueues, body, attachments, res });
2632
};

src/apps/statements/expressPresenter/putStatement.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,53 @@ import getMultipartStatements from './utils/getMultipartStatements';
1313
import storeStatement from './utils/storeStatement';
1414
import validateVersionHeader from './utils/validateHeaderVersion';
1515
import { validateStatementProcessingPriority } from './utils/validateStatementProcessingPriority';
16+
import { validateStatementBypassQueues } from './utils/validateStatementBypassQueues';
1617

1718
export default (config: Config) => {
1819
return catchErrors(
1920
config,
2021
async (req: Request, res: Response): Promise<void> => {
2122
validateStatementProcessingPriority(req.query.priority as string | undefined);
23+
validateStatementBypassQueues(req.query.bypassQueues as string | undefined);
2224
validateVersionHeader(req.header('X-Experience-API-Version'));
2325

2426
const contentType = defaultTo(req.header('Content-Type'), '');
2527
const client = await getClient(config, defaultTo(req.header('Authorization'), ''));
2628
const priority =
2729
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
30+
const bypassQueues =
31+
req.query.bypassQueues && (req.query.bypassQueues as string).trim() !== ''
32+
? (req.query.bypassQueues as string).split(',')
33+
: [];
2834
const statementId = req.query.statementId as string;
2935

3036
if (multipartContentTypePattern.test(contentType)) {
3137
const { body, attachments } = await getMultipartStatements(req);
32-
return storeStatement({ config, priority, body, attachments, client, statementId, res });
38+
return storeStatement({
39+
config,
40+
priority,
41+
bypassQueues,
42+
body,
43+
attachments,
44+
client,
45+
statementId,
46+
res,
47+
});
3348
}
3449

3550
if (jsonContentTypePattern.test(contentType)) {
3651
const body = parseJson(await streamToString(req), ['body']);
3752
const attachments: AttachmentModel[] = [];
38-
return storeStatement({ config, priority, body, attachments, client, statementId, res });
53+
return storeStatement({
54+
config,
55+
priority,
56+
bypassQueues,
57+
body,
58+
attachments,
59+
client,
60+
statementId,
61+
res,
62+
});
3963
}
4064

4165
throw new InvalidContentType(contentType);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as assert from 'assert';
2+
import { StatusCodes } from 'http-status-codes';
3+
import { xapiHeaderVersion } from '../../../../activities/utils/constants';
4+
import createClientModel from '../../../tests/utils/createClientModel';
5+
import createStatement from '../../../tests/utils/createStatement';
6+
import { jsonContentType, statementsRoute } from '../../../utils/constants';
7+
import setup from '../../tests/utils/setup';
8+
import repo from '../../../repo';
9+
10+
describe('postStatements', () => {
11+
const { supertest } = setup();
12+
13+
it('should throw error for incorrect bypassQueues query param', async () => {
14+
const TEST_ID = '1c86d8e9-f325-404f-b3d9-24c451035586';
15+
16+
await supertest
17+
.post(statementsRoute)
18+
.set('Content-Type', jsonContentType)
19+
.set('X-Experience-API-Version', xapiHeaderVersion)
20+
.query({
21+
bypassQueues: 'abc',
22+
statementId: TEST_ID,
23+
})
24+
.send([createStatement()])
25+
.expect((response) => {
26+
assert.equal(response.status, StatusCodes.BAD_REQUEST);
27+
assert.deepEqual(response.body.warnings, [
28+
`Problem in 'query.bypassQueues'. Received '"abc"'`,
29+
]);
30+
});
31+
});
32+
33+
it('should insert statement with proper bypassQueues query param', async () => {
34+
const TEST_ID = '1c86d8e9-f325-404f-b3d9-24c451035587';
35+
const TEST_CLIENT = createClientModel();
36+
const expectedCompletedQueues = ['STATEMENT_QUEUE_1', 'STATEMENT_QUEUE_2'];
37+
38+
await supertest
39+
.post(statementsRoute)
40+
.set('Content-Type', jsonContentType)
41+
.set('X-Experience-API-Version', xapiHeaderVersion)
42+
.query({
43+
bypassQueues: expectedCompletedQueues.join(','),
44+
statementId: TEST_ID,
45+
})
46+
.send([createStatement()])
47+
.expect(async (response) => {
48+
assert.equal(response.status, StatusCodes.NO_CONTENT);
49+
50+
const fullStatement = await repo.getStatement({ id: TEST_ID, client: TEST_CLIENT });
51+
52+
assert.deepEqual(fullStatement.completedQueues, expectedCompletedQueues);
53+
});
54+
});
55+
56+
it('should insert statement with proper bypassQueues query param (using alternate request)', async () => {
57+
const TEST_ID = '1c86d8e9-f325-404f-b3d9-24c451035588';
58+
const TEST_CLIENT = createClientModel();
59+
const expectedCompletedQueues = ['STATEMENT_QUEUE_1', 'STATEMENT_QUEUE_2'];
60+
61+
await supertest
62+
.post(statementsRoute)
63+
.set('Content-Type', jsonContentType)
64+
.set('X-Experience-API-Version', xapiHeaderVersion)
65+
.query({
66+
bypassQueues: expectedCompletedQueues.join(','),
67+
statementId: TEST_ID,
68+
method: 'PUT',
69+
})
70+
.send([createStatement()])
71+
.expect(async (response) => {
72+
assert.equal(response.status, StatusCodes.NO_CONTENT);
73+
74+
const fullStatement = await repo.getStatement({ id: TEST_ID, client: TEST_CLIENT });
75+
76+
assert.deepEqual(fullStatement.completedQueues, expectedCompletedQueues);
77+
});
78+
});
79+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as assert from 'assert';
2+
import { StatusCodes } from 'http-status-codes';
3+
import { xapiHeaderVersion } from '../../../../activities/utils/constants';
4+
import createClientModel from '../../../tests/utils/createClientModel';
5+
import createStatement from '../../../tests/utils/createStatement';
6+
import { jsonContentType, statementsRoute } from '../../../utils/constants';
7+
import setup from '../../tests/utils/setup';
8+
import repo from '../../../repo';
9+
10+
describe('putStatement', () => {
11+
const { supertest } = setup();
12+
13+
it('should throw error for incorrect bypassQueues query param', async () => {
14+
const TEST_ID = '1c86d8e9-f325-404f-b3d9-24c451035586';
15+
16+
await supertest
17+
.put(statementsRoute)
18+
.set('Content-Type', jsonContentType)
19+
.set('X-Experience-API-Version', xapiHeaderVersion)
20+
.query({
21+
bypassQueues: 'abc',
22+
statementId: TEST_ID,
23+
})
24+
.send(createStatement())
25+
.expect((response) => {
26+
assert.equal(response.status, StatusCodes.BAD_REQUEST);
27+
assert.deepEqual(response.body.warnings, [
28+
`Problem in 'query.bypassQueues'. Received '"abc"'`,
29+
]);
30+
});
31+
});
32+
33+
it('should insert statement with proper bypassQueues query param', async () => {
34+
const TEST_ID = '1c86d8e9-f325-404f-b3d9-24c451035587';
35+
const TEST_CLIENT = createClientModel();
36+
const expectedCompletedQueues = ['STATEMENT_QUEUE_1', 'STATEMENT_QUEUE_2'];
37+
38+
await supertest
39+
.put(statementsRoute)
40+
.set('Content-Type', jsonContentType)
41+
.set('X-Experience-API-Version', xapiHeaderVersion)
42+
.query({
43+
bypassQueues: expectedCompletedQueues.join(','),
44+
statementId: TEST_ID,
45+
})
46+
.send(createStatement())
47+
.expect(async (response) => {
48+
assert.equal(response.status, StatusCodes.NO_CONTENT);
49+
50+
const fullStatement = await repo.getStatement({ id: TEST_ID, client: TEST_CLIENT });
51+
52+
assert.deepEqual(fullStatement.completedQueues, expectedCompletedQueues);
53+
});
54+
});
55+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import setupService from 'jscommons/dist/tests/utils/setupService';
2+
import { SuperTest, Test } from 'supertest';
3+
import Service from '../../../serviceFactory/Service';
4+
import service from '../../../utils/testService';
5+
import supertest from './supertest';
6+
7+
const setup = setupService(service);
8+
9+
export interface Result {
10+
readonly service: Service;
11+
readonly supertest: SuperTest<Test>;
12+
}
13+
14+
export default (): Result => {
15+
setup();
16+
return { service, supertest };
17+
};

0 commit comments

Comments
 (0)