Skip to content

Commit 3564311

Browse files
feat(decoders): moved route to decoder endpoint + add roles + add tests
1 parent 7901a95 commit 3564311

File tree

6 files changed

+160
-136
lines changed

6 files changed

+160
-136
lines changed

lib/modules/decoder/DecodersController.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ControllerDefinition, KuzzleRequest } from "kuzzle";
1+
import { BadRequestError, ControllerDefinition, KuzzleRequest } from "kuzzle";
22

33
import { DecodersRegister } from "./DecodersRegister";
44
import { PayloadService } from "./PayloadService";
@@ -32,6 +32,15 @@ export class DecodersController {
3232
{ path: "device-manager/decoders/_prunePayloads", verb: "delete" },
3333
],
3434
},
35+
route: {
36+
handler: this.route.bind(this),
37+
http: [
38+
{
39+
path: "device-manager/decoders/route",
40+
verb: "post",
41+
},
42+
],
43+
},
3544
},
3645
};
3746
}
@@ -61,4 +70,32 @@ export class DecodersController {
6170

6271
return { deleted };
6372
}
73+
74+
async route(request: KuzzleRequest): Promise<{ valid: boolean }> {
75+
const payload = request.getBody();
76+
const model =
77+
payload.deviceModel ?? request.getString("deviceModel", "unknownModel");
78+
const apiAction = `${request.input.controller}:${request.input.action}`;
79+
if (model === "unknownModel") {
80+
app.log.warn(
81+
"Received payload without a device model: routing to receiveUnknown",
82+
);
83+
this.payloadService.receiveUnknown(model, payload, apiAction);
84+
throw new BadRequestError(
85+
"Payload must specify the deviceModel for proper routing",
86+
);
87+
}
88+
const decoder = this.decodersRegister.decoders.find(
89+
(d) => d.deviceModel === model,
90+
);
91+
if (!decoder) {
92+
app.log.warn(
93+
`Received payload from ${model} model, no associated decoder found: routing to receiveUnknown`,
94+
);
95+
this.payloadService.receiveUnknown(model, payload, apiAction);
96+
throw new BadRequestError("The specified device model is unknown");
97+
}
98+
app.log.debug("Routing payload to decoder for " + decoder.deviceModel);
99+
return this.payloadService.receive(request, decoder);
100+
}
64101
}

lib/modules/decoder/DecodersRegister.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ export class DecodersRegister {
128128
private registerDefaultRole() {
129129
const role = {
130130
controllers: {
131+
"device-manager/decoders": {
132+
actions: {
133+
route: true,
134+
},
135+
},
131136
"device-manager/payloads": {
132137
actions: {
133138
"*": true,

lib/modules/decoder/PayloadsController.ts

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BadRequestError, ControllerDefinition, KuzzleRequest } from "kuzzle";
1+
import { ControllerDefinition, KuzzleRequest } from "kuzzle";
22

33
import { DecodersRegister } from "./DecodersRegister";
44
import { PayloadService } from "./PayloadService";
@@ -49,28 +49,6 @@ export class PayloadsController {
4949
},
5050
],
5151
},
52-
route: {
53-
handler: this.route.bind(this),
54-
http: [
55-
{
56-
openapi: {
57-
description: `Receive a payload from a device and reroute it to the corresponding endpoint`,
58-
parameters: [
59-
{
60-
in: "body",
61-
name: "payload",
62-
required: true,
63-
schema: {
64-
type: "object",
65-
},
66-
},
67-
],
68-
},
69-
path: "device-manager/payload/route",
70-
verb: "post",
71-
},
72-
],
73-
},
7452
},
7553
};
7654

@@ -92,22 +70,4 @@ export class PayloadsController {
9270

9371
await this.payloadService.receiveUnknown(deviceModel, payload, apiAction);
9472
}
95-
96-
async route(request: KuzzleRequest): Promise<{ valid: boolean }> {
97-
const payload = request.getBody();
98-
const model = payload.deviceModel;
99-
if (!model) {
100-
throw new BadRequestError(
101-
"Payload must specify the deviceModel for proper routing",
102-
);
103-
}
104-
const decoder = this.decodersRegister.decoders.find(
105-
(d) => d.deviceModel === model,
106-
);
107-
if (!decoder) {
108-
throw new BadRequestError("The specified device model is unknown");
109-
}
110-
app.log.debug("Routing payload to decoder for " + decoder.deviceModel);
111-
return this.payloadService.receive(request, decoder);
112-
}
11373
}

