Compare commits

...

4 Commits

Author SHA1 Message Date
igm
730021a5ea Merge branch 'parallelize-tests-better' 2026-01-10 19:17:50 +08:00
igm
c698a85cc1 update readme 2026-01-10 19:17:48 +08:00
igm
462799ca3d Apply linting fixes and update schema
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 19:17:39 +08:00
igm
dcb48a5d5e Migrate e2e tests to transaction-based isolation
Replace table truncation with transaction rollback for test isolation.
Each test now runs in a transaction that auto-rolls back, improving
test performance and isolation. Tests that call procedures with internal
transactions use getSharedDb() directly with appropriate comments.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 19:16:47 +08:00
8 changed files with 3512 additions and 2995 deletions

View File

@@ -19,6 +19,11 @@ A modern publisher dashboard for managing organizations, members, and sites. Bui
- **PostgreSQL** database
- **Postmark** for transactional emails
### CLI (`apps/cli`)
- **Stricli** for command parsing
- API token-based authentication
- User, organization, and site management commands
### Shared Packages
- `@reviq/api-contract` - Shared API contract (oRPC)
- `@reviq/db` - Database client and queries
@@ -31,7 +36,7 @@ A modern publisher dashboard for managing organizations, members, and sites. Bui
publisher-dashboard/
├── apps/
│ ├── api-server/ # Backend API server
│ ├── cli/ # CLI tools
│ ├── cli/ # Command-line interface
│ └── publisher-dashboard/ # SvelteKit frontend
├── packages/
│ ├── api-contract/ # Shared oRPC contract
@@ -107,6 +112,23 @@ bun run dev
| `bun run test` | Run tests |
| `bun run db:codegen` | Generate database types |
## CLI
The `@reviq/cli` package provides a command-line interface for managing users, organizations, and sites. See [apps/cli/README.md](apps/cli/README.md) for detailed usage.
Quick start:
```bash
# Build the CLI
bun run --cwd apps/cli build
# Login with an API token
./apps/cli/dist/reviq auth login --token <your-token>
# Check status
./apps/cli/dist/reviq auth status
```
## Features
### Authentication

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -238,3 +238,64 @@ export async function createTestUser(
export async function destroyTestDb(db: Kysely<Database>): Promise<void> {
await db.destroy();
}
// ============================================================================
// Shared Database Singleton (for transaction-based test isolation)
// ============================================================================
let sharedDb: Kysely<Database> | null = null;
/**
* Initialize the shared test database once.
* Runs migrations and truncates all tables to start with a clean slate.
* Subsequent calls return the existing connection.
*
* Use this with `withTestTransaction()` for fast test isolation.
*
* @example
* ```typescript
* beforeAll(async () => {
* await initTestDb();
* });
*
* test("does something", async () => {
* await withTestTransaction(getSharedDb(), async (db) => {
* // test code using db
* });
* });
* ```
*/
export async function initTestDb(): Promise<Kysely<Database>> {
if (!sharedDb) {
await runMigrations();
sharedDb = createTestDb();
await truncateAllTables(sharedDb); // Clean slate once at start
}
return sharedDb;
}
/**
* Get the shared test database connection.
* Must call `initTestDb()` first.
*
* @throws Error if database not initialized
*/
export function getSharedDb(): Kysely<Database> {
if (!sharedDb) {
throw new Error(
"Test DB not initialized. Call initTestDb() in beforeAll first.",
);
}
return sharedDb;
}
/**
* Destroy the shared test database connection.
* Call this in a global afterAll if needed.
*/
export async function destroySharedDb(): Promise<void> {
if (sharedDb) {
await sharedDb.destroy();
sharedDb = null;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Transaction-based test isolation helper
*
* Wraps test code in a transaction that auto-rollbacks, providing
* fast test isolation without truncating tables between tests.
*/
import type { Database } from "@reviq/db-schema";
import type { Kysely } from "kysely";
/**
* Signal used to trigger transaction rollback after test completes
*/
class RollbackSignal extends Error {
constructor() {
super("RollbackSignal");
this.name = "RollbackSignal";
}
}
/**
* Runs a test function inside a transaction that auto-rollbacks.
*
* The transaction implements the same interface as Kysely<Database>,
* so it can be passed to context builders and used for all queries.
* After the test completes, the transaction is rolled back, providing
* instant cleanup without truncating tables.
*
* @example
* ```typescript
* test("creates user", async () => {
* await withTestTransaction(getSharedDb(), async (db) => {
* const user = await createTestUser(db, { email: "test@example.com" });
* const ctx = createAPIContext({ db });
* // ... test code
* }); // Auto-rollback here
* });
* ```
*/
export async function withTestTransaction<T>(
db: Kysely<Database>,
testFn: (trx: Kysely<Database>) => Promise<T>,
): Promise<T | undefined> {
let result: T | undefined;
try {
await db.transaction().execute(async (trx) => {
result = await testFn(trx);
// Force rollback by throwing after test completes successfully
throw new RollbackSignal();
});
} catch (e) {
// Swallow the rollback signal - this is expected behavior
if (!(e instanceof RollbackSignal)) {
throw e;
}
}
return result;
}

86
apps/cli/README.md Normal file
View File

@@ -0,0 +1,86 @@
# RevIQ CLI
Command-line interface for RevIQ database and user management.
## Installation
```bash
# Build the CLI
bun run build
# The compiled binary will be at dist/reviq
```
## Usage
```bash
# Run directly with bun
bun run cli <command>
# Or use the compiled binary
./dist/reviq <command>
```
## Commands
### Authentication
```bash
# Login with an API token
reviq auth login --token <your-token>
# Check authentication status
reviq auth status
# Logout
reviq auth logout
```
To get an API token:
1. Log in to the web dashboard
2. Go to Account Settings > API Tokens
3. Create a new token and copy it
### User Management
```bash
# Create a new user
reviq user create --email <email>
# Confirm email
reviq user confirm-email --code <code>
```
### Organization Management
```bash
# List organizations
reviq org list
# Create an organization
reviq org create --name <name> --slug <slug>
# Add a site to an organization
reviq org add-site --org <slug> --domain <domain>
```
### Admin Commands
```bash
# Complete login (admin)
reviq admin complete-login
```
### Other Commands
```bash
# Bootstrap the database
reviq bootstrap
# Generate shell completions
reviq completions
```
## Configuration
Credentials are stored at `~/.config/reviq/credentials.json`.

View File

@@ -1,4 +1,4 @@
\restrict CIj4ub2A9kD8NQM2nKa1cg31hNutT3jXdOch0DnJ2bT48qpQKbe9XxNtViPwfYR
\restrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
-- Dumped from database version 17.7
-- Dumped by pg_dump version 17.7
@@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public.user_devices
-- PostgreSQL database dump complete
--
\unrestrict CIj4ub2A9kD8NQM2nKa1cg31hNutT3jXdOch0DnJ2bT48qpQKbe9XxNtViPwfYR
\unrestrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
--