@owlmeans/test-integration 0.1.18-rc.0 → 0.1.18-rc.10

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
@@ -18,7 +18,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
18
18
  your project's skill store (`.agents/skills/`):
19
19
 
20
20
  ```sh
21
- npx @owlmeans/agent-skills
21
+ npx @owlmeans/agent-skills@^0.1.18-rc.14
22
22
  ```
23
23
 
24
24
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/test-integration",
4
- "version": "0.1.18-rc.0",
5
- "generatedAt": "2026-08-16T22:20:50.504Z",
4
+ "version": "0.1.18-rc.10",
5
+ "generatedAt": "2026-09-10T23:12:07.509Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -1,35 +1,76 @@
1
1
  ---
2
2
  name: testing-integration
3
- description: Category-C integration tests for OwlMeans Common packages that talk to external services (PostgreSQL, MongoDB, Redis, S3, Kubernetes). Env-gated, no mocks, per-suite namespaces, conditional context provisioning. Auto-invoked when writing tests in postgres*, mongo*, redis*, kluster, storage*, image-resource, server-api, server-app, queue.
3
+ description: Category-C integration tests for OwlMeans Common packages that talk to external services (PostgreSQL, MongoDB, Redis, S3, Kubernetes, SMTP). Env-gated, no mocks, per-suite namespaces, conditional context provisioning. Auto-invoked when writing tests in postgres*, mongo*, redis*, kluster, llm, storage-resource, mailer-smtp, server-mailer-mailgun, server-api, server-app.
4
4
  ---
5
5
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
6
6
 
7
7
  # Integration Tests — Category C
8
8
 
9
+ **Install:** `"@owlmeans/test-integration": "^0.1.18-rc.10"` in `devDependencies`
10
+
9
11
  Category C applies to packages that integrate with external services: `postgres`,
10
- `postgres-resource`, `mongo`, `mongo-resource`, `redis`, `redis-resource`, `kluster`,
11
- `storage-common`, `storage-resource`, `image-resource`, `server-api`, `server-app`, `queue`,
12
- `llm` (live inference providers). These tests **never** mock the external service. They run
13
- only when the corresponding env vars are set, and they self-skip cleanly when those variables
14
- are missing.
12
+ `postgres-resource`, `mongo`, `mongo-resource`, `redis`, `redis-resource`, `redis-queue`,
13
+ `kluster`, `storage-resource`, `mailer-smtp`, `server-mailer-mailgun`, `server-api`,
14
+ `server-app`, `llm` (live inference providers). These tests **never** mock the external service.
15
+ They run only when the corresponding env vars are set, and they self-skip cleanly when those
16
+ variables are missing.
17
+
18
+ `queue`, `resource`, `mailer`, `storage-common` and `image-resource` stay in category A: types,
19
+ AJV schemas and error translation only, so their specs need nothing running and there is no gate
20
+ they could open. What decides the category is what a package's own code does at runtime, not what
21
+ its manifest lists — a `*-resource` package that issues real commands through a client the db
22
+ service hands it is category C even when it names no driver of its own.
15
23
 
16
24
  ## Helpers from `@owlmeans/test-integration`
17
25
 
18
26
  | Helper | Purpose |
19
27
  |---|---|
