1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 
19 #include "mcbp_test.h"
20 #include "utilities/protocol2text.h"
21 #include "protocol/connection/client_mcbp_commands.h"
22 
23 #include <include/memcached/protocol_binary.h>
24 #include <memcached/protocol_binary.h>
25 #include <algorithm>
26 #include <gsl/gsl>
27 #include <vector>
28 
29 namespace mcbp {
30 namespace test {
31 
32 enum class SubdocOpcodes : uint8_t {
33     Get = PROTOCOL_BINARY_CMD_SUBDOC_GET,
34     Exists = PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
35     DictAdd = PROTOCOL_BINARY_CMD_SUBDOC_DICT_ADD,
36     Upsert = PROTOCOL_BINARY_CMD_SUBDOC_DICT_UPSERT,
37     Delete = PROTOCOL_BINARY_CMD_SUBDOC_DELETE,
38     Replace = PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
39     PushLast = PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_LAST,
40     PushFirst = PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_FIRST,
41     Insert = PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_INSERT,
42     AddUnique = PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_ADD_UNIQUE,
43     Counter = PROTOCOL_BINARY_CMD_SUBDOC_COUNTER,
44     MultiLookup = PROTOCOL_BINARY_CMD_SUBDOC_MULTI_LOOKUP,
45     MultiMutation = PROTOCOL_BINARY_CMD_SUBDOC_MULTI_MUTATION,
46     GetCount = PROTOCOL_BINARY_CMD_SUBDOC_GET_COUNT
47 };
48 
to_string(const SubdocOpcodes& opcode)49 std::string to_string(const SubdocOpcodes& opcode) {
50 #ifdef JETBRAINS_CLION_IDE
51     // CLion don't properly parse the output when the
52     // output gets written as the string instead of the
53     // number. This makes it harder to debug the tests
54     // so let's just disable it while we're waiting
55     // for them to supply a fix.
56     // See https://youtrack.jetbrains.com/issue/CPP-6039
57     return std::to_string(static_cast<int>(opcode));
58 #else
59     return memcached_opcode_2_text(uint8_t(opcode));
60 #endif
61 }
62 
operator <<(std::ostream& os, const SubdocOpcodes& o)63 std::ostream& operator<<(std::ostream& os, const SubdocOpcodes& o) {
64     os << to_string(o);
65     return os;
66 }
67 
68 /**
69  * Test the extra checks needed for XATTR access in subdoc
70  */
71 class SubdocXattrSingleTest
72     : public ValidatorTest,
73       public ::testing::WithParamInterface<SubdocOpcodes> {
74 public:
SubdocXattrSingleTest()75     SubdocXattrSingleTest()
76         : doc("Document"),
77           path("_sync.cas"),
78           value("\"${Mutation.CAS}\""),
79           flags(SUBDOC_FLAG_XATTR_PATH),
80           docFlags(mcbp::subdoc::doc_flag::None) {
81     }
82 
83     void SetUp() override {
84         ValidatorTest::SetUp();
85 
86         if (!needPayload()) {
87             value.clear();
88         }
89     }
90 
91 protected:
validate()92     int validate() {
93         auto opcode = (protocol_binary_command)GetParam();
94         protocol_binary_request_subdocument* req;
95         const size_t extlen = !isNone(docFlags) ? 1 : 0;
96         std::vector<uint8_t> blob(sizeof(req->bytes) + doc.length() +
97                                   path.length() + value.length() + extlen);
98         req = reinterpret_cast<protocol_binary_request_subdocument*>(
99             blob.data());
100 
101         req->message.header.request.magic = PROTOCOL_BINARY_REQ;
102         req->message.header.request.extlen =
103                 gsl::narrow_cast<uint8_t>(3 + extlen);
104         req->message.header.request.keylen =
105                 ntohs(gsl::narrow<uint16_t>(doc.length()));
106         req->message.header.request.bodylen = ntohl(gsl::narrow<uint32_t>(
107                 3 + doc.length() + path.length() + value.length() + extlen));
108         req->message.header.request.datatype = PROTOCOL_BINARY_RAW_BYTES;
109 
110         req->message.extras.subdoc_flags = (flags);
111         req->message.extras.pathlen =
112                 ntohs(gsl::narrow<uint16_t>(path.length()));
113 
114         auto* ptr = blob.data() + sizeof(req->bytes);
115         if (extlen) {
116             memcpy(ptr, &docFlags, sizeof(docFlags));
117             ptr += sizeof(docFlags);
118         }
119         memcpy(ptr, doc.data(), doc.length());
120         memcpy(ptr + doc.length(), path.data(), path.length());
121         memcpy(ptr + doc.length() + path.length(),
122                value.data(),
123                value.length());
124 
125         return ValidatorTest::validate(opcode, static_cast<void*>(blob.data()));
126     }
127 
needPayload()128     bool needPayload() {
129         switch (GetParam()) {
130         case SubdocOpcodes::Get:
131         case SubdocOpcodes::Exists:
132         case SubdocOpcodes::GetCount:
133         case SubdocOpcodes::Delete:
134             return false;
135 
136         case SubdocOpcodes::MultiMutation:
137         case SubdocOpcodes::MultiLookup:
138             throw std::logic_error("allowMacroExpansion: Multi* is not valid");
139 
140         case SubdocOpcodes::Counter:
141         case SubdocOpcodes::AddUnique:
142         case SubdocOpcodes::Insert:
143         case SubdocOpcodes::PushFirst:
144         case SubdocOpcodes::PushLast:
145         case SubdocOpcodes::Replace:
146         case SubdocOpcodes::Upsert:
147         case SubdocOpcodes::DictAdd:
148             return true;
149         }
150         throw std::logic_error("needPayload: Unknown parameter");
151     }
152 
allowMacroExpansion()153     bool allowMacroExpansion() {
154         switch (GetParam()) {
155         case SubdocOpcodes::Get:
156         case SubdocOpcodes::Exists:
157         case SubdocOpcodes::GetCount:
158         case SubdocOpcodes::Delete:
159         case SubdocOpcodes::Counter:
160         case SubdocOpcodes::AddUnique:
161             return false;
162 
163         case SubdocOpcodes::MultiMutation:
164         case SubdocOpcodes::MultiLookup:
165             throw std::logic_error("allowMacroExpansion: Multi* is not valid");
166 
167         case SubdocOpcodes::Insert:
168         case SubdocOpcodes::PushFirst:
169         case SubdocOpcodes::PushLast:
170         case SubdocOpcodes::Replace:
171         case SubdocOpcodes::Upsert:
172         case SubdocOpcodes::DictAdd:
173             return true;
174         }
175         throw std::logic_error("allowMacroExpansion: Unknown parameter");
176     }
177 
178     const std::string doc;
179     std::string path;
180     std::string value;
181     uint8_t flags;
182     mcbp::subdoc::doc_flag docFlags;
183 };
184 
185 INSTANTIATE_TEST_CASE_P(SubdocOpcodes,
186                         SubdocXattrSingleTest,
187                         ::testing::Values(SubdocOpcodes::Get,
188                                           SubdocOpcodes::Exists,
189                                           SubdocOpcodes::DictAdd,
190                                           SubdocOpcodes::Upsert,
191                                           SubdocOpcodes::Delete,
192                                           SubdocOpcodes::Replace,
193                                           SubdocOpcodes::PushLast,
194                                           SubdocOpcodes::PushFirst,
195                                           SubdocOpcodes::Insert,
196                                           SubdocOpcodes::AddUnique,
197                                           SubdocOpcodes::Counter,
198                                           SubdocOpcodes::GetCount),
199                         ::testing::PrintToStringParamName());
200 
TEST_P(SubdocXattrSingleTest, PathTest)201 TEST_P(SubdocXattrSingleTest, PathTest) {
202     path = "superduperlongpath";
203     flags = SUBDOC_FLAG_NONE;
204     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate())
205                 << memcached_opcode_2_text(uint8_t(GetParam()));
206 
207     // XATTR keys must be < 16 characters (we've got standalone tests
208     // to validate all of the checks for the xattr keys, this is just
209     // to make sure that our validator calls it ;-)
210     flags = SUBDOC_FLAG_XATTR_PATH;
211     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_XATTR_EINVAL, validate())
212                 << memcached_opcode_2_text(uint8_t(GetParam()));
213 
214     // Truncate it to a shorter one, and this time it should pass
215     path = "_sync.cas";
216     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate())
217                 << memcached_opcode_2_text(uint8_t(GetParam()));
218 }
219 
TEST_P(SubdocXattrSingleTest, ValidateFlags)220 TEST_P(SubdocXattrSingleTest, ValidateFlags) {
221     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
222 
223     // Access Deleted should pass without XATTR flag
224     flags = SUBDOC_FLAG_NONE;
225     docFlags = mcbp::subdoc::doc_flag::AccessDeleted;
226     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
227               validate());
228 
229     flags |= SUBDOC_FLAG_XATTR_PATH;
230     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
231 
232     // Check that Add & Mkdoc can't be used together
233     docFlags = mcbp::subdoc::doc_flag::Mkdoc | mcbp::subdoc::doc_flag::Add;
234     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_EINVAL, validate());
235     docFlags = mcbp::subdoc::doc_flag::AccessDeleted;
236 
237     flags |= SUBDOC_FLAG_EXPAND_MACROS;
238     if (allowMacroExpansion()) {
239         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
240 
241         // but it should fail if we don't have the XATTR_PATH
242         flags = SUBDOC_FLAG_EXPAND_MACROS;
243         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_INVALID_FLAG_COMBO,
244                   validate());
245 
246         // And it should also fail if we have Illegal macros
247         flags |= SUBDOC_FLAG_XATTR_PATH;
248         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
249         value = "${UnknownMacro}";
250         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_UNKNOWN_MACRO,
251                   validate());
252     } else {
253         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_EINVAL, validate());
254     }
255 }
256 
257 /**
258  * The SubdocXattrMultiLookupTest tests the XATTR specific constraints
259  * over the normal subdoc constraints tested elsewhere
260  */
261 class SubdocXattrMultiLookupTest : public ValidatorTest {
262 public:
SubdocXattrMultiLookupTest()263     SubdocXattrMultiLookupTest() {
264         request.setKey("Document");
265     }
266 
267     void SetUp() override {
268         ValidatorTest::SetUp();
269     }
270 
271 protected:
validate()272     int validate() {
273         std::vector<uint8_t> packet;
274         request.encode(packet);
275         return ValidatorTest::validate(PROTOCOL_BINARY_CMD_SUBDOC_MULTI_LOOKUP,
276                                        static_cast<void*>(packet.data()));
277     }
278 
279     BinprotSubdocMultiLookupCommand request;
280 };
281 
TEST_F(SubdocXattrMultiLookupTest, XAttrMayBeFirst)282 TEST_F(SubdocXattrMultiLookupTest, XAttrMayBeFirst) {
283     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
284                        SUBDOC_FLAG_XATTR_PATH,
285                        "_sync.cas"});
286     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
287                        SUBDOC_FLAG_NONE,
288                        "meta.author"});
289     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
290 }
291 
TEST_F(SubdocXattrMultiLookupTest, XAttrCantBeLast)292 TEST_F(SubdocXattrMultiLookupTest, XAttrCantBeLast) {
293     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
294                        SUBDOC_FLAG_NONE,
295                        "meta.author"});
296     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
297                        SUBDOC_FLAG_XATTR_PATH,
298                        "_sync.cas"});
299     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_INVALID_XATTR_ORDER, validate());
300 }
301 
TEST_F(SubdocXattrMultiLookupTest, XAttrKeyIsChecked)302 TEST_F(SubdocXattrMultiLookupTest, XAttrKeyIsChecked) {
303     // We got other unit tests that tests all of the different restrictions
304     // for the subdoc key.. just make sure that it is actually called..
305     // Check that we can't insert a key > 16 chars
306     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
307                        SUBDOC_FLAG_XATTR_PATH,
308                        "ThisIsASuperDuperLongPath"});
309     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_XATTR_EINVAL, validate());
310 }
311 
TEST_F(SubdocXattrMultiLookupTest, XattrFlagsMakeSense)312 TEST_F(SubdocXattrMultiLookupTest, XattrFlagsMakeSense) {
313     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
314                        SUBDOC_FLAG_XATTR_PATH,
315                        "_sync.cas"});
316     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
317 
318     // We shouldn't be allowed to expand macros for a lookup command
319     request[0].flags = SUBDOC_FLAG_EXPAND_MACROS;
320     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_EINVAL, validate());
321 
322     // We shouldn't be allowed to expand macros for a lookup command
323     // and the SUBDOC_FLAG_EXPAND_MACROS must have SUBDOC_FLAG_XATTR_PATH
324     request[0].flags = SUBDOC_FLAG_EXPAND_MACROS | SUBDOC_FLAG_XATTR_PATH;
325     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_EINVAL, validate());
326 
327     // Let's try a valid access deleted flag
328     request[0].flags = SUBDOC_FLAG_NONE;
329     request.addDocFlag(mcbp::subdoc::doc_flag::AccessDeleted);
330     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
331               validate());
332 
333     // We should be able to access deleted docs if both flags are set
334     request[0].flags = SUBDOC_FLAG_XATTR_PATH;
335     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
336 }
337 
TEST_F(SubdocXattrMultiLookupTest, AllowWholeDocAndXattrLookup)338 TEST_F(SubdocXattrMultiLookupTest, AllowWholeDocAndXattrLookup) {
339     request.addLookup(
340         {PROTOCOL_BINARY_CMD_SUBDOC_GET, SUBDOC_FLAG_XATTR_PATH, "_sync"});
341     request.addLookup({PROTOCOL_BINARY_CMD_GET, SUBDOC_FLAG_NONE, ""});
342     request.addDocFlag(mcbp::subdoc::doc_flag::AccessDeleted);
343     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
344 }
345 
TEST_F(SubdocXattrMultiLookupTest, AllowMultipleLookups)346 TEST_F(SubdocXattrMultiLookupTest, AllowMultipleLookups) {
347     for (int ii = 0; ii < 10; ii++) {
348         request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
349                            SUBDOC_FLAG_XATTR_PATH,
350                            "_sync.cas"});
351     }
352     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
353 }
354 
TEST_F(SubdocXattrMultiLookupTest, AllLookupsMustBeOnTheSamePath)355 TEST_F(SubdocXattrMultiLookupTest, AllLookupsMustBeOnTheSamePath) {
356     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
357                        SUBDOC_FLAG_XATTR_PATH,
358                        "_sync.cas"});
359     request.addLookup({PROTOCOL_BINARY_CMD_SUBDOC_EXISTS,
360                        SUBDOC_FLAG_XATTR_PATH,
361                        "foo.bar"});
362     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_INVALID_KEY_COMBO,
363               validate());
364 }
365 
366 /**
367  * The SubdocXattrMultiLookupTest tests the XATTR specific constraints
368  * over the normal subdoc constraints tested elsewhere
369  */
370 class SubdocXattrMultiMutationTest : public ValidatorTest {
371 public:
SubdocXattrMultiMutationTest()372     SubdocXattrMultiMutationTest() {
373         request.setKey("Document");
374     }
375 
376     void SetUp() override {
377         ValidatorTest::SetUp();
378     }
379 
380 protected:
validate()381     int validate() {
382         std::vector<uint8_t> packet;
383         request.encode(packet);
384         return ValidatorTest::validate(
385             PROTOCOL_BINARY_CMD_SUBDOC_MULTI_MUTATION,
386             static_cast<void*>(packet.data()));
387     }
388 
389     BinprotSubdocMultiMutationCommand request;
390 };
391 
TEST_F(SubdocXattrMultiMutationTest, XAttrMayBeFirst)392 TEST_F(SubdocXattrMultiMutationTest, XAttrMayBeFirst) {
393     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
394                          SUBDOC_FLAG_XATTR_PATH,
395                          "_sync.cas",
396                          "{\"foo\" : \"bar\"}"});
397     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
398                          SUBDOC_FLAG_NONE,
399                          "meta.author",
400                          "{\"name\" : \"Bubba\"}"});
401     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
402 }
403 
TEST_F(SubdocXattrMultiMutationTest, XAttrCantBeLast)404 TEST_F(SubdocXattrMultiMutationTest, XAttrCantBeLast) {
405     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
406                          SUBDOC_FLAG_NONE,
407                          "meta.author",
408                          "{\"name\" : \"Bubba\"}"});
409     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
410                          SUBDOC_FLAG_XATTR_PATH,
411                          "_sync.cas",
412                          "{\"foo\" : \"bar\"}"});
413     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_INVALID_XATTR_ORDER, validate());
414 }
415 
TEST_F(SubdocXattrMultiMutationTest, XAttrKeyIsChecked)416 TEST_F(SubdocXattrMultiMutationTest, XAttrKeyIsChecked) {
417     // We got other unit tests that tests all of the different restrictions
418     // for the subdoc key.. just make sure that it is actually called..
419     // Check that we can't insert a key > 16 chars
420     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
421                          SUBDOC_FLAG_XATTR_PATH,
422                          "ThisIsASuperDuperLongPath",
423                          "{\"foo\" : \"bar\"}"});
424     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_XATTR_EINVAL, validate());
425 }
426 
TEST_F(SubdocXattrMultiMutationTest, XattrFlagsMakeSense)427 TEST_F(SubdocXattrMultiMutationTest, XattrFlagsMakeSense) {
428     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
429                          SUBDOC_FLAG_XATTR_PATH,
430                          "_sync.cas",
431                          "\"${Mutation.CAS}\""});
432     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
433 
434     request[0].flags = SUBDOC_FLAG_EXPAND_MACROS;
435     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_INVALID_FLAG_COMBO,
436               validate());
437 
438     request[0].flags = SUBDOC_FLAG_EXPAND_MACROS | SUBDOC_FLAG_XATTR_PATH;
439     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
440 
441     request.addDocFlag(mcbp::subdoc::doc_flag::AccessDeleted);
442     request[0].flags = SUBDOC_FLAG_EXPAND_MACROS | SUBDOC_FLAG_XATTR_PATH;
443     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
444 
445     request[0].value = "${UnknownMacro}";
446     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_UNKNOWN_MACRO, validate());
447     request[0].value = "\"${Mutation.CAS}\"";
448 
449     // Let's try a valid access deleted flag
450     request[0].flags = SUBDOC_FLAG_NONE;
451     request.addDocFlag(mcbp::subdoc::doc_flag::AccessDeleted);
452     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
453               validate());
454 
455     // We should be able to access deleted docs if both flags are set
456     request[0].flags = SUBDOC_FLAG_XATTR_PATH;
457     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
458 }
459 
TEST_F(SubdocXattrMultiMutationTest, AllowMultipleMutations)460 TEST_F(SubdocXattrMultiMutationTest, AllowMultipleMutations) {
461     for (int ii = 0; ii < 10; ii++) {
462         request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
463                              SUBDOC_FLAG_XATTR_PATH,
464                              "_sync.cas",
465                              "{\"foo\" : \"bar\"}"});
466     }
467     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
468 }
469 
TEST_F(SubdocXattrMultiMutationTest, AllMutationsMustBeOnTheSamePath)470 TEST_F(SubdocXattrMultiMutationTest, AllMutationsMustBeOnTheSamePath) {
471     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
472                          SUBDOC_FLAG_XATTR_PATH,
473                          "_sync.cas",
474                          "{\"foo\" : \"bar\"}"});
475     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
476                          SUBDOC_FLAG_XATTR_PATH,
477                          "foo.bar",
478                          "{\"foo\" : \"bar\"}"});
479     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_XATTR_INVALID_KEY_COMBO,
480               validate());
481 }
482 
TEST_F(SubdocXattrMultiMutationTest, AllowXattrUpdateAndWholeDocDelete)483 TEST_F(SubdocXattrMultiMutationTest, AllowXattrUpdateAndWholeDocDelete) {
484     request.addMutation({PROTOCOL_BINARY_CMD_SUBDOC_REPLACE,
485                          SUBDOC_FLAG_XATTR_PATH,
486                          "_sync.cas",
487                          "{\"foo\" : \"bar\"}"});
488     request.addMutation({PROTOCOL_BINARY_CMD_DELETE, SUBDOC_FLAG_NONE, "", ""});
489     EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, validate());
490 }
491 } // namespace test
492 } // namespace mcbp
493