diff --git a/.github/labeler.yml b/.github/labeler.yml index 679e499..ace25c5 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -21,7 +21,7 @@ frontend: testing: - changed-files: - - any-glob-to-any-file: ['tests/**', '**.test.{ts,tsx}', 'playwright.config.ts'] + - any-glob-to-any-file: ['tests/**', '**/*.test.ts', '**/*.test.tsx', 'playwright.config.ts'] workflows: - changed-files: diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 474e668..4d98667 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,9 +1,10 @@ -name: Pull Request Labeler +name: PR Labeler on: - pull_request_target jobs: labeler: + name: Labeler permissions: contents: read pull-requests: write diff --git a/.github/workflows/relative-ci.yml b/.github/workflows/relative-ci.yml index 02803e8..da2ba1c 100644 --- a/.github/workflows/relative-ci.yml +++ b/.github/workflows/relative-ci.yml @@ -16,7 +16,7 @@ permissions: jobs: build: - name: Build and report bundle stats + name: Report Bundle Size runs-on: ubuntu-latest steps: diff --git a/convex/auth.test.ts b/convex/auth.test.ts index e41a227..e7d56ab 100644 --- a/convex/auth.test.ts +++ b/convex/auth.test.ts @@ -7,6 +7,25 @@ import { modules } from "./test.setup"; describe("auth", () => { describe("createOrUpdateUser", () => { + it("should return existing user if id already exists", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "testUser@test.com", + role: "user", + }); + }); + + const user = await t.run(async (ctx) => { + return await createOrUpdateUser(ctx, { + existingUserId: userId, + }); + }); + + expect(user).toBe(userId); + }); + it("should set role to admin in development environment", async () => { const t = convexTest(schema, modules); @@ -46,5 +65,25 @@ describe("auth", () => { expect(user?.role).toBe("user"); vi.unstubAllEnvs(); }); + + it("should initialize default user settings", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await createOrUpdateUser(ctx, { + profile: { + email: "testUser@test.com", + }, + }); + }); + + const userSettings = await t.query(api.userSettings.getByUserId, { + userId, + }); + + expect(userSettings).toBeDefined(); + expect(userSettings?.theme).toBe("system"); + expect(userSettings?.groupQuestsBy).toBe("dateAdded"); + }); }); }); diff --git a/convex/userQuests.test.ts b/convex/userQuests.test.ts new file mode 100644 index 0000000..78ee976 --- /dev/null +++ b/convex/userQuests.test.ts @@ -0,0 +1,528 @@ +import { convexTest } from "convex-test"; +import { describe, expect, it } from "vitest"; +import { api } from "./_generated/api"; +import schema from "./schema"; +import { modules } from "./test.setup"; + +describe("userQuests", () => { + describe("getAll", () => { + it("should return all user quests", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + // Create quests + const quest1Id = await ctx.db.insert("quests", { + title: "Test Quest 1", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + const quest2Id = await ctx.db.insert("quests", { + title: "Test Quest 2", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + // Create user quests + await ctx.db.insert("userQuests", { + userId, + questId: quest1Id, + status: "active", + }); + + await ctx.db.insert("userQuests", { + userId, + questId: quest2Id, + status: "completed", + }); + }); + + const quests = await asUser.query(api.userQuests.getAll, {}); + expect(quests).toHaveLength(2); + expect(quests.map((q) => q.title)).toContain("Test Quest 1"); + expect(quests.map((q) => q.title)).toContain("Test Quest 2"); + }); + + it("should not return deleted quests", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + const questId = await ctx.db.insert("quests", { + title: "Deleted Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + deletionTime: Date.now(), + }); + + await ctx.db.insert("userQuests", { + userId, + questId, + status: "active", + }); + }); + + const quests = await asUser.query(api.userQuests.getAll, {}); + expect(quests).toHaveLength(0); + }); + }); + + describe("count", () => { + it("should return count of user quests", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + const questId = await ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + await ctx.db.insert("userQuests", { + userId, + questId, + status: "active", + }); + }); + + const count = await asUser.query(api.userQuests.count, {}); + expect(count).toBe(1); + }); + }); + + describe("create", () => { + it("should create a user quest", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await asUser.run(async (ctx) => { + return await ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + await t.run(async (ctx) => { + const userQuest = await ctx.db + .query("userQuests") + .withIndex("userId", (q) => q.eq("userId", userId)) + .first(); + expect(userQuest?.questId).toBe(questId); + }); + }); + + it("should set a default status", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await asUser.run(async (ctx) => { + return await ctx.db.insert("quests", { + title: "Test Quest", + category: "core", + jurisdiction: "MA", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + const status = await asUser.query(api.userQuests.getStatus, { questId }); + expect(status).toBe("notStarted"); + }); + + it("should throw if quest already exists", async () => { + // ADD + }); + }); + + describe("setStatus", async () => { + it("should update quest status", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + await asUser.mutation(api.userQuests.setStatus, { + questId, + status: "complete", + }); + + const status = await asUser.query(api.userQuests.getStatus, { questId }); + expect(status).toBe("complete"); + }); + + it("should throw error if status is invalid", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "core", + jurisdiction: "MA", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + await expect( + asUser.mutation(api.userQuests.setStatus, { + questId, + status: "invalid", + }), + ).rejects.toThrow("Invalid status"); + }); + + it("should prevent setting 'filed' status on non-core quests", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const nonCoreQuestId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "housing", + jurisdiction: "MA", + creationUser: userId, + }); + }); + + const coreQuestId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "core", + jurisdiction: "MA", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId: nonCoreQuestId }); + await asUser.mutation(api.userQuests.create, { questId: coreQuestId }); + + await expect( + asUser.mutation(api.userQuests.setStatus, { + questId: nonCoreQuestId, + status: "filed", + }), + ).rejects.toThrow("This status is reserved for core quests only."); + + await expect( + asUser.mutation(api.userQuests.setStatus, { + questId: coreQuestId, + status: "filed", + }), + ).resolves.toBeNull(); + }); + + it("should add completionTime when status changed to complete", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + const userQuest = await asUser.query(api.userQuests.getByQuestId, { + questId, + }); + + expect(userQuest?.status).toBe("notStarted"); + expect(userQuest?.completionTime).toBeUndefined(); + + await asUser.mutation(api.userQuests.setStatus, { + questId, + status: "complete", + }); + + const updatedUserQuest = await asUser.query(api.userQuests.getByQuestId, { + questId, + }); + + expect(updatedUserQuest?.status).toBe("complete"); + expect(updatedUserQuest?.completionTime).toBeTypeOf("number"); // Unix timestamp + }); + + it("should remove completionTime when status changed away from complete", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await t.run(async (ctx) => { + return ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + await asUser.mutation(api.userQuests.setStatus, { + questId, + status: "complete", + }); + + const updatedUserQuest = await asUser.query(api.userQuests.getByQuestId, { + questId, + }); + + expect(updatedUserQuest?.status).toBe("complete"); + expect(updatedUserQuest?.completionTime).toBeTypeOf("number"); + + await asUser.mutation(api.userQuests.setStatus, { + questId, + status: "notStarted", + }); + + const updatedQuest = await asUser.query(api.userQuests.getByQuestId, { + questId, + }); + + expect(updatedQuest?.status).toBe("notStarted"); + expect(updatedQuest?.completionTime).toBeUndefined(); + }); + }); + + describe("deleteForever", () => { + it("should delete a user quest", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + const questId = await t.run(async (ctx) => { + return await ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + }); + + await asUser.mutation(api.userQuests.create, { questId }); + + expect( + await asUser.query(api.userQuests.getByQuestId, { questId }), + ).not.toBeNull(); + + await asUser.mutation(api.userQuests.deleteForever, { questId }); + + expect( + await asUser.query(api.userQuests.getByQuestId, { questId }), + ).toBeNull(); + }); + }); + + describe("getByCategory", () => { + it("should return quests grouped by category", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + const quest1Id = await ctx.db.insert("quests", { + title: "Test Quest 1", + category: "core", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + const quest2Id = await ctx.db.insert("quests", { + title: "Test Quest 2", + category: "housing", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + await ctx.db.insert("userQuests", { + userId, + questId: quest1Id, + status: "active", + }); + + await ctx.db.insert("userQuests", { + userId, + questId: quest2Id, + status: "active", + }); + }); + + const questsByCategory = await asUser.query( + api.userQuests.getByCategory, + {}, + ); + expect(Object.keys(questsByCategory)).toContain("core"); + expect(Object.keys(questsByCategory)).toContain("housing"); + expect(questsByCategory.core).toHaveLength(1); + expect(questsByCategory.housing).toHaveLength(1); + }); + }); + + describe("getByStatus", () => { + it("should return quests grouped by status", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + const quest1Id = await ctx.db.insert("quests", { + title: "Active Quest", + category: "core", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + const quest2Id = await ctx.db.insert("quests", { + title: "Completed Quest", + category: "housing", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + await ctx.db.insert("userQuests", { + userId, + questId: quest1Id, + status: "inProgress", + }); + + await ctx.db.insert("userQuests", { + userId, + questId: quest2Id, + status: "complete", + }); + }); + + const questsByStatus = await asUser.query(api.userQuests.getByStatus, {}); + expect(Object.keys(questsByStatus)).toContain("inProgress"); + expect(Object.keys(questsByStatus)).toContain("complete"); + expect(questsByStatus.inProgress).toHaveLength(1); + expect(questsByStatus.complete).toHaveLength(1); + }); + }); +}); diff --git a/convex/userQuests.ts b/convex/userQuests.ts index 5e24f77..65a8fe4 100644 --- a/convex/userQuests.ts +++ b/convex/userQuests.ts @@ -147,7 +147,10 @@ export const setStatus = userMutation({ }); if (userQuest === null) throw new Error("User quest not found"); - // Prevent setting "ready to file" and "filed" on non-core quests + // Throw if status is invalid + if (!STATUS[args.status as Status]) throw new Error("Invalid status"); + + // Prevent setting "filed" on non-core quests if ( STATUS[args.status as Status].isCoreOnly === true && quest.category !== "core" diff --git a/convex/userSettings.test.ts b/convex/userSettings.test.ts new file mode 100644 index 0000000..a0bb33f --- /dev/null +++ b/convex/userSettings.test.ts @@ -0,0 +1,149 @@ +import { convexTest } from "convex-test"; +import { describe, expect, it } from "vitest"; +import { api } from "./_generated/api"; +import schema from "./schema"; +import { modules } from "./test.setup"; + +describe("userSettings", () => { + describe("getByUserId", () => { + it("should throw error if user settings not found", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + await expect( + t.query(api.userSettings.getByUserId, { userId }), + ).rejects.toThrow("User settings not found"); + }); + + it("should return user settings if found", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const settingsId = await t.run(async (ctx) => { + return await ctx.db.insert("userSettings", { + userId, + theme: "dark", + groupQuestsBy: "category", + }); + }); + + const settings = await t.query(api.userSettings.getByUserId, { userId }); + + expect(settings._id).toBe(settingsId); + expect(settings.theme).toBe("dark"); + expect(settings.groupQuestsBy).toBe("category"); + }); + }); + + describe("setTheme", () => { + it("should throw error if user settings not found", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await expect( + asUser.mutation(api.userSettings.setTheme, { + theme: "dark", + }), + ).rejects.toThrow("User settings not found"); + }); + + it("should update existing user settings theme", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + return await ctx.db.insert("userSettings", { + userId, + theme: "light", + groupQuestsBy: "category", + }); + }); + + await asUser.mutation(api.userSettings.setTheme, { + theme: "dark", + }); + + const settings = await t.query(api.userSettings.getByUserId, { userId }); + expect(settings.theme).toBe("dark"); + expect(settings.groupQuestsBy).toBe("category"); // unchanged + }); + }); + + describe("setGroupQuestsBy", () => { + it("should throw error if user settings not found", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await expect( + asUser.mutation(api.userSettings.setGroupQuestsBy, { + groupQuestsBy: "status", + }), + ).rejects.toThrow("User settings not found"); + }); + + it("should update existing user settings groupQuestsBy", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + + await t.run(async (ctx) => { + return await ctx.db.insert("userSettings", { + userId, + theme: "dark", + groupQuestsBy: "category", + }); + }); + + await asUser.mutation(api.userSettings.setGroupQuestsBy, { + groupQuestsBy: "status", + }); + + const settings = await t.query(api.userSettings.getByUserId, { userId }); + expect(settings.groupQuestsBy).toBe("status"); + expect(settings.theme).toBe("dark"); // unchanged + }); + }); +}); diff --git a/convex/userSettings.ts b/convex/userSettings.ts index 6e39a32..a598fbc 100644 --- a/convex/userSettings.ts +++ b/convex/userSettings.ts @@ -1,6 +1,22 @@ +import { v } from "convex/values"; +import { query } from "./_generated/server"; import { userMutation } from "./helpers"; import { groupQuestsBy, theme } from "./validators"; +export const getByUserId = query({ + args: { userId: v.id("users") }, + handler: async (ctx, args) => { + const userSettings = await ctx.db + .query("userSettings") + .withIndex("userId", (q) => q.eq("userId", args.userId)) + .first(); + + if (!userSettings) throw new Error("User settings not found"); + + return userSettings; + }, +}); + export const setTheme = userMutation({ args: { theme: theme }, handler: async (ctx, args) => { diff --git a/convex/users.test.ts b/convex/users.test.ts new file mode 100644 index 0000000..19abe6a --- /dev/null +++ b/convex/users.test.ts @@ -0,0 +1,260 @@ +import { convexTest } from "convex-test"; +import { describe, expect, it } from "vitest"; +import { api } from "./_generated/api"; +import schema from "./schema"; +import { modules } from "./test.setup"; + +describe("users", () => { + describe("getAll", () => { + it("should return all users", async () => { + const t = convexTest(schema, modules); + + await t.run(async (ctx) => { + await ctx.db.insert("users", { + email: "test1@example.com", + role: "user", + }); + + await ctx.db.insert("users", { + email: "test2@example.com", + role: "admin", + }); + }); + + const users = await t.query(api.users.getAll, {}); + expect(users).toHaveLength(2); + expect(users.map((u) => u.email)).toContain("test1@example.com"); + expect(users.map((u) => u.email)).toContain("test2@example.com"); + }); + }); + + describe("getCurrent", () => { + it("should return the current user", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + name: "Test User", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + const user = await asUser.query(api.users.getCurrent, {}); + expect(user?.email).toBe("test@example.com"); + expect(user?.name).toBe("Test User"); + }); + + it("should return null if user not found", async () => { + const t = convexTest(schema, modules); + const asUser = t.withIdentity({ subject: "invalid_id" }); + const user = await asUser.query(api.users.getCurrent, {}); + expect(user).toBeNull(); + }); + }); + + describe("getCurrentRole", () => { + it("should return the current user's role", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "admin", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + const role = await asUser.query(api.users.getCurrentRole, {}); + expect(role).toBe("admin"); + }); + + it("should return null if user not authenticated", async () => { + const t = convexTest(schema, modules); + const role = await t.query(api.users.getCurrentRole, {}); + expect(role).toBeNull(); + }); + }); + + describe("getByEmail", () => { + it("should return user by email", async () => { + const t = convexTest(schema, modules); + + await t.run(async (ctx) => { + await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + name: "Test User", + }); + }); + + const user = await t.query(api.users.getByEmail, { + email: "test@example.com", + }); + expect(user?.name).toBe("Test User"); + }); + + it("should return null if user not found", async () => { + const t = convexTest(schema, modules); + const user = await t.query(api.users.getByEmail, { + email: "nonexistent@example.com", + }); + expect(user).toBeNull(); + }); + }); + + describe("setName", () => { + it("should update user name", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + await asUser.mutation(api.users.setName, { + name: "New Name", + }); + + const user = await t.run(async (ctx) => { + return await ctx.db.get(userId); + }); + expect(user?.name).toBe("New Name"); + }); + + it("should clear user name when undefined", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + name: "Test User", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + await asUser.mutation(api.users.setName, { + name: undefined, + }); + + const user = await t.run(async (ctx) => { + return await ctx.db.get(userId); + }); + expect(user?.name).toBeUndefined(); + }); + }); + + describe("setBirthplace", () => { + it("should update user birthplace", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + await asUser.mutation(api.users.setBirthplace, { + birthplace: "CA", + }); + + const user = await t.run(async (ctx) => { + return await ctx.db.get(userId); + }); + expect(user?.birthplace).toBe("CA"); + }); + }); + + describe("setCurrentUserIsMinor", () => { + it("should update user isMinor status", async () => { + const t = convexTest(schema, modules); + + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + const asUser = t.withIdentity({ subject: userId }); + await asUser.mutation(api.users.setCurrentUserIsMinor, { + isMinor: true, + }); + + const user = await t.run(async (ctx) => { + return await ctx.db.get(userId); + }); + expect(user?.isMinor).toBe(true); + }); + }); + + describe("deleteCurrentUser", () => { + it("should delete user and all associated data", async () => { + const t = convexTest(schema, modules); + + // Create test user + const userId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + email: "test@example.com", + role: "user", + }); + }); + + // Create associated data + await t.run(async (ctx) => { + // Create user settings + await ctx.db.insert("userSettings", { + userId, + theme: "dark", + groupQuestsBy: "category", + }); + + // Create quest + const questId = await ctx.db.insert("quests", { + title: "Test Quest", + category: "Test Category", + jurisdiction: "Test Jurisdiction", + creationUser: userId, + }); + + // Create user quest + await ctx.db.insert("userQuests", { + userId, + questId, + status: "active", + }); + }); + + // Delete user and verify all data is deleted + const asUser = t.withIdentity({ subject: userId }); + await asUser.mutation(api.users.deleteCurrentUser, {}); + + await t.run(async (ctx) => { + // Verify user is deleted + const user = await ctx.db.get(userId); + expect(user).toBeNull(); + + // Verify user settings are deleted + const settings = await ctx.db + .query("userSettings") + .withIndex("userId", (q) => q.eq("userId", userId)) + .first(); + expect(settings).toBeNull(); + + // Verify user quests are deleted + const quests = await ctx.db + .query("userQuests") + .withIndex("userId", (q) => q.eq("userId", userId)) + .collect(); + expect(quests).toHaveLength(0); + }); + }); + }); +});