/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.test.module.extension.streaming;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mule.runtime.api.util.DataUnit.KB;
import static org.mule.runtime.extension.api.ExtensionConstants.STREAMING_STRATEGY_PARAMETER_NAME;
import static org.mule.test.allure.AllureConstants.StreamingFeature.STREAMING;
import static org.mule.test.allure.AllureConstants.StreamingFeature.StreamingStory.BYTES_STREAMING;
import static org.mule.test.module.extension.internal.util.ExtensionsTestUtils.assertType;
import org.mule.metadata.api.model.UnionType;
import org.mule.runtime.api.exception.MuleException;
import org.mule.runtime.api.message.Message;
import org.mule.runtime.api.meta.model.config.ConfigurationModel;
import org.mule.runtime.api.meta.model.parameter.ParameterModel;
import org.mule.runtime.api.meta.model.parameter.ParameterizedModel;
import org.mule.runtime.api.streaming.bytes.CursorStreamProvider;
import org.mule.runtime.core.api.Event;
import org.mule.runtime.core.api.construct.Flow;
import org.mule.runtime.core.util.IOUtils;
import org.mule.tck.probe.JUnitLambdaProbe;
import org.mule.tck.probe.PollingProber;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.junit.Test;
import ru.yandex.qatools.allure.annotations.Description;
import ru.yandex.qatools.allure.annotations.Features;
import ru.yandex.qatools.allure.annotations.Stories;
@Features(STREAMING)
@Stories(BYTES_STREAMING)
public class BytesStreamingExtensionTestCase extends AbstractStreamingExtensionTestCase {
private static final String BARGAIN_SPELL = "dormammu i've come to bargain";
public static final String TOO_BIG = "Too big!";
private static List<String> CASTED_SPELLS = new LinkedList<>();
public static void addSpell(String spell) {
synchronized (CASTED_SPELLS) {
CASTED_SPELLS.add(spell);
}
}
private String data = randomAlphabetic(2048);
@Override
protected String getConfigFile() {
return "bytes-streaming-extension-config.xml";
}
@Override
protected void doTearDownAfterMuleContextDispose() throws Exception {
super.doTearDownAfterMuleContextDispose();
CASTED_SPELLS.clear();
}
@Override
protected boolean isDisposeContextPerClass() {
return true;
}
@Test
@Description("Fully consume a cursor stream")
public void consumeGeneratedCursorAndCloseIt() throws Exception {
Object value = flowRunner("consumeGeneratedStream").withPayload(data).run().getMessage().getPayload().getValue();
assertThat(value, is(data));
}
@Test
@Description("Operation with disabled streaming")
public void operationWithDisabledStreaming() throws Exception {
Object value = flowRunner("toSimpleStream").withPayload(data).run().getMessage().getPayload().getValue();
assertThat(value, is(instanceOf(InputStream.class)));
assertThat(IOUtils.toString((InputStream) value), is(data));
}
@Test(expected = Exception.class)
@Description("If the flow fails, all cursors should be closed")
public void allStreamsClosedInCaseOfException() throws Exception {
flowRunner("crashCar").withPayload(data).run();
}
@Test(expected = Exception.class)
@Description("If a cursor is open in a transaction, it should be closed if the flow fails")
public void allStreamsClosedInCaseOfExceptionInTx() throws Exception {
flowRunner("crashCarTx").withPayload(data).run();
}
@Test
@Description("Read a stream from a random position")
public void seek() throws Exception {
doSeek("seekStream");
}
@Test
@Description("Rewing a stream and consume it twice")
public void rewind() throws Exception {
Event result = flowRunner("rewind").withPayload(data).run();
Message firstRead = (Message) result.getVariable("firstRead").getValue();
Message secondRead = (Message) result.getVariable("secondRead").getValue();
assertThat(firstRead.getPayload().getValue(), equalTo(data));
assertThat(secondRead.getPayload().getValue(), equalTo(data));
}
@Test
@Description("Read from a random position inside a transaction")
public void seekInTx() throws Exception {
doSeek("seekStreamTx");
}
@Test
@Description("When the max buffer size is exceeded, the correct type of error is mapped")
public void throwsBufferSizeExceededError() throws Exception {
data = randomAlphabetic(KB.toBytes(60));
Object value = flowRunner("bufferExceeded").withPayload(data).run().getMessage().getPayload().getValue();
assertThat(value, is(TOO_BIG));
}
private void doSeek(String flowName) throws Exception {
final int position = 10;
Event result = flowRunner(flowName)
.withPayload(data)
.withVariable("position", position)
.run();
Object value = result.getMessage().getPayload().getValue();
assertThat(value, is(data.substring(position)));
}
@Test
@Description("A source generates a cursor stream")
public void sourceStreaming() throws Exception {
startSourceAndListenSpell("bytesCaster", bargainPredicate());
}
@Test
@Description("When the max buffer size is exceeded on a stream generated in a source, the correct type of error is mapped")
public void sourceThrowsBufferSizeExceededError() throws Exception {
startSourceAndListenSpell("sourceWithExceededBuffer", s -> TOO_BIG.equals(s));
}
@Test
@Description("A source generates a cursor in a transaction")
public void sourceStreamingInTx() throws Exception {
startSourceAndListenSpell("bytesCasterInTx", bargainPredicate());
}
@Test
@Description("A source is configured not to stream")
public void sourceWithoutStreaming() throws Exception {
startSourceAndListenSpell("bytesCasterWithoutStreaming", bargainPredicate());
}
@Test
@Description("A stream provider is serialized as a byte[]")
public void streamProviderSerialization() throws Exception {
CursorStreamProvider provider = (CursorStreamProvider) flowRunner("toStream").keepStreamsOpen()
.withPayload(data)
.run().getMessage().getPayload().getValue();
byte[] bytes = muleContext.getObjectSerializer().getInternalProtocol().serialize(provider);
bytes = muleContext.getObjectSerializer().getInternalProtocol().deserialize(bytes);
assertThat(new String(bytes, Charset.defaultCharset()), equalTo(data));
}
@Test
@Description("Streaming operation has a streaming strategy parameter")
public void streamingStrategyParameterInOperation() throws Exception {
ParameterModel streamingParameter =
getStreamingStrategyParameterModel(() -> getConfigurationModel().getOperationModel("toStream").get());
assertStreamingStrategyParameter(streamingParameter);
}
@Test
@Description("Streaming source has a streaming strategy parameter")
public void streamingStrategyParameterInSource() throws Exception {
ParameterModel streamingParameter =
getStreamingStrategyParameterModel(() -> getConfigurationModel().getSourceModel("bytes-caster").get());
assertStreamingStrategyParameter(streamingParameter);
}
private ParameterModel getStreamingStrategyParameterModel(Supplier<ParameterizedModel> model) {
return model.get().getAllParameterModels().stream()
.filter(p -> p.getName().equals(STREAMING_STRATEGY_PARAMETER_NAME))
.findFirst()
.get();
}
private ConfigurationModel getConfigurationModel() {
return getExtensionModel("Marvel")
.map(extension -> extension.getConfigurationModel("dr-strange").get())
.get();
}
private void assertStreamingStrategyParameter(ParameterModel parameter) {
assertType(parameter.getType(), Object.class, UnionType.class);
}
private void startSourceAndListenSpell(String flowName, Predicate<String> predicate) throws Exception {
startFlow(flowName);
new PollingProber(4000, 100).check(new JUnitLambdaProbe(() -> {
synchronized (CASTED_SPELLS) {
return CASTED_SPELLS.stream().anyMatch(predicate);
}
}));
}
private Predicate<String> bargainPredicate() {
return s -> s.equals(BARGAIN_SPELL);
}
private void startFlow(String flowName) throws MuleException {
Flow flow = muleContext.getRegistry().get(flowName);
flow.start();
}
}