summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Shorin <kxepal@apache.org>2014-05-28 05:32:11 +0400
committerAlexander Shorin <kxepal@apache.org>2015-12-02 03:49:05 +0300
commitdd01551b9d2b042f7beac525fc32a499e4194bcf (patch)
tree4ab96ac900a2b7532657b34d7c85249e0a471cc4
parent3a96ebc7192d08adeb61ab381202005d17e0657f (diff)
downloadcouchdb-dd01551b9d2b042f7beac525fc32a499e4194bcf.tar.gz
Port 130-attachments-md5.t etap test suite to eunit
Add random document id generator macros. Have to use handmade http client instead of ibrowse since it makes too complicated sending chunked requests.
-rw-r--r--test/couchdb/Makefile.am1
-rw-r--r--test/couchdb/couchdb_attachments_tests.erl264
-rw-r--r--test/couchdb/include/couch_eunit.hrl.in5
-rwxr-xr-xtest/etap/130-attachments-md5.t248
-rw-r--r--test/etap/Makefile.am1
5 files changed, 270 insertions, 249 deletions
diff --git a/test/couchdb/Makefile.am b/test/couchdb/Makefile.am
index 1fe4da367..540583b5f 100644
--- a/test/couchdb/Makefile.am
+++ b/test/couchdb/Makefile.am
@@ -36,6 +36,7 @@ eunit_files = \
couch_util_tests.erl \
couch_uuids_tests.erl \
couch_work_queue_tests.erl \
+ couchdb_attachments_tests.erl \
couchdb_file_compression_tests.erl \
couchdb_modules_load_tests.erl \
couchdb_update_conflicts_tests.erl \
diff --git a/test/couchdb/couchdb_attachments_tests.erl b/test/couchdb/couchdb_attachments_tests.erl
new file mode 100644
index 000000000..aef187324
--- /dev/null
+++ b/test/couchdb/couchdb_attachments_tests.erl
@@ -0,0 +1,264 @@
+% Licensed 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.
+
+-module(couchdb_attachments_tests).
+
+-include("couch_eunit.hrl").
+-include_lib("couchdb/couch_db.hrl").
+
+-define(TIMEOUT, 1000).
+-define(TIMEWAIT, 100).
+-define(i2l(I), integer_to_list(I)).
+
+
+start() ->
+ {ok, Pid} = couch_server_sup:start_link(?CONFIG_CHAIN),
+ Pid.
+
+stop(Pid) ->
+ erlang:monitor(process, Pid),
+ couch_server_sup:stop(),
+ receive
+ {'DOWN', _, _, Pid, _} ->
+ ok
+ after ?TIMEOUT ->
+ throw({timeout, server_stop})
+ end.
+
+setup() ->
+ DbName = ?tempdb(),
+ {ok, Db} = couch_db:create(DbName, []),
+ ok = couch_db:close(Db),
+ Addr = couch_config:get("httpd", "bind_address", any),
+ Port = mochiweb_socket_server:get(couch_httpd, port),
+ Host = Addr ++ ":" ++ ?i2l(Port),
+ {Host, ?b2l(DbName)}.
+
+teardown({_, DbName}) ->
+ ok = couch_server:delete(?l2b(DbName), []),
+ ok.
+
+
+attachments_test_() ->
+ {
+ "Attachments tests",
+ {
+ setup,
+ fun start/0, fun stop/1,
+ [
+ attachments_md5_tests()
+ ]
+ }
+ }.
+
+attachments_md5_tests() ->
+ {
+ "Attachments MD5 tests",
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun should_upload_attachment_without_md5/1,
+ fun should_upload_attachment_by_chunks_without_md5/1,
+ fun should_upload_attachment_with_valid_md5_header/1,
+ fun should_upload_attachment_by_chunks_with_valid_md5_header/1,
+ fun should_upload_attachment_by_chunks_with_valid_md5_trailer/1,
+ fun should_reject_attachment_with_invalid_md5/1,
+ fun should_reject_chunked_attachment_with_invalid_md5/1,
+ fun should_reject_chunked_attachment_with_invalid_md5_trailer/1
+ ]
+ }
+ }.
+
+
+should_upload_attachment_without_md5({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ Body = "We all live in a yellow submarine!",
+ Headers = [
+ {"Content-Length", "34"},
+ {"Content-Type", "text/plain"},
+ {"Host", Host}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(201, Code),
+ ?assertEqual(true, get_json(Json, [<<"ok">>]))
+ end).
+
+should_upload_attachment_by_chunks_without_md5({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ AttData = <<"We all live in a yellow submarine!">>,
+ <<Part1:21/binary, Part2:13/binary>> = AttData,
+ Body = chunked_body([Part1, Part2]),
+ Headers = [
+ {"Content-Type", "text/plain"},
+ {"Transfer-Encoding", "chunked"},
+ {"Host", Host}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(201, Code),
+ ?assertEqual(true, get_json(Json, [<<"ok">>]))
+ end).
+
+should_upload_attachment_with_valid_md5_header({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ Body = "We all live in a yellow submarine!",
+ Headers = [
+ {"Content-Length", "34"},
+ {"Content-Type", "text/plain"},
+ {"Content-MD5", ?b2l(base64:encode(couch_util:md5(Body)))},
+ {"Host", Host}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(201, Code),
+ ?assertEqual(true, get_json(Json, [<<"ok">>]))
+ end).
+
+should_upload_attachment_by_chunks_with_valid_md5_header({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ AttData = <<"We all live in a yellow submarine!">>,
+ <<Part1:21/binary, Part2:13/binary>> = AttData,
+ Body = chunked_body([Part1, Part2]),
+ Headers = [
+ {"Content-Type", "text/plain"},
+ {"Content-MD5", ?b2l(base64:encode(couch_util:md5(AttData)))},
+ {"Host", Host},
+ {"Transfer-Encoding", "chunked"}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(201, Code),
+ ?assertEqual(true, get_json(Json, [<<"ok">>]))
+ end).
+
+should_upload_attachment_by_chunks_with_valid_md5_trailer({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ AttData = <<"We all live in a yellow submarine!">>,
+ <<Part1:21/binary, Part2:13/binary>> = AttData,
+ Body = [chunked_body([Part1, Part2]),
+ "Content-MD5: ", base64:encode(couch_util:md5(AttData)),
+ "\r\n"],
+ Headers = [
+ {"Content-Type", "text/plain"},
+ {"Host", Host},
+ {"Trailer", "Content-MD5"},
+ {"Transfer-Encoding", "chunked"}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(201, Code),
+ ?assertEqual(true, get_json(Json, [<<"ok">>]))
+ end).
+
+should_reject_attachment_with_invalid_md5({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ Body = "We all live in a yellow submarine!",
+ Headers = [
+ {"Content-Length", "34"},
+ {"Content-Type", "text/plain"},
+ {"Content-MD5", ?b2l(base64:encode(<<"foobar!">>))},
+ {"Host", Host}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(400, Code),
+ ?assertEqual(<<"content_md5_mismatch">>,
+ get_json(Json, [<<"error">>]))
+ end).
+
+
+should_reject_chunked_attachment_with_invalid_md5({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ AttData = <<"We all live in a yellow submarine!">>,
+ <<Part1:21/binary, Part2:13/binary>> = AttData,
+ Body = chunked_body([Part1, Part2]),
+ Headers = [
+ {"Content-Type", "text/plain"},
+ {"Content-MD5", ?b2l(base64:encode(<<"foobar!">>))},
+ {"Host", Host},
+ {"Transfer-Encoding", "chunked"}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(400, Code),
+ ?assertEqual(<<"content_md5_mismatch">>,
+ get_json(Json, [<<"error">>]))
+ end).
+
+should_reject_chunked_attachment_with_invalid_md5_trailer({Host, DbName}) ->
+ ?_test(begin
+ AttUrl = string:join(["", DbName, ?docid(), "readme.txt"], "/"),
+ AttData = <<"We all live in a yellow submarine!">>,
+ <<Part1:21/binary, Part2:13/binary>> = AttData,
+ Body = [chunked_body([Part1, Part2]),
+ "Content-MD5: ", base64:encode(<<"foobar!">>),
+ "\r\n"],
+ Headers = [
+ {"Content-Type", "text/plain"},
+ {"Host", Host},
+ {"Trailer", "Content-MD5"},
+ {"Transfer-Encoding", "chunked"}
+ ],
+ {ok, Code, Json} = request("PUT", AttUrl, Headers, Body),
+ ?assertEqual(400, Code),
+ ?assertEqual(<<"content_md5_mismatch">>,
+ get_json(Json, [<<"error">>]))
+ end).
+
+
+get_json(Json, Path) ->
+ couch_util:get_nested_json_value(Json, Path).
+
+to_hex(Val) ->
+ to_hex(Val, []).
+
+to_hex(0, Acc) ->
+ Acc;
+to_hex(Val, Acc) ->
+ to_hex(Val div 16, [hex_char(Val rem 16) | Acc]).
+
+hex_char(V) when V < 10 -> $0 + V;
+hex_char(V) -> $A + V - 10.
+
+chunked_body(Chunks) ->
+ chunked_body(Chunks, []).
+
+chunked_body([], Acc) ->
+ iolist_to_binary(lists:reverse(Acc, "0\r\n"));
+chunked_body([Chunk | Rest], Acc) ->
+ Size = to_hex(size(Chunk)),
+ chunked_body(Rest, ["\r\n", Chunk, "\r\n", Size | Acc]).
+
+get_socket() ->
+ Options = [binary, {packet, 0}, {active, false}],
+ Addr = couch_config:get("httpd", "bind_address", any),
+ Port = mochiweb_socket_server:get(couch_httpd, port),
+ {ok, Sock} = gen_tcp:connect(Addr, Port, Options),
+ Sock.
+
+request(Method, Url, Headers, Body) ->
+ RequestHead = [Method, " ", Url, " HTTP/1.1"],
+ RequestHeaders = [[string:join([Key, Value], ": "), "\r\n"]
+ || {Key, Value} <- Headers],
+ Request = [RequestHead, "\r\n", RequestHeaders, "\r\n", Body, "\r\n"],
+ Sock = get_socket(),
+ gen_tcp:send(Sock, list_to_binary(lists:flatten(Request))),
+ timer:sleep(?TIMEWAIT), % must wait to receive complete response
+ {ok, R} = gen_tcp:recv(Sock, 0),
+ gen_tcp:close(Sock),
+ [Header, Body1] = re:split(R, "\r\n\r\n", [{return, binary}]),
+ {ok, {http_response, _, Code, _}, _} =
+ erlang:decode_packet(http, Header, []),
+ Json = ejson:decode(Body1),
+ {ok, Code, Json}.
diff --git a/test/couchdb/include/couch_eunit.hrl.in b/test/couchdb/include/couch_eunit.hrl.in
index 6a46d2f83..ff080e1f4 100644
--- a/test/couchdb/include/couch_eunit.hrl.in
+++ b/test/couchdb/include/couch_eunit.hrl.in
@@ -37,3 +37,8 @@
Suffix = lists:concat([integer_to_list(Num) || Num <- Nums]),
list_to_binary(Prefix ++ "-" ++ Suffix)
end).
+-define(docid,
+ fun() ->
+ {A, B, C} = erlang:now(),
+ lists:flatten(io_lib:format("~p~p~p", [A, B, C]))
+ end).
diff --git a/test/etap/130-attachments-md5.t b/test/etap/130-attachments-md5.t
deleted file mode 100755
index a91c9bf18..000000000
--- a/test/etap/130-attachments-md5.t
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/usr/bin/env escript
-% Licensed 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.
-
-test_db_name() ->
- <<"etap-test-db">>.
-
-docid() ->
- case get(docid) of
- undefined ->
- put(docid, 1),
- "1";
- Count ->
- put(docid, Count+1),
- integer_to_list(Count+1)
- end.
-
-main(_) ->
- test_util:init_code_path(),
-
- etap:plan(16),
- case (catch test()) of
- ok ->
- etap:end_tests();
- Other ->
- etap:diag(io_lib:format("Test died abnormally: ~p", [Other])),
- etap:bail(Other)
- end,
- ok.
-
-test() ->
- couch_server_sup:start_link(test_util:config_files()),
- Addr = couch_config:get("httpd", "bind_address", any),
- put(addr, Addr),
- put(port, mochiweb_socket_server:get(couch_httpd, port)),
- timer:sleep(1000),
-
- couch_server:delete(test_db_name(), []),
- couch_db:create(test_db_name(), []),
-
- test_identity_without_md5(),
- test_chunked_without_md5(),
-
- test_identity_with_valid_md5(),
- test_chunked_with_valid_md5_header(),
- test_chunked_with_valid_md5_trailer(),
-
- test_identity_with_invalid_md5(),
- test_chunked_with_invalid_md5_header(),
- test_chunked_with_invalid_md5_trailer(),
-
- couch_server:delete(test_db_name(), []),
- couch_server_sup:stop(),
- ok.
-
-test_identity_without_md5() ->
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Content-Length: 34\r\n",
- "\r\n",
- "We all live in a yellow submarine!"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 201, "Stored with identity encoding and no MD5"),
- etap:is(get_json(Json, [<<"ok">>]), true, "Body indicates success.").
-
-test_chunked_without_md5() ->
- AttData = <<"We all live in a yellow submarine!">>,
- <<Part1:21/binary, Part2:13/binary>> = AttData,
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Transfer-Encoding: chunked\r\n",
- "\r\n",
- to_hex(size(Part1)), "\r\n",
- Part1, "\r\n",
- to_hex(size(Part2)), "\r\n",
- Part2, "\r\n"
- "0\r\n"
- "\r\n"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 201, "Stored with chunked encoding and no MD5"),
- etap:is(get_json(Json, [<<"ok">>]), true, "Body indicates success.").
-
-test_identity_with_valid_md5() ->
- AttData = "We all live in a yellow submarine!",
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Content-Length: 34\r\n",
- "Content-MD5: ", base64:encode(couch_util:md5(AttData)), "\r\n",
- "\r\n",
- AttData],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 201, "Stored with identity encoding and valid MD5"),
- etap:is(get_json(Json, [<<"ok">>]), true, "Body indicates success.").
-
-test_chunked_with_valid_md5_header() ->
- AttData = <<"We all live in a yellow submarine!">>,
- <<Part1:21/binary, Part2:13/binary>> = AttData,
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Transfer-Encoding: chunked\r\n",
- "Content-MD5: ", base64:encode(couch_util:md5(AttData)), "\r\n",
- "\r\n",
- to_hex(size(Part1)), "\r\n",
- Part1, "\r\n",
- to_hex(size(Part2)), "\r\n",
- Part2, "\r\n",
- "0\r\n",
- "\r\n"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 201, "Stored with chunked encoding and valid MD5 header."),
- etap:is(get_json(Json, [<<"ok">>]), true, "Body indicates success.").
-
-test_chunked_with_valid_md5_trailer() ->
- AttData = <<"We all live in a yellow submarine!">>,
- <<Part1:21/binary, Part2:13/binary>> = AttData,
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Transfer-Encoding: chunked\r\n",
- "Trailer: Content-MD5\r\n",
- "\r\n",
- to_hex(size(Part1)), "\r\n",
- Part1, "\r\n",
- to_hex(size(Part2)), "\r\n",
- Part2, "\r\n",
- "0\r\n",
- "Content-MD5: ", base64:encode(couch_util:md5(AttData)), "\r\n",
- "\r\n"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 201, "Stored with chunked encoding and valid MD5 trailer."),
- etap:is(get_json(Json, [<<"ok">>]), true, "Body indicates success.").
-
-test_identity_with_invalid_md5() ->
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Content-Length: 34\r\n",
- "Content-MD5: ", base64:encode(<<"foobar!">>), "\r\n",
- "\r\n",
- "We all live in a yellow submarine!"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 400, "Invalid MD5 header causes an error: identity"),
- etap:is(
- get_json(Json, [<<"error">>]),
- <<"content_md5_mismatch">>,
- "Body indicates reason for failure."
- ).
-
-test_chunked_with_invalid_md5_header() ->
- AttData = <<"We all live in a yellow submarine!">>,
- <<Part1:21/binary, Part2:13/binary>> = AttData,
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Transfer-Encoding: chunked\r\n",
- "Content-MD5: ", base64:encode(<<"so sneaky...">>), "\r\n",
- "\r\n",
- to_hex(size(Part1)), "\r\n",
- Part1, "\r\n",
- to_hex(size(Part2)), "\r\n",
- Part2, "\r\n",
- "0\r\n",
- "\r\n"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 400, "Invalid MD5 header causes an error: chunked"),
- etap:is(
- get_json(Json, [<<"error">>]),
- <<"content_md5_mismatch">>,
- "Body indicates reason for failure."
- ).
-
-test_chunked_with_invalid_md5_trailer() ->
- AttData = <<"We all live in a yellow submarine!">>,
- <<Part1:21/binary, Part2:13/binary>> = AttData,
- Data = [
- "PUT /", test_db_name(), "/", docid(), "/readme.txt HTTP/1.1\r\n",
- "Content-Type: text/plain\r\n",
- "Transfer-Encoding: chunked\r\n",
- "Trailer: Content-MD5\r\n",
- "\r\n",
- to_hex(size(Part1)), "\r\n",
- Part1, "\r\n",
- to_hex(size(Part2)), "\r\n",
- Part2, "\r\n",
- "0\r\n",
- "Content-MD5: ", base64:encode(<<"Kool-Aid Fountain!">>), "\r\n",
- "\r\n"],
-
- {Code, Json} = do_request(Data),
- etap:is(Code, 400, "Invalid MD5 Trailer causes an error"),
- etap:is(
- get_json(Json, [<<"error">>]),
- <<"content_md5_mismatch">>,
- "Body indicates reason for failure."
- ).
-
-
-get_socket() ->
- Options = [binary, {packet, 0}, {active, false}],
- {ok, Sock} = gen_tcp:connect(get(addr), get(port), Options),
- Sock.
-
-do_request(Request) ->
- Sock = get_socket(),
- gen_tcp:send(Sock, list_to_binary(lists:flatten(Request))),
- timer:sleep(1000),
- {ok, R} = gen_tcp:recv(Sock, 0),
- gen_tcp:close(Sock),
- [Header, Body] = re:split(R, "\r\n\r\n", [{return, binary}]),
- {ok, {http_response, _, Code, _}, _} =
- erlang:decode_packet(http, Header, []),
- Json = ejson:decode(Body),
- {Code, Json}.
-
-get_json(Json, Path) ->
- couch_util:get_nested_json_value(Json, Path).
-
-to_hex(Val) ->
- to_hex(Val, []).
-
-to_hex(0, Acc) ->
- Acc;
-to_hex(Val, Acc) ->
- to_hex(Val div 16, [hex_char(Val rem 16) | Acc]).
-
-hex_char(V) when V < 10 -> $0 + V;
-hex_char(V) -> $A + V - 10.
-
diff --git a/test/etap/Makefile.am b/test/etap/Makefile.am
index abe252d70..f54e927e0 100644
--- a/test/etap/Makefile.am
+++ b/test/etap/Makefile.am
@@ -36,7 +36,6 @@ fixture_files = \
fixtures/test.couch
tap_files = \
- 130-attachments-md5.t \
140-attachment-comp.t \
150-invalid-view-seq.t \
160-vhosts.t \