From 9de0192922bd7a152d65eabf7db77e7177eaaaf2 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Wed, 11 Jun 2025 13:05:24 +0700 Subject: [PATCH] feat: e2e sexiness --- .../src/__snapshots__/http-e2e.test.ts.snap | 28 ++++++++++++- apps/e2e/src/http-fixtures.ts | 40 ++++++++++++++++--- ...eate-http-request-for-openapi-operation.ts | 31 ++++++++------ .../src/lib/get-tool-args-from-request.ts | 6 ++- .../src/lib/resolve-origin-tool-call.ts | 4 +- 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap index 4934b017..f09c5aff 100644 --- a/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap +++ b/apps/e2e/src/__snapshots__/http-e2e.test.ts.snap @@ -54,6 +54,30 @@ exports[`Basic MCP origin "add" tool call success > 5.1: GET @dev/test-basic-mcp ] `; +exports[`Basic OpenAPI getPost errors > 1.9: POST @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + +exports[`Basic OpenAPI getPost errors > 1.10: GET @dev/test-basic-openapi/getPost 1`] = ` +{ + "body": "quia et suscipit +suscipit recusandae consequuntur expedita et cum +reprehenderit molestiae ut ut quas totam +nostrum rerum est autem sunt rem eveniet architecto", + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "userId": 1, +} +`; + exports[`Basic OpenAPI getPost success > 0.0: POST @dev/test-basic-openapi/getPost 1`] = ` { "body": "quia et suscipit @@ -222,14 +246,14 @@ et est aut quod aut provident voluptas autem voluptas", } `; -exports[`OpenAPI kitchen sink pure tool > 7.0: POST @dev/test-everything-openapi@390e70bf/pure 1`] = ` +exports[`OpenAPI kitchen sink pure tool > 7.0: POST @dev/test-everything-openapi/pure 1`] = ` { "foo": "bar", "nala": "kitten", } `; -exports[`OpenAPI kitchen sink pure tool > 7.1: POST @dev/test-everything-openapi@390e70bf/pure 1`] = ` +exports[`OpenAPI kitchen sink pure tool > 7.1: POST @dev/test-everything-openapi/pure 1`] = ` { "foo": "bar", "nala": "kitten", diff --git a/apps/e2e/src/http-fixtures.ts b/apps/e2e/src/http-fixtures.ts index 8d6c63a7..50092840 100644 --- a/apps/e2e/src/http-fixtures.ts +++ b/apps/e2e/src/http-fixtures.ts @@ -201,12 +201,12 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ method: 'POST', json: { postId: 1, - // additional json body params should throw an error + // additional json body params are allowed by default foo: 'bar' } }, response: { - status: 400 + status: 200 } }, { @@ -214,10 +214,40 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ request: { searchParams: { postId: 1, - // additional search params should throw an error + // additional search params should allowed by default foo: 'bar' } }, + response: { + status: 200 + } + }, + { + path: '@dev/test-everything-openapi/strict_additional_properties', + request: { + method: 'POST', + json: { + foo: 'bar', + // additional json body params should throw an error if the tool + // config has `additionalProperties: false` + extra: 'nala' + } + }, + response: { + status: 400 + } + }, + { + path: '@dev/test-everything-openapi/strict_additional_properties', + request: { + method: 'GET', + searchParams: { + foo: 'bar', + // additional search params should throw an error if the tool + // config has `additionalProperties: false` + extra: 'nala' + } + }, response: { status: 400 } @@ -482,7 +512,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ compareResponseBodies: true, fixtures: [ { - path: '@dev/test-everything-openapi@390e70bf/pure', + path: '@dev/test-everything-openapi/pure', request: { method: 'POST', json: { @@ -503,7 +533,7 @@ export const fixtureSuites: E2ETestFixtureSuite[] = [ }, { // second request should hit the cache - path: '@dev/test-everything-openapi@390e70bf/pure', + path: '@dev/test-everything-openapi/pure', request: { method: 'POST', json: { diff --git a/apps/gateway/src/lib/create-http-request-for-openapi-operation.ts b/apps/gateway/src/lib/create-http-request-for-openapi-operation.ts index f9831df6..68e54111 100644 --- a/apps/gateway/src/lib/create-http-request-for-openapi-operation.ts +++ b/apps/gateway/src/lib/create-http-request-for-openapi-operation.ts @@ -1,6 +1,7 @@ import type { AdminDeployment, - OpenAPIToolOperation + OpenAPIToolOperation, + ToolConfig } from '@agentic/platform-types' import { assert } from '@agentic/platform-core' @@ -10,12 +11,14 @@ export async function createHttpRequestForOpenAPIOperation({ toolCallArgs, operation, deployment, - request + request, + toolConfig }: { toolCallArgs: ToolCallArgs operation: OpenAPIToolOperation deployment: AdminDeployment request?: Request + toolConfig?: ToolConfig }): Promise { assert(toolCallArgs, 500, 'Tool args are required') assert( @@ -44,16 +47,20 @@ export async function createHttpRequestForOpenAPIOperation({ 'Cookie parameters for OpenAPI operations are not yet supported. If you need cookie parameter support, please contact support@agentic.so.' ) - // TODO: Make this more efficient... - const extraArgs = Object.keys(toolCallArgs).filter((key) => { - if (bodyParams.some(([paramKey]) => paramKey === key)) return false - if (formDataParams.some(([paramKey]) => paramKey === key)) return false - if (headerParams.some(([paramKey]) => paramKey === key)) return false - if (queryParams.some(([paramKey]) => paramKey === key)) return false - if (pathParams.some(([paramKey]) => paramKey === key)) return false - if (cookieParams.some(([paramKey]) => paramKey === key)) return false - return true - }) + const extraArgs = + toolConfig?.additionalProperties === false + ? [] + : // TODO: Make this more efficient... + Object.keys(toolCallArgs).filter((key) => { + if (bodyParams.some(([paramKey]) => paramKey === key)) return false + if (formDataParams.some(([paramKey]) => paramKey === key)) + return false + if (headerParams.some(([paramKey]) => paramKey === key)) return false + if (queryParams.some(([paramKey]) => paramKey === key)) return false + if (pathParams.some(([paramKey]) => paramKey === key)) return false + if (cookieParams.some(([paramKey]) => paramKey === key)) return false + return true + }) const extraArgsEntries = extraArgs .map((key) => [key, toolCallArgs[key]]) .filter(([, value]) => value !== undefined) diff --git a/apps/gateway/src/lib/get-tool-args-from-request.ts b/apps/gateway/src/lib/get-tool-args-from-request.ts index 4327ebcb..7b3bd969 100644 --- a/apps/gateway/src/lib/get-tool-args-from-request.ts +++ b/apps/gateway/src/lib/get-tool-args-from-request.ts @@ -28,13 +28,17 @@ export async function getToolArgsFromRequest( new URL(request.url).searchParams.entries() ) + const toolConfig = deployment.toolConfigs.find( + (toolConfig) => toolConfig.name === tool.name + ) + // Validate incoming request params against the tool's input schema. const incomingRequestArgs = cfValidateJsonSchema>({ schema: tool.inputSchema, data: incomingRequestArgsRaw, errorPrefix: `Invalid request parameters for tool "${tool.name}"`, coerce: true, - strictAdditionalProperties: false + strictAdditionalProperties: toolConfig?.additionalProperties === false }) return incomingRequestArgs diff --git a/apps/gateway/src/lib/resolve-origin-tool-call.ts b/apps/gateway/src/lib/resolve-origin-tool-call.ts index e3088ee1..996cc801 100644 --- a/apps/gateway/src/lib/resolve-origin-tool-call.ts +++ b/apps/gateway/src/lib/resolve-origin-tool-call.ts @@ -176,7 +176,7 @@ export async function resolveOriginToolCall({ schema: tool.inputSchema, data: args, errorPrefix: `Invalid request parameters for tool "${tool.name}"`, - strictAdditionalProperties: false + strictAdditionalProperties: toolConfig?.additionalProperties === false }) const originStartTimeMs = Date.now() @@ -209,6 +209,8 @@ export async function resolveOriginToolCall({ }, waitUntil }) + + // Fetch the origin response without caching (useful for debugging) // const originResponse = await fetch(originRequest) const cacheStatus =