@strav/testing 0.4.31 → 1.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,82 +1,45 @@
1
1
  # @strav/testing
2
2
 
3
- Testing utilities for the [Strav](https://www.npmjs.com/package/@strav/core) framework. Provides HTTP testing helpers, authentication simulation, transaction-based test isolation, and model factories.
4
-
5
- ## Install
6
-
7
- ```bash
8
- bun add -d @strav/testing
9
- ```
10
-
11
- Requires `@strav/core` as a peer dependency.
12
-
13
- ## TestCase
14
-
15
- Boots the app, provides HTTP helpers, and wraps each test in a rolled-back transaction for full isolation.
3
+ Small, focused testing utilities for Strav apps and the framework itself.
16
4
 
17
5
  ```ts
18
- import { describe, test, expect } from 'bun:test'
19
- import { TestCase } from '@strav/testing'
20
-
21
- const t = await TestCase.boot({
22
- auth: true,
23
- routes: () => import('./start/api_routes'),
24
- })
25
-
26
- describe('Posts API', () => {
27
- test('list posts', async () => {
28
- const res = await t.get('/api/posts')
29
- expect(res.status).toBe(200)
30
- })
31
-
32
- test('create post as authenticated user', async () => {
33
- const user = await UserFactory.create()
34
- await t.actingAs(user)
35
-
36
- const res = await t.post('/api/posts', { title: 'Hello' })
37
- expect(res.status).toBe(201)
38
- })
39
- })
6
+ import {
7
+ bootTestApp,
8
+ isPostgresAvailable,
9
+ MemStream,
10
+ stubFetch,
11
+ } from '@strav/testing'
12
+
13
+ if (!await isPostgresAvailable()) {
14
+ test.skip('integration: ', () => {})
15
+ } else {
16
+ // real Postgres flow
17
+ }
18
+
19
+ const stdout = new MemStream()
20
+ const fetch = stubFetch(async (req) => Response.json({ ok: true }))
40
21
  ```
41
22
 
42
- ### HTTP Methods
23
+ Canonical docs live in [`docs/testing/README.md`](../../docs/testing/README.md).
43
24
 
44
- ```ts
45
- await t.get('/path')
46
- await t.post('/path', body)
47
- await t.put('/path', body)
48
- await t.patch('/path', body)
49
- await t.delete('/path')
50
- ```
51
-
52
- ### Auth Helpers
53
-
54
- ```ts
55
- await t.actingAs(user) // authenticate as user
56
- t.withHeaders({ 'X-Custom': 'value' })
57
- t.withoutAuth() // clear auth token
58
- ```
25
+ ## What ships
59
26
 
60
- ## Factory
27
+ | Surface | Notes |
28
+ |---|---|
29
+ | `bootTestApp({ config, schemas, migrations, providers })` | Replaces the ~50-line `beforeAll` boilerplate every e2e was rolling. Auto-supplies the standard four providers, applies schemas + migrations against `setupDb`, returns `{ app, setupDb, dispose }`. |
30
+ | `composeTestConfig(overrides)` | Merges logger + database defaults with per-test overrides. Used internally by `bootTestApp`; exported for tests that want the config tree without the orchestrator. |
31
+ | `TenantManagerProvider` | Standard 3-line wiring extracted from m5 / m6 / m7. |
32
+ | `MemStream` | In-memory `NodeJS.WritableStream` for asserting on stdout / stderr. Pairs with `ConsoleOutput`. |
33
+ | `stubFetch(handler)` | Typed `fetch` replacement. Confines the `as unknown as typeof fetch` cast to one place. |
34
+ | `isPostgresAvailable()` | Cached probe — returns `false` when env is missing or connection fails. |
35
+ | `createTestDatabase()` | Construct a fresh `PostgresDatabase` from `DB_HOST`/`DB_PORT`/etc. |
36
+ | `resetSchema(db)` | DROP + recreate `public` schema. |
37
+ | `connectedRoleBypassesRls(db)` | True for SUPERUSER / BYPASSRLS roles — tests use it to degrade RLS assertions. |
38
+ | `testDatabaseUrl()` | Returns the Postgres URL or `null` when env is missing. |
61
39
 
62
- Lightweight model factory for test data seeding.
63
-
64
- ```ts
65
- import { Factory } from '@strav/testing'
66
-
67
- const UserFactory = Factory.define(User, (seq) => ({
68
- pid: crypto.randomUUID(),
69
- name: `User ${seq}`,
70
- email: `user-${seq}@test.com`,
71
- passwordHash: 'hashed',
72
- }))
73
-
74
- const user = await UserFactory.create()
75
- const users = await UserFactory.createMany(5)
76
- const user = await UserFactory.create({ name: 'Custom' })
77
- const instance = UserFactory.make() // in-memory only, no DB
78
- ```
40
+ ## Subpaths
79
41
 
80
- ## License
42
+ - `@strav/testing/postgres` — narrow import for just the Postgres helpers.
43
+ - `@strav/testing/brain` — `stubBrainProvider({ embed, model? })`. Requires `@strav/brain` installed (peer-optional).
81
44
 
82
- MIT
45
+ Deferred to follow-up slices: `stubPaymentDriver`, `stubSocialDriver` — extract when the inline forms in `tests/e2e/m{6,7}-*/` show overlap.
package/package.json CHANGED
@@ -1,44 +1,38 @@
1
1
  {
2
2
  "name": "@strav/testing",
3
- "version": "0.4.31",
3
+ "version": "1.0.0-alpha.25",
4
+ "description": "Strav testing utilities — in-memory stream, typed fetch stub, Postgres availability probe + schema reset, bootTestApp orchestrator, stub providers.",
4
5
  "type": "module",
5
- "description": "Testing utilities for the Strav framework",
6
- "license": "MIT",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
7
8
  "exports": {
8
9
  ".": "./src/index.ts",
9
- "./*": "./src/*.ts"
10
+ "./brain": "./src/brain/index.ts",
11
+ "./cache": "./src/cache/index.ts",
12
+ "./postgres": "./src/postgres/index.ts"
10
13
  },
11
14
  "files": [
12
- "src/",
13
- "package.json",
14
- "tsconfig.json",
15
- "CHANGELOG.md"
15
+ "src",
16
+ "README.md"
16
17
  ],
18
+ "engines": {
19
+ "bun": ">=1.3.14"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "dependencies": {
25
+ "@strav/database": "1.0.0-alpha.25",
26
+ "@strav/kernel": "1.0.0-alpha.25"
27
+ },
17
28
  "peerDependencies": {
18
- "@strav/kernel": "0.4.31",
19
- "@strav/http": "0.4.31",
20
- "@strav/view": "0.4.31",
21
- "@strav/database": "0.4.31",
22
- "@strav/signal": "0.4.31",
23
- "@strav/cli": "0.4.31",
24
- "playwright-core": "^1.45.0"
29
+ "@strav/brain": "1.0.0-alpha.25",
30
+ "@types/bun": ">=1.3.14"
25
31
  },
26
32
  "peerDependenciesMeta": {
27
- "@strav/signal": {
28
- "optional": true
29
- },
30
- "@strav/cli": {
31
- "optional": true
32
- },
33
- "playwright-core": {
33
+ "@strav/brain": {
34
34
  "optional": true
35
35
  }
36
36
  },
37
- "scripts": {
38
- "test": "bun test tests/",
39
- "typecheck": "tsc --noEmit"
40
- },
41
- "devDependencies": {
42
- "playwright-core": "^1.45.0"
43
- }
37
+ "devDependencies": null
44
38
  }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Boot a tested Application with the standard four providers
3
+ * (`ConfigProvider` → `LoggerProvider` → `DatabaseProvider` →
4
+ * `TenantManagerProvider`) pre-installed, real Postgres reset, schemas
5
+ * applied, optional migrations run, and the per-test providers wired
6
+ * on top. Returns the started `app`, the admin-level `setupDb` (kept
7
+ * open until `dispose()`), and the `dispose()` cleanup that pairs with
8
+ * `afterAll`.
9
+ *
10
+ * Replaces the ~50 LOC `beforeAll` boilerplate that every integration
11
+ * / e2e suite was rolling by hand. Per-test variation (custom config
12
+ * sub-trees, schemas, migrations, post-boot `useDriver` hand-wiring,
13
+ * HTTP server spin-up) lives where it always did.
14
+ *
15
+ * ```ts
16
+ * import { afterAll, beforeAll, describe } from 'bun:test'
17
+ * import {
18
+ * bootTestApp,
19
+ * isPostgresAvailable,
20
+ * type BootTestAppResult,
21
+ * } from '@strav/testing'
22
+ * import { RagProvider, ragVectorSchema, applyRagVectorMigration } from '@strav/rag'
23
+ *
24
+ * const PG = await isPostgresAvailable()
25
+ *
26
+ * describe.skipIf(!PG)('M5 e2e', () => {
27
+ * let booted: BootTestAppResult
28
+ * beforeAll(async () => {
29
+ * booted = await bootTestApp({
30
+ * config: { rag: { ... } },
31
+ * schemas: [tenantSchema, articleSchema, ragVectorSchema],
32
+ * migrations: [(db, registry) => applyRagVectorMigration(db, { dimension: 4, registry })],
33
+ * providers: [new StubBrainProvider(), new RagProvider()],
34
+ * })
35
+ * })
36
+ * afterAll(() => booted.dispose())
37
+ *
38
+ * // test cases pull from `booted.app.resolve(...)` and `booted.setupDb`.
39
+ * })
40
+ * ```
41
+ *
42
+ * The orchestration order, per spec from the e2e survey:
43
+ *
44
+ * 1. `createTestDatabase()` → admin connection for setup.
45
+ * 2. `resetSchema(setupDb)` → DROP + CREATE `public`.
46
+ * 3. Apply each schema's `emitCreateTable(schema, { registry }).sql`.
47
+ * 4. Run each migration with `(setupDb, registry)`.
48
+ * 5. Construct config via `composeTestConfig(config)` (auto-supplies
49
+ * logger + database.url unless overridden).
50
+ * 6. `new Application()`, `useProviders([Config, Logger, Database,
51
+ * TenantManager, ...userProviders])` unless `skipDefaultProviders`.
52
+ * 7. Bind `SchemaRegistry` as a singleton with `schemas` registered.
53
+ * 8. `await app.start({ signalHandlers: false })`.
54
+ */
55
+
56
+ import {
57
+ DatabaseProvider,
58
+ PostgresDatabase,
59
+ type Schema,
60
+ SchemaRegistry,
61
+ emitCreateTable,
62
+ } from '@strav/database'
63
+ import {
64
+ Application,
65
+ ConfigProvider,
66
+ LoggerProvider,
67
+ type ServiceProvider,
68
+ } from '@strav/kernel'
69
+ import { type ConfigOverrides, composeTestConfig } from './compose_test_config.ts'
70
+ import { createTestDatabase } from './postgres/create_test_database.ts'
71
+ import { resetSchema } from './postgres/reset_schema.ts'
72
+ import { TenantManagerProvider } from './tenant_manager_provider.ts'
73
+
74
+ /** Migration hook signature — runs against `setupDb` before `app.start`. */
75
+ export type TestMigration = (
76
+ db: PostgresDatabase,
77
+ registry: SchemaRegistry,
78
+ ) => Promise<void> | void
79
+
80
+ export interface BootTestAppOptions {
81
+ /**
82
+ * Per-test config sub-trees. `logger` and `database` keys are
83
+ * auto-supplied unless overridden here. Other keys (`rag`, `payment`,
84
+ * `social`, `encryption`, …) are passed through verbatim.
85
+ */
86
+ config?: ConfigOverrides
87
+ /**
88
+ * Schemas registered in the `SchemaRegistry` singleton AND applied
89
+ * via `emitCreateTable(schema, { registry }).sql` against `setupDb`
90
+ * before `app.start`. Order matters when there are FK dependencies.
91
+ */
92
+ schemas?: readonly Schema[]
93
+ /**
94
+ * Migrations to run after `schemas` are applied. Each receives the
95
+ * admin connection + the registry so it can compose SQL the same way
96
+ * the production migration helpers do.
97
+ */
98
+ migrations?: readonly TestMigration[]
99
+ /**
100
+ * Per-test service providers — appended after the standard four
101
+ * (`Config`, `Logger`, `Database`, `TenantManager`). Order within
102
+ * this list matters for the kernel's topological sort.
103
+ */
104
+ providers?: readonly ServiceProvider[]
105
+ /**
106
+ * Opt out of the standard four. Useful for tests that need a custom
107
+ * `LoggerProvider`, no `DatabaseProvider`, etc. When `true`, you own
108
+ * the entire provider list via `providers`. Default `false`.
109
+ */
110
+ skipDefaultProviders?: boolean
111
+ }
112
+
113
+ export interface BootTestAppResult {
114
+ /** Started Application — call `resolve(X)` from test cases. */
115
+ app: Application
116
+ /**
117
+ * Admin-level Postgres connection used to apply DDL + migrations and
118
+ * to run setup queries (seeding tenants, asserting schema state). Kept
119
+ * open until `dispose()`.
120
+ */
121
+ setupDb: PostgresDatabase
122
+ /** Cleanup: shutdown app + close setupDb. Use in `afterAll`. */
123
+ dispose(): Promise<void>
124
+ }
125
+
126
+ export async function bootTestApp(options: BootTestAppOptions = {}): Promise<BootTestAppResult> {
127
+ const setupDb = createTestDatabase()
128
+ await resetSchema(setupDb)
129
+
130
+ const schemas = options.schemas ?? []
131
+ const registry = new SchemaRegistry().registerAll(schemas)
132
+
133
+ for (const schema of schemas) {
134
+ await setupDb.execute(emitCreateTable(schema, { registry }).sql)
135
+ }
136
+
137
+ for (const migration of options.migrations ?? []) {
138
+ await migration(setupDb, registry)
139
+ }
140
+
141
+ const configData = composeTestConfig(options.config ?? {})
142
+
143
+ const app = new Application()
144
+ const defaults = options.skipDefaultProviders
145
+ ? []
146
+ : [
147
+ new ConfigProvider(configData),
148
+ new LoggerProvider(),
149
+ new DatabaseProvider(),
150
+ new TenantManagerProvider(),
151
+ ]
152
+ app.useProviders([...defaults, ...(options.providers ?? [])])
153
+
154
+ // Bind the SchemaRegistry singleton so repositories that need it can
155
+ // resolve through the container instead of getting hand-wired.
156
+ app.singleton(SchemaRegistry, () => registry)
157
+
158
+ await app.start({ signalHandlers: false })
159
+
160
+ let disposed = false
161
+ const dispose = async (): Promise<void> => {
162
+ if (disposed) return
163
+ disposed = true
164
+ await app.shutdown()
165
+ await setupDb.close({ timeout: 2 })
166
+ }
167
+
168
+ return { app, setupDb, dispose }
169
+ }
@@ -0,0 +1 @@
1
+ export { stubBrainProvider, type StubBrainOptions } from './stub_brain_provider.ts'
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Stub `BrainManager` wired as a `ServiceProvider` for tests that need
3
+ * deterministic embeddings without dialing an actual brain backend.
4
+ *
5
+ * Extracted from `tests/e2e/m5-rag/`. The provider declares
6
+ * `name: 'brain'` and `dependencies: ['config']`, matching the real
7
+ * `BrainProvider`'s shape — `RagProvider` (which declares
8
+ * `dependencies: ['config', 'brain']`) resolves the stub correctly
9
+ * when both are in the provider list.
10
+ *
11
+ * ```ts
12
+ * import { stubBrainProvider } from '@strav/testing/brain'
13
+ *
14
+ * const provider = stubBrainProvider({
15
+ * embed: (text) => bagOfWords(text), // returns number[]
16
+ * })
17
+ *
18
+ * app.useProviders([
19
+ * new ConfigProvider({ ... }),
20
+ * new LoggerProvider(),
21
+ * new DatabaseProvider(),
22
+ * provider, // ← stub registered here
23
+ * new RagProvider(), // ← resolves the stub for its embed calls
24
+ * ])
25
+ * ```
26
+ *
27
+ * The `embed` callback is per-text — the helper maps over the input
28
+ * array internally. Only `embed` is stubbed; other `BrainManager`
29
+ * surface (`chat`, `stream`, `runWithTools`, …) throws when called.
30
+ * V1 scope is rag-style tests; broader stubs land when a second
31
+ * use case appears.
32
+ */
33
+
34
+ import { BrainManager } from '@strav/brain'
35
+ import { type Application, ServiceProvider } from '@strav/kernel'
36
+
37
+ export interface StubBrainOptions {
38
+ /**
39
+ * Returns the embedding vector for a single text. The provider maps
40
+ * the user's `embed` over `texts` internally to produce `number[][]`.
41
+ */
42
+ embed: (text: string) => number[]
43
+ /** Model identifier surfaced on `embed` results. Default `'stub'`. */
44
+ model?: string
45
+ }
46
+
47
+ export function stubBrainProvider(options: StubBrainOptions): ServiceProvider {
48
+ const model = options.model ?? 'stub'
49
+ const userEmbed = options.embed
50
+ return new (class StubBrainProvider extends ServiceProvider {
51
+ override readonly name = 'brain'
52
+ override readonly dependencies = ['config']
53
+ override register(app: Application): void {
54
+ app.singleton(BrainManager, () => buildStub(userEmbed, model))
55
+ }
56
+ })()
57
+ }
58
+
59
+ function buildStub(
60
+ embed: (text: string) => number[],
61
+ model: string,
62
+ ): BrainManager {
63
+ const stub = {
64
+ embed: async (texts: readonly string[]) => ({
65
+ embeddings: texts.map(embed),
66
+ model,
67
+ usage: { inputTokens: 0 },
68
+ raw: null,
69
+ }),
70
+ }
71
+ return stub as unknown as BrainManager
72
+ }
@@ -0,0 +1,2 @@
1
+ export { isMemcachedAvailable } from './is_memcached_available.ts'
2
+ export { isRedisAvailable } from './is_redis_available.ts'
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Cheap connection probe over Bun's TCP socket — opens a connection,
3
+ * sends `version\r\n`, expects a `VERSION ...\r\n` reply. Cached for
4
+ * the lifetime of the test process.
5
+ *
6
+ * Returns `false` if `MEMCACHED_HOST` / `MEMCACHED_PORT` are missing
7
+ * OR the probe fails. Pair with
8
+ * `describe.skipIf(!await isMemcachedAvailable())`.
9
+ */
10
+
11
+ let cachedAvailability: boolean | null = null
12
+
13
+ export async function isMemcachedAvailable(): Promise<boolean> {
14
+ if (cachedAvailability !== null) return cachedAvailability
15
+ const host = process.env['MEMCACHED_HOST']
16
+ const portStr = process.env['MEMCACHED_PORT']
17
+ if (host === undefined || host === '' || portStr === undefined || portStr === '') {
18
+ cachedAvailability = false
19
+ return false
20
+ }
21
+ const port = Number(portStr)
22
+ if (!Number.isFinite(port) || port <= 0) {
23
+ cachedAvailability = false
24
+ return false
25
+ }
26
+ try {
27
+ cachedAvailability = await probe(host, port)
28
+ } catch {
29
+ cachedAvailability = false
30
+ }
31
+ return cachedAvailability
32
+ }
33
+
34
+ function probe(host: string, port: number): Promise<boolean> {
35
+ return new Promise((resolve) => {
36
+ let settled = false
37
+ const finish = (ok: boolean): void => {
38
+ if (settled) return
39
+ settled = true
40
+ resolve(ok)
41
+ }
42
+ const timeout = setTimeout(() => finish(false), 2_000)
43
+ void Bun.connect({
44
+ hostname: host,
45
+ port,
46
+ socket: {
47
+ open(socket) {
48
+ socket.write('version\r\n')
49
+ },
50
+ data(socket, chunk) {
51
+ const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength)
52
+ const text = new TextDecoder().decode(bytes)
53
+ const ok = text.startsWith('VERSION')
54
+ // Settle before closing — `socket.end()` synchronously fires
55
+ // the `close` callback, which would beat `finish(ok)` to the
56
+ // settle line and report a false negative.
57
+ clearTimeout(timeout)
58
+ finish(ok)
59
+ socket.end()
60
+ },
61
+ error(_socket, _error) {
62
+ clearTimeout(timeout)
63
+ finish(false)
64
+ },
65
+ close() {
66
+ clearTimeout(timeout)
67
+ if (!settled) finish(false)
68
+ },
69
+ },
70
+ }).catch(() => {
71
+ clearTimeout(timeout)
72
+ finish(false)
73
+ })
74
+ })
75
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Cheap connection probe — opens `Bun.RedisClient` against
3
+ * `REDIS_URL`, sends `PING`, reports. Cached for the lifetime of the
4
+ * test process.
5
+ *
6
+ * Returns `false` if `REDIS_URL` is missing OR the connection / PING
7
+ * fails. Pair with `describe.skipIf(!await isRedisAvailable())`.
8
+ */
9
+
10
+ import { RedisClient } from 'bun'
11
+
12
+ let cachedAvailability: boolean | null = null
13
+
14
+ export async function isRedisAvailable(): Promise<boolean> {
15
+ if (cachedAvailability !== null) return cachedAvailability
16
+ const url = process.env['REDIS_URL']
17
+ if (url === undefined || url === '') {
18
+ cachedAvailability = false
19
+ return false
20
+ }
21
+ let client: RedisClient | undefined
22
+ try {
23
+ client = new RedisClient(url)
24
+ // `send('PING', [])` is supported on every Bun.RedisClient build —
25
+ // safer than `ping()` which isn't on the typed surface.
26
+ const reply = await client.send('PING', [])
27
+ cachedAvailability = reply === 'PONG' || reply === 'OK' || typeof reply === 'string'
28
+ return cachedAvailability
29
+ } catch {
30
+ cachedAvailability = false
31
+ return false
32
+ } finally {
33
+ try {
34
+ client?.close()
35
+ } catch {
36
+ // Already closed / never connected — nothing to clean.
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Merge per-test config overrides with the standard test defaults.
3
+ *
4
+ * Defaults supplied:
5
+ *
6
+ * - `logger`: silent + stderr channel — keeps `bun test` output clean.
7
+ * - `database.url`: composed from `DB_*` env vars via `testDatabaseUrl()`.
8
+ *
9
+ * Both are deep-merged with `overrides`. Pass `logger` or `database` in
10
+ * `overrides` to replace the default for that key. Other keys
11
+ * (`rag`, `payment`, `social`, `encryption`, etc.) are taken verbatim
12
+ * from `overrides`.
13
+ *
14
+ * Throws when `DB_*` env vars are missing AND no `database.url` override
15
+ * is supplied — `bootTestApp` callers gate via `isPostgresAvailable()`
16
+ * first, so the throw should only fire in misconfigured environments.
17
+ */
18
+
19
+ import { testDatabaseUrl } from './postgres/test_database_url.ts'
20
+
21
+ export type ConfigOverrides = Record<string, unknown>
22
+
23
+ const DEFAULT_LOGGER = {
24
+ default: 'main',
25
+ level: 'silent',
26
+ channels: { main: { driver: 'stderr' } },
27
+ } as const
28
+
29
+ export function composeTestConfig(overrides: ConfigOverrides = {}): Record<string, unknown> {
30
+ const merged: Record<string, unknown> = { ...overrides }
31
+
32
+ if (!('logger' in merged)) {
33
+ merged.logger = { ...DEFAULT_LOGGER }
34
+ }
35
+
36
+ if (!('database' in merged)) {
37
+ const url = testDatabaseUrl()
38
+ if (url === null) {
39
+ throw new Error(
40
+ 'composeTestConfig: missing DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_DATABASE env. Source .env.test, run docker-compose up, or pass `database` explicitly in overrides.',
41
+ )
42
+ }
43
+ merged.database = { url }
44
+ }
45
+
46
+ return merged
47
+ }
package/src/index.ts CHANGED
@@ -1,17 +1,33 @@
1
- export { TestCase } from './test_case.ts'
2
- export type { TestCaseOptions } from './test_case.ts'
3
- export { Factory } from './factory.ts'
1
+ // Public API of @strav/testing.
2
+ //
3
+ // V1 ships the small utilities that get re-implemented inline in every
4
+ // test: an in-memory writable stream, a typed fetch stub, and the
5
+ // Postgres availability + reset helpers used by integration suites.
6
+ // V2 (this slice) adds bootTestApp + composeTestConfig +
7
+ // TenantManagerProvider for the e2e boot-dance.
4
8
 
5
- // Database management
6
- export { TestDatabaseManager, cleanupTestDatabase } from './database_manager.ts'
9
+ export {
10
+ bootTestApp,
11
+ type BootTestAppOptions,
12
+ type BootTestAppResult,
13
+ type TestMigration,
14
+ } from './boot_test_app.ts'
15
+ export { composeTestConfig, type ConfigOverrides } from './compose_test_config.ts'
16
+ export { MemStream } from './mem_stream.ts'
17
+ export { stubFetch, type FetchHandler } from './stub_fetch.ts'
18
+ export { TenantManagerProvider } from './tenant_manager_provider.ts'
7
19
 
8
- // Browser-driven testing (Playwright). Lazy-loads playwright-core only when
9
- // BrowserTestCase / DemoFlow are actually instantiated.
10
- export { BrowserTestCase, DemoFlow, runFresh } from './browser/index.ts'
11
- export type {
12
- BrowserTestCaseOptions,
13
- BrowserName,
14
- MailMode,
15
- DemoFlowOptions,
16
- ServerHandle,
17
- } from './browser/index.ts'
20
+ // Postgres helpers also re-exported under `@strav/testing/postgres`
21
+ // for apps that want to import them without pulling in the rest of
22
+ // the barrel.
23
+ export {
24
+ connectedRoleBypassesRls,
25
+ createTestDatabase,
26
+ isPostgresAvailable,
27
+ resetSchema,
28
+ testDatabaseUrl,
29
+ } from './postgres/index.ts'
30
+
31
+ // Cache availability probes — also re-exported under
32
+ // `@strav/testing/cache` for the same reason.
33
+ export { isMemcachedAvailable, isRedisAvailable } from './cache/index.ts'