20
- | `postgresGate()`, `mongoGate()`, `redisGate()`, `s3Gate()`, `kubeGate()` | Read env, return `{ skip, reason?, env }`. |
21
- | `randomNamespace(prefix, len?)` | Schema / DB / key / object-prefix namespacing for parallel-safe runs. |
22
- | `registerCleanup(fn)` + `runCleanups()` | Process-global LIFO queue. **Single-spec-file packages only** — see below. |
23
-
24
- The driver libraries (`pg`, `mongodb`, `ioredis`, `@aws-sdk/client-s3`,
25
- `@kubernetes/client-node`) stay where they already live — in the consuming integration
26
- packages — and are imported directly in those packages' `tests/`.
28
+ | `postgresGate()`, `mongoGate()`, `redisGate()`, `s3Gate()`, `kubeGate()`, `smtpGate()` | Read env, return `IntegrationGate<E>` = `{ skip, reason?, env }`. |
29
+ | `IntegrationGate<E>` | `{ skip: boolean, reason?: string, env: Partial<E> }`. `env` holds only the variables that were actually populated. `skip` is a plain `boolean`, not a discriminant, so `env` stays `Partial<E>` on the open branch too — a suite reads a required variable through a cast: `gate.env.MONGO_URL as string`. |
30
+ | `PostgresEnv`, `MongoEnv`, `RedisEnv`, `S3Env`, `KubeEnv`, `SmtpEnv` | The variable set each gate reads, so `tests/context.ts` types its exported gate. |
31
+ | `randomNamespace(prefix, len?)` | Schema / DB / key / object-prefix namespacing for parallel-safe runs. Default 6 hex chars. |
32
+ | `registerCleanup(fn)` + `runCleanups()` | Process-global LIFO queue. Only where **one** spec file of the package provisions anything — see below. |
33
+
34
+ Each gate fails closed on its **required** variables and treats the rest as optional:
35
+
36
+ | Gate | Required | Optional |
37
+ |---|---|---|
38
+ | `mongoGate()` | `MONGO_URL` | `MONGO_TEST_DB_PREFIX` |
39
+ | `postgresGate()` | `POSTGRES_URL` | `POSTGRES_TEST_DB_PREFIX` |
40
+ | `redisGate()` | `REDIS_URL` | `REDIS_TEST_KEY_PREFIX` |
41
+ | `s3Gate()` | `S3_ENDPOINT`, `S3_KEY`, `S3_SECRET`, `S3_TEST_BUCKET` | `S3_REGION` |
42
+ | `kubeGate()` | `KUBE_CONFIG`, `KUBE_TEST_OK` | — |
43
+ | `smtpGate()` | `SMTP_HOST`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_FROM`, `SMTP_TEST_TO` | `SMTP_PORT`, `SMTP_SECURE` |
44
+
45
+ `SMTP_TEST_TO` is required rather than optional because an open SMTP gate sends **real mail** — the
46
+ suite must never guess a recipient.
47
+
48
+ An optional variable is only in `env` when it was actually populated, which is why a suite reads a
49
+ defaulted one — `POSTGRES_TEST_DB_PREFIX ?? 'omt'` — straight from `process.env` instead: calling
50
+ any gate has already run `loadEnv`, so the `.env` values are in `process.env` by then.
51
+
52
+ **Six services, six gates — a category-C package outside that set writes its own.** There is no
53
+ gate for an LLM provider or for the Mailgun HTTP API, and none should be added to
54
+ `@owlmeans/test-integration` for a provider only one package talks to. Declare it in that
55
+ package's `tests/context.ts` with `makeGates` from `@owlmeans/test` instead, listing the variables
56
+ the provider needs; the result skips exactly the same way. `@owlmeans/llm` is the worked example.
57
+
58
+ Every driver stays where it already lives — in the package that integrates with the service, not
59
+ in a test package: `pg` (`@owlmeans/postgres`), `mongodb` (`@owlmeans/mongo`), `ioredis`
60
+ (`@owlmeans/redis`), `bullmq` (`@owlmeans/redis-queue`), `@aws-sdk/client-s3`
61
+ (`@owlmeans/storage-resource`), `@kubernetes/client-node` (`@owlmeans/kluster`) and `nodemailer`
62
+ (`@owlmeans/mailer-smtp`). A suite that must reach the service outside the package's own
63
+ service — to seed a fixture, or to assert on what really landed — imports that driver directly in
64
+ its `tests/`. `@owlmeans/server-mailer-mailgun` has no driver: it posts to the Mailgun HTTP API
65
+ with the global `fetch`, so a suite there imports nothing beyond the package under test and
66
+ asserts on the request it builds and the API's reply.
27
67
 
28
68
  ## Env contract
29
69
 
30
- `/.env.example` documents every required variable, grouped by service, with comments. The
31
- single `.env` at the monorepo root supplies real values for local runs. CI sets them via
32
- secrets. **Empty value = skip** — never a failure.
70
+ One `.env.example` at the workspace root documents every variable, grouped by service, with
71
+ comments; the `.env` beside it supplies real values for local runs and `loadEnv` (from
72
+ `@owlmeans/test`) picks it up. CI sets the same variables from secrets. **Empty value = skip** —
73
+ never a failure.
33
74
 
34
75
  When a required variable is empty:
35
76
  1. The corresponding gate reports `skip: true`.
@@ -45,26 +86,26 @@ POSTGRES_URL="$(<build it from the secret file, without echoing>)" bun test ./te
45
86
 
46
87
  ### Where the real credentials come from
47
88
 
48
- The dev-cluster secrets are the source of truth; `../viable/.env.dev.secrets` holds the same
49
- values under kebab-case keys (`mongo-secret`, `redis-secret`, `s3-storage`, …) for
50
- `kubectl create secret --from-env-file`. Read the cluster secret directly rather than transcribing:
89
+ Read each secret from wherever it actually lives — a cluster secret, a vault, the provider's own
90
+ console — and pipe it into the variable in one step. Never transcribe a credential into a file,
91
+ a fixture or this guidance.
51
92
 
52
93
  ```bash
