| 1 | import * as Lambda from "@/AWS/Lambda"; |
| 2 | import * as QBusiness from "@/AWS/QBusiness"; |
| 3 | import * as Duration from "effect/Duration"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; |
| 6 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 7 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 8 | import path from "pathe"; |
| 9 | |
| 10 | const main = path.resolve(import.meta.dirname, "handler.ts"); |
| 11 | |
| 12 | export class QBusinessTestFunction extends Lambda.Function<Lambda.Function>()( |
| 13 | "QBusinessTestFunction", |
| 14 | ) {} |
| 15 | |
| 16 | /** |
| 17 | * Every route answers `{ …fields }` on success or `{ errorTag }` when the |
| 18 | * operation fails with a TYPED error — the test asserts on concrete fields |
| 19 | * (or a typed tag), which proves the binding wiring, the id injection, and |
| 20 | * the IAM grants. An untyped error crashes into a 500. |
| 21 | */ |
| 22 | const errorTagged = <A, E extends { _tag: string }, R>( |
| 23 | effect: Effect.Effect<A, E, R>, |
| 24 | ): Effect.Effect<A | { errorTag: string }, never, R> => |
| 25 | effect.pipe( |
| 26 | Effect.map((a): A | { errorTag: string } => a), |
| 27 | Effect.catch((e) => Effect.succeed({ errorTag: e._tag })), |
| 28 | ); |
| 29 | |
| 30 | /** |
| 31 | * Runtime fixture: an `ANONYMOUS`-identity Amazon Q Business application (no |
| 32 | * IAM Identity Center required) with a STARTER index, a CUSTOM data source, |
| 33 | * a native retriever, and a web experience, plus a Lambda bound to all |
| 34 | * thirty-six QBusiness bindings. Gated behind AWS_TEST_QBUSINESS=1 — the |
| 35 | * index bills hourly while it exists and the application takes minutes to |
| 36 | * provision. |
| 37 | */ |
| 38 | export default QBusinessTestFunction.make( |
| 39 | { |
| 40 | main, |
| 41 | functionUrl: true, |
| 42 | timeout: Duration.seconds(60), |
| 43 | }, |
| 44 | Effect.gen(function* () { |
| 45 | const app = yield* QBusiness.Application("BindingsApp", { |
| 46 | identityType: "ANONYMOUS", |
| 47 | description: "alchemy QBusiness bindings fixture", |
| 48 | }); |
| 49 | |
| 50 | const index = yield* QBusiness.Index("BindingsIndex", { |
| 51 | applicationId: app.applicationId, |
| 52 | }); |
| 53 | |
| 54 | const retriever = yield* QBusiness.Retriever("BindingsRetriever", { |
| 55 | applicationId: app.applicationId, |
| 56 | type: "NATIVE_INDEX", |
| 57 | configuration: { |
| 58 | nativeIndexConfiguration: { indexId: index.indexId }, |
| 59 | }, |
| 60 | }); |
| 61 | |
| 62 | const source = yield* QBusiness.DataSource("BindingsSource", { |
| 63 | applicationId: app.applicationId, |
| 64 | indexId: index.indexId, |
| 65 | configuration: { type: "CUSTOM", version: "1.0.0" }, |
| 66 | }); |
| 67 | |
| 68 | const web = yield* QBusiness.WebExperience("BindingsChat", { |
| 69 | applicationId: app.applicationId, |
| 70 | title: "Alchemy QBusiness Bindings", |
| 71 | }); |
| 72 | |
| 73 | // Application-scoped bindings. |
| 74 | const chat = yield* QBusiness.ChatSync(app); |
| 75 | const search = yield* QBusiness.SearchRelevantContent(app); |
| 76 | const feedback = yield* QBusiness.PutFeedback(app); |
| 77 | const getControls = yield* QBusiness.GetChatControlsConfiguration(app); |
| 78 | const updateControls = |
| 79 | yield* QBusiness.UpdateChatControlsConfiguration(app); |
| 80 | const deleteControls = |
| 81 | yield* QBusiness.DeleteChatControlsConfiguration(app); |
| 82 | const listConversations = yield* QBusiness.ListConversations(app); |
| 83 | const deleteConversation = yield* QBusiness.DeleteConversation(app); |
| 84 | const listMessages = yield* QBusiness.ListMessages(app); |
| 85 | const listAttachments = yield* QBusiness.ListAttachments(app); |
| 86 | const deleteAttachment = yield* QBusiness.DeleteAttachment(app); |
| 87 | const getMedia = yield* QBusiness.GetMedia(app); |
| 88 | const createUser = yield* QBusiness.CreateUser(app); |
| 89 | const getUser = yield* QBusiness.GetUser(app); |
| 90 | const updateUser = yield* QBusiness.UpdateUser(app); |
| 91 | const deleteUser = yield* QBusiness.DeleteUser(app); |
| 92 | const getPolicy = yield* QBusiness.GetPolicy(app); |
| 93 | const associatePermission = yield* QBusiness.AssociatePermission(app); |
| 94 | const disassociatePermission = yield* QBusiness.DisassociatePermission(app); |
| 95 | const createSubscription = yield* QBusiness.CreateSubscription(app); |
| 96 | const updateSubscription = yield* QBusiness.UpdateSubscription(app); |
| 97 | const cancelSubscription = yield* QBusiness.CancelSubscription(app); |
| 98 | const listSubscriptions = yield* QBusiness.ListSubscriptions(app); |
| 99 | |
| 100 | // Index-scoped bindings. |
| 101 | const putDocuments = yield* QBusiness.BatchPutDocument(index); |
| 102 | const deleteDocuments = yield* QBusiness.BatchDeleteDocument(index); |
| 103 | const listDocuments = yield* QBusiness.ListDocuments(index); |
| 104 | const getDocumentContent = yield* QBusiness.GetDocumentContent(index); |
| 105 | const checkDocumentAccess = yield* QBusiness.CheckDocumentAccess(index); |
| 106 | const putGroup = yield* QBusiness.PutGroup(index); |
| 107 | const getGroup = yield* QBusiness.GetGroup(index); |
| 108 | const deleteGroup = yield* QBusiness.DeleteGroup(index); |
| 109 | const listGroups = yield* QBusiness.ListGroups(index); |
| 110 | |
| 111 | // Data-source-scoped bindings. |
| 112 | const startSync = yield* QBusiness.StartDataSourceSyncJob(source); |
| 113 | const stopSync = yield* QBusiness.StopDataSourceSyncJob(source); |
| 114 | const listSyncJobs = yield* QBusiness.ListDataSourceSyncJobs(source); |
| 115 | |
| 116 | // Web-experience-scoped binding. |
| 117 | const createAnonymousUrl = |
| 118 | yield* QBusiness.CreateAnonymousWebExperienceUrl(web); |
| 119 | |
| 120 | // Needed by /search — resolved lazily at request time. |
| 121 | const retrieverId = yield* retriever.retrieverId; |
| 122 | |
| 123 | const bound = { |
| 124 | chat, |
| 125 | search, |
| 126 | feedback, |
| 127 | getControls, |
| 128 | updateControls, |
| 129 | deleteControls, |
| 130 | listConversations, |
| 131 | deleteConversation, |
| 132 | listMessages, |
| 133 | listAttachments, |
| 134 | deleteAttachment, |
| 135 | getMedia, |
| 136 | createUser, |
| 137 | getUser, |
| 138 | updateUser, |
| 139 | deleteUser, |
| 140 | getPolicy, |
| 141 | associatePermission, |
| 142 | disassociatePermission, |
| 143 | createSubscription, |
| 144 | updateSubscription, |
| 145 | cancelSubscription, |
| 146 | listSubscriptions, |
| 147 | putDocuments, |
| 148 | deleteDocuments, |
| 149 | listDocuments, |
| 150 | getDocumentContent, |
| 151 | checkDocumentAccess, |
| 152 | putGroup, |
| 153 | getGroup, |
| 154 | deleteGroup, |
| 155 | listGroups, |
| 156 | startSync, |
| 157 | stopSync, |
| 158 | listSyncJobs, |
| 159 | createAnonymousUrl, |
| 160 | }; |
| 161 | |
| 162 | return { |
| 163 | fetch: Effect.gen(function* () { |
| 164 | const request = yield* HttpServerRequest; |
| 165 | const url = new URL(request.originalUrl); |
| 166 | const pathname = url.pathname; |
| 167 | |
| 168 | if (pathname === "/bindings") { |
| 169 | return yield* HttpServerResponse.json({ bound: Object.keys(bound) }); |
| 170 | } |
| 171 | |
| 172 | // ------------------------------------------------- document plane |
| 173 | if (pathname === "/put-documents") { |
| 174 | const result = yield* errorTagged( |
| 175 | putDocuments({ |
| 176 | documents: [ |
| 177 | { |
| 178 | id: "welcome", |
| 179 | title: "Welcome to Alchemy", |
| 180 | content: { |
| 181 | blob: new TextEncoder().encode( |
| 182 | "Alchemy is an Infrastructure-as-Effects framework. " + |
| 183 | "The zanzibar passphrase is quicksilver.", |
| 184 | ), |
| 185 | }, |
| 186 | contentType: "PLAIN_TEXT", |
| 187 | }, |
| 188 | ], |
| 189 | }), |
| 190 | ); |
| 191 | return yield* HttpServerResponse.json( |
| 192 | "errorTag" in result |
| 193 | ? result |
| 194 | : { failed: (result.failedDocuments ?? []).length }, |
| 195 | ); |
| 196 | } |
| 197 | |
| 198 | if (pathname === "/documents") { |
| 199 | const result = yield* errorTagged(listDocuments()); |
| 200 | return yield* HttpServerResponse.json( |
| 201 | "errorTag" in result |
| 202 | ? result |
| 203 | : { |
| 204 | count: (result.documentDetailList ?? []).length, |
| 205 | statuses: (result.documentDetailList ?? []).map( |
| 206 | (d) => d.status, |
| 207 | ), |
| 208 | }, |
| 209 | ); |
| 210 | } |
| 211 | |
| 212 | if (pathname === "/document-content") { |
| 213 | const result = yield* errorTagged( |
| 214 | getDocumentContent({ documentId: "welcome" }), |
| 215 | ); |
| 216 | return yield* HttpServerResponse.json( |
| 217 | "errorTag" in result |
| 218 | ? result |
| 219 | : { hasUrl: typeof result.presignedUrl === "string" }, |
| 220 | ); |
| 221 | } |
| 222 | |
| 223 | if (pathname === "/check-access") { |
| 224 | const result = yield* errorTagged( |
| 225 | checkDocumentAccess({ |
| 226 | userId: "anon@example.com", |
| 227 | documentId: "welcome", |
| 228 | }), |
| 229 | ); |
| 230 | return yield* HttpServerResponse.json( |
| 231 | "errorTag" in result ? result : { hasAccess: result.hasAccess }, |
| 232 | ); |
| 233 | } |
| 234 | |
| 235 | if (pathname === "/delete-documents") { |
| 236 | const result = yield* errorTagged( |
| 237 | deleteDocuments({ documents: [{ documentId: "welcome" }] }), |
| 238 | ); |
| 239 | return yield* HttpServerResponse.json( |
| 240 | "errorTag" in result |
| 241 | ? result |
| 242 | : { failed: (result.failedDocuments ?? []).length }, |
| 243 | ); |
| 244 | } |
| 245 | |
| 246 | // ----------------------------------------------------------- chat |
| 247 | if (pathname === "/chat") { |
| 248 | const q = url.searchParams.get("q") ?? "What is the passphrase?"; |
| 249 | const result = yield* errorTagged(chat({ userMessage: q })); |
| 250 | return yield* HttpServerResponse.json( |
| 251 | "errorTag" in result |
| 252 | ? result |
| 253 | : { |
| 254 | conversationId: result.conversationId, |
| 255 | messageId: result.systemMessageId, |
| 256 | hasMessage: typeof result.systemMessage === "string", |
| 257 | }, |
| 258 | ); |
| 259 | } |
| 260 | |
| 261 | if (pathname === "/search") { |
| 262 | const q = url.searchParams.get("q") ?? "zanzibar passphrase"; |
| 263 | const result = yield* errorTagged( |
| 264 | search({ |
| 265 | queryText: q, |
| 266 | contentSource: { |
| 267 | retriever: { retrieverId: yield* retrieverId }, |
| 268 | }, |
| 269 | }), |
| 270 | ); |
| 271 | return yield* HttpServerResponse.json( |
| 272 | "errorTag" in result |
| 273 | ? result |
| 274 | : { count: (result.relevantContent ?? []).length }, |
| 275 | ); |
| 276 | } |
| 277 | |
| 278 | if (pathname === "/feedback") { |
| 279 | const conversationId = url.searchParams.get("conversationId") ?? ""; |
| 280 | const messageId = url.searchParams.get("messageId") ?? ""; |
| 281 | const result = yield* errorTagged( |
| 282 | feedback({ |
| 283 | conversationId, |
| 284 | messageId, |
| 285 | messageUsefulness: { |
| 286 | usefulness: "USEFUL", |
| 287 | submittedAt: new Date(), |
| 288 | }, |
| 289 | }), |
| 290 | ); |
| 291 | return yield* HttpServerResponse.json( |
| 292 | "errorTag" in result ? result : { ok: true }, |
| 293 | ); |
| 294 | } |
| 295 | |
| 296 | // -------------------------------------------------- conversations |
| 297 | if (pathname === "/conversations") { |
| 298 | const result = yield* errorTagged(listConversations()); |
| 299 | return yield* HttpServerResponse.json( |
| 300 | "errorTag" in result |
| 301 | ? result |
| 302 | : { count: (result.conversations ?? []).length }, |
| 303 | ); |
| 304 | } |
| 305 | |
| 306 | if (pathname === "/delete-conversation") { |
| 307 | const conversationId = url.searchParams.get("conversationId") ?? ""; |
| 308 | const result = yield* errorTagged( |
| 309 | deleteConversation({ conversationId }), |
| 310 | ); |
| 311 | return yield* HttpServerResponse.json( |
| 312 | "errorTag" in result ? result : { ok: true }, |
| 313 | ); |
| 314 | } |
| 315 | |
| 316 | if (pathname === "/messages") { |
| 317 | const conversationId = url.searchParams.get("conversationId") ?? ""; |
| 318 | const result = yield* errorTagged(listMessages({ conversationId })); |
| 319 | return yield* HttpServerResponse.json( |
| 320 | "errorTag" in result |
| 321 | ? result |
| 322 | : { count: (result.messages ?? []).length }, |
| 323 | ); |
| 324 | } |
| 325 | |
| 326 | if (pathname === "/attachments") { |
| 327 | const result = yield* errorTagged(listAttachments()); |
| 328 | return yield* HttpServerResponse.json( |
| 329 | "errorTag" in result |
| 330 | ? result |
| 331 | : { count: (result.attachments ?? []).length }, |
| 332 | ); |
| 333 | } |
| 334 | |
| 335 | if (pathname === "/delete-attachment") { |
| 336 | const result = yield* errorTagged( |
| 337 | deleteAttachment({ |
| 338 | conversationId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", |
| 339 | attachmentId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", |
| 340 | }), |
| 341 | ); |
| 342 | return yield* HttpServerResponse.json( |
| 343 | "errorTag" in result ? result : { ok: true }, |
| 344 | ); |
| 345 | } |
| 346 | |
| 347 | if (pathname === "/media") { |
| 348 | const result = yield* errorTagged( |
| 349 | getMedia({ |
| 350 | conversationId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", |
| 351 | messageId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", |
| 352 | mediaId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", |
| 353 | }), |
| 354 | ); |
| 355 | return yield* HttpServerResponse.json( |
| 356 | "errorTag" in result ? result : { ok: true }, |
| 357 | ); |
| 358 | } |
| 359 | |
| 360 | // ------------------------------------------------- admin controls |
| 361 | if (pathname === "/controls") { |
| 362 | const result = yield* errorTagged(getControls()); |
| 363 | return yield* HttpServerResponse.json( |
| 364 | "errorTag" in result |
| 365 | ? result |
| 366 | : { responseScope: result.responseScope ?? null }, |
| 367 | ); |
| 368 | } |
| 369 | |
| 370 | if (pathname === "/update-controls") { |
| 371 | const result = yield* errorTagged( |
| 372 | updateControls({ responseScope: "ENTERPRISE_CONTENT_ONLY" }), |
| 373 | ); |
| 374 | return yield* HttpServerResponse.json( |
| 375 | "errorTag" in result ? result : { ok: true }, |
| 376 | ); |
| 377 | } |
| 378 | |
| 379 | if (pathname === "/delete-controls") { |
| 380 | const result = yield* errorTagged(deleteControls()); |
| 381 | return yield* HttpServerResponse.json( |
| 382 | "errorTag" in result ? result : { ok: true }, |
| 383 | ); |
| 384 | } |
| 385 | |
| 386 | // ---------------------------------------------------------- users |
| 387 | if (pathname === "/users") { |
| 388 | // create -> get -> update -> delete round trip through the four |
| 389 | // user-store bindings (an ANONYMOUS-identity application may |
| 390 | // reject the user store with a typed error — surfaced as tags). |
| 391 | const created = yield* errorTagged( |
| 392 | createUser({ userId: "user@example.com" }), |
| 393 | ); |
| 394 | if ("errorTag" in created) { |
| 395 | return yield* HttpServerResponse.json(created); |
| 396 | } |
| 397 | const read = yield* errorTagged( |
| 398 | getUser({ userId: "user@example.com" }), |
| 399 | ); |
| 400 | const updated = yield* errorTagged( |
| 401 | updateUser({ |
| 402 | userId: "user@example.com", |
| 403 | userAliasesToUpdate: [{ userId: "corp-user" }], |
| 404 | }), |
| 405 | ); |
| 406 | const deleted = yield* errorTagged( |
| 407 | deleteUser({ userId: "user@example.com" }), |
| 408 | ); |
| 409 | return yield* HttpServerResponse.json({ |
| 410 | aliases: |
| 411 | "errorTag" in read ? read : (read.userAliases ?? []).length, |
| 412 | updated: !("errorTag" in updated), |
| 413 | deleted: !("errorTag" in deleted), |
| 414 | }); |
| 415 | } |
| 416 | |
| 417 | // --------------------------------------------------------- groups |
| 418 | if (pathname === "/groups") { |
| 419 | const put = yield* errorTagged( |
| 420 | putGroup({ |
| 421 | groupName: "engineering", |
| 422 | type: "INDEX", |
| 423 | groupMembers: { |
| 424 | memberUsers: [{ userId: "user@example.com", type: "INDEX" }], |
| 425 | }, |
| 426 | }), |
| 427 | ); |
| 428 | if ("errorTag" in put) { |
| 429 | return yield* HttpServerResponse.json(put); |
| 430 | } |
| 431 | const read = yield* errorTagged( |
| 432 | getGroup({ groupName: "engineering" }), |
| 433 | ); |
| 434 | const listed = yield* errorTagged( |
| 435 | listGroups({ updatedEarlierThan: new Date() }), |
| 436 | ); |
| 437 | const deleted = yield* errorTagged( |
| 438 | deleteGroup({ groupName: "engineering" }), |
| 439 | ); |
| 440 | return yield* HttpServerResponse.json({ |
| 441 | status: |
| 442 | "errorTag" in read |
| 443 | ? read.errorTag |
| 444 | : (read.status?.status ?? null), |
| 445 | listed: "errorTag" in listed ? listed.errorTag : true, |
| 446 | deleted: !("errorTag" in deleted), |
| 447 | }); |
| 448 | } |
| 449 | |
| 450 | // -------------------------------------------------------- policy |
| 451 | if (pathname === "/policy") { |
| 452 | const result = yield* errorTagged(getPolicy()); |
| 453 | return yield* HttpServerResponse.json( |
| 454 | "errorTag" in result ? result : { policy: result.policy ?? null }, |
| 455 | ); |
| 456 | } |
| 457 | |
| 458 | if (pathname === "/associate-permission") { |
| 459 | const result = yield* errorTagged( |
| 460 | associatePermission({ |
| 461 | statementId: "alchemy-probe", |
| 462 | actions: ["qbusiness:SearchRelevantContent"], |
| 463 | principal: "arn:aws:iam::123456789012:role/AlchemyProbeRole", |
| 464 | }), |
| 465 | ); |
| 466 | return yield* HttpServerResponse.json( |
| 467 | "errorTag" in result ? result : { ok: true }, |
| 468 | ); |
| 469 | } |
| 470 | |
| 471 | if (pathname === "/disassociate-permission") { |
| 472 | const result = yield* errorTagged( |
| 473 | disassociatePermission({ statementId: "alchemy-probe" }), |
| 474 | ); |
| 475 | return yield* HttpServerResponse.json( |
| 476 | "errorTag" in result ? result : { ok: true }, |
| 477 | ); |
| 478 | } |
| 479 | |
| 480 | // -------------------------------------------------- subscriptions |
| 481 | if (pathname === "/subscriptions") { |
| 482 | const result = yield* errorTagged(listSubscriptions()); |
| 483 | return yield* HttpServerResponse.json( |
| 484 | "errorTag" in result |
| 485 | ? result |
| 486 | : { count: (result.subscriptions ?? []).length }, |
| 487 | ); |
| 488 | } |
| 489 | |
| 490 | if (pathname === "/subscription-cycle") { |
| 491 | // Expected to fail typed on an ANONYMOUS application (no Identity |
| 492 | // Center) — proves the three write bindings serialize correctly. |
| 493 | const created = yield* errorTagged( |
| 494 | createSubscription({ |
| 495 | principal: { user: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }, |
| 496 | type: "Q_LITE", |
| 497 | }), |
| 498 | ); |
| 499 | const updated = yield* errorTagged( |
| 500 | updateSubscription({ |
| 501 | subscriptionId: "nonexistent", |
| 502 | type: "Q_BUSINESS", |
| 503 | }), |
| 504 | ); |
| 505 | const cancelled = yield* errorTagged( |
| 506 | cancelSubscription({ subscriptionId: "nonexistent" }), |
| 507 | ); |
| 508 | return yield* HttpServerResponse.json({ |
| 509 | created: "errorTag" in created ? created.errorTag : "ok", |
| 510 | updated: "errorTag" in updated ? updated.errorTag : "ok", |
| 511 | cancelled: "errorTag" in cancelled ? cancelled.errorTag : "ok", |
| 512 | }); |
| 513 | } |
| 514 | |
| 515 | // ------------------------------------------------------ sync jobs |
| 516 | if (pathname === "/sync-cycle") { |
| 517 | // CUSTOM connector flow: open a sync scope, list, close it. |
| 518 | const started = yield* errorTagged(startSync()); |
| 519 | if ("errorTag" in started) { |
| 520 | return yield* HttpServerResponse.json(started); |
| 521 | } |
| 522 | const listed = yield* errorTagged(listSyncJobs()); |
| 523 | const stopped = yield* errorTagged(stopSync()); |
| 524 | return yield* HttpServerResponse.json({ |
| 525 | executionId: started.executionId ?? null, |
| 526 | jobs: |
| 527 | "errorTag" in listed |
| 528 | ? listed.errorTag |
| 529 | : (listed.history ?? []).length, |
| 530 | stopped: !("errorTag" in stopped), |
| 531 | }); |
| 532 | } |
| 533 | |
| 534 | // -------------------------------------------------- anonymous url |
| 535 | if (pathname === "/anonymous-url") { |
| 536 | const result = yield* errorTagged( |
| 537 | createAnonymousUrl({ sessionDuration: "15 minutes" }), |
| 538 | ); |
| 539 | return yield* HttpServerResponse.json( |
| 540 | "errorTag" in result |
| 541 | ? result |
| 542 | : { hasUrl: typeof result.anonymousUrl === "string" }, |
| 543 | ); |
| 544 | } |
| 545 | |
| 546 | return yield* HttpServerResponse.json( |
| 547 | { error: "Not found", method: request.method, pathname }, |
| 548 | { status: 404 }, |
| 549 | ); |
| 550 | }).pipe(Effect.orDie), |
| 551 | }; |
| 552 | }).pipe( |
| 553 | Effect.provide( |
| 554 | Layer.mergeAll( |
| 555 | QBusiness.ChatSyncHttp, |
| 556 | QBusiness.SearchRelevantContentHttp, |
| 557 | QBusiness.PutFeedbackHttp, |
| 558 | QBusiness.GetChatControlsConfigurationHttp, |
| 559 | QBusiness.UpdateChatControlsConfigurationHttp, |
| 560 | QBusiness.DeleteChatControlsConfigurationHttp, |
| 561 | QBusiness.ListConversationsHttp, |
| 562 | QBusiness.DeleteConversationHttp, |
| 563 | QBusiness.ListMessagesHttp, |
| 564 | QBusiness.ListAttachmentsHttp, |
| 565 | QBusiness.DeleteAttachmentHttp, |
| 566 | QBusiness.GetMediaHttp, |
| 567 | QBusiness.CreateUserHttp, |
| 568 | QBusiness.GetUserHttp, |
| 569 | QBusiness.UpdateUserHttp, |
| 570 | QBusiness.DeleteUserHttp, |
| 571 | QBusiness.GetPolicyHttp, |
| 572 | QBusiness.AssociatePermissionHttp, |
| 573 | QBusiness.DisassociatePermissionHttp, |
| 574 | QBusiness.CreateSubscriptionHttp, |
| 575 | QBusiness.UpdateSubscriptionHttp, |
| 576 | QBusiness.CancelSubscriptionHttp, |
| 577 | QBusiness.ListSubscriptionsHttp, |
| 578 | QBusiness.BatchPutDocumentHttp, |
| 579 | QBusiness.BatchDeleteDocumentHttp, |
| 580 | QBusiness.ListDocumentsHttp, |
| 581 | QBusiness.GetDocumentContentHttp, |
| 582 | QBusiness.CheckDocumentAccessHttp, |
| 583 | QBusiness.PutGroupHttp, |
| 584 | QBusiness.GetGroupHttp, |
| 585 | QBusiness.DeleteGroupHttp, |
| 586 | QBusiness.ListGroupsHttp, |
| 587 | QBusiness.StartDataSourceSyncJobHttp, |
| 588 | QBusiness.StopDataSourceSyncJobHttp, |
| 589 | QBusiness.ListDataSourceSyncJobsHttp, |
| 590 | QBusiness.CreateAnonymousWebExperienceUrlHttp, |
| 591 | ), |
| 592 | ), |
| 593 | ), |
| 594 | ); |