Skip to content

Commit

Permalink
added more documentation for velocity and javascript response templat…
Browse files Browse the repository at this point in the history
…es and fix issues with foreach scope and ES6
  • Loading branch information
jamesdbloom committed Mar 16, 2022
1 parent e01ec4c commit 15798d1
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 3 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- added response template variables for date, uuid and random for velocity
- added response template variables for date, uuid and random for javascript
- added path parameters to response template model
- added support for EMCAScript 6 in JavaScript response templates

### Changed
- included Bouncy Castle now used by default to resolve issues with modules in Java 16+ and backwards compatibility for Java 8
Expand Down
107 changes: 105 additions & 2 deletions jekyll-www.mock-server.com/mock_server/response_templates.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ <h2>Response Templates</h2>
</li>
<li><strong><a href="#velocity_templates">velocity templates</a></strong>
<ul>
<li>uses <a href="https://velocity.apache.org/">Apache Velocity</a></li>
<li>uses <a target="_blank" href="https://velocity.apache.org/">Apache Velocity</a></li>
<li>more complex to use</li>
<li>supports more complex logic</li>
<li>works on all JVM versions</li>
Expand Down Expand Up @@ -615,7 +615,6 @@ <h2>Mustache Response Templates</h2>

<h3>Mustache Syntax</h3>


<h4>Variables</h4>

<p>In mustache a variable is referenced using double braces, for example, <code class="inline code">&lbrace;&lbrace; request.method &rbrace;&rbrace;</code> will print the <strong>method</strong> field of the <strong>request</strong>
Expand Down Expand Up @@ -849,6 +848,77 @@ <h2>Velocity Response Templates</h2>
)
);</code></pre>

<h3>Velocity Syntax</h3>

<p>For full Velocity syntax see: <a target="_blank" href="https://velocity.apache.org/engine/1.7/user-guide.html#velocity-template-language-vtl-an-introduction">Velocity User Guide</a> the following section provide a basic summary and examples of Velocity syntax.</p>

<h4>Variables</h4>

<p>In velocity a variable is referenced using a dollar, for example, <code class="inline code">$request.method</code> will print the <strong>method</strong> field of the <strong>request</strong> variable.</p>
<p><code class="inline code">$name</code> will try to find the <strong>name</strong> variable in the current model.</p>
<p>If no matching variable is found for the variable expression the expression is printed, unless a quiet expression notation is used as follows <code class="inline code">$!name</code></p>

<p>The following basic template example demonstrates using multiple variables:</p>

<pre class="prettyprint lang-java code"><code class="code">{
'statusCode': 200,
'body': {
'method': '#!request.method',
'path': '#!request.path',
'headers': '#!request.headers.host.0'
}
}</code></pre>

<p>Methods can also be called on variables, for example, <code class="inline code">$request.getMethod()</code> will call the <strong>getMethod()</strong> method of the <strong>request</strong> variable.</p>

<p>New variables can be defined using a <code class="inline code">#set</code> directive, for example, <code class="inline code">#set ($message="Hello World")</code> will create a new variables call <strong>message</strong>.</p>

<h4>Conditionals</h4>

<p>Conditional expressions are possible using <code class="inline code">#if</code>, <code class="inline code">#elseif</code> and <code class="inline code">#else</code> directives, as folows:</p>

<pre class="prettyprint lang-java code"><code class="code">#if($request.method == 'POST' && $request.path == '/somePath')
{
'statusCode': 200,
'body': "{'name': 'value'}"
}
#else
{
'statusCode': 406,
'body': "$!request.body"
}
#end</code></pre>

<h4>Loops</h4>

<p>Loops are possible using the <code class="inline code">#foreach</code> directives, as follows:</p>

<pre class="prettyprint lang-java code"><code class="code">{
'statusCode': 200,
'body': "{'headers': [#foreach( $value in $request.headers.values() )'$value[0]'#if( $foreach.hasNext ), #end#end]}"
}</code></pre>

<p>The example produces:</p>

<pre class="prettyprint lang-java code"><code class="code">{
"statusCode" : 200,
"body" : "{'headers': ['mock-server.com', 'plain/text']}"
}</code></pre>

<h4>Mathematical</h4>

