@stratal/testing 0.0.27 → 0.1.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +570 -0
  2. package/README.md +137 -36
  3. package/dist/database/index.d.mts +2 -2
  4. package/dist/database/index.mjs +2 -2
  5. package/dist/database-Rojs69r3.mjs +453 -0
  6. package/dist/database-Rojs69r3.mjs.map +1 -0
  7. package/dist/{decorate-B7nr7eBl.mjs → decorate-RQD1h28J.mjs} +1 -1
  8. package/dist/feature-flags/index.mjs +1 -1
  9. package/dist/{feature-flags-BiLhfSGh.mjs → feature-flags-CZ1a4M2g.mjs} +2 -2
  10. package/dist/{feature-flags-BiLhfSGh.mjs.map → feature-flags-CZ1a4M2g.mjs.map} +1 -1
  11. package/dist/index-B8Mw1Dxk.d.mts +175 -0
  12. package/dist/index-B8Mw1Dxk.d.mts.map +1 -0
  13. package/dist/{index-CrHzUDKX.d.mts → index-D7FM6EKp.d.mts} +21 -3
  14. package/dist/index-D7FM6EKp.d.mts.map +1 -0
  15. package/dist/index-qgWNJRdC.d.mts.map +1 -1
  16. package/dist/index.d.mts +105 -46
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +218 -108
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/mocks/cloudflare-workers.d.mts +24 -0
  21. package/dist/mocks/cloudflare-workers.d.mts.map +1 -0
  22. package/dist/mocks/cloudflare-workers.mjs +28 -0
  23. package/dist/mocks/cloudflare-workers.mjs.map +1 -0
  24. package/dist/mocks/index.d.mts +2 -2
  25. package/dist/mocks/index.mjs +2 -2
  26. package/dist/mocks/noop-rate-limiter-store.d.mts +1 -3
  27. package/dist/mocks/noop-rate-limiter-store.d.mts.map +1 -1
  28. package/dist/mocks/zenstack-language.d.mts +26 -29
  29. package/dist/mocks/zenstack-language.d.mts.map +1 -1
  30. package/dist/mocks/zenstack-language.mjs +1 -1
  31. package/dist/mocks/zenstack-language.mjs.map +1 -1
  32. package/dist/storage/index.d.mts +1 -1
  33. package/dist/storage/index.mjs +1 -1
  34. package/dist/{storage-DhoxWqyF.mjs → storage-BiGamMA8.mjs} +73 -6
  35. package/dist/storage-BiGamMA8.mjs.map +1 -0
  36. package/dist/test-worker-exports-dCRARYEc.d.mts +144 -0
  37. package/dist/test-worker-exports-dCRARYEc.d.mts.map +1 -0
  38. package/dist/test-workers-cache-D02Pt_dE.mjs +183 -0
  39. package/dist/test-workers-cache-D02Pt_dE.mjs.map +1 -0
  40. package/dist/vitest-plugin/index.d.mts +14 -28
  41. package/dist/vitest-plugin/index.d.mts.map +1 -1
  42. package/dist/vitest-plugin/index.mjs +32 -20
  43. package/dist/vitest-plugin/index.mjs.map +1 -1
  44. package/package.json +30 -21
  45. package/dist/database-B02eYKhE.mjs +0 -334
  46. package/dist/database-B02eYKhE.mjs.map +0 -1
  47. package/dist/index-BIr5nLof.d.mts +0 -122
  48. package/dist/index-BIr5nLof.d.mts.map +0 -1
  49. package/dist/index-CrHzUDKX.d.mts.map +0 -1
  50. package/dist/storage-DhoxWqyF.mjs.map +0 -1
  51. package/dist/test-email-provider-B7cjj97-.d.mts +0 -21
  52. package/dist/test-email-provider-B7cjj97-.d.mts.map +0 -1
  53. package/dist/test-email-provider-Dr-nhE0x.mjs +0 -34
  54. package/dist/test-email-provider-Dr-nhE0x.mjs.map +0 -1
package/README.md CHANGED
@@ -1,9 +1,14 @@
1
1
  # @stratal/testing
2
2
 
