route.test.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { describe, it, expect, vi, beforeEach } from "vitest";
  2. // 1) Mocks
  3. vi.mock("@/lib/db", () => ({
  4. getDb: vi.fn(),
  5. }));
  6. vi.mock("@/models/user", () => ({
  7. default: {
  8. findOne: vi.fn(),
  9. },
  10. }));
  11. vi.mock("@/lib/auth/session", () => ({
  12. createSession: vi.fn(),
  13. }));
  14. vi.mock("bcryptjs", () => {
  15. const compare = vi.fn();
  16. return {
  17. default: { compare },
  18. compare,
  19. };
  20. });
  21. // 2) Imports AFTER the mocks
  22. import { getDb } from "@/lib/db";
  23. import User from "@/models/user";
  24. import { createSession } from "@/lib/auth/session";
  25. import { compare as bcryptCompare } from "bcryptjs";
  26. import { POST } from "./route";
  27. function createRequestStub(body) {
  28. return {
  29. async json() {
  30. return body;
  31. },
  32. };
  33. }
  34. describe("POST /api/auth/login", () => {
  35. beforeEach(() => {
  36. vi.clearAllMocks();
  37. getDb.mockResolvedValue({}); // we only need it to "connect"
  38. });
  39. it("logs in successfully with correct credentials", async () => {
  40. const user = {
  41. _id: "507f1f77bcf86cd799439011",
  42. username: "branchuser",
  43. passwordHash: "hashed-password",
  44. role: "branch",
  45. branchId: "NL01",
  46. };
  47. User.findOne.mockReturnValue({
  48. exec: vi.fn().mockResolvedValue(user),
  49. });
  50. bcryptCompare.mockResolvedValue(true);
  51. const request = createRequestStub({
  52. username: "BranchUser", // mixed case, should be normalized
  53. password: "secret-password",
  54. });
  55. const response = await POST(request);
  56. const json = await response.json();
  57. expect(response.status).toBe(200);
  58. expect(json).toEqual({ ok: true });
  59. expect(getDb).toHaveBeenCalledTimes(1);
  60. expect(User.findOne).toHaveBeenCalledWith({ username: "branchuser" });
  61. expect(bcryptCompare).toHaveBeenCalledWith(
  62. "secret-password",
  63. "hashed-password"
  64. );
  65. expect(createSession).toHaveBeenCalledWith({
  66. userId: "507f1f77bcf86cd799439011",
  67. role: "branch",
  68. branchId: "NL01",
  69. });
  70. });
  71. it("returns 400 when JSON parsing fails", async () => {
  72. // Simulate request.json() throwing (invalid JSON body).
  73. const request = {
  74. json: vi.fn().mockRejectedValue(new Error("invalid json")),
  75. };
  76. const response = await POST(request);
  77. const body = await response.json();
  78. expect(response.status).toBe(400);
  79. expect(body).toEqual({
  80. error: {
  81. message: "Invalid request body",
  82. code: "VALIDATION_INVALID_JSON",
  83. },
  84. });
  85. expect(createSession).not.toHaveBeenCalled();
  86. });
  87. it("returns 401 when user does not exist", async () => {
  88. User.findOne.mockReturnValue({
  89. exec: vi.fn().mockResolvedValue(null),
  90. });
  91. const request = createRequestStub({
  92. username: "unknownuser",
  93. password: "some-password",
  94. });
  95. const response = await POST(request);
  96. const body = await response.json();
  97. expect(response.status).toBe(401);
  98. expect(body).toEqual({
  99. error: {
  100. message: "Invalid credentials",
  101. code: "AUTH_INVALID_CREDENTIALS",
  102. },
  103. });
  104. expect(createSession).not.toHaveBeenCalled();
  105. });
  106. it("returns 401 when passwordHash is missing (defensive)", async () => {
  107. User.findOne.mockReturnValue({
  108. exec: vi.fn().mockResolvedValue({
  109. _id: "507f1f77bcf86cd799439099",
  110. username: "branchuser",
  111. // passwordHash missing on purpose
  112. role: "branch",
  113. branchId: "NL01",
  114. }),
  115. });
  116. const request = createRequestStub({
  117. username: "branchuser",
  118. password: "secret-password",
  119. });
  120. const response = await POST(request);
  121. const body = await response.json();
  122. expect(response.status).toBe(401);
  123. expect(body).toEqual({
  124. error: {
  125. message: "Invalid credentials",
  126. code: "AUTH_INVALID_CREDENTIALS",
  127. },
  128. });
  129. expect(bcryptCompare).not.toHaveBeenCalled();
  130. expect(createSession).not.toHaveBeenCalled();
  131. });
  132. it("returns 401 when password is incorrect", async () => {
  133. const user = {
  134. _id: "507f1f77bcf86cd799439012",
  135. username: "branchuser",
  136. passwordHash: "hashed-password",
  137. role: "branch",
  138. branchId: "NL02",
  139. };
  140. User.findOne.mockReturnValue({
  141. exec: vi.fn().mockResolvedValue(user),
  142. });
  143. bcryptCompare.mockResolvedValue(false);
  144. const request = createRequestStub({
  145. username: "branchuser",
  146. password: "wrong-password",
  147. });
  148. const response = await POST(request);
  149. const body = await response.json();
  150. expect(response.status).toBe(401);
  151. expect(body).toEqual({
  152. error: {
  153. message: "Invalid credentials",
  154. code: "AUTH_INVALID_CREDENTIALS",
  155. },
  156. });
  157. expect(createSession).not.toHaveBeenCalled();
  158. });
  159. it("returns 400 when username or password is missing", async () => {
  160. const request = createRequestStub({
  161. username: "only-username",
  162. });
  163. const response = await POST(request);
  164. const body = await response.json();
  165. expect(response.status).toBe(400);
  166. expect(body).toEqual({
  167. error: {
  168. message: "Missing username or password",
  169. code: "VALIDATION_MISSING_FIELD",
  170. details: { fields: ["username", "password"] },
  171. },
  172. });
  173. expect(User.findOne).not.toHaveBeenCalled();
  174. expect(createSession).not.toHaveBeenCalled();
  175. });
  176. it("returns 500 when an unexpected error occurs", async () => {
  177. User.findOne.mockImplementation(() => {
  178. throw new Error("DB failure");
  179. });
  180. const request = createRequestStub({
  181. username: "branchuser",
  182. password: "secret-password",
  183. });
  184. const response = await POST(request);
  185. const body = await response.json();
  186. expect(response.status).toBe(500);
  187. expect(body).toEqual({
  188. error: {
  189. message: "Internal server error",
  190. code: "INTERNAL_SERVER_ERROR",
  191. },
  192. });
  193. expect(createSession).not.toHaveBeenCalled();
  194. });
  195. });