@owlmeans/test-integration 0.1.18-rc.6 → 0.1.18-rc.8
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.12
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/test-integration",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.8",
|
|
5
|
+
"generatedAt": "2026-09-04T22:43:25.440Z",
|
|
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
|
|
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.8"` 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`, `
|
|
11
|
-
`
|
|
12
|
-
`llm` (live inference providers). These tests **never** mock the external service.
|
|
13
|
-
only when the corresponding env vars are set, and they self-skip cleanly when those
|
|
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
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
secrets. **Empty value = skip** —
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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.**
|
|
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
|
-
- **
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
84
|
-
process-global `registerCleanup`/`runCleanups()`
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
```
|
|
200
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.18-rc.8",
|
|
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.
|
|
24
|
+
"@owlmeans/test": "^0.1.18-rc.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@owlmeans/dep-config": "^0.1.18-rc.6",
|
|
28
|
-
"@types/bun": "^1.
|
|
28
|
+
"@types/bun": "^1.4.0",
|
|
29
29
|
"nodemon": "^3.1.14",
|
|
30
30
|
"typescript": "^7.0.2"
|
|
31
31
|
},
|