Add README and technical docs
- README with quick start, package overview, links to docs - docs/setup.md — prerequisites, env vars, installation, running, testing - docs/architecture.md — monorepo structure, backend/frontend design - docs/api.md — full endpoint reference with permissions - docs/database.md — schema overview, migrations, multi-tenancy - docs/testing.md — test runner, suites, writing tests - Updated .env.example with all supported variables
This commit is contained in:
29
.env.example
29
.env.example
@@ -1,13 +1,14 @@
|
||||
# Forte — Development Environment Variables
|
||||
# These are used inside Docker Compose (docker-compose.dev.yml overrides most of these)
|
||||
# Forte — Environment Variables
|
||||
# Copy to .env and adjust values for your setup.
|
||||
# Docker Compose overrides host values (postgres, valkey) automatically.
|
||||
|
||||
# Database
|
||||
DATABASE_URL=postgresql://forte:forte@postgres:5432/forte
|
||||
# Database (PostgreSQL 16)
|
||||
DATABASE_URL=postgresql://forte:forte@localhost:5432/forte
|
||||
|
||||
# Valkey (Redis-compatible)
|
||||
REDIS_URL=redis://valkey:6379
|
||||
# Valkey / Redis
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# JWT
|
||||
# JWT — use a strong random secret in production
|
||||
JWT_SECRET=change-me-in-production-use-a-long-random-string
|
||||
|
||||
# API Server
|
||||
@@ -16,3 +17,17 @@ HOST=0.0.0.0
|
||||
|
||||
# Environment
|
||||
NODE_ENV=development
|
||||
|
||||
# Logging (optional)
|
||||
# LOG_LEVEL=info
|
||||
# LOG_FILE=./logs/forte.log
|
||||
|
||||
# File Storage (optional — defaults to local)
|
||||
# STORAGE_PROVIDER=local
|
||||
# STORAGE_LOCAL_PATH=./data/files
|
||||
|
||||
# CORS (optional — defaults to * in development)
|
||||
# CORS_ORIGINS=https://admin.example.com
|
||||
|
||||
# Frontend URL (used in password reset links)
|
||||
# APP_URL=http://localhost:5173
|
||||
|
||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Forte
|
||||
|
||||
Music store management platform — POS, inventory, rentals, lessons, repairs, and accounting.
|
||||
|
||||
Built by [Lunarfront Tech LLC](https://lunarfront.com).
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Runtime:** Bun
|
||||
- **Language:** TypeScript (end-to-end)
|
||||
- **API:** Fastify + Drizzle ORM + PostgreSQL 16
|
||||
- **Frontend:** React + TanStack Router + TanStack Query
|
||||
- **Validation:** Zod (shared schemas)
|
||||
- **Queue/Cache:** BullMQ + Valkey 8
|
||||
- **Monorepo:** Turborepo + Bun workspaces
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
bun install
|
||||
cp .env.example .env # configure DATABASE_URL, REDIS_URL, JWT_SECRET
|
||||
cd packages/backend && bunx drizzle-kit migrate
|
||||
bun run dev # starts backend (:8000) + admin UI (:5173)
|
||||
```
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Description |
|
||||
|---------|-------------|
|
||||
| `packages/backend` | Fastify API server |
|
||||
| `packages/admin` | Admin UI (React + Vite) |
|
||||
| `packages/shared` | Zod schemas, types, shared utils |
|
||||
|
||||
## Documentation
|
||||
|
||||
| Doc | Description |
|
||||
|-----|-------------|
|
||||
| [Setup](docs/setup.md) | Prerequisites, environment, installation, running |
|
||||
| [Architecture](docs/architecture.md) | Monorepo structure, backend/frontend design, state management |
|
||||
| [API Reference](docs/api.md) | All endpoints, pagination, auth, permissions |
|
||||
| [Database](docs/database.md) | Schema overview, migrations, multi-tenancy |
|
||||
| [Testing](docs/testing.md) | Test runner, suites, writing tests, assertions |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bun run dev # start all packages in dev mode
|
||||
bun run test # run all tests
|
||||
bun run lint # lint all packages
|
||||
bun run format # format with Prettier
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Proprietary. All rights reserved.
|
||||
146
docs/api.md
Normal file
146
docs/api.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# API Reference
|
||||
|
||||
Base URL: `http://localhost:8000/v1`
|
||||
|
||||
All authenticated endpoints require `Authorization: Bearer <token>`. Registration and login require `X-Company-ID` header.
|
||||
|
||||
## Pagination
|
||||
|
||||
Every list endpoint accepts:
|
||||
|
||||
| Param | Default | Description |
|
||||
|-------|---------|-------------|
|
||||
| `page` | `1` | Page number |
|
||||
| `limit` | `25` | Items per page (max 100) |
|
||||
| `q` | — | Search query (ilike across relevant columns) |
|
||||
| `sort` | varies | Sort field name |
|
||||
| `order` | `asc` | `asc` or `desc` |
|
||||
|
||||
Response shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 25,
|
||||
"total": 142,
|
||||
"totalPages": 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Auth
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| POST | `/auth/register` | No (needs `X-Company-ID`) | Create user account |
|
||||
| POST | `/auth/login` | No | Login, returns JWT |
|
||||
| GET | `/auth/me` | Yes | Current user profile |
|
||||
| PATCH | `/auth/me` | Yes | Update profile (firstName, lastName) |
|
||||
| POST | `/auth/change-password` | Yes | Change password |
|
||||
| POST | `/auth/reset-password/:userId` | Yes (`users.admin`) | Generate password reset link |
|
||||
|
||||
## Accounts
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/accounts` | `accounts.view` | List accounts (paginated, searchable) |
|
||||
| POST | `/accounts` | `accounts.edit` | Create account |
|
||||
| GET | `/accounts/:id` | `accounts.view` | Get account |
|
||||
| PATCH | `/accounts/:id` | `accounts.edit` | Update account |
|
||||
| DELETE | `/accounts/:id` | `accounts.admin` | Soft-delete account |
|
||||
|
||||
## Members
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/members` | `accounts.view` | List all members (paginated) |
|
||||
| GET | `/accounts/:id/members` | `accounts.view` | List members for account |
|
||||
| POST | `/accounts/:id/members` | `accounts.edit` | Create member |
|
||||
| GET | `/members/:id` | `accounts.view` | Get member |
|
||||
| PATCH | `/members/:id` | `accounts.edit` | Update member |
|
||||
| DELETE | `/members/:id` | `accounts.admin` | Delete member |
|
||||
| POST | `/members/:id/move` | `accounts.edit` | Move member to another account |
|
||||
|
||||
## Member Sub-Resources
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/members/:id/identifiers` | `accounts.view` | List identifiers |
|
||||
| POST | `/members/:id/identifiers` | `accounts.edit` | Create identifier |
|
||||
| PATCH | `/identifiers/:id` | `accounts.edit` | Update identifier |
|
||||
| DELETE | `/identifiers/:id` | `accounts.admin` | Delete identifier |
|
||||
| GET | `/accounts/:id/payment-methods` | `accounts.view` | List payment methods |
|
||||
| POST | `/accounts/:id/payment-methods` | `accounts.edit` | Create payment method |
|
||||
| PATCH | `/payment-methods/:id` | `accounts.edit` | Update payment method |
|
||||
| DELETE | `/payment-methods/:id` | `accounts.admin` | Delete payment method |
|
||||
| GET | `/accounts/:id/tax-exemptions` | `accounts.view` | List tax exemptions |
|
||||
| POST | `/accounts/:id/tax-exemptions` | `accounts.edit` | Create tax exemption |
|
||||
| PATCH | `/tax-exemptions/:id` | `accounts.edit` | Update tax exemption |
|
||||
| DELETE | `/tax-exemptions/:id` | `accounts.admin` | Delete tax exemption |
|
||||
| GET | `/accounts/:id/processor-links` | `accounts.view` | List processor links |
|
||||
| POST | `/accounts/:id/processor-links` | `accounts.edit` | Create processor link |
|
||||
| PATCH | `/processor-links/:id` | `accounts.edit` | Update processor link |
|
||||
| DELETE | `/processor-links/:id` | `accounts.admin` | Delete processor link |
|
||||
|
||||
## Users & RBAC
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/users` | `users.view` | List users (paginated, includes roles) |
|
||||
| PATCH | `/users/:id/status` | `users.admin` | Enable/disable user |
|
||||
| GET | `/users/:id/roles` | `users.view` | Get user's roles |
|
||||
| POST | `/users/:id/roles` | `users.edit` | Assign role to user |
|
||||
| DELETE | `/users/:id/roles/:roleId` | `users.edit` | Remove role from user |
|
||||
| GET | `/permissions` | `users.view` | List all permissions |
|
||||
| GET | `/roles` | `users.view` | List roles (paginated) |
|
||||
| GET | `/roles/all` | `users.view` | List all roles (unpaginated, for dropdowns) |
|
||||
| GET | `/roles/:id` | `users.view` | Get role with permissions |
|
||||
| POST | `/roles` | `users.admin` | Create custom role |
|
||||
| PATCH | `/roles/:id` | `users.admin` | Update role |
|
||||
| DELETE | `/roles/:id` | `users.admin` | Delete custom role |
|
||||
| GET | `/me/permissions` | Yes | Current user's permissions + roles |
|
||||
|
||||
## Files
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/files?entityType=&entityId=` | `files.view` | List files for entity |
|
||||
| POST | `/files` | `files.upload` | Upload file (multipart) |
|
||||
| GET | `/files/:id` | `files.view` | Get file metadata |
|
||||
| GET | `/files/serve/*` | `files.view` | Serve file content |
|
||||
| DELETE | `/files/:id` | `files.delete` | Delete file |
|
||||
|
||||
Upload accepts multipart form with fields: `file`, `entityType`, `entityId`, `category`.
|
||||
|
||||
Valid entity types: `user`, `member`, `member_identifier`, `product`, `rental_agreement`, `repair_ticket`.
|
||||
|
||||
## Products & Inventory
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/products` | `inventory.view` | List products (paginated) |
|
||||
| POST | `/products` | `inventory.edit` | Create product |
|
||||
| GET | `/products/:id` | `inventory.view` | Get product |
|
||||
| PATCH | `/products/:id` | `inventory.edit` | Update product |
|
||||
| DELETE | `/products/:id` | `inventory.admin` | Delete product |
|
||||
| GET | `/categories` | `inventory.view` | List categories |
|
||||
| POST | `/categories` | `inventory.edit` | Create category |
|
||||
| GET | `/suppliers` | `inventory.view` | List suppliers |
|
||||
| POST | `/suppliers` | `inventory.edit` | Create supplier |
|
||||
|
||||
## Lookup Tables
|
||||
|
||||
| Method | Path | Permission | Description |
|
||||
|--------|------|------------|-------------|
|
||||
| GET | `/lookups/unit-statuses` | `inventory.view` | List unit statuses |
|
||||
| POST | `/lookups/unit-statuses` | `inventory.admin` | Create custom status |
|
||||
| GET | `/lookups/item-conditions` | `inventory.view` | List item conditions |
|
||||
| POST | `/lookups/item-conditions` | `inventory.admin` | Create custom condition |
|
||||
|
||||
## Health
|
||||
|
||||
| Method | Path | Auth | Description |
|
||||
|--------|------|------|-------------|
|
||||
| GET | `/health` | No | Health check |
|
||||
120
docs/architecture.md
Normal file
120
docs/architecture.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Architecture
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
```
|
||||
forte/
|
||||
packages/
|
||||
shared/ @forte/shared — Zod schemas, types, business logic, utils
|
||||
backend/ @forte/backend — Fastify API server
|
||||
admin/ @forte/admin — Admin UI (React + Vite)
|
||||
planning/ Domain planning docs (01-26)
|
||||
docs/ Technical documentation
|
||||
```
|
||||
|
||||
Managed with Turborepo and Bun workspaces. `@forte/shared` is a dependency of both `backend` and `admin`.
|
||||
|
||||
## Backend
|
||||
|
||||
**Fastify** API server with a plugin-based architecture.
|
||||
|
||||
```
|
||||
src/
|
||||
main.ts App entry, plugin registration, server start
|
||||
plugins/
|
||||
database.ts Drizzle ORM + PostgreSQL connection
|
||||
auth.ts JWT auth, permission checking, inheritance
|
||||
redis.ts Valkey/Redis (ioredis)
|
||||
cors.ts CORS configuration
|
||||
storage.ts File storage provider registration
|
||||
error-handler.ts Centralized error → HTTP response mapping
|
||||
dev-auth.ts Dev-only auth bypass
|
||||
routes/v1/
|
||||
auth.ts Register, login, password change, reset, profile
|
||||
accounts.ts Accounts, members, identifiers, payment methods, etc.
|
||||
rbac.ts Users list, roles CRUD, role assignments, permissions
|
||||
files.ts File upload/download/delete
|
||||
services/
|
||||
account.service.ts Account + member business logic
|
||||
rbac.service.ts Roles, permissions, user role management
|
||||
file.service.ts File validation, storage, metadata
|
||||
product.service.ts Products + inventory
|
||||
lookup.service.ts Lookup table management
|
||||
inventory.service.ts Stock receipts, unit tracking
|
||||
db/
|
||||
schema/ Drizzle table definitions
|
||||
migrations/ SQL migrations (drizzle-kit)
|
||||
seeds/ System permission + role definitions
|
||||
storage/
|
||||
provider.ts StorageProvider interface
|
||||
local.ts Local filesystem provider
|
||||
s3.ts S3 provider (placeholder)
|
||||
utils/
|
||||
pagination.ts withPagination, withSort, buildSearchCondition, paginatedResponse
|
||||
```
|
||||
|
||||
### Request Flow
|
||||
|
||||
1. Fastify receives request
|
||||
2. `onRequest` hook sets `companyId` from header
|
||||
3. `authenticate` preHandler verifies JWT, loads permissions, checks `is_active`
|
||||
4. `requirePermission` preHandler checks user has required permission slug
|
||||
5. Route handler validates input with Zod, calls service, returns response
|
||||
6. Error handler catches typed errors and maps to HTTP status codes
|
||||
|
||||
### Permission Inheritance
|
||||
|
||||
Permissions follow a hierarchy: `admin` implies `edit`, which implies `view` for the same domain. Having `accounts.admin` automatically grants `accounts.edit` and `accounts.view`. Non-hierarchical actions (`upload`, `delete`, `send`, `export`) don't cascade.
|
||||
|
||||
## Frontend (Admin UI)
|
||||
|
||||
**React** SPA with TanStack Router (file-based routing) and TanStack Query (data fetching).
|
||||
|
||||
```
|
||||
src/
|
||||
routes/
|
||||
_authenticated.tsx Layout with sidebar, permission-gated nav
|
||||
_authenticated/
|
||||
accounts/ Account list + detail pages
|
||||
members/ Member list + detail pages
|
||||
users.tsx Users admin page
|
||||
roles/ Roles list + create/edit pages
|
||||
profile.tsx User profile + settings
|
||||
help.tsx In-app wiki
|
||||
login.tsx Login page
|
||||
api/ React Query options + mutations per domain
|
||||
components/
|
||||
ui/ shadcn/ui primitives
|
||||
shared/ DataTable, AvatarUpload
|
||||
accounts/ Domain-specific forms
|
||||
stores/
|
||||
auth.store.ts Zustand — token, user, permissions
|
||||
theme.store.ts Zustand — color theme + light/dark mode
|
||||
hooks/
|
||||
use-pagination.ts URL-based pagination state
|
||||
lib/
|
||||
api-client.ts Fetch wrapper with JWT + error handling
|
||||
themes.ts Color theme definitions
|
||||
wiki/
|
||||
index.ts In-app help content
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
| Concern | Solution |
|
||||
|---------|----------|
|
||||
| Auth + permissions | Zustand store, persisted to sessionStorage |
|
||||
| Server data | TanStack Query (cache, refetch, invalidation) |
|
||||
| URL state (pagination) | TanStack Router search params |
|
||||
| Theme | Zustand store, persisted to localStorage |
|
||||
| Component state | React `useState` |
|
||||
|
||||
## Multi-Tenancy
|
||||
|
||||
Every domain table has a `company_id` column. All queries filter by the authenticated user's company. Location-scoped tables (inventory, transactions) additionally filter by `location_id`.
|
||||
|
||||
## Database
|
||||
|
||||
PostgreSQL 16 with Drizzle ORM. Migrations are generated with `bunx drizzle-kit generate` and applied with `bunx drizzle-kit migrate`. Schema files live in `packages/backend/src/db/schema/`.
|
||||
|
||||
Key tables: `company`, `location`, `user`, `account`, `member`, `member_identifier`, `product`, `inventory_unit`, `role`, `permission`, `user_role_assignment`, `role_permission`, `file`.
|
||||
86
docs/database.md
Normal file
86
docs/database.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Database
|
||||
|
||||
## Setup
|
||||
|
||||
PostgreSQL 16. Two databases:
|
||||
|
||||
| Database | Port | Usage |
|
||||
|----------|------|-------|
|
||||
| `forte` | 5432 | Development |
|
||||
| `forte_api_test` | 5432 | API integration tests (auto-created by test runner) |
|
||||
|
||||
## Migrations
|
||||
|
||||
Migrations are managed by Drizzle Kit and live in `packages/backend/src/db/migrations/`.
|
||||
|
||||
```bash
|
||||
cd packages/backend
|
||||
|
||||
# Generate a migration from schema changes
|
||||
bunx drizzle-kit generate
|
||||
|
||||
# Apply pending migrations
|
||||
bunx drizzle-kit migrate
|
||||
```
|
||||
|
||||
Schema files: `packages/backend/src/db/schema/`
|
||||
|
||||
## Multi-Tenancy
|
||||
|
||||
All domain tables include `company_id` (uuid FK to `company`). Every query filters by the authenticated user's company. Location-scoped tables additionally include `location_id`.
|
||||
|
||||
## Schema Overview
|
||||
|
||||
### Core
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `company` | Tenant (music store business) |
|
||||
| `location` | Physical store location |
|
||||
| `user` | Staff/admin user account |
|
||||
|
||||
### Accounts & Members
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `account` | Billing entity (family, individual, business) |
|
||||
| `member` | Individual person on an account |
|
||||
| `member_identifier` | ID documents (DL, passport, school ID) |
|
||||
| `payment_method` | Stored payment methods |
|
||||
| `processor_link` | Payment processor integrations |
|
||||
| `tax_exemption` | Tax exemption records |
|
||||
|
||||
### Inventory
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `product` | Product catalog entry |
|
||||
| `inventory_unit` | Individual serialized/non-serialized unit |
|
||||
| `stock_receipt` | Incoming stock records |
|
||||
| `category` | Product categories |
|
||||
| `supplier` | Product suppliers |
|
||||
| `inventory_unit_status` | Lookup: unit statuses |
|
||||
| `item_condition` | Lookup: item conditions |
|
||||
|
||||
### RBAC
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `permission` | System permissions (global, seeded) |
|
||||
| `role` | Company-scoped roles (system + custom) |
|
||||
| `role_permission` | Role-to-permission mapping |
|
||||
| `user_role_assignment` | User-to-role mapping |
|
||||
|
||||
### Files
|
||||
|
||||
| Table | Description |
|
||||
|-------|-------------|
|
||||
| `file` | File metadata (path, type, size, entity reference) |
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- UUIDs for all primary keys (`defaultRandom()`)
|
||||
- `created_at` and `updated_at` timestamps with timezone on all tables
|
||||
- Soft deletes via `is_active` boolean where applicable
|
||||
- Auto-generated sequential numbers: `account_number` (6-digit), `member_number`
|
||||
- Lookup tables support both system (immutable) and custom (company-scoped) values
|
||||
89
docs/setup.md
Normal file
89
docs/setup.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Development Setup
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Bun](https://bun.sh) v1.1+
|
||||
- PostgreSQL 16
|
||||
- Valkey 8 (or Redis 7+)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone <repo-url> && cd forte
|
||||
bun install
|
||||
```
|
||||
|
||||
## Environment
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://forte:forte@localhost:5432/forte
|
||||
REDIS_URL=redis://localhost:6379
|
||||
JWT_SECRET=your-secret-here
|
||||
NODE_ENV=development
|
||||
```
|
||||
|
||||
### All Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DATABASE_URL` | `postgresql://forte:forte@localhost:5432/forte` | PostgreSQL connection string |
|
||||
| `REDIS_URL` | `redis://localhost:6379` | Valkey/Redis connection string |
|
||||
| `JWT_SECRET` | (auto-generated in dev) | Secret for signing JWTs. **Required in production.** |
|
||||
| `PORT` | `8000` | Backend API port |
|
||||
| `HOST` | `0.0.0.0` | Backend bind address |
|
||||
| `NODE_ENV` | — | `development`, `test`, or `production` |
|
||||
| `LOG_LEVEL` | `info` | Pino log level (`debug`, `info`, `warn`, `error`, `silent`) |
|
||||
| `LOG_FILE` | — | Path to log file (production only, also logs to stdout) |
|
||||
| `STORAGE_PROVIDER` | `local` | File storage provider (`local` or `s3`) |
|
||||
| `STORAGE_LOCAL_PATH` | `./data/files` | Local file storage directory |
|
||||
| `CORS_ORIGINS` | `*` (dev) | Comma-separated allowed origins |
|
||||
| `APP_URL` | `http://localhost:5173` | Frontend URL (used in password reset links) |
|
||||
|
||||
## Database
|
||||
|
||||
```bash
|
||||
# Create the database
|
||||
createdb forte
|
||||
|
||||
# Run migrations
|
||||
cd packages/backend
|
||||
source ../.env # or source .env from project root
|
||||
bunx drizzle-kit migrate
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
# From project root — starts all packages
|
||||
bun run dev
|
||||
|
||||
# Or individually:
|
||||
cd packages/backend && source .env && bun run dev # API on :8000
|
||||
cd packages/admin && bun run dev # UI on :5173
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# API integration tests (starts a backend, seeds DB, runs HTTP tests)
|
||||
cd packages/backend
|
||||
source .env
|
||||
bun run api-test
|
||||
|
||||
# Filter by suite or tag
|
||||
bun run api-test --suite accounts
|
||||
bun run api-test --tag crud
|
||||
|
||||
# Unit tests
|
||||
bun run test
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
bun run lint # ESLint across all packages
|
||||
bun run format # Prettier write
|
||||
bun run build # Build all packages
|
||||
```
|
||||
81
docs/testing.md
Normal file
81
docs/testing.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Testing
|
||||
|
||||
## API Integration Tests
|
||||
|
||||
The primary test suite lives at `packages/backend/api-tests/`. It uses a custom runner that:
|
||||
|
||||
1. Creates/migrates a `forte_api_test` database
|
||||
2. Seeds company, lookup tables, RBAC permissions/roles
|
||||
3. Starts the backend on port 8001
|
||||
4. Registers a test user with admin role
|
||||
5. Runs test suites via HTTP requests
|
||||
6. Tears down the server
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
cd packages/backend
|
||||
source .env # needs DB credentials
|
||||
bun run api-test
|
||||
```
|
||||
|
||||
### Filtering
|
||||
|
||||
```bash
|
||||
bun run api-test --suite accounts # run only the accounts suite
|
||||
bun run api-test --tag crud # run tests tagged 'crud'
|
||||
```
|
||||
|
||||
### Suites
|
||||
|
||||
| Suite | Tests | Coverage |
|
||||
|-------|-------|----------|
|
||||
| `accounts` | 17 | CRUD, search, pagination, sort, billing mode |
|
||||
| `members` | 19 | CRUD, search, move, isMinor, address inheritance |
|
||||
| `files` | 7 | Upload, list, get, delete, validation, profile pictures |
|
||||
| `rbac` | 21 | Permission enforcement, role CRUD, user status, pagination |
|
||||
|
||||
### Writing Tests
|
||||
|
||||
Tests are defined in `api-tests/suites/`. Each file exports a suite:
|
||||
|
||||
```typescript
|
||||
import { suite } from '../lib/context.js'
|
||||
|
||||
suite('MyDomain', { tags: ['domain'] }, (t) => {
|
||||
t.test('does something', { tags: ['crud'] }, async () => {
|
||||
const res = await t.api.post('/v1/endpoint', { name: 'Test' })
|
||||
t.assert.status(res, 201)
|
||||
t.assert.equal(res.data.name, 'Test')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Available on `t`:
|
||||
- `t.api` — HTTP client (`get`, `post`, `patch`, `del`) with auth token
|
||||
- `t.token` — JWT token string
|
||||
- `t.baseUrl` — Backend URL
|
||||
- `t.assert` — Assertion helpers (`status`, `equal`, `ok`, `includes`, `greaterThan`, etc.)
|
||||
|
||||
### Assert Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `status(res, code)` | Check HTTP status |
|
||||
| `equal(a, b)` | Strict equality |
|
||||
| `notEqual(a, b)` | Strict inequality |
|
||||
| `ok(value)` | Truthy check |
|
||||
| `includes(arr, value)` | Array includes |
|
||||
| `contains(str, sub)` | String contains |
|
||||
| `greaterThan(a, b)` | Numeric comparison |
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Unit tests use `bun:test` and live alongside the code or in `__tests__/`:
|
||||
|
||||
```bash
|
||||
cd packages/backend
|
||||
bun run test
|
||||
```
|
||||
|
||||
Current unit test coverage: shared utils (date helpers, currency formatting, state normalization).
|
||||
Reference in New Issue
Block a user