Skip to content

Commit 8d8a035

Browse files
author
Ahmed Hekal
committed
improve: add automatic test for express with custom multer instance
1 parent e58d403 commit 8d8a035

File tree

5 files changed

+295
-0
lines changed

5 files changed

+295
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as express from 'express';
2+
3+
export function expressAuthentication(req: express.Request, name: string, _scopes: string[] | undefined, res: express.Response): Promise<any> {
4+
if (name === 'api_key') {
5+
let token;
6+
if (req.query && req.query.access_token) {
7+
token = req.query.access_token;
8+
} else {
9+
return Promise.reject({});
10+
}
11+
12+
if (token === 'abc123456') {
13+
return Promise.resolve({
14+
id: 1,
15+
name: 'Ironman',
16+
});
17+
} else if (token === 'xyz123456') {
18+
return Promise.resolve({
19+
id: 2,
20+
name: 'Thor',
21+
});
22+
} else {
23+
return Promise.reject({});
24+
}
25+
} else {
26+
if (req.query && req.query.tsoa && req.query.tsoa === 'abc123456') {
27+
return Promise.resolve({});
28+
} else {
29+
return Promise.reject({});
30+
}
31+
}
32+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as bodyParser from 'body-parser';
2+
import * as express from 'express';
3+
import * as methodOverride from 'method-override';
4+
import '../controllers/rootController';
5+
6+
import { RegisterRoutes } from './routes';
7+
8+
export const app: express.Express = express();
9+
export const router = express.Router();
10+
app.use('/v1', router);
11+
router.use(bodyParser.urlencoded({ extended: true }));
12+
router.use(bodyParser.json());
13+
router.use(methodOverride());
14+
router.use((req: any, res: any, next: any) => {
15+
req.stringValue = 'fancyStringForContext';
16+
next();
17+
});
18+
19+
import multer = require('multer');
20+
21+
RegisterRoutes(router, {
22+
multer: multer({
23+
limits: {
24+
fieldNameSize: 120,
25+
},
26+
}),
27+
});
28+
29+
// It's important that this come after the main routes are registered
30+
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
31+
const status = err.status || 500;
32+
const body: any = {
33+
fields: err.fields || undefined,
34+
message: err.message || 'An error occurred during the request.',
35+
name: err.name,
36+
status,
37+
};
38+
res.status(status).json(body);
39+
});
40+
41+
app.listen();

tests/fixtures/express-router/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ router.use((req: any, res: any, next: any) => {
1515
req.stringValue = 'fancyStringForContext';
1616
next();
1717
});
18+
1819
RegisterRoutes(router);
1920

