@stratal/testing 0.0.14 → 0.0.16
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/dist/index-D-Q2cR2v.d.mts +118 -0
- package/dist/index-D-Q2cR2v.d.mts.map +1 -0
- package/dist/index.d.mts +73 -127
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +117 -217
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.d.mts +2 -0
- package/dist/storage/index.mjs +2 -0
- package/dist/storage-PcJUKxwp.mjs +206 -0
- package/dist/storage-PcJUKxwp.mjs.map +1 -0
- package/package.json +9 -3
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { n as __decorate, t as FakeStorageService } from "./storage-PcJUKxwp.mjs";
|
|
1
2
|
import { env, waitUntil } from "cloudflare:workers";
|
|
2
3
|
import { Application } from "stratal";
|
|
3
4
|
import { LogLevel } from "stratal/logger";
|
|
4
5
|
import { Module } from "stratal/module";
|
|
5
|
-
import {
|
|
6
|
-
import { DI_TOKENS
|
|
6
|
+
import { STORAGE_TOKENS } from "stratal/storage";
|
|
7
|
+
import { DI_TOKENS } from "stratal/di";
|
|
7
8
|
import { expect } from "vitest";
|
|
8
9
|
import { connectionSymbol } from "@stratal/framework/database";
|
|
10
|
+
import { SEEDER_TOKENS, SeederNotRegisteredError } from "stratal/seeder";
|
|
9
11
|
import { AUTH_SERVICE } from "@stratal/framework/auth";
|
|
10
12
|
import { setSessionCookie } from "better-auth/cookies";
|
|
11
13
|
import { convertSetCookieToCookie } from "better-auth/test";
|
|
@@ -104,206 +106,6 @@ var ProviderOverrideBuilder = class {
|
|
|
104
106
|
}
|
|
105
107
|
};
|
|
106
108
|
//#endregion
|
|
107
|
-
//#region \0@oxc-project+runtime@0.115.0/helpers/decorateMetadata.js
|
|
108
|
-
function __decorateMetadata(k, v) {
|
|
109
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
110
|
-
}
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region \0@oxc-project+runtime@0.115.0/helpers/decorateParam.js
|
|
113
|
-
function __decorateParam(paramIndex, decorator) {
|
|
114
|
-
return function(target, key) {
|
|
115
|
-
decorator(target, key, paramIndex);
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region \0@oxc-project+runtime@0.115.0/helpers/decorate.js
|
|
120
|
-
function __decorate(decorators, target, key, desc) {
|
|
121
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
122
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
123
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
124
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
125
|
-
}
|
|
126
|
-
//#endregion
|
|
127
|
-
//#region src/storage/fake-storage.service.ts
|
|
128
|
-
var _ref;
|
|
129
|
-
let FakeStorageService = class FakeStorageService extends StorageService {
|
|
130
|
-
files = /* @__PURE__ */ new Map();
|
|
131
|
-
constructor(storageManager, options) {
|
|
132
|
-
super(storageManager, options);
|
|
133
|
-
this.storageManager = storageManager;
|
|
134
|
-
this.options = options;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Upload content to fake storage
|
|
138
|
-
*/
|
|
139
|
-
async upload(body, relativePath, options, disk) {
|
|
140
|
-
const content = await this.bodyToUint8Array(body);
|
|
141
|
-
const diskName = this.resolveDisk(disk);
|
|
142
|
-
this.files.set(relativePath, {
|
|
143
|
-
content,
|
|
144
|
-
mimeType: options.mimeType ?? "application/octet-stream",
|
|
145
|
-
size: options.size,
|
|
146
|
-
metadata: options.metadata,
|
|
147
|
-
uploadedAt: /* @__PURE__ */ new Date()
|
|
148
|
-
});
|
|
149
|
-
return {
|
|
150
|
-
path: relativePath,
|
|
151
|
-
disk: diskName,
|
|
152
|
-
fullPath: relativePath,
|
|
153
|
-
size: options.size,
|
|
154
|
-
mimeType: options.mimeType ?? "application/octet-stream",
|
|
155
|
-
uploadedAt: /* @__PURE__ */ new Date()
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Download a file from fake storage
|
|
160
|
-
*/
|
|
161
|
-
download(path) {
|
|
162
|
-
const file = this.files.get(path);
|
|
163
|
-
if (!file) return Promise.reject(new FileNotFoundError(path));
|
|
164
|
-
return Promise.resolve({
|
|
165
|
-
toStream: () => new ReadableStream({ start(controller) {
|
|
166
|
-
controller.enqueue(file.content);
|
|
167
|
-
controller.close();
|
|
168
|
-
} }),
|
|
169
|
-
toString: () => Promise.resolve(new TextDecoder().decode(file.content)),
|
|
170
|
-
toArrayBuffer: () => Promise.resolve(file.content),
|
|
171
|
-
contentType: file.mimeType,
|
|
172
|
-
size: file.size,
|
|
173
|
-
metadata: file.metadata
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Delete a file from fake storage
|
|
178
|
-
*/
|
|
179
|
-
delete(path) {
|
|
180
|
-
this.files.delete(path);
|
|
181
|
-
return Promise.resolve();
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Check if a file exists in fake storage
|
|
185
|
-
*/
|
|
186
|
-
exists(path) {
|
|
187
|
-
return Promise.resolve(this.files.has(path));
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Generate a fake presigned download URL
|
|
191
|
-
*/
|
|
192
|
-
getPresignedDownloadUrl(path, expiresIn) {
|
|
193
|
-
return Promise.resolve(this.createPresignedUrl(path, "GET", expiresIn));
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Generate a fake presigned upload URL
|
|
197
|
-
*/
|
|
198
|
-
getPresignedUploadUrl(path, expiresIn) {
|
|
199
|
-
return Promise.resolve(this.createPresignedUrl(path, "PUT", expiresIn));
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Generate a fake presigned delete URL
|
|
203
|
-
*/
|
|
204
|
-
getPresignedDeleteUrl(path, expiresIn) {
|
|
205
|
-
return Promise.resolve(this.createPresignedUrl(path, "DELETE", expiresIn));
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Chunked upload (same as regular upload for fake)
|
|
209
|
-
*/
|
|
210
|
-
async chunkedUpload(body, path, options, disk) {
|
|
211
|
-
const content = await this.bodyToUint8Array(body);
|
|
212
|
-
const size = options.size ?? content.length;
|
|
213
|
-
return this.upload(body, path, {
|
|
214
|
-
...options,
|
|
215
|
-
size
|
|
216
|
-
}, disk);
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Assert that a file exists at the given path
|
|
220
|
-
*
|
|
221
|
-
* @param path - Path to check
|
|
222
|
-
* @throws AssertionError if file does not exist
|
|
223
|
-
*/
|
|
224
|
-
assertExists(path) {
|
|
225
|
-
expect(this.files.has(path), `Expected file to exist at: ${path}\nStored files: ${this.getStoredPaths().join(", ") || "(none)"}`).toBe(true);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Assert that a file does NOT exist at the given path
|
|
229
|
-
*
|
|
230
|
-
* @param path - Path to check
|
|
231
|
-
* @throws AssertionError if file exists
|
|
232
|
-
*/
|
|
233
|
-
assertMissing(path) {
|
|
234
|
-
expect(this.files.has(path), `Expected file NOT to exist at: ${path}`).toBe(false);
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Assert storage is empty
|
|
238
|
-
*
|
|
239
|
-
* @throws AssertionError if any files exist
|
|
240
|
-
*/
|
|
241
|
-
assertEmpty() {
|
|
242
|
-
expect(this.files.size, `Expected storage to be empty but found ${this.files.size} files: ${this.getStoredPaths().join(", ")}`).toBe(0);
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Assert storage has exactly N files
|
|
246
|
-
*
|
|
247
|
-
* @param count - Expected number of files
|
|
248
|
-
* @throws AssertionError if count doesn't match
|
|
249
|
-
*/
|
|
250
|
-
assertCount(count) {
|
|
251
|
-
expect(this.files.size, `Expected ${count} files in storage but found ${this.files.size}`).toBe(count);
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Get all stored files (for inspection)
|
|
255
|
-
*/
|
|
256
|
-
getStoredFiles() {
|
|
257
|
-
return new Map(this.files);
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Get all stored file paths
|
|
261
|
-
*/
|
|
262
|
-
getStoredPaths() {
|
|
263
|
-
return Array.from(this.files.keys());
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Get a specific file by path
|
|
267
|
-
*/
|
|
268
|
-
getFile(path) {
|
|
269
|
-
return this.files.get(path);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Clear all stored files (call in beforeEach for test isolation)
|
|
273
|
-
*/
|
|
274
|
-
clear() {
|
|
275
|
-
this.files.clear();
|
|
276
|
-
}
|
|
277
|
-
createPresignedUrl(path, method, expiresIn = 300) {
|
|
278
|
-
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
279
|
-
return {
|
|
280
|
-
url: `https://fake-storage.test/${path}?method=${method}&expires=${expiresAt.toISOString()}`,
|
|
281
|
-
expiresIn,
|
|
282
|
-
expiresAt,
|
|
283
|
-
method
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
async bodyToUint8Array(body) {
|
|
287
|
-
if (!body) return new Uint8Array(0);
|
|
288
|
-
if (body instanceof Uint8Array) return body;
|
|
289
|
-
if (body instanceof ArrayBuffer) return new Uint8Array(body);
|
|
290
|
-
if (typeof body === "string") return new TextEncoder().encode(body);
|
|
291
|
-
if (body instanceof Blob) {
|
|
292
|
-
const buffer = await body.arrayBuffer();
|
|
293
|
-
return new Uint8Array(buffer);
|
|
294
|
-
}
|
|
295
|
-
if (body instanceof ReadableStream) return new Uint8Array(await new Response(body).arrayBuffer());
|
|
296
|
-
if (body instanceof FormData || body instanceof URLSearchParams) return new Uint8Array(await new Response(body).arrayBuffer());
|
|
297
|
-
return new Uint8Array(0);
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
FakeStorageService = __decorate([
|
|
301
|
-
Transient(STORAGE_TOKENS.StorageService),
|
|
302
|
-
__decorateParam(0, inject(STORAGE_TOKENS.StorageManager)),
|
|
303
|
-
__decorateParam(1, inject(STORAGE_TOKENS.Options)),
|
|
304
|
-
__decorateMetadata("design:paramtypes", [typeof (_ref = typeof StorageManagerService !== "undefined" && StorageManagerService) === "function" ? _ref : Object, Object])
|
|
305
|
-
], FakeStorageService);
|
|
306
|
-
//#endregion
|
|
307
109
|
//#region src/core/env/test-env.ts
|
|
308
110
|
/**
|
|
309
111
|
* Get test environment with optional overrides
|
|
@@ -820,6 +622,102 @@ var TestHttpClient = class {
|
|
|
820
622
|
}
|
|
821
623
|
};
|
|
822
624
|
//#endregion
|
|
625
|
+
//#region src/core/quarry/test-command-result.ts
|
|
626
|
+
/**
|
|
627
|
+
* Fluent assertion wrapper for command results.
|
|
628
|
+
*
|
|
629
|
+
* @example
|
|
630
|
+
* ```typescript
|
|
631
|
+
* const result = await module
|
|
632
|
+
* .quarry('users:create')
|
|
633
|
+
* .withInput({ email: 'test@example.com' })
|
|
634
|
+
* .run()
|
|
635
|
+
*
|
|
636
|
+
* result.assertSuccessful()
|
|
637
|
+
* result.assertOutputContains('User created')
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
var TestCommandResult = class {
|
|
641
|
+
constructor(result) {
|
|
642
|
+
this.result = result;
|
|
643
|
+
}
|
|
644
|
+
get exitCode() {
|
|
645
|
+
return this.result.exitCode;
|
|
646
|
+
}
|
|
647
|
+
get output() {
|
|
648
|
+
return this.result.output;
|
|
649
|
+
}
|
|
650
|
+
get errors() {
|
|
651
|
+
return this.result.errors;
|
|
652
|
+
}
|
|
653
|
+
assertSuccessful() {
|
|
654
|
+
expect(this.result.exitCode, `Expected exit code 0, got ${this.result.exitCode}. Errors: ${this.result.errors.join(", ")}`).toBe(0);
|
|
655
|
+
expect(this.result.errors, "Expected no errors").toHaveLength(0);
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
assertFailed(exitCode) {
|
|
659
|
+
if (exitCode !== void 0) expect(this.result.exitCode, `Expected exit code ${exitCode}, got ${this.result.exitCode}`).toBe(exitCode);
|
|
660
|
+
else expect(this.result.exitCode, "Expected non-zero exit code").not.toBe(0);
|
|
661
|
+
return this;
|
|
662
|
+
}
|
|
663
|
+
assertExitCode(code) {
|
|
664
|
+
expect(this.result.exitCode, `Expected exit code ${code}, got ${this.result.exitCode}`).toBe(code);
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
assertOutputContains(text) {
|
|
668
|
+
expect(this.result.output.join("\n"), `Expected output to contain "${text}"`).toContain(text);
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
671
|
+
assertOutputMissing(text) {
|
|
672
|
+
expect(this.result.output.join("\n"), `Expected output NOT to contain "${text}"`).not.toContain(text);
|
|
673
|
+
return this;
|
|
674
|
+
}
|
|
675
|
+
assertErrorContains(text) {
|
|
676
|
+
expect(this.result.errors.join("\n"), `Expected errors to contain "${text}"`).toContain(text);
|
|
677
|
+
return this;
|
|
678
|
+
}
|
|
679
|
+
assertErrorMissing(text) {
|
|
680
|
+
expect(this.result.errors.join("\n"), `Expected errors NOT to contain "${text}"`).not.toContain(text);
|
|
681
|
+
return this;
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/core/quarry/test-command-request.ts
|
|
686
|
+
/**
|
|
687
|
+
* Fluent builder for testing Quarry commands.
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* ```typescript
|
|
691
|
+
* const result = await module
|
|
692
|
+
* .quarry('users:create')
|
|
693
|
+
* .withInput({ email: 'test@example.com', admin: true })
|
|
694
|
+
* .run()
|
|
695
|
+
*
|
|
696
|
+
* result.assertSuccessful()
|
|
697
|
+
* result.assertOutputContains('User created')
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
var TestCommandRequest = class {
|
|
701
|
+
_input = {};
|
|
702
|
+
constructor(commandName, module) {
|
|
703
|
+
this.commandName = commandName;
|
|
704
|
+
this.module = module;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Set the flat input for the command.
|
|
708
|
+
*/
|
|
709
|
+
withInput(input) {
|
|
710
|
+
this._input = { ...input };
|
|
711
|
+
return this;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Execute the command and return a TestCommandResult for assertions.
|
|
715
|
+
*/
|
|
716
|
+
async run() {
|
|
717
|
+
return new TestCommandResult(await this.module.application.handleCommand(this.commandName, this._input));
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
//#endregion
|
|
823
721
|
//#region src/core/sse/test-sse-connection.ts
|
|
824
722
|
/**
|
|
825
723
|
* TestSseConnection
|
|
@@ -1275,7 +1173,7 @@ var TestWsRequest = class {
|
|
|
1275
1173
|
*
|
|
1276
1174
|
* // Database utilities
|
|
1277
1175
|
* await module.truncateDb()
|
|
1278
|
-
* await module.seed(
|
|
1176
|
+
* await module.seed(UserSeeder)
|
|
1279
1177
|
* await module.assertDatabaseHas('user', { email: 'test@example.com' })
|
|
1280
1178
|
*
|
|
1281
1179
|
* // Cleanup
|
|
@@ -1324,6 +1222,12 @@ var TestingModule = class {
|
|
|
1324
1222
|
return new TestSseRequest(path, this);
|
|
1325
1223
|
}
|
|
1326
1224
|
/**
|
|
1225
|
+
* Create a Quarry command test request builder
|
|
1226
|
+
*/
|
|
1227
|
+
quarry(name) {
|
|
1228
|
+
return new TestCommandRequest(name, this);
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1327
1231
|
* Get Application instance
|
|
1328
1232
|
*/
|
|
1329
1233
|
get application() {
|
|
@@ -1366,16 +1270,15 @@ var TestingModule = class {
|
|
|
1366
1270
|
const tableList = tables.map((t) => `"${t.tablename}"`).join(", ");
|
|
1367
1271
|
await db.$executeRawUnsafe(`TRUNCATE ${tableList} RESTART IDENTITY CASCADE`);
|
|
1368
1272
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
});
|
|
1273
|
+
/**
|
|
1274
|
+
* Run seeders by class constructor in the request-scoped container
|
|
1275
|
+
*/
|
|
1276
|
+
async seed(...SeederClasses) {
|
|
1277
|
+
const registry = this._requestContainer.resolve(SEEDER_TOKENS.SeederRegistry);
|
|
1278
|
+
for (const SeederClass of SeederClasses) {
|
|
1279
|
+
if (!registry.has(SeederClass)) throw new SeederNotRegisteredError(SeederClass.name);
|
|
1280
|
+
await registry.run(SeederClass, { container: this._requestContainer });
|
|
1281
|
+
}
|
|
1379
1282
|
}
|
|
1380
1283
|
/**
|
|
1381
1284
|
* Assert that a record exists in the database
|
|
@@ -1656,9 +1559,6 @@ function createMockFetch(handlers) {
|
|
|
1656
1559
|
return new MockFetch(handlers);
|
|
1657
1560
|
}
|
|
1658
1561
|
//#endregion
|
|
1659
|
-
//#region src/types.ts
|
|
1660
|
-
var Seeder = class {};
|
|
1661
|
-
//#endregion
|
|
1662
1562
|
//#region src/errors/test-error.ts
|
|
1663
1563
|
/**
|
|
1664
1564
|
* Base error class for all test framework errors.
|
|
@@ -1684,6 +1584,6 @@ var TestSetupError = class extends TestError {
|
|
|
1684
1584
|
}
|
|
1685
1585
|
};
|
|
1686
1586
|
//#endregion
|
|
1687
|
-
export { ActingAs,
|
|
1587
|
+
export { ActingAs, HttpResponse, MockFetch, ProviderOverrideBuilder, Test, TestCommandRequest, TestCommandResult, TestError, TestHttpClient, TestHttpRequest, TestResponse, TestSetupError, TestSseConnection, TestSseRequest, TestWsConnection, TestWsRequest, TestingModule, TestingModuleBuilder, createMockFetch, getTestEnv, http };
|
|
1688
1588
|
|
|
1689
1589
|
//# sourceMappingURL=index.mjs.map
|