@node-idempotency/fastify
Version:
fastify plugin that provides Race-Condition free idempotency for HTTP requests, preventing unintended duplicate operations.
133 lines • 6.04 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const request = require("supertest");
const test_module_1 = require("./modules/test/test.module");
const shared_1 = require("@node-idempotency/shared");
const idempotencyKey = "Idempotency-Key";
const IDEMPOTENCY_REPLAYED_HEADER = shared_1.HTTPHeaderEnum.idempotentReplayed;
describe("Node-Idempotency", () => {
beforeAll(async () => {
await test_module_1.default.ready();
});
afterAll(async () => {
await test_module_1.default.close();
});
it("should return a cached response when key is reused", async () => {
const res1 = await request(test_module_1.default.server)
.get("/")
.set({ [idempotencyKey]: "1" })
.expect(200, "0");
const res2 = await request(test_module_1.default.server)
.get("/")
.set({ [idempotencyKey]: "1" })
.expect(200, "0");
expect(res1.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
expect(res2.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toEqual("true");
});
it("should return a cached response when key is reused for json endpoint", async () => {
const res1 = await request(test_module_1.default.server)
.get("/json")
.set({ [idempotencyKey]: "1" })
.expect(200, { jsonCounter: 1 });
const res2 = await request(test_module_1.default.server)
.get("/json")
.set({ [idempotencyKey]: "1" })
.expect(200, { jsonCounter: 1 });
expect(res1.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
expect(res2.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toEqual("true");
});
it("should return a cached response when key is reused with request body", async () => {
const res1 = await request(test_module_1.default.server)
.post("/")
.set({ [idempotencyKey]: "4" })
.send({ number: 2 })
.expect(201, "2");
const res2 = await request(test_module_1.default.server)
.post("/")
.set({ [idempotencyKey]: "4" })
.send({ number: 2 })
.expect(201, "2");
expect(res1.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
expect(res2.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toEqual("true");
});
it("should return a cached error when key is reused", async () => {
const res1 = await request(test_module_1.default.server)
.get("/error")
.set({ [idempotencyKey]: "5" })
.expect(500, {
statusCode: 500,
error: "Internal Server Error",
message: "unknown",
});
const res2 = await request(test_module_1.default.server)
.get("/error")
.set({ [idempotencyKey]: "5" })
.expect(500, {
statusCode: 500,
error: "Internal Server Error",
message: "unknown",
});
expect(res1.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
expect(res2.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toEqual("true");
});
it("should return 400 when idempotency key is not sent when its needed", async () => {
await request(test_module_1.default.server).get("/").expect(400);
});
it("should return 400 when idempotency key exceeds length", async () => {
await request(test_module_1.default.server)
.get("/")
.set({ [idempotencyKey]: "2345678" })
.expect(400);
});
it("should return a conflict when parallel requests are made", async () => {
const [res1, res2] = await Promise.all([
request(test_module_1.default.server)
.get("/slow")
.set({ [idempotencyKey]: "2" })
.then((res) => res)
.catch((err) => err),
request(test_module_1.default.server)
.get("/slow")
.set({ [idempotencyKey]: "2" })
.then((res) => res)
.catch((err) => err),
]);
const success = res1.status === 200 ? res1.status : res2.status;
const conflict = res1.status !== 200 ? res1.status : res2.status;
const successBody = res1.status === 200 ? res1.text : res2.text;
const conflictBody = res1.status !== 200 ? res1.body : res2.body;
const conflictHeader = res1.status !== 200 ? res1.headers : res2.headers;
expect(success).not.toBe(conflict);
expect(success).toBe(200);
expect(conflict).toBe(409);
expect(successBody).toEqual("0");
expect(conflictBody).toEqual({
code: "REQUEST_IN_PROGRESS",
error: "Conflict",
message: "A request is outstanding for this Idempotency-Key",
statusCode: 409,
});
expect(conflictHeader[shared_1.HTTPHeaderEnum.retryAfter.toLowerCase()]).toEqual("1");
});
it("should return error when different body is used for the same key", async () => {
const res1 = await request(test_module_1.default.server)
.post("/")
.set({ [idempotencyKey]: "3" })
.send({ number: 1 })
.expect(201, "3");
const res2 = await request(test_module_1.default.server)
.post("/")
.set({ [idempotencyKey]: "3" })
.send({ number: 2 })
.expect(422);
expect(JSON.parse(res2.text)).toEqual({
code: "IDEMPOTENCY_FINGERPRINT_MISSMATCH",
error: "Unprocessable Entity",
message: "Idempotency-Key is already used",
statusCode: 422,
});
expect(res1.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
expect(res2.headers[IDEMPOTENCY_REPLAYED_HEADER.toLowerCase()]).toBeUndefined();
});
});
//# sourceMappingURL=idempotency.spec.js.map