You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by GitBox <gi...@apache.org> on 2022/10/15 01:20:35 UTC

[GitHub] [couchdb] nickva opened a new pull request, #4209: Add bulk_get tests

nickva opened a new pull request, #4209:
URL: https://github.com/apache/couchdb/pull/4209

   Add an integration test suite for `_bulk_get`. Test various errors, missing docs, bad revs, various `atts_since`, `latest`, `attachments` options, as well as a test for multiple documents.
   
   This is related to the effort of optimizing our bulk_get implementation [1]. In order get some confidence we haven't broken something in the process we're adding a bunch of tests.
   
   Normally these tests would be a bit cumbersome, due to a deep level of nesting in the results with all the `ok`, `results`, `docs`, etc. levels of nesting. However, the `?assertMatch/2` EUnit macro macro and Erlang maps came to the rescue, and we're able to match just the parts which interest us in an elegant way.
   
   [1] https://github.com/apache/couchdb/issues/4183
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997333116


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Empty atts_since. Do not expect to get the attachment
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Add a document without atts_since to ensure atts_since applies only to
+        % individual requests
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"stub">> := true}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple_attachments_true({_, DbUrl}) ->
+    % Since attachments=true, expect to always get the attachments, unless
+    % there is an atts_since present and atts_since would prevent the
+    % attachment from being returned.
+    Docs = [
+        % Attachment revpos is 1 so we do not expect this attachment body
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Should get the attachment as it is set as a default option
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Check a doc without atts_since to ensure atts_since applies only to
+        % individual requests, otherwise default options apply
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs, "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"data">> := ?ATT_DATA}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev_latest({_, DbUrl}) ->
+    % Check the case of latest and a missing
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_multiple_docs({_, DbUrl}) ->
+    Reqs = [#{<<"id">> => integer_to_binary(I)} || I <- lists:seq(1, ?DOC_COUNT)],
+    {Code, Res} = bulk_get(DbUrl, Reqs),
+    ?assertEqual(200, Code),
+    ?assertMatch([#{<<"docs">> := _} | _], Res),
+    ?assertEqual(?DOC_COUNT, length(Res)),
+    lists:foreach(
+        fun({I, Docs}) ->
+            Id = integer_to_binary(I),
+            ?assertMatch(
+                #{
+                    <<"docs">> := [
+                        #{
+                            <<"ok">> := #{
+                                <<"_id">> := Id,
+                                <<"_rev">> := <<"1-reva">>
+                            }
+                        }
+                    ],
+                    <<"id">> := Id
+                },
+                Docs
+            )
+        end,
+        lists:zip(lists:seq(1, ?DOC_COUNT), Res)
+    ).
+
+% Utility functions
+
+setup_ctx() ->
+    Ctx = test_util:start_couch([chttpd]),
+    Hashed = couch_passwords:hash_admin_password(?PASS),
+    ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
+    Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
+    Db = binary_to_list(?tempdb()),

Review Comment:
   Oh, good idea



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997332296


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo

Review Comment:
   yes, good find



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997330197


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),

Review Comment:
   Good idea to document it. We document it for doc GETs https://docs.couchdb.org/en/3.2.2-docs/api/document/common.html#get--db-docid and we cheat for the _bulk_get but allowing passing most of the same option.
   
   `latest` as implemented today means, given a particular revision, we don't want to return that revision only but all the latest descendants (leafs) of that revision.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] jaydoane commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
jaydoane commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997291823


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),

Review Comment:
   Also not seeing docs for bulk_get `revs=true`.



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),

