feat: Sprint 1 remainder — Postgres store, Docker client, warm pool, sessions route
Mirror to GitHub / mirror (push) Canceled after 0s

- PostgresUserStore (Queryable) + migration + env-gated wiring
- DockerodeClient behind injected DockerEngine
- ImageManager, WarmPool, Dockerfile.wordpress, docker-compose
- POST /sessions spins up sandbox (503 when unconfigured)
- ADRs 0014-0015; 68 unit tests green
This commit is contained in:
SinachPat
2026-08-15 20:18:34 +01:00
parent 97f31315bd
commit df2315087d
23 changed files with 1119 additions and 11 deletions
@@ -0,0 +1,44 @@
import { describe, it, expect } from 'vitest';
import { PostgresUserStore } from '../../src/services/postgres-user-store.ts';
import type { Queryable } from '../../src/services/postgres-user-store.ts';
class FakeDb implements Queryable {
calls: Array<{ text: string; values?: unknown[] }> = [];
rows: unknown[] = [];
async query(text: string, values?: unknown[]) {
this.calls.push({ text, values });
return { rows: this.rows };
}
}
describe('PostgresUserStore', () => {
it('returns a user when the email exists', async () => {
const db = new FakeDb();
db.rows = [{ id: 'u1', email: 'a@example.com', password_hash: 'hash' }];
const store = new PostgresUserStore(db);
const user = await store.findByEmail('a@example.com');
expect(user).toEqual({ id: 'u1', email: 'a@example.com', passwordHash: 'hash' });
expect(db.calls[0]?.text).toContain('SELECT');
});
it('returns undefined when the email is absent', async () => {
const db = new FakeDb();
db.rows = [];
const store = new PostgresUserStore(db);
expect(await store.findByEmail('nope@example.com')).toBeUndefined();
});
it('inserts a new user and returns it', async () => {
const db = new FakeDb();
const store = new PostgresUserStore(db);
const created = await store.create({ id: 'u1', email: 'a@example.com', passwordHash: 'hash' });
expect(created).toEqual({ id: 'u1', email: 'a@example.com', passwordHash: 'hash' });
const insert = db.calls[0];
expect(insert?.text).toContain('INSERT INTO users');
expect(insert?.values).toEqual(['u1', 'a@example.com', 'hash']);
});
});
+48
View File
@@ -0,0 +1,48 @@
import { describe, it, expect } from 'vitest';
import { WarmPool, computeSpares } from '../../src/services/warm-pool.ts';
import type { DockerClient, SandboxInfo } from '../../src/sandbox/docker-client.ts';
class FakeDocker implements DockerClient {
created: string[] = [];
async createSandbox(image: string): Promise<SandboxInfo> {
this.created.push(image);
return { id: `sb-${this.created.length}`, status: 'running' };
}
async destroySandbox(): Promise<void> {}
async status(): Promise<SandboxInfo | undefined> {
return undefined;
}
}
describe('computeSpares', () => {
it('returns the shortfall', () => {
expect(computeSpares(1, 3)).toBe(2);
});
it('never returns a negative number', () => {
expect(computeSpares(5, 2)).toBe(0);
});
});
describe('WarmPool', () => {
it('tops up the pool to the target hot-spare count', async () => {
const docker = new FakeDocker();
const pool = new WarmPool(docker, { image: 'wursor-base:latest', hotSpares: 2 });
const result = await pool.topUp(0);
expect(result.created).toBe(2);
expect(docker.created).toEqual(['wursor-base:latest', 'wursor-base:latest']);
});
it('does nothing when the pool is already full', async () => {
const docker = new FakeDocker();
const pool = new WarmPool(docker, { image: 'wursor-base:latest', hotSpares: 2 });
const result = await pool.topUp(3);
expect(result.created).toBe(0);
expect(docker.created).toEqual([]);
});
});