Skip to content

feat(EVO-1738): stateless POST /custom-tools/test (test-before-save) - #22

Merged
gomessguii merged 2 commits into
developfrom
feat/EVO-1738-custom-tools-test-payload-endpoint
Jul 24, 2026
Merged

feat(EVO-1738): stateless POST /custom-tools/test (test-before-save)#22
gomessguii merged 2 commits into
developfrom
feat/EVO-1738-custom-tools-test-payload-endpoint

Conversation

@pastoriniMatheus

@pastoriniMatheus pastoriniMatheus commented Jul 24, 2026

Copy link
Copy Markdown

EVO-1738 (core) — endpoint stateless de teste de ferramenta NÃO salva

Metade backend. Par do frontend (botão "Test request" no wizard de Custom Tools). Deploy conjunto.

Contexto

O wizard de Custom Tools só permitia testar uma ferramenta depois de salva (GET /custom-tools/{id}/test). O requisito da issue é test-before-save: testar a requisição com a config ainda em rascunho no wizard, antes de persistir.

Mudança

Novo endpoint stateless POST /custom-tools/test, que executa a requisição de uma ferramenta ainda não persistida:

  • service.TestPayload(ctx, method, endpoint, headers, bodyParams) — valida o método HTTP e reusa o runToolTest já existente (o mesmo caminho SSRF-hardened usado no teste de ferramenta salva). Nada é gravado.
  • handler.TestPayload — bind de {method, endpoint, headers, body_params}; rota POST /custom-tools/test protegida por ai_custom_tools:read (paridade com o gate do GET /{id}/test).
  • Zero mudança no fluxo de ferramentas salvas — só um wrapper público stateless sobre a lógica de execução/validação já existente.

Testes

  • Novo: TestTestPayload_UnsavedTool_RunsAndValidatesMethod — roda uma ferramenta não salva, valida o método e retorna o resultado do teste.
  • go build -modfile=go.community.mod ./... limpo (build community, sem SDK enterprise).
  • Verificado E2E na stack local (evo-core buildado do código local): POST /custom-tools/test com {method:GET, endpoint:jsonplaceholder/todos/1}HTTP 200, test_result.success=true, status_code=200, com headers, body e response_time reais.

Relacionado

Frontend par: frontend #269.

The wizard needs to test a tool's HTTP request BEFORE it is saved, but the only test
endpoint was GET /:id/test (requires a persisted tool). Add a stateless POST
/custom-tools/test accepting {method, endpoint, headers, body_params} and returning the
same TestResult, reusing the SSRF-hardened runToolTest (scheme allowlist, public-IP-only
+ DNS-rebinding revalidation, redirects off, 1 MiB/15s caps, header allowlist).

- service: TestPayload(ctx, method, endpoint, headers, bodyParams) — validates the method
  then delegates to runToolTest. Same defenses as Test, no saved tool required.
- handler: TestPayload binds the payload (ValidationErrorResponse on bad body); route
  POST /custom-tools/test, gated ai_custom_tools:read (parity with GET /:id/test).
- test: TestPayload runs against an httptest server (success 200) and fails fast on an
  unsupported method. go build/vet/test ./pkg/custom_tool/... green.

Backend half of the EVO-1738 wizard "Test" button; the frontend wires it. File is
pre-existing CRLF on develop, kept out of gofmt to avoid a line-ending-only diff.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @pastoriniMatheus, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

…ms, 400 on bad method

Code-review follow-ups on the stateless POST /custom-tools/test endpoint.

- Permission gate read -> create. Unlike GET /:id/test (which only replays a
  tool someone with create rights already authored), this endpoint takes
  method/endpoint/headers straight from the request body, so gating it on
  "read" handed every read-only user an arbitrary server-side HTTP fetcher.
- resolveToolURL applies the tool's path placeholders and query params before
  the request goes out. Both test paths dropped them, so "/users/{user_id}"
  was requested literally and query params never reached the wire -- the
  wizard could report "Request OK -- HTTP 200" for a request that carried
  none of the user's configuration. Path values are percent-escaped and
  validateEndpoint still runs on the RESOLVED url, so the SSRF gate holds.
- normalizeToolMethod shared by Test and TestPayload so the allowlists cannot
  drift, returning a 400 ApiError: an unsupported method is caller input, and
  a plain fmt.Errorf mapped to 500 INTERNAL_ERROR.
- TestPayload takes model.CustomToolTestPayloadRequest instead of a widening
  positional list.

Tests: path/query application, placeholder-cannot-inject-a-host (plus the
literal link-local case still hitting the public-IP gate), 400 mapping, and a
first test file for the handler package (binding, envelope, error mapping).
@gomessguii
gomessguii force-pushed the feat/EVO-1738-custom-tools-test-payload-endpoint branch from 3cad1ce to 46c8fb6 Compare July 24, 2026 22:16
@gomessguii
gomessguii merged commit 3218989 into develop Jul 24, 2026
5 checks passed
@gomessguii
gomessguii deleted the feat/EVO-1738-custom-tools-test-payload-endpoint branch July 24, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants