Challenge #7: Unexpected Status
Good code works. Great code handles errors.
Currently, Rot13Client.transformAsync()
assumes that nothing can go wrong: that the ROT-13 service is always up, running, and providing meaningful responses.
Of course, anyone who’s worked with networked systems knows that perfection is impossible. Something will go wrong... if not today, then tomorrow. In this challenge, you’ll handle the first of those situations, and fail gracefully when the ROT-13 service returns an unexpected status code. This is the Paranoic Telemetry pattern.
Instructions
1. Implement the "fails gracefully when status code has unexpected value"
test (back up at the top):
- Simulate the ROT-13 service returning a
400
(Bad Request) error when you callrot13Client.transformAsync()
. - Assert that
rot13Client.transformAsync()
throws the following error:const expectedError = "Unexpected status from ROT-13 service\n" + `Host: ${HOST}:9999\n` + "Endpoint: /rot13/transform\n" + "Status: 400\n" + `Headers: ${JSON.stringify(VALID_ROT13_HEADERS)}\n` + `Body: ${VALID_ROT13_BODY}`;
2. Modify rot13Client.transformAsync()
:
- Throw the above error when the ROT-13 service responds with any status other than
200
(OK).
Remember to commit your changes when you’re done.
API Documentation
throw new Error(message)
;
Throw an exception.
- message
(string)
- the error message
await assert.throwsAsync(fnAsync, expectedError);
Assert that an asynchronous function throws an error. Use it like this:
await assert.throwsAsync(
() => transformAsync(...), // note: NO await here
"my expected error",
);
- fnAsync
(function)
- the function to run - expectedError
(string)
- the errorfnAsync
is expected to throw
JavaScript Primers
No new concepts.
Hints
1
Your test needs to set up the HTTP response and call rot13Client.transformAsync()
.
You can use the transformAsync()
test helper for that. Don’t forget to await
it.
Remember to specify everything that’s in the error message.
it("fails gracefully when status code has unexpected value", async () => {
await transformAsync({
port: 9999,
rot13ServiceStatus: 400,
rot13ServiceHeaders: VALID_ROT13_HEADERS,
rot13ServiceBody: VALID_ROT13_BODY,
});
});
2
Your test needs to assert that transformAsync()
throws an error.
You can use assert.throwsAsync()
to check for that. Don’t forget to await
it. (You’ll remove the await
from the transformAsync()
call.)
it("fails gracefully when status code has unexpected value", async () => {
const expectedError =
"Unexpected status from ROT-13 service\n" +
`Host: ${HOST}:9999\n` +
"Endpoint: /rot13/transform\n" +
"Status: 400\n" +
`Headers: ${JSON.stringify(VALID_ROT13_HEADERS)}\n` +
`Body: ${VALID_ROT13_BODY}`;
await assert.throwsAsync(
() => transformAsync({
port: 9999,
rot13ServiceStatus: 400,
rot13ServiceHeaders: VALID_ROT13_HEADERS,
rot13ServiceBody: VALID_ROT13_BODY,
}),
expectedError,
);
});
3
The test is ready to run.
It should fail with "Expected exception"
.
That’s because the production code isn’t throwing an exception.
4
rot13Client.transformAsync()
should throw an exception based on the status code.
You can get the status code from response.status
.
async transformAsync(port, text, correlationId) {
this._listener.emit({ port, text, correlationId });
const response = await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: "/rot13/transform",
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
if (response.status !== 200) {
throw new Error(
"Unexpected status from ROT-13 service\n" +
`Host: ${HOST}:${port}\n` +
`Endpoint: ${TRANSFORM_ENDPOINT}\n` +
`Status: ${response.status}\n` +
`Headers: ${JSON.stringify(response.headers)}\n` +
`Body: ${response.body}`
);
}
const parsedBody = JSON.parse(response.body);
return parsedBody.transformed;
}
Complete Solution
Test code:
it("fails gracefully when status code has unexpected value", async () => {
const expectedError =
"Unexpected status from ROT-13 service\n" +
`Host: ${HOST}:9999\n` +
"Endpoint: /rot13/transform\n" +
"Status: 400\n" +
`Headers: ${JSON.stringify(VALID_ROT13_HEADERS)}\n` +
`Body: ${VALID_ROT13_BODY}`;
await assert.throwsAsync(
() => transformAsync({
port: 9999,
rot13ServiceStatus: 400,
rot13ServiceHeaders: VALID_ROT13_HEADERS,
rot13ServiceBody: VALID_ROT13_BODY,
}),
expectedError,
);
});
Production code (JavaScript):
async transformAsync(port, text, correlationId) {
this._listener.emit({ port, text, correlationId });
const response = await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: "/rot13/transform",
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
if (response.status !== 200) {
throw new Error(
"Unexpected status from ROT-13 service\n" +
`Host: ${HOST}:${port}\n` +
`Endpoint: ${TRANSFORM_ENDPOINT}\n` +
`Status: ${response.status}\n` +
`Headers: ${JSON.stringify(response.headers)}\n` +
`Body: ${response.body}`
);
}
const parsedBody = JSON.parse(response.body);
return parsedBody.transformed;
}
Production code (TypeScript):
async transformAsync(
port: number,
text: string,
correlationId: string,
): Promise<string> {
this._listener.emit({ port, text, correlationId });
const response = await this._httpClient.requestAsync({
host: HOST,
port,
method: "POST",
path: "/rot13/transform",
headers: {
"content-type": "application/json",
"x-correlation-id": correlationId,
},
body: JSON.stringify({ text }),
});
if (response.status !== 200) {
throw new Error(
"Unexpected status from ROT-13 service\n" +
`Host: ${HOST}:${port}\n` +
`Endpoint: ${TRANSFORM_ENDPOINT}\n` +
`Status: ${response.status}\n` +
`Headers: ${JSON.stringify(response.headers)}\n` +
`Body: ${response.body}`
);
}
const parsedBody = JSON.parse(response.body);
return parsedBody.transformed;
}