2021
// It's important that this come after the main routes are registered
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { File } from '@tsoa/runtime';
2+
import { expect } from 'chai';
3+
import { readFileSync } from 'fs';
4+
import 'mocha';
5+
import { resolve } from 'path';
6+
import * as request from 'supertest';
7+
import { app } from '../fixtures/express/server';
8+
9+
const basePath = '/v1';
10+
11+
describe('Express Server With custom multer', () => {
12+
describe('file upload With custom multer instance', () => {
13+
it('can post a file', () => {
14+
const formData = { someFile: '@../package.json' };
15+
return verifyFileUploadRequest(basePath + '/PostTest/File', formData, (_err, res) => {
16+
const packageJsonBuffer = readFileSync(resolve(__dirname, '../package.json'));
17+
const returnedBuffer = Buffer.from(res.body.buffer);
18+
expect(res.body).to.not.be.undefined;
19+
expect(res.body.fieldname).to.equal('someFile');
20+
expect(res.body.originalname).to.equal('package.json');
21+
expect(res.body.encoding).to.be.not.undefined;
22+
expect(res.body.mimetype).to.equal('application/json');
23+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
24+
});
25+
});
26+
27+
it('can post a file without name', () => {
28+
const formData = { aFile: '@../package.json' };
29+
return verifyFileUploadRequest(basePath + '/PostTest/FileWithoutName', formData, (_err, res) => {
30+
expect(res.body).to.not.be.undefined;
31+
expect(res.body.fieldname).to.equal('aFile');
32+
});
33+
});
34+
35+
it('cannot post a file with wrong attribute name', async () => {
36+
const formData = { wrongAttributeName: '@../package.json' };
37+
verifyFileUploadRequest(basePath + '/PostTest/File', formData, (_err, res) => {
38+
expect(res.status).to.equal(500);
39+
expect(res.text).to.equal('{"message":"Unexpected field","name":"MulterError","status":500}');
40+
});
41+
});
42+
43+
it('can post multiple files with other form fields', () => {
44+
const formData = {
45+
a: 'b',
46+
c: 'd',
47+
someFiles: ['@../package.json', '@../tsconfig.json'],
48+
};
49+
50+
return verifyFileUploadRequest(basePath + '/PostTest/ManyFilesAndFormFields', formData, (_err, res) => {
51+
for (const file of res.body as File[]) {
52+
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
53+
const returnedBuffer = Buffer.from(file.buffer);
54+
expect(file).to.not.be.undefined;
55+
expect(file.fieldname).to.be.not.undefined;
56+
expect(file.originalname).to.be.not.undefined;
57+
expect(file.encoding).to.be.not.undefined;
58+
expect(file.mimetype).to.equal('application/json');
59+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
60+
}
61+
});
62+
});
63+
64+
it('can post single file to multi file field', () => {
65+
const formData = {
66+
a: 'b',
67+
c: 'd',
68+
someFiles: ['@../package.json'],
69+
};
70+
71+
return verifyFileUploadRequest(basePath + '/PostTest/ManyFilesAndFormFields', formData, (_err, res) => {
72+
expect(res.body).to.be.length(1);
73+
});
74+
});
75+
76+
it('can post multiple files with different field', () => {
77+
const formData = {
78+
file_a: '@../package.json',
79+
file_b: '@../tsconfig.json',
80+
};
81+
return verifyFileUploadRequest(`${basePath}/PostTest/ManyFilesInDifferentFields`, formData, (_err, res) => {
82+
for (const file of res.body as File[]) {
83+
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
84+
const returnedBuffer = Buffer.from(file.buffer);
85+
expect(file).to.not.be.undefined;
86+
expect(file.fieldname).to.be.not.undefined;
87+
expect(file.originalname).to.be.not.undefined;
88+
expect(file.encoding).to.be.not.undefined;
89+
expect(file.mimetype).to.equal('application/json');
90+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
91+
}
92+
});
93+
});
94+
95+
it('can post multiple files with different array fields', () => {
96+
const formData = {
97+
files_a: ['@../package.json', '@../tsconfig.json'],
98+
file_b: '@../tsoa.json',
99+
files_c: ['@../tsconfig.json', '@../package.json'],
100+
};
101+
return verifyFileUploadRequest(`${basePath}/PostTest/ManyFilesInDifferentArrayFields`, formData, (_err, res) => {
102+
for (const fileList of res.body as File[][]) {
103+
for (const file of fileList) {
104+
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
105+
const returnedBuffer = Buffer.from(file.buffer);
106+
expect(file).to.not.be.undefined;
107+
expect(file.fieldname).to.be.not.undefined;
108+
expect(file.originalname).to.be.not.undefined;
109+
expect(file.encoding).to.be.not.undefined;
110+
expect(file.mimetype).to.equal('application/json');
111+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
112+
}
113+
}
114+
});
115+
});
116+
117+
it('can post mixed form data content with file and not providing optional file', () => {
118+
const formData = {
119+
username: 'test',
120+
avatar: '@../tsconfig.json',
121+
};
122+
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
123+
const file = res.body.avatar;
124+
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
125+
const returnedBuffer = Buffer.from(file.buffer);
126+
expect(res.body.username).to.equal(formData.username);
127+
expect(res.body.optionalAvatar).to.undefined;
128+
expect(file).to.not.be.undefined;
129+
expect(file.fieldname).to.be.not.undefined;
130+
expect(file.originalname).to.be.not.undefined;
131+
expect(file.encoding).to.be.not.undefined;
132+
expect(file.mimetype).to.equal('application/json');
133+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
134+
});
135+
});
136+
137+
it('can post mixed form data content with file and provides optional file', () => {
138+
const formData = {
139+
username: 'test',
140+
avatar: '@../tsconfig.json',
141+
optionalAvatar: '@../package.json',
142+
};
143+
return verifyFileUploadRequest(`${basePath}/PostTest/MixedFormDataWithFilesContainsOptionalFile`, formData, (_err, res) => {
144+
expect(res.body.username).to.equal(formData.username);
145+
for (const fieldName of ['avatar', 'optionalAvatar']) {
146+
const file = res.body[fieldName];
147+
const packageJsonBuffer = readFileSync(resolve(__dirname, `../${file.originalname}`));
148+
const returnedBuffer = Buffer.from(file.buffer);
149+
expect(file).to.not.be.undefined;
150+
expect(file.fieldname).to.be.not.undefined;
151+
expect(file.originalname).to.be.not.undefined;
152+
expect(file.encoding).to.be.not.undefined;
153+
expect(file.mimetype).to.equal('application/json');
154+
expect(Buffer.compare(returnedBuffer, packageJsonBuffer)).to.equal(0);
155+
}
156+
});
157+
});
158+
159+
function verifyFileUploadRequest(
160+
path: string,
161+
formData: any,
162+
verifyResponse: (err: any, res: request.Response) => any = () => {
163+
/**/
164+
},
165+
expectedStatus?: number,
166+
) {
167+
return verifyRequest(
168+
verifyResponse,
169+
request =>
170+
Object.keys(formData).reduce((req, key) => {
171+
const values = [].concat(formData[key]);
172+
values.forEach((v: any) => (v.startsWith('@') ? req.attach(key, resolve(__dirname, v.slice(1))) : req.field(key, v)));
173+
return req;
174+
}, request.post(path)),
175+
expectedStatus,
176+
);
177+
}
178+
});
179+
180+
function verifyRequest(verifyResponse: (err: any, res: request.Response) => any, methodOperation: (request: request.SuperTest<any>) => request.Test, expectedStatus = 200) {
181+
return new Promise<void>((resolve, reject) => {
182+
methodOperation(request(app))
183+
.expect(expectedStatus)
184+
.end((err: any, res: any) => {
185+
let parsedError: any;
186+
try {
187+
parsedError = JSON.parse(res.error);
188+
} catch (err) {
189+
parsedError = res?.error;
190+
}
191+
192+
if (err) {
193+
reject({
194+
error: err,
195+
response: parsedError,
196+
});
197+
return;
198+
}
199+
200+
verifyResponse(parsedError, res);
201+
resolve();
202+
});
203+
});
204+
}
205+
});

tests/prepare.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ const log = async <T>(label: string, fn: () => Promise<T>) => {
5656
metadata,
5757
),
5858
),
59+
log('Express Router Route Generation With custom multer instance', () =>
60+
generateRoutes(
61+
{
62+
noImplicitAdditionalProperties: 'silently-remove-extras',
63+
bodyCoercion: true,
64+
authenticationModule: './fixtures/express-router-with-custom-multer/authentication.ts',
65+
entryFile: './fixtures/express-router-with-custom-multer/server.ts',
66+
middleware: 'express',
67+
routesDir: './fixtures/express-router-with-custom-multer',
68+
},
69+
undefined,
70+
undefined,
71+
metadata,
72+
),
73+
),
74+
5975
log('Express Route Generation, OpenAPI3, noImplicitAdditionalProperties', () =>
6076
generateRoutes(
6177
{

0 commit comments

Comments
 (0)