Skip to content
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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"git.enabled": false
"git.enabled": true
}
38 changes: 24 additions & 14 deletions src/domain/sensors-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,30 @@ class SensorsEventService {
notificationCategory = 'default';
}

try {
await axios.post(
`http://localhost/notification/${notificationCategory}`,
{
title: 'Something critical happened',
id,
},
);
eventToHandle.notificationSent = true;
} catch (error) {
eventToHandle.notificationSent = false;
console.log(
`Don't want to stop because of this notification error ${error}`,
);
// Dror's retry mechanism
const callme = async () => {
try {
await axios.post(
`http://localhost/notification/${notificationCategory}`,
{
title: 'Something critical happened',
id,
},
{
timeout: 30000
}
);
return true;
} catch (err) {
return false;
}
};

eventToHandle.notificationSent = false;
for (let i = 0; i < 3; i++) {
if (await callme()) {
eventToHandle.notificationSent = true;
}
}
}

Expand Down
52 changes: 36 additions & 16 deletions test/mission-basic-response-bay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const {
} = require('../src/entry-points/sensors-api');
const { getShortUnique, getSensorEvent } = require('./test-helper');
const sinon = require('sinon');
const SensorsService = require('../src/domain/sensors-service');

let expressApp;

Expand Down Expand Up @@ -52,55 +53,74 @@ describe('Sensors test', () => {
weight: 80,
status: 'active',
category: 'Kids-Room',
category: undefined
// 💡 TIP: Consider explicitly specify that category is undefined by assigning 'undefined'
};

// Act

// 💡 TIP: use any http client lib like Axios OR supertest
// 💡 TIP: This is how it is done with Supertest -> await request(expressApp).post("/sensor-events").send(eventToAdd);
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(eventToAdd);

// Assert

// 💡 TIP: Check that the received response is indeed as stated in the test name
// 💡 TIP: Use this syntax for example: expect(receivedResponse.status).toBe(...);
expect(receivedResponse.status).toBe(400)
});

// ✅ TASK: Test that when a new valid event is posted to /sensor-events route, we get back a valid response
// 💡 TIP: Consider checking both the HTTP status and the body
test('When inserting a valid event, should get successful response', async () => {
// Arrange
const validEvent = getSensorEvent();

// Act
// 💡 TIP: use any http client lib like Axios OR supertest
// 💡 TIP: This is how it is done with Supertest -> await request(expressApp).post("/sensor-events").send(eventToAdd);
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

// Assert
// 💡 TIP: You may check the body and the status all together with the following syntax:
// expect(receivedResponse).toMatchObject({status: 200, body: {...}});
expect(receivedResponse).toMatchObject({status: 200, body: {...validEvent}});
});

// ✅ TASK: Test that when a new valid event is posted to /sensor-events route, it's indeed retrievable from the DB
// 💡 TIP: In the assert phase, query to get the event that was added
// 💡 TIP: Whenever possible, use the public API for verification (not direct DB access)
test('When inserting a valid event, should be able to retrieve it', async () => {
// Arrange
const validEvent = getSensorEvent();

// ✅ Keep the tests very short and readable, strive not to pass 7 statements per test
// 💡 TIP: If it gets too long, extract obvious parts into an external helper
// Act
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

const retrieveResponse = await request(expressApp)
.get('/sensor-events/' + receivedResponse.body.id)

// Assert
expect(receivedResponse).toMatchObject({status: 200, body: {...validEvent}});
expect(receivedResponse.body).toMatchObject(retrieveResponse.body)
});

// ✅🚀 TASK: Code the following test below
test('When an internal unknown error occurs during request, Then get back 500 error', async () => {
// Arrange
const validEvent = getSensorEvent();
// 💡 TIP: Factor a valid event here, otherwise the request will get rejected on start and the failure won't happen
// 💡 TIP: Make some internal function fail, choose any class method
// 💡 TIP: Use the library sinon to alter the behaviour of existing function and make it throw error
// https://sinonjs.org/releases/latest/stubs/
// 💡 TIP: Here is the syntax: sinon.stub(someClass.prototype, 'methodName').rejects(new Error("Error explanation"));
sinon.stub(SensorsService.prototype, 'addEvent').rejects(new Error("Error explanation"));

// Act
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

// Assert
expect(receivedResponse.status).toBe(500)
});

// ✅ Ensure that the webserver is closed when all the tests are completed
// 💡 TIP: Use the right test hook to call the API and instruct it to close

