1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2020 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
18/*
19 * Test extended attribute functionality related to the 'CreateAsDeleted'
20 * doc flag.
21 *
22 * Initial use-case for this doc flag is Transactions.
23 */
24
25#include "testapp_xattr.h"
26
27using namespace mcbp::subdoc;
28using namespace cb::mcbp;
29
30// Naive backport of GTEST_SKIP macro from v1.10. Remove once this is merged
31// to master branch (where we have gtest 1.10).
32#define GTEST_SKIP(message)                              \
33    do {                                                 \
34        fprintf(stderr, "Skipping test: " message "\n"); \
35        return;                                          \
36    } while (false)
37
38// Negative test: The subdoc operation returns Einval as
39// doc_flag::CreateAsDeleted requires one of doc_flag::Mkdoc/Add.
40void XattrNoDocTest::testRequiresMkdocOrAdd() {
41    auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocDictAdd,
42                       name,
43                       "txn.deleted",
44                       "true",
45                       SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P,
46                       doc_flag::CreateAsDeleted,
47                       durReqs);
48    EXPECT_EQ(cb::mcbp::Status::Einval, resp.getStatus());
49}
50
51TEST_P(XattrNoDocTest, RequiresMkdocOrAdd) {
52    testRequiresMkdocOrAdd();
53}
54
55TEST_P(XattrNoDocDurabilityTest, RequiresMkdocOrAdd) {
56    testRequiresMkdocOrAdd();
57}
58
59// Negative test: The Subdoc CreateAsDeleted doesn't allow to write in the
60// body.
61void XattrNoDocTest::testRequiresXattrPath() {
62    // Note: subdoc-flags doesn't set SUBDOC_FLAG_XATTR_PATH
63    auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocDictAdd,
64                       name,
65                       "txn.deleted",
66                       "true",
67                       SUBDOC_FLAG_MKDIR_P,
68                       doc_flag::Mkdoc | doc_flag::CreateAsDeleted);
69    EXPECT_EQ(cb::mcbp::Status::Einval, resp.getStatus());
70}
71
72TEST_P(XattrNoDocTest, RequiresXattrPath) {
73    testRequiresXattrPath();
74}
75
76TEST_P(XattrNoDocDurabilityTest, RequiresXattrPath) {
77    testRequiresXattrPath();
78}
79
80// Positive test: Can User XAttrs be added to a document which doesn't exist
81// (and doesn't have a tombstone) using the new CreateAsDeleted flag.
82void XattrNoDocTest::testSinglePathDictAdd() {
83    if (durReqs && !supportSyncRepl()) {
84        GTEST_SKIP("Requires SyncReplication support");
85    }
86
87    // let's add a user XATTR to a non-existing document
88    auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocDictAdd,
89                       name,
90                       "txn.deleted",
91                       "true",
92                       SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P,
93                       doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
94                       durReqs);
95    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
96
97    resp = subdoc_get(
98            "txn.deleted", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
99    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
100    EXPECT_EQ("true", resp.getValue());
101}
102
103TEST_P(XattrNoDocTest, SinglePathDictAdd) {
104    testSinglePathDictAdd();
105}
106
107TEST_P(XattrNoDocDurabilityTest, SinglePathDictAdd) {
108    testSinglePathDictAdd();
109}
110
111// Positive tests: Can User XAttrs be added to a document which doesn't exist
112// (and doesn't have a tombstone) using the new CreateAsDeleted flag, using
113// multi-mutation with each mutation opcode.
114void XattrNoDocTest::testMultipathDictAdd() {
115    if (durReqs && !supportSyncRepl()) {
116        GTEST_SKIP("Requires SyncReplication support");
117    }
118
119    BinprotSubdocMultiMutationCommand cmd(
120            name,
121            {{ClientOpcode::SubdocDictAdd,
122              SUBDOC_FLAG_XATTR_PATH,
123              "txn",
124              "\"foo\""}},
125            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
126            durReqs);
127
128    auto resp = subdocMultiMutation(cmd);
129    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
130
131    // Check the last path was created correctly.
132    resp = subdoc_get("txn", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
133    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
134    EXPECT_EQ("\"foo\"", resp.getValue());
135}
136
137TEST_P(XattrNoDocTest, MultipathDictAdd) {
138    testMultipathDictAdd();
139}
140
141TEST_P(XattrNoDocDurabilityTest, MultipathDictAdd) {
142    testMultipathDictAdd();
143}
144
145void XattrNoDocTest::testMultipathDictUpsert() {
146    if (durReqs && !supportSyncRepl()) {
147        GTEST_SKIP("Requires SyncReplication support");
148    }
149
150    BinprotSubdocMultiMutationCommand cmd(
151            name,
152            {{ClientOpcode::SubdocDictUpsert,
153              SUBDOC_FLAG_XATTR_PATH,
154              "txn",
155              "\"bar\""}},
156            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
157            durReqs);
158
159    auto resp = subdocMultiMutation(cmd);
160    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
161
162    resp = subdoc_get("txn", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
163    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
164    EXPECT_EQ("\"bar\"", resp.getValue());
165}
166
167TEST_P(XattrNoDocTest, MultipathDictUpsert) {
168    testMultipathDictUpsert();
169}
170
171TEST_P(XattrNoDocDurabilityTest, MultipathDictUpsert) {
172    testMultipathDictUpsert();
173}
174
175void XattrNoDocTest::testMultipathArrayPushLast() {
176    if (durReqs && !supportSyncRepl()) {
177        GTEST_SKIP("Requires SyncReplication support");
178    }
179
180    BinprotSubdocMultiMutationCommand cmd(
181            name,
182            {{ClientOpcode::SubdocArrayPushLast,
183              SUBDOC_FLAG_XATTR_PATH,
184              "array",
185              "1"}},
186            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
187            durReqs);
188
189    auto resp = subdocMultiMutation(cmd);
190    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
191
192    resp = subdoc_get("array", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
193    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
194    EXPECT_EQ("[1]", resp.getValue());
195}
196
197TEST_P(XattrNoDocTest, MultipathArrayPushLast) {
198    testMultipathArrayPushLast();
199}
200
201TEST_P(XattrNoDocDurabilityTest, MultipathArrayPushLast) {
202    testMultipathArrayPushLast();
203}
204
205void XattrNoDocTest::testMultipathArrayPushFirst() {
206    if (durReqs && !supportSyncRepl()) {
207        GTEST_SKIP("Requires SyncReplication support");
208    }
209
210    BinprotSubdocMultiMutationCommand cmd(
211            name,
212            {{ClientOpcode::SubdocArrayPushFirst,
213              SUBDOC_FLAG_XATTR_PATH,
214              "array",
215              "2"}},
216            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
217            durReqs);
218
219    auto resp = subdocMultiMutation(cmd);
220    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
221
222    resp = subdoc_get("array", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
223    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
224    EXPECT_EQ("[2]", resp.getValue());
225}
226
227TEST_P(XattrNoDocTest, MultipathArrayPushFirst) {
228    testMultipathArrayPushFirst();
229}
230
231TEST_P(XattrNoDocDurabilityTest, MultipathArrayPushFirst) {
232    testMultipathArrayPushFirst();
233}
234
235// @todo MB-39545: This test fails because doc_flag::Mkdoc is invalid with
236//  ArrayInsert (the test fails at validation for ArrayInsert with "Invalid
237//  arguments").
238//  By changing to doc_flag::Add (valid for ArrayInsert) the test gets to the
239//  execution of ArrayInsert but is fails with SubdocMultiPathFailure.
240//  Specific and simple (single-path) tests on ArrayInsert suggests that we may
241//  have some existing issue in that area that we should address in a dedicated
242//  patch.
243TEST_P(XattrNoDocTest, DISABLED_MultipathArrayInsert) {
244    BinprotSubdocMultiMutationCommand cmd(
245            name,
246            {{ClientOpcode::SubdocArrayPushFirst,
247              SUBDOC_FLAG_XATTR_PATH,
248              "array",
249              "0"},
250             {ClientOpcode::SubdocArrayInsert,
251              SUBDOC_FLAG_XATTR_PATH,
252              "array.[0]",
253              "1"}},
254            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
255            durReqs);
256
257    auto resp = subdocMultiMutation(cmd);
258    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
259
260    resp = subdoc_get("array", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
261    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
262    EXPECT_EQ("[0,1]", resp.getValue());
263}
264
265void XattrNoDocTest::testMultipathArrayAddUnique() {
266    if (durReqs && !supportSyncRepl()) {
267        GTEST_SKIP("Requires SyncReplication support");
268    }
269
270    BinprotSubdocMultiMutationCommand cmd(
271            name,
272            {{ClientOpcode::SubdocArrayAddUnique,
273              SUBDOC_FLAG_XATTR_PATH,
274              "array",
275              "4"}},
276            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
277            durReqs);
278
279    auto resp = subdocMultiMutation(cmd);
280    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
281
282    resp = subdoc_get("array", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
283    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
284    EXPECT_EQ("[4]", resp.getValue());
285}
286
287TEST_P(XattrNoDocTest, MultipathArrayAddUnique) {
288    testMultipathArrayAddUnique();
289}
290
291TEST_P(XattrNoDocDurabilityTest, MultipathArrayAddUnique) {
292    testMultipathArrayAddUnique();
293}
294
295void XattrNoDocTest::testMultipathCounter() {
296    if (durReqs && !supportSyncRepl()) {
297        GTEST_SKIP("Requires SyncReplication support");
298    }
299
300    BinprotSubdocMultiMutationCommand cmd(
301            name,
302            {{ClientOpcode::SubdocCounter,
303              SUBDOC_FLAG_XATTR_PATH,
304              "counter",
305              "5"}},
306            doc_flag::Mkdoc | doc_flag::CreateAsDeleted,
307            durReqs);
308
309    auto resp = subdocMultiMutation(cmd);
310    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
311
312    // Check the last path was created correctly.
313    resp = subdoc_get(
314            "counter", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
315    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
316    EXPECT_EQ("5", resp.getValue());
317}
318
319TEST_P(XattrNoDocTest, MultipathCounter) {
320    testMultipathCounter();
321}
322
323TEST_P(XattrNoDocDurabilityTest, MultipathCounter) {
324    testMultipathCounter();
325}
326
327// Positive test: Can User XAttrs be added to a document which doesn't exist
328// using the new CreateAsDeleted flag, using a combination of subdoc-multi
329// mutation types.
330void XattrNoDocTest::testMultipathCombo() {
331    if (durReqs && !supportSyncRepl()) {
332        GTEST_SKIP("Requires SyncReplication support");
333    }
334
335    auto cmd = makeSDKTxnMultiMutation();
336    cmd.addDocFlag(doc_flag::Mkdoc);
337    cmd.addDocFlag(doc_flag::CreateAsDeleted);
338
339    if (durReqs) {
340        cmd.setDurabilityReqs(*durReqs);
341    }
342
343    auto& conn = getConnection();
344    conn.sendCommand(cmd);
345    BinprotSubdocResponse resp;
346    conn.recvResponse(resp);
347    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
348
349    // Check the last path was created correctly.
350    resp = subdoc_get(
351            "txn.counter", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
352    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
353    EXPECT_EQ("1", resp.getValue());
354}
355
356TEST_P(XattrNoDocTest, MultipathCombo) {
357    testMultipathCombo();
358}
359
360TEST_P(XattrNoDocDurabilityTest, MultipathCombo) {
361    testMultipathCombo();
362}
363
364// Positive test: Can User XAttrs be added to a document which doesn't exist
365// (and doesn't have a tombstone) using the new CreateAsDeleted flag alongside
366// AccessDeleted (to check for an existing tombstone).
367// This is also a regression test for MB-40162.
368void XattrNoDocTest::testMultipathAccessDeletedCreateAsDeleted() {
369    if (durReqs && !supportSyncRepl()) {
370        GTEST_SKIP("Requires SyncReplication support");
371    }
372
373    auto cmd = makeSDKTxnMultiMutation();
374
375    cmd.addDocFlag(doc_flag::Add);
376    cmd.addDocFlag(doc_flag::AccessDeleted);
377    cmd.addDocFlag(doc_flag::CreateAsDeleted);
378
379    if (durReqs) {
380        cmd.setDurabilityReqs(*durReqs);
381    }
382
383    auto& conn = getConnection();
384    conn.sendCommand(cmd);
385    BinprotSubdocResponse resp;
386    conn.recvResponse(resp);
387    EXPECT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
388
389    // Check the last path was created correctly.
390    resp = subdoc_get(
391            "txn.counter", SUBDOC_FLAG_XATTR_PATH, doc_flag::AccessDeleted);
392    ASSERT_EQ(cb::mcbp::Status::SubdocSuccessDeleted, resp.getStatus());
393    EXPECT_EQ("1", resp.getValue());
394}
395
396TEST_P(XattrNoDocTest, MultipathAccessDeletedCreateAsDeleted) {
397    testMultipathAccessDeletedCreateAsDeleted();
398}
399
400TEST_P(XattrNoDocDurabilityTest, MultipathAccessDeletedCreateAsDeleted) {
401    testMultipathAccessDeletedCreateAsDeleted();
402}
403