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