import { convexTest } from "convex-test"; import { describe, it, expect } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; const modules = import.meta.glob("./**/*.ts"); const shippingAddress = { type: "shipping" as const, firstName: "Alice", lastName: "Smith", phone: "+447911123456", addressLine1: "10 Downing Street", city: "London", postalCode: "SW1A 2AA", country: "GB", }; describe("addresses", () => { it("add sets default correctly and second add unsets previous default", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list1 = await asA.query(api.addresses.list, {}); expect(list1).toHaveLength(1); expect(list1[0].isDefault).toBe(true); await asA.mutation(api.addresses.add, { ...shippingAddress, addressLine1: "221B Baker Street", isDefault: true, }); const list2 = await asA.query(api.addresses.list, {}); expect(list2).toHaveLength(2); expect(list2[0].isDefault).toBe(true); expect(list2[1].isDefault).toBe(false); }); it("delete default address promotes next to default", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await asA.mutation(api.addresses.add, { ...shippingAddress, addressLine1: "221B Baker Street", isDefault: false, }); const listBefore = await asA.query(api.addresses.list, {}); expect(listBefore).toHaveLength(2); const defaultId = listBefore[0]._id; await asA.mutation(api.addresses.remove, { id: defaultId }); const listAfter = await asA.query(api.addresses.list, {}); expect(listAfter).toHaveLength(1); expect(listAfter[0].isDefault).toBe(true); }); it("add derives fullName from firstName + lastName", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list = await asA.query(api.addresses.list, {}); expect(list[0].fullName).toBe("Alice Smith"); }); it("add persists additionalInformation when provided", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, additionalInformation: "Flat 4B", isDefault: true, }); const list = await asA.query(api.addresses.list, {}); expect(list[0].additionalInformation).toBe("Flat 4B"); }); it("add stores undefined for additionalInformation when not provided", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list = await asA.query(api.addresses.list, {}); expect(list[0].additionalInformation).toBeUndefined(); }); it("address records do not have state or addressLine2 fields", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list = await asA.query(api.addresses.list, {}); const addr = list[0] as Record; expect(addr).not.toHaveProperty("state"); expect(addr).not.toHaveProperty("addressLine2"); }); it("firstName and lastName are required and stored", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list = await asA.query(api.addresses.list, {}); expect(list[0].firstName).toBe("Alice"); expect(list[0].lastName).toBe("Smith"); }); it("isValidated defaults to false when passed via add mutation", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); const list = await asA.query(api.addresses.list, {}); expect(list).toHaveLength(1); expect(list[0].isValidated).toBe(false); }); it("add mutation persists isValidated: true when provided", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, isValidated: true, }); const list = await asA.query(api.addresses.list, {}); expect(list[0].isValidated).toBe(true); }); it("update mutation patches isValidated", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); const id = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await asA.mutation(api.addresses.update, { id, isValidated: true }); const list1 = await asA.query(api.addresses.list, {}); expect(list1[0].isValidated).toBe(true); await asA.mutation(api.addresses.update, { id, isValidated: false }); const list2 = await asA.query(api.addresses.list, {}); expect(list2[0].isValidated).toBe(false); }); it("update mutation patches additionalInformation", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); const id = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await asA.mutation(api.addresses.update, { id, additionalInformation: "Floor 3" }); const list = await asA.query(api.addresses.list, {}); expect(list[0].additionalInformation).toBe("Floor 3"); }); it("markValidated sets isValidated to true", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); const id = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); expect((await asA.query(api.addresses.list, {}))[0].isValidated).toBe(false); await asA.mutation(api.addresses.markValidated, { id, isValidated: true }); const list = await asA.query(api.addresses.list, {}); expect(list[0].isValidated).toBe(true); }); it("markValidated throws if user does not own address", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); const asB = t.withIdentity({ name: "Bob", email: "bob@example.com", subject: "clerk_bob_456", }); await asA.mutation(api.users.store, {}); await asB.mutation(api.users.store, {}); const id = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await expect( asB.mutation(api.addresses.markValidated, { id, isValidated: true }), ).rejects.toThrow(/Unauthorized|does not belong/); }); it("markValidated throws for non-existent address", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); await asA.mutation(api.users.store, {}); const fakeId = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await asA.mutation(api.addresses.remove, { id: fakeId }); await expect( asA.mutation(api.addresses.markValidated, { id: fakeId, isValidated: true }), ).rejects.toThrow(/not found/i); }); it("update throws if user does not own the address", async () => { const t = convexTest(schema, modules); const asA = t.withIdentity({ name: "Alice", email: "alice@example.com", subject: "clerk_alice_123", }); const asB = t.withIdentity({ name: "Bob", email: "bob@example.com", subject: "clerk_bob_456", }); await asA.mutation(api.users.store, {}); await asB.mutation(api.users.store, {}); const id = await asA.mutation(api.addresses.add, { ...shippingAddress, isDefault: true, }); await expect( asB.mutation(api.addresses.update, { id, fullName: "Hacker" }), ).rejects.toThrow(/Unauthorized|does not belong/); await asA.mutation(api.addresses.update, { id, fullName: "Alice Updated" }); const list = await asA.query(api.addresses.list, {}); expect(list[0].fullName).toBe("Alice Updated"); }); });