53
- kubectl get secret mongo-owlmeans-mongo-main-db-admin-admin -o jsonpath='{.data.password}' | base64 -d
94
+ kubectl get secret <name> -o jsonpath='{.data.password}' | base64 -d
54
95
  ```
55
96
 
56
97
  Two things that will bite:
57
98
 
58
- - **URL-escape the password before putting it in a connection string.** These generated passwords
99
+ - **URL-escape the password before putting it in a connection string.** Generated passwords
59
100
  contain reserved characters; an unescaped one fails with `MongoParseError: Password contains
60
101
  unescaped characters`. Escape with `urllib.parse.quote(pw, safe='')`.
61
- - **The port-forward may already be running** — the slots share one cluster, so
62
- `bind: address already in use` on 27017/5432/6379 usually means another checkout forwards the
63
- same service. Reuse it; never kill another environment's process.
102
+ - **A port-forward may already be running.** `bind: address already in use` on 27017/5432/6379
103
+ usually means another checkout or another developer already forwards that service. Reuse the
104
+ forward; never kill a process you did not start.
64
105
 
65
106
  The suites namespace every database/schema (`MONGO_TEST_DB_PREFIX`, default `omt`) and drop it on
66
- completion, so they are safe against the shared cluster. Confirm nothing was left behind after a
67
- run that aborted mid-way.
107
+ completion, so they are safe against an instance shared with anything else. Confirm nothing was
108
+ left behind after a run that aborted mid-way.
68
109
 
69
110
  ## Where the specs live
70
111
 
@@ -73,19 +114,31 @@ A resource package (`*-resource`) owns the `Resource<T>` contract; the db packag
73
114
  **depends on** the resource package.
74
115
 
75
116
  - **Unit specs** — schema compilation, identifier derivation, placeholder resolution, error
76
- translation — live in the **resource** package. No gate, no service.
77
- - **Integration specs** that build a real `ServerContext` live in the **db** package. Putting
78
- them in the resource package would need a devDependency on its own dependent, which is a
79
- cycle and risks breaking `publish.ts`'s topological batching.
117
+ translation — live in the **resource** package and gate on nothing: they leave the process
118
+ alone. `@owlmeans/postgres-resource` is entirely this shape.
119
+ - **Raw-driver specs** belong there too when the subject is the round trip and not the service.
120
+ `@owlmeans/mongo-resource` opens `mongoGate()`, derives a namespaced database with
121
+ `randomNamespace`, registers a cleanup that drops it, and drives `mongodb` directly with the
122
+ driver as its own devDependency — legitimate precisely because it never constructs the db
123
+ service. It is also the one package that may use the global cleanup queue, because only that
124
+ one spec file of the three provisions anything.
125
+ - **Integration specs that build a real `ServerContext`** live in the **db** package. It already
126
+ depends on the resource package, so the pair it boots is the real one; putting them in the
127
+ resource package would make a contract dev-depend on its own implementation just to run its
128
+ specs.
80
129
 
81
130
  ## Per-suite namespaces, not the global cleanup queue
82
131
 
83
- Bun runs **every spec file of a package in one process** with one shared module registry. A
84
- process-global `registerCleanup`/`runCleanups()` therefore misfires: the first file to reach
85
- `afterAll` drops the namespaces of the files still to come. Use `registerCleanup` only in a
86
- package with a single spec file.
132
+ Bun runs **every spec file of a package in one process** with one shared module registry, and the
133
+ queue is one array for that whole process. A process-global `registerCleanup`/`runCleanups()`
134
+ therefore misfires as soon as two spec files provision: the first to reach `afterAll` drains the
135
+ entire queue and drops the namespaces of the files still to come. The queue is safe only where a
136
+ **single** spec file provisions anything and that same file calls `runCleanups()`. That is the
137
+ shape `@owlmeans/mongo-resource` has — one gated round-trip spec registering one dropped database,
138
+ beside two ungated spec files that translate criteria and references in memory and open nothing.
87
139
 
88
- Give each spec file its own namespace and its own teardown instead:
140
+ Any package with a second provisioning spec file gives each spec file its own namespace and its
141
+ own teardown instead:
89
142
 
90
143
  ```ts
