feat: initial commit — storefront, convex backend, and shared packages
Completes the first milestone of The Pet Loft ecommerce platform: - apps/storefront: full customer-facing Next.js app with HeroUI (cart, checkout, orders, wishlist, product detail, shop, search, auth) - convex/: serverless backend with schema, queries, mutations, actions, HTTP routes, Stripe/Shippo integrations, and co-located tests - packages/types, packages/utils, packages/convex: shared workspace packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
124
convex/categories.test.ts
Normal file
124
convex/categories.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
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");
|
||||
|
||||
async function setupAdminUser(t: ReturnType<typeof convexTest>) {
|
||||
const asAdmin = t.withIdentity({
|
||||
name: "Admin",
|
||||
email: "admin@example.com",
|
||||
subject: "clerk_admin_123",
|
||||
});
|
||||
const userId = await asAdmin.mutation(api.users.store, {});
|
||||
await t.run(async (ctx) => {
|
||||
await ctx.db.patch(userId, { role: "admin" });
|
||||
});
|
||||
return asAdmin;
|
||||
}
|
||||
|
||||
describe("categories", () => {
|
||||
it("list returns all categories ordered by name", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const asAdmin = await setupAdminUser(t);
|
||||
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Zebra",
|
||||
slug: "zebra",
|
||||
});
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Alpha",
|
||||
slug: "alpha",
|
||||
});
|
||||
|
||||
const list = await t.query(api.categories.list, {});
|
||||
expect(list).toHaveLength(2);
|
||||
expect(list[0].name).toBe("Alpha");
|
||||
expect(list[1].name).toBe("Zebra");
|
||||
});
|
||||
|
||||
it("list with parentId returns only children of that parent", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const asAdmin = await setupAdminUser(t);
|
||||
|
||||
const parentId = await asAdmin.mutation(api.categories.create, {
|
||||
name: "Parent",
|
||||
slug: "parent",
|
||||
});
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Child A",
|
||||
slug: "child-a",
|
||||
parentId,
|
||||
});
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Child B",
|
||||
slug: "child-b",
|
||||
parentId,
|
||||
});
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Other",
|
||||
slug: "other",
|
||||
});
|
||||
|
||||
const children = await t.query(api.categories.list, { parentId });
|
||||
expect(children).toHaveLength(2);
|
||||
expect(children.map((c) => c.name).sort()).toEqual(["Child A", "Child B"]);
|
||||
});
|
||||
|
||||
it("getBySlug returns category when slug exists", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const asAdmin = await setupAdminUser(t);
|
||||
|
||||
await asAdmin.mutation(api.categories.create, {
|
||||
name: "Pet Food",
|
||||
slug: "pet-food",
|
||||
});
|
||||
|
||||
const category = await t.query(api.categories.getBySlug, {
|
||||
slug: "pet-food",
|
||||
});
|
||||
expect(category).not.toBeNull();
|
||||
expect(category?.name).toBe("Pet Food");
|
||||
expect(category?.slug).toBe("pet-food");
|
||||
});
|
||||
|
||||
it("getBySlug returns null for unknown slug", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const category = await t.query(api.categories.getBySlug, {
|
||||
slug: "does-not-exist",
|
||||
});
|
||||
expect(category).toBeNull();
|
||||
});
|
||||
|
||||
it("create succeeds for admin users", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const asAdmin = await setupAdminUser(t);
|
||||
|
||||
const id = await asAdmin.mutation(api.categories.create, {
|
||||
name: "Toys",
|
||||
slug: "toys",
|
||||
});
|
||||
expect(id).toBeTruthy();
|
||||
|
||||
const category = await t.query(api.categories.getBySlug, { slug: "toys" });
|
||||
expect(category?.name).toBe("Toys");
|
||||
});
|
||||
|
||||
it("create throws for non-admin users", async () => {
|
||||
const t = convexTest(schema, modules);
|
||||
const asCustomer = t.withIdentity({
|
||||
name: "Customer",
|
||||
email: "customer@example.com",
|
||||
subject: "clerk_customer_123",
|
||||
});
|
||||
await asCustomer.mutation(api.users.store, {});
|
||||
|
||||
await expect(
|
||||
asCustomer.mutation(api.categories.create, {
|
||||
name: "Illegal",
|
||||
slug: "illegal",
|
||||
}),
|
||||
).rejects.toThrow("Unauthorized: admin access required");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user