@aventara/testing 1.0.0-rc.1 → 1.0.0-rc.11

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.
@@ -1,4 +1,4 @@
1
- import { CORPUS_AUTHOR, CORPUS_CLIENT_CONTRACT_DOCUMENT, CORPUS_CLIENT_CONTRACT_HASH, CORPUS_NOBODY, CORPUS_NONE_CLIENT_CONTRACT_HASH, CORPUS_TRIGGER, } from "./corpus-contract.fixture.js";
1
+ import { CORPUS_ACCESS_CLIENT_CONTRACT_HASH, CORPUS_AUTHOR, CORPUS_CACHE_CLIENT_CONTRACT_HASH, CORPUS_CLIENT_CONTRACT_DOCUMENT, CORPUS_CLIENT_CONTRACT_HASH, CORPUS_NOBODY, CORPUS_NONE_CLIENT_CONTRACT_HASH, CORPUS_TRIGGER, } from "./corpus-contract.fixture.js";
2
2
  const OPERATION_REQUEST = {
3
3
  method: "POST",
4
4
  path: "/_resources/authors/find/many",
@@ -212,6 +212,7 @@ const contractServed = (label, requestId, headers = {}) => ({
212
212
  ...answeredHeaders(requestId),
213
213
  ETag: CORPUS_ETAG,
214
214
  "Cache-Control": "no-cache",
215
+ "X-Content-Type-Options": "nosniff",
215
216
  },
216
217
  bodyBytes: CORPUS_CLIENT_CONTRACT_DOCUMENT,
217
218
  },
@@ -228,6 +229,7 @@ const contractNotModified = (label, requestId, ifNoneMatch) => ({
228
229
  "Aventara-Request-Id": requestId,
229
230
  ETag: CORPUS_ETAG,
230
231
  "Cache-Control": "no-cache",
232
+ "X-Content-Type-Options": "nosniff",
231
233
  },
232
234
  bodyBytes: "",
233
235
  },
@@ -837,6 +839,480 @@ const CORRELATION = [
837
839
  ["an id with a space", "a b"],
838
840
  ].map(([what, id]) => answered(`correlation: ${what} is not adopted, and never refused → 200 A1000`, corpusRequest("find/many", {}, id), 200, 1, { data: [CORPUS_AUTHOR], code: "A1000", cause: null }, { "Content-Type": "application/json" })),
839
841
  ];
