| @@ -20,7 +20,6 @@ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; · 19 unmodified lines |
| 20 | | 20 | |
| 21 | import { | 21 | import { |
| 22 | ConnectionId, | 22 | ConnectionId, |
| 23 | ConnectionReauthRequiredError, | | |
| 24 | CreateConnectionInput, | 23 | CreateConnectionInput, |
| 25 | ScopeId, | 24 | ScopeId, |
| 26 | SecretId, | 25 | SecretId, |
| @@ -41,6 +40,13 @@ import { OAuth2Auth } from "./types"; · 14 unmodified lines |
| 41 | | 40 | |
| 42 | const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; | 41 | const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; |
| 43 | | 42 | |
| | 43 | class OpenApiOauthTestSetupError extends Schema.TaggedErrorClass<OpenApiOauthTestSetupError>()( |
| | 44 | "OpenApiOauthTestSetupError", |
| | 45 | { |
| | 46 | message: Schema.String, |
| | 47 | }, |
| | 48 | ) {} |
| | 49 | |
| 44 | // --------------------------------------------------------------------------- | 50 | // --------------------------------------------------------------------------- |
| 45 | // Test API — one endpoint that echoes the Authorization header so we can | 51 | // Test API — one endpoint that echoes the Authorization header so we can |
| 46 | // prove which access token was in flight at invoke time. | 52 | // prove which access token was in flight at invoke time. |
| @@ -141,11 +147,15 @@ const makeExecutor = () => · 94 unmodified lines |
| 141 | storage: () => ({}), | 147 | storage: () => ({}), |
| 142 | secretProviders: [memoryProvider], | 148 | secretProviders: [memoryProvider], |
| 143 | })); | 149 | })); |
| 144 | const clientLayer = FetchHttpClient.layer; | 150 | const clientLayer = FetchHttpClient.layer; |
| 145 | const server = yield* HttpServer.HttpServer; | 151 | const server = yield* HttpServer.HttpServer; |
| 146 | const address = server.address; | 152 | const address = server.address; |
| 147 | if (address._tag !== "TcpAddress") return yield* Effect.die("test server must bind to TCP"); | 153 | if (!("port" in address)) { |
| 148 | const baseUrl = `http://127.0.0.1:${address.port}`; | 154 | return yield* new OpenApiOauthTestSetupError({ |
| | 155 | message: "Test server must bind to TCP", |
| | 156 | }); |
| | 157 | } |
| | 158 | const baseUrl = `http://127.0.0.1:${address.port}`; |
| 149 | const plugins = [ | 159 | const plugins = [ |
| 150 | openApiPlugin({ httpClientLayer: clientLayer }), | 160 | openApiPlugin({ httpClientLayer: clientLayer }), |
| 151 | memorySecretsPlugin(), | 161 | memorySecretsPlugin(), |
| @@ -277,7 +287,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 125 unmodified lines |
| 277 | | 287 | |
| 278 | yield* executor.openapi.addSpec({ | 288 | yield* executor.openapi.addSpec({ |
| 279 | spec: specJson, | 289 | spec: specJson, |
| 280 | scope: String(scopeId), | 290 | scope: scopeId, |
| 281 | namespace: "petstore", | 291 | namespace: "petstore", |
| 282 | baseUrl, | 292 | baseUrl, |
| 283 | oauth2: auth, | 293 | oauth2: auth, |
| @@ -334,7 +344,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 50 unmodified lines |
| 334 | | 344 | |
| 335 | yield* executor.openapi.addSpec({ | 345 | yield* executor.openapi.addSpec({ |
| 336 | spec: specJson, | 346 | spec: specJson, |
| 337 | scope: String(scopeId), | 347 | scope: scopeId, |
| 338 | namespace: "petstore", | 348 | namespace: "petstore", |
| 339 | baseUrl, | 349 | baseUrl, |
| 340 | oauth2: auth, | 350 | oauth2: auth, |
| @@ -392,7 +402,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 51 unmodified lines |
| 392 | | 402 | |
| 393 | yield* executor.openapi.addSpec({ | 403 | yield* executor.openapi.addSpec({ |
| 394 | spec: specJson, | 404 | spec: specJson, |
| 395 | scope: String(scopeId), | 405 | scope: scopeId, |
| 396 | namespace: "petstore", | 406 | namespace: "petstore", |
| 397 | baseUrl, | 407 | baseUrl, |
| 398 | oauth2: auth, | 408 | oauth2: auth, |
| @@ -402,15 +412,23 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 3 unmodified lines |
| 402 | // generic Error (see openapi invokeTool), so we assert against | 412 | // generic Error (see openapi invokeTool), so we assert against |
| 403 | // the `accessToken` call directly too — that's the surface | 413 | // the `accessToken` call directly too — that's the surface |
| 404 | // the UI bridges use to trigger re-auth. | 414 | // the UI bridges use to trigger re-auth. |
| 405 | const flipped = yield* executor.connections | 415 | const reauthRequired = yield* executor.connections |
| 406 | .accessToken("conn-refresh-dead") | 416 | .accessToken("conn-refresh-dead") |
| 407 | .pipe(Effect.flip); | 417 | .pipe( |
| 408 | expect(flipped._tag).toBe("ConnectionReauthRequiredError"); | 418 | Effect.flatMap(() => |
| 409 | expect((flipped as ConnectionReauthRequiredError).provider).toBe( | 419 | Effect.fail( |
| | 420 | new OpenApiOauthTestSetupError({ |
| | 421 | message: "Expected refresh to require re-auth", |
| | 422 | }), |
| | 423 | ), |
| | 424 | ), |
| | 425 | Effect.catchTag("ConnectionReauthRequiredError", Effect.succeed), |
| | 426 | ); |
| | 427 | expect(reauthRequired.provider).toBe( |
| 410 | "openapi:oauth2", | 428 | "openapi:oauth2", |
| 411 | ); | 429 | ); |
| 412 | expect( | 430 | expect( |
| 413 | (flipped as ConnectionReauthRequiredError).message, | 431 | reauthRequired.message, |
| 414 | ).toMatch(/invalid_grant|revoked/i); | 432 | ).toMatch(/invalid_grant|revoked/i); |
| 415 | }), | 433 | }), |
| 416 | ); | 434 | ); |