package com.tradeshift.reaktive.assertj; import com.tradeshift.reaktive.actors.CommandHandler.Results; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractIterableAssert; import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.Assertions; import org.assertj.core.error.BasicErrorMessageFactory; import io.vavr.collection.Seq; import io.vavr.control.Option; /** * Provides assertions for Results emitted from a command. * * Typically a subclass is written for the actual types in your project, so more type-safe variants of idempotentReply() etc. can * be declared. */ public class AbstractCommandResultsAssert<SELF extends AbstractCommandResultsAssert<SELF,E>,E> extends AbstractAssert<SELF, Results<E>> { public AbstractCommandResultsAssert(Results<E> actual, Class<?> selfType) { super(actual, selfType); } /** * Asserts there is a validation error, and allows further assertions on it. */ public AbstractObjectAssert<?,?> validationError() { return Assertions.assertThat(validationError(Object.class)); } /** * Asserts there is a validation error. */ public SELF isInvalid() { validationError(); return myself; } /** * Asserts there is a validation error and returns it, casting it to the given type. * Useful for subclasses for concrete Results implementations that have a fixed message type. */ protected <M> M validationError(Class<M> type) { Option<Object> err = actual.getValidationError(0); if (err.isEmpty()) { throwAssertionError(new BasicErrorMessageFactory("Expected a Result with validation errors, but instead was %s", actualToString())); } return type.cast(err.get()); } /** * Asserts the results are valid and already applied, and performs further assertions on the reply message. * * It is recommended that subclasses declare a more specific overload of this method, e.g. * <pre> * public AbstractObjectAssert<?, MyResponseType> idempotentReply() { * return Assertions.assertThat(idempotentReply(MyResponseType.class)); * } * </pre> */ public AbstractObjectAssert<?,?> idempotentReply() { return Assertions.assertThat(idempotentReply(Object.class)); } /** * Asserts the results are valid, and are already applied. */ public SELF isIdempotent() { idempotentReply(); return myself; } /** * Asserts the Results are already applied (idempotent), and returns the reply message. * Useful for subclasses for concrete Results implementations that have a fixed message type. */ protected <M> M idempotentReply(Class<M> type) { if (actual.getValidationError(0).isDefined() || !actual.isAlreadyApplied()) { throwAssertionError(new BasicErrorMessageFactory("Expected a Result to be valid and already applied, but instead was %s", actualToString())); } return type.cast(actual.getIdempotentReply(0)); } /** * Asserts the results are valid and NOT already applied, and performs further assertions on the reply message. */ public AbstractObjectAssert<?,?> nonIdempotentReply() { return Assertions.assertThat(nonIdempotentReply(Object.class)); } /** * Asserts the results are valid, and are not already applied (i.e. applying the handler would emit events). */ public SELF isNonIdempotent() { nonIdempotentReply(); if (getEvents().isEmpty()) { throwAssertionError(new BasicErrorMessageFactory("Expected a Result to be valid and not already applied, " + "and isAlreadyApplied() indeed returned false, but getEvents() then yielded no events. This is almost always " + "a bug in the Results implementation. Results were %s", actualToString())); } return myself; } /** * Asserts the Results are NOT already applied, and returns the reply message. * Useful for subclasses for concrete Results implementations that have a fixed message type. */ protected <M> M nonIdempotentReply(Class<M> type) { Seq<E> events = getEvents(); return type.cast(actual.getReply(events, 0)); } /** * Asserts the Results are not already applied, and performs further assertions on the emitted events. * Useful for subclasses for concrete Results implementations that have a fixed message type. */ public AbstractIterableAssert<?, ? extends Iterable<? extends E>, E> events() { return Assertions.assertThat(getEvents()); } protected Seq<E> getEvents() { if (actual.getValidationError(0).isDefined() || actual.isAlreadyApplied()) { throwAssertionError(new BasicErrorMessageFactory("Expected a Result to be valid and not already applied, but instead was %s", actualToString())); } return actual.getEventsToEmit(); } private Object actualToString() { if (actual.getValidationError(0).isDefined()) { return "Result(invalid, reply=" + actual.getValidationError(0) + ")"; } else if (actual.isAlreadyApplied()) { return "Result(idempotent, reply=" + actual.getIdempotentReply(0) + ")"; } else { Seq<E> events = actual.getEventsToEmit(); return "Result(nonIdempotent, reply=" + actual.getReply(events, 0) + ", events=" + events + ")"; } } }