lib/modules/decoder/roles/RolePayloadsAll.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export const RolePayloadsAll: KuzzleRole = {
1919
"*": true,
2020
},
2121
},
22+
"device-manager/decoders": {
23+
actions: {
24+
route: true,
25+
},
26+
},
2227
},
2328
},
2429
};

tests/scenario/migrated/decoder-payload-controller.test.ts

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -294,97 +294,4 @@ describe('features/Decoder/PayloadController', () => {
294294
_source: { measures: {} },
295295
});
296296
});
297-
298-
it('Receive a payload from unknown device', async () => {
299-
let response;
300-
let promise;
301-
302-
response = await sdk.query({
303-
controller: 'device-manager/payloads',
304-
action: 'receiveUnknown',
305-
deviceModel: 'Abeeway',
306-
body: { deviceEUI: 'JORA' },
307-
});
308-
309-
await sdk.collection.refresh('device-manager', 'payloads');
310-
311-
await expect(
312-
sdk.document.search(
313-
'device-manager',
314-
'payloads',
315-
{
316-
query: {},
317-
sort: { '_kuzzle_info.createdAt': 'desc' },
318-
},
319-
{
320-
size: 1,
321-
},
322-
),
323-
).resolves.toMatchObject({
324-
hits: {
325-
'0': {
326-
_source: {
327-
deviceModel: 'Abeeway',
328-
valid: false,
329-
payload: { deviceEUI: 'JORA' },
330-
},
331-
},
332-
},
333-
});
334-
});
335-
336-
it('Reroute a payload to the corresponding handler', async () => {
337-
let response;
338-
let promise;
339-
340-
await sendPayloads(sdk, 'route', [
341-
{
342-
deviceEUI: 'linked2',
343-
deviceModel:'DummyTempPosition',
344-
temperature: 45,
345-
location: { lat: 12, lon: 12, accuracy: 2100 },
346-
battery: 0.1,
347-
},
348-
]);
349-
350-
await expect(
351-
sdk.document.get('device-manager', 'devices', 'DummyTempPosition-linked2'),
352-
).resolves.toMatchObject({
353-
_source: {
354-
reference: 'linked2',
355-
model: 'DummyTempPosition',
356-
measures: {
357-
temperature: { type: 'temperature', values: { temperature: 45 } },
358-
position: {
359-
type: 'position',
360-
values: { position: { lat: 12, lon: 12 }, accuracy: 2100 },
361-
},
362-
battery: { type: 'battery', values: { battery: 10 } },
363-
},
364-
engineId: 'engine-ayse',
365-
assetId: 'Container-linked2',
366-
},
367-
});
368-
});
369-
370-
it('Reroute and reject with error a DummyTemp payload', async () => {
371-
let response;
372-
let promise;
373-
374-
promise = sendPayloads(sdk, 'route', [{ deviceEUI: null, deviceModel:'DummyTemp', temperature: 21 }]);
375-
376-
await expect(promise).rejects.toMatchObject({
377-
message: 'Invalid payload: missing "deviceEUI"',
378-
});
379-
});
380-
it('Reroute and reject if deviceModel missing', async () => {
381-
let response;
382-
let promise;
383-
384-
promise = sendPayloads(sdk, 'route', [{ deviceEUI: 'linked1', temperature: 21 }]);
385-
386-
await expect(promise).rejects.toMatchObject({
387-
message: 'Payload must specify the deviceModel for proper routing',
388-
});
389-
});
390297
});

tests/scenario/modules/decoder/Decoders.test.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { beforeEachTruncateCollections } from "../../../hooks";
1+
import {
2+
beforeEachLoadFixtures,
3+
beforeEachTruncateCollections,
4+
} from "../../../hooks";
25
import { ApiDecoderListRequest, ApiDecoderListResult } from "../../../../index";
36

