@aventara/testing 0.1.0-pilot.3 → 1.0.0-rc.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.
@@ -322,20 +322,20 @@ const TRANSACTIONS = [
322
322
  ]), 422, "A2004", [{ code: "V1019", path: ["operations", 1, "resource"] }], 1),
323
323
  ...["scope", "origin"].map((key) => invalid(`transaction — a plan node carrying "${key}" cannot smuggle an origin → 422 A2004 / V1001`, planRequest(`corpus-tx-smuggle-${key}`, [
324
324
  { ...AUTHORS_FIND_MANY, [key]: "application" },
325
- ]), 422, "A2004", [{ code: "V1001", path: [key] }], 0)),
325
+ ]), 422, "A2004", [{ code: "V1001", path: ["operations", 0, key] }], 0)),
326
326
  invalid("a client-hidden field — single route", corpusRequest("find/many", { select: ["id", "secret"] }, "corpus-r1-field"), 422, "A2004", [{ code: "V1005", path: ["arguments", "select", 1] }]),
327
327
  invalid("a client-hidden field — /_transactions", planRequest("corpus-r1-field-tx", [
328
328
  AUTHORS_FIND_MANY,
329
329
  planNode("authors", "find.many", { select: ["id", "secret"] }, "fp1:1t8vSfPuohv1N5MIX8ccNw"),
330
- ]), 422, "A2004", [{ code: "V1005", path: ["arguments", "select", 1] }], 1),
330
+ ]), 422, "A2004", [{ code: "V1005", path: ["operations", 1, "args", "select", 1] }], 1),
331
331
  invalid("a client-restricted variant — single route", {
332
332
  ...corpusRequest("", {}, "corpus-r1-variant"),
333
333
  path: "/_resources/secrets/find/many",
334
- }, 404, "A2002", [{ code: "V1006", path: ["operation"] }]),
334
+ }, 404, "A2002", [{ code: "V1006", path: ["family"] }]),
335
335
  invalid("a client-restricted variant — /_transactions", planRequest("corpus-r1-variant-tx", [
336
336
  AUTHORS_FIND_MANY,
337
337
  planNode("secrets", "find.many", {}, "fp1:KegI5OcquDHi8z39VH11EQ"),
338
- ]), 404, "A2002", [{ code: "V1006", path: ["operation"] }], 1),
338
+ ]), 404, "A2002", [{ code: "V1006", path: ["operations", 1, "family"] }], 1),
339
339
  invalid("a client-hidden Resource — single route", {
340
340
  ...corpusRequest("", {}, "corpus-r1-resource"),
341
341
  path: "/_resources/hiddenRes/find/many",
@@ -343,7 +343,7 @@ const TRANSACTIONS = [
343
343
  invalid("a client-hidden Resource — /_transactions", planRequest("corpus-r1-resource-tx", [
344
344
  AUTHORS_FIND_MANY,
345
345
  planNode("hiddenRes", "find.many", {}, "fp1:UY6pLVLKM2LaIMnfQa0zGw"),
346
- ]), 404, "A2001", [{ code: "V1005", path: ["resource"] }], 1),
346
+ ]), 404, "A2001", [{ code: "V1005", path: ["operations", 1, "resource"] }], 1),
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)}`;
@@ -370,11 +370,11 @@ const absentRequest = (path, requestId, headers = {}, method = "POST") => {
370
370
  const base = corpusRequest("", {}, requestId);
371
371
  return { ...base, method, path, headers: { ...base.headers, ...headers } };
372
372
  };
373
- const unavailableOperation = (resource, operation) => ({
373
+ const unavailableOperation = (resource, operation, key) => ({
374
374
  message: VALIDATION_FAILED,
375
375
  issues: [
376
376
  {
377
- path: ["operation"],
377
+ path: [key],
378
378
  code: "V1006",
379
379
  message: `Operation "${operation}" is not available on Resource "${resource}".`,
380
380
  },
@@ -391,7 +391,7 @@ const unavailableResource = (resource) => ({
391
391
  ],
392
392
  });
393
393
  const ABSENCE = [
394
- absent("an unadvertised operation, current hash → 404 A2002", absentRequest("/_resources/books/delete/many", "corpus-absent-op"), 404, "A2002", unavailableOperation("books", "delete.many")),
394
+ absent("an unadvertised operation, current hash → 404 A2002", absentRequest("/_resources/books/delete/many", "corpus-absent-op"), 404, "A2002", unavailableOperation("books", "delete.many", "family")),
395
395
  absent("an unadvertised operation, stale hash → 409 A2005 (regenerate)", absentRequest("/_resources/books/delete/many", "corpus-absent-stale", {
396
396
  "Aventara-Contract-Hash": STALE_HASH,
397
397
  }), 409, "A2005", {
@@ -404,7 +404,7 @@ const ABSENCE = [
404
404
  "Aventara-Protocol-Version": "2",
405
405
  }), 400, "A2006", { message: "Aventara protocol version does not match the server." }),
406
406
  absent("an unknown Resource → 404 A2001", absentRequest("/_resources/nope/find/many", "corpus-absent-resource"), 404, "A2001", unavailableResource("nope")),
407
- absent("a Resource with operations: {} → 404 A2002", absentRequest("/_resources/secrets/find/many", "corpus-absent-empty"), 404, "A2002", unavailableOperation("secrets", "find.many")),
407
+ absent("a Resource with operations: {} → 404 A2002", absentRequest("/_resources/secrets/find/many", "corpus-absent-empty"), 404, "A2002", unavailableOperation("secrets", "find.many", "family")),
408
408
  absent("a client-hidden Resource → 404 A2001", absentRequest("/_resources/hiddenRes/find/many", "corpus-absent-hidden"), 404, "A2001", unavailableResource("hiddenRes")),
409
409
  absent('/_transactions under transactions "none" → 404 A2002 at ["operations"]', {
410
410
  ...planRequest("corpus-absent-tx-none", [AUTHORS_FIND_MANY]),
@@ -476,6 +476,35 @@ const CREATE_ID = planNode("authors", "create.one", { data: { name: CORPUS_AUTHO
476
476
  const CREATE_ID_PRINT = CREATE_ID.fingerprint;
477
477
  const FIRST_NOBODY = planNode("authors", "find.first", { where: { id: CORPUS_NOBODY } }, "fp1:qNR9mD7_iVygNtmuuXjymw");
478
478
  const ARGUMENTS_FAILED = "Operation arguments failed framework validation.";
479
+ const TRANSACTION_REFERENCES = [
480
+ answered("transaction — a unique where and a filter reference an earlier step's result → 200 A1009", planRequest("corpus-tx-ref-where", [
481
+ CREATE_ID,
482
+ planNode("authors", "find.unique", { where: { id: ref(0, CREATE_ID_PRINT, "id") } }, "fp1:hKbxzChGakplMZvnpEa1_w"),
483
+ planNode("authors", "find.many", { where: { OR: [{ id: { equals: ref(0, CREATE_ID_PRINT, "id") } }] } }, "fp1:4LK9l_j8Skjk4iU_wSoRzQ"),
484
+ ]), 200, 3, {
485
+ data: [{ id: CORPUS_AUTHOR.id }, CORPUS_AUTHOR, [CORPUS_AUTHOR]],
486
+ code: "A1009",
487
+ cause: null,
488
+ }),
489
+ answered("argument: a plan reference in a where to an operation index no earlier step has → 422 A2007 / V1016", planRequest("corpus-arg-ref-where-index", [
490
+ CREATE_ID,
491
+ planNode("authors", "find.unique", { where: { id: ref(5, CREATE_ID_PRINT, "id") } }, "fp1:VS6Zk71avpYzuC90z8qzrQ"),
492
+ ]), 422, 0, {
493
+ data: null,
494
+ code: "A2007",
495
+ cause: {
496
+ message: ARGUMENTS_FAILED,
497
+ issues: [
498
+ {
499
+ code: "V1016",
500
+ path: ["operations", 1, "args", "where", "id", "$ref", "operation"],
501
+ message: "Transaction reference operation index is invalid.",
502
+ },
503
+ ],
504
+ operation: 1,
505
+ },
506
+ }),
507
+ ];
479
508
  const ARGUMENT_VALIDATION = [
480
509
  answered("argument: an unknown field in where → 422 A2004 / V1005", corpusRequest("find/many", { where: { nope: { equals: "x" } } }, "corpus-arg-field"), 422, 0, {
481
510
  data: null,
@@ -818,6 +847,7 @@ export const AvProtocolFixtures = [
818
847
  ...METHOD,
819
848
  ...ABSENCE,
820
849
  ...TRANSACTIONS,
850
+ ...TRANSACTION_REFERENCES,
821
851
  ...INTERNAL,
822
852
  ...CONFLICT,
823
853
  ...WIRE_SCALARS,
@@ -0,0 +1,43 @@
1
+ import type { AvProtocolFixture } from "./protocol-fixture.js";
2
+ /**
3
+ * A long run of one character in a request body, spelled as a recipe instead of
4
+ * its bytes: `prefix + repeat × times + suffix`. The one oversized-body row
5
+ * would otherwise be a megabyte of `x`.
6
+ */
7
+ export type ProtocolFixtureContentRecipe = {
8
+ readonly prefix?: string;
9
+ readonly repeat: string;
10
+ readonly times: number;
11
+ readonly suffix?: string;
12
+ };
13
+ /** A row as the JSON file holds it: a raw body's content may be a recipe. */
14
+ export type ProtocolFixtureJsonRow = Omit<AvProtocolFixture, "request"> & {
15
+ readonly request: Omit<AvProtocolFixture["request"], "body"> & {
16
+ readonly body: {
17
+ readonly kind: "raw";
18
+ readonly content: string | ProtocolFixtureContentRecipe;
19
+ } | {
20
+ readonly kind: "parsed";
21
+ readonly value: unknown;
22
+ };
23
+ };
24
+ };
25
+ /** The file: the protocol version, the corpus's mount point, its ClientContracts, every row. */
26
+ export type ProtocolFixturesJson = {
27
+ readonly protocolVersion: number;
28
+ readonly entrypoint: string;
29
+ readonly clientContractHash: string;
30
+ /** Each ClientContract a row targets: its hash and its exact `GET /_contract` bytes. */
31
+ readonly contracts: readonly {
32
+ readonly hash: string;
33
+ readonly document: string;
34
+ }[];
35
+ readonly rows: readonly ProtocolFixtureJsonRow[];
36
+ };
37
+ /** A row of the file, expanded back to the TS row it was written from. */
38
+ export declare function expandProtocolFixtureRow(row: ProtocolFixtureJsonRow): AvProtocolFixture;
39
+ /**
40
+ * `fixtures/protocol-v1.json`, as text: one line per ClientContract and per row,
41
+ * in corpus order, so a change to the corpus is a change to its own lines.
42
+ */
43
+ export declare function protocolFixturesJson(): Promise<string>;
@@ -0,0 +1,89 @@
1
+ import { AVENTARA_PROTOCOL_VERSION } from "@aventara/core/internal";
2
+ import { AvProtocol } from "@aventara/core/protocol";
3
+ import { CORPUS_CLIENT_CONTRACT_HASH, CORPUS_ENTRYPOINT, CORPUS_NONE_CLIENT_CONTRACT_HASH, createCorpusFramework, } from "./corpus-contract.fixture.js";
4
+ import { AvProtocolFixtures } from "./protocol-fixtures.js";
5
+ const RECIPE_MINIMUM = 1024;
6
+ function recipeOf(text) {
7
+ let best = { start: 0, length: 0 };
8
+ let start = 0;
9
+ for (let index = 1; index <= text.length; index += 1) {
10
+ if (index === text.length || text[index] !== text[start]) {
11
+ if (index - start > best.length) {
12
+ best = { start, length: index - start };
13
+ }
14
+ start = index;
15
+ }
16
+ }
17
+ if (best.length < RECIPE_MINIMUM) {
18
+ return undefined;
19
+ }
20
+ const prefix = text.slice(0, best.start);
21
+ const suffix = text.slice(best.start + best.length);
22
+ return {
23
+ ...(prefix === "" ? {} : { prefix }),
24
+ repeat: text[best.start],
25
+ times: best.length,
26
+ ...(suffix === "" ? {} : { suffix }),
27
+ };
28
+ }
29
+ function encodeRow(row) {
30
+ const { body } = row.request;
31
+ if (body.kind === "parsed") {
32
+ return { ...row, request: { ...row.request, body } };
33
+ }
34
+ if (typeof body.content !== "string") {
35
+ throw new Error(`protocol fixture "${row.label}": a body of bytes has no JSON spelling`);
36
+ }
37
+ return {
38
+ ...row,
39
+ request: {
40
+ ...row.request,
41
+ body: { kind: "raw", content: recipeOf(body.content) ?? body.content },
42
+ },
43
+ };
44
+ }
45
+ export function expandProtocolFixtureRow(row) {
46
+ const { body } = row.request;
47
+ if (body.kind === "parsed" || typeof body.content === "string") {
48
+ return row;
49
+ }
50
+ const { prefix = "", repeat, times, suffix = "" } = body.content;
51
+ return {
52
+ ...row,
53
+ request: {
54
+ ...row.request,
55
+ body: { kind: "raw", content: prefix + repeat.repeat(times) + suffix },
56
+ },
57
+ };
58
+ }
59
+ async function contractDocument(contractHash) {
60
+ const { framework } = await createCorpusFramework(contractHash);
61
+ return AvProtocol.bind(framework).encodeContract({
62
+ method: "GET",
63
+ path: "/_contract",
64
+ headers: {},
65
+ body: { kind: "raw", content: "" },
66
+ }).body;
67
+ }
68
+ export async function protocolFixturesJson() {
69
+ if (AVENTARA_PROTOCOL_VERSION !== 1) {
70
+ throw new Error(`protocol-v1.json describes protocol version 1, not ${AVENTARA_PROTOCOL_VERSION}`);
71
+ }
72
+ const contracts = [
73
+ {
74
+ hash: CORPUS_CLIENT_CONTRACT_HASH,
75
+ document: await contractDocument(CORPUS_CLIENT_CONTRACT_HASH),
76
+ },
77
+ {
78
+ hash: CORPUS_NONE_CLIENT_CONTRACT_HASH,
79
+ document: await contractDocument(CORPUS_NONE_CLIENT_CONTRACT_HASH),
80
+ },
81
+ ];
82
+ const head = JSON.stringify({
83
+ protocolVersion: AVENTARA_PROTOCOL_VERSION,
84
+ entrypoint: CORPUS_ENTRYPOINT,
85
+ clientContractHash: CORPUS_CLIENT_CONTRACT_HASH,
86
+ }).slice(0, -1);
87
+ const lines = (items) => items.map((item) => JSON.stringify(item)).join(",\n");
88
+ return `${head},"contracts":[\n${lines(contracts)}\n],"rows":[\n${lines(AvProtocolFixtures.map(encodeRow))}\n]}\n`;
89
+ }