fix(GRO-1272): auto-provision staff record on first OIDC login #19

Merged
groombook-engineer[bot] merged 4 commits from fleaflicker/gro-1272-auto-provision-staff-dev into dev 2026-05-21 14:16:42 +00:00
2 changed files with 100 additions and 23 deletions
Showing only changes of commit 4a80440513 - Show all commits
+1 -2
View File
@@ -26,14 +26,13 @@ GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet
| # | Scenario | Steps | Expected |
|---|----------|-------|----------|
| TC-API-1.1 | Login via OIDC | POST to OIDC provider callback, verify JWT token issued | 200 OK, JWT returned with valid claims |
| TC-API-1.2 | Session persistence | Make authenticated request, verify session token valid | 200 OK, request succeeds |
| TC-API-1.3 | Logout | Call logout endpoint, verify token invalidated | 200 OK, subsequent requests return 401 |
| TC-API-1.4 | Email+password login (UAT) | POST /api/auth/sign-in/email with uat-super@groombook.dev + SEED_UAT_SUPER_PASSWORD | 200 OK, session cookie returned |
| TC-API-1.5 | Email+password login — groomer | POST /api/auth/sign-in/email with uat-groomer@groombook.dev + SEED_UAT_GROOMER_PASSWORD | 200 OK, session cookie returned |
| TC-API-1.6 | Email+password login — customer | POST /api/auth/sign-in/email with uat-customer@groombook.dev + SEED_UAT_CUSTOMER_PASSWORD | 200 OK, session cookie returned |
| TC-API-1.7 | Email+password login — tester | POST /api/auth/sign-in/email with uat-tester@groombook.dev + SEED_UAT_TESTER_PASSWORD | 200 OK, session cookie returned |
| TC-API-1.8 | Email+password — invalid password | POST /api/auth/sign-in/email with wrong password | 400 Bad Request, error returned |
| TC-API-1.9 | Email+password — unknown user | POST /api/auth/sign-in/email with non-existent email | 400 Bad Request, error returned |
| TC-API-1.10 | Auto-provision on first OIDC login | First login as a Better-Auth user with no existing staff record | 200 OK, access granted; groomer staff record auto-created with name/email from user table |
### 4.2 Client Management
+99 -21
View File
@@ -45,40 +45,72 @@ const GROOMER: StaffRow = {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
let staffLookupResult: StaffRow | null = null;
let managerFallbackResult: StaffRow | null = MANAGER;
let userLookupResult: { id: string; name: string | null; email: string | null } | null = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
let insertedStaff: StaffRow | null = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
vi.mock("../db", () => {
const staff = new Proxy(
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
{ _name: "staff" },
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
{
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
get(target, prop) {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (prop === "_name") return "staff";
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (prop === "$inferSelect") return {};
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return { table: "staff", column: prop };
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const makeTableProxy = (name: string) =>
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
new Proxy(
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
{ _name: name },
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
{
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
get(target, prop) {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (prop === "_name") return name;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (prop === "$inferSelect") return {};
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return { table: name, column: prop };
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
},
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const staff = makeTableProxy("staff");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const user = makeTableProxy("user");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const buildQuery = (result: unknown, fallback: unknown) => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
limit: () => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
[Symbol.iterator]: function* () {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (result) yield result;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
},
}
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
0: result,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
length: result ? 1 : 0,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return {
getDb: () => ({
select: () => ({
from: () => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
where: () => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
limit: () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
// dev mode fallback to first manager
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return managerFallbackResult ? [managerFallbackResult] : [];
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
},
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
[Symbol.iterator]: function* () {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
if (staffLookupResult) yield staffLookupResult;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
},
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
0: staffLookupResult,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
length: staffLookupResult ? 1 : 0,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
from: (table: unknown) => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
where: () => buildQuery(
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
table === staff ? staffLookupResult : userLookupResult,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
table === staff ? managerFallbackResult : null
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
insert: (table: unknown) => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
values: (vals: Record<string, unknown>) => ({
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
returning: () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const newStaff: StaffRow = {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
id: "new-staff-id",
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
oidcSub: null,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
userId: vals.userId as string,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
role: vals.role as StaffRow["role"],
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
isSuperUser: false,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
name: vals.name as string,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
email: vals.email as string,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
active: true,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
icalToken: null,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
createdAt: new Date(),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
updatedAt: new Date(),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
};
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
insertedStaff = newStaff;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return [newStaff];
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
},
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}),
}),
}),
staff,
user,
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
eq: vi.fn((_col: unknown, _val: unknown) => ({ col: _col, val: _val })),
and: vi.fn((..._clauses: unknown[]) => ({})),
sql: vi.fn((..._args: unknown[]) => ({})),
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
};
});
@@ -87,6 +119,8 @@ vi.mock("../db", () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
function resetMocks() {
staffLookupResult = null;
managerFallbackResult = MANAGER;
userLookupResult = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
insertedStaff = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
}
/** Build a minimal Hono app with jwtPayload pre-set, then apply a middleware. */
@@ -202,6 +236,50 @@ describe("resolveStaffMiddleware", () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const body = await res.json();
expect(body.error).toMatch(/no staff records found/i);
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
it("auto-provision: creates groomer staff record on first login when Better-Auth user exists", async () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
staffLookupResult = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
userLookupResult = { id: "ba-user-new", name: "New User", email: "newuser@example.com" };
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
let capturedStaff: StaffRow | null = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const app = buildApp(resolveStaffMiddleware, (c) => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
capturedStaff = c.get("staff");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return c.json({ ok: true });
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const res = await app.request("/test");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(res.status).toBe(200);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff).not.toBeNull();
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.role).toBe("groomer");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.userId).toBe("ba-user-new");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.name).toBe("New User");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.email).toBe("newuser@example.com");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.isSuperUser).toBe(false);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
it("auto-provision: falls back to email prefix when user has no name", async () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
staffLookupResult = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
userLookupResult = { id: "ba-user-noname", name: null, email: "firstlogin@example.com" };
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
let capturedStaff: StaffRow | null = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const app = buildApp(resolveStaffMiddleware, (c) => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
capturedStaff = c.get("staff");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
return c.json({ ok: true });
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const res = await app.request("/test");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(res.status).toBe(200);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(capturedStaff!.name).toBe("firstlogin");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
it("auto-provision: returns 403 when no staff record and no Better-Auth user exists", async () => {
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
staffLookupResult = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
userLookupResult = null;
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const app = buildApp(resolveStaffMiddleware);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const res = await app.request("/test");
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(res.status).toBe(403);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
const body = await res.json();
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
expect(body.error).toMatch(/no staff record found for authenticated user/i);
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
});
// ─── requireRole tests ────────────────────────────────────────────────────────
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.
Review

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.

returns with no . All non-limit WHERE queries throw TypeError → 500. Add to the returned object, and make fall back to when is null.