Skip to main content
Glama

Karakeep MCP server

by karakeep-app
invites.test.ts17.8 kB
import { eq } from "drizzle-orm"; import { beforeEach, describe, expect, test, vi } from "vitest"; import { invites, users } from "@karakeep/db/schema"; import type { CustomTestContext } from "../testUtils"; import { defaultBeforeEach, getApiCaller } from "../testUtils"; // Mock server config with email settings vi.mock("@karakeep/shared/config", async (original) => { const mod = (await original()) as typeof import("@karakeep/shared/config"); return { ...mod, default: { ...mod.default, email: { smtp: { host: "test-smtp.example.com", port: 587, secure: false, user: "test@example.com", password: "test-password", from: "test@example.com", }, }, }, }; }); // Mock email functions vi.mock("../email", () => ({ sendInviteEmail: vi.fn().mockResolvedValue(undefined), })); beforeEach<CustomTestContext>(defaultBeforeEach(false)); describe("Invites Router", () => { test<CustomTestContext>("admin can create invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); expect(invite.email).toBe("newuser@test.com"); expect(invite.id).toBeDefined(); // Verify the invite was created in the database const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); expect(dbInvite?.invitedBy).toBe(admin.id); expect(dbInvite?.usedAt).toBeNull(); expect(dbInvite?.token).toBeDefined(); }); test<CustomTestContext>("non-admin cannot create invite", async ({ db, unauthedAPICaller, }) => { await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const user = await unauthedAPICaller.users.create({ name: "Regular User", email: "user@test.com", password: "pass1234", confirmPassword: "pass1234", }); const userCaller = getApiCaller(db, user.id, user.email); await expect(() => userCaller.invites.create({ email: "newuser@test.com", }), ).rejects.toThrow(/FORBIDDEN/); }); test<CustomTestContext>("cannot invite existing user", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); await unauthedAPICaller.users.create({ name: "Existing User", email: "existing@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); await expect(() => adminCaller.invites.create({ email: "existing@test.com", }), ).rejects.toThrow(/User with this email already exists/); }); test<CustomTestContext>("cannot create duplicate pending invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); await adminCaller.invites.create({ email: "newuser@test.com", }); await expect(() => adminCaller.invites.create({ email: "newuser@test.com", }), ).rejects.toThrow(/An active invite for this email already exists/); }); test<CustomTestContext>("admin can list invites", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); await adminCaller.invites.create({ email: "user1@test.com", }); await adminCaller.invites.create({ email: "user2@test.com", }); const result = await adminCaller.invites.list(); expect(result.invites).toHaveLength(2); expect( result.invites.find((i) => i.email === "user1@test.com"), ).toBeTruthy(); expect( result.invites.find((i) => i.email === "user2@test.com"), ).toBeTruthy(); }); test<CustomTestContext>("non-admin cannot list invites", async ({ db, unauthedAPICaller, }) => { await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const user = await unauthedAPICaller.users.create({ name: "Regular User", email: "user@test.com", password: "pass1234", confirmPassword: "pass1234", }); const userCaller = getApiCaller(db, user.id, user.email); await expect(() => userCaller.invites.list()).rejects.toThrow(/FORBIDDEN/); }); test<CustomTestContext>("can get invite by token", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); // Get the token from the database since it's not returned by create const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); const retrievedInvite = await unauthedAPICaller.invites.get({ token: dbInvite!.token, }); expect(retrievedInvite.email).toBe("newuser@test.com"); expect(retrievedInvite.expired).toBe(false); }); test<CustomTestContext>("cannot get invite with invalid token", async ({ unauthedAPICaller, }) => { await expect(() => unauthedAPICaller.invites.get({ token: "invalid-token", }), ).rejects.toThrow(/Invite not found/); }); test<CustomTestContext>("can get invite by token", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); const result = await unauthedAPICaller.invites.get({ token: dbInvite!.token, }); expect(result.email).toBe("newuser@test.com"); expect(result.expired).toBe(false); }); test<CustomTestContext>("cannot get used invite (deleted)", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); // Accept the invite (which deletes it) await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "newpass123", }); // Try to get the invite again - should fail await expect(() => unauthedAPICaller.invites.get({ token: dbInvite!.token, }), ).rejects.toThrow(/Invite not found or has been used/); }); test<CustomTestContext>("can accept valid invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); const newUser = await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "newpass123", }); expect(newUser.name).toBe("New User"); expect(newUser.email).toBe("newuser@test.com"); // Verify invite was deleted const deletedInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); expect(deletedInvite).toBeUndefined(); }); test<CustomTestContext>("can accept valid invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); const result = await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "newpass123", }); expect(result.email).toBe("newuser@test.com"); expect(result.name).toBe("New User"); }); test<CustomTestContext>("cannot accept used invite (deleted)", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); // Accept the invite first time await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "newpass123", }); // Try to accept again - should fail because invite is deleted await expect(() => unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "Another User", password: "anotherpass123", }), ).rejects.toThrow(/Invite not found or has been used/); }); test<CustomTestContext>("admin can revoke invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const result = await adminCaller.invites.revoke({ inviteId: invite.id, }); expect(result.success).toBe(true); // Verify the invite is deleted const revokedInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); expect(revokedInvite).toBeUndefined(); }); test<CustomTestContext>("non-admin cannot revoke invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const user = await unauthedAPICaller.users.create({ name: "Regular User", email: "user@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const userCaller = getApiCaller(db, user.id, user.email); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); await expect(() => userCaller.invites.revoke({ inviteId: invite.id, }), ).rejects.toThrow(/FORBIDDEN/); }); test<CustomTestContext>("admin can resend invite", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const originalDbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); await new Promise((resolve) => setTimeout(resolve, 10)); const resentInvite = await adminCaller.invites.resend({ inviteId: invite.id, }); expect(resentInvite.email).toBe("newuser@test.com"); expect(resentInvite.id).toBe(originalDbInvite!.id); // Verify token was updated in database const updatedDbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); expect(updatedDbInvite?.token).not.toBe(originalDbInvite?.token); }); test<CustomTestContext>("cannot resend used invite (deleted)", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); // Accept the invite (which deletes it) await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "newpass123", }); await expect(() => adminCaller.invites.resend({ inviteId: invite.id, }), ).rejects.toThrow(/Invite not found/); }); test<CustomTestContext>("invite creation works without expiration", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); expect(invite.email).toBe("newuser@test.com"); expect(invite.id).toBeDefined(); }); test<CustomTestContext>("invite includes inviter information", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const result = await adminCaller.invites.list(); const createdInvite = result.invites.find((i) => i.id === invite.id); expect(createdInvite?.invitedBy.id).toBe(admin.id); expect(createdInvite?.invitedBy.name).toBe("Admin User"); expect(createdInvite?.invitedBy.email).toBe("admin@test.com"); }); test<CustomTestContext>("all invites create user role", async ({ db, unauthedAPICaller, }) => { const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); const invite = await adminCaller.invites.create({ email: "newuser@test.com", }); const dbInvite = await db.query.invites.findFirst({ where: eq(invites.id, invite.id), }); const newUser = await unauthedAPICaller.invites.accept({ token: dbInvite!.token, name: "New User", password: "userpass123", }); const user = await db.query.users.findFirst({ where: eq(users.email, newUser.email), }); expect(user?.role).toBe("user"); }); test<CustomTestContext>("email sending is called during invite creation", async ({ db, unauthedAPICaller, }) => { // Mock the email module const mockSendInviteEmail = vi.fn().mockResolvedValue(undefined); vi.doMock("../email", () => ({ sendInviteEmail: mockSendInviteEmail, })); const admin = await unauthedAPICaller.users.create({ name: "Admin User", email: "admin@test.com", password: "pass1234", confirmPassword: "pass1234", }); const adminCaller = getApiCaller(db, admin.id, admin.email, "admin"); await adminCaller.invites.create({ email: "newuser@test.com", }); // Note: In a real test environment, we'd need to properly mock the email module // This test demonstrates the structure but may not actually verify the mock call // due to how the module is imported in the router }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/karakeep-app/karakeep'

If you have feedback or need assistance with the MCP directory API, please join our Discord server