create-nodeapi-backend
Version:
A powerful Node.js backend boilerplate with Express, MongoDB, Firebase, JWT auth, Nodemailer, cron jobs, input validation (Joi), and serverless support for Vercel. Scaffold a full-featured API backend in seconds.
272 lines (225 loc) • 8.19 kB
JavaScript
const request = require("supertest");
const app = require("../app");
const mongoose = require("mongoose");
const { connectToDatabase } = require("../lib/database");
const testUser = {
email: `user${Date.now()}@test.com`,
password: "test@password123",
updatePassword: "Update@password456",
newPassword: "NeW@password4569",
profileName: {
firstName: "Optimus",
lastName: "Prime",
displayName: "Primate"
},
profileImages: {
avatar:
"https://res.cloudinary.com/ddhwvds38/image/upload/v1741362007/uploads/glrzqzt7cmxzpo5vgq9z.jpg",
backDrop: null
},
address: {
city: "White House",
country: "USA",
state: "Washington DC"
}
};
let accessToken;
beforeAll(async () => {
await connectToDatabase();
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Database connection timeout"));
}, 25000);
mongoose.connection.once("open", () => {
clearTimeout(timeout);
resolve();
});
mongoose.connection.on("error", (err) => {
clearTimeout(timeout);
reject(err);
});
});
}, 30000);
afterAll(async () => {
try {
// Check connection state
if (mongoose.connection.readyState !== 1) {
console.log("Database not connected, cannot clean up");
return;
}
// Log before deletion
console.log(`Attempting to delete test user: ${testUser.email}`);
// Delete user
const result = await mongoose.models.User.deleteOne({
email: testUser.email
});
console.log(`Deleted ${result.deletedCount} users via User model`);
// Disconnect
await mongoose.disconnect();
console.log("Database disconnected");
} catch (error) {
console.error("Error in afterAll:", error);
// Still try to disconnect
try {
await mongoose.disconnect();
} catch (e) {
console.error("Error disconnecting:", e);
}
}
}, 10000); // 10 second timeout
describe("Auth Flow", () => {
it("should sign up a user", async () => {
console.log(`Attempting to create test user: ${testUser.email}`);
const res = await request(app)
.post("/v1/auth/sign-up")
.send({
email: testUser.email,
password: testUser.password,
profileName: {
firstName: testUser.profileName.firstName,
lastName: testUser.profileName.lastName
}
});
expect(res.statusCode).toBe(201);
expect(res.body.message).toBe("OTP Sent to your email");
const data = res.body.data;
expect(data).toHaveProperty("id");
expect(data).toHaveProperty("email", testUser.email);
expect(data.token).toHaveProperty("access");
expect(data.token).toHaveProperty("expiresIn");
});
it("should sign in the user and respond with verification notice", async () => {
const res = await request(app).post("/v1/auth/sign-in").send({
email: testUser.email,
password: testUser.password
});
expect([200, 203]).toContain(res.statusCode);
expect([
"Login Successful, Please Verify your account",
"Login Successful, Verified"
]).toContain(res.body.message);
const data = res.body.data;
expect(data).toHaveProperty("id");
expect(data).toHaveProperty("email", testUser.email);
expect(data.token).toHaveProperty("access");
expect(data.token).toHaveProperty("expiresIn");
accessToken = data.token.access;
});
});
describe("OTP Verification Flow", () => {
let otpCode;
beforeAll(async () => {
// Retrieve the OTP from the DB (or mock it if you're using a test service)
const OtpModels = mongoose.models.Otp;
const user = await OtpModels.findOne({ email: testUser.email });
if (!user || !user.otp) {
throw new Error("OTP code not found for test user");
}
otpCode = user.otp;
});
it("should resend otp code", async () => {
const res = await request(app)
.get("/v1/otp/resend")
.set("Authorization", `Bearer ${accessToken}`);
expect(res.statusCode).toBe(201);
expect(res.body.message).toBe("OTP sent to your mail");
});
it("should verify the OTP for the user", async () => {
const res = await request(app)
.post("/v1/otp/verify")
.set("Authorization", `Bearer ${accessToken}`)
.send({ otpCode });
expect(res.statusCode).toBe(200);
expect(res.body.message).toBe("VERIFIED");
});
});
describe("User Flow", () => {
it("should update the user's profile and address", async () => {
const updatePayload = {
profileInfo: {
firstName: testUser.profileName.firstName,
lastName: testUser.profileName.lastName,
displayName: testUser.profileName.displayName,
phoneNumber: "+1234567890"
},
avatar: testUser.profileImages.avatar,
address: {
country: testUser.address.country,
state: testUser.address.state,
city: testUser.address.city
}
};
const res = await request(app)
.patch("/v1/user")
.set("Authorization", `Bearer ${accessToken}`)
.send(updatePayload);
expect(res.statusCode).toBe(200);
expect(res.body.message).toBe("User successfully updated.");
});
it("should return the user data with correct structure", async () => {
const response = await request(app)
.get("/v1/user")
.set("Authorization", `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("data");
const data = response.body.data;
// Structure checks
expect(data).toHaveProperty("_id");
expect(data).toHaveProperty("email", testUser.email);
expect(data.profile).toHaveProperty("firstName");
expect(data.profile).toHaveProperty("lastName");
expect(data.profile).toHaveProperty("displayName");
expect(data.profileImages).toHaveProperty("avatar");
expect(data.address).toHaveProperty("city");
expect(data.address).toHaveProperty("country");
expect(data.address).toHaveProperty("state");
// Data value checks
expect(data.profile.firstName).toBe(testUser.profileName.firstName);
expect(data.profile.lastName).toBe(testUser.profileName.lastName);
expect(data.profile.displayName).toBe(testUser.profileName.displayName);
expect(data.profileImages.avatar).toBe(testUser.profileImages.avatar);
expect(data.address.city).toBe(testUser.address.city);
expect(data.address.country).toBe(testUser.address.country);
expect(data.address.state).toBe(testUser.address.state);
});
});
describe("Password Reset Flow", () => {
let resetToken;
it("should request a password reset OTP", async () => {
const res = await request(app)
.post("/v1/password/forgot")
.send({ email: testUser.email });
expect(res.statusCode).toBe(200);
expect(res.body.message).toBe("Password reset link sent to your email");
const result = await mongoose.models.User.findOne({
email: testUser.email
});
const getToken = await mongoose.models.Token.findOne({
userId: result._id.toString(),
type: "reset",
status: "revoked",
role: "user"
});
expect(getToken).toBeTruthy();
resetToken = getToken.token;
});
it("should update the password from old to new password", async () => {
const res = await request(app)
.put("/v1/password/update")
.set("Authorization", `Bearer ${accessToken}`)
.send({
oldPassword: testUser.password,
newPassword: testUser.updatePassword
});
expect(res.statusCode).toBe(200);
expect(res.body.message).toBe("Password updated successfully");
});
it("should change the password", async () => {
const res = await request(app).patch("/v1/password/reset").send({
token: resetToken,
newPassword: testUser.newPassword
});
expect(res.statusCode).toBe(200);
expect(res.body.message).toBe("Password reset successfully");
});
});