@@ -765,3 +765,70 @@ TEST_CASE("transactions public blocking API: can remove doc in bucket not yet op
765765 CHECK (get_err.ec () == couchbase::errc::key_value::document_not_found);
766766 });
767767}
768+
769+ TEST_CASE (" transactions public blocking API: insert then replace with illegal document "
770+ " modification in-between" ,
771+ " [transactions]" )
772+ {
773+ test::utils::integration_test_guard integration;
774+
775+ if (!integration.cluster_version ().supports_replace_body_with_xattr ()) {
776+ // If replace_body_with_xattr is not supported, we have the staged insert's content in memory,
777+ // so the transactional get will not fetch the document from the server, which would give the
778+ // up-to-date CAS.
779+ SKIP (" the server does not support replace_body_with_xattr" );
780+ }
781+
782+ auto doc_id = test::utils::uniq_id (" txn" );
783+ auto txn_content_initial = tao::json::value{ { " num" , 12 } };
784+ auto txn_content_updated = tao::json::value{ { " num" , 20 } };
785+ auto illegal_content = tao::json::value{ { " illegal" , " content" } };
786+
787+ auto cluster = integration.public_cluster ();
788+ auto collection = cluster.bucket (integration.ctx .bucket ).default_collection ();
789+
790+ auto [tx_err, result] = cluster.transactions ()->run (
791+ [&doc_id, &collection, &txn_content_initial, &txn_content_updated, &illegal_content](
792+ std::shared_ptr<couchbase::transactions::attempt_context> ctx) -> couchbase::error {
793+ // Stage an insert
794+ {
795+ auto [err, res] = ctx->insert (collection, doc_id, txn_content_initial);
796+ if (err) {
797+ return err;
798+ }
799+ REQUIRE (res.content_as <tao::json::value>() == txn_content_initial);
800+ }
801+
802+ // Do an illegal non-transactional insert that will override any staged content and txn
803+ // metadata
804+ {
805+ auto [err, res] = collection.insert (doc_id, illegal_content).get ();
806+ REQUIRE_SUCCESS (err.ec ());
807+ }
808+
809+ {
810+ // Now that we implement ExtReplaceBodyWithXattr, this will fetch the document from the
811+ // server (post-illegal mutation) as the staged content of the staged mutation is not stored
812+ // in memory.
813+ auto [get_err, get_res] = ctx->get (collection, doc_id);
814+ if (get_err) {
815+ return get_err;
816+ }
817+ REQUIRE (get_res.content_as <tao::json::value>() == illegal_content);
818+
819+ // This replace will use the CAS from the transaction_get_result, which should be the one
820+ // after the illegal insert. This means the operation will succeed, and will result in a
821+ // staged insert with the CAS from the transaction_get_result.
822+ // When committing, the replace_body_with_xattr op, and the transaction, will succeed.
823+ auto [replace_err, replace_res] = ctx->replace (get_res, txn_content_updated);
824+ if (replace_err) {
825+ return replace_err;
826+ }
827+ REQUIRE (replace_res.content_as <tao::json::value>() == txn_content_updated);
828+ }
829+
830+ return {};
831+ });
832+
833+ REQUIRE_SUCCESS (tx_err.ec ());
834+ }
0 commit comments