aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/routes/auth.test.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-03 05:34:44 +0900
committernsfisis <nsfisis@gmail.com>2025-12-04 23:26:27 +0900
commit742c3e55b08b37d0eb031f72a6d952bc7b7164b3 (patch)
tree119f091be511a9d870815fed9a1b98b86d638376 /pkgs/server/src/routes/auth.test.ts
parent950217ed3ca93a0aa0e964c2a8474ffc13c71912 (diff)
downloadkioku-742c3e55b08b37d0eb031f72a6d952bc7b7164b3.tar.gz
kioku-742c3e55b08b37d0eb031f72a6d952bc7b7164b3.tar.zst
kioku-742c3e55b08b37d0eb031f72a6d952bc7b7164b3.zip
feat(auth): add login endpoint with JWT
Implement POST /api/auth/login endpoint that validates credentials and returns a JWT access token on successful authentication. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'pkgs/server/src/routes/auth.test.ts')
-rw-r--r--pkgs/server/src/routes/auth.test.ts145
1 files changed, 145 insertions, 0 deletions
diff --git a/pkgs/server/src/routes/auth.test.ts b/pkgs/server/src/routes/auth.test.ts
index 2d60636..1dfba46 100644
--- a/pkgs/server/src/routes/auth.test.ts
+++ b/pkgs/server/src/routes/auth.test.ts
@@ -48,6 +48,9 @@ vi.mock("../db", () => {
vi.mock("argon2", () => ({
hash: vi.fn((password: string) => Promise.resolve(`hashed_${password}`)),
+ verify: vi.fn((hash: string, password: string) =>
+ Promise.resolve(hash === `hashed_${password}`),
+ ),
}));
interface RegisterResponse {
@@ -62,6 +65,18 @@ interface RegisterResponse {
};
}
+interface LoginResponse {
+ accessToken?: string;
+ user?: {
+ id: string;
+ username: string;
+ };
+ error?: {
+ code: string;
+ message: string;
+ };
+}
+
describe("POST /register", () => {
let app: Hono;
@@ -146,3 +161,133 @@ describe("POST /register", () => {
expect(body.error?.code).toBe("USERNAME_EXISTS");
});
});
+
+describe("POST /login", () => {
+ let app: Hono;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ app = new Hono();
+ app.onError(errorHandler);
+ app.route("/api/auth", auth);
+ });
+
+ it("returns access token for valid credentials", async () => {
+ const { db } = await import("../db");
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([
+ {
+ id: "user-uuid-123",
+ username: "testuser",
+ passwordHash: "hashed_correctpassword",
+ },
+ ]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ const res = await app.request("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ username: "testuser",
+ password: "correctpassword",
+ }),
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as LoginResponse;
+ expect(body.accessToken).toBeDefined();
+ expect(typeof body.accessToken).toBe("string");
+ expect(body.user).toEqual({
+ id: "user-uuid-123",
+ username: "testuser",
+ });
+ });
+
+ it("returns 401 for non-existent user", async () => {
+ const { db } = await import("../db");
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ const res = await app.request("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ username: "nonexistent",
+ password: "anypassword",
+ }),
+ });
+
+ expect(res.status).toBe(401);
+ const body = (await res.json()) as LoginResponse;
+ expect(body.error?.code).toBe("INVALID_CREDENTIALS");
+ });
+
+ it("returns 401 for incorrect password", async () => {
+ const { db } = await import("../db");
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([
+ {
+ id: "user-uuid-123",
+ username: "testuser",
+ passwordHash: "hashed_correctpassword",
+ },
+ ]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ const res = await app.request("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ username: "testuser",
+ password: "wrongpassword",
+ }),
+ });
+
+ expect(res.status).toBe(401);
+ const body = (await res.json()) as LoginResponse;
+ expect(body.error?.code).toBe("INVALID_CREDENTIALS");
+ });
+
+ it("returns 422 for missing username", async () => {
+ const res = await app.request("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ username: "",
+ password: "somepassword",
+ }),
+ });
+
+ expect(res.status).toBe(422);
+ const body = (await res.json()) as LoginResponse;
+ expect(body.error?.code).toBe("VALIDATION_ERROR");
+ });
+
+ it("returns 422 for missing password", async () => {
+ const res = await app.request("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ username: "testuser",
+ password: "",
+ }),
+ });
+
+ expect(res.status).toBe(422);
+ const body = (await res.json()) as LoginResponse;
+ expect(body.error?.code).toBe("VALIDATION_ERROR");
+ });
+});