// ✅🚀 Spread your tests across multiple files, let the test runner invoke tests in multiple processes - Ensure all pass
// 💡 TIP: You might face port collision where two APIs instances try to open the same port
// 💡 TIP: Use the flag 'jest --maxWorkers=<num>'. Assign zero for max value of some specific number greater than 1
});
125 changes: 90 additions & 35 deletions test/mission-database-bay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,64 +34,119 @@ describe('Sensors test', () => {
// 💡 TIP: The event schema is already defined below
test('When adding a valid event, Then should get successful confirmation', async () => {
// Arrange
const eventToAdd = {
category: 'Home equipment',
temperature: 20,
reason: `Thermostat-failed`, // This must be unique
color: 'Green',
weight: 80,
status: 'active',
};
const eventToAdd = getSensorEvent();

// Act
// 💡 TIP: use any http client lib like Axios OR supertest
// 💡 TIP: This is how it is done with Supertest -> await request(expressApp).post("/sensor-events").send(eventToAdd);
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(eventToAdd);

// Assert
// 💡 TIP: Check not only the HTTP status bot also the body
const expectedResult = eventToAdd;
expectedResult.id = expect.any(Number);
expect(receivedResponse).toMatchObject({
status: 200,
body: expectedResult,
});
});

// ✅ TASK: Run the test above twice, it fails, ah? Let's fix!
// 💡 TIP: The failure is because the field 'reason' is unique. When the test runs for the second time -> This value already exists
// 💡 TIP: Write an helper function that create unique and short value, put this at the end of the reason field
// 💡 TIP: For the sake of this exercise, this helper can be as simple as just randomize number or use a timestamp

// ✅ TASK: In the test above 👆, ensure that 'id' field is also part of the response with the right type
// But hey, there is a challenge here: The 'id' is different in every response
// 💡 TIP: Jest has a dedicated matcher for unknown values, read about:
// https://jestjs.io/docs/en/expect#expectanyconstructor

// ✅ TASK: Let's test that the system indeed enforces the 'reason' field uniqueness by writing this test below 👇
// 💡 TIP: This test probably demands two POST calls, you can use the same JSON payload twice
// test('When a record exist with a specific reason and trying to add a second one, then it fails with status 409');
test('When a record exist with a specific reason and trying to add a second one, then it fails with status 409', async () => {
// Arrange
const eventToAdd = getSensorEvent();

// Act
const receivedResponseOne = await request(expressApp)
.post('/sensor-events')
.send(eventToAdd);

const receivedResponseTwo = await request(expressApp)
.post('/sensor-events')
.send(eventToAdd);

// Assert
expect(receivedResponseOne).toMatchObject({
status: 200,
body: eventToAdd,
});

expect(receivedResponseTwo.status).toBe(409);
});

// ✅ TASK: Let's write the test below 👇 that checks that querying by ID works. For now, temporarily please query for the event that
// was added using the first test above 👆.
// 💡 TIP: This is not the recommended technique (reusing records from previous tests), we do this to understand
// The consequences
test('When querying for event by id, Then the right event is being returned', () => {
// 💡 TIP: At first, query for the event that was added in the first test (In the first test above, store
// the ID of the added event globally). In this test, query for that ID
// 💡 TIP: This is the GET sensor URL: await request(expressApp).get(`/sensor-events/${id}`,
});
test('When querying for event by id, Then the right event is being returned', async () => {
// Arrange
const validEvent = getSensorEvent();

// ✅ TASK: Run the last test 👆 alone (without running other tests). Does it pass now?
// 💡 TIP: To run a single test only, put the word test.only in front of the test method
// 💡 TIP: Other way to run a single test is to run the tests in watch mode - 'npm run test:dev',
// then within the CLI type "t", now type your desired test name
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

// ✅ TASK: The last step should have failed, the query test 👆 fails when running alone... Why?
// 💡 TIP: This is because the first test ('Add sensor event') did not run, so no record added to the DB
// 💡 TIP: A test that relies on records from other tests will always be fragile and increase the maintenance complexity
// Act
const retrieveResponse = await request(expressApp)
.get(`/sensor-events/${receivedResponse.body.id}`)

// Assert
expect(retrieveResponse.body).toMatchObject(validEvent)
});

// ✅ TASK: Let's fix the query test above 👆 - Make it pass all the time, even when running alone
// 💡 TIP: In the arrange phase, add an event to query for. Don't trust any other test!

// ✅ TASK: Test that when a new event is posted to /sensor-events route, the temperature is not specified -> the event is NOT saved to the DB!
// 💡 TIP: Testing the response is not enough, the adequate state (e.g. DB) should also satisfy the expectation
// 💡 TIP: In the assert phase, query to get the event that was (not) added - Ensure the response is empty
test('When creating new event with no temperature should not save to db', async () => {
// Arrange
const validEvent = getSensorEvent();
delete validEvent.temperature

// Act
const receivedResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

const retrieveResponse = await request(expressApp)
.get(`/sensor-events/${validEvent.category}/color`)

// Assert
expect(receivedResponse.status).toBe(400)
expect(retrieveResponse).toMatchObject({
status: 200,
body: [],
});
})

// ✅ TASK: Test that when an event is deleted, then its indeed not existing anymore
test('When deleting event with should not be retrievable', async () => {
// Arrange
const validEvent = getSensorEvent();

// Act
const createdResponse = await request(expressApp)
.post('/sensor-events')
.send(validEvent);

const deletedResponse = await request(expressApp)
.delete(`/sensor-events/${createdResponse.body.id}`)

const getResponse = await request(expressApp)
.get(`/sensor-events/${createdResponse.body.id}`)

// Assert
expect(createdResponse).toMatchObject({
status: 200,
body: validEvent,
});
expect(deletedResponse.status).toBe(200)
expect(getResponse).toMatchObject({
status: 200,
body: null,
});
})

// ✅ TASK: Write the following test below 👇 to check that the app is able to return all records
// 💡 TIP: Checking the number of records in the response might be fragile as there other processes and tests
Expand Down
Loading