842
+ const canRequest = (requestId, questions, overrides = {}) => {
843
+ const base = corpusRequest("", { questions }, requestId);
844
+ return {
845
+ ...base,
846
+ path: "/_access/can",
847
+ headers: {
848
+ ...base.headers,
849
+ "Aventara-Contract-Hash": CORPUS_ACCESS_CLIENT_CONTRACT_HASH,
850
+ },
851
+ ...overrides,
852
+ };
853
+ };
854
+ const AUTHOR_ROW = { where: { id: CORPUS_AUTHOR.id } };
855
+ const QUESTIONS_INVALID = "Operation arguments failed framework validation.";
856
+ const ACCESS_QUESTIONS = [
857
+ answered("can: three questions in one request, one answer each → 200 A1000", canRequest("corpus-can-three", [
858
+ { resource: "authors", action: "update", ...AUTHOR_ROW },
859
+ { resource: "authors", action: "delete", ...AUTHOR_ROW },
860
+ { resource: "authors", action: "read" },
861
+ ]), 200, 1, { data: [true, false, true], code: "A1000", cause: null }),
862
+ answered("can: a row that does not exist answers false, never A2003", canRequest("corpus-can-missing", [
863
+ {
864
+ resource: "authors",
865
+ action: "update",
866
+ where: { id: CORPUS_NOBODY },
867
+ },
868
+ ]), 200, 1, { data: [false], code: "A1000", cause: null }),
869
+ answered("can: an operation the client is not offered answers false", canRequest("corpus-can-unoffered", [
870
+ { resource: "secrets", action: "read" },
871
+ { resource: "samples", action: "delete" },
872
+ ]), 200, 0, { data: [false, false], code: "A1000", cause: null }),
873
+ answered("can: an AvError the engine throws answers false", canRequest("corpus-can-engine-refuses", [
874
+ { resource: "books", action: "read" },
875
+ { resource: "authors", action: "read" },
876
+ ]), 200, 0, { data: [false, true], code: "A1000", cause: null }),
877
+ answered("can: no questions, no answers → 200 A1000", canRequest("corpus-can-none", []), 200, 0, { data: [], code: "A1000", cause: null }),
878
+ absent('/_access/can without access → 404 A2002 at ["questions"]', {
879
+ ...canRequest("corpus-absent-can", []),
880
+ headers: {
881
+ ...canRequest("corpus-absent-can", []).headers,
882
+ "Aventara-Contract-Hash": CORPUS_CLIENT_CONTRACT_HASH,
883
+ },
884
+ }, 404, "A2002", {
885
+ message: "Access questions are not available on the active Contract.",
886
+ issues: [
887
+ {
888
+ code: "V1006",
889
+ path: ["questions"],
890
+ message: "Access questions are not available on the active Contract.",
891
+ },
892
+ ],
893
+ }),
894
+ refused("method: GET on /_access/can → 405 A2012 with Allow: POST", canRequest("corpus-can-get", [], { method: "GET" }), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST" }),
895
+ answered("can: an unknown and a client-hidden Resource → 404 A2001 for the batch", canRequest("corpus-can-unknown", [
896
+ { resource: "authors", action: "read" },
897
+ { resource: "nope", action: "read" },
898
+ { resource: "hiddenRes", action: "read" },
899
+ ]), 404, 0, {
900
+ data: null,
901
+ code: "A2001",
902
+ cause: {
903
+ message: "The requested Resource is not available.",
904
+ issues: [
905
+ {
906
+ code: "V1005",
907
+ path: ["questions", 1, "resource"],
908
+ message: 'Resource "nope" is not available in the active Contract.',
909
+ },
910
+ {
911
+ code: "V1005",
912
+ path: ["questions", 2, "resource"],
913
+ message: 'Resource "hiddenRes" is not available in the active Contract.',
914
+ },
915
+ ],
916
+ },
917
+ }),
918
+ answered('can: a malformed question → 422 A2004 at ["questions", i, …]', canRequest("corpus-can-malformed", [
919
+ { resource: "authors", action: "refund" },
920
+ { resource: "authors", action: "read", where: { name: "Ada" } },
921
+ { resource: "authors", action: "create", ...AUTHOR_ROW },
922
+ ]), 422, 0, {
923
+ data: null,
924
+ code: "A2004",
925
+ cause: {
926
+ message: QUESTIONS_INVALID,
927
+ issues: [
928
+ {
929
+ code: "V1002",
930
+ path: ["questions", 0, "action"],
931
+ message: 'action must be one of "read", "create", "update" or "delete".',
932
+ },
933
+ {
934
+ code: "V1007",
935
+ path: ["questions", 1, "where"],
936
+ message: 'Unique selector must exactly match one active identifier on Resource "authors".',
937
+ },
938
+ {
939
+ code: "V1001",
940
+ path: ["questions", 2, "where"],
941
+ message: "A create question names no row: it takes no where.",
942
+ },
943
+ ],
944
+ },
945
+ }),
946
+ answered("can: more questions than maxListLimit → 422 A2009", canRequest("corpus-can-over-limit", Array.from({ length: 251 }, () => ({
947
+ resource: "authors",
948
+ action: "read",
949
+ }))), 422, 0, {
950
+ data: null,
951
+ code: "A2009",
952
+ cause: {
953
+ message: QUESTIONS_INVALID,
954
+ issues: [
955
+ {
956
+ code: "V1014",
957
+ path: ["questions"],
958
+ message: "Question count exceeds maxListLimit (250).",
959
+ },
960
+ ],
961
+ },
962
+ }),
963
+ answered("can: the engine throws → 500 A3000", canRequest("corpus-can-engine-throws", [
964
+ { resource: "samples", action: "read" },
965
+ ]), 500, 0, {
966
+ data: null,
967
+ code: "A3000",
968
+ cause: { message: "Internal framework error." },
969
+ }),
970
+ answered("can: the Adapter fails reading the row → 500 A3001", canRequest("corpus-can-adapter-throws", [
971
+ {
972
+ resource: "authors",
973
+ action: "update",
974
+ where: { id: CORPUS_TRIGGER.adapterThrows },
975
+ },
976
+ ]), 500, 1, {
977
+ data: null,
978
+ code: "A3001",
979
+ cause: { message: "Adapter execution failed." },
980
+ }),
981
+ ];
982
+ const setEntry = (resource, operation, args = {}) => {
983
+ const [family, variant] = operation.split(".");
984
+ return { resource, family, variant, args };
985
+ };
986
+ const setRequest = (requestId, operations, headers = {}, overrides = {}) => {
987
+ const base = corpusRequest("", { operations }, requestId);
988
+ return {
989
+ ...base,
990
+ path: "/_all",
991
+ headers: { ...base.headers, ...headers },
992
+ ...overrides,
993
+ };
994
+ };
995
+ const countEntries = (count) => Object.fromEntries(Array.from({ length: count }, (_, index) => [
996
+ `k${index}`,
997
+ setEntry("authors", "find.count"),
998
+ ]));
999
+ const NDJSON = "application/x-ndjson";
1000
+ const SET_INVALID = "Operation Set failed framework validation.";
1001
+ const OVER_MAX_ALL = {
1002
+ data: null,
1003
+ code: "A2009",
1004
+ cause: {
1005
+ message: "Operation count exceeds the active Contract limit.",
1006
+ issues: [
1007
+ {
1008
+ code: "V1014",
1009
+ path: ["operations"],
1010
+ message: "Operation count exceeds maxAll (10).",
1011
+ },
1012
+ ],
1013
+ },
1014
+ };
1015
+ const AUTHOR_LIST = {
1016
+ data: [CORPUS_AUTHOR],
1017
+ code: "A1000",
1018
+ cause: null,
1019
+ };
1020
+ const AUTHOR_MISSING = {
1021
+ data: null,
1022
+ code: "A2003",
1023
+ cause: { message: NO_MATCH },
1024
+ };
1025
+ const OPERATION_SETS = [
1026
+ answered("all: reads and writes in one request → 200 A1000, each key its single route's envelope", setRequest("corpus-all-mixed", {
1027
+ list: setEntry("authors", "find.many"),
1028
+ total: setEntry("authors", "find.count"),
1029
+ missing: setEntry("authors", "find.unique", {
1030
+ where: { id: CORPUS_NOBODY },
1031
+ }),
1032
+ created: setEntry("authors", "create.one", { data: DATA }),
1033
+ denied: setEntry("authors", "find.first", {
1034
+ where: { id: CORPUS_TRIGGER.guardDenies },
1035
+ }),
1036
+ hidden: setEntry("hiddenRes", "find.many"),
1037
+ }), 200, 4, {
1038
+ data: {
1039
+ list: AUTHOR_LIST,
1040
+ total: { data: 2, code: "A1000", cause: null },
1041
+ missing: AUTHOR_MISSING,
1042
+ created: { data: CORPUS_AUTHOR, code: "A1002", cause: null },
1043
+ denied: {
1044
+ data: null,
1045
+ code: "A4001",
1046
+ cause: { message: "Operation denied by a framework guard." },
1047
+ },
1048
+ hidden: {
1049
+ data: null,
1050
+ code: "A2001",
1051
+ cause: {
1052
+ message: RESOURCE_UNAVAILABLE,
1053
+ issues: [
1054
+ {
1055
+ code: "V1005",
1056
+ path: ["resource"],
1057
+ message: 'Resource "hiddenRes" is not available in the active Contract.',
1058
+ },
1059
+ ],
1060
+ },
1061
+ },
1062
+ },
1063
+ code: "A1000",
1064
+ cause: null,
1065
+ }),
1066
+ {
1067
+ label: "all: __proto__ and constructor are plain keys",
1068
+ request: {
1069
+ ...setRequest("corpus-all-hostile-keys", {}),
1070
+ body: {
1071
+ kind: "raw",
1072
+ content: '{"operations":{"__proto__":{"resource":"authors","family":"find","variant":"count","args":{}},"constructor":{"resource":"authors","family":"find","variant":"count","args":{}}}}',
1073
+ },
1074
+ },
1075
+ server: {
1076
+ status: 200,
1077
+ code: "A1000",
1078
+ adapterCalls: 2,
1079
+ responseHeaders: answeredHeaders("corpus-all-hostile-keys"),
1080
+ bodyBytes: '{"data":{"__proto__":{"data":2,"code":"A1000","cause":null},"constructor":{"data":2,"code":"A1000","cause":null}},"code":"A1000","cause":null}',
1081
+ },
1082
+ client: { outcome: "resolve" },
1083
+ },
1084
+ {
1085
+ label: "all: streamed (Accept: application/x-ndjson) → 200, one line per entry in settle order",
1086
+ request: setRequest("corpus-all-streamed", {
1087
+ list: setEntry("authors", "find.many"),
1088
+ missing: setEntry("authors", "find.unique", {
1089
+ where: { id: CORPUS_NOBODY },
1090
+ }),
1091
+ }, { Accept: NDJSON }),
1092
+ server: {
1093
+ status: 200,
1094
+ code: null,
1095
+ adapterCalls: 2,
1096
+ responseHeaders: {
1097
+ "Content-Type": NDJSON,
1098
+ "Cache-Control": "no-store",
1099
+ "X-Accel-Buffering": "no",
1100
+ "Aventara-Request-Id": "corpus-all-streamed",
1101
+ },
1102
+ bodyBytes: `${JSON.stringify({ key: "missing", envelope: AUTHOR_MISSING })}\n${JSON.stringify({ key: "list", envelope: AUTHOR_LIST })}\n`,
1103
+ },
1104
+ client: { outcome: "resolve" },
1105
+ },
1106
+ answered("all: refused whole while streamed is plain JSON at its status (more than maxAll → 422 A2009)", setRequest("corpus-all-streamed-refused", countEntries(11), {
1107
+ Accept: NDJSON,
1108
+ }), 422, 0, OVER_MAX_ALL),
1109
+ answered("all: more entries than limits.maxAll → 422 A2009 / V1014", setRequest("corpus-all-over-max", countEntries(11)), 422, 0, OVER_MAX_ALL),
1110
+ answered("all: no entries → 422 A2004 / V1004", setRequest("corpus-all-empty", {}), 422, 0, {
1111
+ data: null,
1112
+ code: "A2004",
1113
+ cause: {
1114
+ message: SET_INVALID,
1115
+ issues: [
1116
+ {
1117
+ code: "V1004",
1118
+ path: ["operations"],
1119
+ message: "Value outside accepted range.",
1120
+ },
1121
+ ],
1122
+ },
1123
+ }),
1124
+ answered("all: a key that is no identifier → 422 A2004 / V1001", setRequest("corpus-all-bad-key", {
1125
+ "a-b": setEntry("authors", "find.count"),
1126
+ }), 422, 0, {
1127
+ data: null,
1128
+ code: "A2004",
1129
+ cause: {
1130
+ message: SET_INVALID,
1131
+ issues: [
1132
+ {
1133
+ code: "V1001",
1134
+ path: ["operations"],
1135
+ message: "Every operation key must be an identifier.",
1136
+ },
1137
+ ],
1138
+ },
1139
+ }),
1140
+ answered("all: an entry with a fifth member → 422 A2004 / V1001 at it", setRequest("corpus-all-entry-member", {
1141
+ a: { ...setEntry("authors", "find.count"), extra: 1 },
1142
+ }), 422, 0, {
1143
+ data: null,
1144
+ code: "A2004",
1145
+ cause: {
1146
+ message: SET_INVALID,
1147
+ issues: [
1148
+ {
1149
+ code: "V1001",
1150
+ path: ["operations", "a", "extra"],
1151
+ message: 'Property "extra" is not valid here.',
1152
+ },
1153
+ ],
1154
+ },
1155
+ }),
1156
+ answered("all: a body member beside operations → 422 A2004 / V1001 at it", {
1157
+ ...setRequest("corpus-all-body-member", {}),
1158
+ body: {
1159
+ kind: "raw",
1160
+ content: JSON.stringify({
1161
+ operations: { a: setEntry("authors", "find.count") },
1162
+ extra: 1,
1163
+ }),
1164
+ },
1165
+ }, 422, 0, {
1166
+ data: null,
1167
+ code: "A2004",
1168
+ cause: {
1169
+ message: SET_INVALID,
1170
+ issues: [
1171
+ {
1172
+ code: "V1001",
1173
+ path: ["extra"],
1174
+ message: 'Property "extra" is not valid here.',
1175
+ },
1176
+ ],
1177
+ },
1178
+ }),
1179
+ refused("all: a stale hash → 409 A2005, nothing runs", setRequest("corpus-all-stale", { a: setEntry("authors", "find.count") }, { "Aventara-Contract-Hash": STALE_HASH }), 409, "A2005", STALE_CLIENT),
1180
+ refused("method: GET on /_all → 405 A2012 with Allow: POST", setRequest("corpus-all-get", { a: setEntry("authors", "find.count") }, {}, { method: "GET" }), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST" }),
1181
+ ];
1182
+ const CACHE_V = encodeURIComponent(CORPUS_CACHE_CLIENT_CONTRACT_HASH);
1183
+ const plainQ = (args) => encodeURIComponent(JSON.stringify(args));
1184
+ const DEFLATED_WHERE_A1 = "q1Yqz0gtSlWyqlbKTFGyUko0VKqtBQA";
1185
+ const DEFLATE_BOMB = `7cEBDQAwCAMwK89k3A06CN4RQttO5ecB${"A".repeat(2582)}DnZBY`;
1186
+ const cachedRead = (route, query, requestId, extra = {}, method = "GET") => ({
1187
+ method,
1188
+ path: `/_resources/${route}`,
1189
+ ...(query === undefined ? {} : { query }),
1190
+ headers: { "Aventara-Request-Id": requestId, ...extra },
1191
+ body: { kind: "raw", content: "" },
1192
+ });
1193
+ const cacheQuery = (args) => `q=${plainQ(args)}&v=${CACHE_V}`;
1194
+ const GET_HEADERS = {
1195
+ "Referrer-Policy": "no-referrer",
1196
+ "X-Content-Type-Options": "nosniff",
1197
+ };
1198
+ const GET_REFUSED = { "Cache-Control": "no-store", ...GET_HEADERS };
1199
+ const USER_CACHE = {
1200
+ "Cache-Control": "private, max-age=60",
1201
+ Vary: "Authorization, Cookie",
1202
+ ...GET_HEADERS,
1203
+ };
1204
+ const AUTHOR_LIST_ETAG = '"Hw1Lk7KqP84xBk693Mz4B2LG8SHbThNf7TUw48xZUGI"';
1205
+ const AUTHOR_ETAG = '"bY0J9tlkShjFaZpC9KaS3xX3YaoRzKji-HAb69ASAsQ"';
1206
+ const EVERYONE_CACHE = {
1207
+ "Cache-Control": "public, max-age=300",
1208
+ ...GET_HEADERS,
1209
+ };
1210
+ const BOOK_LIST_ETAG = '"boybbH17GgkJBSByRCwm0xJj3tbWWrPd2O0BPrDCNSo"';
1211
+ const BOOK_LIST = {
1212
+ data: [{ authorId: CORPUS_AUTHOR.id, id: "b1", label: "book:b1" }],
1213
+ code: "A1000",
1214
+ cause: null,
1215
+ };
1216
+ const AUTHOR_FOUND = {
1217
+ data: CORPUS_AUTHOR,
1218
+ code: "A1000",
1219
+ cause: null,
1220
+ };
1221
+ const CACHED_READS = [
1222
+ answered('cache: GET for "user", plain q → 200 private, Vary, ETag', cachedRead("authors/find/many", cacheQuery({}), "corpus-cache-user"), 200, 1, AUTHOR_LIST, {
1223
+ ...answeredHeaders("corpus-cache-user"),
1224
+ ...USER_CACHE,
1225
+ ETag: AUTHOR_LIST_ETAG,
1226
+ }),
1227
+ answered("cache: GET, compressed q → the plain form's answer", cachedRead("authors/find/unique", `q=${DEFLATED_WHERE_A1}&v=${CACHE_V}`, "corpus-cache-deflated"), 200, 1, AUTHOR_FOUND, {
1228
+ ...answeredHeaders("corpus-cache-deflated"),
1229
+ ...USER_CACHE,
1230
+ ETag: AUTHOR_ETAG,
1231
+ }),
1232
+ {
1233
+ label: "cache: If-None-Match its ETag → 304, no body, the same validators",
1234
+ request: cachedRead("authors/find/many", cacheQuery({}), "corpus-cache-304", { "If-None-Match": AUTHOR_LIST_ETAG }),
1235
+ server: {
1236
+ status: 304,
1237
+ code: null,
1238
+ adapterCalls: 1,
1239
+ responseHeaders: {
1240
+ "Aventara-Request-Id": "corpus-cache-304",
1241
+ ...USER_CACHE,
1242
+ ETag: AUTHOR_LIST_ETAG,
1243
+ },
1244
+ bodyBytes: "",
1245
+ },
1246
+ client: { outcome: "resolve" },
1247
+ },
1248
+ answered('cache: GET for "everyone" → 200 public, no Vary, no request id', cachedRead("books/find/many", cacheQuery({}), "corpus-cache-everyone"), 200, 1, BOOK_LIST, {
1249
+ "Content-Type": "application/json",
1250
+ ...EVERYONE_CACHE,
1251
+ ETag: BOOK_LIST_ETAG,
1252
+ }),
1253
+ {
1254
+ label: "cache: If-None-Match weak W/ on a public answer → 304",
1255
+ request: cachedRead("books/find/many", cacheQuery({}), "corpus-cache-public-304", { "If-None-Match": `W/${BOOK_LIST_ETAG}` }),
1256
+ server: {
1257
+ status: 304,
1258
+ code: null,
1259
+ adapterCalls: 1,
1260
+ responseHeaders: { ...EVERYONE_CACHE, ETag: BOOK_LIST_ETAG },
1261
+ bodyBytes: "",
1262
+ },
1263
+ client: { outcome: "resolve" },
1264
+ },
1265
+ answered('cache: GET for "anonymous", no Cookie or Authorization → public', cachedRead("authors/find/first", cacheQuery({}), "corpus-cache-anonymous"), 200, 1, AUTHOR_FOUND, {
1266
+ "Content-Type": "application/json",
1267
+ "Cache-Control": "public, max-age=30",
1268
+ Vary: "Authorization, Cookie",
1269
+ ...GET_HEADERS,
1270
+ ETag: AUTHOR_ETAG,
1271
+ }),
1272
+ answered('cache: GET for "anonymous" with a Cookie → private', cachedRead("authors/find/first", cacheQuery({}), "corpus-cache-signed-in", {
1273
+ Cookie: "session=s1",
1274
+ }), 200, 1, AUTHOR_FOUND, {
1275
+ ...answeredHeaders("corpus-cache-signed-in"),
1276
+ "Cache-Control": "private, max-age=30",
1277
+ Vary: "Authorization, Cookie",
1278
+ ...GET_HEADERS,
1279
+ ETag: AUTHOR_ETAG,
1280
+ }),
1281
+ refused("cache: a query parameter beside q and v → 400 A2000, no-store", cachedRead("authors/find/many", `${cacheQuery({})}&utm=1`, "corpus-cache-extra-param"), 400, "A2000", MALFORMED, GET_REFUSED),
1282
+ refused("cache: q that is no JSON object → 400 A2000, no-store", cachedRead("authors/find/many", cacheQuery([1]), "corpus-cache-q-list"), 400, "A2000", MALFORMED, GET_REFUSED),
1283
+ refused("cache: a compressed q inflating past maxRequestBytes → 413 A2010, no-store", cachedRead("authors/find/many", `q=${DEFLATE_BOMB}&v=${CACHE_V}`, "corpus-cache-bomb"), 413, "A2010", "The request body exceeds the maximum request size.", GET_REFUSED),
1284
+ refused("cache: a stale v → 409 A2005, no-store", cachedRead("authors/find/many", `q=${plainQ({})}&v=${encodeURIComponent(STALE_HASH)}`, "corpus-cache-stale"), 409, "A2005", STALE_CLIENT, GET_REFUSED),
1285
+ {
1286
+ ...refused("cache: a unique miss → 404 A2003, no-store", cachedRead("authors/find/unique", cacheQuery({ where: { id: CORPUS_NOBODY } }), "corpus-cache-miss"), 404, "A2003", NO_MATCH, GET_REFUSED),
1287
+ server: {
1288
+ status: 404,
1289
+ code: "A2003",
1290
+ causeKeys: ["message"],
1291
+ adapterCalls: 1,
1292
+ responseHeaders: {
1293
+ ...answeredHeaders("corpus-cache-miss"),
1294
+ ...GET_REFUSED,
1295
+ },
1296
+ bodyBytes: JSON.stringify({
1297
+ data: null,
1298
+ code: "A2003",
1299
+ cause: { message: NO_MATCH },
1300
+ }),
1301
+ },
1302
+ },
1303
+ refused("cache: the pipelines run on a GET: a guard refusal → 403 A4001, no-store", cachedRead("authors/find/many", cacheQuery({ where: { id: CORPUS_TRIGGER.guardDenies } }), "corpus-cache-denied"), 403, "A4001", "Operation denied by a framework guard.", GET_REFUSED),
1304
+ refused("method: GET on a write → 405 A2012 with Allow: POST, no-store", cachedRead("authors/create/one", cacheQuery({ data: DATA }), "corpus-cache-get-write"), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST", ...GET_REFUSED }),
1305
+ refused("method: GET on a read with no cache stage → 405 A2012 with Allow: POST, no-store", cachedRead("samples/find/unique", cacheQuery({ where: { id: "s1" } }), "corpus-cache-get-uncached"), 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "POST", ...GET_REFUSED }),
1306
+ refused("method: PUT on a cached read → 405 A2012 with Allow: GET, POST", {
1307
+ ...corpusRequest("find/many", {}, "corpus-cache-put", { method: "PUT" }),
1308
+ headers: {
1309
+ "Content-Type": "application/json",
1310
+ "Aventara-Protocol-Version": "1",
1311
+ "Aventara-Contract-Hash": CORPUS_CACHE_CLIENT_CONTRACT_HASH,
1312
+ "Aventara-Request-Id": "corpus-cache-put",
1313
+ },
1314
+ }, 405, "A2012", "The HTTP method is not allowed on this framework route.", { Allow: "GET, POST" }),
1315
+ ];
840
1316
  export const AvProtocolFixtures = [
841
1317
  ...CONTRACT_DISCOVERY,
842
1318
  ...SUCCESS_STATUSES,
@@ -849,6 +1325,9 @@ export const AvProtocolFixtures = [
849
1325
  ...ABSENCE,
850
1326
  ...TRANSACTIONS,
851
1327
  ...TRANSACTION_REFERENCES,
1328
+ ...ACCESS_QUESTIONS,
1329
+ ...OPERATION_SETS,
1330
+ ...CACHED_READS,
852
1331
  ...INTERNAL,
853
1332
  ...CONFLICT,
854
1333
  ...WIRE_SCALARS,
@@ -4,7 +4,7 @@ import type { AvProtocolFixture } from "./protocol-fixture.js";
4
4
  * its bytes: `prefix + repeat × times + suffix`. The one oversized-body row
5
5
  * would otherwise be a megabyte of `x`.
6
6
  */
7
- export type ProtocolFixtureContentRecipe = {
7
+ type ProtocolFixtureContentRecipe = {
8
8
  readonly prefix?: string;
9
9
  readonly repeat: string;
10
10
  readonly times: number;
@@ -41,3 +41,4 @@ export declare function expandProtocolFixtureRow(row: ProtocolFixtureJsonRow): A
41
41
  * in corpus order, so a change to the corpus is a change to its own lines.
42
42
  */
43
43
  export declare function protocolFixturesJson(): Promise<string>;
44
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { AVENTARA_PROTOCOL_VERSION } from "@aventara/core/internal";
2
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";
3
+ import { CORPUS_ACCESS_CLIENT_CONTRACT_HASH, CORPUS_CACHE_CLIENT_CONTRACT_HASH, CORPUS_CLIENT_CONTRACT_HASH, CORPUS_ENTRYPOINT, CORPUS_NONE_CLIENT_CONTRACT_HASH, createCorpusFramework, } from "./corpus-contract.fixture.js";
4
4
  import { AvProtocolFixtures } from "./protocol-fixtures.js";
5
5
  const RECIPE_MINIMUM = 1024;
6
6
  function recipeOf(text) {
@@ -78,6 +78,14 @@ export async function protocolFixturesJson() {
78
78
  hash: CORPUS_NONE_CLIENT_CONTRACT_HASH,
79
79
  document: await contractDocument(CORPUS_NONE_CLIENT_CONTRACT_HASH),
80
80
  },
81
+ {
82
+ hash: CORPUS_ACCESS_CLIENT_CONTRACT_HASH,
83
+ document: await contractDocument(CORPUS_ACCESS_CLIENT_CONTRACT_HASH),
84
+ },
85
+ {
86
+ hash: CORPUS_CACHE_CLIENT_CONTRACT_HASH,
87
+ document: await contractDocument(CORPUS_CACHE_CLIENT_CONTRACT_HASH),
88
+ },
81
89
  ];
82
90
  const head = JSON.stringify({
83
91
  protocolVersion: AVENTARA_PROTOCOL_VERSION,