Review Comment:
   I looked for docs on `latest=true` [here](https://docs.couchdb.org/en/3.2.2-docs/api/database/bulk-api.html#db-bulk-get) but didn't see anything. Should we improve the docs, or is this an undocumented parameter?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Empty atts_since. Do not expect to get the attachment
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Add a document without atts_since to ensure atts_since applies only to
+        % individual requests
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"stub">> := true}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple_attachments_true({_, DbUrl}) ->
+    % Since attachments=true, expect to always get the attachments, unless
+    % there is an atts_since present and atts_since would prevent the
+    % attachment from being returned.
+    Docs = [
+        % Attachment revpos is 1 so we do not expect this attachment body
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Should get the attachment as it is set as a default option
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Check a doc without atts_since to ensure atts_since applies only to
+        % individual requests, otherwise default options apply
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs, "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"data">> := ?ATT_DATA}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev_latest({_, DbUrl}) ->
+    % Check the case of latest and a missing

Review Comment:
   should this be "and a missing _rev_"?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,

Review Comment:
   Seems like it would be nice to say _why_ it's a bad request (invalid rev in this case), but I guess that ship has sailed?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo

Review Comment:
   missing "since" between attachment and revpos=1?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Empty atts_since. Do not expect to get the attachment
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Add a document without atts_since to ensure atts_since applies only to
+        % individual requests
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"stub">> := true}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple_attachments_true({_, DbUrl}) ->
+    % Since attachments=true, expect to always get the attachments, unless
+    % there is an atts_since present and atts_since would prevent the
+    % attachment from being returned.
+    Docs = [
+        % Attachment revpos is 1 so we do not expect this attachment body
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Should get the attachment as it is set as a default option
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Check a doc without atts_since to ensure atts_since applies only to
+        % individual requests, otherwise default options apply
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>
+        }
+    ],
+    {Code, Res} = bulk_get(DbUrl, Docs, "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            },
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            },
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_attachments">> :=
+                                #{?ATT := #{<<"data">> := ?ATT_DATA}},
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev_latest({_, DbUrl}) ->
+    % Check the case of latest and a missing
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_multiple_docs({_, DbUrl}) ->
+    Reqs = [#{<<"id">> => integer_to_binary(I)} || I <- lists:seq(1, ?DOC_COUNT)],
+    {Code, Res} = bulk_get(DbUrl, Reqs),
+    ?assertEqual(200, Code),
+    ?assertMatch([#{<<"docs">> := _} | _], Res),
+    ?assertEqual(?DOC_COUNT, length(Res)),
+    lists:foreach(
+        fun({I, Docs}) ->
+            Id = integer_to_binary(I),
+            ?assertMatch(
+                #{
+                    <<"docs">> := [
+                        #{
+                            <<"ok">> := #{
+                                <<"_id">> := Id,
+                                <<"_rev">> := <<"1-reva">>
+                            }
+                        }
+                    ],
+                    <<"id">> := Id
+                },
+                Docs
+            )
+        end,
+        lists:zip(lists:seq(1, ?DOC_COUNT), Res)
+    ).
+
+% Utility functions
+
+setup_ctx() ->
+    Ctx = test_util:start_couch([chttpd]),
+    Hashed = couch_passwords:hash_admin_password(?PASS),
+    ok = config:set("admins", ?USER, ?b2l(Hashed), _Persist = false),
+    Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
+    Db = binary_to_list(?tempdb()),

Review Comment:
   Maybe use either `?b2l` or `binary_to_list` rather than both? I prefer the latter personally.



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),

Review Comment:
   ditto



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,

Review Comment:
   As a perplexed user, I might think it's the doc id that's not found rather than the rev. I guess we'd need to change existing behavior to enhance this to become e.g. `rev_not_found`?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true

Review Comment:
   You could alternately change the name to e.g. `t_atts_since_overrides_attachments_true` and possibly avoid the comment?



##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{
+                                    <<"stub">> := true
+                                }
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_multiple({_, DbUrl}) ->
+    % Attachment revpos is 1 so we do not expect this attachment body
+    Docs = [
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"2-revb">>]
+        },
+        % Should get the attachment revpos=1 is greater than 0-foo
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => [<<"0-foo">>]
+        },
+        % Empty atts_since. Do not expect to get the attachment
+        #{
+            <<"id">> => ?DOC,
+            <<"rev">> => <<"2-revb">>,
+            <<"atts_since">> => []
+        },
+        % Add a document without atts_since to ensure atts_since applies only to

Review Comment:
   maybe s/Add/Include/ ?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997338890


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,

Review Comment:
   The `error` field is more general and is a text representation of the HTTP codes mostly. So if this was an individual request it would return a 400.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997347523


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),

Review Comment:
   For `_bulk_get` we could definitely use at least a mention of "most of the individual doc option would apply here too"



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997332036


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_revisions">> :=
+                                #{<<"ids">> := [<<"reva">>], <<"start">> := 1},
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_attachments_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?attachments=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"1-reva">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since({_, DbUrl}) ->
+    % Attachment should not be returned as 2 from 2-revb is not stricly greater
+    % than 1 from our attachment's revpos
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"2-revb">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_returns_attachment({_, DbUrl}) ->
+    % 0-baz revpos 0 is less than revpos 1 of our attachment
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"2-revb">>,
+        <<"atts_since">> => [<<"0-baz">>]
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"data">> := ?ATT_DATA}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_atts_since_attachments_true({_, DbUrl}) ->
+    % Check that atts_since overrides attachments=true

Review Comment:
   Good idea!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997340328


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,

Review Comment:
   The full error message has a reason parameter like "revision not found ..." but I opted to match just the main error code only, which is the equivalent of a http code if it an individual request. So this one would be a 404, for instance.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997330197


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),

Review Comment:
   Good idea to document it. We document it for doc GETs https://docs.couchdb.org/en/3.2.2-docs/api/document/common.html#get--db-docid and we cheat for the _bulk_get but allowing passing most of the same options.
   
   `latest` as implemented today means, given a particular revision, we don't want to return that revision only but all the latest descendants (leafs) of that revision.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997331724


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_revs_true({_, DbUrl}) ->
+    Doc = #{
+        <<"id">> => ?DOC,
+        <<"rev">> => <<"1-reva">>
+    },
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?revs=true"),

Review Comment:
   Just like with `latest=true` we cheat here and accept most of the same options as a single document GET. We cheated down to the level of accepting parameters in the POST requests (we share the params parsing code), instead of having them as json fields in the request body itself.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva commented on a diff in pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva commented on code in PR #4209:
