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#pragma once
18
19#include "testapp_client_test.h"
20#include "xattr/blob.h"
21
22#include <platform/cb_malloc.h>
23
24/**
25 * Text fixture for XAttr tests which do not want to have an initial document
26 * created.
27 */
28class XattrNoDocTest : public TestappXattrClientTest {
29protected:
30    void SetUp() override {
31        TestappXattrClientTest::SetUp();
32    }
33
34    BinprotSubdocResponse subdoc_get(
35            const std::string& path,
36            protocol_binary_subdoc_flag flag = SUBDOC_FLAG_NONE,
37            mcbp::subdoc::doc_flag docFlag = mcbp::subdoc::doc_flag::None) {
38        return subdoc(cb::mcbp::ClientOpcode::SubdocGet,
39                      name,
40                      path,
41                      {},
42                      flag,
43                      docFlag);
44    }
45
46    BinprotSubdocMultiLookupResponse subdoc_multi_lookup(
47            std::vector<BinprotSubdocMultiLookupCommand::LookupSpecifier> specs,
48            mcbp::subdoc::doc_flag docFlags = mcbp::subdoc::doc_flag::None);
49
50    BinprotSubdocMultiMutationResponse subdoc_multi_mutation(
51            std::vector<BinprotSubdocMultiMutationCommand::MutationSpecifier>
52                    specs,
53            mcbp::subdoc::doc_flag docFlags = mcbp::subdoc::doc_flag::None);
54
55    GetMetaResponse get_meta();
56
57    bool supportSyncRepl() const {
58        return mcd_env->getTestBucket().supportsSyncWrites();
59    }
60
61    /// Constructs a Subdoc multi-mutation matching the style of an SDK
62    // Transactions mutation.
63    BinprotSubdocMultiMutationCommand makeSDKTxnMultiMutation() const;
64
65    void testRequiresMkdocOrAdd();
66    void testRequiresXattrPath();
67    void testSinglePathDictAdd();
68    void testMultipathDictAdd();
69    void testMultipathDictUpsert();
70    void testMultipathArrayPushLast();
71    void testMultipathArrayPushFirst();
72    void testMultipathArrayAddUnique();
73    void testMultipathCounter();
74    void testMultipathCombo();
75    void testMultipathAccessDeletedCreateAsDeleted();
76
77    boost::optional<cb::durability::Requirements> durReqs = {};
78};
79
80class XattrNoDocDurabilityTest : public XattrNoDocTest {
81protected:
82    void SetUp() override {
83        XattrNoDocTest::SetUp();
84        // level:majority, timeout:default
85        durReqs = cb::durability::Requirements();
86    }
87};
88
89class XattrTest : public XattrNoDocTest {
90protected:
91    void SetUp() override {
92        XattrNoDocTest::SetUp();
93
94        // Create the document to operate on (with some compressible data).
95        document.info.id = name;
96        document.info.datatype = cb::mcbp::Datatype::Raw;
97        document.value =
98                R"({"couchbase": {"version": "spock", "next_version": "vulcan"}})";
99        if (hasSnappySupport() == ClientSnappySupport::Yes) {
100            // Compress the complete body.
101            document.compress();
102        }
103        getConnection().mutate(document, Vbid(0), MutationType::Set);
104    }
105
106protected:
107    void doArrayInsertTest(const std::string& path) {
108        auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayPushLast,
109                           name,
110                           path,
111                           "\"Smith\"",
112                           SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
113        EXPECT_EQ(cb::mcbp::Status::Success, resp.getStatus());
114
115        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayInsert,
116                      name,
117                      path + "[0]",
118                      "\"Bart\"",
119                      SUBDOC_FLAG_XATTR_PATH);
120        EXPECT_EQ(cb::mcbp::Status::Success, resp.getStatus());
121
122        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayInsert,
123                      name,
124                      path + "[1]",
125                      "\"Jones\"",
126                      SUBDOC_FLAG_XATTR_PATH);
127        EXPECT_EQ(cb::mcbp::Status::Success, resp.getStatus());
128
129        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
130        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
131        EXPECT_EQ("[\"Bart\",\"Jones\",\"Smith\"]", resp.getValue());
132    }
133
134    void doArrayPushLastTest(const std::string& path) {
135        auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayPushLast,
136                           name,
137                           path,
138                           "\"Smith\"",
139                           SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
140        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
141
142        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
143        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
144        EXPECT_EQ("[\"Smith\"]", resp.getValue());
145
146        // Add a second one so we know it was added last ;-)
147        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayPushLast,
148                      name,
149                      path,
150                      "\"Jones\"",
151                      SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
152        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
153
154        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
155        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
156        EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
157    }
158
159    void doArrayPushFirstTest(const std::string& path) {
160        auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayPushFirst,
161                           name,
162                           path,
163                           "\"Smith\"",
164                           SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
165        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
166
167        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
168        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
169        EXPECT_EQ("[\"Smith\"]", resp.getValue());
170
171        // Add a second one so we know it was added first ;-)
172        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayPushFirst,
173                      name,
174                      path,
175                      "\"Jones\"",
176                      SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
177        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
178
179        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
180        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
181        EXPECT_EQ("[\"Jones\",\"Smith\"]", resp.getValue());
182    }
183
184    void doAddUniqueTest(const std::string& path) {
185        auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayAddUnique,
186                           name,
187                           path,
188                           "\"Smith\"",
189                           SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
190        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
191
192        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
193        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
194        EXPECT_EQ("[\"Smith\"]", resp.getValue());
195
196        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayAddUnique,
197                      name,
198                      path,
199                      "\"Jones\"",
200                      SUBDOC_FLAG_XATTR_PATH);
201        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
202
203        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
204        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
205        EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
206
207        resp = subdoc(cb::mcbp::ClientOpcode::SubdocArrayAddUnique,
208                      name,
209                      path,
210                      "\"Jones\"",
211                      SUBDOC_FLAG_XATTR_PATH);
212        ASSERT_EQ(cb::mcbp::Status::SubdocPathEexists, resp.getStatus());
213
214        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
215        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
216        EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
217
218    }
219
220    void doCounterTest(const std::string& path) {
221        auto resp = subdoc(cb::mcbp::ClientOpcode::SubdocCounter,
222                           name,
223                           path,
224                           "1",
225                           SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
226        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
227
228        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
229        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
230        EXPECT_EQ("1", resp.getValue());
231
232        resp = subdoc(cb::mcbp::ClientOpcode::SubdocCounter,
233                      name,
234                      path,
235                      "1",
236                      SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
237        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
238
239        resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
240        ASSERT_EQ(cb::mcbp::Status::Success, resp.getStatus());
241        EXPECT_EQ("2", resp.getValue());
242
243    }
244
245    // Test replacing a compressed/uncompressed value (based on test
246    // variant) with an compressed/uncompressed value (based on input
247    // paramter). XATTRs should be correctly merged.
248    void doReplaceWithXattrTest(bool compress) {
249        // Set initial body+XATTR, compressed depending on test variant.
250        setBodyAndXattr(value, {{sysXattr, xattrVal}});
251
252        // Replace body with new body.
253        const std::string replacedValue = "\"JSON string\"";
254        document.value = replacedValue;
255        document.info.cas = mcbp::cas::Wildcard;
256        document.info.datatype = cb::mcbp::Datatype::Raw;
257        if (compress) {
258            document.compress();
259        }
260        getConnection().mutate(document, Vbid(0), MutationType::Replace);
261
262        // Validate contents.
263        EXPECT_EQ(xattrVal, getXattr(sysXattr).getDataString());
264        auto response = getConnection().get(name, Vbid(0));
265        EXPECT_EQ(replacedValue, response.value);
266        // Combined result will not be compressed; so just check for
267        // JSON / not JSON.
268        EXPECT_EQ(expectedJSONDatatype(), response.info.datatype);
269    }
270
271    /**
272     * Takes a subdoc multimutation command, sends it and checks that the
273     * values set correctly
274     * @param cmd The command to send
275     * @param expectedDatatype The expected datatype of the resulting document.
276     * @return Returns the response from the multi-mutation
277     */
278    BinprotSubdocMultiMutationResponse testBodyAndXattrCmd(
279            BinprotSubdocMultiMutationCommand& cmd,
280            protocol_binary_datatype_t expectedDatatype =
281                    PROTOCOL_BINARY_DATATYPE_JSON |
282                    PROTOCOL_BINARY_DATATYPE_XATTR) {
283        auto& conn = getConnection();
284        conn.sendCommand(cmd);
285
286        BinprotSubdocMultiMutationResponse multiResp;
287        conn.recvResponse(multiResp);
288        EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getStatus());
289
290        // Check the body was set correctly
291        auto doc = getConnection().get(name, Vbid(0));
292        EXPECT_EQ(value, doc.value);
293
294        // Check the xattr was set correctly
295        auto resp = subdoc_get(sysXattr, SUBDOC_FLAG_XATTR_PATH);
296        EXPECT_EQ(xattrVal, resp.getValue());
297
298        // Check the datatype.
299        auto meta = get_meta();
300        EXPECT_EQ(expectedDatatype, meta.datatype);
301
302        return multiResp;
303    }
304
305    void verify_xtoc_user_system_xattr() {
306        // Test to check that we can get both an xattr and the main body in
307        // subdoc multi-lookup
308        setBodyAndXattr(value, {{sysXattr, xattrVal}});
309
310        // Sanity checks and setup done lets try the multi-lookup
311
312        BinprotSubdocMultiLookupCommand cmd;
313        cmd.setKey(name);
314        cmd.addGet("$XTOC", SUBDOC_FLAG_XATTR_PATH);
315        cmd.addLookup("", cb::mcbp::ClientOpcode::Get, SUBDOC_FLAG_NONE);
316
317        auto& conn = getConnection();
318        conn.sendCommand(cmd);
319
320        BinprotSubdocMultiLookupResponse multiResp;
321        conn.recvResponse(multiResp);
322        EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getStatus());
323        EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getResults()[0].status);
324        EXPECT_EQ(R"(["_sync"])", multiResp.getResults()[0].value);
325        EXPECT_EQ(value, multiResp.getResults()[1].value);
326
327        xattr_upsert("userXattr", R"(["Test"])");
328        conn.sendCommand(cmd);
329        multiResp.clear();
330        conn.recvResponse(multiResp);
331        EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getStatus());
332        EXPECT_EQ(cb::mcbp::Status::Success, multiResp.getResults()[0].status);
333        EXPECT_EQ(R"(["_sync","userXattr"])", multiResp.getResults()[0].value);
334    }
335
336    std::string value = "{\"Field\":56}";
337    const std::string sysXattr = "_sync";
338    const std::string xattrVal = "{\"eg\":99}";
339};
340
341/// Explicit text fixutre for tests which want Xattr support disabled.
342class XattrDisabledTest : public XattrTest {};
343