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 496bd36a76aff90c146983580135715491321926 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 11:53:52 2026 +0100 ai-benchmark: connect-sftp-transfer in the new set, a file transfer with the embedded SFTP server of camel infra A file transfer as enterprises run it. The supplier puts invoice batches (CSV) on its SFTP server, in the folder invoices. infra_seed puts the three batches of seed/_sftp-server/ on the server; invoices-2026-11-01.csv has an amount that is not a number. The project starts with a route that reads a local inbox. 1. Connect (host, port, user and password in the request) and download the batches with noop: the log of each download, and the batches are still on the server. 2. Parse the CSV, the count and the total (Groovy) in variables, the log line per batch, and moveFailed: the broken batch is in failed on the server. Its error is expected (errors_ok). The broken batch is moved in this step, since a noop consumer retries a failed file on every poll. 3. Upload a receipt per batch to the folder receipts: the two receipt files on the server, with their text. 4. Move each processed batch to done: the batches are in done, and no longer in invoices. The step has no log check, because a second write would reload and find no batches left to log. Checks with ** globs accept invoices/failed and a top-level failed folder. The reference keeps the server's host key out of ~/.ssh/known_hosts (useUserKnownHostsFile false). Reference pass: 4 of 4, twice. Negative pass: 0 of 4. Model run (k=1, qwen3.6:35b-mlx): steps 1 and 4 passed. Steps 2 and 3 used all 25 calls: the model asked camel_catalog_doc the same question 22 and 17 times. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_016Y4dDi5iQNgtHrDdz2NpwF --- ai-benchmark/gen_stepwise.py | 66 ++++++++++++++++++++++ .../_sftp-server/invoices/invoices-2026-10-30.csv | 3 + .../_sftp-server/invoices/invoices-2026-10-31.csv | 4 ++ .../_sftp-server/invoices/invoices-2026-11-01.csv | 3 + .../inbox/invoices-2026-10-29.csv | 3 + 5 files changed, 79 insertions(+) diff --git a/ai-benchmark/gen_stepwise.py b/ai-benchmark/gen_stepwise.py index be3c80a..04ee906 100644 --- a/ai-benchmark/gen_stepwise.py +++ b/ai-benchmark/gen_stepwise.py @@ -1123,6 +1123,72 @@ EXAMPLES.append({ ]}) +# ---------------------------------------------------------------- connect-service/sftp-transfer (the embedded SFTP server of camel infra) +# Claus, Nov 2: a file transfer as enterprises run it. The supplier puts its invoice batches on its SFTP server; the route +# downloads them, parses each batch, uploads a receipt, moves a batch that fails to failed and, in the last step, each +# processed batch to done. The server serves the folder it was started in, and infra_seed puts the batches of +# seed/_sftp-server/ there. They stay on the server (noop) until the last step, so every reload downloads them again, +# as the other examples read their files again. The broken batch is moved away at once (moveFailed): a noop consumer +# would retry a batch that fails on every poll. +ST = "sftp-transfer.camel.yaml" +ST_PROPS = ("# The SFTP server started by: camel infra run sftp\n" + "sftp.host=localhost\nsftp.port=2222\nsftp.username=admin\nsftp.password=admin\n") +def sftp_params(directory, ind, extra=""): + i = " " * ind + return (i + "host: \"{{sftp.host}}\"\n" + i + "port: \"{{sftp.port}}\"\n" + i + "directoryName: " + directory + "\n" + + i + "username: \"{{sftp.username}}\"\n" + i + "password: \"{{sftp.password}}\"\n" + # the reference keeps the test server's host key out of ~/.ssh/known_hosts + + i + "useUserKnownHostsFile: false\n" + extra) +def st_from(extra): + return " uri: sftp\n parameters:\n" + sftp_params("invoices", 8, extra + " sortBy: \"file:name\"\n") +ST_INBOX = " uri: file\n parameters:\n directoryName: inbox\n noop: true\n sortBy: \"file:name\"\n" +ST_NOOP = " noop: true\n moveFailed: failed\n" +ST_DONE = " move: done\n moveFailed: failed\n" +ST_PARSE = (" - unmarshal:\n csv:\n useMaps: true\n" + " - setVariable:\n name: count\n expression:\n simple:\n" + " expression: \"${body.size()}\"\n" + " - setVariable:\n name: total\n expression:\n groovy:\n" + " expression: \"body.sum { it.amount as BigDecimal }\"\n" + + log("Batch ${file:name}: ${variable.count} invoices, total ${variable.total} EUR")) +ST_RECEIPT = (" - setBody:\n expression:\n simple:\n" + " expression: \"Received ${variable.count} invoices, total ${variable.total} EUR\"\n" + " - to:\n uri: sftp\n parameters:\n" + + sftp_params("receipts", 14, " fileName: \"${file:name.noext}.txt\"\n")) +ST_DOWNLOADED = log("Downloaded ${file:name}") +st_s0 = route("invoice-batches", ST_INBOX, log("Received ${file:name}")) +st_s1 = route("invoice-batches", st_from(" noop: true\n"), ST_DOWNLOADED) +st_s2 = route("invoice-batches", st_from(ST_NOOP), ST_DOWNLOADED + ST_PARSE) +st_s3 = route("invoice-batches", st_from(ST_NOOP), ST_DOWNLOADED + ST_PARSE + ST_RECEIPT) +st_s4 = route("invoice-batches", st_from(ST_DONE), ST_DOWNLOADED + ST_PARSE + ST_RECEIPT) +# 77.90 as a BigDecimal sum, 77.9 as a double one: either is the total +ST_BATCHES = ["Batch invoices-2026-10-30\\.csv: 2 invoices, total 77\\.90? EUR", + "Batch invoices-2026-10-31\\.csv: 3 invoices, total 82\\.90? EUR"] +EXAMPLES.append({ + "name": "connect-sftp-transfer", "route_file": ST, "props_file": "application.properties", "wait_seconds": 10, + "seed": "connect-sftp-transfer", "infra": ["sftp"], "infra_seed": {"sftp": "_sftp-server"}, "steps_dir": NEW, + "initial": {ST: st_s0, "application.properties": "# application properties\n"}, + "steps": [ + {"request": "The supplier now puts its invoice batches on its SFTP server. Connect to the SFTP server that camel infra started (host localhost, port 2222, user admin, password admin) and download the batches from its folder invoices instead of reading the local inbox. Leave the files on the server for now (noop), and log \"Downloaded ${file:name}\" instead of received.", + # the batches are still on the server: a route that deleted or moved them took the next steps' files away + "check": {"file_regex": "sftp", "log_regex": ["Downloaded invoices-2026-10-30\\.csv", "Downloaded invoices-2026-10-31\\.csv"], + "server_files": {"invoices/invoices-2026-10-30.csv": "INV-3001", "invoices/invoices-2026-10-31.csv": "INV-3003"}}, + "reference": {ST: st_s1, "application.properties": ST_PROPS}}, + {"request": "Parse each batch as CSV with a header line, one map per invoice. Keep the number of invoices in a variable count and the sum of the amount column in a variable total (a Groovy expression can sum it), and log \"Batch ${file:name}: ${variable.count} invoices, total ${variable.total} EUR\". The batch invoices-2026-11-01.csv has an amount that is not a number, so it fails: move a batch that fails to the folder failed on the server.", + # the broken batch fails once per reload until it is moved away: its error is expected + "check": {"log_regex": ST_BATCHES, "server_files": {"**/failed/invoices-2026-11-01.csv": "INV-3006"}, "errors_ok": True}, + "reference": {ST: st_s2}}, + {"request": "Upload a receipt for each batch to the folder receipts on the server: a file named like the batch, but ending in .txt instead of .csv, with the text \"Received ${variable.count} invoices, total ${variable.total} EUR\".", + "check": {"server_files": {"**/receipts/invoices-2026-10-30.txt": "Received 2 invoices, total 77\\.90? EUR", + "**/receipts/invoices-2026-10-31.txt": "Received 3 invoices, total 82\\.90? EUR"}}, + "reference": {ST: st_s3}}, + {"request": "Stop leaving the processed batches on the server: move each batch to a folder done once it is processed, while a batch that fails still goes to failed.", + # a log check would fail a model that wrote twice: the second reload finds no batches left to log + "check": {"server_files": {"**/done/invoices-2026-10-30.csv": "INV-3001", "**/done/invoices-2026-10-31.csv": "INV-3003"}, + "server_not_files": ["invoices/invoices-2026-10-30.csv", "invoices/invoices-2026-10-31.csv"]}, + "reference": {ST: st_s4}}, + ]}) + + def write_project(ex): d = os.path.join(PROJECTS, ex["name"]) shutil.rmtree(d, ignore_errors=True) diff --git a/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-30.csv b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-30.csv new file mode 100644 index 0000000..ebc5fdc --- /dev/null +++ b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-30.csv @@ -0,0 +1,3 @@ +invoiceId,orderId,amount,currency +INV-3001,ORD-1001,49.40,EUR +INV-3002,ORD-1002,28.50,EUR diff --git a/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-31.csv b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-31.csv new file mode 100644 index 0000000..8fe8643 --- /dev/null +++ b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-10-31.csv @@ -0,0 +1,4 @@ +invoiceId,orderId,amount,currency +INV-3003,ORD-1003,53.45,EUR +INV-3004,ORD-1004,19.95,EUR +INV-3005,ORD-1005,9.50,EUR diff --git a/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-11-01.csv b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-11-01.csv new file mode 100644 index 0000000..a7be14b --- /dev/null +++ b/ai-benchmark/seed/_sftp-server/invoices/invoices-2026-11-01.csv @@ -0,0 +1,3 @@ +invoiceId,orderId,amount,currency +INV-3006,ORD-1006,n/a,EUR +INV-3007,ORD-1007,12.00,EUR diff --git a/ai-benchmark/seed/connect-sftp-transfer/inbox/invoices-2026-10-29.csv b/ai-benchmark/seed/connect-sftp-transfer/inbox/invoices-2026-10-29.csv new file mode 100644 index 0000000..53b20d0 --- /dev/null +++ b/ai-benchmark/seed/connect-sftp-transfer/inbox/invoices-2026-10-29.csv @@ -0,0 +1,3 @@ +invoiceId,orderId,amount,currency +INV-2990,ORD-0990,15.00,EUR +INV-2991,ORD-0991,32.50,EUR
