express-generator-typescript 2.4.7 → 2.4.8
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/express-generator-typescript.js +1 -1
- package/lib/project-files/package.json +2 -1
- package/lib/project-files/spec/support/apiCb.ts +76 -0
- package/lib/project-files/spec/tests/users.spec.ts +98 -135
- package/lib/project-files/spec/types/misc.ts +18 -0
- package/lib/project-files/src/models/User.ts +13 -27
- package/lib/project-files/src/public/scripts/http.js +3 -1
- package/lib/project-files/src/public/scripts/users.js +31 -65
- package/lib/project-files/src/public/stylesheets/users.css +4 -6
- package/lib/project-files/src/repos/UserRepo.ts +6 -1
- package/lib/project-files/src/repos/database.json +22 -1
- package/lib/project-files/src/routes/types/express/misc.ts +1 -5
- package/lib/project-files/src/routes/types/types.ts +0 -8
- package/lib/project-files/src/views/users.html +100 -5
- package/lib/project-files-auth/package.json +2 -1
- package/lib/project-files-auth/spec/support/apiCb.ts +76 -0
- package/lib/project-files-auth/spec/support/login.ts +3 -6
- package/lib/project-files-auth/spec/tests/auth.spec.ts +50 -67
- package/lib/project-files-auth/spec/tests/users.spec.ts +98 -142
- package/lib/project-files-auth/spec/types/misc.ts +18 -0
- package/lib/project-files-auth/src/models/User.ts +17 -12
- package/lib/project-files-auth/src/public/scripts/http.js +3 -1
- package/lib/project-files-auth/src/public/scripts/login.js +1 -3
- package/lib/project-files-auth/src/public/scripts/users.js +31 -63
- package/lib/project-files-auth/src/public/stylesheets/users.css +4 -11
- package/lib/project-files-auth/src/repos/UserRepo.ts +6 -1
- package/lib/project-files-auth/src/repos/database.json +1 -1
- package/lib/project-files-auth/src/services/UserService.ts +2 -8
- package/lib/project-files-auth/src/views/login.html +26 -3
- package/lib/project-files-auth/src/views/users.html +106 -8
- package/package.json +1 -1
- package/lib/project-files/spec/support/types.ts +0 -1
- package/lib/project-files-auth/spec/support/types.ts +0 -1
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
//
|
|
1
|
+
// **** Variables **** //
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const DateFormatter = new Intl.DateTimeFormat('en-US', {
|
|
4
|
+
year: 'numeric',
|
|
5
|
+
month: '2-digit',
|
|
6
|
+
day: '2-digit',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const formatDate = (date) => DateFormatter.format(new Date(date));
|
|
4
10
|
|
|
5
11
|
|
|
6
|
-
//
|
|
12
|
+
// **** Run **** //
|
|
13
|
+
|
|
14
|
+
// Start
|
|
15
|
+
displayUsers();
|
|
7
16
|
|
|
8
17
|
/**
|
|
9
18
|
* Call api
|
|
@@ -12,59 +21,22 @@ function displayUsers() {
|
|
|
12
21
|
Http
|
|
13
22
|
.get('/api/users/all')
|
|
14
23
|
.then(resp => resp.json())
|
|
15
|
-
.then(
|
|
16
|
-
var
|
|
17
|
-
|
|
24
|
+
.then(resp => {
|
|
25
|
+
var allUsersTemplate = document.getElementById('all-users-template'),
|
|
26
|
+
allUsersTemplateHtml = allUsersTemplate.innerHTML,
|
|
27
|
+
template = Handlebars.compile(allUsersTemplateHtml);
|
|
18
28
|
var allUsersAnchor = document.getElementById('all-users-anchor');
|
|
19
|
-
allUsersAnchor.innerHTML =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
allUsersAnchor.innerHTML = template({
|
|
30
|
+
users: resp.users.map(user => ({
|
|
31
|
+
...user,
|
|
32
|
+
createdFormatted: formatDate(user.created),
|
|
33
|
+
})),
|
|
23
34
|
});
|
|
24
35
|
});
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
/**
|
|
28
|
-
* Get user display element
|
|
29
|
-
*/
|
|
30
|
-
function getUserDisplayEle(user) {
|
|
31
|
-
return (
|
|
32
|
-
`<div class="user-display-ele">
|
|
33
|
-
|
|
34
|
-
<div class="normal-view">
|
|
35
|
-
<div>Name: ${user.name}</div>
|
|
36
|
-
<div>Email: ${user.email}</div>
|
|
37
|
-
<button class="edit-user-btn" data-user-id="${user.id}" data-user-role="${user.role}">
|
|
38
|
-
Edit
|
|
39
|
-
</button>
|
|
40
|
-
<button class="delete-user-btn" data-user-id="${user.id}">
|
|
41
|
-
Delete
|
|
42
|
-
</button>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<div class="edit-view">
|
|
46
|
-
<div>
|
|
47
|
-
Name: <input class="name-edit-input" value="${user.name}">
|
|
48
|
-
</div>
|
|
49
|
-
<div>
|
|
50
|
-
Email: <input class="email-edit-input" value="${user.email}">
|
|
51
|
-
</div>
|
|
52
|
-
<button class="submit-edit-btn" data-user-id="${user.id}">
|
|
53
|
-
Submit
|
|
54
|
-
</button>
|
|
55
|
-
<button class="cancel-edit-btn" data-user-id="${user.id}">
|
|
56
|
-
Cancel
|
|
57
|
-
</button>
|
|
58
|
-
</div>
|
|
59
|
-
</div>`
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// **** Add, Edit, and Delete Users **** //
|
|
65
|
-
|
|
66
38
|
// Setup event listener for button click
|
|
67
|
-
document.addEventListener('click',
|
|
39
|
+
document.addEventListener('click', event => {
|
|
68
40
|
event.preventDefault();
|
|
69
41
|
var ele = event.target;
|
|
70
42
|
if (ele.matches('#add-user-btn')) {
|
|
@@ -77,8 +49,6 @@ document.addEventListener('click', function (event) {
|
|
|
77
49
|
submitEdit(ele);
|
|
78
50
|
} else if (ele.matches('.delete-user-btn')) {
|
|
79
51
|
deleteUser(ele);
|
|
80
|
-
} else if (ele.matches('#logout-btn')) {
|
|
81
|
-
logoutUser();
|
|
82
52
|
}
|
|
83
53
|
}, false);
|
|
84
54
|
|
|
@@ -93,13 +63,17 @@ function addUser() {
|
|
|
93
63
|
id: -1,
|
|
94
64
|
name: nameInput.value,
|
|
95
65
|
email: emailInput.value,
|
|
96
|
-
|
|
66
|
+
created: new Date(),
|
|
97
67
|
},
|
|
98
68
|
};
|
|
99
69
|
// Call api
|
|
100
70
|
Http
|
|
101
71
|
.post('/api/users/add', data)
|
|
102
|
-
.then(() =>
|
|
72
|
+
.then(() => {
|
|
73
|
+
nameInput.value = '';
|
|
74
|
+
emailInput.value = '';
|
|
75
|
+
displayUsers();
|
|
76
|
+
});
|
|
103
77
|
}
|
|
104
78
|
|
|
105
79
|
/**
|
|
@@ -130,13 +104,14 @@ function submitEdit(ele) {
|
|
|
130
104
|
var nameInput = userEle.getElementsByClassName('name-edit-input')[0];
|
|
131
105
|
var emailInput = userEle.getElementsByClassName('email-edit-input')[0];
|
|
132
106
|
var id = ele.getAttribute('data-user-id');
|
|
133
|
-
var
|
|
107
|
+
var created = ele.getAttribute('data-user-created');
|
|
108
|
+
console.log(ele, created)
|
|
134
109
|
var data = {
|
|
135
110
|
user: {
|
|
136
111
|
id: Number(id),
|
|
137
112
|
name: nameInput.value,
|
|
138
113
|
email: emailInput.value,
|
|
139
|
-
|
|
114
|
+
created: new Date(created),
|
|
140
115
|
},
|
|
141
116
|
};
|
|
142
117
|
Http
|
|
@@ -153,12 +128,3 @@ function deleteUser(ele) {
|
|
|
153
128
|
.delete('/api/users/delete/' + id)
|
|
154
129
|
.then(() => displayUsers());
|
|
155
130
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// **** Logout **** //
|
|
159
|
-
|
|
160
|
-
function logoutUser() {
|
|
161
|
-
Http
|
|
162
|
-
.get('/api/auth/logout')
|
|
163
|
-
.then(() => window.location.href = '/');
|
|
164
|
-
}
|
|
@@ -27,15 +27,13 @@ body .add-user-col #add-user-btn {
|
|
|
27
27
|
margin-bottom: 10px;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
body .add-user-col #logout-btn {
|
|
31
|
-
margin-top: 25px;
|
|
32
|
-
margin-bottom: 10px;
|
|
33
|
-
font-size: 15px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
30
|
|
|
37
31
|
/** Users Display Column **/
|
|
38
32
|
|
|
33
|
+
.email-edit {
|
|
34
|
+
padding-bottom: 8px;
|
|
35
|
+
}
|
|
36
|
+
|
|
39
37
|
body .users-column .user-display-ele {
|
|
40
38
|
padding-bottom: 10px;
|
|
41
39
|
}
|
|
@@ -56,7 +56,12 @@ async function update(user: IUser): Promise<void> {
|
|
|
56
56
|
const db = await orm.openDb();
|
|
57
57
|
for (let i = 0; i < db.users.length; i++) {
|
|
58
58
|
if (db.users[i].id === user.id) {
|
|
59
|
-
db.users[i]
|
|
59
|
+
const dbUser = db.users[i];
|
|
60
|
+
db.users[i] = {
|
|
61
|
+
...dbUser,
|
|
62
|
+
name: user.name,
|
|
63
|
+
email: user.email,
|
|
64
|
+
};
|
|
60
65
|
return orm.saveDb(db);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
@@ -1 +1,22 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"users": [
|
|
3
|
+
{
|
|
4
|
+
"id": 366115170645,
|
|
5
|
+
"name": "Sean Maxwell",
|
|
6
|
+
"email": "smaxwell@example.com",
|
|
7
|
+
"created": "2024-03-22T05:14:36.252Z"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": 310946254456,
|
|
11
|
+
"name": "John Smith",
|
|
12
|
+
"email": "john.smith@example.com",
|
|
13
|
+
"created": "2024-03-22T05:20:55.079Z"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": 143027113460,
|
|
17
|
+
"name": "Gordan Freeman",
|
|
18
|
+
"email": "nova@prospect.com",
|
|
19
|
+
"created": "2024-03-22T05:42:18.895Z"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as e from 'express';
|
|
2
2
|
|
|
3
|
-
import { ISessionUser } from '@src/models/User';
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
// **** Express **** //
|
|
7
5
|
|
|
@@ -10,7 +8,5 @@ export interface IReq<T = void> extends e.Request {
|
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
export interface IRes extends e.Response {
|
|
13
|
-
locals:
|
|
14
|
-
sessionUser?: ISessionUser;
|
|
15
|
-
};
|
|
11
|
+
locals: Record<string, unknown>;
|
|
16
12
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as e from 'express';
|
|
2
2
|
import { Query } from 'express-serve-static-core';
|
|
3
3
|
|
|
4
|
-
import { ISessionUser } from '@src/models/User';
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
// **** Express **** //
|
|
8
6
|
|
|
@@ -14,9 +12,3 @@ export interface IReqQuery<T extends Query, U = void> extends e.Request {
|
|
|
14
12
|
query: T;
|
|
15
13
|
body: U;
|
|
16
14
|
}
|
|
17
|
-
|
|
18
|
-
export interface IRes extends e.Response {
|
|
19
|
-
locals: {
|
|
20
|
-
sessionUser: ISessionUser;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<!-- Head -->
|
|
3
5
|
<head>
|
|
4
6
|
<meta charset="UTF-8">
|
|
5
7
|
<title>Users</title>
|
|
6
8
|
<link rel="stylesheet" type="text/css" href="/stylesheets/users.css" />
|
|
9
|
+
|
|
10
|
+
<!-- Handlebars -->
|
|
11
|
+
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js"></script>
|
|
12
|
+
|
|
13
|
+
<!-- Bootstrap -->
|
|
14
|
+
<link
|
|
15
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
|
|
16
|
+
rel="stylesheet"
|
|
17
|
+
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
|
18
|
+
crossorigin="anonymous"
|
|
19
|
+
/>
|
|
20
|
+
<script
|
|
21
|
+
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
|
22
|
+
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
|
23
|
+
crossorigin="anonymous"
|
|
24
|
+
></script>
|
|
7
25
|
</head>
|
|
26
|
+
|
|
27
|
+
<!-- Body -->
|
|
8
28
|
<body>
|
|
9
29
|
|
|
10
30
|
<!-- Add Users -->
|
|
@@ -13,13 +33,26 @@
|
|
|
13
33
|
Add User:
|
|
14
34
|
</div>
|
|
15
35
|
<div>
|
|
16
|
-
<input
|
|
36
|
+
<input
|
|
37
|
+
id="name-input"
|
|
38
|
+
type="text"
|
|
39
|
+
class="form-control"
|
|
40
|
+
placeholder="Name"
|
|
41
|
+
/>
|
|
17
42
|
</div>
|
|
18
43
|
<div>
|
|
19
|
-
<input
|
|
44
|
+
<input
|
|
45
|
+
id="email-input"
|
|
46
|
+
type="email"
|
|
47
|
+
class="form-control"
|
|
48
|
+
placeholder="Email"
|
|
49
|
+
/>
|
|
20
50
|
</div>
|
|
21
51
|
<div>
|
|
22
|
-
<button
|
|
52
|
+
<button
|
|
53
|
+
id="add-user-btn"
|
|
54
|
+
class="btn btn-success"
|
|
55
|
+
>
|
|
23
56
|
Add
|
|
24
57
|
</button>
|
|
25
58
|
</div>
|
|
@@ -27,11 +60,73 @@
|
|
|
27
60
|
|
|
28
61
|
<!-- Display Users -->
|
|
29
62
|
<div class="users-column">
|
|
30
|
-
<div class="column-header">
|
|
63
|
+
<div class="column-header">
|
|
64
|
+
Users:
|
|
65
|
+
</div>
|
|
66
|
+
|
|
31
67
|
<div id="all-users-anchor"></div>
|
|
32
|
-
|
|
68
|
+
<script
|
|
69
|
+
id="all-users-template"
|
|
70
|
+
type="text/x-handlebars-template"
|
|
71
|
+
>
|
|
72
|
+
{{#each users}}
|
|
73
|
+
<div class="user-display-ele">
|
|
74
|
+
<div class="normal-view">
|
|
75
|
+
<div><b>Name:</b> {{name}}</div>
|
|
76
|
+
<div><b>Email:</b> {{email}}</div>
|
|
77
|
+
<div><b>Created:</b> {{createdFormatted}}</div>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
class="btn btn-primary edit-user-btn"
|
|
81
|
+
data-user-id="{{id}}"
|
|
82
|
+
>
|
|
83
|
+
Edit
|
|
84
|
+
</button>
|
|
85
|
+
<button
|
|
86
|
+
class="btn btn-danger delete-user-btn"
|
|
87
|
+
data-user-id="{{id}}"
|
|
88
|
+
>
|
|
89
|
+
Delete
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
33
92
|
|
|
93
|
+
<div class="edit-view">
|
|
94
|
+
<div>
|
|
95
|
+
Name:
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
class="form-control name-edit-input"
|
|
99
|
+
value="{{name}}"
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="email-edit">
|
|
103
|
+
Email:
|
|
104
|
+
<input
|
|
105
|
+
type="email"
|
|
106
|
+
class="form-control email-edit-input"
|
|
107
|
+
value="{{email}}"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
<button
|
|
111
|
+
class="btn btn-primary submit-edit-btn"
|
|
112
|
+
data-user-id="{{id}}"
|
|
113
|
+
data-user-created="{{created}}"
|
|
114
|
+
>
|
|
115
|
+
Submit
|
|
116
|
+
</button>
|
|
117
|
+
<button
|
|
118
|
+
class="btn btn-secondary cancel-edit-btn"
|
|
119
|
+
data-user-id="{{id}}"
|
|
120
|
+
>
|
|
121
|
+
Cancel
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
{{/each}}
|
|
126
|
+
</script>
|
|
127
|
+
</div>
|
|
34
128
|
</body>
|
|
129
|
+
|
|
35
130
|
<script src="scripts/http.js"></script>
|
|
36
131
|
<script src="scripts/users.js"></script>
|
|
37
132
|
</html>
|
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
"helmet": "^6.0.0",
|
|
36
36
|
"inserturlparams": "^1.0.1",
|
|
37
37
|
"jet-logger": "^1.3.1",
|
|
38
|
-
"jet-validator": "^1.0.3",
|
|
39
38
|
"jet-paths": "^1.0.5",
|
|
39
|
+
"jet-validator": "^1.0.3",
|
|
40
40
|
"jsonfile": "^6.1.0",
|
|
41
41
|
"jsonwebtoken": "^8.5.1",
|
|
42
42
|
"module-alias": "^2.2.2",
|
|
43
|
+
"moment": "^2.30.1",
|
|
43
44
|
"morgan": "^1.10.0",
|
|
44
45
|
"ts-command-line-args": "^2.4.2"
|
|
45
46
|
},
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CallbackHandler } from 'supertest';
|
|
2
|
+
import moment from 'moment';
|
|
3
|
+
import logger from 'jet-logger';
|
|
4
|
+
|
|
5
|
+
import { TApiCb, TRes } from 'spec/types/misc';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* API callback function.
|
|
10
|
+
*/
|
|
11
|
+
function apiCb(
|
|
12
|
+
cb: TApiCb,
|
|
13
|
+
dateParam = 'created',
|
|
14
|
+
printErr?: boolean,
|
|
15
|
+
): CallbackHandler {
|
|
16
|
+
return (err: Error, res: TRes) => {
|
|
17
|
+
if (printErr) {
|
|
18
|
+
logger.err(err);
|
|
19
|
+
}
|
|
20
|
+
_strToDate(res.body, dateParam);
|
|
21
|
+
return cb(res);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* When date objects get sent through supertest they are converted to
|
|
27
|
+
* iso-strings. This will cause "toEqual()"" tests to fail, so we need to
|
|
28
|
+
* convert them back to Date objects.
|
|
29
|
+
*/
|
|
30
|
+
function _strToDate(
|
|
31
|
+
param: unknown,
|
|
32
|
+
prop: string,
|
|
33
|
+
): void {
|
|
34
|
+
return _iterate(param, prop);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Interate object recursively and convert string-dates to "Date" objects.
|
|
39
|
+
*/
|
|
40
|
+
function _iterate(param: unknown, prop: string): void {
|
|
41
|
+
if (!param || typeof param !== 'object') {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const paramF = param as Record<string, unknown>;
|
|
45
|
+
// For Arrays
|
|
46
|
+
if (Array.isArray(paramF)) {
|
|
47
|
+
for (const item of paramF) {
|
|
48
|
+
_iterate(item, prop);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Check valid string or Date object. If undefined just skip
|
|
53
|
+
const val = paramF[prop];
|
|
54
|
+
if (
|
|
55
|
+
(typeof val !== 'undefined') &&
|
|
56
|
+
!((typeof val === 'string') || (val instanceof Date)) &&
|
|
57
|
+
!moment(val as string | Date).isValid()
|
|
58
|
+
) {
|
|
59
|
+
throw new Error('Property must be a valid date-string or Date() object');
|
|
60
|
+
}
|
|
61
|
+
// Convert and iterate
|
|
62
|
+
if (typeof val !== 'undefined') {
|
|
63
|
+
paramF[prop] = new Date(val as string | Date);
|
|
64
|
+
}
|
|
65
|
+
for (const key in paramF) {
|
|
66
|
+
const oval = paramF[key];
|
|
67
|
+
if (typeof oval === 'object' && key !== prop) {
|
|
68
|
+
_iterate(oval, prop);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// **** Export default **** //
|
|
75
|
+
|
|
76
|
+
export default apiCb;
|
|
@@ -8,16 +8,12 @@ import PwdUtil from '@src/util/PwdUtil';
|
|
|
8
8
|
import Paths from './Paths';
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// Login Credentials
|
|
13
12
|
const LoginCreds = {
|
|
14
13
|
email: 'jsmith@gmail.com',
|
|
15
14
|
password: 'Password@1',
|
|
16
15
|
} as const;
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
// **** Functions **** //
|
|
20
|
-
|
|
21
17
|
/**
|
|
22
18
|
* Login a user.
|
|
23
19
|
*/
|
|
@@ -25,7 +21,8 @@ function login(beforeAgent: TestAgent<Test>, done: (arg: string) => void) {
|
|
|
25
21
|
// Setup dummy data
|
|
26
22
|
const role = UserRoles.Admin,
|
|
27
23
|
pwdHash = PwdUtil.hashSync(LoginCreds.password),
|
|
28
|
-
loginUser = User.new('john smith', LoginCreds.email, role,
|
|
24
|
+
loginUser = User.new('john smith', LoginCreds.email, new Date(), role,
|
|
25
|
+
pwdHash);
|
|
29
26
|
// Add spy
|
|
30
27
|
spyOn(UserRepo, 'getOne').and.resolveTo(loginUser);
|
|
31
28
|
// Call Login API
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import supertest, { Test, Response } from 'supertest';
|
|
2
|
+
import TestAgent from 'supertest/lib/agent';
|
|
2
3
|
|
|
3
4
|
import app from '@src/server';
|
|
4
5
|
|
|
@@ -11,36 +12,18 @@ import EnvVars from '@src/constants/EnvVars';
|
|
|
11
12
|
import HttpStatusCodes from '@src/constants/HttpStatusCodes';
|
|
12
13
|
|
|
13
14
|
import Paths from 'spec/support/Paths';
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
|
|
15
|
+
import apiCb from 'spec/support/apiCb';
|
|
16
|
+
import { TApiCb } from 'spec/types/misc';
|
|
17
17
|
|
|
18
|
-
// **** Variables **** //
|
|
19
18
|
|
|
20
|
-
//
|
|
21
|
-
const {
|
|
22
|
-
OK,
|
|
23
|
-
UNAUTHORIZED,
|
|
24
|
-
} = HttpStatusCodes;
|
|
25
|
-
|
|
26
|
-
// Login credentials
|
|
19
|
+
// Login Creds
|
|
27
20
|
const LoginCreds = {
|
|
28
21
|
email: 'jsmith@gmail.com',
|
|
29
22
|
password: 'Password@1',
|
|
30
23
|
} as const;
|
|
31
24
|
|
|
32
25
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
type TRes = Omit<Response, 'body'> & {
|
|
36
|
-
body: {
|
|
37
|
-
error: string;
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// **** Tests **** //
|
|
43
|
-
|
|
26
|
+
// Tests
|
|
44
27
|
describe('AuthRouter', () => {
|
|
45
28
|
|
|
46
29
|
let agent: TestAgent<Test>;
|
|
@@ -51,82 +34,82 @@ describe('AuthRouter', () => {
|
|
|
51
34
|
done();
|
|
52
35
|
});
|
|
53
36
|
|
|
54
|
-
//
|
|
37
|
+
// Login
|
|
55
38
|
describe(`"POST:${Paths.Auth.Login}"`, () => {
|
|
56
39
|
|
|
57
40
|
const EMAIL_NOT_FOUND_ERR = Errors.EmailNotFound(LoginCreds.email);
|
|
58
41
|
|
|
59
|
-
|
|
42
|
+
// Setup API
|
|
43
|
+
const callApi = (creds: typeof LoginCreds, cb: TApiCb) =>
|
|
60
44
|
agent
|
|
61
45
|
.post(Paths.Auth.Login)
|
|
62
|
-
.
|
|
63
|
-
.
|
|
46
|
+
.send(creds)
|
|
47
|
+
.end(apiCb(cb));
|
|
64
48
|
|
|
65
49
|
// Success
|
|
66
|
-
it(`should return a response with a status of "${OK}"
|
|
67
|
-
'a jwt if the login was successful.',
|
|
50
|
+
it(`should return a response with a status of "${HttpStatusCodes.OK}" ` +
|
|
51
|
+
'and a cookie with a jwt if the login was successful.', done => {
|
|
68
52
|
// Setup data
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
const pwdHash = PwdUtil.hashSync(LoginCreds.password),
|
|
54
|
+
loginUser = User.new('john smith', LoginCreds.email, new Date(),
|
|
55
|
+
UserRoles.Standard, pwdHash);
|
|
72
56
|
// Add spy
|
|
73
57
|
spyOn(UserRepo, 'getOne').and.resolveTo(loginUser);
|
|
74
58
|
// Call API
|
|
75
|
-
callApi(LoginCreds
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
59
|
+
callApi(LoginCreds, res => {
|
|
60
|
+
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
61
|
+
const cookie = res.headers['set-cookie'][0];
|
|
62
|
+
expect(cookie).toContain(EnvVars.CookieProps.Key);
|
|
63
|
+
done();
|
|
64
|
+
});
|
|
82
65
|
});
|
|
83
66
|
|
|
84
67
|
// Email not found error
|
|
85
|
-
it(
|
|
86
|
-
`json with an error message of
|
|
87
|
-
|
|
68
|
+
it('should return a response with a status of ' +
|
|
69
|
+
`"${HttpStatusCodes.UNAUTHORIZED}" and a json with an error message of ` +
|
|
70
|
+
`"${EMAIL_NOT_FOUND_ERR}" if the email was not found.`, done => {
|
|
88
71
|
// Spy
|
|
89
72
|
spyOn(UserRepo, 'getOne').and.resolveTo(null);
|
|
90
73
|
// Call
|
|
91
|
-
callApi(LoginCreds
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
74
|
+
callApi(LoginCreds, res => {
|
|
75
|
+
expect(res.status).toBe(HttpStatusCodes.UNAUTHORIZED);
|
|
76
|
+
expect(res.body.error).toBe(EMAIL_NOT_FOUND_ERR);
|
|
77
|
+
done();
|
|
78
|
+
});
|
|
97
79
|
});
|
|
98
80
|
|
|
99
81
|
// Password failed
|
|
100
|
-
it(
|
|
101
|
-
`
|
|
102
|
-
|
|
82
|
+
it('should return a response with a status of ' +
|
|
83
|
+
`"${HttpStatusCodes.UNAUTHORIZED}" and a json with the error ` +
|
|
84
|
+
`"${Errors.Unauth}" if the password failed.`, done => {
|
|
103
85
|
// Setup data
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
86
|
+
const pwdHash = PwdUtil.hashSync('bad password'),
|
|
87
|
+
loginUser = User.new('john smith', LoginCreds.email, new Date(),
|
|
88
|
+
UserRoles.Standard, pwdHash);
|
|
107
89
|
// Add spy
|
|
108
90
|
spyOn(UserRepo, 'getOne').and.resolveTo(loginUser);
|
|
109
91
|
// Call API
|
|
110
|
-
callApi(LoginCreds
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
92
|
+
callApi(LoginCreds, res => {
|
|
93
|
+
expect(res.status).toBe(HttpStatusCodes.UNAUTHORIZED);
|
|
94
|
+
expect(res.body.error).toBe(Errors.Unauth);
|
|
95
|
+
done();
|
|
96
|
+
});
|
|
116
97
|
});
|
|
117
98
|
|
|
118
99
|
});
|
|
119
100
|
|
|
120
|
-
//
|
|
101
|
+
// Test logout
|
|
121
102
|
describe(`"GET:${Paths.Auth.Logout}"`, () => {
|
|
122
103
|
|
|
123
104
|
// Successful logout
|
|
124
|
-
it(`should return a response with a status of ${OK}`,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
105
|
+
it(`should return a response with a status of ${HttpStatusCodes.OK}`,
|
|
106
|
+
done => {
|
|
107
|
+
agent
|
|
108
|
+
.get(Paths.Auth.Logout)
|
|
109
|
+
.end((_: Error, res: Response) => {
|
|
110
|
+
expect(res.status).toBe(HttpStatusCodes.OK);
|
|
111
|
+
done();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
131
114
|
});
|
|
132
115
|
});
|