diff options
Diffstat (limited to 'src/couch/test/eunit/couch_doc_json_tests.erl')
-rw-r--r-- | src/couch/test/eunit/couch_doc_json_tests.erl | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/src/couch/test/eunit/couch_doc_json_tests.erl b/src/couch/test/eunit/couch_doc_json_tests.erl new file mode 100644 index 000000000..51f228900 --- /dev/null +++ b/src/couch/test/eunit/couch_doc_json_tests.erl @@ -0,0 +1,493 @@ +% 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(couch_doc_json_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +setup() -> + mock(couch_log), + mock(config), + mock(couch_db_plugin), + ok. + +teardown(_) -> + meck:unload(couch_log), + meck:unload(config), + meck:unload(couch_db_plugin), + ok. + +mock(couch_db_plugin) -> + ok = meck:new(couch_db_plugin, [passthrough]), + ok = meck:expect(couch_db_plugin, validate_docid, fun(_) -> false end), + ok; +mock(couch_log) -> + ok = meck:new(couch_log, [passthrough]), + ok = meck:expect(couch_log, debug, fun(_, _) -> ok end), + ok; +mock(config) -> + meck:new(config, [passthrough]), + meck:expect(config, get_integer, + fun("couchdb", "max_document_size", 4294967296) -> 1024 end), + meck:expect(config, get, fun(_, _) -> undefined end), + meck:expect(config, get, fun(_, _, Default) -> Default end), + ok. + + +json_doc_test_() -> + { + setup, + fun setup/0, fun teardown/1, + fun(_) -> + [{"Document from JSON", [ + from_json_with_dbname_error_cases(), + from_json_with_db_name_success_cases(), + from_json_success_cases(), + from_json_error_cases() + ]}, + {"Document to JSON", [ + to_json_success_cases() + ]}] + end + }. + +from_json_success_cases() -> + Cases = [ + { + {[]}, + #doc{}, + "Return an empty document for an empty JSON object." + }, + { + {[{<<"_id">>, <<"zing!">>}]}, + #doc{id = <<"zing!">>}, + "Parses document ids." + }, + { + {[{<<"_id">>, <<"_design/foo">>}]}, + #doc{id = <<"_design/foo">>}, + "_design/document ids." + }, + { + {[{<<"_id">>, <<"_local/bam">>}]}, + #doc{id = <<"_local/bam">>}, + "_local/document ids." + }, + { + {[{<<"_rev">>, <<"4-230234">>}]}, + #doc{revs = {4, [<<"230234">>]}}, + "_rev stored in revs." + }, + { + {[{<<"soap">>, 35}]}, + #doc{body = {[{<<"soap">>, 35}]}}, + "Non underscore prefixed fields stored in body." + }, + { + {[{<<"_attachments">>, {[ + {<<"my_attachment.fu">>, {[ + {<<"stub">>, true}, + {<<"content_type">>, <<"application/awesome">>}, + {<<"length">>, 45} + ]}}, + {<<"noahs_private_key.gpg">>, {[ + {<<"data">>, <<"SSBoYXZlIGEgcGV0IGZpc2gh">>}, + {<<"content_type">>, <<"application/pgp-signature">>} + ]}} + ]}}]}, + #doc{atts = [ + couch_att:new([ + {name, <<"my_attachment.fu">>}, + {data, stub}, + {type, <<"application/awesome">>}, + {att_len, 45}, + {disk_len, 45}, + {revpos, undefined} + ]), + couch_att:new([ + {name, <<"noahs_private_key.gpg">>}, + {data, <<"I have a pet fish!">>}, + {type, <<"application/pgp-signature">>}, + {att_len, 18}, + {disk_len, 18}, + {revpos, 0} + ]) + ]}, + "Attachments are parsed correctly." + }, + { + {[{<<"_deleted">>, true}]}, + #doc{deleted = true}, + "_deleted controls the deleted field." + }, + { + {[{<<"_deleted">>, false}]}, + #doc{}, + "{\"_deleted\": false} is ok." + }, + { + {[ + {<<"_revisions">>, + {[{<<"start">>, 4}, + {<<"ids">>, [<<"foo1">>, <<"phi3">>, <<"omega">>]}]}}, + {<<"_rev">>, <<"6-something">>} + ]}, + #doc{revs = {4, [<<"foo1">>, <<"phi3">>, <<"omega">>]}}, + "_revisions attribute are preferred to _rev." + }, + { + {[{<<"_revs_info">>, dropping}]}, + #doc{}, + "Drops _revs_info." + }, + { + {[{<<"_local_seq">>, dropping}]}, + #doc{}, + "Drops _local_seq." + }, + { + {[{<<"_conflicts">>, dropping}]}, + #doc{}, + "Drops _conflicts." + }, + { + {[{<<"_deleted_conflicts">>, dropping}]}, + #doc{}, + "Drops _deleted_conflicts." + } + ], + lists:map( + fun({EJson, Expect, Msg}) -> + {Msg, ?_assertMatch(Expect, couch_doc:from_json_obj_validate(EJson))} + end, + Cases). + +from_json_with_db_name_success_cases() -> + Cases = [ + { + {[]}, + <<"_dbs">>, + #doc{}, + "DbName _dbs is acceptable with no docid" + }, + { + {[{<<"_id">>, <<"zing!">>}]}, + <<"_dbs">>, + #doc{id = <<"zing!">>}, + "DbName _dbs is acceptable with a normal docid" + }, + { + {[{<<"_id">>, <<"_users">>}]}, + <<"_dbs">>, + #doc{id = <<"_users">>}, + "_dbs/_users is acceptable" + }, + { + {[{<<"_id">>, <<"_replicator">>}]}, + <<"_dbs">>, + #doc{id = <<"_replicator">>}, + "_dbs/_replicator is acceptable" + }, + { + {[{<<"_id">>, <<"_global_changes">>}]}, + <<"_dbs">>, + #doc{id = <<"_global_changes">>}, + "_dbs/_global_changes is acceptable" + } + ], + lists:map( + fun({EJson, DbName, Expect, Msg}) -> + {Msg, ?_assertMatch(Expect, couch_doc:from_json_obj_validate(EJson, DbName))} + end, + Cases). + +from_json_error_cases() -> + Cases = [ + { + [], + {bad_request, "Document must be a JSON object"}, + "arrays are invalid" + }, + { + 4, + {bad_request, "Document must be a JSON object"}, + "integers are invalid" + }, + { + true, + {bad_request, "Document must be a JSON object"}, + "literals are invalid" + }, + { + {[{<<"_id">>, {[{<<"foo">>, 5}]}}]}, + {illegal_docid, <<"Document id must be a string">>}, + "Document id must be a string." + }, + { + {[{<<"_id">>, <<"_random">>}]}, + {illegal_docid, + <<"Only reserved document ids may start with underscore.">>}, + "Disallow arbitrary underscore prefixed docids." + }, + { + {[{<<"_rev">>, 5}]}, + {bad_request, <<"Invalid rev format">>}, + "_rev must be a string" + }, + { + {[{<<"_rev">>, "foobar"}]}, + {bad_request, <<"Invalid rev format">>}, + "_rev must be %d-%s" + }, + { + {[{<<"_rev">>, "foo-bar"}]}, + "Error if _rev's integer expection is broken." + }, + { + {[{<<"_revisions">>, {[{<<"start">>, true}]}}]}, + {doc_validation, "_revisions.start isn't an integer."}, + "_revisions.start must be an integer." + }, + { + {[{<<"_revisions">>, {[{<<"start">>, 0}, {<<"ids">>, 5}]}}]}, + {doc_validation, "_revisions.ids isn't a array."}, + "_revions.ids must be a list." + }, + { + {[{<<"_revisions">>, {[{<<"start">>, 0}, {<<"ids">>, [5]}]}}]}, + {doc_validation, "RevId isn't a string"}, + "Revision ids must be strings." + }, + { + {[{<<"_revisions">>, {[{<<"start">>, 0}, + {<<"ids">>, [<<"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">>]}]}}]}, + {doc_validation, "RevId isn't a valid hexadecimal"}, + "Revision ids must be a valid hex." + }, + { + {[{<<"_something">>, 5}]}, + {doc_validation, <<"Bad special document member: _something">>}, + "Underscore prefix fields are reserved." + }, + { + fun() -> + {[ + {<<"_id">>, <<"large_doc">>}, + {<<"x">> , << <<"x">> || _ <- lists:seq(1,1025) >>} + ]} + end, + {request_entity_too_large, <<"large_doc">>}, + "Document too large." + } + ], + + lists:map(fun + ({Fun, Expect, Msg}) when is_function(Fun, 0) -> + {Msg, + ?_assertThrow(Expect, couch_doc:from_json_obj_validate(Fun()))}; + ({EJson, Expect, Msg}) -> + {Msg, + ?_assertThrow(Expect, couch_doc:from_json_obj_validate(EJson))}; + ({EJson, Msg}) -> + {Msg, + ?_assertThrow(_, couch_doc:from_json_obj_validate(EJson))} + end, Cases). + +from_json_with_dbname_error_cases() -> + Cases = [ + { + {[{<<"_id">>, <<"_random">>}]}, + <<"_dbs">>, + {illegal_docid, + <<"Only reserved document ids may start with underscore.">>}, + "Disallow non-system-DB underscore prefixed docids in _dbs database." + }, + { + {[{<<"_id">>, <<"_random">>}]}, + <<"foobar">>, + {illegal_docid, + <<"Only reserved document ids may start with underscore.">>}, + "Disallow arbitrary underscore prefixed docids in regular database." + }, + { + {[{<<"_id">>, <<"_users">>}]}, + <<"foobar">>, + {illegal_docid, + <<"Only reserved document ids may start with underscore.">>}, + "Disallow system-DB docid _users in regular database." + } + ], + + lists:map( + fun({EJson, DbName, Expect, Msg}) -> + Error = (catch couch_doc:from_json_obj_validate(EJson, DbName)), + {Msg, ?_assertMatch(Expect, Error)} + end, + Cases). + +to_json_success_cases() -> + Cases = [ + { + #doc{}, + {[{<<"_id">>, <<"">>}]}, + "Empty docs are {\"_id\": \"\"}" + }, + { + #doc{id = <<"foo">>}, + {[{<<"_id">>, <<"foo">>}]}, + "_id is added." + }, + { + #doc{revs = {5, ["foo"]}}, + {[{<<"_id">>, <<>>}, {<<"_rev">>, <<"5-foo">>}]}, + "_rev is added." + }, + { + [revs], + #doc{revs = {5, [<<"first">>, <<"second">>]}}, + {[ + {<<"_id">>, <<>>}, + {<<"_rev">>, <<"5-first">>}, + {<<"_revisions">>, {[ + {<<"start">>, 5}, + {<<"ids">>, [<<"first">>, <<"second">>]} + ]}} + ]}, + "_revisions include with revs option" + }, + { + #doc{body = {[{<<"foo">>, <<"bar">>}]}}, + {[{<<"_id">>, <<>>}, {<<"foo">>, <<"bar">>}]}, + "Arbitrary fields are added." + }, + { + #doc{deleted = true, body = {[{<<"foo">>, <<"bar">>}]}}, + {[{<<"_id">>, <<>>}, {<<"foo">>, <<"bar">>}, {<<"_deleted">>, true}]}, + "Deleted docs no longer drop body members." + }, + { + #doc{meta = [ + {revs_info, 4, [{<<"fin">>, deleted}, {<<"zim">>, missing}]} + ]}, + {[ + {<<"_id">>, <<>>}, + {<<"_revs_info">>, [ + {[{<<"rev">>, <<"4-fin">>}, {<<"status">>, <<"deleted">>}]}, + {[{<<"rev">>, <<"3-zim">>}, {<<"status">>, <<"missing">>}]} + ]} + ]}, + "_revs_info field is added correctly." + }, + { + #doc{meta = [{local_seq, 5}]}, + {[{<<"_id">>, <<>>}, {<<"_local_seq">>, 5}]}, + "_local_seq is added as an integer." + }, + { + #doc{meta = [{conflicts, [{3, <<"yep">>}, {1, <<"snow">>}]}]}, + {[ + {<<"_id">>, <<>>}, + {<<"_conflicts">>, [<<"3-yep">>, <<"1-snow">>]} + ]}, + "_conflicts is added as an array of strings." + }, + { + #doc{meta = [{deleted_conflicts, [{10923, <<"big_cowboy_hat">>}]}]}, + {[ + {<<"_id">>, <<>>}, + {<<"_deleted_conflicts">>, [<<"10923-big_cowboy_hat">>]} + ]}, + "_deleted_conflicsts is added as an array of strings." + }, + { + #doc{atts = [ + couch_att:new([ + {name, <<"big.xml">>}, + {type, <<"xml/sucks">>}, + {data, fun() -> ok end}, + {revpos, 1}, + {att_len, 400}, + {disk_len, 400} + ]), + couch_att:new([ + {name, <<"fast.json">>}, + {type, <<"json/ftw">>}, + {data, <<"{\"so\": \"there!\"}">>}, + {revpos, 1}, + {att_len, 16}, + {disk_len, 16} + ]) + ]}, + {[ + {<<"_id">>, <<>>}, + {<<"_attachments">>, {[ + {<<"big.xml">>, {[ + {<<"content_type">>, <<"xml/sucks">>}, + {<<"revpos">>, 1}, + {<<"length">>, 400}, + {<<"stub">>, true} + ]}}, + {<<"fast.json">>, {[ + {<<"content_type">>, <<"json/ftw">>}, + {<<"revpos">>, 1}, + {<<"length">>, 16}, + {<<"stub">>, true} + ]}} + ]}} + ]}, + "Attachments attached as stubs only include a length." + }, + { + [attachments], + #doc{atts = [ + couch_att:new([ + {name, <<"stuff.txt">>}, + {type, <<"text/plain">>}, + {data, fun() -> <<"diet pepsi">> end}, + {revpos, 1}, + {att_len, 10}, + {disk_len, 10} + ]), + couch_att:new([ + {name, <<"food.now">>}, + {type, <<"application/food">>}, + {revpos, 1}, + {data, <<"sammich">>} + ]) + ]}, + {[ + {<<"_id">>, <<>>}, + {<<"_attachments">>, {[ + {<<"stuff.txt">>, {[ + {<<"content_type">>, <<"text/plain">>}, + {<<"revpos">>, 1}, + {<<"data">>, <<"ZGlldCBwZXBzaQ==">>} + ]}}, + {<<"food.now">>, {[ + {<<"content_type">>, <<"application/food">>}, + {<<"revpos">>, 1}, + {<<"data">>, <<"c2FtbWljaA==">>} + ]}} + ]}} + ]}, + "Attachments included inline with attachments option." + } + ], + + lists:map(fun + ({Doc, EJson, Msg}) -> + {Msg, ?_assertMatch(EJson, couch_doc:to_json_obj(Doc, []))}; + ({Options, Doc, EJson, Msg}) -> + {Msg, ?_assertMatch(EJson, couch_doc:to_json_obj(Doc, Options))} + end, Cases). |