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
28class CollectionsFilterTest : public ::testing::Test {
29public:
30    /// Dummy callback to replace the flusher callback so we can create VBuckets
31    class DummyCB : public Callback<uint16_t> {
32    public:
33        DummyCB() {
34        }
35
36        void callback(uint16_t& dummy) {
37        }
38    };
39
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 */
65TEST_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 */
94TEST_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 */
126TEST_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 */
170TEST_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 */
192TEST_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 */
210TEST_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 */
256TEST_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
282class 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 */
290TEST_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 */
330TEST_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 */
363TEST_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 */
404TEST_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 */
427TEST_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 */
455TEST_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 */
488TEST_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 */
532TEST_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 */
576TEST_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 */
614TEST_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 */
653TEST_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}