jio.test.pbt.rest.RestPropBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jio-test Show documentation
Show all versions of jio-test Show documentation
JIO test library based on Property Based Testing and Java Flight Recording Debuggers
package jio.test.pbt.rest;
import fun.gen.Gen;
import jio.BiLambda;
import jio.IO;
import jio.Lambda;
import jio.test.pbt.Property;
import jio.test.pbt.PropertyBuilder;
import jio.test.pbt.TestFailure;
import jio.test.pbt.TestResult;
import jsonvalues.*;
import jsonvalues.spec.JsParserException;
import java.net.http.HttpResponse;
import java.util.function.BiFunction;
import java.util.function.Function;
import static java.util.Objects.requireNonNull;
/**
* An abstract base class for building property tests for RESTful APIs. This class provides a flexible framework for
* defining property tests for HTTP POST, GET, and DELETE operations on a RESTful API endpoint.
*
* @param The type of data generated to feed the property tests.
* @param The concrete subclass type for fluent builder methods.
*/
abstract class RestPropBuilder> {
@SuppressWarnings("UnnecessaryLambda")
final static Function, TestResult> DEFAULT_RESP_ASSERT =
resp -> resp.statusCode() < 300 ?
TestResult.SUCCESS :
TestFailure.reason("Expected status code < 300, but got a " + resp.statusCode());
final String name;
final BiLambda> post;
final BiLambda> get;
final BiLambda> delete;
final Gen gen;
@SuppressWarnings("UnnecessaryLambda")
private final Function, IO>> getIdFromPath =
path -> (body, resp) -> {
try {
JsObj respBody = JsObj.parse(resp.body());
JsValue id = respBody.get(path);
return id == JsNothing.NOTHING ?
IO.fail(TestFailure.reason(path + " not found in the following json: " + resp.body()))
:
IO.succeed(id.toString());
} catch (JsParserException e) {
return IO.fail(TestFailure.reason("resp body is not a Json well-formed: " + resp.body()));
}
};
Function, TestResult> postAssert = DEFAULT_RESP_ASSERT;
Function, TestResult> getAssert = DEFAULT_RESP_ASSERT;
Function, TestResult> deleteAssert = DEFAULT_RESP_ASSERT;
BiFunction, IO> getId;
/**
* Creates a new instance of the RestPropBuilder class with the specified parameters.
*
* @param name The name of the property test.
* @param gen The data generator that produces pseudorandom data for testing.
* @param p_post The lambda function representing the HTTP POST operation.
* @param p_get The lambda function representing the HTTP GET operation.
* @param p_delete The lambda function representing the HTTP DELETE operation.
*/
public RestPropBuilder(String name,
Gen gen,
BiLambda> p_post,
BiLambda> p_get,
BiLambda> p_delete
) {
this.post = requireNonNull(p_post);
this.get = requireNonNull(p_get);
this.delete = requireNonNull(p_delete);
this.name = requireNonNull(name);
this.gen = requireNonNull(gen);
this.getId = getIdFromPath.apply(JsPath.fromKey("id"));
}
/**
* Sets the assertion function for the HTTP POST operation.
*
* @param postAssert The assertion function for the HTTP POST operation.
* @return This RestPropBuilder instance with the updated assertion function.
*/
@SuppressWarnings("unchecked")
public PropBuilder withPostAssert(final Function, TestResult> postAssert) {
this.postAssert = requireNonNull(postAssert);
return (PropBuilder) this;
}
/**
* Sets the assertion function for the HTTP GET operation.
*
* @param getAssert The assertion function for the HTTP GET operation.
* @return This RestPropBuilder instance with the updated assertion function.
*/
@SuppressWarnings("unchecked")
public PropBuilder withGetAssert(final Function, TestResult> getAssert) {
this.getAssert = requireNonNull(getAssert);
return (PropBuilder) this;
}
/**
* Sets the assertion function for the HTTP DELETE operation.
*
* @param deleteAssert The assertion function for the HTTP DELETE operation.
* @return This RestPropBuilder instance with the updated assertion function.
*/
@SuppressWarnings("unchecked")
public PropBuilder withDeleteAssert(final Function, TestResult> deleteAssert) {
this.deleteAssert = requireNonNull(deleteAssert);
return (PropBuilder) this;
}
/**
* Sets the function to extract an ID for subsequent HTTP requests. You can choose from two specific ways to extract
* the ID:
*
* - Use {@link #withGetIdFromReqBody(Function)} to extract the ID from the request body of type O.
* - Use {@link #withGetIdFromJSONRespPath(JsPath)} to extract the ID from the HTTP response using a specific path.
*
*
* @param getId The function to extract an ID for subsequent HTTP requests.
* @return This RestPropBuilder instance with the updated ID extraction method.
*/
@SuppressWarnings("unchecked")
public PropBuilder withGetId(BiFunction, IO> getId) {
this.getId = requireNonNull(getId);
return (PropBuilder) this;
}
/**
* Sets the path to extract an ID from the JSON response and use it in subsequent HTTP requests.
*
* @param path The path to extract an ID from the HTTP response.
* @return This RestPropBuilder instance with the updated ID extraction path.
*/
@SuppressWarnings("unchecked")
public PropBuilder withGetIdFromJSONRespPath(final JsPath path) {
this.getId = getIdFromPath.apply(requireNonNull(path));
return (PropBuilder) this;
}
/**
* Sets the function to extract an ID from the request body of type O and use it in subsequent HTTP requests.
*
* @param p_getId The function to extract an ID from the request body of type O.
* @return This RestPropBuilder instance with the updated ID extraction function.
*/
@SuppressWarnings("unchecked")
public PropBuilder withGetIdFromReqBody(final Function p_getId) {
requireNonNull(p_getId);
this.getId = (body, resp) -> {
String id = p_getId.apply(body);
return id == null || id.isBlank() || id.isEmpty() ?
IO.fail(TestFailure.reason("id not found")) :
IO.succeed(id);
};
return (PropBuilder) this;
}
Lambda, IdResp> assertResp(
Function, TestResult> assertResp,
String id
) {
return resp -> {
TestResult result = assertResp.apply(resp);
if (result instanceof TestFailure f) {
return IO.fail(f);
}
return IO.succeed(new IdResp(id,
resp));
};
}
/**
* returns a property builder. If no further customization is needed, you can use {@link #build()}
*
* @return a property builder
*/
public abstract PropertyBuilder get();
/**
* Build a property with the default parameters. For further customization (times of generations, description,
* executor etc), use {@link #get()} that returns a property builder
*
* @return a property
*/
public Property build() {
return get().get();
}
}