Skip to content

Commit 357b27d

Browse files
committed
Add route tests
1 parent 705f1a4 commit 357b27d

File tree

8 files changed

+1135
-3
lines changed

8 files changed

+1135
-3
lines changed

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"scripts": {
99
"start": "node ./bin/www",
1010
"devstart": "nodemon ./bin/www",
11-
"serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart"
11+
"serverstart": "DEBUG=express-locallibrary-tutorial:* npm run devstart",
12+
"test": "mocha test/**/*.test.js"
1213
},
1314
"dependencies": {
1415
"compression": "^1.7.4",
@@ -21,11 +22,15 @@
2122
"helmet": "^7.1.0",
2223
"http-errors": "^2.0.0",
2324
"luxon": "^3.4.4",
24-
"mongoose": "^8.9.5",
25+
"mongoose": "^8.16.1",
2526
"morgan": "^1.10.0",
2627
"pug": "^3.0.3"
2728
},
2829
"devDependencies": {
29-
"nodemon": "^3.1.3"
30+
"chai": "^5.2.0",
31+
"mocha": "^11.7.1",
32+
"mongodb-memory-server": "^10.1.4",
33+
"nodemon": "^3.1.3",
34+
"supertest": "^7.1.1"
3035
}
3136
}

test/routes/author.test.js

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// test/routes/author.test.js
2+
3+
const request = require("supertest"); // For testing HTTP endpoints
4+
const { expect } = require("chai"); // For assertions (use 'expect' style)
5+
6+
// Import the Express app and create it.
7+
const { createApp } = require("../../app");
8+
const app = createApp();
9+
10+
// Set up test database
11+
const testDB = require("../utils/test_db");
12+
13+
// Import models used in Author route tests
14+
const Genre = require("../../models/genre");
15+
const Author = require("../../models/author");
16+
const Book = require("../../models/book");
17+
18+
describe("Author Routes", () => {
19+
// Connect to the test database before running tests
20+
before(async () => {
21+
await testDB.connect();
22+
});
23+
24+
// Close the test database connection after all tests
25+
after(async () => {
26+
await testDB.close();
27+
});
28+
29+
// Clear the test database before each test
30+
beforeEach(async () => {
31+
await testDB.clearDatabase();
32+
});
33+
34+
describe("Author List Routes", () => {
35+
describe("GET /catalog/authors", () => {
36+
it("should return 200 and contain 'Author List' or similar text", async () => {
37+
const res = await request(app).get("/catalog/authors").expect(200);
38+
// Adjust this check to your actual HTML output
39+
expect(res.text.toLowerCase()).to.include("author list");
40+
});
41+
});
42+
});
43+
44+
describe("Author Detail Routes", () => {
45+
it("should return author detail page for existing author", async () => {
46+
// Create a test author
47+
const author = new Author({
48+
first_name: "Nasir ",
49+
family_name: "Farooq",
50+
date_of_birth: new Date("1920-01-02"),
51+
date_of_death: new Date("1992-04-06"),
52+
});
53+
54+
await author.save();
55+
56+
const res = await request(app)
57+
.get(`/catalog/author/${author._id}`)
58+
.expect(200);
59+
60+
// You can check for either full name or family name depending on how it's rendered
61+
expect(res.text).to.include("Farooq");
62+
expect(res.text).to.include("Nasir");
63+
});
64+
65+
it("should return 404 for non-existent author", async () => {
66+
const fakeId = "64f1e0aa6a429f0f8d0d0d0d"; // valid ObjectId, doesn't exist
67+
await request(app).get(`/catalog/author/${fakeId}`).expect(404);
68+
});
69+
});
70+
71+
describe("Author Delete Routes", () => {
72+
let author;
73+
74+
beforeEach(async () => {
75+
author = await Author.create({
76+
first_name: "Deletable",
77+
family_name: "Author",
78+
date_of_birth: "1970-01-01",
79+
});
80+
});
81+
82+
it("should load the author delete confirmation page", async () => {
83+
const res = await request(app)
84+
.get(`/catalog/author/${author._id}/delete`)
85+
.expect(200);
86+
87+
expect(res.text).to.include("Delete Author");
88+
expect(res.text).to.include("Deletable");
89+
});
90+
91+
it("should delete the author and redirect to author list", async () => {
92+
const res = await request(app)
93+
.post(`/catalog/author/${author._id}/delete`)
94+
.type("form")
95+
.send({ authorid: author._id.toString() }) // Form body
96+
.expect(302);
97+
98+
expect(res.headers.location).to.equal("/catalog/authors");
99+
100+
const found = await Author.findById(author._id);
101+
expect(found).to.be.null;
102+
});
103+
104+
it("should NOT delete the author if books exist", async () => {
105+
const genre = await Genre.create({ name: "Drama" });
106+
107+
await Book.create({
108+
title: "Bound to Author",
109+
author: author._id,
110+
summary: "A book written by the author",
111+
isbn: "1111111111",
112+
genre: [genre._id],
113+
});
114+
115+
const res = await request(app)
116+
.post(`/catalog/author/${author._id}/delete`)
117+
.type("form")
118+
.send({ id: author._id.toString() })
119+
.expect(200); // Should NOT redirect
120+
121+
const found = await Author.findById(author._id);
122+
expect(found).to.not.be.null;
123+
124+
expect(res.text).to.include("Delete Author");
125+
expect(res.text).to.include("Bound to Author");
126+
});
127+
});
128+
129+
// Start of Author Create Route
130+
describe("Author Create Routes", () => {
131+
it("should load the author create form", async () => {
132+
const res = await request(app).get("/catalog/author/create").expect(200);
133+
134+
expect(res.text).to.include("Create Author");
135+
});
136+
137+
it("should create a new author and redirect to author detail page", async () => {
138+
const authorData = {
139+
first_name: "Test",
140+
family_name: "Author",
141+
date_of_birth: "1970-01-01",
142+
date_of_death: "2020-12-31",
143+
};
144+
145+
const res = await request(app)
146+
.post("/catalog/author/create")
147+
.type("form")
148+
.send(authorData)
149+
.expect(302);
150+
151+
// Redirects to the detail page of the newly created author
152+
expect(res.headers.location).to.match(
153+
/\/catalog\/author\/[a-fA-F0-9]{24}$/
154+
);
155+
156+
const authorId = res.headers.location.split("/").pop();
157+
const author = await Author.findById(authorId);
158+
159+
expect(author).to.exist;
160+
expect(author.first_name).to.equal(authorData.first_name);
161+
expect(author.family_name).to.equal(authorData.family_name);
162+
});
163+
164+
it("should re-render the form with errors if required fields are missing", async () => {
165+
const res = await request(app)
166+
.post("/catalog/author/create")
167+
.type("form")
168+
.send({ first_name: "", family_name: "" }) // Missing required fields
169+
.expect(200);
170+
171+
expect(res.text).to.include("Create Author");
172+
expect(res.text).to.include("First name must be specified"); //
173+
expect(res.text).to.include("Family name must be specified");
174+
// We could also test on: First name has non-alphanumeric characters.
175+
// We could also test on: Family name has non-alphanumeric characters.
176+
});
177+
});
178+
179+
// Author Update Routes
180+
181+
describe("Author Update Routes", () => {
182+
let author;
183+
184+
beforeEach(async () => {
185+
author = await Author.create({
186+
first_name: "UpdateMe",
187+
family_name: "Writer",
188+
date_of_birth: "1970-01-01",
189+
});
190+
});
191+
192+
it("should render the author update form", async () => {
193+
const res = await request(app)
194+
.get(`/catalog/author/${author._id}/update`)
195+
.expect(200);
196+
197+
expect(res.text).to.include("Update Author");
198+
expect(res.text).to.include("UpdateMe");
199+
expect(res.text).to.include("Writer");
200+
});
201+
202+
it("should update the author and redirect to detail page", async () => {
203+
const res = await request(app)
204+
.post(`/catalog/author/${author._id}/update`)
205+
.type("form")
206+
.send({
207+
first_name: "Updated",
208+
family_name: "Author",
209+
date_of_birth: "1960-05-15",
210+
date_of_death: "2020-01-01",
211+
authorid: author._id.toString(), // This is used in controller logic
212+
})
213+
.expect(302);
214+
215+
expect(res.headers.location).to.equal(`/catalog/author/${author._id}`);
216+
217+
const updated = await Author.findById(author._id);
218+
expect(updated.first_name).to.equal("Updated");
219+
expect(updated.family_name).to.equal("Author");
220+
});
221+
222+
it("should re-render the form with errors if required fields are missing", async () => {
223+
const res = await request(app)
224+
.post(`/catalog/author/${author._id}/update`)
225+
.type("form")
226+
.send({
227+
first_name: "", // Missing required
228+
family_name: "",
229+
date_of_birth: "1970-01-01",
230+
date_of_death: "2020-01-01",
231+
authorid: author._id.toString(),
232+
})
233+
.expect(200); // Should not redirect
234+
235+
expect(res.text).to.include("Update Author");
236+
expect(res.text).to.include("First name must be specified");
237+
expect(res.text).to.include("Family name must be specified");
238+
});
239+
});
240+
});

0 commit comments

Comments
 (0)