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 * Try and create filter for collections which exist, but have been deleted
202 * i.e. they aren't writable so should never feature in a new VB::Filter
204 TEST_F(CollectionsVBFilterTest, deleted_collection) {
205 Collections::Manifest m1(
206 R"({"revision":0,"separator":"$",)"
207 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
208 Collections::Manifest m2(
209 R"({"revision":1,"separator":"$",)"
210 R"("collections":["$default", "meat", "dairy"]})");
212 // Create the "producer" level filter so that we in theory produce at least
214 std::string jsonFilter = R"({"collections":["vegetable", "fruit"]})";
215 boost::optional<const std::string&> json(jsonFilter);
216 Collections::Filter f(json, m1);
218 Collections::VB::Manifest vbm({});
220 vbm.wlock().update(vb, m1);
221 // push deletes, removing both filtered collections
222 vbm.wlock().update(vb, m2);
224 // Construction will fail as the filter would not match anything valid
225 EXPECT_THROW(std::make_unique<Collections::VB::Filter>(f, vbm),
226 std::invalid_argument);
230 * Create a filter with collections and check we allow what should be allowed.
232 TEST_F(CollectionsVBFilterTest, basic_allow) {
233 Collections::Manifest m(
234 R"({"revision":0,"separator":"$",)"
235 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
237 std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
238 boost::optional<const std::string&> json(jsonFilter);
239 Collections::Filter f(json, m);
241 Collections::VB::Manifest vbm({});
242 vbm.wlock().update(vb, m);
244 Collections::VB::Filter vbf(f, vbm);
247 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
248 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
249 EXPECT_TRUE(vbf.allow({"meat$bacon", DocNamespace::Collections}));
252 EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
253 EXPECT_FALSE(vbf.allow({"vegetable$cabbage", DocNamespace::Collections}));
255 // There's no need yet to call the filter with DocKey's in system space, so
257 EXPECT_THROW(vbf.allow({"meat$bacon", DocNamespace::System}),
258 std::invalid_argument);
262 * Create a filter as if a legacy DCP connection would, i.e. the optional
263 * JSON filter is not initialised (because DCP open does not send a value).
265 TEST_F(CollectionsVBFilterTest, legacy_filter) {
266 Collections::Manifest m(
267 R"({"revision":0,"separator":"$","collections":["$default", "meat"]})");
269 boost::optional<const std::string&> json;
270 Collections::Filter f(json, m);
272 Collections::VB::Manifest vbm({});
273 vbm.wlock().update(vb, m);
275 Collections::VB::Filter vbf(f, vbm);
276 // Legacy would only allow default
277 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
278 EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
282 * Create a passthrough filter and check it allows anything
284 TEST_F(CollectionsVBFilterTest, passthrough) {
285 Collections::Manifest m(
286 R"({"revision":0,"separator":"$","collections":["meat"]})");
287 std::string filterJson; // empty string
288 boost::optional<const std::string&> json(filterJson);
289 Collections::Filter f(json, m);
291 Collections::VB::Manifest vbm({});
292 vbm.wlock().update(vb, m);
294 // Everything is allowed (even junk, which isn't the filter's job to police)
295 Collections::VB::Filter vbf(f, vbm);
296 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
297 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
298 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
299 EXPECT_TRUE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
300 EXPECT_TRUE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
304 * Create a filter which blocks the default collection
306 TEST_F(CollectionsVBFilterTest, no_default) {
307 Collections::Manifest m(
308 R"({"revision":0,"separator":"$",)"
309 R"("collections":["$default", "vegetable", "fruit", "meat", "dairy"]})");
310 Collections::VB::Manifest vbm({});
311 vbm.wlock().update(vb, m);
313 std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
314 boost::optional<const std::string&> json(jsonFilter);
315 Collections::Filter f(json, m);
318 Collections::VB::Filter vbf(f, vbm);
319 EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
320 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
321 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
322 EXPECT_FALSE(vbf.allow({"dairy$milk", DocNamespace::Collections}));
323 EXPECT_FALSE(vbf.allow({"JUNK!!", DocNamespace::Collections}));
327 * Check we can remove collections from the filter (which live DCP may do)and
328 * check ::allow works as expected
330 TEST_F(CollectionsVBFilterTest, remove1) {
331 Collections::Manifest m(
332 R"({"revision":0,"separator":"$",)"
333 R"("collections":["vegetable", "fruit", "meat", "dairy"]})");
334 Collections::VB::Manifest vbm({});
335 vbm.wlock().update(vb, m);
337 std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
338 boost::optional<const std::string&> json(jsonFilter);
340 Collections::Filter f(json, m);
341 Collections::VB::Filter vbf(f, vbm);
342 EXPECT_TRUE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
343 EXPECT_FALSE(vbf.remove("fruit"));
344 EXPECT_FALSE(vbf.allow({"fruit$apple", DocNamespace::Collections}));
346 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
347 EXPECT_TRUE(vbf.remove("meat"));
348 EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
352 * Check we can remove collections from the filter (which live DCP may do) and
353 * check ::allow works as expected
354 * This test includes checking we can remove $default
356 TEST_F(CollectionsVBFilterTest, remove2) {
357 Collections::Manifest m(
358 R"({"revision":0,"separator":"$",)"
359 R"("collections":["$default", "fruit", "meat", "dairy"]})");
360 Collections::VB::Manifest vbm({});
361 vbm.wlock().update(vb, m);
363 std::string jsonFilter = R"({"collections":["$default", "meat"]})";
364 boost::optional<const std::string&> json(jsonFilter);
366 Collections::Filter f(json, m);
367 Collections::VB::Filter vbf(f, vbm);
368 EXPECT_TRUE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
369 EXPECT_FALSE(vbf.remove("$default"));
370 EXPECT_FALSE(vbf.allow({"anykey", DocNamespace::DefaultCollection}));
372 EXPECT_TRUE(vbf.allow({"meat$steak", DocNamespace::Collections}));
373 EXPECT_TRUE(vbf.remove("meat"));
374 EXPECT_FALSE(vbf.allow({"meat$apple", DocNamespace::Collections}));
377 std::unique_ptr<SystemEventConsumerMessage> makeTestMessage(
378 const std::string name, SystemEvent ev, const int* rev) {
379 cb::const_byte_buffer n{reinterpret_cast<const uint8_t*>(name.data()),
381 cb::const_byte_buffer r{reinterpret_cast<const uint8_t*>(rev), sizeof(int)};
382 return std::make_unique<SystemEventConsumerMessage>(
383 0, ev, 0 /*seq*/, 0 /*vb*/, n, r);
387 * System events are checked by a different interface (allowSystemEvent)
388 * Test that a filter allows the right events, this is a passthrough filter
389 * so everything is allowed.
391 TEST_F(CollectionsVBFilterTest, system_events1) {
392 Collections::Manifest m(
393 R"({"revision":0,"separator":"$",)"
394 R"("collections":["$default", "fruit", "meat", "dairy"]})");
395 Collections::VB::Manifest vbm({});
396 vbm.wlock().update(vb, m);
398 std::string jsonFilter;
399 boost::optional<const std::string&> json(jsonFilter);
401 Collections::Filter f(json, m);
402 Collections::VB::Filter vbf(f, vbm);
405 // create and delete of meat is allowed by the meat filter
406 std::string name = "meat";
407 EXPECT_TRUE(vbf.allowSystemEvent(
408 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
409 EXPECT_TRUE(vbf.allowSystemEvent(
410 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
413 // create and delete of $default is allowed by the filter
415 EXPECT_TRUE(vbf.allowSystemEvent(
416 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
417 EXPECT_TRUE(vbf.allowSystemEvent(
418 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
421 // create and delete of dairy is not allowed by the filter
423 EXPECT_TRUE(vbf.allowSystemEvent(
424 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
425 EXPECT_TRUE(vbf.allowSystemEvent(
426 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
429 // A change of separator is also allowed
430 EXPECT_TRUE(vbf.allowSystemEvent(
432 name, SystemEvent::CollectionsSeparatorChanged, &rev)
437 * System events are checked by a different interface (allowSystemEvent)
438 * Test that a filter allows the right events
440 TEST_F(CollectionsVBFilterTest, system_events2) {
441 Collections::Manifest m(
442 R"({"revision":0,"separator":"$",)"
443 R"("collections":["$default", "fruit", "meat", "dairy"]})");
444 Collections::VB::Manifest vbm({});
445 vbm.wlock().update(vb, m);
447 std::string jsonFilter = R"({"collections":["$default", "meat"]})";
448 boost::optional<const std::string&> json(jsonFilter);
450 Collections::Filter f(json, m);
451 Collections::VB::Filter vbf(f, vbm);
454 // create and delete of meat is allowed by the meat filter
455 std::string name = "meat";
456 EXPECT_TRUE(vbf.allowSystemEvent(
457 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
458 EXPECT_TRUE(vbf.allowSystemEvent(
459 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
462 // create and delete of $default is allowed by the filter
464 EXPECT_TRUE(vbf.allowSystemEvent(
465 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
466 EXPECT_TRUE(vbf.allowSystemEvent(
467 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
470 // create and delete of dairy is not allowed by the filter
472 EXPECT_FALSE(vbf.allowSystemEvent(
473 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
474 EXPECT_FALSE(vbf.allowSystemEvent(
475 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
478 // A change of separator is also allowed
479 EXPECT_TRUE(vbf.allowSystemEvent(
481 name, SystemEvent::CollectionsSeparatorChanged, &rev)
486 * System events are checked by a different interface
487 * Test that a legacy filter denies all system events, they shouldn't be sent
490 TEST_F(CollectionsVBFilterTest, system_events3) {
491 Collections::Manifest m(
492 R"({"revision":0,"separator":"$",)"
493 R"("collections":["$default", "fruit", "meat", "dairy"]})");
494 Collections::VB::Manifest vbm({});
495 vbm.wlock().update(vb, m);
497 boost::optional<const std::string&> json;
499 Collections::Filter f(json, m);
500 Collections::VB::Filter vbf(f, vbm);
502 // All system events dropped by this empty/legacy filter
503 std::string name = "meat";
505 EXPECT_FALSE(vbf.allowSystemEvent(
506 makeTestMessage(name, SystemEvent::CreateCollection, &rev).get()));
507 EXPECT_FALSE(vbf.allowSystemEvent(
508 makeTestMessage(name, SystemEvent::BeginDeleteCollection, &rev)
510 EXPECT_FALSE(vbf.allowSystemEvent(
512 name, SystemEvent::CollectionsSeparatorChanged, &rev)