@@ -91,21 +91,22 @@ public class ApiRunner { | |||||
private Map<String, ApiClientBase> alternateApis = new HashMap<>(); | private Map<String, ApiClientBase> alternateApis = new HashMap<>(); | ||||
private ApiRunnerListener listener; | |||||
@Getter @Setter private ApiScriptIncludeHandler includeHandler; | |||||
private ApiRunnerListener listener = new ApiRunnerListenerBase("default"); | |||||
@Getter @Setter private ApiScriptIncludeHandler includeHandler = new ApiScriptIncludeHandlerBase(); | |||||
protected final Map<String, Object> ctx = new ConcurrentHashMap<>(); | protected final Map<String, Object> ctx = new ConcurrentHashMap<>(); | ||||
public Map<String, Object> getContext () { return ctx; } | public Map<String, Object> getContext () { return ctx; } | ||||
@Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | |||||
protected Handlebars initHandlebars() { | |||||
final Handlebars hb = new Handlebars(new HandlebarsUtil("api-runner(" + api + ")")); | |||||
HandlebarsUtil.registerUtilityHelpers(hb); | |||||
HandlebarsUtil.registerCurrencyHelpers(hb); | |||||
HandlebarsUtil.registerDateHelpers(hb); | |||||
HandlebarsUtil.registerJurisdictionHelpers(hb, SimpleJurisdictionResolver.instance); | |||||
HandlebarsUtil.registerJavaScriptHelper(hb, StandardJsEngine::new); | |||||
return hb; | |||||
@Getter(lazy=true) private final Handlebars handlebars = standardHandlebars(new Handlebars(new HandlebarsUtil("api-runner(" + api + ")"))); | |||||
public static Handlebars standardHandlebars(Handlebars hbs) { | |||||
HandlebarsUtil.registerUtilityHelpers(hbs); | |||||
HandlebarsUtil.registerDateHelpers(hbs); | |||||
HandlebarsUtil.registerCurrencyHelpers(hbs); | |||||
HandlebarsUtil.registerJavaScriptHelper(hbs, StandardJsEngine::new); | |||||
HandlebarsUtil.registerJurisdictionHelpers(hbs, SimpleJurisdictionResolver.instance); | |||||
HandlebarsUtil.registerJavaScriptHelper(hbs, StandardJsEngine::new); | |||||
return hbs; | |||||
} | } | ||||
protected final Map<String, Class> storeTypes = new HashMap<>(); | protected final Map<String, Class> storeTypes = new HashMap<>(); | ||||
@@ -1,27 +0,0 @@ | |||||
package org.cobbzilla.wizard.client.script; | |||||
import lombok.Getter; | |||||
import lombok.Setter; | |||||
import lombok.experimental.Accessors; | |||||
import java.nio.file.Paths; | |||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | |||||
@Accessors(chain=true) | |||||
public class ApiScriptIncludeClasspathHandler implements ApiScriptIncludeHandler { | |||||
@Getter @Setter private String includePrefix; | |||||
@Getter @Setter private String commonPath; | |||||
@Override public String include(String path) { | |||||
final String fileName = path + ".json"; | |||||
try { | |||||
return stream2string(Paths.get(getIncludePrefix(), fileName).toString()); | |||||
} catch (IllegalArgumentException e) { | |||||
if (getCommonPath() == null) throw e; | |||||
return stream2string(Paths.get(getCommonPath(), fileName).toString()); | |||||
} | |||||
} | |||||
} |
@@ -1,7 +1,39 @@ | |||||
package org.cobbzilla.wizard.client.script; | package org.cobbzilla.wizard.client.script; | ||||
import org.cobbzilla.util.string.StringUtil; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.nio.file.Paths; | |||||
import java.util.Arrays; | |||||
import java.util.List; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.shortErrorString; | |||||
import static org.cobbzilla.util.io.StreamUtil.loadResourceAsString; | |||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | |||||
public interface ApiScriptIncludeHandler { | public interface ApiScriptIncludeHandler { | ||||
String include (String path); | |||||
Logger log = LoggerFactory.getLogger(ApiScriptIncludeHandler.class); | |||||
List<String> DEFAULT_INCLUDE_PATHS = Arrays.asList("", "models", "tests", "include", "models/tests", "models/include"); | |||||
default List<String> getIncludePaths() { return DEFAULT_INCLUDE_PATHS; } | |||||
default String include (String path) { | |||||
final String fileName = path + ".json"; | |||||
for (String inc : getIncludePaths()) { | |||||
try { | |||||
return stream2string(Paths.get(inc, fileName).toString()); | |||||
} catch (Exception e) { | |||||
try { | |||||
return loadResourceAsString(inc + "/" + fileName); | |||||
} catch (Exception e2) { | |||||
log.debug("include(" + path + "): not found in " + inc+": (e="+shortErrorString(e)+", e2="+shortErrorString(e2)+")"); | |||||
} | |||||
} | |||||
} | |||||
return die("include("+path+"): not found anywhere in "+ StringUtil.toString(getIncludePaths())); | |||||
} | |||||
} | } |
@@ -0,0 +1,3 @@ | |||||
package org.cobbzilla.wizard.client.script; | |||||
public class ApiScriptIncludeHandlerBase implements ApiScriptIncludeHandler {} |
@@ -0,0 +1,261 @@ | |||||
# ApiRunner | |||||
ApiRunner is a set of Java tools to facilitate integration testing of REST APIs that use the cobbzilla-wizard framework | |||||
for running the API and populating a data model. | |||||
ApiRunner uses a declarative approach to API testing, while allowing highly dynamic behavior and state management. | |||||
## JUnit | |||||
The easiest way to use ApiRunner is to create a JUnit test class that is a subclass of `ApiModelTestBase`, and | |||||
call modelTest, for example: | |||||
```code | |||||
import org.apache.commons.io.FileUtils; | |||||
import org.cobbzilla.wizard.server.config.factory.ConfigurationSource; | |||||
import org.cobbzilla.wizardtest.resources.AbstractResourceIT; | |||||
import org.junit.Test; | |||||
import java.io.File; | |||||
import java.io.FileInputStream; | |||||
import java.io.IOException; | |||||
public class FooTest extends AbstractResourceIT { | |||||
@Override protected ConfigurationSource getConfigurationSource() { | |||||
return () -> new FileInputStream("/path/to/config.yml"); | |||||
} | |||||
@Test public void testSomeApiStuff () throws Exception { runScript("basic_test"); } | |||||
@Test public void testSomeOtherApiStuff () throws Exception { runScript("some_dir/another_test"); } | |||||
} | |||||
``` | |||||
When JUnit runs, it will call methods testSomeApiStuff and testSomeOtherApiStuff. | |||||
For each of these tests, `runScript` will run the ApiScripts contained in each file. The default resolution is to append `.json` and | |||||
try to load from the current classloader. In the above, the ApiRunner would expect to find `basic_test.json` in the base resource directory | |||||
and `some_dir/another_test.json` in | |||||
The default "relative path" for test scripts is `models/tests`, and must be visible on the classpath (via `ClassLoader.getResourceAsStream`, using the JUnit class's class-loader). | |||||
Subclasses can override the default include resolution process, for example to load from a directory on the filesystem | |||||
instead of the classpath: | |||||
```code | |||||
import org.apache.commons.io.FileUtils; | |||||
... | |||||
public class FooTest extends AbstractResourceIT { | |||||
... | |||||
protected String resolveInclude(String path) { | |||||
try { | |||||
return FileUtils.readFileToString(new File("/tmp/my_tests/"+path+".json"), "UTF-8"); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("resolveInclude("+path+"): "+e, e); | |||||
} | |||||
} | |||||
} | |||||
``` | |||||
# ApiScript | |||||
An ApiScript is a JSON file of type ApiScript[]. The root element is an array ApiScript objects. | |||||
The array may be empty, in which case the running the script is a "no-op". | |||||
If the script array contains ApiScript objects, each such object represents an API request/response, and some additional associated data. | |||||
It's design supports the idea that simple things should be easy, and complex things should be possible. | |||||
# ApiScriptRequest | |||||
The `request` portion of an ApiScript contains minimally a `uri` field. This is taken to be relative to an API "base" | |||||
which is set elsewhere in the framework, and provided as a context variable to the ApiRunner. | |||||
## GET requests | |||||
Just specify the `uri` property, nothing else. ApiRunner will execute the GET request against the API, and return the | |||||
JSON object, optionally storing it in a context variable if the `store` property is set on the response. | |||||
{"request": "/me"} | |||||
The ApiRunner will verify that the response had a 200 status, but otherwise does nothing with the response. | |||||
## POST requests | |||||
To `POST` data, add an `entity` property: | |||||
{ | |||||
"request" { | |||||
"uri": "/account/info", | |||||
"entity": { | |||||
"username": "jsmith123", | |||||
"some_prop": "some_value" | |||||
} | |||||
} | |||||
} | |||||
} | |||||
## Other requests | |||||
For requests with a method that is not `GET` or `POST`, add a `method` property. | |||||
The method name is case-insensitive, use capitals or lowercase at your preference. | |||||
For example: | |||||
{ | |||||
"request" { | |||||
"uri": "/account/preferences", | |||||
"method": "put", | |||||
"entity": [ | |||||
{"favorite_fruit": "apple"}, | |||||
{"best_movie": "Logan's Run"} | |||||
] | |||||
} | |||||
} | |||||
} | |||||
## ApiSession | |||||
As the ApiRunner runs, each connection to the backend API server is done in the context of a session, or no session. | |||||
Without a session, (or if the `request.session` property is `new`), the ApiRunner will not send an API credential token | |||||
with any request. | |||||
For each ApiScript that the ApiRunner runs, a single session will be used. The ApiScript can specify a | |||||
`request.session` of `new` to indicate that any previous session should not be used, and the request should be made | |||||
without any authentication added. | |||||
Once a session is established, or set, the corresponding session token will be sent to the API server, | |||||
until some subsequent ApiScript sets the `request.session` property to a different session, or to `new`. | |||||
A session can be established by an ApiScript in two ways: | |||||
### Set session from an ApiScriptResponse | |||||
{ | |||||
"request" { | |||||
"uri": "/login", | |||||
"method": "put", | |||||
"entity": [ | |||||
{"username": "jsmith123"}, | |||||
{"password": "B8F8UJ7PZX93AF9M4EJ8O9QXY"} | |||||
] | |||||
} | |||||
}, | |||||
"response": { | |||||
"sessionName": "userSession", | |||||
"session": "token" | |||||
} | |||||
} | |||||
In the above, if the `/login` request succeeds, then the ApiRunner will create a new API session and continue to use | |||||
that session for future requests, until changed. | |||||
The session will be saved by the ApiRunner under the name `userSession`. The `"session": "token"` part means that, in the JSON returned from the | |||||
`/login` request, the `token` property contains the session token. | |||||
In order to ensure this token is returned to the server in the appropriate header, API tests requests require | |||||
small subclass of `ApiClientBase` to tell ApiRunner which HTTP header to use for sending the session token. | |||||
Do this by overriding the `getApi` method and providing your subclass which has a `getTokenHeader` method defined. | |||||
```code | |||||
import org.cobbzilla.wizard.client.ApiClientBase; | |||||
... | |||||
public class FooTest extends AbstractResourceIT { | |||||
@Override public ApiClientBase getApi() { | |||||
return new ApiClientBase(super.getApi()) { | |||||
@Override public String getTokenHeader() { return "X-MyApp-Session"; } | |||||
}; | |||||
} | |||||
``` | |||||
If session management requires more complex processing, override the `beforeSend` method in your client class for full control: | |||||
```code | |||||
... | |||||
public class FooTest extends AbstractResourceIT { | |||||
@Override public ApiClientBase getApi() { | |||||
return new ApiClientBase(super.getApi()) { | |||||
@Override protected HttpRequestBase beforeSend(HttpRequestBase request) { | |||||
// ... adjust request as needed / add authentication | |||||
return request; | |||||
} | |||||
}; | |||||
} | |||||
``` | |||||
### Set session by name in an ApiScriptRequest | |||||
An ApiScriptRequest can set the session for a request. When the session is set, it will be used on subesquent requests, | |||||
unless they specify a different session, or a new session. | |||||
For example, let's say the current session was created at login and named `userSession`. Then our test script created | |||||
a second user and logged in as them, and saved it with the session name `secondUser`. The ApiRunner's current session | |||||
is thus `secondUser`, but later in the test we want to make an API call as the first user. | |||||
A GET using a named session can switch between sessions: | |||||
{ | |||||
"request" { | |||||
"uri": "/account/something", | |||||
"session": "userSession" | |||||
} | |||||
} | |||||
} | |||||
Setting the `response.session` property to `userSession` means that ApiRunner will make the GET request using the | |||||
authentication credentials for that session, instead of the `secondUser` session. | |||||
## ApiScriptResponse | |||||
Whereas an ApiScriptRequest represents instructions to ApiRunner to do actively very specific things to an API server, | |||||
ApiScriptResponse reads a response and applies various tests and checks to verify that everything is as expected before continuing. | |||||
Things that will cause ApiRunner to fail an ApiScript: | |||||
* By default, only HTTP status 200 is considered successful. | |||||
* If `response.status` (integer) was set, and the HTTP status received does not match its value | |||||
* If `response.okStatuses` (array of integers) was set, and the HTTP status received does not match any of these values | |||||
* If any of the tests in the `check` return `false` or throw an exception | |||||
### The `check` tests | |||||
The `response.check` property, if present, is an array of tests. Each test is a JavaScript expression. The variables | |||||
available within the JavaScript context are: | |||||
* All objects that have been saved via the `response.store` property | |||||
* All variables in the Map returned from getConfiguration().getServerEnvironment() in the JUnit test class (a subclass of `AbstractResourceIT`) | |||||
* A special variable called `configuration` references the object returned by getConfiguration() | |||||
* A special variable called `json` references the object returned by the API for the current ApiScript. | |||||
### Handlebars + JavaScript | |||||
ApiRunner has a dual-context system: Handlebars *and* JavaScript are used to give tests the flexibility and expressive power | |||||
that serious REST API testing demands. | |||||
Handlebars is applied to all properties in the ApiScript before it is run. | |||||
For example `request.url` will often contain variables from previous ApiScripts, and you can also use Handlebars within | |||||
the JavaScript `check` conditions. | |||||
The `check` conditions are JavaScript expressions, which are evaluated as booleans. | |||||
Any test that returns false or throws an exception will cause the JUnit test to fail. | |||||
Note that the `json` variable is not available in Handlebars contexts used in the `request` section, since it references the response object. | |||||
Let's say we previously fetched the current user via `{"request":{"uri":"/me"}, "response":{"store":"someUser"}}`, | |||||
so the current user is saved in the `someUser` variable. Then we fetch the user via the `/users/` API, using the id, | |||||
and verify the username is the same. | |||||
{ | |||||
"request": { "uri": "/users/{{someUser.uuid}}/" }, | |||||
"response": { | |||||
"store": "checkUser", | |||||
"check": [ {"condition": "checkUser.getName() === someUser.getName()"} ] | |||||
} | |||||
}, // ...more script items... | |||||
Because we can also use Handlebars within JavaScript, an equivalent of above the would be: | |||||
{ | |||||
"request": { "uri": "/users/{{someUser.id}}/" }, | |||||
"response": { | |||||
"store": "checkUser", | |||||
"check": [ | |||||
{"condition": "'{{checkUser.name}}' === '{{someUser.name}}'"} | |||||
] | |||||
} | |||||
}, // ...more script items... | |||||
The Handlebars is evaluated first, so the JavaScript test is now a comparison of two string literals. | |||||
Handlebars object notation is less verbose than Java/JavaScript, especially for heavily nested objects. | |||||
Using Handlebars expressions within the JavaScript tests can often aid in test clarity. | |||||
@@ -24,6 +24,7 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.system.Sleep.sleep; | import static org.cobbzilla.util.system.Sleep.sleep; | ||||
import static org.cobbzilla.util.time.TimeUtil.parseDuration; | import static org.cobbzilla.util.time.TimeUtil.parseDuration; | ||||
import static org.cobbzilla.wizard.client.script.ApiRunner.standardHandlebars; | |||||
import static org.cobbzilla.wizard.main.ScriptMainBase.SLEEP; | import static org.cobbzilla.wizard.main.ScriptMainBase.SLEEP; | ||||
import static org.cobbzilla.wizard.main.ScriptMainBase.handleSleep; | import static org.cobbzilla.wizard.main.ScriptMainBase.handleSleep; | ||||
@@ -44,7 +45,7 @@ public class SimpleApiRunnerListener extends ApiRunnerListenerBase { | |||||
private ApiClientBase currentApi() { return getApiRunner().getCurrentApi(); } | private ApiClientBase currentApi() { return getApiRunner().getCurrentApi(); } | ||||
@Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | @Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | ||||
protected Handlebars initHandlebars() { return new Handlebars(new HandlebarsUtil(getClass().getSimpleName())); } | |||||
protected Handlebars initHandlebars() { return standardHandlebars(new Handlebars(new HandlebarsUtil(getClass().getSimpleName()))); } | |||||
@Override public void beforeScript(String before, Map<String, Object> ctx) throws Exception { | @Override public void beforeScript(String before, Map<String, Object> ctx) throws Exception { | ||||
if (before == null) return; | if (before == null) return; | ||||
@@ -88,9 +89,9 @@ public class SimpleApiRunnerListener extends ApiRunnerListenerBase { | |||||
// todo: allow listeners to have access to the runner, or at least the current/correct API | // todo: allow listeners to have access to the runner, or at least the current/correct API | ||||
// we are getting 404 because we're sending the wrong token (default API instead of remote) | // we are getting 404 because we're sending the wrong token (default API instead of remote) | ||||
private boolean handleAwaitUrl(String arg, Map<String, Object> ctx) { | private boolean handleAwaitUrl(String arg, Map<String, Object> ctx) { | ||||
final String[] parts = arg.split("\\s+"); | |||||
final String[] parts = HandlebarsUtil.apply(getHandlebars(), arg, ctx).split("\\s+"); | |||||
if (parts.length < 3) return die(AWAIT_URL+": no URL and/or timeout specified"); | if (parts.length < 3) return die(AWAIT_URL+": no URL and/or timeout specified"); | ||||
final String url = formatUrl(parts[1], ctx); | |||||
final String url = formatUrl(parts[1]); | |||||
final long timeout = parseDuration(parts[2]); | final long timeout = parseDuration(parts[2]); | ||||
final long checkInterval = (parts.length >= 4) ? parseDuration(parts[3]) : DEFAULT_AWAIT_URL_CHECK_INTERVAL; | final long checkInterval = (parts.length >= 4) ? parseDuration(parts[3]) : DEFAULT_AWAIT_URL_CHECK_INTERVAL; | ||||
final String jsCondition = (parts.length >= 5) ? parseJs(parts, 4) : "true"; | final String jsCondition = (parts.length >= 5) ? parseJs(parts, 4) : "true"; | ||||
@@ -122,7 +123,7 @@ public class SimpleApiRunnerListener extends ApiRunnerListenerBase { | |||||
private boolean handleVerifyUnreachable(String arg, Map<String, Object> ctx) { | private boolean handleVerifyUnreachable(String arg, Map<String, Object> ctx) { | ||||
final String[] parts = arg.split("\\s+"); | final String[] parts = arg.split("\\s+"); | ||||
if (parts.length < 2) return die(VERIFY_UNREACHABLE+": no URL specified"); | if (parts.length < 2) return die(VERIFY_UNREACHABLE+": no URL specified"); | ||||
final String url = formatUrl(parts[1], ctx); | |||||
final String url = formatUrl(parts[1]); | |||||
final long connectTimeout = (parts.length >= 3) ? parseDuration(parts[2]) : DEFAULT_VERIFY_UNAVAILABLE_TIMEOUT; | final long connectTimeout = (parts.length >= 3) ? parseDuration(parts[2]) : DEFAULT_VERIFY_UNAVAILABLE_TIMEOUT; | ||||
final long socketTimeout = (parts.length >= 4) ? parseDuration(parts[3]) : connectTimeout; | final long socketTimeout = (parts.length >= 4) ? parseDuration(parts[3]) : connectTimeout; | ||||
@@ -153,9 +154,8 @@ public class SimpleApiRunnerListener extends ApiRunnerListenerBase { | |||||
} | } | ||||
} | } | ||||
private String formatUrl(String url, Map<String, Object> ctx) { | |||||
private String formatUrl(String url) { | |||||
final ApiClientBase currentApi = currentApi(); | final ApiClientBase currentApi = currentApi(); | ||||
url = HandlebarsUtil.apply(getHandlebars(), url, ctx); | |||||
if (!url.startsWith("http://") && !url.startsWith("https://")) { | if (!url.startsWith("http://") && !url.startsWith("https://")) { | ||||
if (!url.startsWith("/") && !currentApi.getBaseUri().endsWith("/")) url = "/" + url; | if (!url.startsWith("/") && !currentApi.getBaseUri().endsWith("/")) url = "/" + url; | ||||
url = currentApi.getBaseUri() + url; | url = currentApi.getBaseUri() + url; | ||||
@@ -272,6 +272,7 @@ public abstract class ScriptMainBase<OPT extends ScriptMainOptionsBase> | |||||
api.setCaptureHeaders(getOptions().isCaptureHeaders()); | api.setCaptureHeaders(getOptions().isCaptureHeaders()); | ||||
return new ApiRunner(api, getScriptListener()).setIncludeHandler(this); } | return new ApiRunner(api, getScriptListener()).setIncludeHandler(this); } | ||||
@Override public String include(String path) { | @Override public String include(String path) { | ||||
final OPT options = getOptions(); | final OPT options = getOptions(); | ||||
final String envInclude = getPathEnvVar() == null ? null : System.getenv(getPathEnvVar()); | final String envInclude = getPathEnvVar() == null ? null : System.getenv(getPathEnvVar()); | ||||
@@ -6,6 +6,8 @@ import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.http.HttpStatusCodes; | import org.cobbzilla.util.http.HttpStatusCodes; | ||||
import org.cobbzilla.util.json.JsonUtil; | import org.cobbzilla.util.json.JsonUtil; | ||||
import org.cobbzilla.wizard.client.ApiClientBase; | import org.cobbzilla.wizard.client.ApiClientBase; | ||||
import org.cobbzilla.wizard.client.script.ApiRunner; | |||||
import org.cobbzilla.wizard.client.script.ApiRunnerListenerBase; | |||||
import org.cobbzilla.wizard.server.RestServer; | import org.cobbzilla.wizard.server.RestServer; | ||||
import org.cobbzilla.wizard.server.RestServerConfigurationFilter; | import org.cobbzilla.wizard.server.RestServerConfigurationFilter; | ||||
import org.cobbzilla.wizard.server.RestServerHarness; | import org.cobbzilla.wizard.server.RestServerHarness; | ||||
@@ -29,6 +31,7 @@ import java.util.concurrent.atomic.AtomicReference; | |||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | import static org.cobbzilla.util.daemon.ZillaRuntime.*; | ||||
import static org.cobbzilla.util.io.StreamUtil.loadResourceAsStringOrDie; | |||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | import static org.cobbzilla.util.io.StreamUtil.stream2string; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.reflect.ReflectionUtil.getFirstTypeParam; | import static org.cobbzilla.util.reflect.ReflectionUtil.getFirstTypeParam; | ||||
@@ -187,6 +190,13 @@ public abstract class AbstractResourceIT<C extends PgRestServerConfiguration, S | |||||
protected boolean createSqlIndexes () { return false; } | protected boolean createSqlIndexes () { return false; } | ||||
protected String[] getSqlPostScripts() { return getConfiguration().getSqlConstraints(createSqlIndexes()); } | protected String[] getSqlPostScripts() { return getConfiguration().getSqlConstraints(createSqlIndexes()); } | ||||
// default resolution | |||||
protected String resolveInclude(String path) { return loadResourceAsStringOrDie(path+".json"); } | |||||
public void runScript(String script) throws Exception { | |||||
new ApiRunner(getApi(), new ApiRunnerListenerBase(getClass().getName())).run(script); | |||||
} | |||||
@Override public void beforeStop(RestServer<C> server) {} | @Override public void beforeStop(RestServer<C> server) {} | ||||
@Override public void onStop(RestServer<C> server) {} | @Override public void onStop(RestServer<C> server) {} | ||||
@@ -5,9 +5,10 @@ import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.collection.SingletonList; | import org.cobbzilla.util.collection.SingletonList; | ||||
import org.cobbzilla.util.io.FileUtil; | import org.cobbzilla.util.io.FileUtil; | ||||
import org.cobbzilla.util.jdbc.UncheckedSqlException; | import org.cobbzilla.util.jdbc.UncheckedSqlException; | ||||
import org.cobbzilla.util.string.StringUtil; | |||||
import org.cobbzilla.util.system.Sleep; | import org.cobbzilla.util.system.Sleep; | ||||
import org.cobbzilla.wizard.client.ApiClientBase; | |||||
import org.cobbzilla.wizard.client.script.ApiRunner; | import org.cobbzilla.wizard.client.script.ApiRunner; | ||||
import org.cobbzilla.wizard.client.script.ApiRunnerListenerBase; | |||||
import org.cobbzilla.wizard.client.script.ApiRunnerMultiListener; | import org.cobbzilla.wizard.client.script.ApiRunnerMultiListener; | ||||
import org.cobbzilla.wizard.client.script.ApiScriptIncludeHandler; | import org.cobbzilla.wizard.client.script.ApiScriptIncludeHandler; | ||||
import org.cobbzilla.wizard.model.entityconfig.ModelSetupListener; | import org.cobbzilla.wizard.model.entityconfig.ModelSetupListener; | ||||
@@ -21,7 +22,6 @@ import org.junit.Before; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.file.Paths; | |||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -33,10 +33,7 @@ import java.util.concurrent.atomic.AtomicReference; | |||||
import static java.lang.System.identityHashCode; | import static java.lang.System.identityHashCode; | ||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | import static org.cobbzilla.util.daemon.ZillaRuntime.*; | ||||
import static org.cobbzilla.util.io.FileUtil.abs; | |||||
import static org.cobbzilla.util.io.FileUtil.temp; | |||||
import static org.cobbzilla.util.io.FileUtil.writeStringOrDie; | |||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | |||||
import static org.cobbzilla.util.io.FileUtil.*; | |||||
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; | import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; | ||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
import static org.cobbzilla.wizard.model.entityconfig.ModelSetup.modelHash; | import static org.cobbzilla.wizard.model.entityconfig.ModelSetup.modelHash; | ||||
@@ -47,9 +44,16 @@ public abstract class ApiModelTestBase<C extends PgRestServerConfiguration, S ex | |||||
extends ApiDocsResourceIT<C, S> | extends ApiDocsResourceIT<C, S> | ||||
implements ApiScriptIncludeHandler { | implements ApiScriptIncludeHandler { | ||||
protected abstract String getModelPrefix(); | |||||
protected abstract String getEntityConfigsEndpoint(); | |||||
protected abstract ApiRunner getApiRunner(); | |||||
protected String getModelPrefix() { return "models/"; } | |||||
protected String getEntityConfigsEndpoint() { return "/ec"; } | |||||
protected String getBaseUri() { return getConfiguration().getApiUriBase(); } | |||||
@Getter private final AtomicReference<ApiRunner> _defaultRunner = new AtomicReference<>(); | |||||
protected ApiRunner getApiRunner() { | |||||
return new ApiRunner(new ApiClientBase(getBaseUri()), new ApiRunnerListenerBase(getBaseUri())); | |||||
} | |||||
@Override public boolean useTestSpecificDatabase () { return true; } | @Override public boolean useTestSpecificDatabase () { return true; } | ||||
@@ -275,19 +279,7 @@ public abstract class ApiModelTestBase<C extends PgRestServerConfiguration, S ex | |||||
apiRunner.run(include(name)); | apiRunner.run(include(name)); | ||||
} | } | ||||
@Override public String include(String path) { | |||||
final String fileName = path + ".json"; | |||||
for (String inc : getIncludePaths()) { | |||||
try { | |||||
return stream2string(Paths.get(inc, fileName).toString()); | |||||
} catch (Exception e) { | |||||
log.debug("include(" + path + "): not found in " + inc); | |||||
} | |||||
} | |||||
return die("include("+path+"): not found anywhere in "+StringUtil.toString(getIncludePaths())); | |||||
} | |||||
protected List<String> getIncludePaths() { | |||||
@Override public List<String> getIncludePaths() { | |||||
return new SingletonList<>(getModelPrefix() + (getModelPrefix().endsWith("/") ? "" : File.separator) + "tests"); | return new SingletonList<>(getModelPrefix() + (getModelPrefix().endsWith("/") ? "" : File.separator) + "tests"); | ||||
} | } | ||||
@@ -4,6 +4,6 @@ import org.cobbzilla.wizard.server.config.RestServerConfiguration; | |||||
public interface RestServerConfigurationFilter<C extends RestServerConfiguration> { | public interface RestServerConfigurationFilter<C extends RestServerConfiguration> { | ||||
C filterConfiguration(C configuration); | |||||
default C filterConfiguration(C configuration) { return configuration; } | |||||
} | } |