feat: update tests for auth
This commit is contained in:
parent
9131a86262
commit
42d2054532
@ -1,80 +1,10 @@
|
|||||||
import { beforeAll, describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
import { migrate } from "drizzle-orm/bun-sql/migrator";
|
|
||||||
import app from ".";
|
import app from ".";
|
||||||
import { db } from "./db";
|
|
||||||
import { auth } from "./lib/auth";
|
|
||||||
import { buildHeaders } from "./test/utils";
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
describe("API Health Check", () => {
|
||||||
await migrate(db, { migrationsFolder: "./drizzle" });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("First Test", () => {
|
|
||||||
it("should return 200 Response", async () => {
|
it("should return 200 Response", async () => {
|
||||||
const req = new Request("http://localhost:3000/");
|
const req = new Request("http://localhost:3000/");
|
||||||
const res = await app.fetch(req);
|
const res = await app.fetch(req);
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Auth", () => {
|
|
||||||
const user = {
|
|
||||||
email: "yadunand@yadunut.com",
|
|
||||||
password: "password123",
|
|
||||||
name: "Yadunand Prem",
|
|
||||||
username: "yadunut",
|
|
||||||
};
|
|
||||||
|
|
||||||
let authHeaders: Headers;
|
|
||||||
|
|
||||||
it("creates a new user", async () => {
|
|
||||||
const res = await auth.api.signUpEmail({
|
|
||||||
body: {
|
|
||||||
...user,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(res.user.email).toBe("yadunand@yadunut.com");
|
|
||||||
const foundUser = await db.query.user.findFirst();
|
|
||||||
expect(foundUser).not.toBeNull();
|
|
||||||
expect(foundUser?.email).toBe(user.email);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("logs in user and stores auth headers", async () => {
|
|
||||||
const { headers: signInHeaders, response: signInRes } =
|
|
||||||
await auth.api.signInEmail({
|
|
||||||
body: {
|
|
||||||
email: user.email,
|
|
||||||
password: user.password,
|
|
||||||
},
|
|
||||||
returnHeaders: true,
|
|
||||||
});
|
|
||||||
expect(signInRes.token).not.toBeNull();
|
|
||||||
expect(signInRes.user.name).toBe(user.name);
|
|
||||||
|
|
||||||
// Store the auth headers for subsequent tests
|
|
||||||
authHeaders = buildHeaders(signInHeaders);
|
|
||||||
|
|
||||||
// Verify session works with stored headers
|
|
||||||
const res = await auth.api.getSession({ headers: authHeaders });
|
|
||||||
expect(res?.user.username).toBe(user.username);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("creates auth token using stored headers", async () => {
|
|
||||||
const data = await auth.api.createApiKey({
|
|
||||||
headers: authHeaders,
|
|
||||||
body: {},
|
|
||||||
});
|
|
||||||
// Add expectations based on what createApiKey should return
|
|
||||||
expect(data).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deletes user using stored headers", async () => {
|
|
||||||
const deleteRes = await auth.api.deleteUser({
|
|
||||||
headers: authHeaders,
|
|
||||||
body: {
|
|
||||||
password: user.password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(deleteRes.success).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { logger } from "hono/logger";
|
import { logger } from "hono/logger";
|
||||||
import { auth } from "./lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
304
api/src/test/auth/apikey.test.ts
Normal file
304
api/src/test/auth/apikey.test.ts
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { AuthTestHelper, buildHeaders } from "../utils";
|
||||||
|
|
||||||
|
describe("API Key Management", () => {
|
||||||
|
const user = {
|
||||||
|
email: "apikey-test@example.com",
|
||||||
|
password: "password123",
|
||||||
|
name: "API Key Test User",
|
||||||
|
username: "apikeyuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
let authHeaders: Headers;
|
||||||
|
let userId: string;
|
||||||
|
|
||||||
|
let authHelper: AuthTestHelper;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Create user and sign in for each test
|
||||||
|
authHelper = new AuthTestHelper();
|
||||||
|
const authUser = await authHelper.createAuthenticatedUser();
|
||||||
|
authHeaders = authUser.authHeaders;
|
||||||
|
userId = authUser.userId;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// Clean up: delete user and associated data
|
||||||
|
authHelper.cleanupUser(authHeaders, user.password);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("API Key Creation", () => {
|
||||||
|
it("creates an API key successfully", async () => {
|
||||||
|
const res = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: {
|
||||||
|
name: "Test API Key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res.id).toBeDefined();
|
||||||
|
expect(res.name).toBe("Test API Key");
|
||||||
|
expect(res.key).toBeDefined();
|
||||||
|
expect(res.userId).toBe(userId);
|
||||||
|
expect(res.enabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates an API key with custom name", async () => {
|
||||||
|
const res = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: {
|
||||||
|
name: "Custom API Key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res.name).toBe("Custom API Key");
|
||||||
|
expect(res.key).toBeDefined();
|
||||||
|
expect(res.userId).toBe(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects API key creation without authentication", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.createApiKey({
|
||||||
|
headers: new Headers(),
|
||||||
|
body: {
|
||||||
|
name: "Unauthorized Key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("API Key Listing", () => {
|
||||||
|
it("lists user's API keys", async () => {
|
||||||
|
// Create multiple API keys
|
||||||
|
await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Key 1" },
|
||||||
|
});
|
||||||
|
await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Key 2" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(Array.isArray(res)).toBe(true);
|
||||||
|
expect(res.length).toBe(2);
|
||||||
|
expect(res.some((key) => key.name === "Key 1")).toBe(true);
|
||||||
|
expect(res.some((key) => key.name === "Key 2")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty list for user with no API keys", async () => {
|
||||||
|
const res = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(Array.isArray(res)).toBe(true);
|
||||||
|
expect(res.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects listing without authentication", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.listApiKeys({
|
||||||
|
headers: new Headers(),
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("API Key Deletion", () => {
|
||||||
|
let apiKeyId: string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const res = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Key to Revoke" },
|
||||||
|
});
|
||||||
|
apiKeyId = res.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes an API key successfully", async () => {
|
||||||
|
const res = await auth.api.deleteApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { keyId: apiKeyId },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.success).toBe(true);
|
||||||
|
|
||||||
|
// Verify the key is no longer in the list
|
||||||
|
const keys = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
expect(keys.find((key) => key.id === apiKeyId)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects deleting non-existent API key", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.deleteApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { keyId: "non-existent-key-id" },
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects deleting API key without authentication", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.deleteApiKey({
|
||||||
|
headers: new Headers(),
|
||||||
|
body: { keyId: apiKeyId },
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects deleting another user's API key", async () => {
|
||||||
|
// Create another user
|
||||||
|
const otherUser = {
|
||||||
|
email: "other-user@example.com",
|
||||||
|
password: "password123",
|
||||||
|
name: "Other User",
|
||||||
|
username: "otheruser",
|
||||||
|
};
|
||||||
|
|
||||||
|
await auth.api.signUpEmail({
|
||||||
|
body: otherUser,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { headers: otherHeaders } = await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email: otherUser.email,
|
||||||
|
password: otherUser.password,
|
||||||
|
},
|
||||||
|
returnHeaders: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auth.api.deleteApiKey({
|
||||||
|
headers: buildHeaders(otherHeaders),
|
||||||
|
body: { keyId: apiKeyId },
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up other user
|
||||||
|
await auth.api.deleteUser({
|
||||||
|
headers: buildHeaders(otherHeaders),
|
||||||
|
body: { password: otherUser.password },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("API Key Verification", () => {
|
||||||
|
let apiKey: string;
|
||||||
|
let apiKeyId: string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const res = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Test Auth Key" },
|
||||||
|
});
|
||||||
|
apiKey = res.key;
|
||||||
|
apiKeyId = res.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("verifies valid API key", async () => {
|
||||||
|
const res = await auth.api.verifyApiKey({
|
||||||
|
body: { key: apiKey },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res.valid).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects invalid API key", async () => {
|
||||||
|
const res = await auth.api.verifyApiKey({
|
||||||
|
body: { key: "invalid-key" },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("gets API key details", async () => {
|
||||||
|
const res = await auth.api.getApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
query: { id: apiKeyId },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res.id).toBe(apiKeyId);
|
||||||
|
expect(res.name).toBe("Test Auth Key");
|
||||||
|
expect(res.userId).toBe(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates API key", async () => {
|
||||||
|
const res = await auth.api.updateApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: {
|
||||||
|
keyId: apiKeyId,
|
||||||
|
name: "Updated API Key Name",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toBeDefined();
|
||||||
|
expect(res.name).toBe("Updated API Key Name");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("API Key Management", () => {
|
||||||
|
it("manages multiple API keys correctly", async () => {
|
||||||
|
// Create multiple keys
|
||||||
|
const key1 = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Key 1" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const key2 = await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { name: "Key 2" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// List keys
|
||||||
|
const keys = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(keys.length).toBeGreaterThanOrEqual(2);
|
||||||
|
expect(keys.find((k) => k.id === key1.id)).toBeDefined();
|
||||||
|
expect(keys.find((k) => k.id === key2.id)).toBeDefined();
|
||||||
|
|
||||||
|
// Delete one key
|
||||||
|
await auth.api.deleteApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { keyId: key1.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify it's removed
|
||||||
|
const updatedKeys = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updatedKeys.find((k) => k.id === key1.id)).toBeUndefined();
|
||||||
|
expect(updatedKeys.find((k) => k.id === key2.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
121
api/src/test/auth/integration.test.ts
Normal file
121
api/src/test/auth/integration.test.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { AuthTestHelper } from "../utils";
|
||||||
|
|
||||||
|
describe("Authentication Integration Flow", () => {
|
||||||
|
let authHelper: AuthTestHelper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
authHelper = new AuthTestHelper();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("completes full auth flow: signup -> signin -> create API key -> use API key -> cleanup", async () => {
|
||||||
|
// 1. Create and authenticate user
|
||||||
|
const { user, userId, authHeaders } =
|
||||||
|
await authHelper.createAuthenticatedUser({
|
||||||
|
email: `integration-${Date.now()}@example.com`,
|
||||||
|
name: "Integration Test User",
|
||||||
|
username: `integrationuser${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Create API key using session auth
|
||||||
|
const apiKeyRes = await authHelper.createApiKey(authHeaders, {
|
||||||
|
name: "Integration Test Key",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(apiKeyRes.id).toBeDefined();
|
||||||
|
expect(apiKeyRes.key).toBeDefined();
|
||||||
|
expect(apiKeyRes.userId).toBe(userId);
|
||||||
|
expect(apiKeyRes.name).toBe("Integration Test Key");
|
||||||
|
|
||||||
|
// 3. Verify API key with verification endpoint
|
||||||
|
const verifyRes = await auth.api.verifyApiKey({
|
||||||
|
body: { key: apiKeyRes.key },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(verifyRes.valid).toBe(true);
|
||||||
|
|
||||||
|
// 4. List API keys to verify it appears
|
||||||
|
const keysRes = await auth.api.listApiKeys({
|
||||||
|
headers: authHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(keysRes).toBeDefined();
|
||||||
|
expect(Array.isArray(keysRes)).toBe(true);
|
||||||
|
expect(keysRes.length).toBe(1);
|
||||||
|
expect(keysRes?.[0]?.id).toBe(apiKeyRes.id);
|
||||||
|
|
||||||
|
// 5. Delete API key
|
||||||
|
const deleteRes = await auth.api.deleteApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { keyId: apiKeyRes.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deleteRes.success).toBe(true);
|
||||||
|
|
||||||
|
// 6. Verify API key no longer works
|
||||||
|
const invalidVerifyRes = await auth.api.verifyApiKey({
|
||||||
|
body: { key: apiKeyRes.key },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(invalidVerifyRes.valid).toBe(false);
|
||||||
|
|
||||||
|
// 7. Cleanup user
|
||||||
|
await authHelper.cleanupUser(authHeaders, user.password);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple concurrent API keys per user", async () => {
|
||||||
|
const { user, authHeaders } = await authHelper.createAuthenticatedUser();
|
||||||
|
|
||||||
|
// Create multiple API keys
|
||||||
|
const [key1, key2, key3] = await Promise.all([
|
||||||
|
authHelper.createApiKey(authHeaders, { name: "Key 1" }),
|
||||||
|
authHelper.createApiKey(authHeaders, { name: "Key 2" }),
|
||||||
|
authHelper.createApiKey(authHeaders, { name: "Key 3" }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Verify all keys work
|
||||||
|
const [verify1, verify2, verify3] = await Promise.all([
|
||||||
|
auth.api.verifyApiKey({ body: { key: key1.key } }),
|
||||||
|
auth.api.verifyApiKey({ body: { key: key2.key } }),
|
||||||
|
auth.api.verifyApiKey({ body: { key: key3.key } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(verify1.valid).toBe(true);
|
||||||
|
expect(verify2.valid).toBe(true);
|
||||||
|
expect(verify3.valid).toBe(true);
|
||||||
|
|
||||||
|
// List all keys
|
||||||
|
const allKeys = await auth.api.listApiKeys({ headers: authHeaders });
|
||||||
|
expect(allKeys.length).toBe(3);
|
||||||
|
|
||||||
|
// Delete one key
|
||||||
|
await auth.api.deleteApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { keyId: key2.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify only 2 keys remain
|
||||||
|
const remainingKeys = await auth.api.listApiKeys({ headers: authHeaders });
|
||||||
|
expect(remainingKeys.length).toBe(2);
|
||||||
|
expect(remainingKeys.find((k) => k.id === key2.id)).toBeUndefined();
|
||||||
|
|
||||||
|
// Verify deleted key doesn't work
|
||||||
|
const deletedVerify = await auth.api.verifyApiKey({
|
||||||
|
body: { key: key2.key },
|
||||||
|
});
|
||||||
|
expect(deletedVerify.valid).toBe(false);
|
||||||
|
|
||||||
|
// Verify other keys still work
|
||||||
|
const [stillWorking1, stillWorking3] = await Promise.all([
|
||||||
|
auth.api.verifyApiKey({ body: { key: key1.key } }),
|
||||||
|
auth.api.verifyApiKey({ body: { key: key3.key } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(stillWorking1.valid).toBe(true);
|
||||||
|
expect(stillWorking3.valid).toBe(true);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
await authHelper.cleanupUser(authHeaders, user.password);
|
||||||
|
});
|
||||||
|
});
|
125
api/src/test/auth/user.test.ts
Normal file
125
api/src/test/auth/user.test.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { describe, expect, it } from "bun:test";
|
||||||
|
import { db } from "@/db";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
import { buildHeaders } from "../utils";
|
||||||
|
|
||||||
|
describe("User Authentication", () => {
|
||||||
|
const user = {
|
||||||
|
email: "test-user@example.com",
|
||||||
|
password: "password123",
|
||||||
|
name: "Test User",
|
||||||
|
username: "testuser",
|
||||||
|
};
|
||||||
|
|
||||||
|
let authHeaders: Headers;
|
||||||
|
|
||||||
|
describe("User Registration", () => {
|
||||||
|
it("creates a new user successfully", async () => {
|
||||||
|
const res = await auth.api.signUpEmail({
|
||||||
|
body: {
|
||||||
|
...user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.user.email).toBe(user.email);
|
||||||
|
expect(res.user.name).toBe(user.name);
|
||||||
|
// Note: Username field may not be returned in the response even if set
|
||||||
|
|
||||||
|
const foundUser = await db.query.user.findFirst({
|
||||||
|
where: (users, { eq }) => eq(users.email, user.email),
|
||||||
|
});
|
||||||
|
expect(foundUser).not.toBeNull();
|
||||||
|
expect(foundUser?.email).toBe(user.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prevents duplicate user registration", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.signUpEmail({
|
||||||
|
body: {
|
||||||
|
...user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("User Login", () => {
|
||||||
|
it("logs in user with correct credentials", async () => {
|
||||||
|
const { headers: signInHeaders, response: signInRes } =
|
||||||
|
await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email: user.email,
|
||||||
|
password: user.password,
|
||||||
|
},
|
||||||
|
returnHeaders: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(signInRes.token).not.toBeNull();
|
||||||
|
expect(signInRes.user.name).toBe(user.name);
|
||||||
|
expect(signInRes.user.email).toBe(user.email);
|
||||||
|
|
||||||
|
authHeaders = buildHeaders(signInHeaders);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects login with incorrect password", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email: user.email,
|
||||||
|
password: "wrongpassword",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects login with non-existent email", async () => {
|
||||||
|
try {
|
||||||
|
await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email: "nonexistent@example.com",
|
||||||
|
password: user.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(true).toBe(false); // Should not reach here
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Session Management", () => {
|
||||||
|
it("retrieves valid session with auth headers", async () => {
|
||||||
|
const res = await auth.api.getSession({ headers: authHeaders });
|
||||||
|
expect(res?.user.username).toBe(user.username);
|
||||||
|
expect(res?.user.email).toBe(user.email);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for session without auth headers", async () => {
|
||||||
|
const res = await auth.api.getSession({ headers: new Headers() });
|
||||||
|
expect(res?.user).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("User Deletion", () => {
|
||||||
|
it("deletes user with correct password", async () => {
|
||||||
|
const deleteRes = await auth.api.deleteUser({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: {
|
||||||
|
password: user.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(deleteRes.success).toBe(true);
|
||||||
|
|
||||||
|
// Verify user is actually deleted
|
||||||
|
const foundUser = await db.query.user.findFirst({
|
||||||
|
where: (users, { eq }) => eq(users.email, user.email),
|
||||||
|
});
|
||||||
|
expect(foundUser).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
17
api/src/test/setup.ts
Normal file
17
api/src/test/setup.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { afterAll, beforeAll } from "bun:test";
|
||||||
|
import { migrate } from "drizzle-orm/bun-sql/migrator";
|
||||||
|
import { db } from "@/db";
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await migrate(db, { migrationsFolder: "./drizzle" });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// Clear all test data from database - note: order matters due to foreign keys
|
||||||
|
const { session, apikey, account, user } = await import("@/db/schema/auth");
|
||||||
|
|
||||||
|
await db.delete(session).execute();
|
||||||
|
await db.delete(apikey).execute();
|
||||||
|
await db.delete(account).execute();
|
||||||
|
await db.delete(user).execute();
|
||||||
|
});
|
@ -1,3 +1,6 @@
|
|||||||
|
import { db } from "@/db";
|
||||||
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
export function buildHeaders(signInHeaders: Headers) {
|
export function buildHeaders(signInHeaders: Headers) {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
for (const cookie of signInHeaders.getSetCookie() ?? []) {
|
for (const cookie of signInHeaders.getSetCookie() ?? []) {
|
||||||
@ -5,3 +8,94 @@ export function buildHeaders(signInHeaders: Headers) {
|
|||||||
}
|
}
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TestUser {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthTestHelper {
|
||||||
|
createTestUser(userOverrides: Partial<TestUser> = {}): TestUser {
|
||||||
|
const defaultUser: TestUser = {
|
||||||
|
email: `test-${Date.now()}@example.com`,
|
||||||
|
password: "password123",
|
||||||
|
name: "Test User",
|
||||||
|
username: `testuser${Date.now()}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...defaultUser, ...userOverrides };
|
||||||
|
}
|
||||||
|
|
||||||
|
async signUpUser(user: TestUser) {
|
||||||
|
return await auth.api.signUpEmail({
|
||||||
|
body: user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async signInUser(user: Pick<TestUser, "email" | "password">) {
|
||||||
|
return await auth.api.signInEmail({
|
||||||
|
body: {
|
||||||
|
email: user.email,
|
||||||
|
password: user.password,
|
||||||
|
},
|
||||||
|
returnHeaders: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAuthenticatedUser(userOverrides: Partial<TestUser> = {}) {
|
||||||
|
const user = this.createTestUser(userOverrides);
|
||||||
|
const signUpRes = await this.signUpUser(user);
|
||||||
|
const { headers: signInHeaders } = await this.signInUser(user);
|
||||||
|
const authHeaders = buildHeaders(signInHeaders);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
userId: signUpRes.user.id,
|
||||||
|
authHeaders,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanupUser(authHeaders: Headers, password: string) {
|
||||||
|
try {
|
||||||
|
await auth.api.deleteUser({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: { password },
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
// User might already be deleted, ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createApiKey(
|
||||||
|
authHeaders: Headers,
|
||||||
|
keyOptions: Partial<
|
||||||
|
Parameters<typeof auth.api.createApiKey>["0"]["body"]
|
||||||
|
> = {},
|
||||||
|
) {
|
||||||
|
return await auth.api.createApiKey({
|
||||||
|
headers: authHeaders,
|
||||||
|
body: {
|
||||||
|
name: `Test API Key ${Date.now()}`,
|
||||||
|
...keyOptions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createApiKeyHeaders(apiKey: string) {
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.set("Authorization", `Bearer ${apiKey}`);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearDatabase() {
|
||||||
|
// Clear all test data from database - note: order matters due to foreign keys
|
||||||
|
const { session, apikey, account, user } = await import("@/db/schema/auth");
|
||||||
|
|
||||||
|
await db.delete(session).execute();
|
||||||
|
await db.delete(apikey).execute();
|
||||||
|
await db.delete(account).execute();
|
||||||
|
await db.delete(user).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user