|
13 | 13 | // limitations under the License.
|
14 | 14 |
|
15 | 15 | #include <chrono>
|
| 16 | +#include <iostream> |
| 17 | +#include <iterator> |
| 18 | +#include <new> |
| 19 | +#include <sstream> |
16 | 20 | #include <vector>
|
17 | 21 |
|
18 | 22 | #include <bsoncxx/builder/basic/document.hpp>
|
@@ -2333,10 +2337,10 @@ TEST_CASE("read_concern is inherited from parent", "[collection]") {
|
2333 | 2337 | }
|
2334 | 2338 | }
|
2335 | 2339 |
|
2336 |
| -void find_index_and_validate(collection& coll, |
2337 |
| - stdx::string_view index_name, |
2338 |
| - const std::function<void(bsoncxx::document::view)>& validate = |
2339 |
| - [](bsoncxx::document::view) {}) { |
| 2340 | +void find_index_and_validate( |
| 2341 | + collection& coll, |
| 2342 | + stdx::string_view index_name, |
| 2343 | + const std::function<void(bsoncxx::document::view)>& validate = [](bsoncxx::document::view) {}) { |
2340 | 2344 | auto cursor = coll.list_indexes();
|
2341 | 2345 |
|
2342 | 2346 | for (auto&& index : cursor) {
|
@@ -2808,4 +2812,104 @@ TEST_CASE("Ensure that the WriteConcernError 'errInfo' object is propagated", "[
|
2808 | 2812 | REQUIRE(contains_err_info);
|
2809 | 2813 | }
|
2810 | 2814 |
|
| 2815 | +TEST_CASE("expose writeErrors[].errInfo", "[collection]") { |
| 2816 | + // A helper for checking that an error document is well-formed according to our requirements: |
| 2817 | + auto writeErrors_well_formed = [](const bsoncxx::document::view& reply_view) -> bool { |
| 2818 | + if (!reply_view["writeErrors"]) { |
| 2819 | + return false; |
| 2820 | + } |
| 2821 | + |
| 2822 | + const auto& errdoc = reply_view["writeErrors"][0]; |
| 2823 | + |
| 2824 | + auto error_code = errdoc["code"].get_int32(); |
| 2825 | + |
| 2826 | + // The code should always be 121 (DocumentValidationFailure): |
| 2827 | + if (121 != error_code) { |
| 2828 | + std::ostringstream os; |
| 2829 | + os << "writeErrors expected to have code 121, but had " << error_code << " instead"; |
| 2830 | + throw std::runtime_error(os.str()); |
| 2831 | + } |
| 2832 | + |
| 2833 | + // We require the "details" field be present: |
| 2834 | + if (!errdoc["errInfo"]["details"]) { |
| 2835 | + throw std::runtime_error("no \"details\" field in \"writeErrors\""); |
| 2836 | + } |
| 2837 | + |
| 2838 | + return true; |
| 2839 | + }; |
| 2840 | + |
| 2841 | + // Set up our test environment: |
| 2842 | + instance::current(); |
| 2843 | + |
| 2844 | + mongocxx::options::apm apm_opts; |
| 2845 | + |
| 2846 | + auto client_opts = test_util::add_test_server_api(); |
| 2847 | + |
| 2848 | + // We set this by side effect in on_command_succeeded to make sure the callback was actually |
| 2849 | + // triggered: |
| 2850 | + bool insert_succeeded = false; |
| 2851 | + |
| 2852 | + // Listen to the insertion-failed event: we want to get a copy of the server's |
| 2853 | + // response so that we can compare it to the thrown exception later: |
| 2854 | + apm_opts.on_command_succeeded([&writeErrors_well_formed, &insert_succeeded]( |
| 2855 | + const mongocxx::events::command_succeeded_event& ev) { |
| 2856 | + if (0 != ev.command_name().compare("insert")) { |
| 2857 | + return; |
| 2858 | + } |
| 2859 | + |
| 2860 | + REQUIRE(writeErrors_well_formed(ev.reply())); |
| 2861 | + |
| 2862 | + // Make sure that "we" were actually called: |
| 2863 | + insert_succeeded = true; |
| 2864 | + }); |
| 2865 | + |
| 2866 | + client_opts.apm_opts(apm_opts); |
| 2867 | + |
| 2868 | + auto mongodb_client = mongocxx::client(uri{}, client_opts); |
| 2869 | + |
| 2870 | + if (!test_util::newer_than(mongodb_client, "5.0")) { |
| 2871 | + WARN("skip: test requires MongoDB server 5.0 or newer"); |
| 2872 | + return; |
| 2873 | + } |
| 2874 | + |
| 2875 | + database db = mongodb_client["prose_test_expose_details"]; |
| 2876 | + |
| 2877 | + const std::string collname{"mongo_cxx_driver-expose_details"}; |
| 2878 | + |
| 2879 | + // Drop the existing collection, if any: |
| 2880 | + db[collname].drop(); |
| 2881 | + |
| 2882 | + // Make a new collection with validation checking: |
| 2883 | + collection coll = db.create_collection( |
| 2884 | + collname, |
| 2885 | + make_document(kvp("validator", |
| 2886 | + make_document(kvp("field_x", make_document(kvp("$type", "string"))))))); |
| 2887 | + |
| 2888 | + SECTION("cause a type violation on insert") { |
| 2889 | + bsoncxx::builder::basic::document entry; |
| 2890 | + |
| 2891 | + entry.append(kvp("_id", bsoncxx::oid()), kvp("field_x", 42)); |
| 2892 | + |
| 2893 | + try { |
| 2894 | + coll.insert_one(entry.view()); |
| 2895 | + |
| 2896 | + // We should not make it here (i.e. this is an error): |
| 2897 | + CHECK(false); |
| 2898 | + } catch (const operation_exception& e) { |
| 2899 | + auto rse = e.raw_server_error(); |
| 2900 | + |
| 2901 | + // We have no has_value() check: |
| 2902 | + CHECK(rse); |
| 2903 | + |
| 2904 | + CHECK(writeErrors_well_formed(*rse)); |
| 2905 | + } catch (...) { |
| 2906 | + // An exception was thrown, but of the wrong type: |
| 2907 | + CHECK(false); |
| 2908 | + } |
| 2909 | + |
| 2910 | + // Make sure that our callback was actually triggered and completed successfully: |
| 2911 | + REQUIRE(insert_succeeded); |
| 2912 | + } |
| 2913 | +} |
| 2914 | + |
2811 | 2915 | } // namespace
|
0 commit comments