Commit Graph

11 Commits

Author SHA1 Message Date
Scrubs McBarkley 0c182da366 fix: address CTO review feedback on quick-find search (GH #97, GRO-134)
- Remove unused makeSelectChain function from search.test.ts (lint blocker)
- Fix handleClientClick/handlePetClick to navigate to /admin/clients?highlight={id}
  so the target client is identified in the URL rather than silently ignored
- Add console.warn for fetch errors in GlobalSearch instead of swallowing silently

Auth middleware verified: searchRouter is registered on the api Hono instance
which applies authMiddleware + resolveStaffMiddleware globally — no coverage gap.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-22 04:10:54 +00:00
Scrubs McBarkley c826f65bd6 feat: quick-find search for clients and pets (GH #97, GRO-140)
Backend:
- GET /api/search?q={query} — returns up to 10 matching active clients and 10
  matching pets in a single request; clients matched on name/email/phone,
  pets matched on name/breed with owner name included
- Special chars (%, _, \) escaped before ILIKE to prevent injection/accidents
- Disabled clients excluded; pets from disabled client owners excluded via JOIN filter
- Route registered under protected API (auth + RBAC middleware applies automatically)
- Export `ilike` from @groombook/db alongside existing drizzle-orm helpers

Frontend:
- GlobalSearch component in sticky admin header: debounced input (300ms),
  grouped dropdown (Clients / Pets sections), loading/empty states
- Client results show name + phone; pet results show name, breed, owner name
- Touch-friendly: 44px input height, 48px min row height, full-width dropdown
- Outside-click closes dropdown; selecting a result navigates to /admin/clients

Tests (apps/api/src/__tests__/search.test.ts):
- 400 on missing/empty/whitespace q
- Returns matching clients and pets
- Empty arrays on no match
- Response shape always has clients/pets keys
- Special character inputs handled without errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 00:16:28 +00:00
Scrubs McBarkley 1f50fdff54 test(db): add unit tests for test factories (GitHub #94)
Tests cover resetFactoryCounters(), counter determinism, override
merging, and compile-time enforcement of required fields on
buildAppointment. All 16 new tests pass (92 total).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:43:47 +00:00
Scrubs McBarkley ad6024f3d9 feat: deterministic seed, impersonation migration, test factories (GRO-110)
Phase 1 — Seed Hardening:
- Replace all Math.random() calls in seed.ts with a Mulberry32 seeded PRNG
  (seed 42) so the same data set is reproduced on every run
- Replace crypto.randomUUID() with a PRNG-based UUID v4 generator
- Add manager (Jordan Lee) and receptionist (Sam Rivera) staff members
  to seed — previously all staff were groomers
- New packages/db/src/reset.ts drops all tables/enums and re-runs
  migrate + seed; exposed as `pnpm db:reset` at root
- Generate migration 0010_impersonation_sessions.sql for the
  impersonation_sessions and impersonation_audit_logs tables that were
  already in schema.ts but had no corresponding migration

Phase 2 — Test Factories:
- New packages/db/src/factories.ts with buildStaff, buildClient, buildPet,
  buildService, buildAppointment and resetFactoryCounters helpers
- Exported via @groombook/db/factories subpath (package.json + vitest alias)
- impersonation.test.ts updated to use buildStaff instead of hand-rolled
  fixture objects

Closes #90 (Phases 1 + 2)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 19:34:52 +00:00
Scrubs McBarkley 543c13f182 fix: correct TypeScript types in rbac.test.ts
Use StaffRow type for all staff fixture objects so groomer/receptionist
variants don't cause type errors. Simplify buildApp/buildWithStaff helper
signatures to MiddlewareHandler<AppEnv> / Context<AppEnv> — no more
Parameters<...> inference gymnastics.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 18:52:10 +00:00
Scrubs McBarkley 93a9ae4461 feat: add RBAC middleware with role-based route guards (GRO-103)
- New `apps/api/src/middleware/rbac.ts` with `resolveStaffMiddleware`
  (resolves staff from DB by OIDC sub, supports AUTH_DISABLED dev mode)
  and `requireRole(...roles)` factory for per-route role enforcement
- Wire `resolveStaffMiddleware` after `authMiddleware` on api basePath
- Route guards per permission matrix:
  - Manager only: /staff/*, /admin/*, /reports/*, /invoices/*, /impersonation/*
  - Manager + Receptionist only: /appointment-groups/*, /grooming-logs/*
  - Groomers read-only on /clients/*, /pets/*, /appointments/* (write requires manager/receptionist)
  - Services: all roles read, manager-only write
- Refactor impersonation router to use AppEnv and c.get("staff") instead
  of inline staff resolution; role check delegated to requireRole middleware
- Unit tests in rbac.test.ts covering resolveStaffMiddleware and requireRole
- Update impersonation.test.ts to inject staff directly via context

Closes #88 (Phase 1)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 15:50:45 +00:00
Scrubs McBarkley b7145271fb fix: assert on deletedId in DELETE test to resolve unused-vars lint error
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:52:18 +00:00
Scrubs McBarkley d4629baaea fix: update ImpersonationBanner tests to match current component API
- Import ImpersonationSession from @groombook/types (component was updated in #78)
- Remove stale tests: "shows customer name" and "returns null when inactive"
  (component no longer renders customer name or checks session.active)
- Add isExtended prop to all render calls (component now takes isExtended as prop)
- Fix "does not show Extend button when already extended" to pass isExtended={true}
  instead of session.extended (prop was extracted from session in #78)
- Fix clients.test.ts: selectRows typed as Record<string,unknown>[] to allow
  spread in returning() callbacks (resolves TS2698)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:50:51 +00:00
Scrubs McBarkley d85e09cb11 test: add unit tests for email service, clients route, and ImpersonationBanner
- Email service: 16 tests covering buildConfirmationEmail and buildReminderEmail
  (recipient, subject, body content, groomer presence/absence, reminder timing)
- Clients route: 17 tests covering CRUD endpoints including validation,
  404 handling, soft-disable (disabledAt), and confirm-required delete
- ImpersonationBanner: 8 tests covering render, session expiry auto-end,
  Extend button visibility, and End/Audit button callbacks

Part of GRO-76 (Phase 1 unit/integration tests).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 01:50:02 +00:00
groombook-paperclip[bot] 70958542f8 feat: Staff Impersonation backend + frontend wiring (#75)
* feat: implement Staff Impersonation backend and wire frontend

Add server-side impersonation session management with full audit
logging, replacing the frontend-only mock. Managers can start
time-limited sessions to view the app as a specific client.

Backend:
- Add impersonation_sessions and impersonation_audit_logs tables
  (Drizzle schema) with proper FK constraints and status enum
- Add Hono API routes: start/get/extend/end session + audit logging
- Server-side session expiration, one-active-per-staff enforcement
- Staff role validation (manager-only)

Frontend:
- Add CustomerPortal wrapper with URL-param session init
- Add ImpersonationBanner with live countdown timer
- Add AuditLogViewer modal for session audit trail
- Add "View as Customer" button on Clients page
- Auto-log page visits during impersonation

Closes #74

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* chore: remove unused useNavigate import from Clients.tsx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add authorization + expiry checks to impersonation endpoints, add tests

Security: Add ownership verification (resolveStaff + staffId check) to
GET /sessions/:id, POST /sessions/:id/log, and GET /sessions/:id/audit-log
endpoints that were previously unprotected.

Bug: Add time-based expiry checks to extend, end, get-session, and log
endpoints via checkAndExpireSession() helper. Expired sessions are now
auto-marked as expired in the DB and cannot be extended or logged to.

Tests: Add 23 tests covering session creation (happy path, auth, conflict),
extend (active, expired, non-owner, ended), end (active, expired, non-owner),
audit logging (owner, non-owner, expired, ended), and audit-log retrieval
(owner, non-owner, not found).

Addresses QA review on PR #75 (GRO-66).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve @groombook/db source in vitest config

Add resolve alias so vitest can resolve @groombook/db from source
TypeScript files without requiring a prior build step. Fixes CI
test failures when dist/ has not been compiled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Groom Book CEO <ceo@groombook.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Groom Book CTO <cto@groombook.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Scrubs McBarkley <scrubs@groombook.app>
2026-03-20 08:16:09 +00:00
groombook-paperclip[bot] cba502e35f Set up unit testing infrastructure
Extract slot generation from book.ts into pure utility for unit testing.
Add 8 API unit tests and 4 web component tests with coverage thresholds.

Closes #39

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-18 01:55:02 +00:00