Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

Select the types of activity you want to include in your feed.

at lex 403 lines 9.9 kB view raw
1import type { LexiconDoc } from "@atp/lexicon"; 2import { 3 XrpcClient, 4 XRPCError, 5 XRPCInvalidResponseError, 6} from "./_xrpc-client.ts"; 7import * as xrpcServer from "../mod.ts"; 8import { closeServer, createServer } from "./_util.ts"; 9import { 10 assert, 11 assertEquals, 12 assertRejects, 13 assertStringIncludes, 14} from "@std/assert"; 15 16const UPSTREAM_LEXICONS: LexiconDoc[] = [ 17 { 18 lexicon: 1, 19 id: "io.example.upstreamInvalidResponse", 20 defs: { 21 main: { 22 type: "query", 23 output: { 24 encoding: "application/json", 25 schema: { 26 type: "object", 27 required: ["expectedValue"], 28 properties: { 29 expectedValue: { type: "string" }, 30 }, 31 }, 32 }, 33 }, 34 }, 35 }, 36]; 37 38const LEXICONS: LexiconDoc[] = [ 39 { 40 lexicon: 1, 41 id: "io.example.error", 42 defs: { 43 main: { 44 type: "query", 45 parameters: { 46 type: "params", 47 properties: { 48 which: { type: "string", default: "foo" }, 49 }, 50 }, 51 errors: [{ name: "Foo" }, { name: "Bar" }], 52 }, 53 }, 54 }, 55 { 56 lexicon: 1, 57 id: "io.example.throwFalsyValue", 58 defs: { 59 main: { 60 type: "query", 61 }, 62 }, 63 }, 64 { 65 lexicon: 1, 66 id: "io.example.query", 67 defs: { 68 main: { 69 type: "query", 70 }, 71 }, 72 }, 73 { 74 lexicon: 1, 75 id: "io.example.procedure", 76 defs: { 77 main: { 78 type: "procedure", 79 }, 80 }, 81 }, 82 { 83 lexicon: 1, 84 id: "io.example.invalidResponse", 85 defs: { 86 main: { 87 type: "query", 88 output: { 89 encoding: "application/json", 90 schema: { 91 type: "object", 92 required: ["expectedValue"], 93 properties: { 94 expectedValue: { type: "string" }, 95 }, 96 }, 97 }, 98 }, 99 }, 100 }, 101 { 102 lexicon: 1, 103 id: "io.example.invalidUpstreamResponse", 104 defs: { 105 main: { 106 type: "query", 107 }, 108 }, 109 }, 110]; 111 112const MISMATCHED_LEXICONS: LexiconDoc[] = [ 113 { 114 lexicon: 1, 115 id: "io.example.query", 116 defs: { 117 main: { 118 type: "procedure", 119 }, 120 }, 121 }, 122 { 123 lexicon: 1, 124 id: "io.example.procedure", 125 defs: { 126 main: { 127 type: "query", 128 }, 129 }, 130 }, 131 { 132 lexicon: 1, 133 id: "io.example.doesNotExist", 134 defs: { 135 main: { 136 type: "query", 137 }, 138 }, 139 }, 140]; 141 142let upstreamServer: ReturnType<typeof xrpcServer.createServer>; 143let upstreamS: Deno.HttpServer; 144let upstreamClient: XrpcClient; 145let server: ReturnType<typeof xrpcServer.createServer>; 146let s: Deno.HttpServer; 147let client: XrpcClient; 148let badClient: XrpcClient; 149 150Deno.test.beforeAll(async () => { 151 // Setup upstream server 152 upstreamServer = xrpcServer.createServer(UPSTREAM_LEXICONS, { 153 validateResponse: false, 154 }); // disable validateResponse to test client validation 155 upstreamServer.method("io.example.upstreamInvalidResponse", () => { 156 return { encoding: "json", body: { something: "else" } }; 157 }); 158 upstreamS = await createServer(upstreamServer); 159 const upstreamPort = (upstreamS as Deno.HttpServer & { port: number }).port; 160 upstreamClient = new XrpcClient( 161 `http://localhost:${upstreamPort}`, 162 UPSTREAM_LEXICONS, 163 ); 164 165 // Setup main server 166 server = xrpcServer.createServer(LEXICONS, { 167 validateResponse: false, 168 }); // disable validateResponse to test client validation 169 s = await createServer(server); 170 const port = (s as Deno.HttpServer & { port: number }).port; 171 172 server.method("io.example.error", (ctx: xrpcServer.HandlerContext) => { 173 if (ctx.params["which"] === "foo") { 174 throw new xrpcServer.InvalidRequestError("It was this one!", "Foo"); 175 } else if (ctx.params["which"] === "bar") { 176 return { status: 400, error: "Bar", message: "It was that one!" }; 177 } else { 178 return { status: 400 }; 179 } 180 }); 181 server.method("io.example.throwFalsyValue", () => { 182 throw ""; 183 }); 184 server.method("io.example.query", () => { 185 return undefined; 186 }); 187 // @ts-ignore We're intentionally giving the wrong response! -prf 188 server.method("io.example.invalidResponse", () => { 189 return { encoding: "application/json", body: { something: "else" } }; 190 }); 191 server.method("io.example.invalidUpstreamResponse", async () => { 192 await upstreamClient.call("io.example.upstreamInvalidResponse"); 193 return { 194 encoding: "json", 195 body: {}, 196 }; 197 }); 198 server.method("io.example.procedure", () => { 199 return undefined; 200 }); 201 202 client = new XrpcClient(`http://localhost:${port}`, LEXICONS); 203 badClient = new XrpcClient( 204 `http://localhost:${port}`, 205 MISMATCHED_LEXICONS, 206 ); 207}); 208 209Deno.test.afterAll(async () => { 210 await closeServer(s); 211 await closeServer(upstreamS); 212}); 213 214Deno.test("throws XRPCError for foo error", { 215 sanitizeOps: false, 216 sanitizeResources: false, 217}, async () => { 218 await assertRejects( 219 async () => { 220 await client.call("io.example.error", { 221 which: "foo", 222 }); 223 }, 224 XRPCError, 225 "It was this one!", 226 ); 227 228 const fooError = await client.call("io.example.error", { which: "foo" }) 229 .catch((e) => e); 230 assert(fooError instanceof XRPCError); 231 assert(!fooError.success); 232 assertEquals(fooError.error, "Foo"); 233}); 234 235Deno.test("throws XRPCError for bar error", { 236 sanitizeOps: false, 237 sanitizeResources: false, 238}, async () => { 239 await assertRejects( 240 async () => { 241 await client.call("io.example.error", { 242 which: "bar", 243 }); 244 }, 245 XRPCError, 246 "It was that one!", 247 ); 248 249 const barError = await client.call("io.example.error", { which: "bar" }) 250 .catch((e) => e); 251 assert(barError instanceof XRPCError); 252 assert(!barError.success); 253 assertEquals(barError.error, "Bar"); 254}); 255 256Deno.test("throws XRPCError for falsy value", { 257 sanitizeOps: false, 258 sanitizeResources: false, 259}, async () => { 260 await assertRejects( 261 async () => { 262 await client.call("io.example.throwFalsyValue"); 263 }, 264 XRPCError, 265 "Internal Server Error", 266 ); 267 268 const falsyError = await client.call("io.example.throwFalsyValue").catch( 269 (e) => e, 270 ); 271 assert(falsyError instanceof XRPCError); 272 assert(!falsyError.success); 273 assertEquals(falsyError.error, "InternalServerError"); 274}); 275 276Deno.test("throws XRPCError for other error type", { 277 sanitizeOps: false, 278 sanitizeResources: false, 279}, async () => { 280 await assertRejects( 281 async () => { 282 await client.call("io.example.error", { 283 which: "other", 284 }); 285 }, 286 XRPCError, 287 "Invalid Request", 288 ); 289 290 const otherError = await client.call("io.example.error", { 291 which: "other", 292 }).catch((e) => e); 293 assert(otherError instanceof XRPCError); 294 assert(!otherError.success); 295 assertEquals(otherError.error, "InvalidRequest"); 296}); 297 298Deno.test("throws XRPCInvalidResponseError for invalid response", { 299 sanitizeOps: false, 300 sanitizeResources: false, 301}, async () => { 302 await assertRejects( 303 async () => { 304 await client.call("io.example.invalidResponse"); 305 }, 306 XRPCInvalidResponseError, 307 "The server gave an invalid response and may be out of date.", 308 ); 309 310 const invalidError = await client.call("io.example.invalidResponse") 311 .catch((e) => e); 312 assert(invalidError instanceof XRPCInvalidResponseError); 313 assert(!invalidError.success); 314 assertEquals(invalidError.error, "Invalid Response"); 315 assertStringIncludes(invalidError.validationError.message, "expectedValue"); 316 assertEquals(invalidError.responseBody, { something: "else" }); 317}); 318 319Deno.test("throws XRPCError for invalid upstream response", { 320 sanitizeOps: false, 321 sanitizeResources: false, 322}, async () => { 323 await assertRejects( 324 async () => { 325 await client.call("io.example.invalidUpstreamResponse"); 326 }, 327 XRPCError, 328 "Internal Server Error", 329 ); 330 331 const upstreamError = await client.call( 332 "io.example.invalidUpstreamResponse", 333 ).catch((e) => e); 334 assert(upstreamError instanceof XRPCError); 335 assert(!upstreamError.success); 336 assertEquals(upstreamError.status, 500); 337 assertEquals(upstreamError.error, "InternalServerError"); 338}); 339 340Deno.test("serves successful requests for query and procedure", { 341 sanitizeOps: false, 342 sanitizeResources: false, 343}, async () => { 344 await client.call("io.example.query"); // No error 345 await client.call("io.example.procedure"); // No error 346}); 347 348Deno.test("serves error for incorrect HTTP method on query", { 349 sanitizeOps: false, 350 sanitizeResources: false, 351}, async () => { 352 await assertRejects( 353 async () => { 354 await badClient.call("io.example.query"); 355 }, 356 XRPCError, 357 "Incorrect HTTP method (POST) expected GET", 358 ); 359 360 const queryError = await badClient.call("io.example.query").catch((e) => e); 361 assert(queryError instanceof XRPCError); 362 assert(!queryError.success); 363 assertEquals(queryError.error, "InvalidRequest"); 364}); 365 366Deno.test("serves error for incorrect HTTP method on procedure", { 367 sanitizeOps: false, 368 sanitizeResources: false, 369}, async () => { 370 await assertRejects( 371 async () => { 372 await badClient.call("io.example.procedure"); 373 }, 374 XRPCError, 375 "Incorrect HTTP method (GET) expected POST", 376 ); 377 378 const procError = await badClient.call("io.example.procedure").catch( 379 (e) => e, 380 ); 381 assert(procError instanceof XRPCError); 382 assert(!procError.success); 383 assertEquals(procError.error, "InvalidRequest"); 384}); 385 386Deno.test("serves error for non-existent method", { 387 sanitizeOps: false, 388 sanitizeResources: false, 389}, async () => { 390 await assertRejects( 391 async () => { 392 await badClient.call("io.example.doesNotExist"); 393 }, 394 XRPCError, 395 "Method Not Implemented", 396 ); 397 398 const notFoundError = await badClient.call("io.example.doesNotExist") 399 .catch((e) => e); 400 assert(notFoundError instanceof XRPCError); 401 assert(!notFoundError.success); 402 assertEquals(notFoundError.error, "MethodNotImplemented"); 403});