1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 * Copyright 2017 Couchbase, Inc
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include "collections/filter.h"
19 #include "collections/vbucket_filter.h"
20 #include "collections/vbucket_manifest.h"
22 #include "failover-table.h"
24 #include <gtest/gtest.h>
28 class CollectionsFilterTest : public ::testing::Test {
30 /// Dummy callback to replace the flusher callback so we can create VBuckets
31 class DummyCB : public Callback<uint16_t> {
36 void callback(uint16_t& dummy) {
40 CollectionsFilterTest()
50 std::make_shared<DummyCB>(),
51 /*newSeqnoCb*/ nullptr,
57 CheckpointConfig checkpoint_config;
63 * Test invalid inputs to the filter.
65 TEST_F(CollectionsFilterTest, junk_in) {
66 Collections::Manifest m(
67 R"({"revision":0,"separator":"::",)"
68 R"("collections":["$default", "vegetable"]})");
70 std::vector<std::string> inputs = {"{}",
71 R"({"collections":1})",
72 R"({"collections:"this"})",
73 R"({"collections:{"a":1})",
74 R"({"collection:["a"])",
75 R"({"collections:[a])"};
77 for (const auto& s : inputs) {
78 boost::optional<const std::string&> json = s;
79 EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
80 std::invalid_argument)
81 << "Failed for " << s;
86 * Test valid inputs to the filter.
88 TEST_F(CollectionsFilterTest, validation1) {
89 Collections::Manifest m(
90 R"({"revision":0,"separator":"::",)"
91 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
93 std::vector<std::string> inputs = {R"({"collections":["$default"]})",
94 R"({"collections":["vegetable"]})",
95 R"({"collections":["fruit", "meat"]})"};
97 for (const auto& s : inputs) {
98 boost::optional<const std::string&> json = s;
100 EXPECT_NO_THROW(std::make_unique<Collections::Filter>(json, m))
101 << "Exception thrown with input " << s;
106 * Test valid inputs to the filter, but they are not known collections, so
107 * should trigger an exception.
109 TEST_F(CollectionsFilterTest, validation2) {
110 Collections::Manifest m(
111 R"({"revision":0,"separator":"::",)"
112 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
114 std::vector<std::string> inputs = {R"({"collections":["cheese"]})",
115 R"({"collections":["fruit","beer"]})",
116 R"({"collections":["$dufault"]})"};
118 for (const auto& s : inputs) {
119 boost::optional<const std::string&> json = s;
120 EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
121 std::invalid_argument);
126 * Test that we cannot create default collection filter when no default
129 TEST_F(CollectionsFilterTest, validation_no_default) {
130 // m does not include $default
131 Collections::Manifest m(
132 R"({"revision":0,"separator":"::",)"
133 R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
135 boost::optional<const std::string&> json;
136 EXPECT_THROW(std::make_unique<Collections::Filter>(json, m),
141 * Construct a valid Collections::Filter and check its public methods
142 * This creates a filter which contains a set of collections
144 TEST_F(CollectionsFilterTest, filter_basic1) {
145 Collections::Manifest m(
146 R"({"revision":0,"separator":"$",)"
147 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
149 std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
150 boost::optional<const std::string&> json(jsonFilter);
151 Collections::Filter f(json, m);
153 // This is not a passthrough filter
154 EXPECT_FALSE(f.isPassthrough());
156 // But this filter would send the default
157 EXPECT_TRUE(f.allowDefaultCollection());
158 // and allow system events
159 EXPECT_TRUE(f.allowSystemEvents());
161 // The actual filter "list" only stores fruit and meat though, default is
162 // special cased via doesDefaultCollectionExist
163 EXPECT_EQ(2, f.getFilter().size());
165 auto list = f.getFilter();
166 EXPECT_TRUE(std::find(std::begin(list), std::end(list), "fruit") !=
168 EXPECT_TRUE(std::find(std::begin(list), std::end(list), "meat") !=
173 * Construct a valid Collections::Filter and check its public methods
174 * This creates a filter which is passthrough
176 TEST_F(CollectionsFilterTest, filter_basic2) {
177 Collections::Manifest m(
178 R"({"revision":0,"separator":"$",)"
179 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
181 std::string jsonFilter; // empty string creates a pass through
182 boost::optional<const std::string&> json(jsonFilter);
183 Collections::Filter f(json, m);
185 // This is a passthrough filter
186 EXPECT_TRUE(f.isPassthrough());
188 // So this filter would send the default
189 EXPECT_TRUE(f.allowDefaultCollection());
191 // and still allow system events
192 EXPECT_TRUE(f.allowSystemEvents());
194 // The actual filter "list" stores nothing
195 EXPECT_EQ(0, f.getFilter().size());
198 class CollectionsVBFilterTest : public CollectionsFilterTest {};
201 * Create a filter with collections and check we allow what should be allowed.
203 TEST_F(CollectionsVBFilterTest, basic_allow) {
204 Collections::Manifest m(
205 R"({"revision":0,"separator":"$",)"
206 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
208 std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
209 boost::optional<const std::string&> json(jsonFilter);
210 Collections::Filter f(json, m);
212 Collections::VB::Manifest vbm({});
213 vbm.wlock().update(vb, m);
215 Collections::VB::Filter vbf(f, vbm);
218 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
219 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
220 EXPECT_TRUE(vbf.allow({"meat$bacon", DocNamespace::Collections}));
223 EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
224 EXPECT_FALSE(vbf.allow({"vegetable$cabbage", DocNamespace::Collections}));
226 // There's no need yet to call the filter with DocKey's in system space, so
228 EXPECT_THROW(vbf.allow({"meat$bacon", DocNamespace::System}),
229 std::invalid_argument);
233 * Create a filter as if a legacy DCP connection would, i.e. the optional
234 * JSON filter is not initialised (because DCP open does not send a value).
236 TEST_F(CollectionsVBFilterTest, legacy_filter) {
237 Collections::Manifest m(
238 R"({"revision":0,"separator":"$","collections":["$default", "meat"]})");
240 boost::optional<const std::string&> json;
241 Collections::Filter f(json, m);
243 Collections::VB::Manifest vbm({});
244 vbm.wlock().update(vb, m);
246 Collections::VB::Filter vbf(f, vbm);
247 // Legacy would only allow default
248 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
249 EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
253 * Create a passthrough filter and check it allows anything
255 TEST_F(CollectionsVBFilterTest, passthrough) {
256 Collections::Manifest m(
257 R"({"revision":0,"separator":"$","collections":["meat"]})");
258 std::string filterJson; // empty string
259 boost::optional<const std::string&> json(filterJson);
260 Collections::Filter f(json, m);
262 Collections::VB::Manifest vbm({});
263 vbm.wlock().update(vb, m);
265 // Everything is allowed (even junk, which isn't the filter's job to police)
266 Collections::VB::Filter vbf(f, vbm);
267 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
268 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
269 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
270 EXPECT_TRUE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
271 EXPECT_TRUE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
275 * Create a filter which blocks the default collection
277 TEST_F(CollectionsVBFilterTest, no_default) {
278 Collections::Manifest m(
279 R"({"revision":0,"separator":"$",)"
280 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
281 Collections::VB::Manifest vbm({});
282 vbm.wlock().update(vb, m);
284 std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
285 boost::optional<const std::string&> json(jsonFilter);
286 Collections::Filter f(json, m);
289 Collections::VB::Filter vbf(f, vbm);
290 EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
291 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
292 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
293 EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
294 EXPECT_FALSE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
298 * Check we can remove collections from the filter (which live DCP may do)and
299 * check ::allow works as expected
301 TEST_F(CollectionsVBFilterTest, remove1) {
302 Collections::Manifest m(
303 R"({"revision":0,"separator":"$",)"
304 R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
305 Collections::VB::Manifest vbm({});
306 vbm.wlock().update(vb, m);
308 std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
309 boost::optional<const std::string&> json(jsonFilter);
311 Collections::Filter f(json, m);
312 Collections::VB::Filter vbf(f, vbm);
313 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
314 EXPECT_FALSE(vbf.remove("fruit"));
315 EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
317 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
318 EXPECT_TRUE(vbf.remove("meat"));
319 EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
323 * Check we can remove collections from the filter (which live DCP may do) and
324 * check ::allow works as expected
325 * This test includes checking we can remove $default
327 TEST_F(CollectionsVBFilterTest, remove2) {
328 Collections::Manifest m(
329 R"({"revision":0,"separator":"$",)"
330 R"("collections":["$default", "fruit", "meat", "dairy"]})");
331 Collections::VB::Manifest vbm({});
332 vbm.wlock().update(vb, m);
334 std::string jsonFilter = R"({"collections":["$default", "meat"]})";
335 boost::optional<const std::string&> json(jsonFilter);
337 Collections::Filter f(json, m);
338 Collections::VB::Filter vbf(f, vbm);
339 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
340 EXPECT_FALSE(vbf.remove("$default"));
341 EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
343 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
344 EXPECT_TRUE(vbf.remove("meat"));
345 EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
348 std::unique_ptr<SystemEventConsumerMessage> makeTestMessage(
349 const std::string name, SystemEvent ev, const int* rev) {
350 cb::const_byte_buffer n{reinterpret_cast<const uint8_t*>(name.data()),
352 cb::const_byte_buffer r{reinterpret_cast<const uint8_t*>(rev), sizeof(int)};
353 return std::make_unique<SystemEventConsumerMessage>(
354 0, ev, 0 /*seq*/, 0 /*vb*/, n, r);
358 * System events are checked by a different interface (allowSystemEvent)
359 * Test that a filter allows the right events, this is a passthrough filter
360 * so everything is allowed.
362 TEST_F(CollectionsVBFilterTest, system_events1) {
363 Collections::Manifest m(
364 R"({"revision":0,"separator":"$",)"
365 R"("collections":["$default", "fruit", "meat", "dairy"]})");
366 Collections::VB::Manifest vbm({});
367 vbm.wlock().update(vb, m);
369 std::string jsonFilter;
370 boost::optional<const std::string&> json(jsonFilter);
372 Collections::Filter f(json, m);
373 Collections::VB::Filter vbf(f, vbm);
376 // create and delete of meat is allowed by the meat filter
377 std::string name = "meat";
378 EXPECT_TRUE(vbf.allowSystemEvent(
379 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
380 EXPECT_TRUE(vbf.allowSystemEvent(
381 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
384 // create and delete of $default is allowed by the filter
386 EXPECT_TRUE(vbf.allowSystemEvent(
387 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
388 EXPECT_TRUE(vbf.allowSystemEvent(
389 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
392 // create and delete of dairy is not allowed by the filter
394 EXPECT_TRUE(vbf.allowSystemEvent(
395 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
396 EXPECT_TRUE(vbf.allowSystemEvent(
397 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
400 // A change of separator is also allowed
401 EXPECT_TRUE(vbf.allowSystemEvent(
403 name, SystemEvent::CollectionsSeparatorChanged, &rev)
408 * System events are checked by a different interface (allowSystemEvent)
409 * Test that a filter allows the right events
411 TEST_F(CollectionsVBFilterTest, system_events2) {
412 Collections::Manifest m(
413 R"({"revision":0,"separator":"$",)"
414 R"("collections":["$default", "fruit", "meat", "dairy"]})");
415 Collections::VB::Manifest vbm({});
416 vbm.wlock().update(vb, m);
418 std::string jsonFilter = R"({"collections":["$default", "meat"]})";
419 boost::optional<const std::string&> json(jsonFilter);
421 Collections::Filter f(json, m);
422 Collections::VB::Filter vbf(f, vbm);
425 // create and delete of meat is allowed by the meat filter
426 std::string name = "meat";
427 EXPECT_TRUE(vbf.allowSystemEvent(
428 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
429 EXPECT_TRUE(vbf.allowSystemEvent(
430 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
433 // create and delete of $default is allowed by the filter
435 EXPECT_TRUE(vbf.allowSystemEvent(
436 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
437 EXPECT_TRUE(vbf.allowSystemEvent(
438 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
441 // create and delete of dairy is not allowed by the filter
443 EXPECT_FALSE(vbf.allowSystemEvent(
444 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
445 EXPECT_FALSE(vbf.allowSystemEvent(
446 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
449 // A change of separator is also allowed
450 EXPECT_TRUE(vbf.allowSystemEvent(
452 name, SystemEvent::CollectionsSeparatorChanged, &rev)
457 * System events are checked by a different interface
458 * Test that a legacy filter denies all system events, they shouldn't be sent
461 TEST_F(CollectionsVBFilterTest, system_events3) {
462 Collections::Manifest m(
463 R"({"revision":0,"separator":"$",)"
464 R"("collections":["$default", "fruit", "meat", "dairy"]})");
465 Collections::VB::Manifest vbm({});
466 vbm.wlock().update(vb, m);
468 boost::optional<const std::string&> json;
470 Collections::Filter f(json, m);
471 Collections::VB::Filter vbf(f, vbm);
473 // All system events dropped by this empty/legacy filter
474 std::string name = "meat";
476 EXPECT_FALSE(vbf.allowSystemEvent(
477 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
478 EXPECT_FALSE(vbf.allowSystemEvent(
479 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
481 EXPECT_FALSE(vbf.allowSystemEvent(
483 name, SystemEvent::CollectionsSeparatorChanged, &rev)