Skip to content

Commit 7421c6c

Browse files
authored
Merge pull request #51 from jaredwray/feat-adding-in-cookies-from-httpbin
feat: adding in cookies from httpbin
2 parents 2427314 + 24997b2 commit 7421c6c

File tree

7 files changed

+218
-1
lines changed

7 files changed

+218
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"xo": "^0.60.0"
5050
},
5151
"dependencies": {
52+
"@fastify/cookie": "^11.0.2",
5253
"@fastify/helmet": "^13.0.1",
5354
"@fastify/static": "^8.1.1",
5455
"@fastify/swagger": "^9.5.1",

src/mock-http.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'node:path';
22
import fastifyStatic from '@fastify/static';
33
import fastifyHelmet from '@fastify/helmet';
44
import {fastifySwagger} from '@fastify/swagger';
5+
import fastifyCookie from '@fastify/cookie';
56
import Fastify, {type FastifyInstance} from 'fastify';
67
import {Hookified, type HookifiedOptions} from 'hookified';
78
import {detect} from 'detect-port';
@@ -16,6 +17,7 @@ import {statusCodeRoute} from './routes/status-codes/index.js';
1617
import {ipRoute, headersRoute, userAgentRoute} from './routes/request-inspection/index.js';
1718
import {cacheRoutes, etagRoutes, responseHeadersRoutes} from './routes/response-inspection/index.js';
1819
import {absoluteRedirectRoute, relativeRedirectRoute, redirectToRoute} from './routes/redirects/index.js';
20+
import {getCookiesRoute, postCookieRoute, deleteCookieRoute} from './routes/cookies/index.js';
1921

2022
// eslint-disable-next-line unicorn/prevent-abbreviations
2123
export type HttpBinOptions = {
@@ -24,6 +26,7 @@ export type HttpBinOptions = {
2426
requestInspection?: boolean;
2527
responseInspection?: boolean;
2628
statusCodes?: boolean;
29+
cookies?: boolean;
2730
};
2831

2932
export type MockHttpOptions = {
@@ -70,6 +73,7 @@ export class MockHttp extends Hookified {
7073
requestInspection: true,
7174
responseInspection: true,
7275
statusCodes: true,
76+
cookies: true,
7377
};
7478

7579
// eslint-disable-next-line new-cap
@@ -245,7 +249,7 @@ export class MockHttp extends Hookified {
245249
await this.registerApiDocs();
246250
}
247251

248-
const {httpMethods, redirects, requestInspection, responseInspection, statusCodes} = this._httpBin;
252+
const {httpMethods, redirects, requestInspection, responseInspection, statusCodes, cookies} = this._httpBin;
249253

250254
if (httpMethods) {
251255
await this.registerHttpMethods();
@@ -267,6 +271,10 @@ export class MockHttp extends Hookified {
267271
await this.registerRedirectRoutes();
268272
}
269273

274+
if (cookies) {
275+
await this.registerCookieRoutes();
276+
}
277+
270278
if (this._autoDetectPort) {
271279
const originalPort = this._port;
272280
this._port = await this.detectPort();
@@ -370,6 +378,18 @@ export class MockHttp extends Hookified {
370378
await fastify.register(relativeRedirectRoute);
371379
await fastify.register(redirectToRoute);
372380
}
381+
382+
/**
383+
* Register the cookie routes.
384+
* @param fastifyInstance - the server instance to register the routes on.
385+
*/
386+
public async registerCookieRoutes(fastifyInstance?: FastifyInstance): Promise<void> {
387+
const fastify = fastifyInstance ?? this._server;
388+
await fastify.register(fastifyCookie);
389+
await fastify.register(getCookiesRoute);
390+
await fastify.register(postCookieRoute);
391+
await fastify.register(deleteCookieRoute);
392+
}
373393
}
374394

375395
export const mockhttp = MockHttp;

src/routes/cookies/delete.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
type FastifyInstance, type FastifySchema,
3+
} from 'fastify';
4+
5+
export const cookiesDeleteSchema: FastifySchema = {
6+
description: 'Delete a cookie',
7+
tags: ['Cookies'],
8+
querystring: {
9+
type: 'object',
10+
properties: {
11+
name: {type: 'string'},
12+
},
13+
required: ['name'],
14+
additionalProperties: false,
15+
},
16+
response: {
17+
// eslint-disable-next-line @typescript-eslint/naming-convention
18+
204: {
19+
type: 'null',
20+
},
21+
},
22+
};
23+
24+
export const deleteCookieRoute = (fastify: FastifyInstance) => {
25+
fastify.delete('/cookies', {schema: cookiesDeleteSchema}, async (request, reply) => {
26+
const {name} = request.query as {name: string};
27+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
28+
reply.clearCookie(name, {path: '/'});
29+
30+
// Send back a 204 No Content response
31+
return reply.status(204).send();
32+
});
33+
};

src/routes/cookies/get.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
type FastifyInstance, type FastifyRequest, type FastifyReply, type FastifySchema,
3+
} from 'fastify';
4+
5+
const getCookiesRouteSchema: FastifySchema = {
6+
description: 'Return cookies from the request',
7+
tags: ['Cookies'],
8+
response: {
9+
// eslint-disable-next-line @typescript-eslint/naming-convention
10+
200: {
11+
type: 'object',
12+
properties: {
13+
cookies: {
14+
type: 'object',
15+
additionalProperties: {type: 'string'},
16+
},
17+
},
18+
required: ['cookies'],
19+
},
20+
},
21+
};
22+
23+
export const getCookiesRoute = (fastify: FastifyInstance) => {
24+
fastify.get('/cookies', {schema: getCookiesRouteSchema}, async (request: FastifyRequest, reply: FastifyReply) => {
25+
await reply.send({
26+
cookies: request.cookies,
27+
});
28+
});
29+
};

src/routes/cookies/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export {getCookiesRoute} from './get.js';
2+
export {postCookieRoute} from './post.js';
3+
export {deleteCookieRoute} from './delete.js';
4+

src/routes/cookies/post.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
type FastifyInstance, type FastifySchema,
3+
} from 'fastify';
4+
5+
const postCookieRouteSchema: FastifySchema = {
6+
description: 'Set a cookie',
7+
tags: ['Cookies'],
8+
body: {
9+
type: 'object',
10+
properties: {
11+
name: {type: 'string'},
12+
value: {type: 'string'},
13+
expires: {type: 'string', format: 'date-time'},
14+
},
15+
required: ['name', 'value'],
16+
additionalProperties: false,
17+
},
18+
response: {
19+
// eslint-disable-next-line @typescript-eslint/naming-convention
20+
200: {
21+
type: 'object',
22+
properties: {
23+
cookies: {
24+
type: 'object',
25+
additionalProperties: {type: 'string'},
26+
},
27+
},
28+
required: ['cookies'],
29+
},
30+
},
31+
};
32+
33+
export const postCookieRoute = (fastify: FastifyInstance) => {
34+
fastify.post<{Body: {name: string; value: string; expires?: string}}>('/cookies', {schema: postCookieRouteSchema}, async (request, reply) => {
35+
const {name, value, expires} = request.body;
36+
if (expires) {
37+
const date = new Date(expires);
38+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
39+
reply.setCookie(name, value, {path: '/', expires: date});
40+
} else {
41+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
42+
reply.setCookie(name, value, {path: '/'});
43+
}
44+
45+
// Send back the current cookies
46+
return reply.status(200).send();
47+
});
48+
};

test/routes/cookies/index.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {describe, it, expect} from 'vitest';
2+
import Fastify from 'fastify';
3+
import fastifyCookie from '@fastify/cookie';
4+
import {getCookiesRoute, postCookieRoute, deleteCookieRoute} from '../../../src/routes/cookies/index.js';
5+
6+
describe('Cookies route', async () => {
7+
// eslint-disable-next-line new-cap
8+
const fastify = Fastify();
9+
await fastify.register(fastifyCookie);
10+
await fastify.register(getCookiesRoute);
11+
await fastify.register(postCookieRoute);
12+
await fastify.register(deleteCookieRoute);
13+
await fastify.ready();
14+
15+
it('should return no cookies in the response', async () => {
16+
const response = await fastify.inject({
17+
method: 'GET',
18+
url: '/cookies',
19+
});
20+
21+
expect(response.statusCode).toBe(200);
22+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
23+
const responseBody = response.json();
24+
expect(responseBody).toEqual({
25+
cookies: {},
26+
});
27+
});
28+
29+
it('should set a cookie', async () => {
30+
const setCookieResponse = await fastify.inject({
31+
method: 'POST',
32+
url: '/cookies',
33+
payload: {
34+
name: 'testCookie',
35+
value: 'testValue',
36+
expires: new Date(Date.now() + (60 * 60 * 1000)).toISOString(),
37+
},
38+
});
39+
40+
expect(setCookieResponse.statusCode).toBe(200);
41+
42+
const setCookieHeader = setCookieResponse.headers['set-cookie'];
43+
44+
expect(setCookieHeader).toBeDefined();
45+
expect(setCookieHeader).toContain('testCookie=testValue');
46+
});
47+
48+
it('should set a cookie with no expires', async () => {
49+
const setCookieResponse = await fastify.inject({
50+
method: 'POST',
51+
url: '/cookies',
52+
payload: {
53+
name: 'testCookieNoExpires',
54+
value: 'testValueNoExpires',
55+
},
56+
});
57+
58+
expect(setCookieResponse.statusCode).toBe(200);
59+
60+
const setCookieHeader = setCookieResponse.headers['set-cookie'];
61+
62+
expect(setCookieHeader).toBeDefined();
63+
expect(setCookieHeader).toContain('testCookieNoExpires=testValueNoExpires');
64+
});
65+
66+
it('should delete a cookie', async () => {
67+
const deleteCookieResponse = await fastify.inject({
68+
method: 'DELETE',
69+
url: '/cookies',
70+
query: {
71+
name: 'testCookieToDelete',
72+
},
73+
});
74+
75+
expect(deleteCookieResponse.statusCode).toBe(204);
76+
77+
const deleteCookieHeader = deleteCookieResponse.headers['set-cookie'];
78+
79+
expect(deleteCookieHeader).toBeDefined();
80+
expect(deleteCookieHeader).toContain('testCookieToDelete=; Max-Age=0; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax');
81+
});
82+
});

0 commit comments

Comments
 (0)