FluentSmsAssert.java

package fr.sii.ogham.testing.assertion.sms;

import static fr.sii.ogham.testing.assertion.util.AssertionHelper.assertThat;
import static fr.sii.ogham.testing.assertion.util.AssertionHelper.usingContext;
import static fr.sii.ogham.testing.sms.simulator.decode.SmsUtils.getSmsContent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.hamcrest.Matcher;

import fr.sii.ogham.testing.assertion.context.SingleMessageContext;
import fr.sii.ogham.testing.assertion.util.AssertionRegistry;
import fr.sii.ogham.testing.sms.simulator.bean.SubmitSm;
import fr.sii.ogham.testing.sms.simulator.decode.Charset;
import fr.sii.ogham.testing.util.HasParent;

/**
 * Make assertions on particular message.
 * 
 * @author Aurélien Baudet
 *
 * @param <P>
 *            the parent type
 * @param <S>
 *            the type of the {@link SubmitSm}
 */
public class FluentSmsAssert<P, S extends SubmitSm> extends HasParent<P> {
	/**
	 * The list of messages that will be used for assertions
	 */
	private final List<S> actual;
	private int index;
	private final AssertionRegistry registry;

	/**
	 * Initializes with a single received message
	 * 
	 * @param actual
	 *            received message
	 * @param index
	 *            the index of the message
	 * @param parent
	 *            the parent
	 * @param registry
	 *            used to register assertions
	 */
	public FluentSmsAssert(S actual, int index, P parent, AssertionRegistry registry) {
		this(Arrays.asList(actual), parent, registry);
		this.index = index;
	}

	/**
	 * Initializes with several received messages
	 * 
	 * @param actual
	 *            received messages
	 * @param parent
	 *            the parent
	 * @param registry
	 *            used to register assertions
	 */
	public FluentSmsAssert(List<S> actual, P parent, AssertionRegistry registry) {
		super(parent);
		this.actual = actual;
		this.registry = registry;
	}

