Files
route-commerce/tests/unit/getAdminUser.test.ts
T
openclaw c86e97e816
Deploy to route.crispygoat.com / deploy (push) Failing after 7s
feat(storage): MinIO object storage, Neon Auth, Supabase removal
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
  deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
  new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
  Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
  brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
  bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
  TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
  public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
  wholesale_settings lookup
2026-06-09 12:21:57 -06:00

189 lines
5.9 KiB
TypeScript

/**
* Unit tests for `getAdminUser()`. The dev_session cookie bypass has been
* removed; the only path into the admin is through a real Neon Auth
* session. These tests mock the `getSession()` function and the DB layer
* to exercise the lookup.
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
// Stub the server-only guard so the module can be imported under vitest.
vi.mock("server-only", () => ({}));
// Mock the Drizzle client wrapper so we don't need a real DB.
type MockFn = (arg: unknown) => Promise<unknown>;
const mockSelect = vi.fn();
const mockWithPlatformAdmin = vi.fn(async (fn: MockFn) => fn({ select: mockSelect }));
vi.mock("@/db/client", () => ({
withPlatformAdmin: (fn: MockFn) => mockWithPlatformAdmin(fn),
}));
// Mock the getSession() function. The default mock returns null (no session).
const getSessionMock = vi.fn();
vi.mock("@/lib/auth", () => ({
getSession: getSessionMock,
}));
// Mock cookies() so we don't read a real cookie store.
const cookieStoreGet = vi.fn();
vi.mock("next/headers", () => ({
cookies: () =>
Promise.resolve({
get: (name: string) => cookieStoreGet(name),
}),
}));
import { getAdminUser, buildDevAdmin, permissionsForRole } from "@/lib/admin-permissions";
beforeEach(() => {
mockSelect.mockReset();
getSessionMock.mockReset();
cookieStoreGet.mockReset();
});
describe("getAdminUser()", () => {
it("returns null when there is no Neon Auth session", async () => {
getSessionMock.mockResolvedValue({ data: null });
cookieStoreGet.mockReturnValue(undefined);
const u = await getAdminUser();
expect(u).toBeNull();
});
it("returns null when the session has no email", async () => {
getSessionMock.mockResolvedValue({ data: { user: { name: "no-email" } } });
const u = await getAdminUser();
expect(u).toBeNull();
});
it("returns null when the email is not in the users table", async () => {
getSessionMock.mockResolvedValue({ data: { user: { email: "unknown@example.com" } } });
// First select: users. Returns empty.
mockSelect.mockReturnValueOnce({
from: () => ({
where: () => ({
limit: async () => [],
}),
}),
});
const u = await getAdminUser();
expect(u).toBeNull();
});
it("returns null when the user exists but has no admin_user_brands row", async () => {
getSessionMock.mockResolvedValue({ data: { user: { email: "no-brand@example.com" } } });
// First select: users — returns the user
mockSelect
.mockReturnValueOnce({
from: () => ({
where: () => ({
limit: async () => [
{
id: "user-1",
email: "no-brand@example.com",
name: "No Brand",
},
],
}),
}),
})
// Second select: adminUserBrands — returns empty
.mockReturnValueOnce({
from: () => ({
innerJoin: () => ({
where: () => ({
limit: async () => [],
}),
}),
}),
});
const u = await getAdminUser();
expect(u).toBeNull();
});
it("returns a fully-populated AdminUser for a provisioned brand_admin", async () => {
getSessionMock.mockResolvedValue({ data: { user: { email: "admin@tuxedo.example" } } });
mockSelect
.mockReturnValueOnce({
from: () => ({
where: () => ({
limit: async () => [
{
id: "user-tux",
email: "admin@tuxedo.example",
name: "Tux Admin",
},
],
}),
}),
})
.mockReturnValueOnce({
from: () => ({
innerJoin: () => ({
where: () => ({
limit: async () => [
{
brandId: "brand-tux",
brandName: "Tuxedo Citrus",
brandSlug: "tuxedo",
role: "brand_admin",
},
],
}),
}),
}),
});
const u = await getAdminUser();
expect(u).not.toBeNull();
expect(u?.email).toBe("admin@tuxedo.example");
expect(u?.brand_id).toBe("brand-tux");
expect(u?.brand_slug).toBe("tuxedo");
expect(u?.role).toBe("brand_admin");
expect(u?.can_manage_products).toBe(true);
expect(u?.can_manage_team).toBe(true);
});
});
describe("buildDevAdmin()", () => {
it("returns a platform_admin with no brand", () => {
const u = buildDevAdmin("platform_admin");
expect(u.role).toBe("platform_admin");
expect(u.brand_id).toBeNull();
expect(u.brand_slug).toBeNull();
expect(u.can_manage_billing).toBe(true);
});
it("returns a brand_admin tied to the tuxedo brand", () => {
const u = buildDevAdmin("brand_admin");
expect(u.role).toBe("brand_admin");
expect(u.brand_slug).toBe("tuxedo");
expect(u.can_manage_users).toBe(false);
});
it("returns a store_employee with limited permissions", () => {
const u = buildDevAdmin("store_employee");
expect(u.role).toBe("store_employee");
expect(u.can_manage_products).toBe(false);
expect(u.can_manage_orders).toBe(true);
expect(u.can_manage_billing).toBe(false);
});
});
describe("permissionsForRole()", () => {
it("platform_admin can do everything", () => {
const p = permissionsForRole("platform_admin");
expect(Object.values(p).every((v) => v === true)).toBe(true);
});
it("brand_admin can manage most things but not users", () => {
const p = permissionsForRole("brand_admin");
expect(p.can_manage_users).toBe(false);
expect(p.can_manage_billing).toBe(true);
});
it("store_employee is restricted to orders + pickup", () => {
const p = permissionsForRole("store_employee");
expect(p.can_manage_orders).toBe(true);
expect(p.can_manage_pickup).toBe(true);
expect(p.can_manage_products).toBe(false);
expect(p.can_manage_billing).toBe(false);
});
});