<p>In addition to the <a href="#dynamic_model_variables">dynamic model variables</a> it is possible to perform basic mathematical operations, as follows:</p>

<pre class="prettyprint lang-java code"><code class="code">#set($percent = $number / 100)
#set($remainder = $dividend % $divisor)</code></pre>

<p>A range operator is also supported which can be useful in conjunction with <code class="inline code">#set</code> and <code class="inline code">#foreach</code></p>

<pre class="prettyprint lang-java code"><code class="code">#set($array = [0..10])
#foreach($item in $arr)
$item
#end</code></pre>

<a id="javascript_templates" class="anchor" href="#javascript_templates">&nbsp;</a>

<h2>JavaScript Response Templates</h2>
Expand All @@ -870,3 +940,36 @@ <h2>JavaScript Response Templates</h2>
"};"
)
);</code></pre>

<h3>JavaScript Syntax</h3>

<p>All JavaScript response templates should return the response as an object as shown in the example above.</p>

<p>JavaScript response templates support ECMAScript 6 (ES6) syntax, for a summary of ES6 see <a target="_blank" href="https://www.w3schools.com/js/js_es6.asp">w3schools</a>.</p>

<h4>Variables</h4>

<p>In javascript a variable is referenced directly, for example, <code class="inline code">request.method</code> will return the <strong>method</strong> field of the <strong>request</strong> variable.</p>

<p>The following basic template example demonstrates using multiple variables:</p>

<pre class="prettyprint lang-java code"><code class="code">return {
'statusCode': 200,
'body': '{\'method\': \'' + request.method + '\', \'path\': \'' + request.path + '\', \'headers\': \'' + request.headers.host[0] + '\'}'
};</code></pre>

<h4>Conditionals</h4>

<p>Conditional expressions are possible, as follows:</p>