91
144
  // tests/context.ts
@@ -142,10 +195,12 @@ export const capabilities: PgCapabilities = gate.skip
142
195
 
143
196
  Specs then branch on it once, at the top: `const it = gate.skip || !capabilities.bootstrap ? test.skip : test`.
144
197
 
145
- ## Module-scope declarations survive a context switch
198
+ ## Module-scope declarations outlive a `boot()`
146
199
 
147
- Migration registries and table declarations are keyed by resource **alias** at module scope so
148
- they outlive `reinitializeContext`. That means they also outlive a `boot()` — deliberately.
200
+ Migration registries and table declarations are keyed by resource **alias** at module scope, so a
201
+ maker that runs more than once for the same alias re-declares the same entries and loses nothing.
202
+ A `boot()` builds a whole new context, but the declarations belong to the module registry rather
203
+ than to any context, so they carry across every boot in the process — deliberately.
149
204
  Never reset them inside a shared `boot()` helper: a `.migration()` registered before the boot
150
205
  would be silently dropped. A spec that wants a clean slate calls `resetDeclarations(alias)`
151
206
  itself, which is what a spec simulating a restarted process should have to say out loud.
@@ -192,13 +247,25 @@ Add `"./tests/**/*"` to the package's `tsconfig.json` `exclude` so `tsc -b` neve
192
247
  specs into `build/`, and declare `@owlmeans/test-integration` plus `@types/bun` as
193
248
  devDependencies.
194
249
 
195
- The Bun runtime auto-loads `<root>/.env` when the test command runs from a workspace package.
196
- If you need to override, `bun test --env-file=../../.env` works. Long-running specs may need a
197
- higher timeout — set it locally in a per-package `bunfig.toml`:
250
+ **Nothing auto-loads the workspace `.env`.** Bun reads a `.env` beside the working directory, so
251
+ `bun test` inside a package never sees the one at the workspace root. The gates work because
252
+ `@owlmeans/test`'s `loadEnv` does the loading itself and every gate calls it before reading a
253
+ variable: it walks up from the working directory for a `bun.lock`, or for a `package.json`
254
+ sitting beside a directory named `packages`, and reads the `.env` it finds there. A variable
255
+ already set to a non-empty value always wins, so `MONGO_URL=… bun test ./tests` still overrides
256
+ the file. A workspace shaped any other way names the file itself — `loadEnv({ file })` at the top
257
+ of `tests/context.ts`, before the gates run.
258
+
259
+ Bun's per-test timeout is 5s, and **`bunfig.toml`'s `[test] timeout` does not raise it** — bun
260
+ 1.4.0 reads the section (`preload` in it works) and ignores that key. Give a long-running spec its
261
+ own budget as the third argument to `test`, or raise the whole run on the command line:
262
+
263
+ ```ts
264
+ test('round-trips through a real service', async () => { /* … */ }, 60_000)
265
+ ```
198
266
 
199
- ```toml
200
- [test]
201
- timeout = 60000
267
+ ```sh
268
+ bun test --timeout=60000 ./tests
202
269
  ```
203
270
 
204
271
  ## Bun assertion gotcha
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/test-integration",
3
- "version": "0.1.18-rc.0",
3
+ "version": "0.1.18-rc.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,11 +21,11 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/test": "^0.1.18-rc.0"
24
+ "@owlmeans/test": "^0.1.18-rc.10"
25
25
  },
26
26
  "devDependencies": {
27
- "@owlmeans/dep-config": "^0.1.18-rc.0",
28
- "@types/bun": "^1.3.14",
27
+ "@owlmeans/dep-config": "^0.1.18-rc.6",
28
+ "@types/bun": "^1.4.0",
29
29
  "nodemon": "^3.1.14",
30
30
  "typescript": "^7.0.2"
31
31
  },