47
import { useSdk, sendPayloads } from "../../../helpers";
@@ -123,4 +126,111 @@ describe("DecodersController", () => {
123126
]),
124127
);
125128
});
129+
it("Reroute a payload to the corresponding handler", async () => {
130+
await beforeEachLoadFixtures(sdk);
131+
await sdk.query({
132+
controller: "device-manager/decoders",
133+
action: "route",
134+
body: {
135+
deviceEUI: "linked2",
136+
deviceModel: "DummyTempPosition",
137+
temperature: 45,
138+
location: { lat: 12, lon: 12, accuracy: 2100 },
139+
battery: 0.1,
140+
},
141+
});
142+
143+
await expect(
144+
sdk.document.get(
145+
"device-manager",
146+
"devices",
147+
"DummyTempPosition-linked2",
148+
),
149+
).resolves.toMatchObject({
150+
_source: {
151+
reference: "linked2",
152+
model: "DummyTempPosition",
153+
measures: {
154+
temperature: { type: "temperature", values: { temperature: 45 } },
155+
position: {
156+
type: "position",
157+
values: { position: { lat: 12, lon: 12 }, accuracy: 2100 },
158+
},
159+
battery: { type: "battery", values: { battery: 10 } },
160+
},
161+
engineId: "engine-ayse",
162+
assetId: "Container-linked2",
163+
},
164+
});
165+
});
166+
167+
it("Reroute and reject with error a DummyTemp payload", async () => {
168+
const promise = sdk.query({
169+
controller: "device-manager/decoders",
170+
action: "route",
171+
body: { deviceEUI: null, deviceModel: "DummyTemp", temperature: 21 },
172+
});
173+
await expect(promise).rejects.toMatchObject({
174+
message: 'Invalid payload: missing "deviceEUI"',
175+
});
176+
});
177+
it("Reroute and reject if deviceModel missing", async () => {
178+
const noModelPayload = { deviceEUI: "linked1", temperature: 21 };
179+
const promise = sdk.query({
180+
controller: "device-manager/decoders",
181+
action: "route",
182+
body: noModelPayload,
183+
});
184+
185+
await expect(promise).rejects.toMatchObject({
186+
message: "Payload must specify the deviceModel for proper routing",
187+
});
188+
await sdk.collection.refresh("device-manager", "payloads");
189+
const payloadReceived = await sdk.query({
190+
controller: "document",
191+
action: "search",
192+
index: "device-manager",
193+
collection: "payloads",
194+
sort: {
195+
"_kuzzle_info.createdAt": "DESC",
196+
},
197+
size: 1,
198+
});
199+
expect(payloadReceived.result.hits[0]._source.payload).toMatchObject(
200+
noModelPayload,
201+
);
202+
});
203+
it("Reroute and reject if decoder missing", async () => {
204+
const noDecoderPayload = {
205+
deviceEUI: "linked1",
206+
deviceModel: "unknownDecoder",
207+
temperature: 21,
208+
};
209+
const promise = sdk.query({
210+
controller: "device-manager/decoders",
211+
action: "route",
212+
body: noDecoderPayload,
213+
});
214+
215+
await expect(promise).rejects.toMatchObject({
216+
message: "The specified device model is unknown",
217+
});
218+
await sdk.collection.refresh("device-manager", "payloads");
219+
const payloadReceived = await sdk.query({
220+
controller: "document",
221+
action: "search",
222+
index: "device-manager",
223+
collection: "payloads",
224+
sort: {
225+
"_kuzzle_info.createdAt": "DESC",
226+
},
227+
size: 1,
228+
});
229+
expect(payloadReceived.result.hits[0]._source.deviceModel).toBe(
230+
noDecoderPayload.deviceModel,
231+
);
232+
expect(payloadReceived.result.hits[0]._source.payload).toMatchObject(
233+
noDecoderPayload,
234+
);
235+
});
126236
});

0 commit comments

Comments
 (0)