Skip to content

Commit

Permalink
new module ts_simple_http_api_SUITE
Browse files Browse the repository at this point in the history
Originally by @lehoff.
  • Loading branch information
hmmr committed Apr 19, 2016
1 parent bb0994a commit e51626a
Showing 1 changed file with 368 additions and 0 deletions.
368 changes: 368 additions & 0 deletions tests/ts_simple_http_api_SUITE.erl
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).

0 comments on commit e51626a

Please sign in to comment.