1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 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 
18 #include "collections/filter.h"
19 #include "collections/vbucket_filter.h"
20 #include "collections/vbucket_manifest.h"
21 #include "ep_vb.h"
22 #include "failover-table.h"
23 
24 #include <gtest/gtest.h>
25 
26 #include <limits>
27 
28 class CollectionsFilterTest : public ::testing::Test {
29 public:
30     /// Dummy callback to replace the flusher callback so we can create VBuckets
31     class DummyCB : public Callback<uint16_t> {
32     public:
DummyCB()33         DummyCB() {
34         }
35 
callback(uint16_t& dummy)36         void callback(uint16_t& dummy) {
37         }
38     };
39 
CollectionsFilterTest()40     CollectionsFilterTest()
41         : vb(0,
42              vbucket_state_active,
43              global_stats,
44              checkpoint_config,
45              /*kvshard*/ nullptr,
46              /*lastSeqno*/ 0,
47              /*lastSnapStart*/ 0,
48              /*lastSnapEnd*/ 0,
49              /*table*/ nullptr,
50              std::make_shared<DummyCB>(),
51              /*newSeqnoCb*/ nullptr,
52              config,
53              VALUE_ONLY) {
54     }
55 
56     EPStats global_stats;
57     CheckpointConfig checkpoint_config;
58     Configuration config;
59     EPVBucket vb;
60 };
61 
62 /**
63  * Test invalid JSON formats as an input
64  */
TEST_F(CollectionsFilterTest, junk_in)65 TEST_F(CollectionsFilterTest, junk_in) {
66     Collections::Manifest m(
67             R"({"separator":":","uid":"0",)"
68             R"("collections":[{"name":"$default","uid":"0"},
69                               {"name":"vegetable","uid":"1"}]})");
70 
71     std::vector<std::string> inputs = {"{}",
72                                        R"({"collections":1})",
73                                        R"({"collections:"this"})",
74                                        R"({"collections:{"a":1})",
75                                        R"({"collection:["a"])",
76                                        R"({"collections:[a])"};
77 
78     for (const auto& s : inputs) {
79         boost::optional<const std::string&> json = s;
80         try {
81             Collections::Filter f(json, &m);
82             FAIL() << "Should of thrown an exception";
83         } catch (const cb::engine_error& e) {
84             EXPECT_EQ(cb::engine_errc::invalid_arguments, e.code());
85         } catch (...) {
86             FAIL() << "Should of thrown cb::engine_error";
87         }
88     }
89 }
90 
91 /**
92  * Test valid inputs to the filter.
93  */
94 TEST_F(CollectionsFilterTest, validation1) {
95     Collections::Manifest m(
96             R"({"separator":":","uid":"0",)"
97             R"("collections":[{"name":"$default","uid":"0"},
98                               {"name":"vegetable","uid":"1"},
99                               {"name":"meat","uid":"3"},
100                               {"name":"fruit", "uid":"4"},
101                               {"name":"dairy","uid":"5"}]})");
102 
103     std::vector<std::string> inputs = {
104             R"({"collections":["$default"]})",
105             R"({"collections":["vegetable"]})",
106             R"({"collections":["fruit", "meat"]})",
107             R"({"collections":[{"name":"vegetable","uid":"1"}]})",
108             R"({"collections":[{"name":"dairy","uid":"5"},{"name":"meat","uid":"3"}]})",
109             R"({"collections":[{"name":"$default","uid":"0"}]})"};
110 
111     for (const auto& s : inputs) {
112         boost::optional<const std::string&> json = s;
113 
114         try {
115             Collections::Filter f(json, &m);
116         } catch (...) {
117             FAIL() << "Exception thrown with input " << s;
118         }
119     }
120 }
121 
122 /**
123  * Test valid JSON formats to the filter, but they are contain invalid content
124  * such as unknown collections
125  */
126 TEST_F(CollectionsFilterTest, validation2) {
127     Collections::Manifest m(
128             R"({"revision":0,"separator":":","uid":"0",)"
129             R"("collections":[{"name":"$default","uid":"0"},
130                               {"name":"vegetable","uid":"1"},
131                               {"name":"meat","uid":"3"},
132                               {"name":"fruit", "uid":"4"},
133                               {"name":"dairy","uid":"5"}]})");
134 
135     std::vector<std::string> inputs = {
136             // wrong name inputs
137             R"({"collections":["cheese"]})",
138             R"({"collections":["fruit","beer"]})",
139             R"({"collections":["$dufault"]})",
140             // wrong UID inputs
141             R"({"collections":[{"name":"vegetable","uid":"2"}]})",
142             R"({"collections":[{"name":"meat","uid":"1"}]})",
143             R"({"collections":[{"name":"vegetable","uid":"1"},{"name":"dairy","uid":"9"}]})",
144             // wrong name and UID inputs
145             R"({"collections":[{"name":"vugetable","uid":"2"}]})",
146             R"({"collections":[{"name":"getable","uid":"1"}]})",
147             R"({"collections":[{"name":"vegetable","uid":"1"},{"name":"fairy","uid":"5"}]})",
148 
149             // cannot mix name/uid and name
150             R"({"collections":[{"name":"vegetable","uid":"1"}, "cheese"]})",
151     };
152 
153     for (const auto& s : inputs) {
154         boost::optional<const std::string&> json = s;
155         try {
156             Collections::Filter f(json, &m);
157             FAIL() << "Should of thrown an exception";
158         } catch (const cb::engine_error& e) {
159             EXPECT_EQ(cb::engine_errc::unknown_collection, e.code());
160         } catch (...) {
161             FAIL() << "Should of thrown cb::engine_error";
162         }
163     }
164 }
165 
166 /**
167  * Test that we cannot create default collection filter when no default
168  * collection exists
169  */
170 TEST_F(CollectionsFilterTest, validation_no_default) {
171     // m does not include $default
172     Collections::Manifest m(
173             R"({"separator":":","uid":"0",)"
174             R"("collections":[{"name":"vegetable","uid":"1"},
175                               {"name":"meat","uid":"3"},
176                               {"name":"fruit", "uid":"4"},
177                               {"name":"dairy","uid":"5"}]})");
178     boost::optional<const std::string&> json;
179     try {
180         Collections::Filter f(json, &m);
181         FAIL() << "Should of thrown an exception";
182     } catch (const cb::engine_error& e) {
183         EXPECT_EQ(cb::engine_errc::unknown_collection, e.code());
184     } catch (...) {
185         FAIL() << "Should of thrown cb::engine_error";
186     }
187 }
188 
189 /**
190  * Test that we cannot create a true filter without a manifest
191  */
192 TEST_F(CollectionsFilterTest, no_manifest) {
193     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
194     boost::optional<const std::string&> json(jsonFilter);
195 
196     try {
197         Collections::Filter f(json, nullptr);
198         FAIL() << "Should of thrown an exception";
199     } catch (const cb::engine_error& e) {
200         EXPECT_EQ(cb::engine_errc::no_collections_manifest, e.code());
201     } catch (...) {
202         FAIL() << "Should of thrown cb::engine_error";
203     }
204 }
205 
206 /**
207  * Construct a valid Collections::Filter and check its public methods
208  * This creates a filter which contains a set of collections
209  */
210 TEST_F(CollectionsFilterTest, filter_basic1) {
211     Collections::Manifest m(
212             R"({"separator":"$","uid":"0",)"
213             R"("collections":[{"name":"$default","uid":"0"},
214                               {"name":"vegetable","uid":"1"},
215                               {"name":"meat","uid":"3"},
216                               {"name":"fruit", "uid":"4"},
217                               {"name":"dairy","uid":"5"}]})");
218 
219     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
220     boost::optional<const std::string&> json(jsonFilter);
221     Collections::Filter f(json, &m);
222 
223     // This is not a passthrough filter
224     EXPECT_FALSE(f.isPassthrough());
225 
226     // But this filter would send the default
227     EXPECT_TRUE(f.allowDefaultCollection());
228     // and allow system events
229     EXPECT_TRUE(f.allowSystemEvents());
230 
231     // The actual filter "list" only stores fruit and meat though, default is
232     // special cased via doesDefaultCollectionExist
233     EXPECT_EQ(2, f.getFilter().size());
234 
235     auto list = f.getFilter();
236     EXPECT_TRUE(std::find_if(
237                         std::begin(list),
238                         std::end(list),
239                         [](const std::pair<std::string,
240                                            boost::optional<Collections::uid_t>>&
241                                    item) { return item.first == "fruit"; }) !=
242                 list.end());
243     EXPECT_TRUE(std::find_if(
244                         std::begin(list),
245                         std::end(list),
246                         [](const std::pair<std::string,
247                                            boost::optional<Collections::uid_t>>&
248                                    item) { return item.first == "meat"; }) !=
249                 list.end());
250 }
251 
252 /**
253  * Construct a valid Collections::Filter and check its public methods
254  * This creates a filter which is passthrough
255  */
256 TEST_F(CollectionsFilterTest, filter_basic2) {
257     Collections::Manifest m(
258             R"({"separator":"$","uid":"0",)"
259             R"("collections":[{"name":"$default","uid":"0"},
260                               {"name":"vegetable","uid":"1"},
261                               {"name":"meat","uid":"3"},
262                               {"name":"fruit", "uid":"4"},
263                               {"name":"dairy","uid":"5"}]})");
264 
265     std::string jsonFilter; // empty string creates a pass through
266     boost::optional<const std::string&> json(jsonFilter);
267     Collections::Filter f(json, &m);
268 
269     // This is a passthrough filter
270     EXPECT_TRUE(f.isPassthrough());
271 
272     // So this filter would send the default
273     EXPECT_TRUE(f.allowDefaultCollection());
274 
275     // and still allow system events
276     EXPECT_TRUE(f.allowSystemEvents());
277 
278     // The actual filter "list" stores nothing
279     EXPECT_EQ(0, f.getFilter().size());
280 }
281 
282 class CollectionsVBFilterTest : public CollectionsFilterTest {};
283 
284 /**
285  * Try and create filter for collections which exist by name but not with the
286  * UID. This represents what could happen if a filtered producer was created
287  * successfully, but later when a stream request occurs, the VB's view of
288  * collections has shifted.
289  */
290 TEST_F(CollectionsVBFilterTest, uid_mismatch) {
291     Collections::Manifest m1(
292             R"({"separator":"$","uid":"0",)"
293             R"("collections":[{"name":"$default","uid":"0"},
294                               {"name":"vegetable","uid":"1"},
295                               {"name":"meat","uid":"3"},
296                               {"name":"fruit", "uid":"4"},
297                               {"name":"dairy","uid":"5"}]})");
298     Collections::Manifest m2(
299             R"({"separator":"$","uid":"0",)"
300             R"("collections":[{"name":"$default","uid":"0"},
301                               {"name":"vegetable","uid":"8"},
302                               {"name":"meat","uid":"99"},
303                               {"name":"fruit", "uid":"4"},
304                               {"name":"dairy","uid":"5"}]})");
305 
306     // Create the "producer" level filter so that we in theory produce at least
307     // these collections
308     std::string jsonFilter =
309             R"({"collections":[{"name":"meat","uid":"3"}, {"name":"vegetable","uid":"1"}]})";
310     boost::optional<const std::string&> json(jsonFilter);
311     // At this point the requested collections are valid for m1
312     Collections::Filter f(json, &m1);
313 
314     Collections::VB::Manifest vbm({});
315     // push creates
316     vbm.wlock().update(vb, m1);
317     // push deletes, removing both filtered collections
318     vbm.wlock().update(vb, m2);
319 
320     // Construction will now fail as the newly calculated filter doesn't match
321     // the collections of vbm
322     Collections::VB::Filter vbf(f, vbm);
323     EXPECT_TRUE(vbf.empty());
324 }
325 
326 /**
327  * Try and create filter for collections which exist, but have been deleted
328  * i.e. they aren't writable so should never feature in a new VB::Filter
329  */
330 TEST_F(CollectionsVBFilterTest, deleted_collection) {
331     Collections::Manifest m1(
332             R"({"separator":"$","uid":"0",)"
333             R"("collections":[{"name":"$default","uid":"0"},
334                               {"name":"vegetable","uid":"1"},
335                               {"name":"meat","uid":"3"},
336                               {"name":"fruit", "uid":"4"},
337                               {"name":"dairy","uid":"5"}]})");
338     Collections::Manifest m2(
339             R"({"separator":"$","uid":"0",)"
340             R"("collections":[{"name":"$default","uid":"0"},
341                               {"name":"meat","uid":"3"},
342                               {"name":"dairy","uid":"5"}]})");
343 
344     // Create the "producer" level filter so that we in theory produce at least
345     // these collections
346     std::string jsonFilter = R"({"collections":["vegetable", "fruit"]})";
347     boost::optional<const std::string&> json(jsonFilter);
348     Collections::Filter f(json, &m1);
349 
350     Collections::VB::Manifest vbm({});
351     // push creates
352     vbm.wlock().update(vb, m1);
353     // push deletes, removing both filtered collections
354     vbm.wlock().update(vb, m2);
355 
356     Collections::VB::Filter vbf(f, vbm);
357     EXPECT_TRUE(vbf.empty());
358 }
359 
360 /**
361  * Create a filter with collections and check we allow what should be allowed.
362  */
363 TEST_F(CollectionsVBFilterTest, basic_allow) {
364     Collections::Manifest m(
365             R"({"separator":"$","uid":"0",)"
366             R"("collections":[{"name":"$default","uid":"0"},
367                               {"name":"vegetable","uid":"1"},
368                               {"name":"meat","uid":"3"},
369                               {"name":"fruit", "uid":"4"},
370                               {"name":"dairy","uid":"5"}]})");
371 
372     std::string jsonFilter = R"({"collections":["$default", "fruit", "meat"]})";
373     boost::optional<const std::string&> json(jsonFilter);
374     Collections::Filter f(json, &m);
375 
376     Collections::VB::Manifest vbm({});
377     vbm.wlock().update(vb, m);
378 
379     Collections::VB::Filter vbf(f, vbm);
380 
381     // Yes to these guys
382     EXPECT_TRUE(vbf.checkAndUpdate(
383             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
384     EXPECT_TRUE(vbf.checkAndUpdate(
385             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
386     EXPECT_TRUE(vbf.checkAndUpdate(
387             {{"meat$bacon", DocNamespace::Collections}, 0, 0, nullptr, 0}));
388 
389     // No to these keys
390     EXPECT_FALSE(vbf.checkAndUpdate(
391             {{"dairy$milk", DocNamespace::Collections}, 0, 0, nullptr, 0}));
392     EXPECT_FALSE(vbf.checkAndUpdate(
393             {{"vegetable$cabbage", DocNamespace::Collections},
394              0,
395              0,
396              nullptr,
397              0}));
398 }
399 
400 /**
401  * Create a filter as if a legacy DCP connection would, i.e. the optional
402  * JSON filter is not initialised (because DCP open does not send a value).
403  */
404 TEST_F(CollectionsVBFilterTest, legacy_filter) {
405     Collections::Manifest m(
406             R"({"separator":"$","uid":"0",)"
407             R"("collections":[{"name":"$default","uid":"0"},
408                               {"name":"meat","uid":"3"}]})");
409 
410     boost::optional<const std::string&> json;
411     Collections::Filter f(json, &m);
412 
413     Collections::VB::Manifest vbm({});
414     vbm.wlock().update(vb, m);
415 
416     Collections::VB::Filter vbf(f, vbm);
417     // Legacy would only allow default
418     EXPECT_TRUE(vbf.checkAndUpdate(
419             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
420     EXPECT_FALSE(vbf.checkAndUpdate(
421             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
422 }
423 
424 /**
425  * Create a passthrough filter and check it allows anything
426  */
427 TEST_F(CollectionsVBFilterTest, passthrough) {
428     Collections::Manifest m(
429             R"({"separator":"$","uid":"0",)"
430             R"("collections":[{"name":"meat","uid":"3"}]})");
431     std::string filterJson; // empty string
432     boost::optional<const std::string&> json(filterJson);
433     Collections::Filter f(json, &m);
434 
435     Collections::VB::Manifest vbm({});
436     vbm.wlock().update(vb, m);
437 
438     // Everything is allowed (even junk, which isn't the filter's job to police)
439     Collections::VB::Filter vbf(f, vbm);
440     EXPECT_TRUE(vbf.checkAndUpdate(
441             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
442     EXPECT_TRUE(vbf.checkAndUpdate(
443             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
444     EXPECT_TRUE(vbf.checkAndUpdate(
445             {{"meat$steak", DocNamespace::Collections}, 0, 0, nullptr, 0}));
446     EXPECT_TRUE(vbf.checkAndUpdate(
447             {{"dairy$milk", DocNamespace::Collections}, 0, 0, nullptr, 0}));
448     EXPECT_TRUE(vbf.checkAndUpdate(
449             {{"JUNK!!", DocNamespace::Collections}, 0, 0, nullptr, 0}));
450 }
451 
452 /**
453  * Create a filter which blocks the default collection
454  */
455 TEST_F(CollectionsVBFilterTest, no_default) {
456     Collections::Manifest m(
457             R"({"separator":"$","uid":"0",)"
458             R"("collections":[{"name":"$default","uid":"0"},
459                               {"name":"vegetable","uid":"1"},
460                               {"name":"meat","uid":"3"},
461                               {"name":"fruit", "uid":"4"},
462                               {"name":"dairy","uid":"5"}]})");
463     Collections::VB::Manifest vbm({});
464     vbm.wlock().update(vb, m);
465 
466     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
467     boost::optional<const std::string&> json(jsonFilter);
468     Collections::Filter f(json, &m);
469 
470     // Now filter!
471     Collections::VB::Filter vbf(f, vbm);
472     EXPECT_FALSE(vbf.checkAndUpdate(
473             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
474     EXPECT_TRUE(vbf.checkAndUpdate(
475             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
476     EXPECT_TRUE(vbf.checkAndUpdate(
477             {{"meat$steak", DocNamespace::Collections}, 0, 0, nullptr, 0}));
478     EXPECT_FALSE(vbf.checkAndUpdate(
479             {{"dairy$milk", DocNamespace::Collections}, 0, 0, nullptr, 0}));
480     EXPECT_FALSE(vbf.checkAndUpdate(
481             {{"JUNK!!", DocNamespace::Collections}, 0, 0, nullptr, 0}));
482 }
483 
484 /**
485  * Check we can remove collections from the filter (which live DCP may do) and
486  * check ::checkAndUpdate works as expected
487  */
488 TEST_F(CollectionsVBFilterTest, remove1) {
489     Collections::Manifest m(
490             R"({"separator":"$","uid":"0",)"
491             R"("collections":[{"name":"vegetable","uid":"1"},
492                               {"name":"meat","uid":"3"},
493                               {"name":"fruit", "uid":"4"},
494                               {"name":"dairy","uid":"5"}]})");
495     Collections::VB::Manifest vbm({});
496     vbm.wlock().update(vb, m);
497 
498     std::string jsonFilter = R"({"collections":["fruit", "meat"]})";
499     boost::optional<const std::string&> json(jsonFilter);
500 
501     Collections::Filter f(json, &m);
502     Collections::VB::Filter vbf(f, vbm);
503     EXPECT_TRUE(vbf.checkAndUpdate(
504             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
505 
506     // Process a deletion of fruit
507     Item deleteFruit{
508             {"$collections:fruit", DocNamespace::System}, 0, 0, nullptr, 0};
509     deleteFruit.setDeleted();
510     EXPECT_TRUE(vbf.checkAndUpdate(deleteFruit));
511 
512     EXPECT_FALSE(vbf.checkAndUpdate(
513             {{"fruit$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
514 
515     EXPECT_TRUE(vbf.checkAndUpdate(
516             {{"meat$steak", DocNamespace::Collections}, 0, 0, nullptr, 0}));
517 
518     // Process a deletion of meat
519     Item deleteMeat{
520             {"$collections:meat", DocNamespace::System}, 0, 0, nullptr, 0};
521     deleteMeat.setDeleted();
522     EXPECT_TRUE(vbf.checkAndUpdate(deleteMeat));
523     EXPECT_TRUE(vbf.empty()); // now empty
524     EXPECT_FALSE(vbf.checkAndUpdate(deleteMeat)); // no more meat for you
525 }
526 
527 /**
528  * Check we can remove collections from the filter (which live DCP may do) and
529  * check ::allow works as expected
530  * This test includes checking we can remove $default
531  */
532 TEST_F(CollectionsVBFilterTest, remove2) {
533     Collections::Manifest m(
534             R"({"separator":"$","uid":"0",)"
535             R"("collections":[{"name":"$default","uid":"0"},
536                               {"name":"meat","uid":"3"},
537                               {"name":"fruit", "uid":"4"},
538                               {"name":"dairy","uid":"5"}]})");
539     Collections::VB::Manifest vbm({});
540     vbm.wlock().update(vb, m);
541 
542     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
543     boost::optional<const std::string&> json(jsonFilter);
544 
545     Collections::Filter f(json, &m);
546     Collections::VB::Filter vbf(f, vbm);
547     EXPECT_TRUE(vbf.checkAndUpdate(
548             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
549     // Process a deletion of $default
550     Item deleteDefault{
551             {"$collections:$default", DocNamespace::System}, 0, 0, nullptr, 0};
552     deleteDefault.setDeleted();
553     EXPECT_TRUE(vbf.checkAndUpdate(deleteDefault));
554     EXPECT_FALSE(vbf.checkAndUpdate(
555             {{"anykey", DocNamespace::DefaultCollection}, 0, 0, nullptr, 0}));
556 
557     EXPECT_TRUE(vbf.checkAndUpdate(
558             {{"meat$steak", DocNamespace::Collections}, 0, 0, nullptr, 0}));
559     // Process a deletion of meat
560     Item deleteMeat{
561             {"$collections:meat", DocNamespace::System}, 0, 0, nullptr, 0};
562     deleteMeat.setDeleted();
563     EXPECT_TRUE(vbf.checkAndUpdate(deleteMeat));
564     EXPECT_FALSE(vbf.checkAndUpdate(
565             {{"meat$apple", DocNamespace::Collections}, 0, 0, nullptr, 0}));
566     EXPECT_TRUE(vbf.empty()); // now empty
567     EXPECT_FALSE(vbf.checkAndUpdate(deleteMeat)); // no more meat for you
568     EXPECT_FALSE(vbf.checkAndUpdate(
569             {{"meat$steak", DocNamespace::Collections}, 0, 0, nullptr, 0}));
570 }
571 
572 /**
573  * Test that a filter allows the right system events.
574  * This test creates a passthrough filter so everything is allowed.
575  */
576 TEST_F(CollectionsVBFilterTest, system_events1) {
577     Collections::Manifest m(
578             R"({"separator":"$","uid":"0",)"
579             R"("collections":[{"name":"$default","uid":"0"},
580                               {"name":"meat","uid":"3"},
581                               {"name":"fruit", "uid":"4"}]})");
582     Collections::VB::Manifest vbm({});
583     vbm.wlock().update(vb, m);
584 
585     std::string jsonFilter;
586     boost::optional<const std::string&> json(jsonFilter);
587 
588     Collections::Filter f(json, &m);
589     Collections::VB::Filter vbf(f, vbm);
590 
591     // meat system event is allowed by the meat filter
592     EXPECT_TRUE(vbf.checkAndUpdate(
593             *SystemEventFactory::make(SystemEvent::Collection, "meat", 0, {})));
594 
595     // $default system event is allowed by the filter
596     EXPECT_TRUE(vbf.checkAndUpdate(*SystemEventFactory::make(
597             SystemEvent::Collection, "$default", 0, {})));
598 
599     // dairy system event is allowed even though dairy doesn't exist in the
600     // manifest, we wouldn't actually create this event as dairy isn't present
601     // but this just shows the passthrough interface at work.
602     EXPECT_TRUE(vbf.checkAndUpdate(*SystemEventFactory::make(
603             SystemEvent::Collection, "dairy", 0, {})));
604 
605     // A change of separator is also allowed
606     EXPECT_TRUE(vbf.checkAndUpdate(*SystemEventFactory::make(
607             SystemEvent::CollectionsSeparatorChanged, "::", 0, {})));
608 }
609 
610 /**
611  * Test that a filter allows the right system events.
612  * This test creates a filter where only matching events are allowed
613  */
614 TEST_F(CollectionsVBFilterTest, system_events2) {
615     Collections::Manifest m(
616             R"({"separator":"$","uid":"0",)"
617             R"("collections":[{"name":"$default","uid":"0"},
618                               {"name":"meat","uid":"3"},
619                               {"name":"fruit", "uid":"4"},
620                               {"name":"dairy","uid":"5"}]})");
621     Collections::VB::Manifest vbm({});
622     vbm.wlock().update(vb, m);
623 
624     // only events for default and meat are allowed
625     std::string jsonFilter = R"({"collections":["$default", "meat"]})";
626     boost::optional<const std::string&> json(jsonFilter);
627 
628     Collections::Filter f(json, &m);
629     Collections::VB::Filter vbf(f, vbm);
630 
631     // meat system event is allowed by the meat filter
632     EXPECT_TRUE(vbf.checkAndUpdate(
633             *SystemEventFactory::make(SystemEvent::Collection, "meat", 0, {})));
634 
635     // $default system event is allowed by the filter
636     EXPECT_TRUE(vbf.checkAndUpdate(*SystemEventFactory::make(
637             SystemEvent::Collection, "$default", 0, {})));
638 
639     // dairy system event is not allowed by the filter
640     EXPECT_FALSE(vbf.checkAndUpdate(*SystemEventFactory::make(
641             SystemEvent::Collection, "dairy", 0, {})));
642 
643     // A change of separator is also allowed
644     EXPECT_TRUE(vbf.checkAndUpdate(*SystemEventFactory::make(
645             SystemEvent::CollectionsSeparatorChanged, "::", 0, {})));
646 }
647 
648 /**
649  * Test that a filter allows the right system events.
650  * This test creates a 'legacy' filter, one which an old DCP client would be
651  * attached to - no system events at all are allowed.
652  */
653 TEST_F(CollectionsVBFilterTest, system_events3) {
654     Collections::Manifest m(
655             R"({"separator":"$","uid":"0",)"
656             R"("collections":[{"name":"$default","uid":"0"},
657                               {"name":"meat","uid":"3"},
658                               {"name":"fruit", "uid":"4"},
659                               {"name":"dairy","uid":"5"}]})");
660     Collections::VB::Manifest vbm({});
661     vbm.wlock().update(vb, m);
662 
663     boost::optional<const std::string&> json;
664 
665     Collections::Filter f(json, &m);
666     Collections::VB::Filter vbf(f, vbm);
667 
668     // All system events dropped by this empty/legacy filter
669     EXPECT_FALSE(vbf.checkAndUpdate(
670             *SystemEventFactory::make(SystemEvent::Collection, "meat", 0, {})));
671     EXPECT_FALSE(vbf.checkAndUpdate(*SystemEventFactory::make(
672             SystemEvent::Collection, "$default", 0, {})));
673     EXPECT_FALSE(vbf.checkAndUpdate(*SystemEventFactory::make(
674             SystemEvent::Collection, "dairy", 0, {})));
675     EXPECT_FALSE(vbf.checkAndUpdate(*SystemEventFactory::make(
676             SystemEvent::CollectionsSeparatorChanged, "::", 0, {})));
677 }