URL: https://github.com/apache/couchdb/pull/4209#discussion_r997330197


##########
src/chttpd/test/eunit/chttpd_bulk_get_test.erl:
##########
@@ -0,0 +1,785 @@
+% 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(chttpd_bulk_get_test).
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+
+-define(USER, "chttpd_bulk_get_test_admin").
+-define(PASS, "pass").
+-define(AUTH, {basic_auth, {?USER, ?PASS}}).
+-define(JSON, {"Content-Type", "application/json"}).
+
+-define(DOC, <<"doc">>).
+-define(REVA, <<"reva">>).
+-define(REVB, <<"revb">>).
+-define(REVC, <<"revc">>).
+-define(ATT, <<"att">>).
+-define(ATT_DATA, <<"dGhlZGF0YQ==">>).
+
+-define(DOC_COUNT, 2000).
+
+test_docs_revs() ->
+    [
+        {?DOC, [?REVA]},
+        {?DOC, [?REVB, ?REVA]},
+        {?DOC, [?REVC, ?REVA]}
+    ].
+
+bulk_get_test_() ->
+    {
+        setup,
+        fun setup_basic/0,
+        fun teardown/1,
+        with([
+            ?TDEF(t_empty_request),
+            ?TDEF(t_no_docs),
+            ?TDEF(t_invalid_doc),
+            ?TDEF(t_doc_no_id),
+            ?TDEF(t_missing_doc),
+            ?TDEF(t_invalid_rev),
+            ?TDEF(t_missing_rev),
+            ?TDEF(t_doc_all_revs),
+            ?TDEF(t_specific_rev),
+            ?TDEF(t_specific_rev_latest),
+            ?TDEF(t_ancestor_rev_latest),
+            ?TDEF(t_revs_true),
+            ?TDEF(t_attachments_true),
+            ?TDEF(t_atts_since),
+            ?TDEF(t_atts_since_returns_attachment),
+            ?TDEF(t_atts_since_attachments_true),
+            ?TDEF(t_atts_since_multiple),
+            ?TDEF(t_atts_since_multiple_attachments_true),
+            ?TDEF(t_missing_rev_latest)
+        ])
+    }.
+
+bulk_get_multiple_docs_test_() ->
+    {
+        foreach,
+        fun setup_multiple/0,
+        fun teardown/1,
+        [
+            ?TDEF_FE(t_multiple_docs, 10)
+        ]
+    }.
+
+t_empty_request({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, []),
+    ?assertEqual(200, Code),
+    ?assertEqual([], Res).
+
+t_no_docs({_, DbUrl}) ->
+    {Code, #{}} = req(post, DbUrl ++ "/_bulk_get", #{}),
+    ?assertEqual(400, Code).
+
+t_invalid_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [<<"foo">>]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_doc_no_id({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"rev">> => <<"1-foo">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"id">> := null,
+                            <<"rev">> := null,
+                            <<"error">> := <<"bad_request">>
+                        }
+                    }
+                ],
+                <<"id">> := null
+            }
+        ],
+        Res
+    ).
+
+t_missing_doc({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => <<"missing">>}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := <<"missing">>,
+                            <<"rev">> := <<"undefined">>
+                        }
+                    }
+                ],
+                <<"id">> := <<"missing">>
+            }
+        ],
+        Res
+    ).
+
+t_invalid_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => 42},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"bad_request">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := 42
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_missing_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-x">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"error">> := #{
+                            <<"error">> := <<"not_found">>,
+                            <<"id">> := ?DOC,
+                            <<"rev">> := <<"1-x">>
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_doc_all_revs({_, DbUrl}) ->
+    {Code, Res} = bulk_get(DbUrl, [#{<<"id">> => ?DOC}]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    },
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revc">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc]),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_specific_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"2-revb">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),
+    ?assertEqual(200, Code),
+    ?assertMatch(
+        [
+            #{
+                <<"docs">> := [
+                    #{
+                        <<"ok">> := #{
+                            <<"_id">> := ?DOC,
+                            <<"_rev">> := <<"2-revb">>,
+                            <<"_attachments">> := #{
+                                ?ATT := #{<<"stub">> := true}
+                            }
+                        }
+                    }
+                ],
+                <<"id">> := ?DOC
+            }
+        ],
+        Res
+    ).
+
+t_ancestor_rev_latest({_, DbUrl}) ->
+    Doc = #{<<"id">> => ?DOC, <<"rev">> => <<"1-reva">>},
+    {Code, Res} = bulk_get(DbUrl, [Doc], "?latest=true"),

Review Comment:
   Good idea to document it. We document it for doc GETs https://docs.couchdb.org/en/3.2.2-docs/api/document/common.html#get--db-docid and we cheat for the _bulk_get but allowing passing most of the same options.
   
   `latest` as implemented today means, given a particular revision, we don't want to return that revision all the latest descendants (leafs) of that revision.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [couchdb] nickva merged pull request #4209: Add bulk_get tests

Posted by GitBox <gi...@apache.org>.
nickva merged PR #4209:
URL: https://github.com/apache/couchdb/pull/4209


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org