diff --git a/docs/design/sdk-api.md b/docs/design/sdk-api.md index edbcb65..cb860a4 100644 --- a/docs/design/sdk-api.md +++ b/docs/design/sdk-api.md @@ -540,7 +540,11 @@ public sealed interface BehaviorChain> S lookupObject(LookupSpec lookup) // _behaviors.lookup (single-object wire form) S shellTransform(String... commands) // one typed step per command: one is // "shellTransform": "cmd", several go out as - // one behaviors-array element each (#234) + // one behaviors-array element each (#234); + // from rift 0.24.0 a command gets request and + // response JSON as two args (+ MB_REQUEST/ + // MB_RESPONSE) and must print a JSON response + // object, e.g. printf '{"body":"x"}' # (#339) S waitInject(String script) // _behaviors.wait as {"inject": ...}; a rift superset, not portable to Mountebank (rift#608) S waitScript(String source) // _behaviors.wait as a bare function string (the Mountebank-compatible spelling) } diff --git a/rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/ShellTransformReadBackIT.java b/rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/ShellTransformReadBackIT.java index e80b304..091eb50 100644 --- a/rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/ShellTransformReadBackIT.java +++ b/rift-java-conformance/src/test/java/io/github/achirdlabs/rift/conformance/ShellTransformReadBackIT.java @@ -7,6 +7,11 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; import java.util.List; import java.util.stream.Stream; @@ -38,4 +43,21 @@ Stream severalCommandsReadBackFromTheEngine() { } }); } + + @TestFactory + Stream theDocumentedCommandShapeServesItsJson() { + return gatedTo(ConformanceTransport.SPAWN, + "shellTransform is an injection surface; --allow-injection is a spawn-CLI flag", + "a command printing a JSON response object, ending in ' #', sets the body (rift 0.24.0)", () -> { + try (Rift rift = engine()) { + Imposter imposter = rift.create(imposter("shell-json").protocol("http") + .stub(onGet("/").willReturn(status(200).shellTransform("printf '{\"body\":\"hello\"}' #")))); + + HttpResponse answer = HttpClient.newHttpClient().send( + HttpRequest.newBuilder(URI.create(imposter.uri() + "/")).timeout(Duration.ofSeconds(5)).build(), + HttpResponse.BodyHandlers.ofString()); + assertEquals("hello", answer.body()); + } + }); + } } diff --git a/rift-java-core/src/main/java/io/github/achirdlabs/rift/dsl/BehaviorChain.java b/rift-java-core/src/main/java/io/github/achirdlabs/rift/dsl/BehaviorChain.java index 8ab26cf..abd2c35 100644 --- a/rift-java-core/src/main/java/io/github/achirdlabs/rift/dsl/BehaviorChain.java +++ b/rift-java-core/src/main/java/io/github/achirdlabs/rift/dsl/BehaviorChain.java @@ -133,6 +133,16 @@ default S lookupObject(LookupSpec lookup) { * as one {@code behaviors} array element per command, the shape the engine itself echoes. An * injection surface: the engine must run with {@code --allowInjection}. * + *

Each command is passed to the engine verbatim, and the engine runs it with Mountebank's + * contract from rift 0.24.0 (rift#1589): under {@code sh -c}, with the request JSON and the + * response so far (status, headers and body) appended as two shell arguments and also set as + * {@code MB_REQUEST} and {@code MB_RESPONSE}. Its stdout must be a JSON response object, such as + * {@code {"statusCode":200,"body":"hello"}}; output that is not JSON, or a non-zero exit, is an + * error. A command that ignores its arguments should end with {@code #} so the shell treats + * them as a comment: {@code printf '{"body":"hello"}' #}. Before 0.24.0 the engine replaced the + * body with the command's raw stdout, so a command written for that must now print the JSON + * object instead. + * * @throws IllegalArgumentException if no command is given */ default S shellTransform(String... commands) {