aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/server/src/routes')
-rw-r--r--pkgs/server/src/routes/auth.test.ts205
1 files changed, 205 insertions, 0 deletions
diff --git a/pkgs/server/src/routes/auth.test.ts b/pkgs/server/src/routes/auth.test.ts
index 28bd558..7de04f5 100644
--- a/pkgs/server/src/routes/auth.test.ts
+++ b/pkgs/server/src/routes/auth.test.ts
@@ -37,6 +37,9 @@ vi.mock("../db", () => {
}),
})),
})),
+ delete: vi.fn(() => ({
+ where: vi.fn(() => Promise.resolve(undefined)),
+ })),
},
users: {
id: "id",
@@ -306,3 +309,205 @@ describe("POST /login", () => {
expect(body.error?.code).toBe("VALIDATION_ERROR");
});
});
+
+interface RefreshResponse {
+ accessToken?: string;
+ refreshToken?: string;
+ user?: {
+ id: string;
+ username: string;
+ };
+ error?: {
+ code: string;
+ message: string;
+ };
+}
+
+describe("POST /refresh", () => {
+ let app: Hono;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ app = new Hono();
+ app.onError(errorHandler);
+ app.route("/api/auth", auth);
+ });
+
+ it("returns new tokens for valid refresh token", async () => {
+ const { db } = await import("../db");
+
+ // Mock finding valid refresh token
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([
+ {
+ id: "token-id-123",
+ userId: "user-uuid-123",
+ expiresAt: new Date(Date.now() + 86400000), // expires in 1 day
+ },
+ ]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ // Mock finding user
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([
+ {
+ id: "user-uuid-123",
+ username: "testuser",
+ },
+ ]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ // Mock delete old token
+ vi.mocked(db.delete).mockReturnValueOnce({
+ where: vi.fn().mockResolvedValue(undefined),
+ } as unknown as ReturnType<typeof db.delete>);
+
+ // Mock insert new token
+ vi.mocked(db.insert).mockReturnValueOnce({
+ values: vi.fn().mockResolvedValue(undefined),
+ } as unknown as ReturnType<typeof db.insert>);
+
+ const res = await app.request("/api/auth/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ refreshToken: "valid-refresh-token-hex",
+ }),
+ });
+
+ expect(res.status).toBe(200);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.accessToken).toBeDefined();
+ expect(typeof body.accessToken).toBe("string");
+ expect(body.refreshToken).toBeDefined();
+ expect(typeof body.refreshToken).toBe("string");
+ expect(body.user).toEqual({
+ id: "user-uuid-123",
+ username: "testuser",
+ });
+ });
+
+ it("returns 401 for invalid refresh token", async () => {
+ const { db } = await import("../db");
+
+ // Mock no token found
+ 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/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ refreshToken: "invalid-refresh-token",
+ }),
+ });
+
+ expect(res.status).toBe(401);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.error?.code).toBe("INVALID_REFRESH_TOKEN");
+ });
+
+ it("returns 401 for expired refresh token", async () => {
+ const { db } = await import("../db");
+
+ // Mock no valid (non-expired) token found (empty result because expiry check in query)
+ 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/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ refreshToken: "expired-refresh-token",
+ }),
+ });
+
+ expect(res.status).toBe(401);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.error?.code).toBe("INVALID_REFRESH_TOKEN");
+ });
+
+ it("returns 401 when user not found", async () => {
+ const { db } = await import("../db");
+
+ // Mock finding valid refresh token
+ vi.mocked(db.select).mockReturnValueOnce({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([
+ {
+ id: "token-id-123",
+ userId: "deleted-user-id",
+ expiresAt: new Date(Date.now() + 86400000),
+ },
+ ]),
+ }),
+ }),
+ } as unknown as ReturnType<typeof db.select>);
+
+ // Mock user not found
+ 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/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ refreshToken: "valid-refresh-token",
+ }),
+ });
+
+ expect(res.status).toBe(401);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.error?.code).toBe("USER_NOT_FOUND");
+ });
+
+ it("returns 422 for missing refresh token", async () => {
+ const res = await app.request("/api/auth/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({}),
+ });
+
+ expect(res.status).toBe(422);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.error?.code).toBe("VALIDATION_ERROR");
+ });
+
+ it("returns 422 for empty refresh token", async () => {
+ const res = await app.request("/api/auth/refresh", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ refreshToken: "",
+ }),
+ });
+
+ expect(res.status).toBe(422);
+ const body = (await res.json()) as RefreshResponse;
+ expect(body.error?.code).toBe("VALIDATION_ERROR");
+ });
+});