@fougere/testing 0.9.3-alpha.0 → 0.10.0-alpha.0
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 +1 -1
- package/dist/{comparison.d.ts → DoorContractCase.d.ts} +5 -13
- package/dist/DoorContractCase.d.ts.map +1 -0
- package/dist/{comparison.js → DoorContractCase.js} +31 -31
- package/dist/DoorContractCase.js.map +1 -0
- package/dist/DoorInput.d.ts +5 -0
- package/dist/DoorInput.d.ts.map +1 -0
- package/dist/DoorInput.js +2 -0
- package/dist/DoorInput.js.map +1 -0
- package/dist/DoorOptions.d.ts +7 -0
- package/dist/DoorOptions.d.ts.map +1 -0
- package/dist/DoorOptions.js +2 -0
- package/dist/DoorOptions.js.map +1 -0
- package/dist/all.d.ts +4 -4
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js +3 -3
- package/dist/all.js.map +1 -1
- package/dist/app/TestApp.d.ts +15 -0
- package/dist/app/TestApp.d.ts.map +1 -0
- package/dist/{app.js → app/TestApp.js} +3 -3
- package/dist/app/TestApp.js.map +1 -0
- package/dist/{app.d.ts → app/TestAppOptions.d.ts} +2 -14
- package/dist/app/TestAppOptions.d.ts.map +1 -0
- package/dist/app/TestAppOptions.js +2 -0
- package/dist/app/TestAppOptions.js.map +1 -0
- package/dist/facades/CheckOptions.d.ts +6 -0
- package/dist/facades/CheckOptions.d.ts.map +1 -0
- package/dist/facades/CheckOptions.js +2 -0
- package/dist/facades/CheckOptions.js.map +1 -0
- package/dist/{doors.d.ts → facades/Verdict.d.ts} +4 -8
- package/dist/facades/Verdict.d.ts.map +1 -0
- package/dist/{doors.js → facades/Verdict.js} +4 -4
- package/dist/facades/Verdict.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/load.d.ts +2 -2
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +4 -4
- package/dist/load.js.map +1 -1
- package/dist/spans.d.ts +1 -1
- package/dist/spans.js +1 -1
- package/dist/stub/Port.d.ts +3 -0
- package/dist/stub/Port.d.ts.map +1 -0
- package/dist/stub/Port.js +2 -0
- package/dist/stub/Port.js.map +1 -0
- package/dist/{stub.d.ts → stub/Stub.d.ts} +2 -3
- package/dist/stub/Stub.d.ts.map +1 -0
- package/dist/{stub.js → stub/Stub.js} +1 -1
- package/dist/stub/Stub.js.map +1 -0
- package/dist/{sync.d.ts → sync/SyncDrift.d.ts} +2 -10
- package/dist/sync/SyncDrift.d.ts.map +1 -0
- package/dist/{sync.js → sync/SyncDrift.js} +5 -18
- package/dist/sync/SyncDrift.js.map +1 -0
- package/dist/sync/SyncedRemote.d.ts +10 -0
- package/dist/sync/SyncedRemote.d.ts.map +1 -0
- package/dist/sync/SyncedRemote.js +15 -0
- package/dist/sync/SyncedRemote.js.map +1 -0
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +4 -0
- package/dist/vitest.js.map +1 -1
- package/package.json +14 -14
- package/src/{comparison.ts → DoorContractCase.ts} +34 -40
- package/src/DoorInput.ts +1 -0
- package/src/DoorOptions.ts +7 -0
- package/src/all.ts +7 -5
- package/src/{app.ts → app/TestApp.ts} +4 -26
- package/src/app/TestAppOptions.ts +25 -0
- package/src/facades/CheckOptions.ts +6 -0
- package/src/{doors.ts → facades/Verdict.ts} +5 -9
- package/src/index.ts +5 -7
- package/src/load.ts +6 -6
- package/src/spans.ts +1 -1
- package/src/stub/Port.ts +2 -0
- package/src/{stub.ts → stub/Stub.ts} +1 -3
- package/src/{sync.ts → sync/SyncDrift.ts} +3 -23
- package/src/sync/SyncedRemote.ts +22 -0
- package/src/vitest.ts +4 -0
- package/dist/app.d.ts.map +0 -1
- package/dist/app.js.map +0 -1
- package/dist/comparison.d.ts.map +0 -1
- package/dist/comparison.js.map +0 -1
- package/dist/doors.d.ts.map +0 -1
- package/dist/doors.js.map +0 -1
- package/dist/stub.d.ts.map +0 -1
- package/dist/stub.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Port.d.ts","sourceRoot":"","sources":["../../src/stub/Port.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,MAAM,IAAI,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Port.js","sourceRoot":"","sources":["../../src/stub/Port.ts"],"names":[],"mappings":""}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type Mock } from 'vitest';
|
|
2
2
|
import type { App } from '@fougere/core';
|
|
3
|
-
|
|
4
|
-
export type Port = abstract new (...args: never[]) => unknown;
|
|
3
|
+
import type { Port } from './Port.js';
|
|
5
4
|
/** The double handed in place of a port — one spy per method the port declares. */
|
|
6
5
|
export type Stub<T> = {
|
|
7
6
|
[K in keyof T]: T[K] extends (...args: never[]) => unknown ? Mock : T[K];
|
|
@@ -12,4 +11,4 @@ export declare function methodsOf(port: Port): string[];
|
|
|
12
11
|
export declare function stubOf<T>(port: Port): Stub<T>;
|
|
13
12
|
/** Put doubles in front of ports, in every frond scope that answers under their name. */
|
|
14
13
|
export declare function installStubs(app: App, ports: Port[]): Map<Port, Stub<unknown>>;
|
|
15
|
-
//# sourceMappingURL=
|
|
14
|
+
//# sourceMappingURL=Stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stub.d.ts","sourceRoot":"","sources":["../../src/stub/Stub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAM,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEvC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,mFAAmF;AACnF,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAEnG,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,CAY9C;AAED,2BAA2B;AAC3B,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAI7C;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAiB9E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Stub.js","sourceRoot":"","sources":["../../src/stub/Stub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAa,MAAM,QAAQ,CAAC;AAQvC,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,KAAK,GAAkB,IAAI,CAAC,SAAmB,CAAC;IACpD,OAAO,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC;QAC3C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;YACrD,IAAI,IAAI,KAAK,aAAa;gBAAE,SAAS;YACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC9D,IAAI,OAAO,QAAQ,EAAE,KAAK,KAAK,UAAU;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAkB,CAAC;IACxD,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,MAAM,CAAI,IAAU;IAClC,MAAM,MAAM,GAAyB,EAAE,CAAC;IACxC,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC;QAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;IAC/D,OAAO,MAAiB,CAAC;AAC3B,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,YAAY,CAAC,GAAQ,EAAE,KAAa;IAClD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAY,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAExF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,UAAU,IAAI,CAAC,IAAI,mDAAmD;kBACpE,sFAAsF,CACzF,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,KAAK,MAAM,KAAK,IAAI,SAAS;YAAE,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { type Change, type SchemaDescriptor } from '@fougere/schema';
|
|
2
2
|
import type { IdentityCard } from '@fougere/core';
|
|
3
|
-
|
|
4
|
-
export interface SyncedRemote {
|
|
5
|
-
name: string;
|
|
6
|
-
url: string;
|
|
7
|
-
/** Where the synced classes were written. */
|
|
8
|
-
path: string;
|
|
9
|
-
}
|
|
3
|
+
import type { SyncedRemote } from './SyncedRemote.js';
|
|
10
4
|
/** What separates a consumer's synced copy from what the producer serves today. */
|
|
11
5
|
export interface SyncDrift {
|
|
12
6
|
frond: string;
|
|
@@ -18,12 +12,10 @@ export interface SyncDrift {
|
|
|
18
12
|
changes: Change[];
|
|
19
13
|
}[];
|
|
20
14
|
}
|
|
21
|
-
/** The remotes a project synced, read from the file `fougere sync` writes. */
|
|
22
|
-
export declare function syncedRemotes(root: string): Promise<SyncedRemote[]>;
|
|
23
15
|
/** The shapes a consumer holds for one remote frond. */
|
|
24
16
|
export declare function heldShapes(remote: SyncedRemote): Promise<Map<string, SchemaDescriptor>>;
|
|
25
17
|
/** What the consumer's copy no longer matches. */
|
|
26
18
|
export declare function syncDriftOf(held: Map<string, SchemaDescriptor>, card: IdentityCard, frond: string): SyncDrift;
|
|
27
19
|
/** Whether the copy still matches. */
|
|
28
20
|
export declare function inSync(drift: SyncDrift): boolean;
|
|
29
|
-
//# sourceMappingURL=
|
|
21
|
+
//# sourceMappingURL=SyncDrift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncDrift.d.ts","sourceRoot":"","sources":["../../src/sync/SyncDrift.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,KAAK,MAAM,EAAE,KAAK,gBAAgB,EAAmB,MAAM,iBAAiB,CAAC;AAC5F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,gEAAgE;IAChE,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;CAChD;AAED,wDAAwD;AACxD,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAW7F;AAYD,kDAAkD;AAClD,wBAAgB,WAAW,CACzB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACnC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,MAAM,GACZ,SAAS,CAWX;AAED,sCAAsC;AACtC,wBAAgB,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAEhD"}
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
1
|
import { join } from 'node:path';
|
|
3
2
|
import { pathToFileURL } from 'node:url';
|
|
4
3
|
import { Card } from '@fougere/schema';
|
|
5
|
-
/** The remotes a project synced, read from the file `fougere sync` writes. */
|
|
6
|
-
export async function syncedRemotes(root) {
|
|
7
|
-
try {
|
|
8
|
-
const raw = await readFile(join(root, '.fougere', 'remotes.json'), 'utf8');
|
|
9
|
-
const parsed = JSON.parse(raw);
|
|
10
|
-
return Object.entries(parsed).map(([name, one]) => ({ name, ...one }));
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
// No file is the ordinary case: an app with no remote synced nothing.
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
4
|
/** The shapes a consumer holds for one remote frond. */
|
|
18
5
|
export async function heldShapes(remote) {
|
|
19
6
|
const cards = new Map();
|
|
@@ -27,15 +14,15 @@ export async function heldShapes(remote) {
|
|
|
27
14
|
}
|
|
28
15
|
return cards;
|
|
29
16
|
}
|
|
30
|
-
/** The shapes a card announces, by the name a
|
|
17
|
+
/** The shapes a card announces, by the name a facade carries. */
|
|
31
18
|
function servedShapes(card, frond) {
|
|
32
19
|
const served = new Map();
|
|
33
20
|
for (const one of card.fronds) {
|
|
34
21
|
if (one.name !== frond)
|
|
35
22
|
continue;
|
|
36
|
-
for (const
|
|
37
|
-
if (
|
|
38
|
-
served.set(
|
|
23
|
+
for (const facade of one.facades)
|
|
24
|
+
if (facade.schema)
|
|
25
|
+
served.set(facade.schema.title ?? facade.name, facade.schema);
|
|
39
26
|
}
|
|
40
27
|
return served;
|
|
41
28
|
}
|
|
@@ -59,4 +46,4 @@ export function syncDriftOf(held, card, frond) {
|
|
|
59
46
|
export function inSync(drift) {
|
|
60
47
|
return drift.gone.length === 0 && drift.moved.length === 0;
|
|
61
48
|
}
|
|
62
|
-
//# sourceMappingURL=
|
|
49
|
+
//# sourceMappingURL=SyncDrift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncDrift.js","sourceRoot":"","sources":["../../src/sync/SyncDrift.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAuD,MAAM,iBAAiB,CAAC;AAa5F,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAoB;IACnD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAClD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAA4B,CAAC;IAE9D,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,QAAkC,CAAC;QAClD,IAAI,OAAO,MAAM,KAAK,UAAU,IAAI,OAAQ,MAAqB,CAAC,SAAS,KAAK,UAAU;YAAE,SAAS;QACrG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iEAAiE;AACjE,SAAS,YAAY,CAAC,IAAkB,EAAE,KAAa;IACrD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,SAAS;QACjC,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO;YAAE,IAAI,MAAM,CAAC,MAAM;gBAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACrH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,WAAW,CACzB,IAAmC,EACnC,IAAkB,EAClB,KAAa;IAEb,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzC,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAExD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,MAAM,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAAC,SAAS;QAAC,CAAC;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAA0B,CAAC,CAAC,CAAC;QAC9F,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,MAAM,CAAC,KAAgB;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One entry of `.fougere/remotes.json`, written by `fougere sync`. */
|
|
2
|
+
export interface SyncedRemote {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
/** Where the synced classes were written. */
|
|
6
|
+
path: string;
|
|
7
|
+
}
|
|
8
|
+
/** The remotes a project synced, read from the file `fougere sync` writes. */
|
|
9
|
+
export declare function syncedRemotes(root: string): Promise<SyncedRemote[]>;
|
|
10
|
+
//# sourceMappingURL=SyncedRemote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncedRemote.d.ts","sourceRoot":"","sources":["../../src/sync/SyncedRemote.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8EAA8E;AAC9E,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CASzE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** The remotes a project synced, read from the file `fougere sync` writes. */
|
|
4
|
+
export async function syncedRemotes(root) {
|
|
5
|
+
try {
|
|
6
|
+
const raw = await readFile(join(root, '.fougere', 'remotes.json'), 'utf8');
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
return Object.entries(parsed).map(([name, one]) => ({ name, ...one }));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// No file is the ordinary case: an app with no remote synced nothing.
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=SyncedRemote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncedRemote.js","sourceRoot":"","sources":["../../src/sync/SyncedRemote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAUjC,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY;IAC9C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkD,CAAC;QAChF,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;QACtE,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/dist/vitest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6B5F"}
|
package/dist/vitest.js
CHANGED
|
@@ -16,6 +16,10 @@ export function fougereTest(overrides = {}) {
|
|
|
16
16
|
// the fronds when it is about several of them.
|
|
17
17
|
include: ['tests/**/*.test.ts', 'fronds/*/tests/**/*.test.ts'],
|
|
18
18
|
server: { deps: { inline: [/@fougere\//, '@pothos/core'] } },
|
|
19
|
+
// A Fougere test boots an app, and a boot runs the scan — a tsc program over the
|
|
20
|
+
// project. Vitest's 5s default times that out on a cold machine, which three
|
|
21
|
+
// packages had each discovered and patched on their own.
|
|
22
|
+
testTimeout: 20_000,
|
|
19
23
|
},
|
|
20
24
|
};
|
|
21
25
|
return {
|
package/dist/vitest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,SAAS,GAA4B,EAAE;IACjE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,UAA8B,CAAC;IACnC,IAAI,CAAC;QACH,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,IAAI,EAAE;YACJ,gFAAgF;YAChF,+CAA+C;YAC/C,OAAO,EAAE,CAAC,oBAAoB,EAAE,6BAA6B,CAAC;YAC9D,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,YAAY,EAAE,cAAc,CAAC,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,SAAS,GAA4B,EAAE;IACjE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,UAA8B,CAAC;IACnC,IAAI,CAAC;QACH,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,IAAI,EAAE;YACJ,gFAAgF;YAChF,+CAA+C;YAC/C,OAAO,EAAE,CAAC,oBAAoB,EAAE,6BAA6B,CAAC;YAC9D,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,YAAY,EAAE,cAAc,CAAC,EAAE,EAAE;YAC5D,iFAAiF;YACjF,6EAA6E;YAC7E,yDAAyD;YACzD,WAAW,EAAE,MAAM;SACpB;KACF,CAAC;IAEF,OAAO;QACL,GAAG,IAAI;QACP,GAAG,SAAS;QACZ,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,OAAiB,IAAI,EAAE,CAAC,EAAE;QACpE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,IAAc,IAAI,EAAE,CAAC,EAAE;KAC5D,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-alpha.0",
|
|
4
4
|
"description": "Tests derived from the declaration — cases, stubs and a gradient of realities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"json-schema-faker": "^0.6.3",
|
|
37
|
-
"@fougere/
|
|
38
|
-
"@fougere/
|
|
39
|
-
"@fougere/
|
|
40
|
-
"@fougere/
|
|
41
|
-
"@fougere/
|
|
42
|
-
"@fougere/
|
|
43
|
-
"@fougere/
|
|
37
|
+
"@fougere/core": "0.10.0-alpha.0",
|
|
38
|
+
"@fougere/transport-http": "0.10.0-alpha.0",
|
|
39
|
+
"@fougere/schema": "0.10.0-alpha.0",
|
|
40
|
+
"@fougere/app": "0.10.0-alpha.0",
|
|
41
|
+
"@fougere/container": "0.10.0-alpha.0",
|
|
42
|
+
"@fougere/defaults": "0.10.0-alpha.0",
|
|
43
|
+
"@fougere/compiler": "0.10.0-alpha.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"vitest": ">=2",
|
|
50
|
-
"@fougere/
|
|
51
|
-
"@fougere/
|
|
52
|
-
"@fougere/adapter-
|
|
50
|
+
"@fougere/adapter-graphql": "0.10.0-alpha.0",
|
|
51
|
+
"@fougere/observability": "0.10.0-alpha.0",
|
|
52
|
+
"@fougere/adapter-sql": "0.10.0-alpha.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"vitest": {
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"vitest": "^4.1.0",
|
|
70
|
-
"@fougere/adapter-
|
|
71
|
-
"@fougere/adapter-
|
|
72
|
-
"@fougere/observability": "0.
|
|
70
|
+
"@fougere/adapter-sql": "0.10.0-alpha.0",
|
|
71
|
+
"@fougere/adapter-graphql": "0.10.0-alpha.0",
|
|
72
|
+
"@fougere/observability": "0.10.0-alpha.0"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "rm -rf dist && tsc",
|
|
@@ -4,9 +4,11 @@ import { Invocation } from '@fougere/core/contract';
|
|
|
4
4
|
import { serveRest, serveRpc, tableOf } from '@fougere/app';
|
|
5
5
|
import { lowerFirst, type SchemaView } from '@fougere/schema';
|
|
6
6
|
import { listQuery, findQuery, mutationFor, at } from './gql.js';
|
|
7
|
-
import { sampleInput
|
|
7
|
+
import { sampleInput } from './sample.js';
|
|
8
|
+
import type { DoorOptions } from './DoorOptions.js';
|
|
9
|
+
import type { DoorInput } from './DoorInput.js';
|
|
8
10
|
|
|
9
|
-
/** The rows a
|
|
11
|
+
/** The rows a facade hands back, with its own envelope taken off. */
|
|
10
12
|
function rowsOf(value: unknown): unknown {
|
|
11
13
|
if (Array.isArray(value)) return [...value];
|
|
12
14
|
const page = value as { items?: unknown } | null;
|
|
@@ -23,21 +25,13 @@ function written(value: unknown, sent: Record<string, unknown>): unknown {
|
|
|
23
25
|
return wire(Object.fromEntries(Object.keys(sent).map((key) => [key, row[key]])));
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
given?: Record<string, unknown>;
|
|
28
|
-
/** The audience, when the app serves named surfaces. */
|
|
29
|
-
surface?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface Doors {
|
|
28
|
+
interface Facades {
|
|
33
29
|
local: (op: string, call?: DoorInput) => Promise<unknown>;
|
|
34
30
|
rpc: (op: string, call?: DoorInput) => Promise<unknown>;
|
|
35
31
|
rest: (op: string, call?: DoorInput) => Promise<unknown>;
|
|
36
32
|
graphql: (op: string, call?: DoorInput) => Promise<unknown>;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
export interface DoorInput { id?: string; input?: Record<string, unknown> }
|
|
40
|
-
|
|
41
35
|
export interface DoorContractCase {
|
|
42
36
|
/** What this case proves — becomes the test name. */
|
|
43
37
|
name: string;
|
|
@@ -48,17 +42,17 @@ export interface DoorContractCase {
|
|
|
48
42
|
expected: unknown;
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
/** One entity, four
|
|
45
|
+
/** One entity, four facades, the same answers. */
|
|
52
46
|
export function checkDoors(app: App, entity: SchemaView, options: DoorOptions = {}): void {
|
|
53
47
|
const name = lowerFirst(entity.name ?? '');
|
|
54
|
-
const
|
|
48
|
+
const facades = facadesOf(app, entity, name, options.surface);
|
|
55
49
|
const inputOf = () => sampleInput(entity, options.given ?? {}, options);
|
|
56
50
|
|
|
57
|
-
describe(`${entity.name} — the
|
|
51
|
+
describe(`${entity.name} — the facades agree`, () => {
|
|
58
52
|
it('on create, over what the caller sent', async () => {
|
|
59
53
|
const sent = inputOf();
|
|
60
54
|
const answers = await Promise.all(
|
|
61
|
-
(['local', 'rpc', 'rest', 'graphql'] as const).map((
|
|
55
|
+
(['local', 'rpc', 'rest', 'graphql'] as const).map((facade) => facades[facade]('create', { input: sent })),
|
|
62
56
|
);
|
|
63
57
|
|
|
64
58
|
const [local, ...others] = answers.map((answer) => written(answer, sent));
|
|
@@ -68,33 +62,33 @@ export function checkDoors(app: App, entity: SchemaView, options: DoorOptions =
|
|
|
68
62
|
});
|
|
69
63
|
|
|
70
64
|
it('on list', async () => {
|
|
71
|
-
await
|
|
65
|
+
await facades.local('create', { input: inputOf() });
|
|
72
66
|
|
|
73
|
-
const local = wire(rowsOf(await
|
|
67
|
+
const local = wire(rowsOf(await facades.local('list')));
|
|
74
68
|
expect(Array.isArray(local) && local.length > 0, 'nothing to compare').toBe(true);
|
|
75
69
|
|
|
76
|
-
expect(wire(rowsOf(await
|
|
77
|
-
expect(wire(rowsOf(await
|
|
78
|
-
expect(wire(rowsOf(await
|
|
70
|
+
expect(wire(rowsOf(await facades.rpc('list'))), 'rpc ≠ local').toEqual(local);
|
|
71
|
+
expect(wire(rowsOf(await facades.rest('list'))), 'rest ≠ local').toEqual(local);
|
|
72
|
+
expect(wire(rowsOf(await facades.graphql('list'))), 'graphql ≠ local').toEqual(local);
|
|
79
73
|
});
|
|
80
74
|
|
|
81
75
|
it('on findById', async () => {
|
|
82
|
-
const row = await
|
|
76
|
+
const row = await facades.local('create', { input: inputOf() }) as { id: string };
|
|
83
77
|
|
|
84
|
-
const local = wire(await
|
|
78
|
+
const local = wire(await facades.local('findById', { id: row.id }));
|
|
85
79
|
|
|
86
|
-
expect(wire(await
|
|
87
|
-
expect(wire(await
|
|
88
|
-
expect(wire(await
|
|
80
|
+
expect(wire(await facades.rpc('findById', { id: row.id })), 'rpc ≠ local').toEqual(local);
|
|
81
|
+
expect(wire(await facades.rest('findById', { id: row.id })), 'rest ≠ local').toEqual(local);
|
|
82
|
+
expect(wire(await facades.graphql('findById', { id: row.id })), 'graphql ≠ local').toEqual(local);
|
|
89
83
|
});
|
|
90
84
|
|
|
91
85
|
it('on update, over what the caller sent', async () => {
|
|
92
|
-
const rows = await Promise.all([1, 2, 3, 4].map(() =>
|
|
86
|
+
const rows = await Promise.all([1, 2, 3, 4].map(() => facades.local('create', { input: inputOf() }))) as { id: string }[];
|
|
93
87
|
const patch = inputOf();
|
|
94
88
|
|
|
95
89
|
const answers = await Promise.all(
|
|
96
90
|
(['local', 'rpc', 'rest', 'graphql'] as const)
|
|
97
|
-
.map((
|
|
91
|
+
.map((facade, index) => facades[facade]('update', { id: rows[index].id, input: patch })),
|
|
98
92
|
);
|
|
99
93
|
|
|
100
94
|
const [local, ...others] = answers.map((answer) => written(answer, patch));
|
|
@@ -104,32 +98,32 @@ export function checkDoors(app: App, entity: SchemaView, options: DoorOptions =
|
|
|
104
98
|
});
|
|
105
99
|
|
|
106
100
|
it('on delete', async () => {
|
|
107
|
-
const rows = await Promise.all([1, 2, 3, 4].map(() =>
|
|
101
|
+
const rows = await Promise.all([1, 2, 3, 4].map(() => facades.local('create', { input: inputOf() }))) as { id: string }[];
|
|
108
102
|
|
|
109
103
|
const answers = await Promise.all(
|
|
110
|
-
(['local', 'rpc', 'rest', 'graphql'] as const).map((
|
|
104
|
+
(['local', 'rpc', 'rest', 'graphql'] as const).map((facade, index) => facades[facade]('delete', { id: rows[index].id })),
|
|
111
105
|
);
|
|
112
106
|
|
|
113
107
|
// REST answers a deletion with no content at all, which is the protocol saying yes.
|
|
114
108
|
const onTheWire = answers.map((answer) => (answer === undefined || answer === null ? true : wire(answer)));
|
|
115
|
-
expect(new Set(onTheWire).size, `the
|
|
109
|
+
expect(new Set(onTheWire).size, `the facades disagree: ${JSON.stringify(onTheWire)}`).toBe(1);
|
|
116
110
|
});
|
|
117
111
|
|
|
118
112
|
it('on a refusal', async () => {
|
|
119
|
-
// A refusal is where the
|
|
113
|
+
// A refusal is where the facades diverge most, and where each is most tempted to
|
|
120
114
|
// answer in its own words. What must match is that it WAS refused.
|
|
121
115
|
const bad = { ...inputOf(), __unknown__: 'x' };
|
|
122
116
|
const refusals = await Promise.all(
|
|
123
|
-
(['local', 'rpc', 'rest', 'graphql'] as const).map((
|
|
117
|
+
(['local', 'rpc', 'rest', 'graphql'] as const).map((facade) => refused(() => facades[facade]('create', { input: bad }))),
|
|
124
118
|
);
|
|
125
119
|
|
|
126
120
|
expect(refusals[0], 'local accepted a body outside the contract').toBe(true);
|
|
127
|
-
expect(refusals, `the
|
|
121
|
+
expect(refusals, `the facades disagree: ${JSON.stringify(refusals)}`).toEqual([true, true, true, true]);
|
|
128
122
|
});
|
|
129
123
|
});
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
/** Run one hand-written invocation contract through every
|
|
126
|
+
/** Run one hand-written invocation contract through every facade. */
|
|
133
127
|
export function checkDoorContract(
|
|
134
128
|
app: App,
|
|
135
129
|
entity: SchemaView,
|
|
@@ -137,13 +131,13 @@ export function checkDoorContract(
|
|
|
137
131
|
options: Pick<DoorOptions, 'surface'> = {},
|
|
138
132
|
): void {
|
|
139
133
|
const name = lowerFirst(entity.name ?? '');
|
|
140
|
-
const
|
|
134
|
+
const facades = facadesOf(app, entity, name, options.surface);
|
|
141
135
|
const names = ['local', 'rpc', 'rest', 'graphql'] as const;
|
|
142
136
|
|
|
143
|
-
describe(`${entity.name} — its invocation contract crosses every
|
|
137
|
+
describe(`${entity.name} — its invocation contract crosses every facade`, () => {
|
|
144
138
|
for (const one of cases) {
|
|
145
139
|
it(one.name, async () => {
|
|
146
|
-
const answers = await Promise.all(names.map((
|
|
140
|
+
const answers = await Promise.all(names.map((facade) => facades[facade](one.operation, one.input)));
|
|
147
141
|
for (const [index, answer] of answers.entries()) {
|
|
148
142
|
expect(wire(answer), `${names[index]} disagrees with the canonical invocation`).toEqual(wire(one.expected));
|
|
149
143
|
}
|
|
@@ -156,8 +150,8 @@ async function refused(call: () => Promise<unknown>): Promise<boolean> {
|
|
|
156
150
|
try { await call(); return false; } catch { return true; }
|
|
157
151
|
}
|
|
158
152
|
|
|
159
|
-
/** The four
|
|
160
|
-
function
|
|
153
|
+
/** The four facades, each reduced to `(op, input) => answer` so the tests above read alike. */
|
|
154
|
+
function facadesOf(app: App, entity: SchemaView, name: string, surface?: string): Facades {
|
|
161
155
|
const run = createLocalRunner(app, surface);
|
|
162
156
|
const state: Record<string, unknown> = {};
|
|
163
157
|
|
|
@@ -181,7 +175,7 @@ function doorsOf(app: App, entity: SchemaView, name: string, surface?: string):
|
|
|
181
175
|
},
|
|
182
176
|
|
|
183
177
|
rest: async (op, call) => {
|
|
184
|
-
// The route the REST
|
|
178
|
+
// The route the REST facade itself would match, read from its own table — rebuilding
|
|
185
179
|
// the path here would be a second opinion on where an entity lives.
|
|
186
180
|
const route = tableOf(app).find((one) => one.entityName === name && one.operationName === op);
|
|
187
181
|
if (!route) throw new Error(`[checkDoors] REST serves no ${name}.${op}`);
|
package/src/DoorInput.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export interface DoorInput { id?: string; input?: Record<string, unknown> }
|
package/src/all.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import type { App } from '@fougere/core';
|
|
3
3
|
import type { SchemaView } from '@fougere/schema';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { type CheckOptions } from './facades/CheckOptions.js';
|
|
5
|
+
import { checkContract, checkOutput } from './facades/Verdict.js';
|
|
6
|
+
import { checkDoors } from './DoorContractCase.js';
|
|
7
|
+
import { type DoorOptions } from './DoorOptions.js';
|
|
6
8
|
|
|
7
9
|
export interface CheckAllOptions extends DoorOptions, CheckOptions {
|
|
8
10
|
/** Entities to leave out, by name — one whose rows a test cannot seed, typically. */
|
|
9
11
|
except?: string[];
|
|
10
|
-
/** Skip the four-
|
|
11
|
-
|
|
12
|
+
/** Skip the four-facade comparison. The contract and the leak are still checked. */
|
|
13
|
+
facades?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/** Every entity the app SERVES, with its handler. */
|
|
@@ -37,6 +39,6 @@ export function checkAll(app: App, options: CheckAllOptions = {}): void {
|
|
|
37
39
|
for (const { entity } of served) {
|
|
38
40
|
checkContract(app, entity, options);
|
|
39
41
|
checkOutput(app, entity, options);
|
|
40
|
-
if (options.
|
|
42
|
+
if (options.facades !== false) checkDoors(app, entity, options);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -2,33 +2,11 @@ import { type App } from '@fougere/core';
|
|
|
2
2
|
import { boot } from '@fougere/compiler';
|
|
3
3
|
import { createContainer } from '@fougere/container';
|
|
4
4
|
import { resolveStorage, type DbConfig } from '@fougere/defaults';
|
|
5
|
-
import { installStubs, type
|
|
6
|
-
import {
|
|
5
|
+
import { installStubs, type Stub } from '../stub/Stub.js';
|
|
6
|
+
import type { Port } from '../stub/Port.js';
|
|
7
|
+
import { scopeOfRun } from '../scope.js';
|
|
7
8
|
import { lowerFirst } from '@fougere/schema';
|
|
8
|
-
|
|
9
|
-
export interface TestAppOptions {
|
|
10
|
-
/** The project to scan. */
|
|
11
|
-
root?: string;
|
|
12
|
-
/** Boot only these fronds, by name. Deduced from the position when absent. */
|
|
13
|
-
fronds?: string[];
|
|
14
|
-
/**
|
|
15
|
-
* The running test file, for hosts other than vitest. Vitest is read automatically
|
|
16
|
-
* through `expect.getState()`; anything else hands its own path in.
|
|
17
|
-
*/
|
|
18
|
-
testPath?: string;
|
|
19
|
-
/** Where the rows go. */
|
|
20
|
-
db?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Follow `remotes:` from the config. False by default — a test that meant to exercise
|
|
23
|
-
* one frond should not silently reach for a process that is not running.
|
|
24
|
-
*/
|
|
25
|
-
topology?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Ports answered by a double instead of their realization. A double carries every
|
|
28
|
-
* method the port declares and returns nothing until the test says what it returns.
|
|
29
|
-
*/
|
|
30
|
-
stub?: Port[];
|
|
31
|
-
}
|
|
9
|
+
import type { TestAppOptions } from './TestAppOptions.js';
|
|
32
10
|
|
|
33
11
|
/** An app, plus the doubles standing in front of its ports and the facts it announced. */
|
|
34
12
|
export interface TestApp extends App {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Port } from '../stub/Port.js';
|
|
2
|
+
|
|
3
|
+
export interface TestAppOptions {
|
|
4
|
+
/** The project to scan. */
|
|
5
|
+
root?: string;
|
|
6
|
+
/** Boot only these fronds, by name. Deduced from the position when absent. */
|
|
7
|
+
fronds?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* The running test file, for hosts other than vitest. Vitest is read automatically
|
|
10
|
+
* through `expect.getState()`; anything else hands its own path in.
|
|
11
|
+
*/
|
|
12
|
+
testPath?: string;
|
|
13
|
+
/** Where the rows go. */
|
|
14
|
+
db?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Follow `remotes:` from the config. False by default — a test that meant to exercise
|
|
17
|
+
* one frond should not silently reach for a process that is not running.
|
|
18
|
+
*/
|
|
19
|
+
topology?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Ports answered by a double instead of their realization. A double carries every
|
|
22
|
+
* method the port declares and returns nothing until the test says what it returns.
|
|
23
|
+
*/
|
|
24
|
+
stub?: Port[];
|
|
25
|
+
}
|
|
@@ -3,16 +3,17 @@ import { createLocalRunner, validationErrorsOf, type App } from '@fougere/core';
|
|
|
3
3
|
import { Invocation } from '@fougere/core/contract';
|
|
4
4
|
import { lowerFirst, Visibility, type SchemaView, type ValidationError } from '@fougere/schema';
|
|
5
5
|
import { Cases } from '@fougere/schema';
|
|
6
|
-
import { derivedCases } from '
|
|
7
|
-
import { sampleInput, replaySeed
|
|
6
|
+
import { derivedCases } from '../derive.js';
|
|
7
|
+
import { sampleInput, replaySeed } from '../sample.js';
|
|
8
|
+
import type { CheckOptions } from './CheckOptions.js';
|
|
8
9
|
|
|
9
|
-
/** The one shape both the local validator and a
|
|
10
|
+
/** The one shape both the local validator and a facade already speak. */
|
|
10
11
|
export interface Verdict {
|
|
11
12
|
success: boolean;
|
|
12
13
|
errors?: ValidationError[];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/** What a
|
|
16
|
+
/** What a facade answers, in the shape a verdict is compared in. */
|
|
16
17
|
export async function verdictOf(call: () => Promise<unknown>): Promise<Verdict> {
|
|
17
18
|
try {
|
|
18
19
|
await call();
|
|
@@ -29,11 +30,6 @@ function opsFor(entity: SchemaView): { create: string; update: string; name: str
|
|
|
29
30
|
return { name, create: 'create', update: 'update' };
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export interface CheckOptions extends SampleOptions {
|
|
33
|
-
/** Values the generator cannot invent — the id of a row a `ref()` points at. */
|
|
34
|
-
given?: Record<string, unknown>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
33
|
/** The declared contract, posed to the façade that will receive it. */
|
|
38
34
|
export function checkContract(app: App, entity: SchemaView, options: CheckOptions = {}): void {
|
|
39
35
|
const { name, create, update } = opsFor(entity);
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
export { sampleInput } from './sample.js';
|
|
2
2
|
// The derivation itself lives with the axes it reads.
|
|
3
3
|
export { Cases, type ValidationCase } from '@fougere/schema';
|
|
4
|
-
export { testApp } from './app.js';
|
|
5
|
-
export { checkContract, checkOutput, verdictOf, type Verdict } from './
|
|
6
|
-
export {
|
|
4
|
+
export { testApp } from './app/TestApp.js';
|
|
5
|
+
export { checkContract, checkOutput, verdictOf, type Verdict } from './facades/Verdict.js';
|
|
6
|
+
export { type Port } from './stub/Port.js';
|
|
7
|
+
export { stubOf, type Stub } from './stub/Stub.js';
|
|
7
8
|
export { frondOf, type Scope } from './scope.js';
|
|
8
9
|
export { loadScript, reachableOps } from './load.js';
|
|
9
10
|
export { statementsOf } from './statements.js';
|
|
10
11
|
export { spansOf } from './spans.js';
|
|
11
|
-
export {
|
|
12
|
-
checkDoorContract,
|
|
13
|
-
checkDoors,
|
|
14
|
-
} from './comparison.js';
|
|
12
|
+
export { checkDoorContract, checkDoors } from './DoorContractCase.js';
|
|
15
13
|
export { at } from './gql.js';
|
|
16
14
|
export { driftOf, agrees, explain, type CardDrift } from './remotes.js';
|
|
17
15
|
export { checkAll } from './all.js';
|