/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.cassandra;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Configuration;
import com.datastax.driver.core.ConsistencyLevel;
import com.datastax.driver.core.Metadata;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import com.datastax.driver.core.exceptions.UnavailableException;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.junit.Before;
import org.junit.Test;
import javax.net.ssl.SSLContext;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for the PutCassandraQL processor
*/
public class PutCassandraQLTest {
private TestRunner testRunner;
private MockPutCassandraQL processor;
@Before
public void setUp() throws Exception {
processor = new MockPutCassandraQL();
testRunner = TestRunners.newTestRunner(processor);
}
@Test
public void testProcessorConfigValidity() {
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "localhost:9042");
testRunner.assertValid();
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "password");
testRunner.assertNotValid();
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "username");
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.assertValid();
}
@Test
public void testProcessorELConfigValidity() {
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "${hosts}");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "${pass}");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "${user}");
testRunner.setProperty(AbstractCassandraProcessor.CHARSET, "${charset}");
testRunner.setProperty(PutCassandraQL.STATEMENT_TIMEOUT, "${timeout}");
testRunner.assertValid();
}
@Test
public void testProcessorHappyPath() {
setUpStandardTestConfig();
testRunner.enqueue("INSERT INTO users (user_id, first_name, last_name, properties, bits, scaleset, largenum, scale, byteobject, ts) VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
new HashMap<String, String>() {
{
put("cql.args.1.type", "int");
put("cql.args.1.value", "1");
put("cql.args.2.type", "text");
put("cql.args.2.value", "Joe");
put("cql.args.3.type", "text");
// No value for arg 3 to test setNull
put("cql.args.4.type", "map<text,text>");
put("cql.args.4.value", "{'a':'Hello', 'b':'World'}");
put("cql.args.5.type", "list<boolean>");
put("cql.args.5.value", "[true,false,true]");
put("cql.args.6.type", "set<double>");
put("cql.args.6.value", "{1.0, 2.0}");
put("cql.args.7.type", "bigint");
put("cql.args.7.value", "20000000");
put("cql.args.8.type", "float");
put("cql.args.8.value", "1.0");
put("cql.args.9.type", "blob");
put("cql.args.9.value", "0xDEADBEEF");
put("cql.args.10.type", "timestamp");
put("cql.args.10.value", "2016-07-01T15:21:05Z");
}
});
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_SUCCESS, 1);
testRunner.clearTransferState();
}
@Test
public void testProcessorHappyPathELConfig() {
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "${hosts}");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "${pass}");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "${user}");
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.setProperty(AbstractCassandraProcessor.CHARSET, "${charset}");
testRunner.setProperty(PutCassandraQL.STATEMENT_TIMEOUT, "${timeout}");
testRunner.assertValid();
testRunner.setVariable("hosts", "localhost:9042");
testRunner.setVariable("user", "username");
testRunner.setVariable("pass", "password");
testRunner.setVariable("charset", "UTF-8");
testRunner.setVariable("timeout", "30 sec");
testRunner.enqueue("INSERT INTO users (user_id, first_name, last_name, properties, bits, scaleset, largenum, scale, byteobject, ts) VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
new HashMap<String, String>() {
{
put("cql.args.1.type", "int");
put("cql.args.1.value", "1");
put("cql.args.2.type", "text");
put("cql.args.2.value", "Joe");
put("cql.args.3.type", "text");
// No value for arg 3 to test setNull
put("cql.args.4.type", "map<text,text>");
put("cql.args.4.value", "{'a':'Hello', 'b':'World'}");
put("cql.args.5.type", "list<boolean>");
put("cql.args.5.value", "[true,false,true]");
put("cql.args.6.type", "set<double>");
put("cql.args.6.value", "{1.0, 2.0}");
put("cql.args.7.type", "bigint");
put("cql.args.7.value", "20000000");
put("cql.args.8.type", "float");
put("cql.args.8.value", "1.0");
put("cql.args.9.type", "blob");
put("cql.args.9.value", "0xDEADBEEF");
put("cql.args.10.type", "timestamp");
put("cql.args.10.value", "2016-07-01T15:21:05Z");
}
});
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_SUCCESS, 1);
testRunner.clearTransferState();
}
@Test
public void testProcessorBadTimestamp() {
setUpStandardTestConfig();
processor.setExceptionToThrow(
new InvalidQueryException(new InetSocketAddress("localhost", 9042), "invalid timestamp"));
testRunner.enqueue("INSERT INTO users (user_id, first_name, last_name, properties, bits, scaleset, largenum, scale, byteobject, ts) VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?",
new HashMap<String, String>() {
{
put("cql.args.1.type", "int");
put("cql.args.1.value", "1");
put("cql.args.2.type", "text");
put("cql.args.2.value", "Joe");
put("cql.args.3.type", "text");
// No value for arg 3 to test setNull
put("cql.args.4.type", "map<text,text>");
put("cql.args.4.value", "{'a':'Hello', 'b':'World'}");
put("cql.args.5.type", "list<boolean>");
put("cql.args.5.value", "[true,false,true]");
put("cql.args.6.type", "set<double>");
put("cql.args.6.value", "{1.0, 2.0}");
put("cql.args.7.type", "bigint");
put("cql.args.7.value", "20000000");
put("cql.args.8.type", "float");
put("cql.args.8.value", "1.0");
put("cql.args.9.type", "blob");
put("cql.args.9.value", "0xDEADBEEF");
put("cql.args.10.type", "timestamp");
put("cql.args.10.value", "not a timestamp");
}
});
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_FAILURE, 1);
testRunner.clearTransferState();
}
@Test
public void testProcessorInvalidQueryException() {
setUpStandardTestConfig();
// Test exceptions
processor.setExceptionToThrow(
new InvalidQueryException(new InetSocketAddress("localhost", 9042), "invalid query"));
testRunner.enqueue("UPDATE users SET cities = [ 'New York', 'Los Angeles' ] WHERE user_id = 'coast2coast';");
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_FAILURE, 1);
testRunner.clearTransferState();
}
@Test
public void testProcessorUnavailableException() {
setUpStandardTestConfig();
processor.setExceptionToThrow(
new UnavailableException(new InetSocketAddress("localhost", 9042), ConsistencyLevel.ALL, 5, 2));
testRunner.enqueue("UPDATE users SET cities = [ 'New York', 'Los Angeles' ] WHERE user_id = 'coast2coast';");
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_RETRY, 1);
}
@Test
public void testProcessorNoHostAvailableException() {
setUpStandardTestConfig();
processor.setExceptionToThrow(new NoHostAvailableException(new HashMap<InetSocketAddress, Throwable>()));
testRunner.enqueue("UPDATE users SET cities = [ 'New York', 'Los Angeles' ] WHERE user_id = 'coast2coast';");
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_RETRY, 1);
}
@Test
public void testProcessorProcessException() {
setUpStandardTestConfig();
processor.setExceptionToThrow(new ProcessException());
testRunner.enqueue("UPDATE users SET cities = [ 'New York', 'Los Angeles' ] WHERE user_id = 'coast2coast';");
testRunner.run(1, true, true);
testRunner.assertAllFlowFilesTransferred(PutCassandraQL.REL_FAILURE, 1);
}
private void setUpStandardTestConfig() {
testRunner.setProperty(AbstractCassandraProcessor.CONTACT_POINTS, "localhost:9042");
testRunner.setProperty(AbstractCassandraProcessor.PASSWORD, "password");
testRunner.setProperty(AbstractCassandraProcessor.USERNAME, "username");
testRunner.setProperty(AbstractCassandraProcessor.CONSISTENCY_LEVEL, "ONE");
testRunner.assertValid();
}
/**
* Provides a stubbed processor instance for testing
*/
private static class MockPutCassandraQL extends PutCassandraQL {
private Exception exceptionToThrow = null;
private Session mockSession = mock(Session.class);
@Override
protected Cluster createCluster(List<InetSocketAddress> contactPoints, SSLContext sslContext,
String username, String password) {
Cluster mockCluster = mock(Cluster.class);
try {
Metadata mockMetadata = mock(Metadata.class);
when(mockMetadata.getClusterName()).thenReturn("cluster1");
when(mockCluster.getMetadata()).thenReturn(mockMetadata);
when(mockCluster.connect()).thenReturn(mockSession);
when(mockCluster.connect(anyString())).thenReturn(mockSession);
Configuration config = Configuration.builder().build();
when(mockCluster.getConfiguration()).thenReturn(config);
ResultSetFuture future = mock(ResultSetFuture.class);
ResultSet rs = CassandraQueryTestUtil.createMockResultSet();
PreparedStatement ps = mock(PreparedStatement.class);
when(mockSession.prepare(anyString())).thenReturn(ps);
BoundStatement bs = mock(BoundStatement.class);
when(ps.bind()).thenReturn(bs);
when(future.getUninterruptibly()).thenReturn(rs);
try {
doReturn(rs).when(future).getUninterruptibly(anyLong(), any(TimeUnit.class));
} catch (TimeoutException te) {
throw new IllegalArgumentException("Mocked cluster doesn't time out");
}
if (exceptionToThrow != null) {
doThrow(exceptionToThrow).when(mockSession).executeAsync(anyString());
doThrow(exceptionToThrow).when(mockSession).executeAsync(any(Statement.class));
} else {
when(mockSession.executeAsync(anyString())).thenReturn(future);
when(mockSession.executeAsync(any(Statement.class))).thenReturn(future);
}
when(mockSession.getCluster()).thenReturn(mockCluster);
} catch (Exception e) {
fail(e.getMessage());
}
return mockCluster;
}
public void setExceptionToThrow(Exception e) {
exceptionToThrow = e;
doThrow(exceptionToThrow).when(mockSession).executeAsync(anyString());
doThrow(exceptionToThrow).when(mockSession).executeAsync(any(Statement.class));
}
}
}