
- Updated `.env` and `.env.test` files to include PostgreSQL connection settings and Redis configuration. - Migrated database from SQLite to PostgreSQL, updating relevant queries and connection logic. - Enhanced error handling and logging throughout the application. - Added new test utilities for PostgreSQL integration and updated user model methods. - Introduced new routes for user authentication and form management, ensuring compatibility with the new database structure. - Created login and registration views in EJS for user interaction.
127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
// __tests__/unit/services/jwtService.test.js
|
|
const jwtService = require("../../../src/services/jwtService"); // Adjust path
|
|
const User = require("../../../src/models/User"); // Adjust path
|
|
const jwt = require("jsonwebtoken");
|
|
|
|
jest.mock("../../../src/models/User"); // Mock the User model
|
|
|
|
describe("JWT Service", () => {
|
|
const mockUser = { id: 1, email: "test@example.com", role: "user" };
|
|
const originalJwtSecret = process.env.JWT_SECRET;
|
|
|
|
beforeAll(() => {
|
|
process.env.JWT_SECRET = "test-secret-for-jwt-service"; // Use a fixed secret for tests
|
|
});
|
|
afterAll(() => {
|
|
process.env.JWT_SECRET = originalJwtSecret; // Restore original
|
|
});
|
|
beforeEach(() => {
|
|
User.saveSession.mockClear();
|
|
User.isTokenBlacklisted.mockClear();
|
|
User.revokeSession.mockClear();
|
|
});
|
|
|
|
describe("generateAccessToken", () => {
|
|
it("should generate a valid access token and save session", async () => {
|
|
User.saveSession.mockResolvedValue(true);
|
|
const { token, expiresAt, jti } =
|
|
jwtService.generateAccessToken(mockUser);
|
|
|
|
expect(token).toBeDefined();
|
|
expect(jti).toBeDefined();
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
expect(decoded.sub).toBe(mockUser.id);
|
|
expect(decoded.type).toBe("access");
|
|
expect(decoded.jti).toBe(jti);
|
|
expect(User.saveSession).toHaveBeenCalledWith(
|
|
mockUser.id,
|
|
jti,
|
|
expiresAt,
|
|
undefined,
|
|
undefined
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("generateRefreshToken", () => {
|
|
it("should generate a valid refresh token and save session", async () => {
|
|
User.saveSession.mockResolvedValue(true);
|
|
const { token } = jwtService.generateRefreshToken(mockUser);
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
expect(decoded.sub).toBe(mockUser.id);
|
|
expect(decoded.type).toBe("refresh");
|
|
});
|
|
});
|
|
|
|
describe("verifyToken", () => {
|
|
it("should verify a valid token", () => {
|
|
const { token } = jwtService.generateAccessToken(mockUser);
|
|
const decoded = jwtService.verifyToken(token, "access");
|
|
expect(decoded.sub).toBe(mockUser.id);
|
|
});
|
|
|
|
it("should throw error for an expired token", () => {
|
|
// Generate token with 0s expiry (sign options need to be passed to jwt.sign)
|
|
const expiredToken = jwt.sign(
|
|
{ sub: mockUser.id, type: "access" },
|
|
process.env.JWT_SECRET,
|
|
{ expiresIn: "0s" }
|
|
);
|
|
// Wait a bit for it to actually expire
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
expect(() => jwtService.verifyToken(expiredToken, "access")).toThrow(
|
|
"Token has expired"
|
|
);
|
|
resolve();
|
|
}, 10);
|
|
});
|
|
});
|
|
|
|
it("should throw error for an invalid token type", () => {
|
|
const { token } = jwtService.generateAccessToken(mockUser); // This is an 'access' token
|
|
expect(() => jwtService.verifyToken(token, "refresh")).toThrow(
|
|
"Invalid token type. Expected refresh"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("refreshAccessToken", () => {
|
|
it("should refresh access token with a valid refresh token", async () => {
|
|
const { token: rToken, jti: refreshJti } =
|
|
jwtService.generateRefreshToken(mockUser);
|
|
User.isTokenBlacklisted.mockResolvedValue(false); // Not blacklisted
|
|
User.findById.mockResolvedValue(mockUser); // User exists
|
|
User.saveSession.mockResolvedValue(true); // For the new access token
|
|
|
|
const { accessToken } = await jwtService.refreshAccessToken(rToken);
|
|
expect(accessToken).toBeDefined();
|
|
const decodedAccess = jwt.verify(accessToken, process.env.JWT_SECRET);
|
|
expect(decodedAccess.type).toBe("access");
|
|
expect(User.isTokenBlacklisted).toHaveBeenCalledWith(refreshJti);
|
|
});
|
|
|
|
it("should throw if refresh token is blacklisted", async () => {
|
|
const { token: rToken, jti: refreshJti } =
|
|
jwtService.generateRefreshToken(mockUser);
|
|
User.isTokenBlacklisted.mockResolvedValue(true); // Blacklisted
|
|
|
|
await expect(jwtService.refreshAccessToken(rToken)).rejects.toThrow(
|
|
"Refresh token has been revoked"
|
|
);
|
|
expect(User.isTokenBlacklisted).toHaveBeenCalledWith(refreshJti);
|
|
});
|
|
});
|
|
|
|
describe("revokeToken", () => {
|
|
it("should call User.revokeSession with JTI", async () => {
|
|
const { token, jti } = jwtService.generateAccessToken(mockUser);
|
|
User.revokeSession.mockResolvedValue(true);
|
|
|
|
await jwtService.revokeToken(token);
|
|
expect(User.revokeSession).toHaveBeenCalledWith(jti);
|
|
});
|
|
});
|
|
// ... more tests ...
|
|
});
|