@aventara/testing 1.0.0-rc.0 → 1.0.0-rc.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.
- package/CHANGELOG.md +429 -0
- package/README.md +22 -21
- package/dist/adapters/adapter-factory.d.ts +2 -2
- package/dist/adapters/fake-adapter.d.ts +16 -16
- package/dist/index.d.ts +2 -2
- package/dist/protocol/corpus-contract.fixture.d.ts +59 -46
- package/dist/protocol/corpus-contract.fixture.js +40 -31
- package/dist/protocol/protocol-conformance.d.ts +4 -4
- package/dist/protocol/protocol-corpus.d.ts +3 -3
- package/dist/protocol/protocol-driver.d.ts +2 -2
- package/dist/protocol/protocol-fixture.d.ts +4 -4
- package/dist/protocol/protocol-fixtures.js +4 -3
- package/fixtures/protocol-v1.json +100 -100
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { AvContext, AvFramework } from "@aventara/core";
|
|
2
|
+
import { AvDecimal } from "@aventara/core";
|
|
3
3
|
/**
|
|
4
4
|
* The corpus's adapter model:
|
|
5
5
|
*
|
|
@@ -7,7 +7,7 @@ import { Decimal } from "@aventara/core";
|
|
|
7
7
|
* client scope;
|
|
8
8
|
* - `books`: references `authors`, and deleting an author cascades into it
|
|
9
9
|
* ({@link CORPUS_REFERENTIAL_ACTIONS}; a plan's `V1019`); the client scope
|
|
10
|
-
*
|
|
10
|
+
* defines a computed `label` the client alone sees ({@link CORPUS_RESOURCES});
|
|
11
11
|
* - `secrets`: its one operation restricted away at client scope;
|
|
12
12
|
* - `hiddenRes`: hidden at client scope;
|
|
13
13
|
* - `samples`: one field of each scalar whose wire form is not its runtime
|
|
@@ -283,9 +283,9 @@ export declare const CORPUS_REFERENTIAL_ACTIONS: {
|
|
|
283
283
|
* The `where.id` a row sends to make the corpus Framework fail one way:
|
|
284
284
|
*
|
|
285
285
|
* - `A4000`, `A4001`, `A4002`, `A3000` — the client guard throws
|
|
286
|
-
* `
|
|
287
|
-
* `
|
|
288
|
-
* - `A2008`, `A2013`, `A2014` — the Adapter throws `
|
|
286
|
+
* `AvError("A4000")`, denies (`false`), throws
|
|
287
|
+
* `AvError("A4002")`, or throws a plain `Error`;
|
|
288
|
+
* - `A2008`, `A2013`, `A2014` — the Adapter throws `AvError` with that
|
|
289
289
|
* conflict code; `A3001` — it throws a plain `Error`;
|
|
290
290
|
* - `A3002` — a plan step carrying it runs, and the Adapter's transaction then
|
|
291
291
|
* fails to commit;
|
|
@@ -307,65 +307,78 @@ export declare const CORPUS_TRIGGER: {
|
|
|
307
307
|
readonly pipeOutputInvalid: "A3004";
|
|
308
308
|
};
|
|
309
309
|
/** The corpus's one client guard: it decides nothing unless a row names a trigger. */
|
|
310
|
-
declare function corpusGuard(context:
|
|
310
|
+
declare function corpusGuard(context: AvContext): boolean;
|
|
311
311
|
/**
|
|
312
312
|
* The corpus's one client pipe: it rewrites nothing unless a row names the
|
|
313
313
|
* `A3004` trigger, and then adds a filter on a field the server does not have.
|
|
314
314
|
*/
|
|
315
315
|
declare function corpusPipe(args: unknown): unknown;
|
|
316
316
|
/**
|
|
317
|
-
* The corpus's
|
|
318
|
-
*
|
|
319
|
-
* computed on read from `id`.
|
|
317
|
+
* The corpus's root `resources` tree: `books.label`, a VIRTUAL field computed on
|
|
318
|
+
* read from `id`.
|
|
320
319
|
*/
|
|
321
|
-
export declare const
|
|
322
|
-
readonly
|
|
323
|
-
readonly
|
|
320
|
+
export declare const CORPUS_RESOURCES: {
|
|
321
|
+
readonly books: {
|
|
322
|
+
readonly fields: {
|
|
324
323
|
readonly label: {
|
|
325
|
-
readonly
|
|
326
|
-
readonly
|
|
324
|
+
readonly definition: {
|
|
325
|
+
readonly type: {
|
|
326
|
+
readonly scalar: "string";
|
|
327
|
+
};
|
|
328
|
+
readonly nullable: false;
|
|
329
|
+
readonly list: false;
|
|
330
|
+
readonly lifecycle: readonly ["VIRTUAL"];
|
|
331
|
+
};
|
|
332
|
+
readonly pipelines: {
|
|
333
|
+
readonly compute: {
|
|
334
|
+
readonly read: {
|
|
335
|
+
readonly dependsOn: readonly ["id"];
|
|
336
|
+
readonly compute: ({ record, }: {
|
|
337
|
+
readonly record: Readonly<Record<string, unknown>>;
|
|
338
|
+
}) => string;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
327
341
|
};
|
|
328
|
-
readonly nullable: false;
|
|
329
|
-
readonly list: false;
|
|
330
|
-
readonly lifecycle: readonly ["VIRTUAL"];
|
|
331
342
|
};
|
|
332
343
|
};
|
|
333
344
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
};
|
|
345
|
+
};
|
|
346
|
+
/** The corpus's application layer: `books.label` is the client's only. */
|
|
347
|
+
export declare const CORPUS_APPLICATION_CONFIG: {
|
|
348
|
+
readonly exposed: true;
|
|
349
|
+
readonly exemptions: {
|
|
350
|
+
readonly resources: {
|
|
351
|
+
readonly books: {
|
|
352
|
+
readonly fields: {
|
|
353
|
+
readonly label: true;
|
|
344
354
|
};
|
|
345
355
|
};
|
|
346
356
|
};
|
|
347
357
|
};
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* The corpus's client layer: what the ClientContract hides or restricts, and
|
|
361
|
+
* the client's own guard and pipe.
|
|
362
|
+
*/
|
|
363
|
+
export declare const CORPUS_CLIENT_CONFIG: {
|
|
348
364
|
readonly pipelines: {
|
|
349
365
|
readonly guards: readonly [typeof corpusGuard];
|
|
350
366
|
readonly pipes: readonly [typeof corpusPipe];
|
|
351
367
|
};
|
|
352
|
-
readonly
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
368
|
+
readonly exposed: true;
|
|
369
|
+
readonly exemptions: {
|
|
370
|
+
readonly resources: {
|
|
371
|
+
readonly authors: {
|
|
372
|
+
readonly fields: {
|
|
373
|
+
readonly secret: true;
|
|
357
374
|
};
|
|
358
375
|
};
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
readonly find: {
|
|
363
|
-
readonly many: false;
|
|
376
|
+
readonly secrets: {
|
|
377
|
+
readonly operations: {
|
|
378
|
+
readonly find: readonly ["many"];
|
|
364
379
|
};
|
|
365
380
|
};
|
|
366
|
-
|
|
367
|
-
readonly hiddenRes: {
|
|
368
|
-
readonly hidden: true;
|
|
381
|
+
readonly hiddenRes: true;
|
|
369
382
|
};
|
|
370
383
|
};
|
|
371
384
|
};
|
|
@@ -375,20 +388,20 @@ export declare const CORPUS_ENTRYPOINT = "/api";
|
|
|
375
388
|
* The corpus ClientContract's hash: what a generated client for it sends as
|
|
376
389
|
* `Aventara-Contract-Hash`. A literal, as a generated client holds it.
|
|
377
390
|
*/
|
|
378
|
-
export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:
|
|
391
|
+
export declare const CORPUS_CLIENT_CONTRACT_HASH = "sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307";
|
|
379
392
|
/**
|
|
380
393
|
* The corpus ClientContract as `GET /_contract` serves it: its JCS-canonical
|
|
381
394
|
* bytes, `protocol.hash` included. A literal, as a host must serve it byte for
|
|
382
395
|
* byte.
|
|
383
396
|
*/
|
|
384
|
-
export declare const CORPUS_CLIENT_CONTRACT_DOCUMENT = "{\"enums\":{},\"limits\":{\"maxBooleanNodes\":50,\"maxListLimit\":250,\"maxNestingDepth\":12,\"maxRequestBytes\":1048576,\"maxTransactionOperations\":20},\"protocol\":{\"hash\":\"sha256:
|
|
397
|
+
export declare const CORPUS_CLIENT_CONTRACT_DOCUMENT = "{\"enums\":{},\"limits\":{\"maxBooleanNodes\":50,\"maxJsonDepth\":64,\"maxListLimit\":250,\"maxNestingDepth\":12,\"maxRequestBytes\":1048576,\"maxTransactionOperations\":20},\"protocol\":{\"hash\":\"sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307\",\"version\":1},\"resources\":{\"authors\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"name\":{\"capabilities\":{\"create\":[],\"filter\":[\"equals\"],\"select\":true,\"update\":[]},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"count\":true,\"many\":true,\"one\":true},\"delete\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"find\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"update\":{\"count\":true,\"first\":true,\"many\":true,\"unique\":true},\"upsert\":{\"unique\":true}}},\"books\":{\"fields\":{\"authorId\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}},\"label\":{\"capabilities\":{\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[\"COMPUTED_ON_READ\",\"VIRTUAL\"],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"find\":{\"many\":true}}},\"samples\":{\"fields\":{\"amount\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"decimal\"}},\"at\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"datetime\"}},\"blob\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bytes\"}},\"count\":{\"capabilities\":{\"create\":[],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"bigint\"}},\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{\"create\":{\"one\":true},\"find\":{\"unique\":true}}},\"secrets\":{\"fields\":{\"id\":{\"capabilities\":{\"filter\":[\"equals\"],\"select\":true},\"kind\":\"scalar\",\"lifecycle\":[],\"list\":false,\"nullable\":false,\"type\":{\"scalar\":\"string\"}}},\"identifiers\":[[\"id\"]],\"operations\":{}}},\"scalars\":{\"bigint\":{\"builtin\":true},\"bytes\":{\"builtin\":true},\"datetime\":{\"builtin\":true},\"decimal\":{\"builtin\":true},\"string\":{\"builtin\":true}},\"transactions\":\"interactive\"}";
|
|
385
398
|
/**
|
|
386
399
|
* The hash of the corpus's second ClientContract: the same model and
|
|
387
400
|
* configuration with `transactions: "none"` (the `/_transactions` absence
|
|
388
401
|
* rows). A row targets it by sending this hash, exactly as a client generated
|
|
389
402
|
* against it would.
|
|
390
403
|
*/
|
|
391
|
-
export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:
|
|
404
|
+
export declare const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:5e99809294a2aae922b635e9a44b464b1b6e03359a54d30420292c62676a7ce5";
|
|
392
405
|
/** The one record the corpus Adapter knows. */
|
|
393
406
|
export declare const CORPUS_AUTHOR: {
|
|
394
407
|
readonly id: "a1";
|
|
@@ -404,7 +417,7 @@ export declare const CORPUS_SAMPLE: {
|
|
|
404
417
|
readonly id: "s1";
|
|
405
418
|
readonly count: 9007199254740993n;
|
|
406
419
|
readonly at: Date;
|
|
407
|
-
readonly amount:
|
|
420
|
+
readonly amount: AvDecimal;
|
|
408
421
|
readonly blob: Uint8Array<ArrayBuffer>;
|
|
409
422
|
};
|
|
410
423
|
/**
|
|
@@ -415,7 +428,7 @@ export declare const CORPUS_SAMPLE: {
|
|
|
415
428
|
* otherwise (a stale or absent hash included).
|
|
416
429
|
*/
|
|
417
430
|
export declare function createCorpusFramework(contractHash?: string): Promise<{
|
|
418
|
-
readonly framework:
|
|
431
|
+
readonly framework: AvFramework;
|
|
419
432
|
readonly adapterCalls: () => number;
|
|
420
433
|
}>;
|
|
421
434
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AvDecimal, AvError, createFramework } from "@aventara/core";
|
|
2
2
|
import { createFakeAdapter } from "../adapters/fake-adapter.js";
|
|
3
3
|
const identityString = {
|
|
4
4
|
kind: "scalar",
|
|
@@ -113,13 +113,13 @@ function whereIdOf(args) {
|
|
|
113
113
|
return typeof id === "string" ? id : undefined;
|
|
114
114
|
}
|
|
115
115
|
function corpusGuard(context) {
|
|
116
|
-
switch (whereIdOf(context
|
|
116
|
+
switch (whereIdOf(context.$args)) {
|
|
117
117
|
case CORPUS_TRIGGER.unauthenticated:
|
|
118
|
-
throw new
|
|
118
|
+
throw new AvError("A4000", "Authentication is required.");
|
|
119
119
|
case CORPUS_TRIGGER.guardDenies:
|
|
120
120
|
return false;
|
|
121
121
|
case CORPUS_TRIGGER.policyDenies:
|
|
122
|
-
throw new
|
|
122
|
+
throw new AvError("A4002", "Denied by the corpus policy.");
|
|
123
123
|
case CORPUS_TRIGGER.guardThrows:
|
|
124
124
|
throw new Error("the corpus guard failed");
|
|
125
125
|
default:
|
|
@@ -133,47 +133,54 @@ function corpusPipe(args) {
|
|
|
133
133
|
const record = args;
|
|
134
134
|
return { ...record, where: { ...record.where, nope: "x" } };
|
|
135
135
|
}
|
|
136
|
-
export const
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
export const CORPUS_RESOURCES = {
|
|
137
|
+
books: {
|
|
138
|
+
fields: {
|
|
139
139
|
label: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
dependsOn: ["id"],
|
|
153
|
-
compute: ({ record, }) => `book:${String(record.id)}`,
|
|
140
|
+
definition: {
|
|
141
|
+
type: { scalar: "string" },
|
|
142
|
+
nullable: false,
|
|
143
|
+
list: false,
|
|
144
|
+
lifecycle: ["VIRTUAL"],
|
|
145
|
+
},
|
|
146
|
+
pipelines: {
|
|
147
|
+
compute: {
|
|
148
|
+
read: {
|
|
149
|
+
dependsOn: ["id"],
|
|
150
|
+
compute: ({ record, }) => `book:${String(record.id)}`,
|
|
151
|
+
},
|
|
154
152
|
},
|
|
155
153
|
},
|
|
156
154
|
},
|
|
157
155
|
},
|
|
158
156
|
},
|
|
157
|
+
};
|
|
158
|
+
export const CORPUS_APPLICATION_CONFIG = {
|
|
159
|
+
exposed: true,
|
|
160
|
+
exemptions: { resources: { books: { fields: { label: true } } } },
|
|
161
|
+
};
|
|
162
|
+
export const CORPUS_CLIENT_CONFIG = {
|
|
159
163
|
pipelines: { guards: [corpusGuard], pipes: [corpusPipe] },
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
+
exposed: true,
|
|
165
|
+
exemptions: {
|
|
166
|
+
resources: {
|
|
167
|
+
authors: { fields: { secret: true } },
|
|
168
|
+
secrets: { operations: { find: ["many"] } },
|
|
169
|
+
hiddenRes: true,
|
|
170
|
+
},
|
|
164
171
|
},
|
|
165
172
|
};
|
|
166
173
|
export const CORPUS_ENTRYPOINT = "/api";
|
|
167
|
-
export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:
|
|
168
|
-
export const CORPUS_CLIENT_CONTRACT_DOCUMENT = '{"enums":{},"limits":{"maxBooleanNodes":50,"maxListLimit":250,"maxNestingDepth":12,"maxRequestBytes":1048576,"maxTransactionOperations":20},"protocol":{"hash":"sha256:
|
|
169
|
-
export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:
|
|
174
|
+
export const CORPUS_CLIENT_CONTRACT_HASH = "sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307";
|
|
175
|
+
export const CORPUS_CLIENT_CONTRACT_DOCUMENT = '{"enums":{},"limits":{"maxBooleanNodes":50,"maxJsonDepth":64,"maxListLimit":250,"maxNestingDepth":12,"maxRequestBytes":1048576,"maxTransactionOperations":20},"protocol":{"hash":"sha256:502530d5b918266c61f45e70035acd7d9637bbf7305c5572106185d5e63ec307","version":1},"resources":{"authors":{"fields":{"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"name":{"capabilities":{"create":[],"filter":["equals"],"select":true,"update":[]},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"create":{"count":true,"many":true,"one":true},"delete":{"count":true,"first":true,"many":true,"unique":true},"find":{"count":true,"first":true,"many":true,"unique":true},"update":{"count":true,"first":true,"many":true,"unique":true},"upsert":{"unique":true}}},"books":{"fields":{"authorId":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}},"label":{"capabilities":{"select":true},"kind":"scalar","lifecycle":["COMPUTED_ON_READ","VIRTUAL"],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"find":{"many":true}}},"samples":{"fields":{"amount":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"decimal"}},"at":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"datetime"}},"blob":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"bytes"}},"count":{"capabilities":{"create":[],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"bigint"}},"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{"create":{"one":true},"find":{"unique":true}}},"secrets":{"fields":{"id":{"capabilities":{"filter":["equals"],"select":true},"kind":"scalar","lifecycle":[],"list":false,"nullable":false,"type":{"scalar":"string"}}},"identifiers":[["id"]],"operations":{}}},"scalars":{"bigint":{"builtin":true},"bytes":{"builtin":true},"datetime":{"builtin":true},"decimal":{"builtin":true},"string":{"builtin":true}},"transactions":"interactive"}';
|
|
176
|
+
export const CORPUS_NONE_CLIENT_CONTRACT_HASH = "sha256:5e99809294a2aae922b635e9a44b464b1b6e03359a54d30420292c62676a7ce5";
|
|
170
177
|
export const CORPUS_AUTHOR = { id: "a1", name: "Ada" };
|
|
171
178
|
export const CORPUS_NOBODY = "nobody";
|
|
172
179
|
export const CORPUS_SAMPLE = {
|
|
173
180
|
id: "s1",
|
|
174
181
|
count: 9007199254740993n,
|
|
175
182
|
at: new Date("2026-10-05T12:34:56.789Z"),
|
|
176
|
-
amount: new
|
|
183
|
+
amount: new AvDecimal("12.50"),
|
|
177
184
|
blob: new Uint8Array([255, 0, 16]),
|
|
178
185
|
};
|
|
179
186
|
const CONFLICTS = {
|
|
@@ -190,7 +197,7 @@ function corpusAnswer(operation) {
|
|
|
190
197
|
const id = whereIdOf(operation.arguments);
|
|
191
198
|
const conflict = id === undefined ? undefined : CONFLICTS[id];
|
|
192
199
|
if (conflict !== undefined) {
|
|
193
|
-
throw new
|
|
200
|
+
throw new AvError(conflict, `The corpus Adapter answered ${conflict}.`);
|
|
194
201
|
}
|
|
195
202
|
if (id === CORPUS_TRIGGER.adapterThrows) {
|
|
196
203
|
throw new Error("the corpus Adapter failed");
|
|
@@ -245,8 +252,10 @@ export async function createCorpusFramework(contractHash) {
|
|
|
245
252
|
: { transaction: failingCommit(fake.transaction) }),
|
|
246
253
|
});
|
|
247
254
|
const framework = await createFramework({
|
|
248
|
-
|
|
255
|
+
api: CORPUS_ENTRYPOINT,
|
|
249
256
|
adapter,
|
|
257
|
+
resources: CORPUS_RESOURCES,
|
|
258
|
+
application: CORPUS_APPLICATION_CONFIG,
|
|
250
259
|
client: CORPUS_CLIENT_CONFIG,
|
|
251
260
|
});
|
|
252
261
|
return { framework, adapterCalls: () => adapter.operations.length };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AvProtocolDriver } from "./protocol-driver.js";
|
|
2
2
|
/**
|
|
3
|
-
* `runAvProtocolConformance(driver)`
|
|
3
|
+
* `runAvProtocolConformance(driver)` drives the protocol fixture corpus
|
|
4
4
|
* through one host.
|
|
5
5
|
*
|
|
6
6
|
* Every row a host answers is driven; rows whose `client.outcome` is
|
|
@@ -12,17 +12,17 @@ import type { AvProtocolDriver } from "./protocol-driver.js";
|
|
|
12
12
|
* held to the row's `server` block:
|
|
13
13
|
*
|
|
14
14
|
* - `status`, exactly;
|
|
15
|
-
* - `code`: the body is a framework envelope carrying that code
|
|
15
|
+
* - `code`: the body is a framework envelope carrying that code or, for
|
|
16
16
|
* `null`, is none;
|
|
17
17
|
* - `causeKeys`: the exact sorted key set of `cause`;
|
|
18
18
|
* - `issues`: each issue's `code` and `path`, in order;
|
|
19
19
|
* - `adapterCalls`: the operations the corpus Adapter was handed, exactly;
|
|
20
20
|
* - `responseHeaders`: each named header present (names matched without
|
|
21
|
-
* regard to case) with exactly that value
|
|
21
|
+
* regard to case) with exactly that value; a host may add its own;
|
|
22
22
|
* - `bodyBytes`: the body text, exactly.
|
|
23
23
|
*
|
|
24
24
|
* Resolves one result per driven row, in corpus order, each with the
|
|
25
|
-
* mismatches found
|
|
25
|
+
* mismatches found (none when the host conforms). A driver that throws is a
|
|
26
26
|
* mismatch of its row, not a rejection of the run. Works with any test
|
|
27
27
|
* runner: assert on the results with whatever runner you use.
|
|
28
28
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvFramework } from "@aventara/core";
|
|
2
2
|
/**
|
|
3
3
|
* The server side of the protocol fixture corpus (`AvProtocolFixtures`), for
|
|
4
4
|
* a client or host author who serves it through their own stack: a Framework
|
|
@@ -19,11 +19,11 @@ export declare const AvProtocolCorpus: {
|
|
|
19
19
|
* the corpus's `transactions: "none"` contract for that contract's hash (the
|
|
20
20
|
* `/_transactions` absence rows send it), the main one for anything else.
|
|
21
21
|
*/
|
|
22
|
-
readonly framework: (contractHash?: string) => Promise<
|
|
22
|
+
readonly framework: (contractHash?: string) => Promise<AvFramework>;
|
|
23
23
|
/** Where the corpus Framework is mounted; every row's `path` is relative to it. */
|
|
24
24
|
readonly entrypoint: string;
|
|
25
25
|
/**
|
|
26
|
-
* The hash of the main corpus ClientContract
|
|
26
|
+
* The hash of the main corpus ClientContract: what a client generated against
|
|
27
27
|
* it sends as `Aventara-Contract-Hash`, and what `GET /_contract` names.
|
|
28
28
|
*/
|
|
29
29
|
readonly clientContractHash: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvFramework } from "@aventara/core";
|
|
2
2
|
import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/protocol";
|
|
3
3
|
/**
|
|
4
4
|
* One host under test, as `runAvProtocolConformance` drives it: given the
|
|
@@ -7,7 +7,7 @@ import type { AvProtocolRequest, AvProtocolResponse } from "@aventara/core/proto
|
|
|
7
7
|
* and body text, as an `AvProtocolResponse`). The run calls `close` once per
|
|
8
8
|
* row, after its one request.
|
|
9
9
|
*/
|
|
10
|
-
export type AvProtocolDriver = (framework:
|
|
10
|
+
export type AvProtocolDriver = (framework: AvFramework) => Promise<{
|
|
11
11
|
readonly send: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
|
|
12
12
|
readonly close: () => Promise<void>;
|
|
13
13
|
}>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvOperationCode, AvValidationCode } from "@aventara/core";
|
|
2
2
|
import type { AvProtocolRequest } from "@aventara/core/protocol";
|
|
3
3
|
/**
|
|
4
4
|
* One row of the protocol fixture corpus: the HTTP request a host receives
|
|
@@ -25,11 +25,11 @@ export type AvProtocolFixture = {
|
|
|
25
25
|
*/
|
|
26
26
|
readonly status: number;
|
|
27
27
|
/** `null`: the host produces no framework envelope at all. */
|
|
28
|
-
readonly code:
|
|
28
|
+
readonly code: AvOperationCode | null;
|
|
29
29
|
/** The exact, sorted key set of `cause`. */
|
|
30
30
|
readonly causeKeys?: readonly string[];
|
|
31
31
|
readonly issues?: readonly {
|
|
32
|
-
readonly code:
|
|
32
|
+
readonly code: AvValidationCode;
|
|
33
33
|
readonly path?: readonly (string | number)[];
|
|
34
34
|
}[];
|
|
35
35
|
/** Always stated: `0` is an assertion, not a default. */
|
|
@@ -47,6 +47,6 @@ export type AvProtocolFixture = {
|
|
|
47
47
|
*/
|
|
48
48
|
readonly outcome: "resolve" | "framework-error" | "transport-error";
|
|
49
49
|
/** Present iff `outcome` is `"framework-error"`. */
|
|
50
|
-
readonly code?:
|
|
50
|
+
readonly code?: AvOperationCode;
|
|
51
51
|
};
|
|
52
52
|
};
|
|
@@ -347,7 +347,8 @@ const TRANSACTIONS = [
|
|
|
347
347
|
refused("method: GET on /_transactions → 405 A2012 with Allow: POST", planRequest("corpus-tx-get", [AUTHORS_FIND_MANY], { method: "GET" }), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST" }),
|
|
348
348
|
];
|
|
349
349
|
const STALE_HASH = `sha256:${"0".repeat(64)}`;
|
|
350
|
-
const
|
|
350
|
+
const RESOURCE_UNAVAILABLE = "The requested Resource is not available.";
|
|
351
|
+
const OPERATION_UNAVAILABLE = "The requested operation is not available on this Resource.";
|
|
351
352
|
const absent = (label, request, status, code, cause) => ({
|
|
352
353
|
label: `absent: ${label}`,
|
|
353
354
|
request,
|
|
@@ -371,7 +372,7 @@ const absentRequest = (path, requestId, headers = {}, method = "POST") => {
|
|
|
371
372
|
return { ...base, method, path, headers: { ...base.headers, ...headers } };
|
|
372
373
|
};
|
|
373
374
|
const unavailableOperation = (resource, operation, key) => ({
|
|
374
|
-
message:
|
|
375
|
+
message: OPERATION_UNAVAILABLE,
|
|
375
376
|
issues: [
|
|
376
377
|
{
|
|
377
378
|
path: [key],
|
|
@@ -381,7 +382,7 @@ const unavailableOperation = (resource, operation, key) => ({
|
|
|
381
382
|
],
|
|
382
383
|
});
|
|
383
384
|
const unavailableResource = (resource) => ({
|
|
384
|
-
message:
|
|
385
|
+
message: RESOURCE_UNAVAILABLE,
|
|
385
386
|
issues: [
|
|
386
387
|
{
|
|
387
388
|
code: "V1005",
|