Chapter 8: Traffic Parrot programmatic setup using REST APIs

« Back to tutorials home

API reference

The canonical references for Traffic Parrot's programmatic-setup REST APIs are:

  • OpenAPI documentation — the full request/response contract for every endpoint, including request bodies, status codes, and media types. Use this as the source of truth.
  • Postman collection — interactive request explorer with pre-populated examples. Useful for poking at endpoints before scripting them into a pipeline.

The rest of this page works through one concrete example: a CI/CD job that toggles a scenario, adds it to the Composite, asserts that the system under test called the expected endpoints, and cleans up after itself. Treat it as an illustration of how to use the APIs above — the full API surface is in the references.

Example: what this guide builds

The walkthrough below drives a running Traffic Parrot instance from the command line and from Java code. You will not click a single button in the UI — everything happens through HTTP requests, which is what you need when Traffic Parrot is part of a CI/CD pipeline.

Specifically, the example covers:

  1. Switching a test scenario on and off without restarting Traffic Parrot
  2. Adding a scenario to the Composite Scenario so it is served alongside others
  3. Listing the HTTP requests Traffic Parrot has received, to assert that the system under test made the calls you expected
  4. Listing all configured scenarios as a sanity check at pipeline start

Each step shows two flavours of the same call: a one-line curl invocation suitable for a bash step in any CI runner, and an equivalent Java 11+ HttpClient snippet for engineers who prefer to drive Traffic Parrot from JUnit setup methods or build-tool plugins.

Prerequisites

To follow this chapter you should first:

  • Be familiar with what Traffic Parrot is and what a scenario is. If not, please start with Chapter 1: Getting started with stubbing, mocking and service virtualization.
  • Have a running Traffic Parrot instance on your machine. Listening on the default management port 8080 is assumed throughout the examples — adjust if you have configured a different port.
  • Have curl available on the command line for the shell examples.
  • Have Java 11 or later installed for the HttpClient examples. The standard library java.net.http.HttpClient is used — no third-party HTTP library is needed.

All examples in this chapter target Traffic Parrot 5.59.x.

Switch a scenario on or off

A scenario in Traffic Parrot is either enabled (its mappings are being served on the configured port) or disabled. Toggling that flag is one of the most common pipeline operations — you typically enable a fixture scenario at the start of a job and disable it at the end so that subsequent jobs start from a clean baseline.

Use PUT /api/scenarios/{name} with a JSON body that names the new state. The body fields that are omitted keep their previous values, so you can send a minimal update.

curl

$ curl -X PUT http://localhost:8080/api/scenarios/checkoutHappyPath \
    -H "Content-Type: application/vnd.trafficparrot.scenarios.v1+json" \
    -H "Accept: application/vnd.trafficparrot.scenarios.v1+json" \
    -d '{"name":"checkoutHappyPath","enabled":true}'

The versioned application/vnd.trafficparrot.scenarios.v1+json media type is preferred over plain application/json — it pins the request to the v1 contract so future API revisions cannot silently change the shape of the response your pipeline parses.

On success the response is 200 OK with the full updated scenario as JSON. On a port conflict the response is 412 Precondition Failed with a plain-text body explaining which port is busy.

Java HttpClient

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/api/scenarios/checkoutHappyPath"))
        .header("Content-Type", "application/vnd.trafficparrot.scenarios.v1+json")
        .header("Accept", "application/vnd.trafficparrot.scenarios.v1+json")
        .PUT(HttpRequest.BodyPublishers.ofString(
                "{\"name\":\"checkoutHappyPath\",\"enabled\":true}"))
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
    throw new IllegalStateException("Scenario toggle failed: " + response.statusCode() + " " + response.body());
}

Add a scenario to the Composite Scenario

The Composite Scenario aggregates the mappings of several sub-scenarios on a single shared port. This is useful when one virtual service needs to behave like several upstream services at once, or when an end-to-end test depends on a combination of scenarios being active together.

Use POST /api/scenarios/http/composite/add with a scenario query parameter. The request body is empty.

curl

$ curl -X POST "http://localhost:8080/api/scenarios/http/composite/add?scenario=checkoutHappyPath" \
    -H "Accept: application/json"

On success the response is 200 OK with a small confirmation message:

{"message": "Scenario 'checkoutHappyPath' added to Composite"}

If the named scenario does not exist the response is 400 Bad Request with a JSON error field. Treat anything outside the 2xx range as a build failure in your pipeline.

Java HttpClient

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/api/scenarios/http/composite/add?scenario=checkoutHappyPath"))
        .header("Accept", "application/json")
        .POST(HttpRequest.BodyPublishers.noBody())
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
    throw new IllegalStateException("Compose-add failed: " + response.statusCode() + " " + response.body());
}

The matching POST /api/scenarios/http/composite/remove?scenario=... endpoint takes a scenario back out of the composite. You will typically pair the two around each test job.

Inspect the HTTP requests received

After the system under test has finished its run, you usually want to assert that it actually called the endpoints you mocked. GET /api/http/requests returns the journal of every HTTP request Traffic Parrot has received, with method, URL, headers, body, and whether the request matched a stubbed mapping.

