#!/usr/bin/env escript %% -*- erlang -*- % 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() -> <<"couch_test_atts_compression">>. main(_) -> test_util:init_code_path(), etap:plan(85), 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()), put(addr, couch_config:get("httpd", "bind_address", "127.0.0.1")), put(port, integer_to_list(mochiweb_socket_server:get(couch_httpd, port))), application:start(inets), timer:sleep(1000), couch_server:delete(test_db_name(), []), couch_db:create(test_db_name(), []), couch_config:set("attachments", "compression_level", "8"), couch_config:set("attachments", "compressible_types", "text/*"), create_1st_text_att(), create_1st_png_att(), create_2nd_text_att(), create_2nd_png_att(), tests_for_1st_text_att(), tests_for_1st_png_att(), tests_for_2nd_text_att(), tests_for_2nd_png_att(), create_already_compressed_att(db_url() ++ "/doc_comp_att", "readme.txt"), test_already_compressed_att(db_url() ++ "/doc_comp_att", "readme.txt"), test_create_already_compressed_att_with_invalid_content_encoding( db_url() ++ "/doc_att_deflate", "readme.txt", zlib:compress(test_text_data()), "deflate" ), test_create_already_compressed_att_with_invalid_content_encoding( db_url() ++ "/doc_att_compress", "readme.txt", % Note: As of OTP R13B04, it seems there's no LZW compression % (i.e. UNIX compress utility implementation) lib in OTP. % However there's a simple working Erlang implementation at: % http://scienceblogs.com/goodmath/2008/01/simple_lempelziv_compression_i.php test_text_data(), "compress" ), test_compressible_type_with_parameters(), timer:sleep(3000), % to avoid mochiweb socket closed exceptions couch_server:delete(test_db_name(), []), couch_server_sup:stop(), ok. db_url() -> "http://" ++ get(addr) ++ ":" ++ get(port) ++ "/" ++ binary_to_list(test_db_name()). create_1st_text_att() -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {db_url() ++ "/testdoc1/readme.txt", [], "text/plain", test_text_data()}, [], [{sync, true}]), etap:is(Code, 201, "Created text attachment using the standalone api"), ok. create_1st_png_att() -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {db_url() ++ "/testdoc2/icon.png", [], "image/png", test_png_data()}, [], [{sync, true}]), etap:is(Code, 201, "Created png attachment using the standalone api"), ok. % create a text attachment using the non-standalone attachment api create_2nd_text_att() -> DocJson = {[ {<<"_attachments">>, {[ {<<"readme.txt">>, {[ {<<"content_type">>, <<"text/plain">>}, {<<"data">>, base64:encode(test_text_data())} ]} }]}} ]}, {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {db_url() ++ "/testdoc3", [], "application/json", list_to_binary(couch_util:json_encode(DocJson))}, [], [{sync, true}]), etap:is(Code, 201, "Created text attachment using the non-standalone api"), ok. % create a png attachment using the non-standalone attachment api create_2nd_png_att() -> DocJson = {[ {<<"_attachments">>, {[ {<<"icon.png">>, {[ {<<"content_type">>, <<"image/png">>}, {<<"data">>, base64:encode(test_png_data())} ]} }]}} ]}, {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {db_url() ++ "/testdoc4", [], "application/json", list_to_binary(couch_util:json_encode(DocJson))}, [], [{sync, true}]), etap:is(Code, 201, "Created png attachment using the non-standalone api"), ok. create_already_compressed_att(DocUri, AttName) -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {DocUri ++ "/" ++ AttName, [{"Content-Encoding", "gzip"}], "text/plain", zlib:gzip(test_text_data())}, [], [{sync, true}]), etap:is( Code, 201, "Created already compressed attachment using the standalone api" ), ok. tests_for_1st_text_att() -> test_get_1st_text_att_with_accept_encoding_gzip(), test_get_1st_text_att_without_accept_encoding_header(), test_get_1st_text_att_with_accept_encoding_deflate(), test_get_1st_text_att_with_accept_encoding_deflate_only(), test_get_doc_with_1st_text_att(), test_1st_text_att_stub(). tests_for_1st_png_att() -> test_get_1st_png_att_without_accept_encoding_header(), test_get_1st_png_att_with_accept_encoding_gzip(), test_get_1st_png_att_with_accept_encoding_deflate(), test_get_doc_with_1st_png_att(), test_1st_png_att_stub(). tests_for_2nd_text_att() -> test_get_2nd_text_att_with_accept_encoding_gzip(), test_get_2nd_text_att_without_accept_encoding_header(), test_get_doc_with_2nd_text_att(), test_2nd_text_att_stub(). tests_for_2nd_png_att() -> test_get_2nd_png_att_without_accept_encoding_header(), test_get_2nd_png_att_with_accept_encoding_gzip(), test_get_doc_with_2nd_png_att(), test_2nd_png_att_stub(). test_get_1st_text_att_with_accept_encoding_gzip() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc1/readme.txt", [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, true, "received body is gziped"), Uncompressed = binary_to_list(zlib:gunzip(list_to_binary(Body))), etap:is( Uncompressed, test_text_data(), "received data for the 1st text attachment is ok" ), ok. test_get_1st_text_att_without_accept_encoding_header() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc1/readme.txt", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), etap:is( Body, test_text_data(), "received data for the 1st text attachment is ok" ), ok. test_get_1st_text_att_with_accept_encoding_deflate() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc1/readme.txt", [{"Accept-Encoding", "deflate"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), Deflated = lists:member({"content-encoding", "deflate"}, Headers), etap:is(Deflated, false, "received body is not deflated"), etap:is( Body, test_text_data(), "received data for the 1st text attachment is ok" ), ok. test_get_1st_text_att_with_accept_encoding_deflate_only() -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( get, {db_url() ++ "/testdoc1/readme.txt", [{"Accept-Encoding", "deflate, *;q=0"}]}, [], [{sync, true}]), etap:is( Code, 406, "HTTP response code is 406 for an unsupported content encoding request" ), ok. test_get_1st_png_att_without_accept_encoding_header() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc2/icon.png", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Encoding = couch_util:get_value("content-encoding", Headers), etap:is(Encoding, undefined, "received body is not gziped"), etap:is( Body, test_png_data(), "received data for the 1st png attachment is ok" ), ok. test_get_1st_png_att_with_accept_encoding_gzip() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc2/icon.png", [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Encoding = couch_util:get_value("content-encoding", Headers), etap:is(Encoding, undefined, "received body is not gziped"), etap:is( Body, test_png_data(), "received data for the 1st png attachment is ok" ), ok. test_get_1st_png_att_with_accept_encoding_deflate() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc2/icon.png", [{"Accept-Encoding", "deflate"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Encoding = couch_util:get_value("content-encoding", Headers), etap:is(Encoding, undefined, "received body is in identity form"), etap:is( Body, test_png_data(), "received data for the 1st png attachment is ok" ), ok. test_get_doc_with_1st_text_att() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc1?attachments=true", [{"Accept", "application/json"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), TextAttJson = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"readme.txt">>] ), TextAttType = couch_util:get_nested_json_value( TextAttJson, [<<"content_type">>] ), TextAttData = couch_util:get_nested_json_value( TextAttJson, [<<"data">>] ), etap:is( TextAttType, <<"text/plain">>, "1st text attachment has type text/plain" ), %% check the attachment's data is the base64 encoding of the plain text %% and not the base64 encoding of the gziped plain text etap:is( TextAttData, base64:encode(test_text_data()), "1st text attachment data is properly base64 encoded" ), ok. test_1st_text_att_stub() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc1?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), {TextAttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"readme.txt">>] ), TextAttLength = couch_util:get_value(<<"length">>, TextAttJson), etap:is( TextAttLength, length(test_text_data()), "1st text attachment stub length matches the uncompressed length" ), TextAttEncoding = couch_util:get_value(<<"encoding">>, TextAttJson), etap:is( TextAttEncoding, <<"gzip">>, "1st text attachment stub has the encoding field set to gzip" ), TextAttEncLength = couch_util:get_value(<<"encoded_length">>, TextAttJson), etap:is( TextAttEncLength, iolist_size(zlib:gzip(test_text_data())), "1st text attachment stub encoded_length matches the compressed length" ), ok. test_get_doc_with_1st_png_att() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc2?attachments=true", [{"Accept", "application/json"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), PngAttJson = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"icon.png">>] ), PngAttType = couch_util:get_nested_json_value( PngAttJson, [<<"content_type">>] ), PngAttData = couch_util:get_nested_json_value( PngAttJson, [<<"data">>] ), etap:is(PngAttType, <<"image/png">>, "attachment has type image/png"), etap:is( PngAttData, base64:encode(test_png_data()), "1st png attachment data is properly base64 encoded" ), ok. test_1st_png_att_stub() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc2?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), {PngAttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"icon.png">>] ), PngAttLength = couch_util:get_value(<<"length">>, PngAttJson), etap:is( PngAttLength, length(test_png_data()), "1st png attachment stub length matches the uncompressed length" ), PngEncoding = couch_util:get_value(<<"encoding">>, PngAttJson), etap:is( PngEncoding, undefined, "1st png attachment stub doesn't have an encoding field" ), PngEncLength = couch_util:get_value(<<"encoded_length">>, PngAttJson), etap:is( PngEncLength, undefined, "1st png attachment stub doesn't have an encoded_length field" ), ok. test_get_2nd_text_att_with_accept_encoding_gzip() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc3/readme.txt", [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, true, "received body is gziped"), Uncompressed = binary_to_list(zlib:gunzip(list_to_binary(Body))), etap:is( Uncompressed, test_text_data(), "received data for the 2nd text attachment is ok" ), ok. test_get_2nd_text_att_without_accept_encoding_header() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc3/readme.txt", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), etap:is( Body, test_text_data(), "received data for the 2nd text attachment is ok" ), ok. test_get_2nd_png_att_without_accept_encoding_header() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc4/icon.png", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), etap:is( Body, test_png_data(), "received data for the 2nd png attachment is ok" ), ok. test_get_2nd_png_att_with_accept_encoding_gzip() -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {db_url() ++ "/testdoc4/icon.png", [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), etap:is( Body, test_png_data(), "received data for the 2nd png attachment is ok" ), ok. test_get_doc_with_2nd_text_att() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc3?attachments=true", [{"Accept", "application/json"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), TextAttJson = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"readme.txt">>] ), TextAttType = couch_util:get_nested_json_value( TextAttJson, [<<"content_type">>] ), TextAttData = couch_util:get_nested_json_value( TextAttJson, [<<"data">>] ), etap:is(TextAttType, <<"text/plain">>, "attachment has type text/plain"), %% check the attachment's data is the base64 encoding of the plain text %% and not the base64 encoding of the gziped plain text etap:is( TextAttData, base64:encode(test_text_data()), "2nd text attachment data is properly base64 encoded" ), ok. test_2nd_text_att_stub() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc3?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), {TextAttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"readme.txt">>] ), TextAttLength = couch_util:get_value(<<"length">>, TextAttJson), etap:is( TextAttLength, length(test_text_data()), "2nd text attachment stub length matches the uncompressed length" ), TextAttEncoding = couch_util:get_value(<<"encoding">>, TextAttJson), etap:is( TextAttEncoding, <<"gzip">>, "2nd text attachment stub has the encoding field set to gzip" ), TextAttEncLength = couch_util:get_value(<<"encoded_length">>, TextAttJson), etap:is( TextAttEncLength, iolist_size(zlib:gzip(test_text_data())), "2nd text attachment stub encoded_length matches the compressed length" ), ok. test_get_doc_with_2nd_png_att() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc4?attachments=true", [{"Accept", "application/json"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), PngAttJson = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"icon.png">>] ), PngAttType = couch_util:get_nested_json_value( PngAttJson, [<<"content_type">>] ), PngAttData = couch_util:get_nested_json_value( PngAttJson, [<<"data">>] ), etap:is(PngAttType, <<"image/png">>, "attachment has type image/png"), etap:is( PngAttData, base64:encode(test_png_data()), "2nd png attachment data is properly base64 encoded" ), ok. test_2nd_png_att_stub() -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {db_url() ++ "/testdoc4?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), {PngAttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"icon.png">>] ), PngAttLength = couch_util:get_value(<<"length">>, PngAttJson), etap:is( PngAttLength, length(test_png_data()), "2nd png attachment stub length matches the uncompressed length" ), PngEncoding = couch_util:get_value(<<"encoding">>, PngAttJson), etap:is( PngEncoding, undefined, "2nd png attachment stub doesn't have an encoding field" ), PngEncLength = couch_util:get_value(<<"encoded_length">>, PngAttJson), etap:is( PngEncLength, undefined, "2nd png attachment stub doesn't have an encoded_length field" ), ok. test_already_compressed_att(DocUri, AttName) -> test_get_already_compressed_att_with_accept_gzip(DocUri, AttName), test_get_already_compressed_att_without_accept(DocUri, AttName), test_get_already_compressed_att_stub(DocUri, AttName). test_get_already_compressed_att_with_accept_gzip(DocUri, AttName) -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {DocUri ++ "/" ++ AttName, [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, true, "received body is gziped"), etap:is( iolist_to_binary(Body), iolist_to_binary(zlib:gzip(test_text_data())), "received data for the already compressed attachment is ok" ), ok. test_get_already_compressed_att_without_accept(DocUri, AttName) -> {ok, {{_, Code, _}, Headers, Body}} = http:request( get, {DocUri ++ "/" ++ AttName, []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers), etap:is(Gziped, false, "received body is not gziped"), etap:is( iolist_to_binary(Body), iolist_to_binary(test_text_data()), "received data for the already compressed attachment is ok" ), ok. test_get_already_compressed_att_stub(DocUri, AttName) -> {ok, {{_, Code, _}, _Headers, Body}} = http:request( get, {DocUri ++ "?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body), {AttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, iolist_to_binary(AttName)] ), AttLength = couch_util:get_value(<<"length">>, AttJson), etap:is( AttLength, iolist_size((zlib:gzip(test_text_data()))), "Already compressed attachment stub length matches the " "compressed length" ), Encoding = couch_util:get_value(<<"encoding">>, AttJson), etap:is( Encoding, <<"gzip">>, "Already compressed attachment stub has the encoding field set to gzip" ), EncLength = couch_util:get_value(<<"encoded_length">>, AttJson), etap:is( EncLength, AttLength, "Already compressed attachment stub encoded_length matches the " "length field value" ), ok. test_create_already_compressed_att_with_invalid_content_encoding( DocUri, AttName, AttData, Encoding) -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {DocUri ++ "/" ++ AttName, [{"Content-Encoding", Encoding}], "text/plain", AttData}, [], [{sync, true}]), etap:is( Code, 415, "Couldn't create an already compressed attachment using the " "unsupported encoding '" ++ Encoding ++ "'" ), ok. test_compressible_type_with_parameters() -> {ok, {{_, Code, _}, _Headers, _Body}} = http:request( put, {db_url() ++ "/testdoc5/readme.txt", [], "text/plain; charset=UTF-8", test_text_data()}, [], [{sync, true}]), etap:is(Code, 201, "Created text attachment with MIME type " "'text/plain; charset=UTF-8' using the standalone api"), {ok, {{_, Code2, _}, Headers2, Body}} = http:request( get, {db_url() ++ "/testdoc5/readme.txt", [{"Accept-Encoding", "gzip"}]}, [], [{sync, true}]), etap:is(Code2, 200, "HTTP response code is 200"), Gziped = lists:member({"content-encoding", "gzip"}, Headers2), etap:is(Gziped, true, "received body is gziped"), Uncompressed = binary_to_list(zlib:gunzip(list_to_binary(Body))), etap:is(Uncompressed, test_text_data(), "received data is gzipped"), {ok, {{_, Code3, _}, _Headers3, Body3}} = http:request( get, {db_url() ++ "/testdoc5?att_encoding_info=true", []}, [], [{sync, true}]), etap:is(Code3, 200, "HTTP response code is 200"), Json = couch_util:json_decode(Body3), {TextAttJson} = couch_util:get_nested_json_value( Json, [<<"_attachments">>, <<"readme.txt">>] ), TextAttLength = couch_util:get_value(<<"length">>, TextAttJson), etap:is( TextAttLength, length(test_text_data()), "text attachment stub length matches the uncompressed length" ), TextAttEncoding = couch_util:get_value(<<"encoding">>, TextAttJson), etap:is( TextAttEncoding, <<"gzip">>, "text attachment stub has the encoding field set to gzip" ), TextAttEncLength = couch_util:get_value(<<"encoded_length">>, TextAttJson), etap:is( TextAttEncLength, iolist_size(zlib:gzip(test_text_data())), "text attachment stub encoded_length matches the compressed length" ), ok. test_png_data() -> {ok, Data} = file:read_file( test_util:source_file("share/www/image/logo.png") ), binary_to_list(Data). test_text_data() -> {ok, Data} = file:read_file( test_util:source_file("README") ), binary_to_list(Data).