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 class XattrTest : public TestappXattrClientTest {
25 public:
26     void SetUp() override {
27         TestappXattrClientTest::SetUp();
28 
29         // Create the document to operate on (with some compressible data).
30         document.info.id = name;
31         document.info.datatype = cb::mcbp::Datatype::Raw;
32         document.value =
33                 R"({"couchbase": {"version": "spock", "next_version": "vulcan"}})";
34         if (hasSnappySupport() == ClientSnappySupport::Yes) {
35             // Compress the complete body.
36             document.compress();
37         }
38         getConnection().mutate(document, 0, MutationType::Set);
39     }
40 
41 protected:
doArrayInsertTest(const std::string& path)42     void doArrayInsertTest(const std::string& path) {
43         auto resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_LAST,
44                            name, path, "\"Smith\"",
45                            SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
46         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
47 
48         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_INSERT,
49                       name, path + "[0]", "\"Bart\"",
50                       SUBDOC_FLAG_XATTR_PATH);
51         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
52 
53         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_INSERT,
54                       name, path + "[1]", "\"Jones\"",
55                       SUBDOC_FLAG_XATTR_PATH);
56         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
57 
58         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
59         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
60         EXPECT_EQ("[\"Bart\",\"Jones\",\"Smith\"]", resp.getValue());
61     }
62 
doArrayPushLastTest(const std::string& path)63     void doArrayPushLastTest(const std::string& path) {
64         auto resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_LAST,
65                            name, path, "\"Smith\"",
66                            SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
67         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
68 
69         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
70         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
71         EXPECT_EQ("[\"Smith\"]", resp.getValue());
72 
73         // Add a second one so we know it was added last ;-)
74         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_LAST,
75                       name, path, "\"Jones\"",
76                       SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
77         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
78 
79         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
80         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
81         EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
82     }
83 
doArrayPushFirstTest(const std::string& path)84     void doArrayPushFirstTest(const std::string& path) {
85         auto resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_FIRST,
86                            name, path, "\"Smith\"",
87                            SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
88         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
89 
90         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
91         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
92         EXPECT_EQ("[\"Smith\"]", resp.getValue());
93 
94         // Add a second one so we know it was added first ;-)
95         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_PUSH_FIRST,
96                       name, path, "\"Jones\"",
97                       SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
98         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
99 
100         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
101         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
102         EXPECT_EQ("[\"Jones\",\"Smith\"]", resp.getValue());
103     }
104 
doAddUniqueTest(const std::string& path)105     void doAddUniqueTest(const std::string& path) {
106         auto resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_ADD_UNIQUE,
107                            name, path, "\"Smith\"",
108                            SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
109         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
110 
111         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
112         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
113         EXPECT_EQ("[\"Smith\"]", resp.getValue());
114 
115         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_ADD_UNIQUE,
116                       name, path, "\"Jones\"",
117                       SUBDOC_FLAG_XATTR_PATH);
118         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
119 
120         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
121         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
122         EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
123 
124         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_ARRAY_ADD_UNIQUE,
125                       name, path, "\"Jones\"",
126                       SUBDOC_FLAG_XATTR_PATH);
127         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUBDOC_PATH_EEXISTS, resp.getStatus());
128 
129         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
130         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
131         EXPECT_EQ("[\"Smith\",\"Jones\"]", resp.getValue());
132 
133     }
134 
doCounterTest(const std::string& path)135     void doCounterTest(const std::string& path) {
136         auto resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_COUNTER,
137                            name, path, "1",
138                            SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
139         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
140 
141         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
142         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
143         EXPECT_EQ("1", resp.getValue());
144 
145         resp = subdoc(PROTOCOL_BINARY_CMD_SUBDOC_COUNTER,
146                       name, path, "1",
147                       SUBDOC_FLAG_XATTR_PATH | SUBDOC_FLAG_MKDIR_P);
148         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
149 
150         resp = subdoc_get(path, SUBDOC_FLAG_XATTR_PATH);
151         ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
152         EXPECT_EQ("2", resp.getValue());
153 
154     }
155 
156     // Test replacing a compressed/uncompressed value (based on test
157     // variant) with an compressed/uncompressed value (based on input
158     // paramter). XATTRs should be correctly merged.
doReplaceWithXattrTest(bool compress)159     void doReplaceWithXattrTest(bool compress) {
160         // Set initial body+XATTR, compressed depending on test variant.
161         setBodyAndXattr(value, {{sysXattr, xattrVal}});
162 
163         // Replace body with new body.
164         const std::string replacedValue = "\"JSON string\"";
165         document.value = replacedValue;
166         document.info.cas = mcbp::cas::Wildcard;
167         document.info.datatype = cb::mcbp::Datatype::Raw;
168         if (compress) {
169             document.compress();
170         }
171         getConnection().mutate(document, 0, MutationType::Replace);
172 
173         // Validate contents.
174         EXPECT_EQ(xattrVal, getXattr(sysXattr).getDataString());
175         auto response = getConnection().get(name, 0);
176         EXPECT_EQ(replacedValue, response.value);
177         // Combined result will not be compressed; so just check for
178         // JSON / not JSON.
179         EXPECT_EQ(expectedJSONDatatype(), response.info.datatype);
180     }
181 
subdoc_get( const std::string& path, protocol_binary_subdoc_flag flag = SUBDOC_FLAG_NONE, mcbp::subdoc::doc_flag docFlag = mcbp::subdoc::doc_flag::None)182     BinprotSubdocResponse subdoc_get(
183             const std::string& path,
184             protocol_binary_subdoc_flag flag = SUBDOC_FLAG_NONE,
185             mcbp::subdoc::doc_flag docFlag = mcbp::subdoc::doc_flag::None) {
186         return subdoc(
187                 PROTOCOL_BINARY_CMD_SUBDOC_GET, name, path, {}, flag, docFlag);
188     }
189 
190     /**
191      * Takes a subdoc multimutation command, sends it and checks that the
192      * values set correctly
193      * @param cmd The command to send
194      * @return Returns the response from the multi-mutation
195      */
testBodyAndXattrCmd( BinprotSubdocMultiMutationCommand& cmd)196     BinprotSubdocMultiMutationResponse testBodyAndXattrCmd(
197             BinprotSubdocMultiMutationCommand& cmd) {
198         auto& conn = getConnection();
199         conn.sendCommand(cmd);
200 
201         BinprotSubdocMultiMutationResponse multiResp;
202         conn.recvResponse(multiResp);
203         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, multiResp.getStatus());
204 
205         // Check the body was set correctly
206         auto doc = getConnection().get(name, 0);
207         EXPECT_EQ(value, doc.value);
208 
209         // Check the xattr was set correctly
210         auto resp = subdoc_get(sysXattr, SUBDOC_FLAG_XATTR_PATH);
211         EXPECT_EQ(xattrVal, resp.getValue());
212 
213         return multiResp;
214     }
215 
verify_xtoc_user_system_xattr()216     void verify_xtoc_user_system_xattr() {
217         // Test to check that we can get both an xattr and the main body in
218         // subdoc multi-lookup
219         setBodyAndXattr(value, {{sysXattr, xattrVal}});
220 
221         // Sanity checks and setup done lets try the multi-lookup
222 
223         BinprotSubdocMultiLookupCommand cmd;
224         cmd.setKey(name);
225         cmd.addGet("$XTOC", SUBDOC_FLAG_XATTR_PATH);
226         cmd.addLookup("", PROTOCOL_BINARY_CMD_GET, SUBDOC_FLAG_NONE);
227 
228         auto& conn = getConnection();
229         conn.sendCommand(cmd);
230 
231         BinprotSubdocMultiLookupResponse multiResp;
232         conn.recvResponse(multiResp);
233         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, multiResp.getStatus());
234         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
235                   multiResp.getResults()[0].status);
236         EXPECT_EQ(R"(["_sync"])", multiResp.getResults()[0].value);
237         EXPECT_EQ(value, multiResp.getResults()[1].value);
238 
239         xattr_upsert("userXattr", R"(["Test"])");
240         conn.sendCommand(cmd);
241         multiResp.clear();
242         conn.recvResponse(multiResp);
243         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, multiResp.getStatus());
244         EXPECT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS,
245                   multiResp.getResults()[0].status);
246         EXPECT_EQ(R"(["_sync","userXattr"])", multiResp.getResults()[0].value);
247     }
248 
249     std::string value = "{\"Field\":56}";
250     const std::string sysXattr = "_sync";
251     const std::string xattrVal = "{\"eg\":99}";
252 };
253 
254 /// Explicit text fixutre for tests which want Xattr support disabled.
255 class XattrDisabledTest : public XattrTest {};
256