blob: 934d12ac392468ec7edf7bd4b0afdbf55773c1e5 [file] [log] [blame]
/*
* 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.drill.exec.store.http;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.TypeProtos.MinorType;
import org.apache.drill.common.util.DrillFileUtils;
import org.apache.drill.exec.physical.rowSet.RowSet;
import org.apache.drill.exec.physical.rowSet.RowSetBuilder;
import org.apache.drill.exec.record.metadata.SchemaBuilder;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.apache.drill.common.logical.security.PlainCredentialsProvider;
import org.apache.drill.shaded.guava.com.google.common.base.Charsets;
import org.apache.drill.shaded.guava.com.google.common.io.Files;
import org.apache.drill.test.ClusterFixture;
import org.apache.drill.test.ClusterTest;
import org.apache.drill.test.rowSet.RowSetUtilities;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.apache.drill.test.rowSet.RowSetUtilities.mapArray;
import static org.apache.drill.test.rowSet.RowSetUtilities.mapValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Tests the HTTP Storage plugin. Since the plugin makes use of REST requests,
* this test class makes use of the okhttp3 MockWebServer to simulate a remote
* web server. There are two unit tests that make remote REST calls, however
* these tests are ignored by default.
* <p>
* The HTTP reader uses Drill's existing JSON reader class, so the unit tests
* focus on testing the plugin configurations rather than how well it parses the
* JSON as this is tested elsewhere.
*/
public class TestHttpPlugin extends ClusterTest {
private static final int MOCK_SERVER_PORT = 8091;
private static String TEST_JSON_RESPONSE;
private static String TEST_CSV_RESPONSE;
private static String TEST_XML_RESPONSE;
@BeforeClass
public static void setup() throws Exception {
startCluster(ClusterFixture.builder(dirTestWatcher));
TEST_JSON_RESPONSE = Files.asCharSource(DrillFileUtils.getResourceAsFile("/data/response.json"), Charsets.UTF_8).read();
TEST_CSV_RESPONSE = Files.asCharSource(DrillFileUtils.getResourceAsFile("/data/response.csv"), Charsets.UTF_8).read();
TEST_XML_RESPONSE = Files.asCharSource(DrillFileUtils.getResourceAsFile("/data/response.xml"), Charsets.UTF_8).read();
dirTestWatcher.copyResourceToRoot(Paths.get("data/"));
makeLiveConfig();
makeMockConfig();
}
/**
* Create configs against live external servers. Must be tested manually, and
* subject to the whims of the external site. Timeout is 10 seconds to allow
* for real-world delays.
*/
private static void makeLiveConfig() {
HttpApiConfig sunriseConfig = new HttpApiConfig("https://api.sunrise-sunset.org/json", "GET", null, null, null, null, null, null, null, null, null, 0);
HttpApiConfig sunriseWithParamsConfig = new HttpApiConfig("https://api.sunrise-sunset.org/json", "GET", null, null, null, null, null,
Arrays.asList("lat", "lng", "date"), "results", false, null, 0);
HttpApiConfig stockConfig = new HttpApiConfig("https://api.worldtradingdata.com/api/v1/stock?symbol=SNAP,TWTR,VOD" +
".L&api_token=zuHlu2vZaehdZN6GmJdTiVlp7xgZn6gl6sfgmI4G6TY4ej0NLOzvy0TUl4D4", "get", null, null, null, null, null, null, null, null, null, 0);
Map<String, HttpApiConfig> configs = new HashMap<>();
configs.put("stock", stockConfig);
configs.put("sunrise", sunriseConfig);
configs.put("sunrise2", sunriseWithParamsConfig);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
new HttpStoragePluginConfig(false, configs, 10, "", 80, "", "", "", PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("live", mockStorageConfigWithWorkspace);
}
/**
* Create configs for an in-process mock server. Used for normal automated unit
* testing. Timeout is short to allow for timeout testing. The mock server is
* useful, but won't catch bugs related to real-world server glitches.
*/
private static void makeMockConfig() {
Map<String, String> headers = new HashMap<>();
headers.put("header1", "value1");
headers.put("header2", "value2");
// Use the mock server with HTTP parameters passed as table name.
// The connection acts like a schema.
// Ignores the message body except for data.
HttpApiConfig mockSchema = new HttpApiConfig("http://localhost:8091/json", "GET", headers,
"basic", "user", "pass", null, null, "results", null, null, 0);
// Use the mock server with the HTTP parameters passed as WHERE
// clause filters. The connection acts like a table.
// Ignores the message body except for data.
// This is the preferred approach, the base URL contains as much info as possible;
// all other parameters are specified in SQL. See README for an example.
HttpApiConfig mockTable = new HttpApiConfig("http://localhost:8091/json", "GET", headers,
"basic", "user", "pass", null, Arrays.asList("lat", "lng", "date"), "results", false, null, 0);
HttpApiConfig mockPostConfig = new HttpApiConfig("http://localhost:8091/", "POST", headers, null, null, null, "key1=value1\nkey2=value2", null, null, null, null, 0);
HttpApiConfig mockCsvConfig = new HttpApiConfig("http://localhost:8091/csv", "GET", headers,
"basic", "user", "pass", null, null, "results", null, "csv", 0);
HttpApiConfig mockXmlConfig = new HttpApiConfig("http://localhost:8091/xml", "GET", headers,
"basic", "user", "pass", null, null, "results", null, "xml", 2);
Map<String, HttpApiConfig> configs = new HashMap<>();
configs.put("sunrise", mockSchema);
configs.put("mocktable", mockTable);
configs.put("mockpost", mockPostConfig);
configs.put("mockcsv", mockCsvConfig);
configs.put("mockxml", mockXmlConfig);
HttpStoragePluginConfig mockStorageConfigWithWorkspace =
new HttpStoragePluginConfig(false, configs, 2, "", 80, "", "", "", PlainCredentialsProvider.EMPTY_CREDENTIALS_PROVIDER);
mockStorageConfigWithWorkspace.setEnabled(true);
cluster.defineStoragePlugin("local", mockStorageConfigWithWorkspace);
}
@Test
public void verifyPluginConfig() throws Exception {
String sql = "SELECT SCHEMA_NAME, TYPE FROM INFORMATION_SCHEMA.`SCHEMATA` WHERE TYPE='http'\n" +
"ORDER BY SCHEMA_NAME";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("SCHEMA_NAME", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("TYPE", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.buildSchema();
// Expect table-like connections to NOT appear here.
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("live", "http") // For table-like connections
.addRow("live.stock", "http")
.addRow("live.sunrise", "http")
.addRow("local", "http")
.addRow("local.mockcsv", "http")
.addRow("local.mockpost", "http")
.addRow("local.mockxml", "http")
.addRow("local.sunrise", "http")
.build();
RowSetUtilities.verify(expected, results);
}
/**
* Evaluates the HTTP plugin with the results from an API that returns the
* sunrise/sunset times for a given lat/long and date. API documentation is
* available here: https://sunrise-sunset.org/api
*
* The API returns results in the following format:
* <pre><code>
* {
* "results":
* {
* "sunrise":"7:27:02 AM",
* "sunset":"5:05:55 PM",
* "solar_noon":"12:16:28 PM",
* "day_length":"9:38:53",
* "civil_twilight_begin":"6:58:14 AM",
* "civil_twilight_end":"5:34:43 PM",
* "nautical_twilight_begin":"6:25:47 AM",
* "nautical_twilight_end":"6:07:10 PM",
* "astronomical_twilight_begin":"5:54:14 AM",
* "astronomical_twilight_end":"6:38:43 PM"
* },
* "status":"OK"
* }
* }</code></pre>
*
* @throws Exception
* Throws exception if something goes awry
*/
@Test
@Ignore("Requires Remote Server")
public void simpleStarQuery() throws Exception {
String sql = "SELECT * FROM live.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.addMap("results")
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("solar_noon", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("day_length", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.resumeSchema()
.add("status", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow(mapValue("6:13:58 AM", "5:59:55 PM", "12:06:56 PM", "11:45:57",
"5:48:14 AM", "6:25:38 PM", "5:18:16 AM", "6:55:36 PM",
"4:48:07 AM", "7:25:45 PM"), "OK")
.build();
RowSetUtilities.verify(expected, results);
}
/**
* As above, but we return only the contents of {@code results}, and use
* filter push-down for the arguments.
*
* @throws Exception
*/
@Test
@Ignore("Requires Remote Server")
public void wildcardQueryWithParams() throws Exception {
String sql =
"SELECT * FROM live.sunrise2\n" +
"WHERE `lat`=36.7201600 AND `lng`=-4.4203400 AND `date`='2019-10-02'";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("solar_noon", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("day_length", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("6:13:58 AM", "5:59:55 PM", "12:06:56 PM", "11:45:57", "5:48:14 AM",
"6:25:38 PM", "5:18:16 AM", "6:55:36 PM", "4:48:07 AM", "7:25:45 PM")
.build();
RowSetUtilities.verify(expected, results);
}
@Test
@Ignore("Requires Remote Server")
public void simpleSpecificQuery() throws Exception {
String sql = "SELECT t1.results.sunrise AS sunrise, t1.results.sunset AS sunset\n" +
"FROM live.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02` AS t1";
doSimpleSpecificQuery(sql);
}
@Test
@Ignore("Requires Remote Server")
public void simpleSpecificQueryWithParams() throws Exception {
String sql =
"SELECT sunrise, sunset\n" +
"FROM live.sunrise2\n" +
"WHERE `lat`=36.7201600 AND `lng`=-4.4203400 AND `date`='2019-10-02'";
doSimpleSpecificQuery(sql);
}
private void doSimpleSpecificQuery(String sql) throws Exception {
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.buildSchema();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("6:13:58 AM", "5:59:55 PM")
.build();
RowSetUtilities.verify(expected, results);
}
@Test
public void testSerDe() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
);
String sql = "SELECT COUNT(*) FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
String plan = queryBuilder().sql(sql).explainJson();
long cnt = queryBuilder().physical(plan).singletonLong();
assertEquals("Counts should match", 1L, cnt);
}
}
@Test
public void simpleTestWithMockServer() throws Exception {
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
doSimpleTestWithMockServer(sql);
}
@Test
public void simpleTestWithMockServerWithParams() throws Exception {
String sql = "SELECT * FROM local.mocktable\n" +
"WHERE `lat` = 36.7201600 AND `lng` = -4.4203400 AND `date` = '2019-10-02'";
doSimpleTestWithMockServer(sql);
}
@Test
public void testCsvResponse() throws Exception {
String sql = "SELECT * FROM local.mockcsv.`csv?arg1=4`";
try (MockWebServer server = startServer()) {
server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_CSV_RESPONSE));
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("col1", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("col2", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("col3", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("1", "2", "3")
.addRow("4", "5", "6")
.build();
RowSetUtilities.verify(expected, results);
}
}
@Test
public void testXmlResponse() throws Exception {
String sql = "SELECT * FROM local.mockxml.`?arg1=4` LIMIT 5";
try (MockWebServer server = startServer()) {
server.enqueue(new MockResponse().setResponseCode(200).setBody(TEST_XML_RESPONSE));
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("attributes", MinorType.MAP)
.addNullable("COMMON", MinorType.VARCHAR)
.addNullable("BOTANICAL", MinorType.VARCHAR)
.addNullable("ZONE", MinorType.VARCHAR)
.addNullable("LIGHT", MinorType.VARCHAR)
.addNullable("PRICE", MinorType.VARCHAR)
.addNullable("AVAILABILITY", MinorType.VARCHAR)
.buildSchema();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow(mapArray(), "Bloodroot", "Sanguinaria canadensis", "4", "Mostly Shady", "$2.44", "031599")
.addRow(mapArray(),"Columbine", "Aquilegia canadensis", "3", "Mostly Shady", "$9.37", "030699")
.addRow(mapArray(),"Marsh Marigold", "Caltha palustris", "4", "Mostly Sunny", "$6.81", "051799")
.addRow(mapArray(), "Cowslip", "Caltha palustris", "4", "Mostly Shady", "$9.90", "030699")
.addRow(mapArray(), "Dutchman's-Breeches", "Dicentra cucullaria", "3", "Mostly Shady", "$6.44", "012099")
.build();
RowSetUtilities.verify(expected, results);
}
}
private void doSimpleTestWithMockServer(String sql) throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
);
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("solar_noon", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("day_length", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("6:13:58 AM", "5:59:55 PM", "12:06:56 PM", "11:45:57", "5:48:14 AM", "6:25:38 PM", "5:18:16 AM", "6:55:36 PM", "4:48:07 AM", "7:25:45 PM")
.build();
RowSetUtilities.verify(expected, results);
}
}
@Test
public void testPostWithMockServer() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse()
.setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
);
String sql = "SELECT * FROM local.mockPost.`json?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.addMap("results")
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("solar_noon", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("day_length", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.resumeSchema()
.add("status", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow(mapValue("6:13:58 AM", "5:59:55 PM", "12:06:56 PM", "11:45:57", "5:48:14 AM", "6:25:38 PM", "5:18:16 AM", "6:55:36 PM", "4:48:07 AM", "7:25:45 PM"), "OK")
.build();
RowSetUtilities.verify(expected, results);
RecordedRequest recordedRequest = server.takeRequest();
assertEquals("POST", recordedRequest.getMethod());
assertEquals(recordedRequest.getHeader("header1"), "value1");
assertEquals(recordedRequest.getHeader("header2"), "value2");
}
}
@Test
public void specificTestWithMockServer() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
);
String sql = "SELECT sunrise, sunset FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02` AS t1";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.buildSchema();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("6:13:58 AM", "5:59:55 PM")
.build();
RowSetUtilities.verify(expected, results);
}
}
@Test
public void testLimitPushdown() throws Exception {
String sql = "SELECT sunrise, sunset FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02` AS t1 LIMIT 5";
queryBuilder()
.sql(sql)
.planMatcher()
.include("Limit", "maxRecords=5")
.match();
}
@Test
public void testSlowResponse() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
.throttleBody(64, 4, TimeUnit.SECONDS)
);
String sql = "SELECT sunrise AS sunrise, sunset AS sunset FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02` AS t1";
try {
client.queryBuilder().sql(sql).rowSet();
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("DATA_READ ERROR: timeout"));
}
}
}
@Test
public void testZeroByteResponse() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody("")
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
assertNull(results);
}
}
// The connection expects a response object of the form
// { results: { ... } }, but there is no such object, which
// is treated as a null (no data, no schema) result set.
@Test
public void testEmptyJSONObjectResponse() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody("{}")
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
assertNull(results);
}
}
@Test
public void testNullContent() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody("{results: null}")
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
assertNull(results);
}
}
// Note that, in this test, the response is not empty. Instead, the
// response has a single row with no columns.
@Test
public void testEmptyContent() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody("{results: {} }")
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.buildSchema();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow()
.build();
RowSetUtilities.verify(expected, results);
}
}
@Test
public void testErrorResponse() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(404)
.setBody("{}")
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
try {
client.queryBuilder().sql(sql).rowSet();
fail();
} catch (Exception e) {
String msg = e.getMessage();
assertTrue(msg.contains("DATA_READ ERROR: HTTP request failed"));
assertTrue(msg.contains("Response code: 404"));
assertTrue(msg.contains("Response message: Client Error"));
assertTrue(msg.contains("Connection: sunrise"));
assertTrue(msg.contains("Plugin: local"));
}
}
}
@Test
public void testHeaders() throws Exception {
try (MockWebServer server = startServer()) {
server.enqueue(
new MockResponse().setResponseCode(200)
.setBody(TEST_JSON_RESPONSE)
);
String sql = "SELECT * FROM local.sunrise.`?lat=36.7201600&lng=-4.4203400&date=2019-10-02`";
RowSet results = client.queryBuilder().sql(sql).rowSet();
TupleMetadata expectedSchema = new SchemaBuilder()
.add("sunrise", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("sunset", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("solar_noon", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("day_length", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("civil_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("nautical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_begin", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.add("astronomical_twilight_end", TypeProtos.MinorType.VARCHAR, TypeProtos.DataMode.OPTIONAL)
.build();
RowSet expected = new RowSetBuilder(client.allocator(), expectedSchema)
.addRow("6:13:58 AM", "5:59:55 PM", "12:06:56 PM", "11:45:57", "5:48:14 AM",
"6:25:38 PM", "5:18:16 AM", "6:55:36 PM", "4:48:07 AM", "7:25:45 PM")
.build();
RowSetUtilities.verify(expected, results);
RecordedRequest request = server.takeRequest();
assertEquals("value1", request.getHeader("header1"));
assertEquals("value2", request.getHeader("header2"));
assertEquals("Basic dXNlcjpwYXNz", request.getHeader("Authorization"));
}
}
/**
* Helper function to start the MockHTTPServer
* @return Started Mock server
* @throws IOException If the server cannot start, throws IOException
*/
private MockWebServer startServer() throws IOException {
MockWebServer server = new MockWebServer();
server.start(MOCK_SERVER_PORT);
return server;
}
}