<pre class="prettyprint lang-java code"><code class="code">if (request.method === 'POST' && request.path === '/somePath') {
return {
'statusCode': 200,
'body': JSON.stringify({name: 'value'})
};
} else {
return {
'statusCode': 406,
'body': request.body
};
}</code></pre>
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class JavaScriptTemplateEngine implements TemplateEngine {
private HttpTemplateOutputDeserializer httpTemplateOutputDeserializer;

public JavaScriptTemplateEngine(MockServerLogger mockServerLogger) {
System.setProperty("nashorn.args", "--language=es6");
if (engine == null) {
engine = new ScriptEngineManager().getEngineByName("nashorn");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class VelocityTemplateEngine implements TemplateEngine {
velocityProperties.put("directive.parse.max_depth", "10");
velocityProperties.put("context.scope_control.template", "false");
velocityProperties.put("context.scope_control.evaluate", "false");
velocityProperties.put("context.scope_control.foreach", "false");
velocityProperties.put("context.scope_control.foreach", "true");
velocityProperties.put("context.scope_control.macro", "false");
velocityProperties.put("context.scope_control.define", "false");
velocityProperties.put("runtime.strict_mode.enable", "false");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,51 @@ public void resetLogLevel() {
ConfigurationProperties.logLevel(originalLogLevel.name());
}

@Test
public void shouldHandleHttpRequestsWithJavaScriptResponseTemplateWithECMA6() throws JsonProcessingException {
// given
String template = "var customer = { name: \"Foo\" }" + NEW_LINE +
"var card = { amount: 7, product: \"Bar\", unitprice: 42 }" + NEW_LINE +
"return {" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': `Hello ${customer.name}, want to buy ${card.amount} ${card.product} for a total of ${card.amount * card.unitprice} bucks?`" + NEW_LINE +
"};";
HttpRequest request = request()
.withPath("/somePath")
.withMethod("POST")
.withHeader(HOST.toString(), "mock-server.com")
.withBody("some_body");

// when
HttpResponse actualHttpResponse = new JavaScriptTemplateEngine(mockServerLogger).executeTemplate(template, request, HttpResponseDTO.class);

if (new ScriptEngineManager().getEngineByName("nashorn") != null) {
// then
assertThat(actualHttpResponse, is(
response()
.withStatusCode(200)
.withBody("Hello Foo, want to buy 7 Bar for a total of 294 bucks?")
));
verify(mockServerLogger).logEvent(
new LogEntry()
.setType(TEMPLATE_GENERATED)
.setLogLevel(INFO)
.setHttpRequest(request)
.setMessageFormat("generated output:{}from template:{}for request:{}")
.setArguments(OBJECT_MAPPER.readTree("" +
"{" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': \"Hello Foo, want to buy 7 Bar for a total of 294 bucks?\"" + NEW_LINE +
"}" + NEW_LINE),
JavaScriptTemplateEngine.wrapTemplate(template),
request
)
);
} else {
assertThat(actualHttpResponse, nullValue());
}
}

@Test
public void shouldHandleHttpRequestsWithJavaScriptResponseTemplateWithMethodPathAndHeader() throws JsonProcessingException {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.mockserver.uuid.UUIDService;
import org.slf4j.event.Level;

import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -27,6 +28,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderNames.HOST;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -247,6 +249,48 @@ private void shouldPopulateRandomValue(String function, Matcher<Integer> matcher
assertThat(actualHttpResponse.getBodyAsString().length(), matcher);
}

@Test
public void shouldHandleHttpRequestsWithVelocityResponseTemplateWithLoopOverValuesUsingThis() throws JsonProcessingException {
// given
String template = "{" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': \"{'headers': [#foreach( $value in $request.headers.values() )'$value[0]'#if( $foreach.hasNext ), #end#end]}\"" + NEW_LINE +
"}";


HttpRequest request = request()
.withPath("/somePath")
.withMethod("POST")
.withHeader(HOST.toString(), "mock-server.com")
.withHeader(CONTENT_TYPE.toString(), "plain/text")
.withBody("some_body");

// when
HttpResponse actualHttpResponse = new VelocityTemplateEngine(mockServerLogger).executeTemplate(template, request, HttpResponseDTO.class);

// then
assertThat(actualHttpResponse, is(
response()
.withStatusCode(200)
.withBody("{'headers': ['mock-server.com', 'plain/text']}")
));
verify(mockServerLogger).logEvent(
new LogEntry()
.setType(TEMPLATE_GENERATED)
.setLogLevel(INFO)
.setHttpRequest(request)
.setMessageFormat("generated output:{}from template:{}for request:{}")
.setArguments(OBJECT_MAPPER.readTree("" +
"{" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': \"{'headers': ['mock-server.com', 'plain/text']}\"" + NEW_LINE +
"}" + NEW_LINE),
template,
request
)
);
}

@Test
public void shouldHandleHttpRequestsWithVelocityResponseTemplateWithIfElse() throws JsonProcessingException {
// given
Expand Down Expand Up @@ -338,6 +382,45 @@ public void shouldHandleHttpRequestsWithVelocityForwardTemplateWithPathBodyParam
);
}

@Test
public void shouldHandleHttpRequestsWithVelocityResponseTemplateInvalidFields() throws JsonProcessingException {
// given
String template = "{" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': \"{'method': '$!request.method.invalid', 'path': '$request.invalid', 'headers': '$!request.headers.host[0]'}\"" + NEW_LINE +
"}";
HttpRequest request = request()
.withPath("/somePath")
.withMethod("POST")
.withHeader(HOST.toString(), "mock-server.com")
.withBody("some_body".getBytes(StandardCharsets.UTF_8));

// when
HttpResponse actualHttpResponse = new VelocityTemplateEngine(mockServerLogger).executeTemplate(template, request, HttpResponseDTO.class);

// then
assertThat(actualHttpResponse, is(
response()
.withStatusCode(200)
.withBody("{'method': '', 'path': '$request.invalid', 'headers': 'mock-server.com'}")
));
verify(mockServerLogger).logEvent(
new LogEntry()
.setType(TEMPLATE_GENERATED)
.setLogLevel(INFO)
.setHttpRequest(request)
.setMessageFormat("generated output:{}from template:{}for request:{}")
.setArguments(OBJECT_MAPPER.readTree("" +
"{" + NEW_LINE +
" 'statusCode': 200," + NEW_LINE +
" 'body': \"{'method': '', 'path': '$request.invalid', 'headers': 'mock-server.com'}\"" + NEW_LINE +
"}" + NEW_LINE),
template,
request
)
);
}

@Test
public void shouldHandleInvalidVelocityTemplate() {
// given
Expand Down

0 comments on commit 15798d1

Please sign in to comment.