	/**
	 * Make assertions on the content of the message(s). Alphabet/encoding is
	 * automatically determined from message (based on Data Coding Scheme).
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .content(allOf(notNullValue(), is("hello world"))
	 * </pre>
	 * 
	 * Or:
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .content(notNullValue())
	 *    .content(is("hello world"))
	 * </pre>
	 * 
	 * Will check if the content of the first message is not null and is exactly
	 * "hello world".
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .content(allOf(notNullValue(), is("hello world"))
	 * </pre>
	 * 
	 * Or:
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .content(notNullValue())
	 *    .content(is("hello world"))
	 * </pre>
	 * 
	 * Will check if the content of every message is not null and is exactly
	 * "hello world".
	 * 
	 * @param matcher
	 *            the assertion to apply on message content
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentSmsAssert<P, S> content(Matcher<? super String> matcher) {
		String desc = "content of message ${messageIndex}";
		int msgIdx = index;
		for (S message : actual) {
			final int idx = msgIdx;
			registry.register(() -> assertThat(getSmsContent(message), usingContext(desc, new SingleMessageContext(idx), matcher)));
			msgIdx++;
		}
		return this;
	}

	/**
	 * Make assertions on the content of the message(s). Alphabet/encoding used
	 * to decode the message is explicitly provided.
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .content(allOf(notNullValue(), is("hello world"))
	 * </pre>
	 * 
	 * Or:
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .content(notNullValue())
	 *    .content(is("hello world"))
	 * </pre>
	 * 
	 * Will check if the content of the first message is not null and is exactly
	 * "hello world".
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .content(allOf(notNullValue(), is("hello world"))
	 * </pre>
	 * 
	 * Or:
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .content(notNullValue())
	 *    .content(is("hello world"))
	 * </pre>
	 * 
	 * Will check if the content of every message is not null and is exactly
	 * "hello world".
	 * 
	 * @param charset
	 *            the charset to use to decode the message (bytes to string)
	 * @param matcher
	 *            the assertion to apply on message content
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentSmsAssert<P, S> content(Charset charset, Matcher<? super String> matcher) {
		String desc = "content of message ${messageIndex}";
		int msgIdx = index;
		for (S message : actual) {
			final int idx = msgIdx;
			registry.register(() -> assertThat(getSmsContent(message, charset), usingContext(desc, new SingleMessageContext(idx), matcher)));
			msgIdx++;
		}
		return this;
	}

	/**
	 * Make assertions on the raw request ({@link SubmitSm}) of the message(s)
	 * using fluent API.
	 * 
	 * <pre>
	 * .receivedMessages().message(0).rawRequest()
	 *   .alphabet(is(Alphabet.ALPHA_DEFAULT))
	 *   .shortMessage()
	 *     .header(array(equalTo(0x01), equalTo(0x02), equalTo(0x03))))
	 *     .payload(arrayWithSize(10))
	 * </pre>
	 * 
	 * Will check if the received byte array of the first message has a header
	 * with some expected bytes, the alphabet used to encode the message is
	 * ALPHA_DEFAULT and the payload has exactly 10 bytes.
	 * 
	 * <pre>
	 * .receivedMessages().every().rawRequest()
	 *   .alphabet(is(Alphabet.ALPHA_DEFAULT))
	 *   .shortMessage()
	 *     .header(array(equalTo(0x01), equalTo(0x02), equalTo(0x03))))
	 *     .payload(arrayWithSize(10))
	 * </pre>
	 * 
	 * Will check if the received byte array of every message has a header with
	 * some expected bytes, the alphabet used to encode the message is
	 * ALPHA_DEFAULT and the payload has exactly 10 bytes.
	 * 
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPduRequestAssert<FluentSmsAssert<P, S>, S> rawRequest() {
		List<PduRequestWithContext<S>> requests = new ArrayList<>();
		int msgIdx = index;
		for (S request : actual) {
			requests.add(new PduRequestWithContext<>(request, "raw request", new SingleMessageContext(msgIdx)));
			msgIdx++;
		}
		return new FluentPduRequestAssert<>(requests, this, registry);
	}

	/**
	 * Make assertions on the sender of the message(s) using fluent API.
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .from(notNullValue())
	 * </pre>
	 * 
	 * Will check if the sender phone number of the first message is not null.
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .from(notNullValue())
	 * </pre>
	 * 
	 * Will check if the sender phone number of every message is not null.
	 * 
	 * @param matcher
	 *            the assertion to apply on the phone number
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentSmsAssert<P, S> from(Matcher<PhoneNumberInfo> matcher) {
		String desc = "sender of message ${messageIndex}";
		int msgIdx = index;
		for (S message : actual) {
			PhoneNumberInfo number = new PhoneNumberInfo(message.getSourceAddress());
			final int idx = msgIdx;
			registry.register(() -> assertThat(number, usingContext(desc, new SingleMessageContext(idx), matcher)));
			msgIdx++;
		}
		return this;
	}

	/**
	 * Make assertions on the sender of the message(s) using fluent API.
	 * 
	 * <pre>
	 * .receivedMessages().message(0).from()
	 *    .number(is("+33102030405"))
	 *    .typeOfNumber(is(TypeOfNumber.INTERNATIONAL))
	 * </pre>
	 * 
	 * Will check if the sender phone number of the first message is exactly
	 * "+33102030405" and sender phone number type of the first message is an
	 * international number.
	 * 
	 * <pre>
	 * .receivedMessages().every().from()
	 *    .number(is("+33102030405"))
	 *    .typeOfNumber(is(TypeOfNumber.INTERNATIONAL))
	 * </pre>
	 * 
	 * Will check if the sender phone number of every message is exactly
	 * "+33102030405" and sender phone number type of every message is an
	 * international number.
	 * 
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPhoneNumberAssert<FluentSmsAssert<P, S>> from() {
		List<PhoneNumberWithContext> numbers = new ArrayList<>();
		int msgIdx = index;
		for (S message : actual) {
			PhoneNumberInfo number = new PhoneNumberInfo(message.getSourceAddress());
			numbers.add(new PhoneNumberWithContext(number, "sender", new SingleMessageContext(msgIdx)));
			msgIdx++;
		}
		return new FluentPhoneNumberAssert<>(numbers, this, registry);
	}

	/**
	 * Make assertions on the recipient of the message(s) using fluent API.
	 * 
	 * <pre>
	 * .receivedMessages().message(0)
	 *    .to(notNullValue())
	 * </pre>
	 * 
	 * Will check if the recipient phone number of the first message is not
	 * null.
	 * 
	 * <pre>
	 * .receivedMessages().every()
	 *    .to(notNullValue())
	 * </pre>
	 * 
	 * Will check if the recipient phone number of every message is not null.
	 * 
	 * @param matcher
	 *            the assertion to apply on the phone number
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentSmsAssert<P, S> to(Matcher<PhoneNumberInfo> matcher) {
		String desc = "recipient of message ${messageIndex}";
		int msgIdx = index;
		for (S message : actual) {
			PhoneNumberInfo number = new PhoneNumberInfo(message.getDestAddress());
			final int idx = msgIdx;
			registry.register(() -> assertThat(number, usingContext(desc, new SingleMessageContext(idx), matcher)));
			msgIdx++;
		}
		return this;
	}

	/**
	 * Make assertions on the recipient of the message(s) using fluent API.
	 * 
	 * <pre>
	 * .receivedMessages().message(0).to()
	 *    .number(is("+33102030405"))
	 *    .typeOfNumber(is(TypeOfNumber.INTERNATIONAL))
	 * </pre>
	 * 
	 * Will check if the recipient phone number of the first message is exactly
	 * "+33102030405" and recipient phone number type of the first message is an
	 * international number.
	 * 
	 * <pre>
	 * .receivedMessages().every().to()
	 *    .number(is("+33102030405"))
	 *    .typeOfNumber(is(TypeOfNumber.INTERNATIONAL))
	 * </pre>
	 * 
	 * Will check if the recipient phone number of every message is exactly
	 * "+33102030405" and recipient phone number type of every message is an
	 * international number.
	 * 
	 * @return the fluent API for chaining assertions on received message(s)
	 */
	public FluentPhoneNumberAssert<FluentSmsAssert<P, S>> to() {
		List<PhoneNumberWithContext> numbers = new ArrayList<>();
		int msgIdx = index;
		for (S message : actual) {
			PhoneNumberInfo number = new PhoneNumberInfo(message.getDestAddress());
			numbers.add(new PhoneNumberWithContext(number, "recipient", new SingleMessageContext(msgIdx)));
			msgIdx++;
		}
		return new FluentPhoneNumberAssert<>(numbers, this, registry);
	}

}