Skip to content

Commit

Permalink
refactor(experimental): abstract data layer
Browse files Browse the repository at this point in the history
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
  • Loading branch information
06kellyjac committed Dec 13, 2024
1 parent 3f355df commit 6c4553c
Show file tree
Hide file tree
Showing 14 changed files with 398 additions and 164 deletions.
51 changes: 27 additions & 24 deletions experimental/license-inventory/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,42 @@
import express from 'express';
import { logger } from '@/logger';
import apiRouter from '@/routes/api';
import createApiRouter from '@/routes/api';
import pinoHTTP from 'pino-http';
import bodyParser from 'body-parser';
import { rateLimit } from 'express-rate-limit';
import helmet from 'helmet';
import { LicenseDataService } from './services/data';
// import lusca from 'lusca';

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

const app = express();
const createApp = (lds: LicenseDataService) => {
const app = express();

const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
standardHeaders: 'draft-7',
legacyHeaders: false,
// in memory store
});
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 100,
standardHeaders: 'draft-7',
legacyHeaders: false,
// in memory store
});

app.use(helmet());
app.use(limiter);
app.use(bodyParser.json());
app.use(
pinoHTTP({
logger,
autoLogging: process.env.NODE_ENV === 'development',
// overrides core logger redaction
// please update in logger.ts
// redact: [],
}),
);
app.use(helmet());
app.use(limiter);
app.use(bodyParser.json());
app.use(
pinoHTTP({
logger,
autoLogging: process.env.NODE_ENV === 'development',
// overrides core logger redaction
// please update in logger.ts
// redact: [],
}),
);

app.use('/api', apiRouter);

export { app };
app.use('/api', createApiRouter(lds));
return app;
};
export { createApp };
8 changes: 4 additions & 4 deletions experimental/license-inventory/src/db/connect.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { AsyncResult } from '@/types';
import mongoose from 'mongoose';
import mongoose, { type Mongoose } from 'mongoose';

export const connectDB = async (dbURI: string): AsyncResult<void> => {
export const connectDB = async (dbURI: string): AsyncResult<Mongoose> => {
try {
await mongoose.connect(dbURI);
return { error: null, data: null };
const connection = await mongoose.connect(dbURI);
return { error: null, data: connection };
} catch (e: unknown) {
if (e instanceof Error) {
return { error: e, data: null };
Expand Down
11 changes: 11 additions & 0 deletions experimental/license-inventory/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Mongoose, Model } from 'mongoose';
import { licenseSchema, type LicenseSchema } from './schemas/license/license';

export class Database {
mongoose: Mongoose;
License: Model<LicenseSchema>;
constructor(mongoose: Mongoose) {
this.mongoose = mongoose;
this.License = mongoose.model<LicenseSchema>('License', licenseSchema);
}
}
13 changes: 9 additions & 4 deletions experimental/license-inventory/src/routes/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import express from 'express';
import v0Router from './v0';
const router = express.Router();
import createV0Router from './v0';
import { LicenseDataService } from '@/services/data';

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

export default router;
router.use('/v0', createV0Router(lds));
return router;
};

export default createRouter;
13 changes: 9 additions & 4 deletions experimental/license-inventory/src/routes/api/v0/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import express from 'express';
import licensesRouter from './licenses';
const router = express.Router();
import createLicensesRouter from './licenses';
import { LicenseDataService } from '@/services/data';

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

export default router;
router.use('/licenses', createLicensesRouter(lds));
return router;
};

export default createRouter;
64 changes: 51 additions & 13 deletions experimental/license-inventory/src/routes/api/v0/licenses.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, it, expect, afterEach, jest } from '@jest/globals';
import request from 'supertest';
import { License } from '@/db/collections';
import { app } from '@/app';
import { v4 as uuidv4 } from 'uuid';
import { createApp } from '@/app';
import { genMockLicenseDataService } from '@/test/mock/db';

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

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

expect(res.body).toEqual([]);
});

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

expect(res.body).toEqual([inputData]);
});
});

describe(`GET /:id - read`, () => {
const testID = '157c0c6a-5c99-4298-9529-95816da2255a';
it('invalid id - not uuid', async () => {
const mockLDS = genMockLicenseDataService();
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: null });
const app = createApp(mockLDS);
await request(app)
.get(genRoute('/' + 'apache-2_0'))
.expect('Content-Type', /json/)
.expect(500);
});

it('valid id - no data', async () => {
const mockLDS = genMockLicenseDataService();
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: null });
const app = createApp(mockLDS);
const res = await request(app)
.get(genRoute('/' + testID))
.expect('Content-Type', /json/)
.expect(200);

expect(res.body).toEqual({ license: null });
});

it('valid id - data', async () => {
const licenseData = {
id: testID,
name: 'test',
};
const mockLDS = genMockLicenseDataService();
mockLDS.getByUUID.mockResolvedValueOnce({ error: null, data: licenseData });
const app = createApp(mockLDS);
const res = await request(app)
.get(genRoute('/' + testID))
.expect('Content-Type', /json/)
.expect(200);

expect(res.body).toEqual({ license: licenseData });
});
});
});
Loading

0 comments on commit 6c4553c

Please sign in to comment.