smartrics.rest.fitnesse.fixture.RestFixture Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smartrics-RestFixture Show documentation
Show all versions of smartrics-RestFixture Show documentation
The RestFixture is a FitNesse (http://fitnesse.org) fixture that allows
developers and/or product owners to write test fixtures for REST services
with simplicity in mind. The idea is to write tests that are self
documenting and easy to write and read, without the need to write Java code.
The fixture allows test writers to express tests as actions (any of the
allowed HTTP methods) to operate on resource URIs and express expectations on
the content of the return code, headers and body. All without writing one
single line of Java code.
And it also works as a living/executable documentation of the API.
/* Copyright 2008 Fabrizio Cannizzo
*
* This file is part of RestFixture.
*
* RestFixture (http://code.google.com/p/rest-fixture/) is free software:
* you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* RestFixture is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with RestFixture. If not, see .
*
* If you want to contact the author please leave a comment here
* http://smartrics.blogspot.com/2008/08/get-fitnesse-with-some-rest.html
*/
package smartrics.rest.fitnesse.fixture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fitnesse.slim.StatementExecutorConsumer;
import fitnesse.slim.StatementExecutorInterface;
import smartrics.rest.client.RestClient;
import smartrics.rest.client.RestData.Header;
import smartrics.rest.client.RestRequest;
import smartrics.rest.client.RestResponse;
import smartrics.rest.fitnesse.fixture.support.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* A fixture that allows to simply test REST APIs with minimal efforts. The core
* principles underpinning this fixture are:
*
* - allowing documentation of a REST API by showing how the API looks like.
* For REST this means
*
* - show what the resource URI looks like. For example
*
/resource-a/123/resource-b/234
* - show what HTTP operation is being executed on that resource. Specifically
* which one fo the main HTTP verbs where under test (GET, POST, PUT, DELETE,
* HEAD, OPTIONS).
*
- have the ability to set headers and body in the request
*
- check expectations on the return code of the call in order to document
* the behaviour of the API
*
- check expectation on the HTTP headers and body in the response. Again, to
* document the behaviour
*
* - should work without the need to write/maintain java code: tests are
* written in wiki syntax.
*
- tests should be easy to write and above all read.
*
*
* Configuring RestFixture
* RestFixture can be configured by using the {@link RestFixtureConfig}. A
* {@code RestFixtureConfig} can define named maps with configuration key/value
* pairs. The name of the map is passed as second parameter to the
* {@code RestFixture}. Using a named configuration is optional: if no name is
* passed, the default configuration map is used. See {@link RestFixtureConfig}
* for more details.
*
* The following list of configuration parameters can are supported.
*
*
*
* smartrics.rest.fitnesse.fixture.RestFixtureConfig
* optional named config
*
*
* http.proxy.host
* http proxy host name (RestClient proxy configuration)
*
*
* http.proxy.port
* http proxy host port (RestClient proxy configuration)
*
*
* http.basicauth.username
* username for basic authentication (RestClient proxy configuration)
*
*
*
* http.basicauth.password
* password for basic authentication (RestClient proxy configuration)
*
*
*
* http.client.connection.timeout
* client timeout for http connection (default 3s). (RestClient proxy
* configuration)
*
*
*
* http.client.use.new.http.uri.factory
* If set to true uses a more relaxed validation rule to validate URIs.
* It, for example, allows array parameters in the query string. Defaults to
* false.
*
*
* restfixture.requests.follow.redirects
* If set to true the underlying client is instructed to follow redirects
* for the requests in the current fixture. This setting is not applied to POST
* and PUT (for which redirection is set to false) Defaults to true.
*
*
* restfixture.resource.uris.are.escaped
* boolean value. if true, RestFixture will assume that the resource uris
* are already escaped. If false, resource uri will be escaped. Defaults to
* false.
*
*
* restfixture.display.actual.on.right
* boolean value. if true, the actual value of the header or body in an
* expectation cell is displayed even when the expectation is met.
*
*
* restfixture.default.headers
* comma separated list of key value pairs representing the default list
* of headers to be passed for each request. key and values are separated by a
* colon. Entries are sepatated by \n. {@link RestFixture#setHeader()} will
* override this value.
*
*
* restfixture.xml.namespaces.context
* comma separated list of key value pairs representing namespace
* declarations. The key is the namespace alias, the value is the namespace URI.
* alias and URI are separated by a = sign. Entries are sepatated by
* {@code System.getProperty("line.separator")}. These entries will be used to
* define the namespace context to be used in xpaths that are evaluated in the
* results.
*
*
* restfixture.content.default.charset
* The default charset name (e.g. UTF-8) to use when parsing the response
* body, when a response doesn't contain a valid value in the Content-Type
* header. If a default is not specified with this property, the fixture will
* use the default system charset, available via
* Charset.defaultCharset().name()
*
*
* restfixture.content.handlers.map
* a map of contenty type to type adapters, entries separated by \n, and
* kye-value separated by '='. Available type adapters are JS, TEXT, JSON, XML
* (see {@link smartrics.rest.fitnesse.fixture.support.BodyTypeAdapterFactory}
* ).
*
*
* restfixture.null.value.representation
* This string is used in replacement of the default string substituted
* when a null value is set for a symbol. Because now the RestFixture labels
* support is implemented on top of the Fitnesse symbols, such default value is
* defined in Fitnesse, and that is the string 'null'. Hence, every substitution
* that would result in rendering the string 'null' is replaced with the value
* set for this config key. This value can also be the empty string to replace
* null with empty.
*
*
*
*
* @author smartrics
*/
public class RestFixture implements StatementExecutorConsumer, RunnerVariablesProvider {
/**
* What runner this table is running on.
*
* Note, the OTHER runner is primarily for testing purposes.
*
* @author smartrics
*
*/
public enum Runner {
/**
* the slim runner
*/
SLIM,
/**
* the fit runner
*/
FIT,
/**
* any other runner
*/
OTHER;
};
/* (non-Javadoc)
* @see smartrics.rest.fitnesse.fixture.RunnerVariablesProvider#createRunnerVariables()
*/
@Override
public Variables createRunnerVariables() {
switch (runner) {
case SLIM:
return new SlimVariables(config, slimStatementExecutor);
case FIT:
return new FitVariables(config);
default:
// Use FitVariables for tests
return new FitVariables(config);
}
}
private static final String LINE_SEPARATOR = "\n";
private static final String FILE = "file";
private static final Logger LOG = LoggerFactory.getLogger(RestFixture.class);
protected Variables GLOBALS;
private RestResponse lastResponse;
private RestRequest lastRequest;
protected String fileName = null;
protected String multipartFileName = null;
protected String multipartFileParameterName = FILE;
protected String requestBody;
protected boolean resourceUrisAreEscaped = false;
protected Map requestHeaders;
private RestClient restClient;
private Config config;
private Runner runner;
private boolean displayActualOnRight;
private boolean debugMethodCall = false;
/**
* the headers passed to each request by default.
*/
private Map defaultHeaders = new HashMap();
private Map namespaceContext = new HashMap();
private Url baseUrl;
@SuppressWarnings("rawtypes")
protected RowWrapper row;
private CellFormatter> formatter;
private PartsFactory partsFactory;
private String lastEvaluation;
private int minLenForCollapseToggle;
private boolean followRedirects = true;
private StatementExecutorInterface slimStatementExecutor;
/**
* Constructor for Fit runner.
*/
public RestFixture() {
super();
this.partsFactory = new PartsFactory(this);
this.displayActualOnRight = true;
this.minLenForCollapseToggle = -1;
this.resourceUrisAreEscaped = false;
}
/**
* Constructor for Slim runner.
*
* @param hostName
* the cells following up the first cell in the first row.
*/
public RestFixture(String hostName) {
this(hostName, Config.DEFAULT_CONFIG_NAME);
}
/**
* Constructor for Slim runner.
*
* @param hostName
* the cells following up the first cell in the first row.
* @param configName
* the value of cell number 3 in first row of the fixture table.
*/
public RestFixture(String hostName, String configName) {
this.displayActualOnRight = true;
this.minLenForCollapseToggle = -1;
this.partsFactory = new PartsFactory(this);
this.config = Config.getConfig(configName);
this.baseUrl = new Url(stripTag(hostName));
}
/**
* @param partsFactory
* the factory of parts necessary to create the rest fixture
* @param hostName
* @param configName
*/
public RestFixture(PartsFactory partsFactory, String hostName,
String configName) {
this.displayActualOnRight = true;
this.minLenForCollapseToggle = -1;
this.partsFactory = partsFactory;
this.config = Config.getConfig(configName);
this.baseUrl = new Url(stripTag(hostName));
}
/**
* @return the config used for this fixture instance
*/
public Config getConfig() {
return config;
}
/**
* @return the result of the last evaluation performed via evalJs.
*/
public String getLastEvaluation() {
return lastEvaluation;
}
/**
* The base URL as defined by the rest fixture ctor or input args.
*
* @return the base URL as string
*/
public String getBaseUrl() {
if (baseUrl != null) {
return baseUrl.toString();
}
return null;
}
/**
* sets the base url.
*
* @param url
*/
public void setBaseUrl(Url url) {
this.baseUrl = url;
}
/**
* The default headers as defined in the config used to initialise this
* fixture.
*
* @return the map of default headers.
*/
public Map getDefaultHeaders() {
return defaultHeaders;
}
/**
* The formatter for this instance of the RestFixture.
*
* @return the formatter for the cells
*/
public CellFormatter> getFormatter() {
return formatter;
}
/**
* Slim Table table hook.
*
* @param rows
* @return the rendered content.
*/
public List> doTable(List> rows) {
initialize(Runner.SLIM);
List> res = new Vector>();
getFormatter().setDisplayActual(displayActualOnRight);
getFormatter().setMinLenghtForToggleCollapse(minLenForCollapseToggle);
for (List r : rows) {
processSlimRow(res, r);
}
return res;
}
/**
* Overrideable method to validate the state of the instance in execution. A
* {@link RestFixture} is valid if the baseUrl is not null.
*
* @return true if the state is valid, false otherwise
*/
protected boolean validateState() {
return baseUrl != null;
}
protected void setConfig(Config c) {
this.config = c;
}
/**
* Method invoked to notify that the state of the RestFixture is invalid. It
* throws a {@link RuntimeException} with a message displayed in the
* FitNesse page.
*
* @param state
* as returned by {@link RestFixture#validateState()}
*/
protected void notifyInvalidState(boolean state) {
if (!state) {
throw new RuntimeException(
"You must specify a base url in the |start|, after the fixture to start");
}
}
/**
* Allows setting of the name of the multi-part file to upload.
*
* | setMultipartFileName | Name of file |
*
* body text should be location of file which needs to be sent
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setMultipartFileName() {
CellWrapper cell = row.getCell(1);
if (cell == null) {
getFormatter().exception(row.getCell(0),
"You must pass a multipart file name to set");
} else {
multipartFileName = GLOBALS.substitute(cell.text());
renderReplacement(cell, multipartFileName);
}
}
/**
* @return the multipart filename
*/
public String getMultipartFileName() {
return multipartFileName;
}
/**
* Allows setting of the name of the file to upload.
*
* | setFileName | Name of file |
*
* body text should be location of file which needs to be sent
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setFileName() {
CellWrapper cell = row.getCell(1);
if (cell == null) {
getFormatter().exception(row.getCell(0),
"You must pass a file name to set");
} else {
fileName = GLOBALS.substitute(cell.text());
renderReplacement(cell, fileName);
}
}
/**
* @return the filename
*/
public String getFileName() {
return fileName;
}
/**
* Sets the parameter to send in the request storing the multi-part file to
* upload. If not specified the default is file
*
* | setMultipartFileParameterName | Name of form parameter for the uploaded file |
*
* body text should be the name of the form parameter, defaults to 'file'
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setMultipartFileParameterName() {
CellWrapper cell = row.getCell(1);
if (cell == null) {
getFormatter().exception(row.getCell(0),
"You must pass a parameter name to set");
} else {
multipartFileParameterName = GLOBALS.substitute(cell.text());
renderReplacement(cell, multipartFileParameterName);
}
}
/**
* @return the multipart file parameter name.
*/
public String getMultipartFileParameterName() {
return multipartFileParameterName;
}
/**
* | setBody | body text goes here |
*
* body text can either be a kvp or a xml. The ClientHelper
* will figure it out
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setBody() {
CellWrapper cell = row.getCell(1);
if (cell == null) {
getFormatter().exception(row.getCell(0), "You must pass a body to set");
} else {
String text = getFormatter().fromRaw(cell.text());
requestBody = GLOBALS.substitute(text);
renderReplacement(cell, requestBody);
}
}
/**
* | setHeader | http headers go here as nvp |
*
* header text must be nvp. name and value must be separated by ':' and each
* header is in its own line
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setHeader() {
CellWrapper cell = row.getCell(1);
if (cell == null) {
getFormatter().exception(row.getCell(0),
"You must pass a header map to set");
} else {
String substitutedHeaders = GLOBALS.substitute(cell.text());
requestHeaders = parseHeaders(substitutedHeaders);
cell.body(getFormatter().gray(substitutedHeaders));
}
}
/**
* Equivalent to setHeader - syntactic sugar to indicate that you can now.
*
* set multiple headers in a single call
*/
public void setHeaders() {
setHeader();
}
/**
* | PUT | URL | ?ret | ?headers | ?body |
*
* executes a PUT on the URL and checks the return (a string representation
* the operation return code), the HTTP response headers and the HTTP
* response body
*
* URL is resolved by replacing global variables previously defined with
* let()
*
* the HTTP request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void PUT() {
debugMethodCallStart();
doMethod(emptifyBody(requestBody), "Put");
debugMethodCallEnd();
}
/**
* | GET | uri | ?ret | ?headers | ?body |
*
* executes a GET on the uri and checks the return (a string repr the
* operation return code), the http response headers and the http response
* body
*
* uri is resolved by replacing vars previously defined with
* let()
*
* the http request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void GET() {
debugMethodCallStart();
doMethod("Get");
debugMethodCallEnd();
}
/**
* | HEAD | uri | ?ret | ?headers | |
*
* executes a HEAD on the uri and checks the return (a string repr the
* operation return code) and the http response headers. Head is meant to
* return no-body.
*
* uri is resolved by replacing vars previously defined with
* let()
*
* the http request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void HEAD() {
debugMethodCallStart();
doMethod("Head");
debugMethodCallEnd();
}
/**
* | OPTIONS | uri | ?ret | ?headers | ?body |
*
* executes a OPTIONS on the uri and checks the return (a string repr the
* operation return code), the http response headers, the http response body
*
* uri is resolved by replacing vars previously defined with
* let()
*
* the http request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void OPTIONS() {
debugMethodCallStart();
doMethod("Options");
debugMethodCallEnd();
}
/**
* | DELETE | uri | ?ret | ?headers | ?body |
*
* executes a DELETE on the uri and checks the return (a string repr the
* operation return code), the http response headers and the http response
* body
*
* uri is resolved by replacing vars previously defined with
* let()
*
* the http request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void DELETE() {
debugMethodCallStart();
doMethod("Delete");
debugMethodCallEnd();
}
/**
* | TRACE | uri | ?ret | ?headers | ?body |
*/
public void TRACE() {
debugMethodCallStart();
doMethod("Trace");
debugMethodCallEnd();
}
/**
* | POST | uri | ?ret | ?headers | ?body |
*
* executes a POST on the uri and checks the return (a string repr the
* operation return code), the http response headers and the http response
* body
*
* uri is resolved by replacing vars previously defined with
* let()
*
* post requires a body that can be set via setBody()
.
*
* the http request headers can be set via setHeaders()
. If not
* set, the list of default headers will be set. See
* DEF_REQUEST_HEADERS
*/
public void POST() {
debugMethodCallStart();
doMethod(emptifyBody(requestBody), "Post");
debugMethodCallEnd();
}
/**
* | let | label | type | loc | expr |
*
* allows to associate a value to a label. values are extracted from the
* body of the last successful http response.
*
* label
is the label identifier
*
* type
is the type of operation to perform on the last
* http response. At the moment only XPaths and Regexes are supported. In
* case of regular expressions, the expression must contain only one group
* match, if multiple groups are matched the label will be assigned to the
* first found type
only allowed values are xpath
* and regex
*
* loc
where to apply the expr
of the given
* type
. Currently only header
and
* body
are supported. If type is xpath
by default
* the expression is matched against the body and the value in loc is
* ignored.
*
* expr
is the expression of type type
to be
* executed on the last http response to extract the content to be
* associated to the label.
*
*
* label
s can be retrieved after they have been defined and
* their scope is the fixture instance under execution. They are stored in a
* map so multiple calls to let()
with the same label will
* override the current value of that label.
*
* Labels are resolved in uri
s, header
s and
* body
es.
*
* In order to be resolved a label must be between %
, e.g.
* %id%
.
*
* The test row must have an empy cell at the end that will display the
* value extracted and assigned to the label.
*
* Example:
* | GET | /services | 200 | | |
* | let | id | body | /services/id[0]/text() | |
* | GET | /services/%id% | 200 | | |
*
* or
*
* | POST | /services | 201 | | |
* | let | id | header | /services/([.]+) | |
* | GET | /services/%id% | 200 | | |
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void let() {
debugMethodCallStart();
if(row.size() != 5) {
getFormatter().exception(row.getCell(row.size() - 1), "Not all cells found: | let | label | type | expr | result |");
debugMethodCallEnd();
return;
}
String label = row.getCell(1).text().trim();
String loc = row.getCell(2).text();
CellWrapper exprCell = row.getCell(3);
try {
exprCell.body(GLOBALS.substitute(exprCell.body()));
String expr = exprCell.text();
CellWrapper valueCell = row.getCell(4);
String valueCellText = valueCell.body();
String valueCellTextReplaced = GLOBALS.substitute(valueCellText);
valueCell.body(valueCellTextReplaced);
String sValue = null;
LetHandler letHandler = LetHandlerFactory.getHandlerFor(loc);
if (letHandler != null) {
StringTypeAdapter adapter = new StringTypeAdapter();
try {
sValue = letHandler.handle(this, getLastResponse(), namespaceContext, expr);
exprCell.body(getFormatter().gray(exprCell.body()));
} catch (RuntimeException e) {
getFormatter().exception(exprCell, e.getMessage());
LOG.error("Exception occurred when processing cell=" + exprCell, e);
}
GLOBALS.put(label, sValue);
adapter.set(sValue);
getFormatter().check(valueCell, adapter);
} else {
getFormatter().exception(
exprCell,
"I don't know how to process the expression for '"
+ loc + "'");
}
} catch (RuntimeException e) {
getFormatter().exception(exprCell, e);
} finally {
debugMethodCallEnd();
}
}
/**
* allows to add comments to a rest fixture - basically does nothing except ignoring the text.
* the text is substituted if variables are found.
*/
@SuppressWarnings("unchecked")
public void comment() {
debugMethodCallStart();
@SuppressWarnings("rawtypes")
CellWrapper messageCell = row.getCell(1);
try {
String message = messageCell.text().trim();
message = GLOBALS.substitute(message);
messageCell.body(getFormatter().gray(message));
} catch (RuntimeException e) {
getFormatter().exception(messageCell, e);
} finally {
debugMethodCallEnd();
}
}
/**
* Evaluates a string using the internal JavaScript engine. Result of the
* last evaluation is set in the attribute lastEvaluation.
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public void evalJs() {
CellWrapper jsCell = row.getCell(1);
if (jsCell == null) {
getFormatter().exception(row.getCell(0),
"Missing string to evaluate)");
return;
}
JavascriptWrapper wrapper = new JavascriptWrapper(this);
Object result = null;
try {
result = wrapper.evaluateExpression(lastResponse, jsCell.body());
} catch (JavascriptException e) {
getFormatter().exception(row.getCell(1), e);
return;
}
lastEvaluation = null;
if (result != null) {
lastEvaluation = result.toString();
}
StringTypeAdapter adapter = new StringTypeAdapter();
adapter.set(lastEvaluation);
getFormatter().right(row.getCell(1), adapter);
}
/**
* Process the row in input. Abstracts the test runner via the wrapper
* interfaces.
*
* @param currentRow
*/
@SuppressWarnings("rawtypes")
public void processRow(RowWrapper> currentRow) {
row = currentRow;
CellWrapper cell0 = row.getCell(0);
if (cell0 == null) {
throw new RuntimeException(
"Current RestFixture row is not parseable (maybe empty or not existent)");
}
String methodName = cell0.text();
if ("".equals(methodName)) {
throw new RuntimeException("RestFixture method not specified");
}
Method method1;
try {
method1 = getClass().getMethod(methodName);
method1.invoke(this);
} catch (SecurityException e) {
throw new RuntimeException(
"Not enough permissions to access method " + methodName
+ " for this class "
+ this.getClass().getSimpleName(), e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Class " + this.getClass().getName()
+ " doesn't have a callable method named " + methodName, e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Method named " + methodName
+ " invoked with the wrong argument.", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Method named " + methodName
+ " is not public.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Method named " + methodName
+ " threw an exception when executing.", e);
}
}
protected void initialize(Runner runner) {
this.runner = runner;
boolean state = validateState();
notifyInvalidState(state);
configFormatter();
configFixture();
configRestClient();
}
protected String emptifyBody(String b) {
String body = b;
if (body == null) {
body = "";
}
return body;
}
/**
* @return the request headers
*/
public Map getHeaders() {
Map headers = null;
if (requestHeaders != null) {
headers = requestHeaders;
} else {
headers = defaultHeaders;
}
return headers;
}
// added for RestScriptFixture
protected String getRequestBody() {
return requestBody;
}
// added for RestScriptFixture
protected void setRequestBody(String text) {
requestBody = text;
}
protected Map getNamespaceContext() {
return namespaceContext;
}
private void doMethod(String m) {
doMethod(null, m);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void doMethod(String body, String method) {
CellWrapper urlCell = row.getCell(1);
String url = deHtmlify(stripTag(urlCell.text()));
String resUrl = GLOBALS.substitute(url);
String rBody = GLOBALS.substitute(body);
Map rHeaders = substitute(getHeaders());
try {
doMethod(method, resUrl, rHeaders, rBody);
completeHttpMethodExecution();
} catch (RuntimeException e) {
getFormatter().exception(
row.getCell(0),
"Execution of " + method + " caused exception '"
+ e.getMessage() + "'");
LOG.error("Exception occurred when processing method=" + method, e);
}
}
protected void doMethod(String method, String resUrl, String rBody) {
doMethod(method, resUrl, substitute(getHeaders()), rBody);
}
protected void doMethod(String method, String resUrl,
Map headers, String rBody) {
setLastRequest(partsFactory.buildRestRequest());
getLastRequest().setMethod(RestRequest.Method.valueOf(method));
getLastRequest().addHeaders(headers);
getLastRequest().setFollowRedirect(followRedirects);
getLastRequest().setResourceUriEscaped(resourceUrisAreEscaped);
if (fileName != null) {
getLastRequest().setFileName(fileName);
}
if (multipartFileName != null) {
getLastRequest().setMultipartFileName(multipartFileName);
}
getLastRequest().setMultipartFileParameterName(
multipartFileParameterName);
String[] uri = resUrl.split("\\?", 2);
String[] thisRequestUrlParts = buildThisRequestUrl(uri[0]);
getLastRequest().setResource(thisRequestUrlParts[1]);
if (uri.length > 1) {
String query = uri[1];
for (int i=2; i lastHeaders = getLastResponse().getHeaders();
process(row.getCell(3), lastHeaders, new HeadersTypeAdapter());
CellWrapper bodyCell = row.getCell(4);
if (bodyCell == null) {
throw new IllegalStateException("You must specify a body cell");
}
bodyCell.body(GLOBALS.substitute(bodyCell.body()));
BodyTypeAdapter bodyTypeAdapter = createBodyTypeAdapter();
process(bodyCell, getLastResponse().getBody(), bodyTypeAdapter);
}
// Split out of completeHttpMethodExecution so RestScriptFixture can call
// this
protected BodyTypeAdapter createBodyTypeAdapter() {
return createBodyTypeAdapter(ContentType.parse(getLastResponse()
.getContentType()));
}
// Split out of completeHttpMethodExecution so RestScriptFixture can call
// this
protected BodyTypeAdapter createBodyTypeAdapter(ContentType ct) {
String charset = getLastResponse().getCharset();
BodyTypeAdapter bodyTypeAdapter = partsFactory.buildBodyTypeAdapter(ct,
charset);
bodyTypeAdapter.setContext(namespaceContext);
return bodyTypeAdapter;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void process(CellWrapper expected, Object actual,
RestDataTypeAdapter ta) {
if (expected == null) {
throw new IllegalStateException("You must specify a headers cell");
}
ta.set(actual);
boolean ignore = "".equals(expected.text().trim());
if (ignore) {
String actualString = ta.toString();
if (!"".equals(actualString)) {
expected.addToBody(getFormatter().gray(actualString));
}
} else {
boolean success = false;
try {
String substitute = GLOBALS.substitute(Tools.fromHtml(expected
.text()));
Object parse = ta.parse(substitute);
success = ta.equals(parse, actual);
} catch (Exception e) {
getFormatter().exception(expected, e);
return;
}
if (success) {
getFormatter().right(expected, ta);
} else {
getFormatter().wrong(expected, ta);
}
}
}
private void debugMethodCallStart() {
debugMethodCall("=> ");
}
private void debugMethodCallEnd() {
debugMethodCall("<= ");
}
private void debugMethodCall(String h) {
if (debugMethodCall) {
StackTraceElement el = Thread.currentThread().getStackTrace()[4];
LOG.debug(h + el.getMethodName());
}
}
private Map substitute(Map headers) {
Map sub = new HashMap();
for (Map.Entry e : headers.entrySet()) {
sub.put(e.getKey(), GLOBALS.substitute(e.getValue()));
}
return sub;
}
protected RestResponse getLastResponse() {
return lastResponse;
}
protected RestRequest getLastRequest() {
return lastRequest;
}
private String[] buildThisRequestUrl(String uri) {
String[] parts = new String[2];
if (baseUrl == null || uri.startsWith(baseUrl.toString())) {
Url url = new Url(uri);
parts[0] = url.getBaseUrl();
parts[1] = url.getResource();
} else {
try {
Url attempted = new Url(uri);
parts[0] = attempted.getBaseUrl();
parts[1] = attempted.getResource();
} catch (RuntimeException e) {
parts[0] = baseUrl.toString();
parts[1] = uri;
}
}
return parts;
}
private void setLastResponse(RestResponse lastResponse) {
this.lastResponse = lastResponse;
}
private void setLastRequest(RestRequest lastRequest) {
this.lastRequest = lastRequest;
}
protected Map parseHeaders(String str) {
return Tools.convertStringToMap(str, ":", LINE_SEPARATOR, true);
}
private Map parseNamespaceContext(String str) {
return Tools.convertStringToMap(str, "=", LINE_SEPARATOR, true);
}
private String stripTag(String somethingWithinATag) {
return Tools.fromSimpleTag(somethingWithinATag);
}
private void configFormatter() {
formatter = partsFactory.buildCellFormatter(runner);
}
/**
* Configure the fixture with data from {@link RestFixtureConfig}.
*/
private void configFixture() {
GLOBALS = createRunnerVariables();
displayActualOnRight = config.getAsBoolean(
"restfixture.display.actual.on.right", displayActualOnRight);
resourceUrisAreEscaped = config
.getAsBoolean("restfixture.resource.uris.are.escaped",
resourceUrisAreEscaped);
followRedirects = config.getAsBoolean(
"restfixture.requests.follow.redirects", followRedirects);
minLenForCollapseToggle = config.getAsInteger(
"restfixture.display.toggle.for.cells.larger.than",
minLenForCollapseToggle);
String str = config.get("restfixture.default.headers", "");
defaultHeaders = parseHeaders(str);
str = config.get("restfixture.xml.namespace.context", "");
namespaceContext = parseNamespaceContext(str);
ContentType.resetDefaultMapping();
ContentType.config(config);
}
/**
* Allows to config the rest client implementation. the method shoudl
* configure the instance attribute {@link RestFixture#restClient} created
* by the {@link smartrics.rest.fitnesse.fixture.PartsFactory#buildRestClient(smartrics.rest.fitnesse.fixture.support.Config)}.
*/
private void configRestClient() {
restClient = partsFactory.buildRestClient(getConfig());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void renderReplacement(CellWrapper cell, String actual) {
StringTypeAdapter adapter = new StringTypeAdapter();
adapter.set(actual);
if (!adapter.equals(actual, cell.body())) {
// eg - a substitution has occurred
cell.body(actual);
getFormatter().right(cell, adapter);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void processSlimRow(List> resultTable, List row) {
RowWrapper currentRow = new SlimRow(row);
try {
processRow(currentRow);
} catch (Exception e) {
LOG.error("Exception raised when processing row " + row.get(0), e);
getFormatter().exception(currentRow.getCell(0), e);
} finally {
List rowAsList = mapSlimRow(row, currentRow);
resultTable.add(rowAsList);
}
}
@SuppressWarnings("rawtypes")
private List mapSlimRow(List resultRow,
RowWrapper currentRow) {
List rowAsList = ((SlimRow) currentRow).asList();
for (int c = 0; c < rowAsList.size(); c++) {
// HACK: it seems that even if the content is unchanged,
// Slim renders red cell
String v = rowAsList.get(c);
if (v.equals(resultRow.get(c))) {
rowAsList.set(c, "");
}
}
return rowAsList;
}
private String deHtmlify(String someHtml) {
return Tools.fromHtml(someHtml);
}
private void configureCredentials() {
String username = config.get("http.basicauth.username");
String password = config.get("http.basicauth.password");
if (username != null && password != null) {
String newUsername = GLOBALS.substitute(username);
String newPassword = GLOBALS.substitute(password);
Config newConfig = getConfig();
newConfig.add("http.basicauth.username", newUsername);
newConfig.add("http.basicauth.password", newPassword);
restClient = partsFactory.buildRestClient(newConfig);
}
}
@Override
public void setStatementExecutor(StatementExecutorInterface arg0) {
this.slimStatementExecutor = arg0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy