Showing the changes to packages/plugins/openapi/src (2 of 2 files). Show all
Unified · Split
| @@ -31,6 +31,13 @@ import { OAuth2Auth } from "./types"; · 30 unmodified lines | |||
| 31 | 31 | ||
| 32 | const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; | 32 | const autoApprove: InvokeOptions = { onElicitation: "accept-all" }; |
| 33 | 33 | ||
| 34 | class OpenApiClientCredentialsTestSetupError extends Schema.TaggedErrorClass<OpenApiClientCredentialsTestSetupError>()( | ||
| 35 | "OpenApiClientCredentialsTestSetupError", | ||
| 36 | { | ||
| 37 | message: Schema.String, | ||
| 38 | }, | ||
| 39 | ) {} | ||
| 40 | |||
| 34 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
| 35 | // Test API — single endpoint that echoes the Authorization header. | 42 | // Test API — single endpoint that echoes the Authorization header. |
| 36 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
| @@ -147,7 +154,11 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 110 unmodified lines | |||
| 147 | const clientLayer = FetchHttpClient.layer; | 154 | const clientLayer = FetchHttpClient.layer; |
| 148 | const server = yield* HttpServer.HttpServer; | 155 | const server = yield* HttpServer.HttpServer; |
| 149 | const address = server.address; | 156 | const address = server.address; |
| 150 | if (address._tag !== "TcpAddress") return yield* Effect.die("test server must bind to TCP"); | 157 | if (!("port" in address)) { |
| 158 | return yield* new OpenApiClientCredentialsTestSetupError({ | ||
| 159 | message: "Test server must bind to TCP", | ||
| 160 | }); | ||
| 161 | } | ||
| 151 | const baseUrl = `http://127.0.0.1:${address.port}`; | 162 | const baseUrl = `http://127.0.0.1:${address.port}`; |
| 152 | const plugins = [ | 163 | const plugins = [ |
| 153 | openApiPlugin({ httpClientLayer: clientLayer }), | 164 | openApiPlugin({ httpClientLayer: clientLayer }), |
| @@ -231,12 +242,15 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 77 unmodified lines | |||
| 231 | }, | 242 | }, |
| 232 | }); | 243 | }); |
| 233 | 244 | ||
| 234 | if (!started.completedConnection) { | 245 | const completedConnection = started.completedConnection; |
| 235 | throw new Error("expected completed clientCredentials connection"); | 246 | if (!completedConnection) { |
| 247 | return yield* new OpenApiClientCredentialsTestSetupError({ | ||
| 248 | message: "Expected completed clientCredentials connection", | ||
| 249 | }); | ||
| 236 | } | 250 | } |
| 237 | const auth = new OAuth2Auth({ | 251 | const auth = new OAuth2Auth({ |
| 238 | kind: "oauth2", | 252 | kind: "oauth2", |
| 239 | connectionId: started.completedConnection.connectionId, | 253 | connectionId: completedConnection.connectionId, |
| 240 | securitySchemeName: "oauth2", | 254 | securitySchemeName: "oauth2", |
| 241 | flow: "clientCredentials", | 255 | flow: "clientCredentials", |
| 242 | tokenUrl: "https://token.example.com/token", | 256 | tokenUrl: "https://token.example.com/token", |
| @@ -257,7 +271,7 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 14 unmodified lines | |||
| 257 | // Add the source with OAuth2Auth pointing at the completed connection. | 271 | // Add the source with OAuth2Auth pointing at the completed connection. |
| 258 | yield* userExec.openapi.addSpec({ | 272 | yield* userExec.openapi.addSpec({ |
| 259 | spec: specJson, | 273 | spec: specJson, |
| 260 | scope: userScope.id as string, | 274 | scope: userScope.id, |
| 261 | namespace: "petstore", | 275 | namespace: "petstore", |
| 262 | baseUrl, | 276 | baseUrl, |
| 263 | oauth2: auth, | 277 | oauth2: auth, |
| @@ -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 | ); |