curl

$ curl http://localhost:8080/api/http/requests \
    -H "Accept: application/json"

The response shape is roughly:

{
  "requestJournalDisabled": false,
  "meta": { "total": 2 },
  "requests": [
    { "request": { "url": "/api/checkout", "method": "POST" }, "wasMatched": true, "...": "..." },
    { "request": { "url": "/api/checkout", "method": "GET"  }, "wasMatched": true, "...": "..." }
  ]
}

Note the nesting: each entry has the inbound request fields (url, method, headers, body) under a request sub-object, while wasMatched sits at the top level alongside the entry's responseDefinition and timing.

Loop over requests in your pipeline and assert what you need. DELETE /api/http/requests clears the journal — useful as a per-test or per-job reset.

Java HttpClient

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/api/http/requests"))
        .header("Accept", "application/json")
        .GET()
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// response.body() is the JSON above; parse with your preferred library and
// assert that the expected endpoints appear with wasMatched == true.

List all configured scenarios

Before kicking off a test job it is worth checking that the fixture scenarios your tests expect are actually loaded — otherwise the failure later in the run is harder to diagnose. GET /api/scenarios returns every scenario currently known to Traffic Parrot, including ports and enabled state.

curl

$ curl http://localhost:8080/api/scenarios \
    -H "Accept: application/vnd.trafficparrot.scenarios.v1+json"

Sample response (abridged):

[
  { "name": "Composite",          "enabled": true,  "httpPort": 8080, "httpsPort": null },
  { "name": "checkoutHappyPath",  "enabled": false, "httpPort": 8090, "httpsPort": null },
  { "name": "checkoutCardDecline","enabled": false, "httpPort": 8091, "httpsPort": null }
]

Java HttpClient

HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://localhost:8080/api/scenarios"))
        .header("Accept", "application/vnd.trafficparrot.scenarios.v1+json")
        .GET()
        .build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Parse the array and assert the names you depend on are present.

Traffic Parrot programmatic setup in a CI pipeline

A typical job that drives Traffic Parrot for a single integration test step looks like this. The example is shell so it slots into GitHub Actions, GitLab CI, Jenkins, or any other runner without translation.

#!/usr/bin/env bash
set -euo pipefail
TP_URL="${TP_URL:-http://localhost:8080}"

# 1. Sanity check: required fixture scenarios are loaded.
curl --fail --silent "$TP_URL/api/scenarios" \
    -H "Accept: application/vnd.trafficparrot.scenarios.v1+json" \
    | grep -q '"checkoutHappyPath"'

# 2. Enable the fixture and add it to the composite for this job.
curl --fail --silent -X PUT "$TP_URL/api/scenarios/checkoutHappyPath" \
    -H "Content-Type: application/vnd.trafficparrot.scenarios.v1+json" \
    -d '{"name":"checkoutHappyPath","enabled":true}' > /dev/null
curl --fail --silent -X POST "$TP_URL/api/scenarios/http/composite/add?scenario=checkoutHappyPath" > /dev/null

# 3. Run the system under test (omitted) — it should call into Traffic Parrot.
./gradlew :acceptance:test

# 4. Assert that the expected requests were received.
curl --fail --silent "$TP_URL/api/http/requests" | grep -q '"/api/checkout"'

# 5. Teardown: take the scenario out of the composite and clear the journal.
curl --fail --silent -X POST "$TP_URL/api/scenarios/http/composite/remove?scenario=checkoutHappyPath" > /dev/null
curl --fail --silent -X DELETE "$TP_URL/api/http/requests" > /dev/null

Three things make this template robust enough to copy into a real pipeline: set -euo pipefail so any failing curl aborts the job; --fail on each curl so a non-2xx response is surfaced as a non-zero exit code; and a teardown step that runs whether the test passed or failed (move it into a trap if your runner does not have an explicit always-run hook).

Troubleshooting

  • Symptom: 415 Unsupported Media Type on PUT /api/scenarios/{name}. Fix: add the Content-Type: application/vnd.trafficparrot.scenarios.v1+json header (or plain application/json) on the request — Traffic Parrot rejects the request when the body media type is not declared.
  • Symptom: 404 Not Found on a scenario name you are sure exists. Fix: scenario names are case-sensitive and URL-encoded. Check the spelling against the output of GET /api/scenarios.
  • Symptom: 412 Precondition Failed on PUT /api/scenarios/{name} with a port-in-use message. Fix: another scenario is already listening on the requested port. Disable it first or pick a different port.
  • Symptom: connection refused from curl. Fix: confirm the Traffic Parrot management port. The default is 8080 — if you set trafficparrot.gui.http.port to a different value in trafficparrot.properties, use that.
  • Symptom: GET /api/http/requests returns an empty requests array even though tests ran. Fix: check that requestJournalDisabled is false in the response — the request journal is disabled by default in some performance-tuning profiles. Re-enable it in the scenario settings.

Next steps

Continue with Chapter 6a: API first development with gRPCurl to see a different angle on driving Traffic Parrot from outside, or browse the Traffic Parrot OpenAPI documentation for the full API surface. The Postman collection is also handy for exploring requests interactively before scripting them.