Skip to content

Commit 984dc5c

Browse files
committed
refactor(experimental): abstract data layer
Defined a database layer Added an implementation using mongoose Refactored the express routes to take an implementation Added a mockable implementation Refactored the tests to use the mockable implementation Added tests for the get by uuid route
1 parent 1df2381 commit 984dc5c

File tree

14 files changed

+398
-164
lines changed

14 files changed

+398
-164
lines changed
Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,42 @@
11
import express from 'express';
22
import { logger } from '@/logger';
3-
import apiRouter from '@/routes/api';
3+
import createApiRouter from '@/routes/api';
44
import pinoHTTP from 'pino-http';
55
import bodyParser from 'body-parser';
66
import { rateLimit } from 'express-rate-limit';
77
import helmet from 'helmet';
8+
import { LicenseDataService } from './services/data';
89
// import lusca from 'lusca';
910

1011
// helmet and lusca comparison
1112
// https://github.com/krakenjs/lusca/issues/42#issuecomment-65093906
1213
// TODO: integrate lusca once added sessions/auth
1314

14-
const app = express();
15+
const createApp = (lds: LicenseDataService) => {
16+
const app = express();
1517

16-
const limiter = rateLimit({
17-
windowMs: 15 * 60 * 1000,
18-
limit: 100,
19-
standardHeaders: 'draft-7',
20-
legacyHeaders: false,
21-
// in memory store
22-
});
18+
const limiter = rateLimit({
19+
windowMs: 15 * 60 * 1000,
20+
limit: 100,
21+
standardHeaders: 'draft-7',
22+
legacyHeaders: false,
23+
// in memory store
24+
});
2325

24-
app.use(helmet());
25-
app.use(limiter);
26-
app.use(bodyParser.json());
27-
app.use(
28-
pinoHTTP({
29-
logger,
30-
autoLogging: process.env.NODE_ENV === 'development',
31-
// overrides core logger redaction
32-
// please update in logger.ts
33-
// redact: [],
34-
}),
35-
);
26+
app.use(helmet());
27+
app.use(limiter);
28+
app.use(bodyParser.json());
29+
app.use(
30+
pinoHTTP({
31+
logger,
32+
autoLogging: process.env.NODE_ENV === 'development',
33+
// overrides core logger redaction
34+
// please update in logger.ts
35+
// redact: [],
36+
}),
37+
);
3638

37-
app.use('/api', apiRouter);
38-
39-
export { app };
39+
app.use('/api', createApiRouter(lds));
40+
return app;
41+
};
42+
export { createApp };

experimental/license-inventory/src/db/connect.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { AsyncResult } from '@/types';
2-
import mongoose from 'mongoose';
2+
import mongoose, { type Mongoose } from 'mongoose';
33

