kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { eq } from "drizzle-orm";
2import { beforeEach, describe, expect, it } from "vitest";
3import db, { schema } from "../../apps/api/src/database";
4import { createApp } from "../../apps/api/src/index";
5import { mockAnonymousSession, mockAuthenticatedSession } from "./helpers/auth";
6import { resetTestDatabase } from "./helpers/database";
7import { createWorkspaceMember } from "./helpers/fixtures";
8
9describe("API integration: project creation", () => {
10 beforeEach(async () => {
11 await resetTestDatabase();
12 });
13
14 it("rejects unauthenticated project creation requests", async () => {
15 mockAnonymousSession();
16 const { app } = createApp();
17
18 const response = await app.request("/api/project", {
19 method: "POST",
20 headers: {
21 "content-type": "application/json",
22 },
23 body: JSON.stringify({
24 workspaceId: "workspace-missing",
25 name: "Unauthorized Project",
26 icon: "Folder",
27 slug: "unauthorized-project",
28 }),
29 });
30
31 expect(response.status).toBe(401);
32 await expect(response.text()).resolves.toBe("Unauthorized");
33 });
34
35 it("creates a project for a workspace member and seeds default columns", async () => {
36 const member = await createWorkspaceMember();
37 mockAuthenticatedSession(member.user);
38 const { app } = createApp();
39
40 const response = await app.request("/api/project", {
41 method: "POST",
42 headers: {
43 "content-type": "application/json",
44 },
45 body: JSON.stringify({
46 workspaceId: member.workspace.id,
47 name: "Roadmap",
48 icon: "FolderKanban",
49 slug: "roadmap",
50 }),
51 });
52
53 expect(response.status).toBe(200);
54 const payload =
55 (await response.json()) as typeof schema.projectTable.$inferSelect;
56
57 expect(payload).toMatchObject({
58 workspaceId: member.workspace.id,
59 name: "Roadmap",
60 icon: "FolderKanban",
61 slug: "roadmap",
62 });
63
64 const persistedProject = await db.query.projectTable.findFirst({
65 where: eq(schema.projectTable.id, payload.id),
66 });
67
68 expect(persistedProject).toMatchObject({
69 id: payload.id,
70 workspaceId: member.workspace.id,
71 name: "Roadmap",
72 slug: "roadmap",
73 });
74
75 const columns = await db.query.columnTable.findMany({
76 where: eq(schema.columnTable.projectId, payload.id),
77 orderBy: (column, { asc }) => [asc(column.position)],
78 });
79
80 expect(columns).toHaveLength(4);
81 expect(columns.map((column) => column.slug)).toEqual([
82 "to-do",
83 "in-progress",
84 "in-review",
85 "done",
86 ]);
87 expect(columns.map((column) => column.isFinal)).toEqual([
88 false,
89 false,
90 false,
91 true,
92 ]);
93 });
94
95 it("rejects project creation for users outside the workspace", async () => {
96 const member = await createWorkspaceMember();
97 const outsiderId = "user-outsider";
98
99 const [outsider] = await db
100 .insert(schema.userTable)
101 .values({
102 id: outsiderId,
103 email: `${outsiderId}@example.com`,
104 emailVerified: true,
105 name: "Outsider",
106 })
107 .returning();
108
109 mockAuthenticatedSession(outsider);
110 const { app } = createApp();
111
112 const response = await app.request("/api/project", {
113 method: "POST",
114 headers: {
115 "content-type": "application/json",
116 },
117 body: JSON.stringify({
118 workspaceId: member.workspace.id,
119 name: "Forbidden Project",
120 icon: "Folder",
121 slug: "forbidden-project",
122 }),
123 });
124
125 expect(response.status).toBe(403);
126 await expect(response.text()).resolves.toBe(
127 "You don't have access to this workspace",
128 );
129 });
130});