express-generator-typescript 2.5.14 → 2.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/project-files/package.json +1 -1
- package/lib/project-files/spec/support/Paths.ts +1 -1
- package/lib/project-files/spec/support/index.ts +13 -16
- package/lib/project-files/spec/tests/users.spec.ts +35 -20
- package/lib/project-files/src/common/ENV.ts +9 -2
- package/lib/project-files/src/common/constants.ts +2 -1
- package/lib/project-files/src/repos/MockOrm.ts +2 -2
- package/lib/project-files/src/repos/UserRepo.ts +13 -5
- package/lib/project-files/src/repos/database.test.json +1 -1
- package/lib/project-files/src/routes/UserRoutes.ts +0 -1
- package/lib/project-files/src/routes/common/index.ts +42 -26
- package/lib/project-files/src/routes/index.ts +1 -1
- package/lib/project-files/src/server.ts +6 -6
- package/lib/project-files/src/util/misc.ts +1 -0
- package/package.json +1 -1
- /package/lib/project-files/src/{routes/common → common}/Paths.ts +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { IReqPropErr } from '@src/routes/common';
|
|
1
|
+
import { Response } from 'supertest';
|
|
2
|
+
import { TParseReqErr } from '@src/routes/common';
|
|
5
3
|
import UserRepo from '@src/repos/UserRepo';
|
|
6
4
|
|
|
7
5
|
|
|
@@ -9,9 +7,19 @@ import UserRepo from '@src/repos/UserRepo';
|
|
|
9
7
|
Types
|
|
10
8
|
******************************************************************************/
|
|
11
9
|
|
|
10
|
+
// Use generics to add properties to 'body'
|
|
11
|
+
export type TRes<T = object> = Omit<Response, 'body'> & {
|
|
12
|
+
body: T & { error?: string | IErrObj },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface IErrObj {
|
|
16
|
+
message: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
export interface IValidationErr {
|
|
13
21
|
message: string;
|
|
14
|
-
parameters:
|
|
22
|
+
parameters: TParseReqErr[];
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
|
|
@@ -27,14 +35,3 @@ export async function cleanDatabase(): Promise<void> {
|
|
|
27
35
|
UserRepo.deleteAllUsers(),
|
|
28
36
|
]);
|
|
29
37
|
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Put things you need to do to modify the resp body object here.
|
|
33
|
-
*/
|
|
34
|
-
export function parseResBody(body: unknown) {
|
|
35
|
-
return traverseObject((key, val, parentObj) => {
|
|
36
|
-
if (key === 'created' && isValidDate(val)) {
|
|
37
|
-
parentObj[key] = new Date(val);
|
|
38
|
-
}
|
|
39
|
-
})(body);
|
|
40
|
-
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import supertest from 'supertest';
|
|
2
2
|
import insertUrlParams from 'inserturlparams';
|
|
3
|
-
import { parseJson } from 'jet-validators/utils';
|
|
3
|
+
import { parseJson, customDeepCompare } from 'jet-validators/utils';
|
|
4
4
|
|
|
5
5
|
import app from '@src/server';
|
|
6
6
|
|
|
7
7
|
import UserRepo from '@src/repos/UserRepo';
|
|
8
|
-
import User from '@src/models/User';
|
|
8
|
+
import User, { IUser } from '@src/models/User';
|
|
9
9
|
import { USER_NOT_FOUND_ERR } from '@src/services/UserService';
|
|
10
10
|
|
|
11
11
|
import HttpStatusCodes from '@src/common/HttpStatusCodes';
|
|
12
12
|
import { ValidationErr } from '@src/common/route-errors';
|
|
13
13
|
|
|
14
14
|
import Paths from 'spec/support/Paths';
|
|
15
|
-
import { cleanDatabase, IValidationErr,
|
|
15
|
+
import { cleanDatabase, IValidationErr, TRes } from 'spec/support';
|
|
16
|
+
import { isObject } from 'jet-validators';
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
/******************************************************************************
|
|
@@ -21,24 +22,34 @@ import { cleanDatabase, IValidationErr, parseResBody } from 'spec/support';
|
|
|
21
22
|
|
|
22
23
|
// Dummy users for GET req
|
|
23
24
|
const DB_USERS = [
|
|
24
|
-
User.new({
|
|
25
|
-
User.new({
|
|
26
|
-
User.new({
|
|
25
|
+
User.new({ name: 'Sean Maxwell', email: 'sean.maxwell@gmail.com' }),
|
|
26
|
+
User.new({ name: 'John Smith', email: 'john.smith@gmail.com' }),
|
|
27
|
+
User.new({ name: 'Gordan Freeman', email: 'gordan.freeman@gmail.com' }),
|
|
27
28
|
] as const;
|
|
28
29
|
|
|
30
|
+
// Don't compare "id" and "created" cause those are set dynamically by the
|
|
31
|
+
// database
|
|
32
|
+
const compareUserArrays = customDeepCompare({
|
|
33
|
+
onlyCompareProps: ['name', 'email'],
|
|
34
|
+
});
|
|
35
|
+
|
|
29
36
|
|
|
30
37
|
/******************************************************************************
|
|
31
38
|
Tests
|
|
39
|
+
IMPORTANT: Following TypeScript best practices, we test all scenarios that
|
|
40
|
+
can be triggered by a user under normal circumstances. Not all theoretically
|
|
41
|
+
scenarios (i.e. a failed database connection).
|
|
32
42
|
******************************************************************************/
|
|
33
43
|
|
|
34
44
|
describe('UserRouter', () => {
|
|
35
45
|
|
|
36
46
|
const agent = supertest.agent(app);
|
|
47
|
+
let dbUsers: IUser[] = [];
|
|
37
48
|
|
|
38
49
|
// Run before all tests
|
|
39
50
|
beforeEach(async () => {
|
|
40
51
|
await cleanDatabase();
|
|
41
|
-
await UserRepo.insertMult(DB_USERS);
|
|
52
|
+
dbUsers = await UserRepo.insertMult(DB_USERS);
|
|
42
53
|
});
|
|
43
54
|
|
|
44
55
|
// Get all users
|
|
@@ -49,10 +60,9 @@ describe('UserRouter', () => {
|
|
|
49
60
|
`of "${HttpStatusCodes.OK}" if the request was successful.`, done => {
|
|
50
61
|
agent
|
|
51
62
|
.get(Paths.Users.Get)
|
|
52
|
-
.end((_, res) => {
|
|
53
|
-
parseResBody(res.body);
|
|
63
|
+
.end((_, res: TRes<{ users: IUser[]}>) => {
|
|
54
64
|
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
55
|
-
expect(res.body
|
|
65
|
+
expect(compareUserArrays(res.body.users, DB_USERS)).toBeTruthy();
|
|
56
66
|
done();
|
|
57
67
|
});
|
|
58
68
|
});
|
|
@@ -81,11 +91,12 @@ describe('UserRouter', () => {
|
|
|
81
91
|
agent
|
|
82
92
|
.post(Paths.Users.Add)
|
|
83
93
|
.send({ user: null })
|
|
84
|
-
.end((_, res) => {
|
|
94
|
+
.end((_, res: TRes) => {
|
|
85
95
|
expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
|
|
86
|
-
const errorObj = parseJson<IValidationErr>(res.body.error)
|
|
96
|
+
const errorObj = parseJson<IValidationErr>(res.body.error),
|
|
97
|
+
parseErr = errorObj.parameters[0];
|
|
87
98
|
expect(errorObj.message).toBe(ValidationErr.MSG);
|
|
88
|
-
expect(
|
|
99
|
+
expect(isObject(parseErr) && parseErr.prop === 'user').toBeTruthy();
|
|
89
100
|
done();
|
|
90
101
|
});
|
|
91
102
|
});
|
|
@@ -115,11 +126,12 @@ describe('UserRouter', () => {
|
|
|
115
126
|
agent
|
|
116
127
|
.put(Paths.Users.Update)
|
|
117
128
|
.send({ user: null })
|
|
118
|
-
.end((_, res) => {
|
|
129
|
+
.end((_, res: TRes) => {
|
|
119
130
|
expect(res.status).toBe(HttpStatusCodes.BAD_REQUEST);
|
|
120
|
-
const errorObj = parseJson<IValidationErr>(res.body.error)
|
|
131
|
+
const errorObj = parseJson<IValidationErr>(res.body.error),
|
|
132
|
+
parseErr = errorObj.parameters[0];
|
|
121
133
|
expect(errorObj.message).toBe(ValidationErr.MSG);
|
|
122
|
-
expect(
|
|
134
|
+
expect(isObject(parseErr) && parseErr.prop === 'user').toBeTruthy();
|
|
123
135
|
done();
|
|
124
136
|
});
|
|
125
137
|
});
|
|
@@ -132,7 +144,7 @@ describe('UserRouter', () => {
|
|
|
132
144
|
agent
|
|
133
145
|
.put(Paths.Users.Update)
|
|
134
146
|
.send({ user })
|
|
135
|
-
.end((_, res) => {
|
|
147
|
+
.end((_, res: TRes) => {
|
|
136
148
|
expect(res.status).toBe(HttpStatusCodes.NOT_FOUND);
|
|
137
149
|
expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
|
|
138
150
|
done();
|
|
@@ -143,11 +155,14 @@ describe('UserRouter', () => {
|
|
|
143
155
|
// Delete User
|
|
144
156
|
describe(`"DELETE:${Paths.Users.Delete}"`, () => {
|
|
145
157
|
|
|
158
|
+
const getPath = (id: number) => insertUrlParams(Paths.Users.Delete, { id });
|
|
159
|
+
|
|
146
160
|
// Success
|
|
147
161
|
it(`should return a status code of "${HttpStatusCodes.OK}" if the ` +
|
|
148
162
|
'request was successful.', done => {
|
|
163
|
+
const id = dbUsers[0].id;
|
|
149
164
|
agent
|
|
150
|
-
.delete(
|
|
165
|
+
.delete(getPath(id))
|
|
151
166
|
.end((_, res) => {
|
|
152
167
|
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
153
168
|
done();
|
|
@@ -159,8 +174,8 @@ describe('UserRouter', () => {
|
|
|
159
174
|
`"${USER_NOT_FOUND_ERR}" and a status code of ` +
|
|
160
175
|
`"${HttpStatusCodes.NOT_FOUND}" if the id was not found.`, done => {
|
|
161
176
|
agent
|
|
162
|
-
.delete(
|
|
163
|
-
.end((_, res) => {
|
|
177
|
+
.delete(getPath(-1))
|
|
178
|
+
.end((_, res: TRes) => {
|
|
164
179
|
expect(res.status).toBe(HttpStatusCodes.NOT_FOUND);
|
|
165
180
|
expect(res.body.error).toBe(USER_NOT_FOUND_ERR);
|
|
166
181
|
done();
|
|
@@ -5,10 +5,17 @@ import { NodeEnvs } from './constants';
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
/******************************************************************************
|
|
8
|
-
|
|
8
|
+
Setup
|
|
9
9
|
******************************************************************************/
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const ENV = jetEnv({
|
|
12
12
|
NodeEnv: isEnumVal(NodeEnvs),
|
|
13
13
|
Port: num,
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/******************************************************************************
|
|
18
|
+
Export default
|
|
19
|
+
******************************************************************************/
|
|
20
|
+
|
|
21
|
+
export default ENV;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IUser } from '@src/models/User';
|
|
2
2
|
import { getRandomInt } from '@src/util/misc';
|
|
3
|
+
|
|
3
4
|
import orm from './MockOrm';
|
|
4
5
|
|
|
5
6
|
|
|
@@ -98,14 +99,21 @@ async function deleteAllUsers(): Promise<void> {
|
|
|
98
99
|
* Insert multiple users. Can't do multiple at once cause using a plain file
|
|
99
100
|
* for nmow.
|
|
100
101
|
*/
|
|
101
|
-
async function insertMult(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
async function insertMult(
|
|
103
|
+
users: IUser[] | readonly IUser[],
|
|
104
|
+
): Promise<IUser[]> {
|
|
105
|
+
const db = await orm.openDb(),
|
|
106
|
+
usersF = [ ...users ];
|
|
107
|
+
for (const user of usersF) {
|
|
108
|
+
user.id = getRandomInt();
|
|
109
|
+
user.created = new Date();
|
|
110
|
+
}
|
|
111
|
+
db.users = [ ...db.users, ...users ];
|
|
112
|
+
await orm.saveDb(db);
|
|
113
|
+
return usersF;
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
|
|
108
|
-
|
|
109
117
|
/******************************************************************************
|
|
110
118
|
Export default
|
|
111
119
|
******************************************************************************/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"users":[{"id":
|
|
1
|
+
{"users":[{"id":833335167307,"name":"Bill","created":"2025-02-21T15:41:21.593Z","email":"sean.maxwell@gmail.com"},{"id":721615734671,"name":"John Smith","created":"2025-02-21T15:41:21.593Z","email":"john.smith@gmail.com"},{"id":531901228132,"name":"Gordan Freeman","created":"2025-02-21T15:41:21.593Z","email":"gordan.freeman@gmail.com"},{"id":811228870992,"name":"a","created":"2025-02-21T15:41:21.594Z","email":"a@a.com"}]}
|
|
@@ -13,11 +13,11 @@ type TRecord = Record<string, unknown>;
|
|
|
13
13
|
export type IReq = Request<TRecord, void, TRecord, TRecord>;
|
|
14
14
|
export type IRes = Response<unknown, TRecord>;
|
|
15
15
|
|
|
16
|
-
export
|
|
17
|
-
prop: string
|
|
18
|
-
value: unknown
|
|
19
|
-
moreInfo?: string
|
|
20
|
-
}
|
|
16
|
+
export type TParseReqErr = {
|
|
17
|
+
prop: string,
|
|
18
|
+
value: unknown,
|
|
19
|
+
moreInfo?: string,
|
|
20
|
+
} | string;
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
/******************************************************************************
|
|
@@ -29,34 +29,50 @@ export interface IReqPropErr {
|
|
|
29
29
|
*/
|
|
30
30
|
export function parseReq<U extends TSchema>(schema: U) {
|
|
31
31
|
return (arg: unknown) => {
|
|
32
|
-
// Don't alter original object
|
|
32
|
+
// Don't alter original object (shallow copy is good enough)
|
|
33
33
|
if (isObject(arg)) {
|
|
34
34
|
arg = { ...arg };
|
|
35
35
|
}
|
|
36
|
-
//
|
|
37
|
-
const errArr:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
value?: unknown,
|
|
41
|
-
caughtErr?: unknown,
|
|
42
|
-
) => {
|
|
43
|
-
const err: IReqPropErr = { prop, value };
|
|
44
|
-
if (caughtErr !== undefined) {
|
|
45
|
-
let moreInfo;
|
|
46
|
-
if (!isString(caughtErr)) {
|
|
47
|
-
moreInfo = JSON.stringify(caughtErr);
|
|
48
|
-
} else {
|
|
49
|
-
moreInfo = caughtErr;
|
|
50
|
-
}
|
|
51
|
-
err.moreInfo = moreInfo;
|
|
52
|
-
}
|
|
53
|
-
errArr.push(err);
|
|
54
|
-
};
|
|
55
|
-
// Return
|
|
36
|
+
// Setup error callback
|
|
37
|
+
const errArr: TParseReqErr[] = [],
|
|
38
|
+
errCb = setupErrorCb(errArr);
|
|
39
|
+
// Run Tests
|
|
56
40
|
const retVal = parseObject<U>(schema, errCb)(arg);
|
|
57
41
|
if (errArr.length > 0) {
|
|
58
42
|
throw new ValidationErr(errArr);
|
|
59
43
|
}
|
|
44
|
+
// Return
|
|
60
45
|
return retVal;
|
|
61
46
|
};
|
|
62
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Setup the error callback function for when "parseReq" fires and error.
|
|
51
|
+
*/
|
|
52
|
+
function setupErrorCb(errArr: TParseReqErr[]) {
|
|
53
|
+
return function (
|
|
54
|
+
prop = 'undefined',
|
|
55
|
+
value?: unknown,
|
|
56
|
+
caughtErr?: unknown,
|
|
57
|
+
) {
|
|
58
|
+
// Initialize err
|
|
59
|
+
let err: TParseReqErr;
|
|
60
|
+
if (arguments.length === 1) {
|
|
61
|
+
err = prop;
|
|
62
|
+
} else {
|
|
63
|
+
err = { prop, value };
|
|
64
|
+
}
|
|
65
|
+
// Check if there's a "caught error"
|
|
66
|
+
if (isObject(err) && caughtErr !== undefined) {
|
|
67
|
+
let moreInfo;
|
|
68
|
+
if (!isString(caughtErr)) {
|
|
69
|
+
moreInfo = JSON.stringify(caughtErr);
|
|
70
|
+
} else {
|
|
71
|
+
moreInfo = caughtErr;
|
|
72
|
+
}
|
|
73
|
+
err.moreInfo = moreInfo;
|
|
74
|
+
}
|
|
75
|
+
// Add error to array
|
|
76
|
+
errArr.push(err);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -8,7 +8,7 @@ import 'express-async-errors';
|
|
|
8
8
|
|
|
9
9
|
import BaseRouter from '@src/routes';
|
|
10
10
|
|
|
11
|
-
import Paths from '@src/
|
|
11
|
+
import Paths from '@src/common/Paths';
|
|
12
12
|
import ENV from '@src/common/ENV';
|
|
13
13
|
import HttpStatusCodes from '@src/common/HttpStatusCodes';
|
|
14
14
|
import { RouteError } from '@src/common/route-errors';
|
|
@@ -16,25 +16,25 @@ import { NodeEnvs } from '@src/common/constants';
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
/******************************************************************************
|
|
19
|
-
|
|
19
|
+
Setup
|
|
20
20
|
******************************************************************************/
|
|
21
21
|
|
|
22
22
|
const app = express();
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
// ****
|
|
25
|
+
// **** Middleware **** //
|
|
26
26
|
|
|
27
27
|
// Basic middleware
|
|
28
28
|
app.use(express.json());
|
|
29
29
|
app.use(express.urlencoded({extended: true}));
|
|
30
30
|
|
|
31
31
|
// Show routes called in console during development
|
|
32
|
-
if (ENV.NodeEnv === NodeEnvs.Dev
|
|
32
|
+
if (ENV.NodeEnv === NodeEnvs.Dev) {
|
|
33
33
|
app.use(morgan('dev'));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Security
|
|
37
|
-
if (ENV.NodeEnv === NodeEnvs.Production
|
|
37
|
+
if (ENV.NodeEnv === NodeEnvs.Production) {
|
|
38
38
|
app.use(helmet());
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ app.use((err: Error, _: Request, res: Response, next: NextFunction) => {
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
// ****
|
|
58
|
+
// **** FrontEnd Content **** //
|
|
59
59
|
|
|
60
60
|
// Set views directory (html)
|
|
61
61
|
const viewsDir = path.join(__dirname, 'views');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.16",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"express-generator-typescript": "node bin/cli.js",
|
|
File without changes
|