| @@ -0,0 +1,202 @@ |
| | 1 | import { createServer } from "node:http"; |
| | 2 | |
| | 3 | import { describe, expect, it } from "@effect/vitest"; |
| | 4 | import { |
| | 5 | EXECUTABLE_BINDING_VERSION, |
| | 6 | type Source, |
| | 7 | } from "@executor/source-core"; |
| | 8 | import * as Effect from "effect/Effect"; |
| | 9 | |
| | 10 | import { googleDiscoverySourceAdapter } from "./adapter"; |
| | 11 | import { googleDiscoveryProviderDataFromDefinition } from "./tools"; |
| | 12 | |
| | 13 | const withJsonServer = async <T>(handler: (input: { |
| | 14 | baseUrl: string; |
| | 15 | requests: Array<{ |
| | 16 | method: string; |
| | 17 | url: string; |
| | 18 | headers: Record<string, string | string[] | undefined>; |
| | 19 | body: string; |
| | 20 | }>; |
| | 21 | }) => Promise<T>): Promise<T> => { |
| | 22 | const requests: Array<{ |
| | 23 | method: string; |
| | 24 | url: string; |
| | 25 | headers: Record<string, string | string[] | undefined>; |
| | 26 | body: string; |
| | 27 | }> = []; |
| | 28 | const server = createServer(async (request, response) => { |
| | 29 | const chunks: Buffer[] = []; |
| | 30 | for await (const chunk of request) { |
| | 31 | chunks.push(Buffer.from(chunk)); |
| | 32 | } |
| | 33 | requests.push({ |
| | 34 | method: request.method ?? "GET", |
| | 35 | url: request.url ?? "/", |
| | 36 | headers: request.headers, |
| | 37 | body: Buffer.concat(chunks).toString("utf8"), |
| | 38 | }); |
| | 39 | |
| | 40 | response.statusCode = 200; |
| | 41 | response.setHeader("content-type", "application/json"); |
| | 42 | response.end( |
| | 43 | JSON.stringify({ |
| | 44 | ok: true, |
| | 45 | url: request.url ?? "/", |
| | 46 | }), |
| | 47 | ); |
| | 48 | }); |
| | 49 | |
| | 50 | await new Promise<void>((resolve, reject) => { |
| | 51 | server.listen(0, "127.0.0.1", (error?: Error) => { |
| | 52 | if (error) { |
| | 53 | reject(error); |
| | 54 | return; |
| | 55 | } |
| | 56 | resolve(); |
| | 57 | }); |
| | 58 | }); |
| | 59 | |
| | 60 | try { |
| | 61 | const address = server.address(); |
| | 62 | if (!address || typeof address === "string") { |
| | 63 | throw new Error("Failed to resolve local adapter test server address"); |
| | 64 | } |
| | 65 | return await handler({ |
| | 66 | baseUrl: `http://127.0.0.1:${address.port}`, |
| | 67 | requests, |
| | 68 | }); |
| | 69 | } finally { |
| | 70 | await new Promise<void>((resolve, reject) => { |
| | 71 | server.close((error) => { |
| | 72 | if (error) { |
| | 73 | reject(error); |
| | 74 | return; |
| | 75 | } |
| | 76 | resolve(); |
| | 77 | }); |
| | 78 | }); |
| | 79 | } |
| | 80 | }; |
| | 81 | |
| | 82 | const makeGoogleDiscoverySource = (discoveryUrl: string): Source => ({ |
| | 83 | id: "src_google_gmail", |
| | 84 | workspaceId: "ws_test", |
| | 85 | name: "Gmail", |
| | 86 | kind: "google_discovery", |
| | 87 | endpoint: discoveryUrl, |
| | 88 | status: "connected", |
| | 89 | enabled: true, |
| | 90 | namespace: "google.gmail", |
| | 91 | bindingVersion: 1, |
| | 92 | binding: { |
| | 93 | service: "gmail", |
| | 94 | version: "v1", |
| | 95 | discoveryUrl, |
| | 96 | defaultHeaders: null, |
| | 97 | scopes: [], |
| | 98 | }, |
| | 99 | importAuthPolicy: "reuse_runtime", |
| | 100 | importAuth: { kind: "none" }, |
| | 101 | auth: { kind: "none" }, |
| | 102 | sourceHash: null, |
| | 103 | lastError: null, |
| | 104 | createdAt: 1, |
| | 105 | updatedAt: 1, |
| | 106 | }); |
| | 107 | |
| | 108 | describe("google discovery source adapter", () => { |
| | 109 | it("invokes requests against the discovery document runtime root instead of the stored discovery endpoint", () => |
| | 110 | Effect.tryPromise({ |
| | 111 | try: async () => { |
| | 112 | await withJsonServer(async ({ baseUrl, requests }) => { |
| | 113 | const discoveryUrl = `${baseUrl}/$discovery/rest?version=v1`; |
| | 114 | const source = makeGoogleDiscoverySource(discoveryUrl); |
| | 115 | const providerData = googleDiscoveryProviderDataFromDefinition({ |
| | 116 | service: "gmail", |
| | 117 | version: "v1", |
| | 118 | rootUrl: `${baseUrl}/`, |
| | 119 | servicePath: "gmail/v1/", |
| | 120 | definition: { |
| | 121 | toolId: "users.messages.list", |
| | 122 | rawToolId: "users.messages.list", |
| | 123 | methodId: "gmail.users.messages.list", |
| | 124 | name: "List Messages", |
| | 125 | description: null, |
| | 126 | group: "users.messages", |
| | 127 | leaf: "list", |
| | 128 | method: "get", |
| | 129 | path: "users/{userId}/messages", |
| | 130 | flatPath: null, |
| | 131 | parameters: [ |
| | 132 | { |
| | 133 | name: "userId", |
| | 134 | location: "path", |
| | 135 | required: true, |
| | 136 | repeated: false, |
| | 137 | description: null, |
| | 138 | type: "string", |
| | 139 | }, |
| | 140 | { |
| | 141 | name: "labelIds", |
| | 142 | location: "query", |
| | 143 | required: false, |
| | 144 | repeated: true, |
| | 145 | description: null, |
| | 146 | type: "string", |
| | 147 | }, |
| | 148 | ], |
| | 149 | requestSchemaId: null, |
| | 150 | responseSchemaId: null, |
| | 151 | scopes: [], |
| | 152 | supportsMediaUpload: false, |
| | 153 | supportsMediaDownload: false, |
| | 154 | }, |
| | 155 | }); |
| | 156 | |
| | 157 | const result = await Effect.runPromise( |
| | 158 | googleDiscoverySourceAdapter.invoke({ |
| | 159 | source, |
| | 160 | capability: {} as any, |
| | 161 | executable: { |
| | 162 | id: "exe_google_gmail_list", |
| | 163 | bindingVersion: EXECUTABLE_BINDING_VERSION, |
| | 164 | binding: providerData, |
| | 165 | } as any, |
| | 166 | descriptor: {} as any, |
| | 167 | catalog: {} as any, |
| | 168 | args: { |
| | 169 | userId: "me", |
| | 170 | labelIds: ["INBOX", "UNREAD"], |
| | 171 | }, |
| | 172 | auth: { |
| | 173 | placements: [], |
| | 174 | headers: { |
| | 175 | authorization: "Bearer live-test-token", |
| | 176 | }, |
| | 177 | queryParams: {}, |
| | 178 | cookies: {}, |
| | 179 | bodyValues: {}, |
| | 180 | expiresAt: null, |
| | 181 | refreshAfter: null, |
| | 182 | }, |
| | 183 | }), |
| | 184 | ); |
| | 185 | |
| | 186 | expect(result.status).toBe(200); |
| | 187 | expect(result.error).toBeNull(); |
| | 188 | expect(result.data).toEqual({ |
| | 189 | ok: true, |
| | 190 | url: "/gmail/v1/users/me/messages?labelIds=INBOX&labelIds=UNREAD", |
| | 191 | }); |
| | 192 | expect(requests).toHaveLength(1); |
| | 193 | expect(requests[0]?.url).toBe( |
| | 194 | "/gmail/v1/users/me/messages?labelIds=INBOX&labelIds=UNREAD", |
| | 195 | ); |
| | 196 | expect(requests[0]?.headers.authorization).toBe("Bearer live-test-token"); |
| | 197 | }); |
| | 198 | }, |
| | 199 | catch: (cause) => |
| | 200 | cause instanceof Error ? cause : new Error(String(cause)), |
| | 201 | })); |
| | 202 | }); |