-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreadme.test.ts
More file actions
211 lines (189 loc) · 5.36 KB
/
readme.test.ts
File metadata and controls
211 lines (189 loc) · 5.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import { describe, expect, test } from "bun:test";
import { Router } from ".";
// Oversimplified model of a request and response; use whatever definition is appropriate
// for your application and platform, but the `method`, `path`, and `status` attributes
// are required for routing.
type Request = { method: string; path: string | null; body?: string };
type Response = { status: number; body?: string };
// Instantiate the routing utilities
const router = new Router<Response>({
response: (responseOptions: { status: number }) => ({
status: responseOptions.status,
}),
});
// A typical sort of HTTP application server
class Server {
// The constituent parts of the server: The website and API
website = new Website();
api = new Api();
// The server's main entry point (called by the tests below). The website gets to live
// the top level for vanity, and the API is relegated to a subdirectory.
fetch: (request: Request) => Promise<Response> = router.define({
staticContents: [
...this.website.routes,
{ name: "api", server: this.api.fetch },
],
});
}
class Website {
// The "cart" pages are a subsite, defined below.
cart = new CartWebsite();
// A static list of three routes: The empty string is the home page, "about" is another
// page at the top level, and "cart" routes to a subsite.
get routes(): Array<{
name: string;
server: (request: Request) => Promise<Response>;
}> {
return [
{
name: "",
server: router.define({
resource: [{ method: "GET", server: this.getHome }],
}),
},
{
name: "about",
server: router.define({
resource: [{ method: "GET", server: this.getAbout }],
}),
},
{ name: "cart", server: this.cart.fetch },
];
}
// Trivial values representing web pages in this example application
async getHome() {
return { status: 200, body: "Home" };
}
async getAbout() {
return { status: 200, body: "About" };
}
}
// Pages that Website nests within the "cart" directory
class CartWebsite {
fetch: (request: Request) => Promise<Response> = router.define({
staticContents: [
{
name: "view",
server: router.define({
resource: [{ method: "GET", server: this.getView }],
}),
},
{
name: "checkout",
server: router.define({
resource: [{ method: "GET", server: this.getCheckout }],
}),
},
],
});
async getView() {
return { status: 200, body: "Cart" };
}
async getCheckout() {
return { status: 200, body: "Checkout" };
}
}
class Api {
book = new BookApi();
fetch: (request: Request) => Promise<Response> = router.define({
staticContents: [
{
name: "v1",
server: router.define({
staticContents: [
{
name: "books-by-id",
// books-by-id is a *dynamic* directory; its contents are not statically enumerated.
server: router.define({
dynamicContents: async ({ path, method, parent, body }) =>
// We would at this point insert some check that the requested book ID exists
// before handing off the control flow to BookApi. This would also be a reasonable
// place to insert authorization for this resource, or perhaps fetch some
// additional information about the book.
this.book.fetch({ path, method, body, bookId: parent }),
}),
},
],
}),
},
],
});
}
class BookApi {
fetch: (request: Request & { bookId: string }) => Promise<Response> =
router.define({
staticContents: [
{
name: "synopsis",
server: router.define({
resource: [
{ method: "GET", server: this.getSynopsis },
{ method: "POST", server: this.postSynopsis },
],
}),
},
],
});
async getSynopsis(request: Request & { bookId: string }): Promise<Response> {
// This would pull from a database or something
return { status: 200, body: `Synopsis of book ${request.bookId}` };
}
async postSynopsis(request: Request & { bookId: string }): Promise<Response> {
// This would write to a database or something
return {
status: 200,
body: `Synopsis of book ${request.bookId}:\n${request.body ?? ""}`,
};
}
}
describe("Server example", () => {
const server = new Server();
test("root path", async () => {
expect(await server.fetch({ method: "GET", path: "" })).toEqual({
status: 200,
body: "Home",
});
});
test("top level path", async () => {
expect(await server.fetch({ method: "GET", path: "about" })).toEqual({
status: 200,
body: "About",
});
});
test("unsupported method", async () => {
expect(await server.fetch({ method: "POST", path: "about" })).toEqual({
status: 405,
});
});
test("not found", async () => {
expect(await server.fetch({ method: "GET", path: "teapot" })).toEqual({
status: 404,
});
});
test("nested paths", async () => {
expect(await server.fetch({ method: "GET", path: "cart/view" })).toEqual({
status: 200,
body: "Cart",
});
expect(
await server.fetch({ method: "GET", path: "cart/checkout" }),
).toEqual({ status: 200, body: "Checkout" });
});
test("dynamic directory", async () => {
expect(
await server.fetch({
method: "GET",
path: "api/v1/books-by-id/4/synopsis",
}),
).toEqual({ status: 200, body: "Synopsis of book 4" });
});
test("post", async () => {
expect(
await server.fetch({
method: "POST",
path: "api/v1/books-by-id/5/synopsis",
body: "abc",
}),
).toEqual({ status: 200, body: "Synopsis of book 5:\nabc" });
});
});