4-
export const connectDB = async (dbURI: string): AsyncResult<void> => {
4+
export const connectDB = async (dbURI: string): AsyncResult<Mongoose> => {
55
try {
6-
await mongoose.connect(dbURI);
7-
return { error: null, data: null };
6+
const connection = await mongoose.connect(dbURI);
7+
return { error: null, data: connection };
88
} catch (e: unknown) {
99
if (e instanceof Error) {
1010
return { error: e, data: null };
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { Mongoose, Model } from 'mongoose';
2+
import { licenseSchema, type LicenseSchema } from './schemas/license/license';
3+
4+
export class Database {
5+
mongoose: Mongoose;
6+
License: Model<LicenseSchema>;
7+
constructor(mongoose: Mongoose) {
8+
this.mongoose = mongoose;
9+
this.License = mongoose.model<LicenseSchema>('License', licenseSchema);
10+
}
11+
}
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import express from 'express';
2-
import v0Router from './v0';
3-
const router = express.Router();
2+
import createV0Router from './v0';
3+
import { LicenseDataService } from '@/services/data';
44

5-
router.use('/v0', v0Router);
5+
const createRouter = (lds: LicenseDataService) => {
6+
const router = express.Router();
67

7-
export default router;
8+
router.use('/v0', createV0Router(lds));
9+
return router;
10+
};
11+
12+
export default createRouter;
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import express from 'express';
2-
import licensesRouter from './licenses';
3-
const router = express.Router();
2+
import createLicensesRouter from './licenses';
3+
import { LicenseDataService } from '@/services/data';
44

5-
router.use('/licenses', licensesRouter);
5+
const createRouter = (lds: LicenseDataService) => {
6+
const router = express.Router();
67

7-
export default router;
8+
router.use('/licenses', createLicensesRouter(lds));
9+
return router;
10+
};
11+
12+
export default createRouter;
Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { describe, it, expect, afterEach, jest } from '@jest/globals';
22
import request from 'supertest';
3-
import { License } from '@/db/collections';
4-
import { app } from '@/app';
3+
import { v4 as uuidv4 } from 'uuid';
4+
import { createApp } from '@/app';
5+
import { genMockLicenseDataService } from '@/test/mock/db';
56

67
const basePath = '/api/v0/licenses';
78
const genRoute = (p: string) => basePath + p;
@@ -13,29 +14,66 @@ describe(basePath, () => {
1314

1415
describe('GET / - list', () => {
1516
it('no data', async () => {
16-
const execMock = jest.fn(() => Promise.resolve([]));
17-
jest.spyOn(License, 'find').mockReturnValueOnce({
18-
exec: execMock,
19-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
20-
} as any);
17+
const mockLDS = genMockLicenseDataService();
18+
mockLDS.list.mockResolvedValueOnce({ error: null, data: [] });
19+
const app = createApp(mockLDS);
2120
const res = await request(app).get(genRoute('/')).expect('Content-Type', /json/).expect(200);
2221

2322
expect(res.body).toEqual([]);
2423
});
2524

2625
it('one entry', async () => {
2726
const inputData = {
28-
id: 'test',
27+
id: uuidv4(),
2928
name: 'test',
3029
};
31-
const execMock = jest.fn(() => Promise.resolve([{ toJSON: async () => inputData }]));
32-
jest.spyOn(License, 'find').mockReturnValueOnce({
33-
exec: execMock,
34-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
35-
} as any);
30+
const mockLDS = genMockLicenseDataService();
31+
mockLDS.list.mockResolvedValueOnce({ error: null, data: [inputData] });
32+
const app = createApp(mockLDS);
3633
const res = await request(app).get(genRoute('/')).expect('Content-Type', /json/).expect(200);
3734

3835
expect(res.body).toEqual([inputData]);
3936
});
4037
});
38+
39+
describe(`GET /:id - read`, () => {
40+
const testID = '157c0c6a-5c99-4298-9529-95816da2255a';
41+
it('invalid id - not uuid', async () => {
42+
const mockLDS = genMockLicenseDataService();
43+
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: null });
44+
const app = createApp(mockLDS);
45+
await request(app)
46+
.get(genRoute('/' + 'apache-2_0'))
47+
.expect('Content-Type', /json/)
48+
.expect(500);
49+
});
50+
51+
it('valid id - no data', async () => {
52+
const mockLDS = genMockLicenseDataService();
53+
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: null });
54+
const app = createApp(mockLDS);
55+
const res = await request(app)
56+
.get(genRoute('/' + testID))
57+
.expect('Content-Type', /json/)
58+
.expect(200);
59+
60+
expect(res.body).toEqual({ license: null });
61+
});
62+
63+
it('valid id - data', async () => {
64+
const licenseData = {
65+
id: testID,
66+
name: 'test',
67+
};
68+
const mockLDS = genMockLicenseDataService();
69+
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: licenseData });
70+
const app = createApp(mockLDS);
71+
const res = await request(app)
72+
.get(genRoute('/' + testID))
73+
.expect('Content-Type', /json/)
74+
.expect(200);
75+
76+
expect(res.body).toEqual({ license: licenseData });
77+
});
78+
});
4179
});

0 commit comments

Comments
 (0)