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 | 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 | 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 | 154 | const clientLayer = FetchHttpClient.layer; |
| 148 | 155 | const server = yield* HttpServer.HttpServer; |
| 149 | 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 | 162 | const baseUrl = `http://127.0.0.1:${address.port}`; |
| 152 | 163 | const plugins = [ |
| 153 | 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) { | |
| 235 | − throw new Error("expected completed clientCredentials connection"); | |
| 245 | + const completedConnection = started.completedConnection; | |
| 246 | + if (!completedConnection) { | |
| 247 | + return yield* new OpenApiClientCredentialsTestSetupError({ | |
| 248 | + message: "Expected completed clientCredentials connection", | |
| 249 | + }); | |
| 236 | 250 | } |
| 237 | 251 | const auth = new OAuth2Auth({ |
| 238 | 252 | kind: "oauth2", |
| 239 | − connectionId: started.completedConnection.connectionId, | |
| 253 | + connectionId: completedConnection.connectionId, | |
| 240 | 254 | securitySchemeName: "oauth2", |
| 241 | 255 | flow: "clientCredentials", |
| 242 | 256 | tokenUrl: "https://token.example.com/token", |
| @@ -257,7 +271,7 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 14 unmodified lines | ||
| 257 | 271 | // Add the source with OAuth2Auth pointing at the completed connection. |
| 258 | 272 | yield* userExec.openapi.addSpec({ |
| 259 | 273 | spec: specJson, |
| 260 | − scope: userScope.id as string, | |
| 274 | + scope: userScope.id, | |
| 261 | 275 | namespace: "petstore", |
| 262 | 276 | baseUrl, |
| 263 | 277 | oauth2: auth, |
| @@ -20,7 +20,6 @@ import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; · 19 unmodified lines | ||
| 20 | 20 | |
| 21 | 21 | import { |
| 22 | 22 | ConnectionId, |
| 23 | − ConnectionReauthRequiredError, | |
| 24 | 23 | CreateConnectionInput, |
| 25 | 24 | ScopeId, |
| 26 | 25 | SecretId, |
| @@ -41,6 +40,13 @@ import { OAuth2Auth } from "./types"; · 14 unmodified lines | ||
| 41 | 40 | |
| 42 | 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 | 51 | // Test API — one endpoint that echoes the Authorization header so we can |
| 46 | 52 | // prove which access token was in flight at invoke time. |
| @@ -141,11 +147,15 @@ const makeExecutor = () => · 94 unmodified lines | ||
| 141 | 147 | storage: () => ({}), |
| 142 | 148 | secretProviders: [memoryProvider], |
| 143 | 149 | })); |
| 144 | − const clientLayer = FetchHttpClient.layer; | |
| 145 | − const server = yield* HttpServer.HttpServer; | |
| 146 | − const address = server.address; | |
| 147 | − if (address._tag !== "TcpAddress") return yield* Effect.die("test server must bind to TCP"); | |
| 148 | − const baseUrl = `http://127.0.0.1:${address.port}`; | |
| 150 | + const clientLayer = FetchHttpClient.layer; | |
| 151 | + const server = yield* HttpServer.HttpServer; | |
| 152 | + const address = server.address; | |
| 153 | + if (!("port" in address)) { | |
| 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 | 159 | const plugins = [ |
| 150 | 160 | openApiPlugin({ httpClientLayer: clientLayer }), |
| 151 | 161 | memorySecretsPlugin(), |
| @@ -277,7 +287,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 125 unmodified lines | ||
| 277 | 287 | |
| 278 | 288 | yield* executor.openapi.addSpec({ |
| 279 | 289 | spec: specJson, |
| 280 | − scope: String(scopeId), | |
| 290 | + scope: scopeId, | |
| 281 | 291 | namespace: "petstore", |
| 282 | 292 | baseUrl, |
| 283 | 293 | oauth2: auth, |
| @@ -334,7 +344,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 50 unmodified lines | ||
| 334 | 344 | |
| 335 | 345 | yield* executor.openapi.addSpec({ |
| 336 | 346 | spec: specJson, |
| 337 | − scope: String(scopeId), | |
| 347 | + scope: scopeId, | |
| 338 | 348 | namespace: "petstore", |
| 339 | 349 | baseUrl, |
| 340 | 350 | oauth2: auth, |
| @@ -392,7 +402,7 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 51 unmodified lines | ||
| 392 | 402 | |
| 393 | 403 | yield* executor.openapi.addSpec({ |
| 394 | 404 | spec: specJson, |
| 395 | − scope: String(scopeId), | |
| 405 | + scope: scopeId, | |
| 396 | 406 | namespace: "petstore", |
| 397 | 407 | baseUrl, |
| 398 | 408 | oauth2: auth, |
| @@ -402,15 +412,23 @@ layer(TestLayer)("OpenAPI oauth refresh", (it) => { · 3 unmodified lines | ||
| 402 | 412 | // generic Error (see openapi invokeTool), so we assert against |
| 403 | 413 | // the `accessToken` call directly too — that's the surface |
| 404 | 414 | // the UI bridges use to trigger re-auth. |
| 405 | − const flipped = yield* executor.connections | |
| 415 | + const reauthRequired = yield* executor.connections | |
| 406 | 416 | .accessToken("conn-refresh-dead") |
| 407 | − .pipe(Effect.flip); | |
| 408 | − expect(flipped._tag).toBe("ConnectionReauthRequiredError"); | |
| 409 | − expect((flipped as ConnectionReauthRequiredError).provider).toBe( | |
| 417 | + .pipe( | |
| 418 | + Effect.flatMap(() => | |
| 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 | 428 | "openapi:oauth2", |
| 411 | 429 | ); |
| 412 | 430 | expect( |
| 413 | − (flipped as ConnectionReauthRequiredError).message, | |
| 431 | + reauthRequired.message, | |
| 414 | 432 | ).toMatch(/invalid_grant|revoked/i); |
| 415 | 433 | }), |
| 416 | 434 | ); |