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.
@@ -27,7 +27,7 @@
27
27
  "jet-logger": "^2.0.1",
28
28
  "jet-paths": "^1.1.0",
29
29
  "jet-schema": "^1.4.2",
30
- "jet-validators": "^1.0.9",
30
+ "jet-validators": "^1.1.0",
31
31
  "jsonfile": "^6.1.0",
32
32
  "module-alias": "^2.2.2",
33
33
  "morgan": "^1.10.0"
@@ -1,5 +1,5 @@
1
1
  import jetPaths from 'jet-paths';
2
- import Paths from '@src/routes/common/Paths';
2
+ import Paths from '@src/common/Paths';
3
3
 
4
4
 
5
5
  export default jetPaths(Paths);
@@ -1,7 +1,5 @@
1
- import { isValidDate } from 'jet-validators';
2
- import { traverseObject } from 'jet-validators/utils';
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: IReqPropErr[];
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, parseResBody } from 'spec/support';
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({ id: 1, name: 'Sean Maxwell', email: 'sean.maxwell@gmail.com' }),
25
- User.new({ id: 2, name: 'John Smith', email: 'john.smith@gmail.com' }),
26
- User.new({ id: 3, name: 'Gordan Freeman', email: 'gordan.freeman@gmail.com' }),
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).toEqual({ users: DB_USERS });
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(errorObj.parameters[0].prop).toBe('user');
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(errorObj.parameters[0].prop).toBe('user');
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(insertUrlParams(Paths.Users.Delete, { id: 3 }))
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(insertUrlParams(Paths.Users.Delete, { id: -1 }))
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
- Export default
8
+ Setup
9
9
  ******************************************************************************/
10
10
 
11
- export default jetEnv({
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;
@@ -3,8 +3,9 @@
3
3
  Enums
4
4
  ******************************************************************************/
5
5
 
6
+ // NOTE: These need to match the names of your ".env" files
6
7
  export enum NodeEnvs {
7
8
  Dev = 'development',
8
9
  Test = 'test',
9
10
  Production = 'production'
10
- }
11
+ }
@@ -11,8 +11,8 @@ import { IUser } from '@src/models/User';
11
11
 
12
12
  const DB_FILE_NAME = (
13
13
  ENV.NodeEnv === NodeEnvs.Test
14
- ? 'database.test.json'
15
- : 'database.json'
14
+ ? 'database.test.json'
15
+ : 'database.json'
16
16
  );
17
17
 
18
18
 
@@ -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(users: IUser[] | readonly IUser[]): Promise<void> {
102
- const db = await orm.openDb();
103
- db.users = [ ...db.users, ...users];
104
- return orm.saveDb(db);
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,"name":"Bill","created":"2025-02-10T05:36:18.360Z","email":"sean.maxwell@gmail.com"},{"id":2,"name":"John Smith","created":"2025-02-10T05:36:18.360Z","email":"john.smith@gmail.com"},{"id":3,"name":"Gordan Freeman","created":"2025-02-10T05:36:18.360Z","email":"gordan.freeman@gmail.com"}]}
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"}]}
@@ -5,7 +5,6 @@ import HttpStatusCodes from '@src/common/HttpStatusCodes';
5
5
  import UserService from '@src/services/UserService';
6
6
  import User from '@src/models/User';
7
7
 
8
-
9
8
  import { parseReq, IReq, IRes } from './common';
10
9
 
11
10
 
@@ -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 interface IReqPropErr {
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
- // Error callback
37
- const errArr: IReqPropErr[] = [];
38
- const errCb = (
39
- prop = 'undefined',
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
+ }
@@ -1,6 +1,6 @@
1
1
  import { Router } from 'express';
2
2
 
3
- import Paths from './common/Paths';
3
+ import Paths from '../common/Paths';
4
4
  import UserRoutes from './UserRoutes';
5
5
 
6
6
 
@@ -8,7 +8,7 @@ import 'express-async-errors';
8
8
 
9
9
  import BaseRouter from '@src/routes';
10
10
 
11
- import Paths from '@src/routes/common/Paths';
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
- Variables
19
+ Setup
20
20
  ******************************************************************************/
21
21
 
22
22
  const app = express();
23
23
 
24
24
 
25
- // **** Setup
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.valueOf()) {
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.valueOf()) {
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
- // **** Front-End Content
58
+ // **** FrontEnd Content **** //
59
59
 
60
60
  // Set views directory (html)
61
61
  const viewsDir = path.join(__dirname, 'views');
@@ -1,3 +1,4 @@
1
+
1
2
  /**
2
3
  * Get a random number between 1 and 1,000,000,000,000
3
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-generator-typescript",
3
- "version": "2.5.14",
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",