-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Originally by @lehoff.
- Loading branch information
Showing
1 changed file
with
368 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,368 @@ | ||
%% ------------------------------------------------------------------- | ||
%% | ||
%% Copyright (c) 2016 Basho Technologies, Inc. | ||
%% | ||
%% This file is provided 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. | ||
%% | ||
%% Tests for range queries around the boundaries of quanta. | ||
%% | ||
%% ------------------------------------------------------------------- | ||
-module(ts_simple_http_api_SUITE). | ||
|
||
-compile(export_all). | ||
|
||
-include_lib("common_test/include/ct.hrl"). | ||
-include_lib("eunit/include/eunit.hrl"). | ||
|
||
%%-------------------------------------------------------------------- | ||
%% COMMON TEST CALLBACK FUNCTIONS | ||
%%-------------------------------------------------------------------- | ||
|
||
suite() -> | ||
[{timetrap,{minutes,10}}]. | ||
|
||
init_per_suite(Config) -> | ||
[_Node|_] = Cluster = ts_util:build_cluster(single), | ||
rt:wait_until_nodes_ready(Cluster), | ||
[{cluster, Cluster} | Config]. | ||
|
||
end_per_suite(_Config) -> | ||
ok. | ||
|
||
init_per_group(_GroupName, Config) -> | ||
Config. | ||
|
||
end_per_group(_GroupName, _Config) -> | ||
ok. | ||
|
||
init_per_testcase(_TestCase, Config) -> | ||
Config. | ||
|
||
end_per_testcase(_TestCase, _Config) -> | ||
ok. | ||
|
||
groups() -> | ||
[]. | ||
|
||
all() -> | ||
[ create_table_test, | ||
create_bad_table_test, | ||
create_existing_table_test, | ||
describe_table_test, | ||
describe_nonexisting_table_test, | ||
bad_describe_query_test, | ||
post_single_row_test, | ||
post_single_row_missing_field_test, | ||
post_single_row_wrong_field_test, | ||
post_several_rows_test, | ||
post_row_to_nonexisting_table_test, | ||
list_keys_test, | ||
list_keys_nonexisting_table_test, | ||
select_test, | ||
select_subset_test, | ||
invalid_select_test, | ||
invalid_query_test, | ||
delete_data_existing_row_test, | ||
delete_data_nonexisting_row_test, | ||
delete_data_nonexisting_table_test, | ||
delete_data_wrong_path_test | ||
]. | ||
|
||
|
||
%% column_names_def_1() -> | ||
%% [<<"a">>, <<"b">>, <<"c">>]. | ||
|
||
|
||
table_def_bob() -> | ||
"create table bob (" | ||
" a varchar not null," | ||
" b varchar not null," | ||
" c timestamp not null," | ||
" d sint64," | ||
" primary key ((a, b, quantum(c, 1, m)), a, b, c))". | ||
|
||
bad_table_def() -> | ||
"create table pap (" | ||
" a timestamp not null," | ||
" b timestamp not null," | ||
" c timestamp not null)". | ||
|
||
%% client_pid(Ctx) -> | ||
%% [Node|_] = proplists:get_value(cluster, Ctx), | ||
%% rt:pbc(Node). | ||
|
||
%%% | ||
%%% HTTP API tests | ||
%%% | ||
|
||
%%% query | ||
create_table_test(Cfg) -> | ||
Query = table_def_bob(), | ||
{ok, "200", _Headers, Body } = execute_query(Query, Cfg), | ||
Body = success_body(). | ||
|
||
create_bad_table_test(Cfg) -> | ||
Query = bad_table_def(), | ||
{ok, "400", Headers, Body} = execute_query(Query, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Query error: Missing primary key" = Body. | ||
|
||
|
||
create_existing_table_test(Cfg) -> | ||
Query = table_def_bob(), | ||
{ok, "409", Headers, Body} = | ||
execute_query(Query, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Table \"bob\" already exists" = Body. | ||
|
||
|
||
describe_table_test(Cfg) -> | ||
Query = "describe bob", | ||
{ok, "200", Headers, Body } = execute_query(Query, Cfg), | ||
"application/json" = content_type(Headers), | ||
"{\"columns\":"++_ = Body. | ||
|
||
describe_nonexisting_table_test(Cfg) -> | ||
Query = "describe john", | ||
{ok, "404", Headers, Body} = execute_query(Query, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Table \"john\" does not exist" = Body. | ||
|
||
bad_describe_query_test(Cfg) -> | ||
Query = "descripe bob", | ||
{ok, "400", Headers, Body} = execute_query(Query, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Query error: Unexpected token: 'descripe'" = Body. | ||
|
||
%%% put | ||
post_single_row_test(Cfg) -> | ||
RowStr = row("q1", "w1", 11, 110), | ||
{ok, "200", Headers, RespBody} = post_data("bob", RowStr, Cfg), | ||
"application/json" = content_type(Headers), | ||
RespBody = success_body(). | ||
|
||
post_single_row_missing_field_test(Cfg) -> | ||
RowStr = missing_field_row("q1", 12, 200), | ||
{ok, "400", Headers, Body} = | ||
post_data("bob", RowStr, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Missing field \"b\" for key in table \"bob\"" = Body. | ||
|
||
post_single_row_wrong_field_test(Cfg) -> | ||
RowStr = wrong_field_type_row("q1", "w1", 12, "raining"), | ||
{ok,"400", Headers, Body} = post_data("bob", RowStr, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Bad value for field \"d\" of type sint64 in table \"bob\"" = Body. | ||
|
||
|
||
post_several_rows_test(Cfg) -> | ||
RowStrs = string:join([row("q1", "w2", 20, 150), row("q1", "w1", 20, 119)], | ||
", "), | ||
Body = io_lib:format("[~s]", [RowStrs]), | ||
{ok, "200", Headers, RespBody} = post_data("bob", Body, Cfg), | ||
"application/json" = content_type(Headers), | ||
RespBody = success_body(). | ||
|
||
post_row_to_nonexisting_table_test(Cfg) -> | ||
RowStr = row("q1", "w1", 30, 142), | ||
{ok,"404", Headers, Body} = post_data("bill", RowStr, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Table \"bill\" does not exist" = Body. | ||
|
||
%%% list_keys | ||
list_keys_test(Cfg) -> | ||
{"200", Headers, Body} = list_keys("bob", Cfg), | ||
"text/plain" = content_type(Headers), | ||
RecordURLs = string:tokens(Body, "\n"), | ||
?assertEqual(length(RecordURLs), 3), | ||
%% do a get on each key | ||
lists:foreach( | ||
fun(URL) -> | ||
{ok, "200", _Headers, _Body} = ibrowse:send_req(URL, [], get) | ||
end, | ||
RecordURLs). | ||
|
||
|
||
list_keys_nonexisting_table_test(Cfg) -> | ||
{"404", Headers, Body} = list_keys("john", Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Table \"john\" does not exist" = Body. | ||
|
||
%%% select | ||
select_test(Cfg) -> | ||
Select = "select * from bob where a='q1' and b='w1' and c>1 and c<99", | ||
{ok,"200", Headers, Body} = execute_query(Select, Cfg), | ||
"application/json" = content_type(Headers), | ||
"{\"columns\":[\"a\",\"b\",\"c\",\"d\"]," | ||
"\"rows\":[[\"q1\",\"w1\",11,110]," | ||
"[\"q1\",\"w1\",20,119]]}" = Body. | ||
|
||
select_subset_test(Cfg) -> | ||
Select = "select * from bob where a='q1' and b='w1' and c>1 and c<15", | ||
{ok, "200", Headers, Body} = execute_query(Select, Cfg), | ||
"application/json" = content_type(Headers), | ||
"{\"columns\":[\"a\",\"b\",\"c\",\"d\"]," | ||
"\"rows\":[[\"q1\",\"w1\",11,110]]}" = Body. | ||
|
||
invalid_select_test(Cfg) -> | ||
Select = "select * from bob where a='q1' and c>1 and c<15", | ||
%% @todo: this really ought to be a 4XX error, but digging into the errors | ||
%% from riak_ql might be too much for this API. | ||
{ok, "500", Headers, Body} = execute_query(Select, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Execution of select query failed on table \"bob\" (The 'b' parameter is part the primary key but not specified in the where clause.)" | ||
= Body. | ||
|
||
invalid_query_test(Cfg) -> | ||
Select = "OHNOES A DANGLING QUOTE ' ", | ||
{ok, "400", Headers, Body} = execute_query(Select, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Query error: Unexpected token '''." = Body. | ||
|
||
%%% delete | ||
delete_data_existing_row_test(Cfg) -> | ||
{ok, "200", Headers, Body} = delete("bob", "q1", "w1", 11, Cfg), | ||
"application/json" = content_type(Headers), | ||
Body = success_body(), | ||
Select = "select * from bob where a='q1' and b='w1' and c>1 and c<99", | ||
{ok, "200", _Headers2, | ||
"{\"columns\":[\"a\",\"b\",\"c\",\"d\"],\"rows\":[[\"q1\",\"w1\",20,119]]}"} = | ||
execute_query(Select, Cfg). | ||
|
||
delete_data_nonexisting_row_test(Cfg) -> | ||
{ok, "404", Headers, Body } = delete("bob", "q1", "w1", 500, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Key not found" | ||
= Body. | ||
|
||
delete_data_nonexisting_table_test(Cfg) -> | ||
{ok, "404", Headers, Body } = delete("bill", "q1", "w1", 20, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Table \"bill\" does not exist" = Body. | ||
|
||
delete_data_wrong_path_test(Cfg) -> | ||
{ok, "400", Headers, Body} = delete_wrong_path("bob", "q1", "w1", 20, Cfg), | ||
"text/plain" = content_type(Headers), | ||
"Not all key-constituent fields given on URL" = Body. | ||
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
%%% Helper functions | ||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | ||
execute_query(Query, Cfg) -> | ||
Node = get_node(Cfg), | ||
URL = query_url(Node), | ||
ibrowse:send_req(URL, [], post, Query). | ||
|
||
post_data(Table, Body, Cfg) -> | ||
Node = get_node(Cfg), | ||
URL = post_data_url(Node, Table), | ||
ibrowse:send_req(URL, [{"Content-Type", "application/json"}], post, lists:flatten(Body)). | ||
|
||
|
||
get_node(Cfg) -> | ||
[Node|_] = ?config(cluster, Cfg), | ||
Node. | ||
|
||
node_ip_and_port(Node) -> | ||
{ok, [{IP, Port}]} = rpc:call(Node, application, get_env, [riak_api, http]), | ||
{IP, Port}. | ||
|
||
query_url(Node) -> | ||
{IP, Port} = node_ip_and_port(Node), | ||
query_url(IP, Port). | ||
|
||
query_url(IP, Port) -> | ||
lists:flatten( | ||
io_lib:format("http://~s:~B/ts/v1/query", | ||
[IP, Port])). | ||
|
||
post_data_url(Node, Table) -> | ||
{IP, Port} = node_ip_and_port(Node), | ||
lists:flatten( | ||
io_lib:format("http://~s:~B/ts/v1/tables/~s/keys", | ||
[IP, Port, Table])). | ||
|
||
list_keys(Table, Cfg) -> | ||
Node = get_node(Cfg), | ||
URL = list_keys_url(Node, Table), | ||
{ibrowse_req_id, ReqID} = ibrowse:send_req(URL, [], get, [], [{stream_to, self()}]), | ||
collect_stream(ReqID). | ||
|
||
collect_stream(ReqID) -> | ||
{Code, Headers} = collect_headers(ReqID), | ||
Body = collect_body(ReqID), | ||
{Code, Headers, Body}. | ||
|
||
collect_headers(ReqID) -> | ||
receive | ||
{ibrowse_async_headers, ReqID, Code, Headers} -> | ||
{Code, Headers} | ||
end. | ||
|
||
collect_body(ReqID) -> | ||
receive | ||
{ibrowse_async_response, ReqID, BodyPart} -> | ||
BodyPart ++ collect_body(ReqID); | ||
{ibrowse_async_response_end, ReqID} -> | ||
[] | ||
end. | ||
|
||
list_keys_url(Node, Table) -> | ||
{IP, Port} = node_ip_and_port(Node), | ||
lists:flatten( | ||
io_lib:format("http://~s:~B/ts/v1/tables/~s/list_keys", | ||
[IP, Port, Table])). | ||
|
||
delete(Table, A, B, C, Cfg) -> | ||
Node = get_node(Cfg), | ||
URL = delete_url(Node, Table, A, B, C), | ||
ibrowse:send_req(URL, [], delete). | ||
|
||
delete_url(Node, Table, A, B, C) -> | ||
{IP, Port} = node_ip_and_port(Node), | ||
lists:flatten( | ||
io_lib:format("http://~s:~B/ts/v1/tables/~s/keys/a/~s/b/~s/c/~B", | ||
[IP, Port, Table, A, B, C])). | ||
|
||
delete_wrong_path(Table, A, B, C, Cfg) -> | ||
Node = get_node(Cfg), | ||
URL = delete_url_wrong_path(Node, Table, A, B, C), | ||
ibrowse:send_req(URL, [], delete). | ||
|
||
delete_url_wrong_path(Node, Table, A, B, C) -> | ||
{IP, Port} = node_ip_and_port(Node), | ||
lists:flatten( | ||
io_lib:format("http://~s:~B/ts/v1/tables/~s/keys/a/~s/b/~s/d/~B", | ||
[IP, Port, Table, A, B, C])). | ||
|
||
|
||
row(A, B, C, D) -> | ||
io_lib:format("{\"a\": \"~s\", \"b\": \"~s\", \"c\": ~B, \"d\":~B}", | ||
[A, B, C, D]). | ||
|
||
missing_field_row(A, C, D) -> | ||
io_lib:format("{\"a\": \"~s\", \"c\": ~B, \"d\":~B}", | ||
[A, C, D]). | ||
|
||
wrong_field_type_row(A, B, C, D) -> | ||
io_lib:format("{\"a\": \"~s\", \"b\": \"~s\", \"c\": ~B, \"d\":~p}", | ||
[A, B, C, D]). | ||
|
||
|
||
success_body() -> | ||
"{\"success\":true}". | ||
|
||
content_type(Headers) -> | ||
proplists:get_value("Content-Type", Headers). |