@afora/core 0.0.9 → 0.0.12
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 +62 -189
- package/dist/adapters.js +31 -55
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.js +67 -0
- package/dist/core/application/providers.js +108 -0
- package/dist/core/application/registry.js +23 -0
- package/dist/core/application/schema.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +23 -3
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +10 -2
- package/dist/core/blueprint/blueprint-plugin.js +7 -0
- package/dist/core/blueprint/blueprint.js +146 -0
- package/dist/core/blueprint/callable.js +13 -0
- package/dist/core/blueprint/exceptions.js +20 -0
- package/dist/core/blueprint/is-blueprint.js +4 -0
- package/dist/core/blueprint/list.js +19 -0
- package/dist/core/blueprint/optional.js +69 -0
- package/dist/core/blueprint/record-field.js +14 -0
- package/dist/core/blueprint/slice.js +127 -0
- package/dist/core/blueprint/types/blueprint.interface.d.ts +16 -0
- package/dist/core/blueprint/variant.js +72 -0
- package/dist/core/command/command.js +428 -0
- package/dist/core/command/exceptions.js +30 -0
- package/dist/core/command/types/command.interface.d.ts +10 -1
- package/dist/core/entity/abstract-entity.d.ts +19 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.js +209 -0
- package/dist/core/entity/event.d.ts +39 -0
- package/dist/core/entity/event.js +80 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/version.js +13 -0
- package/dist/core/environment/environment.d.ts +26 -3
- package/dist/core/environment/environment.js +71 -0
- package/dist/core/event-policy/event-policy.js +76 -0
- package/dist/core/event-policy/exceptions.js +12 -0
- package/dist/core/event-policy/publish.js +98 -0
- package/dist/core/event-policy/publishers.js +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
- package/dist/core/exception/enums/exception-layers.js +15 -0
- package/dist/core/exception/exception.d.ts +15 -0
- package/dist/core/exception/exception.js +128 -0
- package/dist/core/exception/exported.js +49 -0
- package/dist/core/exception/guard.d.ts +20 -1
- package/dist/core/exception/guard.js +16 -0
- package/dist/core/exception/types/exception.interface.d.ts +13 -0
- package/dist/core/json.js +19 -0
- package/dist/core/logger/exceptions.js +7 -0
- package/dist/core/logger/logger.d.ts +14 -1
- package/dist/core/logger/logger.js +178 -0
- package/dist/core/openapi/exceptions.js +7 -0
- package/dist/core/openapi/openapi.js +132 -0
- package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/{index-y280ew0k.js → core/output/http.js} +35 -80
- package/dist/core/output/openapi-sink.d.ts +10 -2
- package/dist/{index-hg1z2s2v.js → core/output/openapi-sink.js} +60 -544
- package/dist/core/output/output.js +192 -0
- package/dist/core/output/problem.js +30 -0
- package/dist/core/output/select.js +28 -0
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +5 -1
- package/dist/core/output/types/output.interface.d.ts +24 -0
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +8 -1
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.js +209 -0
- package/dist/core/property/enums/property-states.js +8 -0
- package/dist/core/property/exceptions.js +12 -0
- package/dist/core/property/input.js +10 -0
- package/dist/core/property/is-property.js +3 -0
- package/dist/core/property/lineage.js +8 -0
- package/dist/core/property/property.d.ts +27 -0
- package/dist/core/property/property.js +46 -0
- package/dist/core/property/refine.js +103 -0
- package/dist/core/record/abstract-record.d.ts +2 -1
- package/dist/core/record/abstract-record.js +307 -0
- package/dist/core/record/exceptions.js +49 -0
- package/dist/core/record/marks.js +7 -0
- package/dist/core/record/mask.js +60 -0
- package/dist/core/record/name.js +23 -0
- package/dist/core/record/plugin-catalog.js +115 -0
- package/dist/core/record/plugin.js +198 -0
- package/dist/core/record/record.js +117 -0
- package/dist/core/record/types/record.interface.d.ts +5 -0
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.js +254 -0
- package/dist/core/repository/plan.js +27 -0
- package/dist/core/repository/provider.js +29 -0
- package/dist/core/repository/query.js +153 -0
- package/dist/core/repository/references.js +196 -0
- package/dist/core/repository/repository.d.ts +22 -2
- package/dist/core/repository/repository.js +720 -0
- package/dist/core/repository/state.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +20 -8
- package/dist/core/repository/types/schema.interface.d.ts +8 -2
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +9 -0
- package/dist/core/schema/schema.js +542 -0
- package/dist/core/standard/standard-schema.js +1 -0
- package/dist/core/standard/standard.js +23 -0
- package/dist/core/symbols/demo.d.ts +4 -0
- package/dist/core/symbols/demo.js +1 -0
- package/dist/core/symbols/kind.js +1 -0
- package/dist/core/symbols/meta.js +1 -0
- package/dist/core/symbols/state.d.ts +5 -0
- package/dist/core/symbols/state.js +1 -0
- package/dist/core/tracing/exceptions.d.ts +4 -0
- package/dist/core/tracing/exceptions.js +7 -0
- package/dist/core/tracing/tracing.d.ts +72 -0
- package/dist/core/tracing/tracing.js +101 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.js +65 -0
- package/dist/core/use-case/exceptions.d.ts +5 -1
- package/dist/core/use-case/exceptions.js +48 -0
- package/dist/core/use-case/plugin-catalog.js +68 -0
- package/dist/core/use-case/plugin.js +133 -0
- package/dist/core/use-case/route.js +200 -0
- package/dist/core/use-case/scope.d.ts +5 -0
- package/dist/core/use-case/scope.js +4 -0
- package/dist/core/use-case/steps/describe.d.ts +5 -1
- package/dist/core/use-case/steps/describe.js +24 -0
- package/dist/core/use-case/steps/references.js +105 -0
- package/dist/core/use-case/steps/run.js +139 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +27 -8
- package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
- package/dist/core/use-case/steps/validate.js +368 -0
- package/dist/core/use-case/target.js +100 -0
- package/dist/core/use-case/types/route.interface.d.ts +25 -5
- package/dist/core/use-case/use-case.d.ts +77 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +5 -0
- package/dist/experimental.js +18 -161
- package/dist/index.d.ts +7 -5
- package/dist/index.js +213 -367
- package/dist/std/blueprint-plugin.js +42 -0
- package/dist/std/blueprint.d.ts +21 -0
- package/dist/std/blueprint.js +3 -0
- package/dist/std/command.js +3 -0
- package/dist/std/entity.d.ts +20 -1
- package/dist/std/entity.js +30 -0
- package/dist/std/event-policies.js +3 -0
- package/dist/std/properties/contact.d.ts +3 -0
- package/dist/std/properties/contact.js +29 -0
- package/dist/std/properties/enum.js +26 -0
- package/dist/std/properties/exceptions.js +14 -0
- package/dist/std/properties/headless.js +45 -0
- package/dist/std/properties/identity.js +28 -0
- package/dist/std/properties/json.js +161 -0
- package/dist/std/properties/password.js +79 -0
- package/dist/std/properties/primitives.d.ts +4 -0
- package/dist/std/properties/primitives.js +28 -0
- package/dist/std/properties/time.js +55 -0
- package/dist/std/record-plugins.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +5 -1
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.js +3 -1435
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +12 -3
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +17 -5
- package/dist/index-3aae5jfq.js +0 -2997
- package/dist/index-ny3as79r.js +0 -4567
- package/dist/index-xegv6a1p.js +0 -4291
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
blueprintOf,
|
|
3
|
+
commandOf,
|
|
4
|
+
ExceptionLayers,
|
|
5
|
+
exceptionOf,
|
|
6
|
+
outputOf,
|
|
7
|
+
recordOf,
|
|
8
|
+
useCaseOf
|
|
9
|
+
} from "../index.js";
|
|
10
|
+
const Note = recordOf(blueprintOf((ctx) => ({ title: ctx.string, secret: ctx.password })));
|
|
11
|
+
const Rejected = exceptionOf({
|
|
12
|
+
layer: ExceptionLayers.UseCase,
|
|
13
|
+
name: "ContractRejected",
|
|
14
|
+
message: "rejected"
|
|
15
|
+
});
|
|
16
|
+
const Echo = blueprintOf((ctx) => ({
|
|
17
|
+
id: ctx.optional.string,
|
|
18
|
+
page: ctx.optional.integer,
|
|
19
|
+
flag: ctx.optional.boolean,
|
|
20
|
+
locale: ctx.optional.string,
|
|
21
|
+
session: ctx.optional.string,
|
|
22
|
+
title: ctx.optional.string,
|
|
23
|
+
caller: ctx.optional.string,
|
|
24
|
+
tenant: ctx.optional.string
|
|
25
|
+
}));
|
|
26
|
+
function operations() {
|
|
27
|
+
|
|
28
|
+
class Get extends useCaseOf({
|
|
29
|
+
name: "ContractGet",
|
|
30
|
+
blueprint: blueprintOf((ctx) => ({
|
|
31
|
+
id: ctx.string,
|
|
32
|
+
page: ctx.optional.integer,
|
|
33
|
+
flag: ctx.optional.boolean
|
|
34
|
+
})),
|
|
35
|
+
response: Echo,
|
|
36
|
+
route: { method: "get", path: "/items/{id}" }
|
|
37
|
+
}) {
|
|
38
|
+
async execute() {
|
|
39
|
+
return { ...this.input };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class Change extends useCaseOf({
|
|
44
|
+
name: "ContractChange",
|
|
45
|
+
blueprint: blueprintOf((ctx) => ({
|
|
46
|
+
id: ctx.string,
|
|
47
|
+
page: ctx.integer,
|
|
48
|
+
locale: ctx.optional.string,
|
|
49
|
+
session: ctx.optional.string,
|
|
50
|
+
title: ctx.string
|
|
51
|
+
})),
|
|
52
|
+
response: Echo,
|
|
53
|
+
context: blueprintOf((ctx) => ({ tenant: ctx.optional.string })),
|
|
54
|
+
errors: [Rejected],
|
|
55
|
+
route: {
|
|
56
|
+
method: "put",
|
|
57
|
+
path: "/items/{id}",
|
|
58
|
+
status: 201,
|
|
59
|
+
query: ["page"],
|
|
60
|
+
headers: { locale: "accept-language" },
|
|
61
|
+
cookie: { session: "sid" }
|
|
62
|
+
}
|
|
63
|
+
}) {
|
|
64
|
+
async execute() {
|
|
65
|
+
if (this.input.title === "reject")
|
|
66
|
+
throw new Rejected({ source: "x" });
|
|
67
|
+
if (this.input.title === "crash")
|
|
68
|
+
throw new Error("internal detail");
|
|
69
|
+
return { ...this.input, tenant: this.context.tenant };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class Remove extends useCaseOf({
|
|
74
|
+
name: "ContractRemove",
|
|
75
|
+
blueprint: blueprintOf((ctx) => ({ id: ctx.string })),
|
|
76
|
+
response: blueprintOf(() => ({})),
|
|
77
|
+
route: { method: "delete", path: "/items/{id}", status: 204 }
|
|
78
|
+
}) {
|
|
79
|
+
async execute() {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class Reveal extends useCaseOf({
|
|
85
|
+
name: "ContractReveal",
|
|
86
|
+
blueprint: blueprintOf(() => ({})),
|
|
87
|
+
response: blueprintOf(() => ({ note: Note })),
|
|
88
|
+
route: { method: "get", path: "/note" }
|
|
89
|
+
}) {
|
|
90
|
+
async execute() {
|
|
91
|
+
return { note: new Note({ title: "t", secret: await hashed() }) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class Mine extends useCaseOf({
|
|
96
|
+
name: "ContractMine",
|
|
97
|
+
blueprint: blueprintOf(() => ({})),
|
|
98
|
+
response: Echo,
|
|
99
|
+
context: blueprintOf((ctx) => ({ caller: ctx.string })),
|
|
100
|
+
route: { method: "get", path: "/mine" },
|
|
101
|
+
access: { scopes: ["items:read"], context: { caller: "subject" } }
|
|
102
|
+
}) {
|
|
103
|
+
async execute() {
|
|
104
|
+
return { caller: this.context.caller };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class Enter extends useCaseOf({
|
|
109
|
+
name: "ContractEnter",
|
|
110
|
+
blueprint: blueprintOf((ctx) => ({ title: ctx.string })),
|
|
111
|
+
response: blueprintOf((ctx) => ({
|
|
112
|
+
title: ctx.string,
|
|
113
|
+
token: ctx.string,
|
|
114
|
+
refreshToken: ctx.string
|
|
115
|
+
})),
|
|
116
|
+
route: {
|
|
117
|
+
method: "post",
|
|
118
|
+
path: "/enter",
|
|
119
|
+
setCookie: {
|
|
120
|
+
token: { name: "sid", secure: false },
|
|
121
|
+
refreshToken: { name: "rid", secure: false, path: "/refresh" }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}) {
|
|
125
|
+
async execute() {
|
|
126
|
+
return { title: this.input.title, token: "t 1", refreshToken: "" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return commandOf({
|
|
130
|
+
name: "Contract",
|
|
131
|
+
useCases: {
|
|
132
|
+
get: Get,
|
|
133
|
+
change: Change,
|
|
134
|
+
remove: Remove,
|
|
135
|
+
reveal: Reveal,
|
|
136
|
+
mine: Mine,
|
|
137
|
+
enter: Enter
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function hashed() {
|
|
142
|
+
return "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHRzb21lc2FsdA$4VbQ3OqJ7xk8Y0mG0iZ1c2k1m2Qe0sQeJ7xk8Y0mG0i";
|
|
143
|
+
}
|
|
144
|
+
const authenticator = (request) => {
|
|
145
|
+
const token = request.header("authorization");
|
|
146
|
+
if (token === "Bearer reader")
|
|
147
|
+
return { subject: "u1", scopes: ["items:read"] };
|
|
148
|
+
if (token === "Bearer other")
|
|
149
|
+
return { subject: "u2", scopes: [] };
|
|
150
|
+
return null;
|
|
151
|
+
};
|
|
152
|
+
export function httpProviderContract(name, settings, runner) {
|
|
153
|
+
const { describe, it, expect } = runner;
|
|
154
|
+
const origin = "http://contract.test";
|
|
155
|
+
const withServer = async (options, body, hooks = { authenticator }) => {
|
|
156
|
+
const server = outputOf({
|
|
157
|
+
name: "Contract",
|
|
158
|
+
commands: [operations()],
|
|
159
|
+
statuses: { ContractRejected: 409 },
|
|
160
|
+
pipe: [settings.sink(options, hooks)]
|
|
161
|
+
}).build();
|
|
162
|
+
try {
|
|
163
|
+
await body(async (method, path, init = {}) => {
|
|
164
|
+
const hasBody = init.body !== undefined;
|
|
165
|
+
const response = await settings.fetch(server, new Request(`${origin}${path}`, {
|
|
166
|
+
method,
|
|
167
|
+
headers: {
|
|
168
|
+
...hasBody ? { "content-type": "application/json" } : {},
|
|
169
|
+
...init.headers
|
|
170
|
+
},
|
|
171
|
+
...hasBody ? {
|
|
172
|
+
body: typeof init.body === "string" ? init.body : JSON.stringify(init.body)
|
|
173
|
+
} : {}
|
|
174
|
+
}));
|
|
175
|
+
const text = await response.text();
|
|
176
|
+
let json;
|
|
177
|
+
try {
|
|
178
|
+
json = text === "" ? undefined : JSON.parse(text);
|
|
179
|
+
} catch {
|
|
180
|
+
json = undefined;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
status: response.status,
|
|
184
|
+
type: (response.headers.get("content-type") ?? "").split(";")[0],
|
|
185
|
+
headers: response.headers,
|
|
186
|
+
text,
|
|
187
|
+
json
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
} finally {
|
|
191
|
+
await settings.close?.(server);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
describe(`${name}: HTTP provider contract`, () => {
|
|
195
|
+
it("serves the route a use case declares, path and query coerced", async () => {
|
|
196
|
+
await withServer({ basePath: "/api" }, async (send) => {
|
|
197
|
+
const found = await send("GET", "/api/items/a%20b?page=2&flag=true");
|
|
198
|
+
expect(found.status).toBe(200);
|
|
199
|
+
expect(found.type).toBe("application/json");
|
|
200
|
+
expect(found.json).toEqual({ id: "a b", page: 2, flag: true });
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
it("reads each key where the route places it, over the body", async () => {
|
|
204
|
+
await withServer({}, async (send) => {
|
|
205
|
+
const changed = await send("PUT", "/items/p?page=3", {
|
|
206
|
+
headers: { "accept-language": "pt-BR", cookie: "x=1; sid=s%201" },
|
|
207
|
+
body: { id: "from-body", page: 9, title: "T" }
|
|
208
|
+
});
|
|
209
|
+
expect(changed.status).toBe(201);
|
|
210
|
+
expect(changed.json).toEqual({
|
|
211
|
+
id: "p",
|
|
212
|
+
page: 3,
|
|
213
|
+
locale: "pt-BR",
|
|
214
|
+
session: "s 1",
|
|
215
|
+
title: "T"
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
it("fills the context from headers", async () => {
|
|
220
|
+
await withServer({ context: { headers: { tenant: "x-tenant" } } }, async (send) => {
|
|
221
|
+
const changed = await send("PUT", "/items/p?page=1", {
|
|
222
|
+
headers: { "x-tenant": "acme" },
|
|
223
|
+
body: { title: "T" }
|
|
224
|
+
});
|
|
225
|
+
expect(changed.json.tenant).toBe("acme");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
it("answers invalid input and invalid JSON with a 400 problem", async () => {
|
|
229
|
+
await withServer({}, async (send) => {
|
|
230
|
+
const invalid = await send("PUT", "/items/p?page=1", {
|
|
231
|
+
body: { title: 1 }
|
|
232
|
+
});
|
|
233
|
+
expect(invalid.status).toBe(400);
|
|
234
|
+
expect(invalid.type).toBe("application/problem+json");
|
|
235
|
+
const problem = invalid.json;
|
|
236
|
+
expect(problem.title).toBe("InvalidUseCaseInput");
|
|
237
|
+
expect(problem.issues.map((issue) => issue.path)).toEqual([["title"]]);
|
|
238
|
+
const notJson = await send("PUT", "/items/p?page=1", {
|
|
239
|
+
body: "{nope"
|
|
240
|
+
});
|
|
241
|
+
expect(notJson.status).toBe(400);
|
|
242
|
+
expect(notJson.json.title).toBe("InvalidRequestBody");
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
it("gives errors the status of the output, and hides internal ones", async () => {
|
|
246
|
+
await withServer({}, async (send) => {
|
|
247
|
+
const rejected = await send("PUT", "/items/p?page=1", {
|
|
248
|
+
body: { title: "reject" }
|
|
249
|
+
});
|
|
250
|
+
expect(rejected.status).toBe(409);
|
|
251
|
+
expect(rejected.type).toBe("application/problem+json");
|
|
252
|
+
const crashed = await send("PUT", "/items/p?page=1", {
|
|
253
|
+
body: { title: "crash" }
|
|
254
|
+
});
|
|
255
|
+
expect(crashed.status).toBe(500);
|
|
256
|
+
expect(crashed.text.includes("internal detail")).toBe(false);
|
|
257
|
+
});
|
|
258
|
+
await withServer({ debug: true }, async (send) => {
|
|
259
|
+
const crashed = await send("PUT", "/items/p?page=1", {
|
|
260
|
+
body: { title: "crash" }
|
|
261
|
+
});
|
|
262
|
+
expect(crashed.text.includes("internal detail")).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
it("answers a 204 without a body, and an unknown route with a 404 problem", async () => {
|
|
266
|
+
await withServer({}, async (send) => {
|
|
267
|
+
const removed = await send("DELETE", "/items/p");
|
|
268
|
+
expect(removed.status).toBe(204);
|
|
269
|
+
expect(removed.text).toBe("");
|
|
270
|
+
const unknown = await send("GET", "/nowhere");
|
|
271
|
+
expect(unknown.status).toBe(404);
|
|
272
|
+
expect(unknown.type).toBe("application/problem+json");
|
|
273
|
+
expect(unknown.json.title).toBe("NotFound");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
it("masks sensitive values", async () => {
|
|
277
|
+
await withServer({}, async (send) => {
|
|
278
|
+
const revealed = await send("GET", "/note");
|
|
279
|
+
expect(revealed.json).toEqual({
|
|
280
|
+
note: { title: "t", secret: "********" }
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
it("enforces the access of a use case", async () => {
|
|
285
|
+
await withServer({}, async (send) => {
|
|
286
|
+
const anonymous = await send("GET", "/mine");
|
|
287
|
+
expect(anonymous.status).toBe(401);
|
|
288
|
+
expect(anonymous.json.title).toBe("Unauthenticated");
|
|
289
|
+
const other = await send("GET", "/mine", {
|
|
290
|
+
headers: { authorization: "Bearer other" }
|
|
291
|
+
});
|
|
292
|
+
expect(other.status).toBe(403);
|
|
293
|
+
const reader = await send("GET", "/mine", {
|
|
294
|
+
headers: { authorization: "Bearer reader" }
|
|
295
|
+
});
|
|
296
|
+
expect(reader.status).toBe(200);
|
|
297
|
+
expect(reader.json).toEqual({ caller: "u1" });
|
|
298
|
+
});
|
|
299
|
+
await withServer({}, async (send) => {
|
|
300
|
+
const closed = await send("GET", "/mine", {
|
|
301
|
+
headers: { authorization: "Bearer reader" }
|
|
302
|
+
});
|
|
303
|
+
expect(closed.status).toBe(401);
|
|
304
|
+
}, {});
|
|
305
|
+
});
|
|
306
|
+
it("sends the cookies of a route, out of the body", async () => {
|
|
307
|
+
await withServer({}, async (send) => {
|
|
308
|
+
const entered = await send("POST", "/enter", { body: { title: "T" } });
|
|
309
|
+
expect(entered.status).toBe(200);
|
|
310
|
+
expect(entered.json).toEqual({ title: "T" });
|
|
311
|
+
expect(entered.headers.getSetCookie()).toEqual([
|
|
312
|
+
"sid=t%201; Path=/; HttpOnly; SameSite=Lax",
|
|
313
|
+
"rid=; Path=/refresh; HttpOnly; SameSite=Lax; Max-Age=0"
|
|
314
|
+
]);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
it("puts the request id on successes and problems", async () => {
|
|
318
|
+
await withServer({ requestId: true }, async (send) => {
|
|
319
|
+
const sent = await send("GET", "/items/a", {
|
|
320
|
+
headers: { "x-request-id": "abc-1" }
|
|
321
|
+
});
|
|
322
|
+
expect(sent.headers.get("x-request-id")).toBe("abc-1");
|
|
323
|
+
const problem = await send("GET", "/mine");
|
|
324
|
+
expect((problem.headers.get("x-request-id") ?? "").length).toBe(36);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
it("serves the document of the same routes", async () => {
|
|
328
|
+
await withServer({
|
|
329
|
+
basePath: "/api",
|
|
330
|
+
docs: { path: "/openapi.json", info: { title: "C", version: "1" } }
|
|
331
|
+
}, async (send) => {
|
|
332
|
+
const docs = await send("GET", "/openapi.json");
|
|
333
|
+
expect(docs.status).toBe(200);
|
|
334
|
+
expect(docs.type).toBe("application/json");
|
|
335
|
+
expect(Object.keys(docs.json.paths).sort()).toEqual([
|
|
336
|
+
"/api/enter",
|
|
337
|
+
"/api/items/{id}",
|
|
338
|
+
"/api/mine",
|
|
339
|
+
"/api/note"
|
|
340
|
+
]);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|