3
- Testing utilities and mocks for [Stratal](https://github.com/strataljs/stratal) framework applications.
3
+ Testing utilities and mocks for [Stratal](https://stratal.dev) applications.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@stratal/testing)](https://www.npmjs.com/package/@stratal/testing)
6
+ [![CI](https://github.com/strataljs/stratal/actions/workflows/ci.yml/badge.svg)](https://github.com/strataljs/stratal/actions/workflows/ci.yml)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@stratal/testing)](https://www.npmjs.com/package/@stratal/testing)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/strataljs/stratal/pulls)
11
+ [![GitHub stars](https://img.shields.io/github/stars/strataljs/stratal?style=social)](https://github.com/strataljs/stratal)
7
12
 
8
13
  ## Installation
9
14
 
@@ -29,7 +34,22 @@ npx skills add strataljs/stratal
29
34
 
30
35
  | Skill | Description |
31
36
  |---|---|
32
- | `stratal` | Build Cloudflare Workers apps with the Stratal framework — modules, DI, controllers, routing, OpenAPI, queues, cron, events, seeders, CLI, auth, database, RBAC, testing, and more |
37
+ | `stratal` | Build Cloudflare Workers apps with the Stratal framework — modules, DI, controllers, routing, OpenAPI, queues, cron, events, seeders, CLI, auth, database, access control, testing, and more |
38
+
39
+ ## Vitest setup
40
+
41
+ `stratalTest()` wraps [`@cloudflare/vitest-plugin`](https://developers.cloudflare.com/workers/testing/vitest-integration/) with Stratal defaults (tslib alias, ZenStack mocks, SSR externals). It needs no database — `Test.createTestingModule()` and unit tests run with no DB wiring at all.
42
+
43
+ ```typescript
44
+ // vitest.config.ts
45
+ import { fixNobleHashesCjs, fixPgCjs, stratalTest } from '@stratal/testing/vitest-plugin'
46
+
47
+ export default defineConfig({
48
+ plugins: [fixPgCjs(), fixNobleHashesCjs(), stratalTest()],
49
+ })
50
+ ```
51
+
52
+ A database is opt-in via the `database` option, which gives **each test file** its own database cloned from a migrated template. Omit it entirely for suites that don't touch Postgres.
33
53
 
34
54
  ## Quick Start
35
55
 
@@ -120,9 +140,17 @@ module.http
120
140
  // Access fake storage for assertions
121
141
  module.storage
122
142
 
143
+ // Other test clients and fakes
144
+ module.ws('/ws/chat') // WebSocket request builder
145
+ module.sse('/streaming/events') // Server-sent events request builder
146
+ module.quarry('users:create') // Quarry command request builder
147
+ module.cache // Workers Cache stub — inspect purges
148
+ module.featureFlags // Fake feature flag service
149
+ module.sentEmails // Emails sent during the test
150
+
123
151
  // Execute code in a request-scoped container
124
- await module.runInRequestScope(async () => {
125
- const scoped = module.get(REQUEST_SCOPED_TOKEN)
152
+ await module.runInRequestScope(async (container) => {
153
+ const scoped = container.resolve(REQUEST_SCOPED_TOKEN)
126
154
  })
127
155
 
128
156
  // Cleanup in afterAll
@@ -146,6 +174,14 @@ const response = await module.http
146
174
 
147
175
  All HTTP methods are supported: `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`.
148
176
 
177
+ Requests can also act as a user, force JSON, or carry a locale:
178
+
179
+ ```typescript
180
+ await module.http.get('/api/v1/profile').actingAs({ id: user.id }).send()
181
+ await module.http.get('/api/v1/notes').asJson().send()
182
+ await module.http.get('/api/v1/notes').withLocale('fr').send()
183
+ ```
184
+
149
185
  ### Response assertions
150
186
 
151
187
  ```typescript
@@ -218,59 +254,60 @@ const module = await Test.createTestingModule({
218
254
 
219
255
  ## Fetch Mocking
220
256
 
221
- Mock external HTTP calls with `createFetchMock()`, backed by [undici MockAgent](https://undici.nodejs.org/#/docs/api/MockAgent):
257
+ Mock external HTTP calls with `createMockFetch()`, backed by [Mock Service Worker](https://mswjs.io). `http` and `HttpResponse` are re-exported for convenience.
222
258
 
223
259
  ```typescript
224
- import { createFetchMock, type FetchMock } from '@stratal/testing'
260
+ import { createMockFetch, http, HttpResponse, type MockFetch } from '@stratal/testing'
225
261
 
226
262
  describe('GeoService', () => {
227
- let module: TestingModule
228
- let fetchMock: FetchMock
229
-
230
- beforeEach(() => {
231
- fetchMock = createFetchMock()
232
- fetchMock.activate()
233
- fetchMock.disableNetConnect()
263
+ let mockFetch: MockFetch
264
+
265
+ beforeAll(() => {
266
+ mockFetch = createMockFetch([
267
+ http.get('https://geo.api.com/lookup', () => {
268
+ return HttpResponse.json({ lat: 40.7128, lng: -74.006 })
269
+ }),
270
+ ])
271
+ mockFetch.listen()
234
272
  })
235
273
 
236
- afterEach(() => {
237
- fetchMock.reset()
238
- })
274
+ afterEach(() => mockFetch.reset())
275
+ afterAll(() => mockFetch.close())
239
276
 
240
277
  it('looks up coordinates', async () => {
241
- fetchMock.mockJsonResponse('https://geo.api.com/lookup', {
242
- lat: 40.7128,
243
- lng: -74.006,
244
- })
245
-
246
278
  const response = await module.http
247
279
  .get('/api/geo/lookup?address=NYC')
248
280
  .send()
249
281
 
250
282
  response.assertOk()
251
283
  await response.assertJsonPath('data.lat', 40.7128)
252
- fetchMock.assertNoPendingInterceptors()
253
284
  })
285
+ })
286
+ ```
254
287
 
255
- it('handles API errors', async () => {
256
- fetchMock.mockError('https://geo.api.com/lookup', 503, 'Service Unavailable')
288
+ ### Lifecycle
257
289
 
258
- const response = await module.http
259
- .get('/api/geo/lookup?address=NYC')
260
- .send()
290
+ ```typescript
291
+ mockFetch.listen() // start intercepting
292
+ mockFetch.reset() // clear runtime handlers, between tests
293
+ mockFetch.close() // stop intercepting
294
+ ```
261
295
 
262
- response.assertServerError()
263
- })
264
- })
296
+ ### Adding handlers for a single test
297
+
298
+ ```typescript
299
+ mockFetch.use(
300
+ http.post('https://geo.api.com/submit', () => {
301
+ return HttpResponse.json({ success: true }, { status: 201 })
302
+ }),
303
+ )
265
304
  ```
266
305
 
267
- For advanced scenarios, access the undici `MockPool` directly:
306
+ ### Shorthands
268
307
 
269
308
  ```typescript
270
- fetchMock
271
- .get('https://api.example.com')
272
- .intercept({ path: '/users', method: 'POST' })
273
- .reply(201, { id: '1' })
309
+ mockFetch.mockJsonResponse('https://geo.api.com/lookup', { lat: 40.7128 })
310
+ mockFetch.mockError('https://geo.api.com/lookup', 503, 'Service Unavailable')
274
311
  ```
275
312
 
276
313
  ## Storage Testing
@@ -300,6 +337,59 @@ afterEach(() => {
300
337
  })
301
338
  ```
302
339
 
340
+ ## WebSocket Testing
341
+
342
+ ```typescript
343
+ const ws = await module.ws('/ws/chat')
344
+ .actingAs({ id: user.id })
345
+ .connect()
346
+
347
+ ws.send('hello')
348
+ await ws.assertMessage('echo:hello')
349
+
350
+ ws.close()
351
+ await ws.waitForClose()
352
+ ```
353
+
354
+ ## SSE Testing
355
+
356
+ ```typescript
357
+ const sse = await module.sse('/streaming/events')
358
+ .actingAs({ id: user.id })
359
+ .connect()
360
+
361
+ await sse.assertEvent({ event: 'message', data: 'hello' })
362
+ await sse.assertJsonEventData({ status: 'done' })
363
+ await sse.waitForEnd()
364
+ ```
365
+
366
+ ## Command Testing
367
+
368
+ ```typescript
369
+ const result = await module
370
+ .quarry('users:create')
371
+ .withInput({ email: 'test@example.com', admin: true })
372
+ .run()
373
+
374
+ result.assertSuccessful()
375
+ result.assertOutputContains('User created')
376
+ result.assertExitCode(0)
377
+ ```
378
+
379
+ ## Response Cache
380
+
381
+ A `ctx.cache` stub is installed by default, so `@Cacheable` / `@PurgesCache` routes are testable with no configuration.
382
+
383
+ ```typescript
384
+ const response = await module.http.get('/blog/hello-world').send()
385
+ response.assertHeader('Cache-Control', 'public, max-age=300')
386
+
387
+ await module.http.post('/posts/hello-world/publish').send()
388
+ expect(module.cache.purges).toEqual([{ tags: ['post:hello-world'] }])
389
+ ```
390
+
391
+ Pass `cache: false` to `Test.createTestingModule()` to reproduce a runtime where Workers Caching is genuinely unconfigured.
392
+
303
393
  ## Deep Mocking
304
394
 
305
395
  Create deeply-mocked instances of any interface or class with `createMock()` from [`@golevelup/ts-vitest`](https://github.com/golevelup/nestjs/tree/master/packages/ts-vitest):
@@ -318,10 +408,21 @@ beforeEach(() => {
318
408
  ## Sub-path Exports
319
409
 
320
410
  ```typescript
321
- import { Test, TestingModule, createFetchMock } from '@stratal/testing'
411
+ import { Test, TestingModule, createMockFetch, http, HttpResponse } from '@stratal/testing'
322
412
  import { createMock, type DeepMocked } from '@stratal/testing/mocks'
413
+ import { stratalTest, fixPgCjs, fixNobleHashesCjs } from '@stratal/testing/vitest-plugin'
414
+ import { FakeStorageService } from '@stratal/testing/storage'
415
+ import { FakeFeatureFlagService } from '@stratal/testing/feature-flags'
323
416
  ```
324
417
 
418
+ ## Support the project
419
+
420
+ If Stratal is useful to you, **[star the repository](https://github.com/strataljs/stratal)** — it is the simplest way to help others find it.
421
+
422
+ ## Maintainer
423
+
424
+ Built and maintained by **Temitayo Fadojutimi** — [@adesege_](https://x.com/adesege_).
425
+
325
426
  ## License
326
427
 
327
428
  MIT
@@ -1,2 +1,2 @@
1
- import { a as TestDatabaseGlobalSetupOptions, c as createTestDatabaseGlobalSetup, d as deriveDbName, f as deriveTemplateName, i as ISOLATION_ENV_VAR, l as databasePrefix, m as normalizeIsolation, n as DEFAULT_DB_BINDING, o as buildConnectionString, p as dropDatabase, r as DatabaseIsolation, s as createDatabaseFromTemplate, t as BINDING_ENV_VAR, u as deriveAdminConnectionString } from "../index-BIr5nLof.mjs";
2
- export { BINDING_ENV_VAR, DEFAULT_DB_BINDING, type DatabaseIsolation, ISOLATION_ENV_VAR, type TestDatabaseGlobalSetupOptions, buildConnectionString, createDatabaseFromTemplate, createTestDatabaseGlobalSetup, databasePrefix, deriveAdminConnectionString, deriveDbName, deriveTemplateName, dropDatabase, normalizeIsolation };
1
+ import { a as BINDING_ENV_VAR, c as WorkerDatabaseLease, d as createTestDatabaseGlobalSetup, f as databasePrefix, g as leaseWorkerDatabase, h as deriveWorkerDbName, i as resetWorkerDatabase, l as buildConnectionString, m as deriveTemplateName, n as buildTableDiscoverySql, o as DEFAULT_DB_BINDING, p as deriveAdminConnectionString, r as buildTruncateSql, s as TestDatabaseGlobalSetupOptions, t as ResetOptions, u as cloneWorkerDatabase } from "../index-B8Mw1Dxk.mjs";
2
+ export { BINDING_ENV_VAR, DEFAULT_DB_BINDING, type ResetOptions, type TestDatabaseGlobalSetupOptions, type WorkerDatabaseLease, buildConnectionString, buildTableDiscoverySql, buildTruncateSql, cloneWorkerDatabase, createTestDatabaseGlobalSetup, databasePrefix, deriveAdminConnectionString, deriveTemplateName, deriveWorkerDbName, leaseWorkerDatabase, resetWorkerDatabase };
@@ -1,2 +1,2 @@
1
- import { a as createDatabaseFromTemplate, c as deriveAdminConnectionString, d as dropDatabase, f as normalizeIsolation, i as buildConnectionString, l as deriveDbName, n as DEFAULT_DB_BINDING, o as createTestDatabaseGlobalSetup, r as ISOLATION_ENV_VAR, s as databasePrefix, t as BINDING_ENV_VAR, u as deriveTemplateName } from "../database-B02eYKhE.mjs";
2
- export { BINDING_ENV_VAR, DEFAULT_DB_BINDING, ISOLATION_ENV_VAR, buildConnectionString, createDatabaseFromTemplate, createTestDatabaseGlobalSetup, databasePrefix, deriveAdminConnectionString, deriveDbName, deriveTemplateName, dropDatabase, normalizeIsolation };
1
+ import { a as DEFAULT_DB_BINDING, c as createTestDatabaseGlobalSetup, d as deriveTemplateName, f as deriveWorkerDbName, i as BINDING_ENV_VAR, l as databasePrefix, n as buildTruncateSql, o as buildConnectionString, p as leaseWorkerDatabase, r as resetWorkerDatabase, s as cloneWorkerDatabase, t as buildTableDiscoverySql, u as deriveAdminConnectionString } from "../database-Rojs69r3.mjs";
2
+ export { BINDING_ENV_VAR, DEFAULT_DB_BINDING, buildConnectionString, buildTableDiscoverySql, buildTruncateSql, cloneWorkerDatabase, createTestDatabaseGlobalSetup, databasePrefix, deriveAdminConnectionString, deriveTemplateName, deriveWorkerDbName, leaseWorkerDatabase, resetWorkerDatabase };