This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-performance-tests.git
commit 81b7209bd78302eaf909fb2de2e1054855d2fe82 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Oct 18 15:22:38 2026 +0200 ai-benchmark: contracts/rest-code-first in the new set, and probe regexes match the whole body rest-code-first: GET /orders/{id} with a 404, POST /orders bound to a Java record (restart to compile it) with a 400, and the generated API description at /openapi (restart, as camel-openapi-java comes from camel.jbang.dependencies). Checked with HTTP probes. The final reference is the example's route file (apache/camel-jbang-examples#118). Reference pass 3/3 twice. A probe kept the first 600 characters of the answer and matched its regex on them; the /orders/{id} path of the generated API description comes later. The regex is now matched on the whole body; the result still keeps the start. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- ai-benchmark/agent_mcp_stepwise.py | 14 ++++--- ai-benchmark/gen_stepwise.py | 45 ++++++++++++++++++++++ .../seed/contracts-rest-code-first/orders.json | 5 +++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/ai-benchmark/agent_mcp_stepwise.py b/ai-benchmark/agent_mcp_stepwise.py index c4858f0..c4875e1 100755 --- a/ai-benchmark/agent_mcp_stepwise.py +++ b/ai-benchmark/agent_mcp_stepwise.py @@ -305,22 +305,26 @@ def probe(pr): server may still be settling after the reload, so the request is retried a few times until the expected status.""" method = pr.get("method", "GET"); url = pr["url"]; want = int(pr.get("expect_status", 200)) out = {"url": url, "method": method, "ok": False, "status": None, "body": ""} + full = "" for _ in range(6): try: data = pr["body"].encode() if pr.get("body") is not None else None req = urllib.request.Request(url, data=data, method=method, headers=pr.get("headers") or {}) with urllib.request.urlopen(req, timeout=8) as r: - out["status"], out["body"] = r.status, r.read().decode(errors="replace")[:600] + out["status"], full = r.status, r.read().decode(errors="replace") except urllib.error.HTTPError as e: - out["status"], out["body"] = e.code, e.read().decode(errors="replace")[:600] + out["status"], full = e.code, e.read().decode(errors="replace") except Exception as e: - out["status"], out["body"] = None, str(e)[:200] + out["status"], full = None, str(e)[:200] + # the result keeps the start of the body; the regex is matched on all of it (the /orders/{id} path of a + # generated API description comes after the first 600 characters, rcref Oct 18) + out["body"] = full[:600] # the status alone is not enough: right after a reload the previous route may still answer 200 with its own # body, and that is not the answer the step asks for -- retry until the body matches too - if out["status"] == want and body_matches(pr.get("body_regex"), out["body"]): + if out["status"] == want and body_matches(pr.get("body_regex"), full): break time.sleep(2) - out["ok"] = out["status"] == want and body_matches(pr.get("body_regex"), out["body"]) + out["ok"] = out["status"] == want and body_matches(pr.get("body_regex"), full) return out diff --git a/ai-benchmark/gen_stepwise.py b/ai-benchmark/gen_stepwise.py index 7b29409..93f3cd6 100644 --- a/ai-benchmark/gen_stepwise.py +++ b/ai-benchmark/gen_stepwise.py @@ -998,6 +998,51 @@ EXAMPLES.append({ ]}) +# ---------------------------------------------------------------- contracts/rest-code-first (REST DSL code first, json binding, probes) +# The answers are Java objects written by the json binding; error answers are JSON text (skipBindingOnErrorCode). Step 2 +# adds a Java record (a restart compiles it); step 3 needs camel-openapi-java, which camel run does not add by itself +# for a code-first apiContextPath (the dependency applies on the restart). +RC = "rest-code-first.camel.yaml" +RC_LIST = '- route:\n id: list-orders\n from:\n uri: direct\n parameters:\n name: list-orders\n steps:\n - setBody:\n expression:\n constant:\n expression: "resource:file:orders.json"\n - unmarshal:\n json:\n library: Jackson\n' +RC_GET = '- route:\n id: get-order\n from:\n uri: direct\n parameters:\n name: get-order\n steps:\n - setBody:\n expression:\n constant:\n expression: "resource:file:orders.json"\n - unmarshal:\n json:\n library: Jackson\n - setBody:\n expression:\n groovy:\n expression: "body.find { it.orderId == headers.id }"\n - filter:\n [...] +RC_CREATE = '- route:\n id: create-order\n from:\n uri: direct\n parameters:\n name: create-order\n steps:\n - choice:\n when:\n - expression:\n simple:\n expression: "${body.lines} == null || ${body.lines.isEmpty()}"\n steps:\n - setHeader:\n name: CamelHttpResponseCode\n expression:\n constant:\n [...] +RC_ORDER_JAVA = '/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the "License"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICE [...] +def rc_conf(api): + return "- restConfiguration:\n bindingMode: json\n" + (" apiContextPath: openapi\n" if api else "") +def rc_rest(get_id, post): + s = ("- rest:\n path: /orders\n get:\n - id: listOrders\n description: All orders\n" + " to:\n uri: direct:list-orders\n") + if get_id: + s += (" - id: getOrder\n path: \"/{id}\"\n description: One order, by its id\n" + " to:\n uri: direct:get-order\n") + if post: + s += (" post:\n - id: createOrder\n description: Creates an order\n type: camel.example.Order\n" + " to:\n uri: direct:create-order\n") + return s +def rc_state(get_id, post, api): + parts = [rc_conf(api), rc_rest(get_id, post), RC_LIST] + ([RC_GET] if get_id else []) + ([RC_CREATE] if post else []) + return "\n".join(parts) +RC_NEW = ('{"orderId": "ORD-1004", "customer": "C-482", "country": "DK", "status": "paid", ' + '"lines": [{"sku": "CAMEL-MUG", "qty": 1, "price": 9.5}]}') +RC_EMPTY = '{"orderId": "ORD-1005", "customer": "C-482", "country": "DK", "status": "paid", "lines": []}' +EXAMPLES.append({ + "name": "contracts-rest-code-first", "route_file": RC, "props_file": "application.properties", "wait_seconds": 8, + "seed": "contracts-rest-code-first", "steps_dir": NEW, + "initial": {RC: rc_state(False, False, False), "application.properties": PORT_PROPS}, + "steps": [ + {"request": "Add GET /orders/{id} to the REST API: answer the order with that id from orders.json, or 404 with {\"error\": \"unknown order <id>\"}.", + "check": {"file_regex": "\\{id\\}", "probes": [GET("/orders/ORD-1002", 200, "\"customer\":\"C-207\""), GET("/orders/ORD-9999", 404, "unknown order ORD-9999")]}, + "reference": {RC: rc_state(True, False, False)}}, + {"request": "Add POST /orders that binds the JSON of a new order with the json binding to a Java record camel.example.Order (orderId, customer, country, status and lines; each line has sku, qty and price). Answer 400 with {\"error\": \"an order needs at least one line\"} when the order has no lines; otherwise log \"Order ${body.orderId} created with ${body.lines.size()} line(s)\" and answer 201 with the order. A new Java class needs a restart of the integration to be compiled; it [...] + "check": {"files": {"Order.java": "record Order"}, "probes": [POST("/orders", RC_NEW, 201, "\"orderId\":\"ORD-1004\""), POST("/orders", RC_EMPTY, 400, "at least one line")], + "log_regex": "Order ORD-1004 created with 1 line\\(s\\)"}, + "reference": {RC: rc_state(True, True, False), "Order.java": RC_ORDER_JAVA}, "restart": True}, + {"request": "Serve the API description that Camel generates from the REST operations at /openapi. The integration is restarted after your change.", + "check": {"probes": [GET("/openapi", 200, "\"/orders/\\{id\\}\"")]}, + "reference": {RC: rc_state(True, True, True), "application.properties": PORT_PROPS + "camel.jbang.dependencies=camel:openapi-java\n"}, "restart": True}, + ]}) + + def write_project(ex): d = os.path.join(PROJECTS, ex["name"]) shutil.rmtree(d, ignore_errors=True) diff --git a/ai-benchmark/seed/contracts-rest-code-first/orders.json b/ai-benchmark/seed/contracts-rest-code-first/orders.json new file mode 100644 index 0000000..ceea517 --- /dev/null +++ b/ai-benchmark/seed/contracts-rest-code-first/orders.json @@ -0,0 +1,5 @@ +[ + {"orderId": "ORD-1001", "customer": "C-482", "country": "DK", "status": "paid", "lines": [{"sku": "CAMEL-TSHIRT", "qty": 2, "price": 19.95}, {"sku": "CAMEL-MUG", "qty": 1, "price": 9.5}]}, + {"orderId": "ORD-1002", "customer": "C-207", "country": "DE", "status": "paid", "lines": [{"sku": "CAMEL-MUG", "qty": 3, "price": 9.5}]}, + {"orderId": "ORD-1003", "customer": "C-134", "country": "US", "status": "pending", "lines": [{"sku": "CAMEL-TSHIRT", "qty": 1, "price": 19.95}, {"sku": "CAMEL-CAP", "qty": 1, "price": 14.0}, {"sku": "CAMEL-MUG", "qty": 2, "price": 9.5}]} +]
