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