• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / jorgen.edelbo_138

13 Mar 2024 08:41AM UTC coverage: 91.77% (-0.3%) from 92.078%
jorgen.edelbo_138

Pull #7356

Evergreen

jedelbo
Add ability to get path to modified collections in object notifications
Pull Request #7356: Add ability to get path to modified collections in object notifications

94532 of 174642 branches covered (54.13%)

118 of 163 new or added lines in 16 files covered. (72.39%)

765 existing lines in 41 files now uncovered.

242808 of 264584 relevant lines covered (91.77%)

5878961.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.93
/test/object-store/audit.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2022 Realm 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

19
#include <util/event_loop.hpp>
20
#include <util/test_file.hpp>
21
#include <util/test_utils.hpp>
22
#include <util/sync/baas_admin_api.hpp>
23
#include <util/sync/flx_sync_harness.hpp>
24

25
#include <realm/set.hpp>
26
#include <realm/list.hpp>
27
#include <realm/dictionary.hpp>
28

29
#include <realm/object-store/audit.hpp>
30
#include <realm/object-store/audit_serializer.hpp>
31
#include <realm/object-store/property.hpp>
32
#include <realm/object-store/object.hpp>
33
#include <realm/object-store/object_schema.hpp>
34
#include <realm/object-store/schema.hpp>
35
#include <realm/object-store/shared_realm.hpp>
36
#include <realm/object-store/impl/object_accessor_impl.hpp>
37
#include <realm/object-store/sync/sync_user.hpp>
38
#include <realm/object-store/sync/sync_manager.hpp>
39
#include <realm/object-store/sync/sync_session.hpp>
40
#include <realm/object-store/sync/mongo_client.hpp>
41
#include <realm/object-store/sync/mongo_database.hpp>
42
#include <realm/object-store/sync/mongo_collection.hpp>
43

44
#include <realm/util/logger.hpp>
45

46
#include <catch2/catch_all.hpp>
47

48
#include <external/json/json.hpp>
49

50
using namespace realm;
51
using namespace std::string_literals;
52
using Catch::Matchers::StartsWith;
53
using nlohmann::json;
54

55
static auto audit_logger =
56
#ifdef AUDIT_LOG_LEVEL
57
    std::make_shared<util::StderrLogger>(AUDIT_LOG_LEVEL);
58
#else
59
    std::make_shared<util::NullLogger>();
60
#endif
61

62
namespace {
63

64
struct AuditEvent {
65
    std::string activity;
66
    util::Optional<std::string> event;
67
    json data;
68
    util::Optional<std::string> raw_data;
69
    Timestamp timestamp;
70
    std::map<std::string, std::string> metadata;
71
};
72

73
util::Optional<std::string> to_optional_string(StringData sd)
74
{
393✔
75
    return sd ? util::Optional<std::string>(sd) : none;
390✔
76
}
393✔
77

78
std::vector<AuditEvent> get_audit_events(TestSyncManager& manager, bool parse_events = true)
79
{
50✔
80
    // Wait for all sessions to be fully uploaded and then tear them down
81
    auto sync_manager = manager.sync_manager();
50✔
82
    REALM_ASSERT(sync_manager);
50✔
83
    auto sessions = sync_manager->get_all_sessions();
50✔
84
    for (auto& session : sessions) {
97✔
85
        // The realm user session has been manually closed, don't try to wait for it to sync
86
        // If the session is still active (in this case the audit session) wait for audit to complete
87
        if (session->state() == SyncSession::State::Active) {
97✔
88
            auto [promise, future] = util::make_promise_future<void>();
51✔
89
            session->wait_for_upload_completion([promise = std::move(promise)](Status) mutable {
51✔
90
                // Don't care if error occurred, just finish operation
91
                promise.emplace_value();
51✔
92
            });
51✔
93
            future.get();
51✔
94
        }
51✔
95
        session->shutdown_and_wait();
97✔
96
    }
97✔
97
    sync_manager->wait_for_sessions_to_terminate();
50✔
98

99
    // Stop the sync server so that we can safely inspect its Realm files
100
    auto& server = manager.sync_server();
50✔
101
    server.stop();
50✔
102

103
    std::vector<AuditEvent> events;
50✔
104

105
    // Iterate over all of the audit Realm files in the server's storage
106
    // directory, opening them in read-only mode (as they use Server history),
107
    // and slurp the audit events out of them.
108
    auto root = server.local_root_dir();
50✔
109
    std::string file_name;
50✔
110
    util::DirScanner dir(root);
50✔
111
    while (dir.next(file_name)) {
365✔
112
        StringData sd(file_name);
315✔
113
        if (!sd.begins_with("audit-") || !sd.ends_with(".realm"))
315✔
114
            continue;
251✔
115

116
        Group g(root + "/" + file_name);
64✔
117
        auto table = g.get_table("class_AuditEvent");
64✔
118
        if (!table)
64✔
UNCOV
119
            continue;
×
120

121
        ColKey activity_key, event_key, data_key, timestamp_key;
64✔
122
        std::vector<std::pair<std::string, ColKey>> metadata_keys;
64✔
123
        for (auto col_key : table->get_column_keys()) {
523✔
124
            auto name = table->get_column_name(col_key);
523✔
125
            if (name == "activity")
523✔
126
                activity_key = col_key;
64✔
127
            else if (name == "event")
459✔
128
                event_key = col_key;
64✔
129
            else if (name == "data")
395✔
130
                data_key = col_key;
64✔
131
            else if (name == "timestamp")
331✔
132
                timestamp_key = col_key;
64✔
133
            else if (name != "_id")
267✔
134
                metadata_keys.push_back({name, col_key});
203✔
135
        }
523✔
136

137
        for (auto& obj : *table) {
393✔
138
            AuditEvent event;
393✔
139
            event.activity = obj.get<StringData>(activity_key);
393✔
140
            event.event = to_optional_string(obj.get<StringData>(event_key));
393✔
141
            event.timestamp = obj.get<Timestamp>(timestamp_key);
393✔
142
            for (auto& [name, key] : metadata_keys) {
20,011✔
143
                if (auto sd = obj.get<StringData>(key))
20,011✔
144
                    event.metadata[name] = sd;
5,159✔
145
            }
20,011✔
146
            if (parse_events) {
393✔
147
                if (auto data = obj.get<StringData>(data_key))
289✔
148
                    event.data = json::parse(data.data(), data.data() + data.size());
289✔
149
            }
289✔
150
            else {
104✔
151
                if (auto data = obj.get<StringData>(data_key))
104✔
152
                    event.raw_data = std::string(data);
102✔
153
            }
104✔
154
            REALM_ASSERT(!event.timestamp.is_null() && event.timestamp != Timestamp(0, 0));
393✔
155
            events.push_back(std::move(event));
393✔
156
        }
393✔
157
    }
64✔
158

159
    return events;
50✔
160
}
50✔
161

162
void sort_events(std::vector<AuditEvent>& events)
163
{
8✔
164
    std::sort(events.begin(), events.end(), [](auto& a, auto& b) {
537✔
165
        return a.timestamp < b.timestamp;
537✔
166
    });
537✔
167
}
8✔
168

169
#if REALM_ENABLE_AUTH_TESTS
170
static std::vector<AuditEvent> get_audit_events_from_baas(TestAppSession& session, SyncUser& user,
171
                                                          size_t expected_count)
172
{
5✔
173
    static const std::set<std::string> nonmetadata_fields = {"activity", "event", "data", "realm_id"};
5✔
174

175
    auto documents = session.get_documents(user, "AuditEvent", expected_count);
5✔
176
    std::vector<AuditEvent> events;
5✔
177
    events.reserve(documents.size());
5✔
178
    for (auto doc : documents) {
8✔
179
        AuditEvent event;
8✔
180
        event.activity = static_cast<std::string>(doc["activity"]);
8✔
181
        event.timestamp = static_cast<Timestamp>(doc["timestamp"]);
8✔
182
        if (auto val = doc.find("event"); bool(val) && *val != bson::Bson()) {
8✔
183
            event.event = static_cast<std::string>(*val);
8✔
184
        }
8✔
185
        if (auto val = doc.find("data"); bool(val) && *val != bson::Bson()) {
8✔
186
            event.data = json::parse(static_cast<std::string>(*val));
8✔
187
        }
8✔
188
        for (auto [key, value] : doc) {
52✔
189
            if (value.type() == bson::Bson::Type::String && !nonmetadata_fields.count(key))
52✔
190
                event.metadata.insert({key, static_cast<std::string>(value)});
4✔
191
        }
52✔
192
        events.push_back(event);
8✔
193
    }
8✔
194
    sort_events(events);
5✔
195
    return events;
5✔
196
}
5✔
197
#endif
198

199
// Check that the given key is present and the value is null
200
#define REQUIRE_NULL(v, k)                                                                                           \
201
    do {                                                                                                             \
8✔
202
        REQUIRE(v.contains(k));                                                                                      \
8✔
203
        REQUIRE(v[k] == nullptr);                                                                                    \
8✔
204
    } while (0)
8✔
205

206
#define REQUIRE_SET_EQUAL(a, ...)                                                                                    \
207
    do {                                                                                                             \
19✔
208
        json actual = (a);                                                                                           \
19✔
209
        json expected = __VA_ARGS__;                                                                                 \
19✔
210
        std::sort(actual.begin(), actual.end());                                                                     \
19✔
211
        std::sort(expected.begin(), expected.end());                                                                 \
19✔
212
        REQUIRE(actual == expected);                                                                                 \
19✔
213
    } while (0)
19✔
214

215
class CustomSerializer : public AuditObjectSerializer {
216
public:
217
    const Obj* expected_obj = nullptr;
218
    bool error = false;
219
    size_t completion_count = 0;
220

221
    void to_json(json& out, const Obj& obj) override
222
    {
169✔
223
        if (error) {
169✔
224
            throw std::runtime_error("custom serialization error");
1✔
225
        }
1✔
226
        if (expected_obj) {
168✔
227
            REQUIRE(expected_obj->get_key() == obj.get_key());
2!
228
            REQUIRE(expected_obj->get_table()->get_key() == obj.get_table()->get_key());
2!
229
            out["obj"] = obj.get_key().value;
2✔
230
            out["table"] = obj.get_table()->get_key().value;
2✔
231
        }
2✔
232
        else {
166✔
233
            AuditObjectSerializer::to_json(out, obj);
166✔
234
        }
166✔
235
    }
168✔
236

237
    void scope_complete() override
238
    {
40✔
239
        ++completion_count;
40✔
240
    }
40✔
241
};
242

243
void assert_no_error(std::exception_ptr e)
244
{
433✔
245
    REALM_ASSERT(!e);
433✔
246
}
433✔
247

248
struct TestClock {
249
    std::atomic<int32_t> timestamp = 1000;
250
    TestClock()
251
    {
27✔
252
        audit_test_hooks::set_clock([&] {
280✔
253
            auto now = timestamp.fetch_add(1);
280✔
254
            return Timestamp(now, now);
280✔
255
        });
280✔
256
    }
27✔
257
    ~TestClock()
258
    {
27✔
259
        audit_test_hooks::set_clock(nullptr);
27✔
260
    }
27✔
261
};
262

263
} // namespace
264

265
TEST_CASE("audit object serialization", "[sync][pbs][audit]") {
36✔
266
    TestSyncManager test_session;
36✔
267
    SyncTestFile config(test_session, "parent");
36✔
268
    config.automatic_change_notifications = false;
36✔
269
    config.schema_version = 1;
36✔
270
    config.schema = Schema{
36✔
271
        {"object",
36✔
272
         {{"_id", PropertyType::Int, Property::IsPrimary{true}},
36✔
273

274
          {"int", PropertyType::Int | PropertyType::Nullable},
36✔
275
          {"bool", PropertyType::Bool | PropertyType::Nullable},
36✔
276
          {"string", PropertyType::String | PropertyType::Nullable},
36✔
277
          {"data", PropertyType::Data | PropertyType::Nullable},
36✔
278
          {"date", PropertyType::Date | PropertyType::Nullable},
36✔
279
          {"float", PropertyType::Float | PropertyType::Nullable},
36✔
280
          {"double", PropertyType::Double | PropertyType::Nullable},
36✔
281
          {"mixed", PropertyType::Mixed | PropertyType::Nullable},
36✔
282
          {"objectid", PropertyType::ObjectId | PropertyType::Nullable},
36✔
283
          {"decimal", PropertyType::Decimal | PropertyType::Nullable},
36✔
284
          {"uuid", PropertyType::UUID | PropertyType::Nullable},
36✔
285

286
          {"int list", PropertyType::Int | PropertyType::Nullable | PropertyType::Array},
36✔
287
          {"int set", PropertyType::Int | PropertyType::Nullable | PropertyType::Set},
36✔
288
          {"int dictionary", PropertyType::Int | PropertyType::Nullable | PropertyType::Dictionary},
36✔
289

290
          {"object", PropertyType::Object | PropertyType::Nullable, "target"},
36✔
291
          {"object list", PropertyType::Object | PropertyType::Array, "target"},
36✔
292
          {"object set", PropertyType::Object | PropertyType::Set, "target"},
36✔
293
          {"object dictionary", PropertyType::Object | PropertyType::Nullable | PropertyType::Dictionary, "target"},
36✔
294

295
          {"embedded object", PropertyType::Object | PropertyType::Nullable, "embedded target"},
36✔
296
          {"embedded object list", PropertyType::Object | PropertyType::Array, "embedded target"},
36✔
297
          {"embedded object dictionary", PropertyType::Object | PropertyType::Nullable | PropertyType::Dictionary,
36✔
298
           "embedded target"}}},
36✔
299
        {"target", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
36✔
300
        {"embedded target", ObjectSchema::ObjectType::Embedded, {{"value", PropertyType::Int}}}};
36✔
301
    config.audit_config = std::make_shared<AuditConfig>();
36✔
302
    auto serializer = std::make_shared<CustomSerializer>();
36✔
303
    config.audit_config->serializer = serializer;
36✔
304
    config.audit_config->logger = audit_logger;
36✔
305
    auto realm = Realm::get_shared_realm(config);
36✔
306
    auto audit = realm->audit_context();
36✔
307
    REQUIRE(audit);
36!
308
    auto wait_for_completion = util::make_scope_exit([=]() noexcept {
36✔
309
        audit->wait_for_completion();
36✔
310
    });
36✔
311

312
    // We open in proper sync mode to let the audit context initialize from that,
313
    // but we don't actually want the realm to be synchronizing
314
    realm->sync_session()->close();
36✔
315

316
    auto table = realm->read_group().get_table("class_object");
36✔
317
    auto target_table = realm->read_group().get_table("class_target");
36✔
318
    CppContext context(realm);
36✔
319

320
    auto populate_object = [&](Obj& obj) {
4✔
321
        obj.set("int", 1);
4✔
322
        obj.set("bool", true);
4✔
323
        obj.set("string", "abc");
4✔
324
        obj.set("data", BinaryData("abc", 3));
4✔
325
        obj.set("date", Timestamp(123, 456));
4✔
326
        obj.set("float", 1.1f);
4✔
327
        obj.set("double", 2.2);
4✔
328
        obj.set("mixed", Mixed(10));
4✔
329
        obj.set("objectid", ObjectId("000000000000000000000001"));
4✔
330
        obj.set("uuid", UUID("00000000-0000-0000-0000-000000000001"));
4✔
331

332
        auto int_list = obj.get_list<util::Optional<int64_t>>("int list");
4✔
333
        int_list.add(1);
4✔
334
        int_list.add(2);
4✔
335
        int_list.add(3);
4✔
336
        int_list.add(none);
4✔
337

338
        auto int_set = obj.get_set<util::Optional<int64_t>>("int set");
4✔
339
        int_set.insert(1);
4✔
340
        int_set.insert(2);
4✔
341
        int_set.insert(3);
4✔
342
        int_set.insert(none);
4✔
343

344
        auto int_dictionary = obj.get_dictionary("int dictionary");
4✔
345
        int_dictionary.insert("1", 1);
4✔
346
        int_dictionary.insert("2", 2);
4✔
347
        int_dictionary.insert("3", 3);
4✔
348
        int_dictionary.insert("4", none);
4✔
349

350
        auto obj_list = obj.get_linklist("object list");
4✔
351
        obj_list.add(target_table->create_object_with_primary_key(1).set_all(1).get_key());
4✔
352
        obj_list.add(target_table->create_object_with_primary_key(2).set_all(2).get_key());
4✔
353
        obj_list.add(target_table->create_object_with_primary_key(3).set_all(3).get_key());
4✔
354

355
        auto obj_set = obj.get_linkset(obj.get_table()->get_column_key("object set"));
4✔
356
        obj_set.insert(target_table->create_object_with_primary_key(4).set_all(4).get_key());
4✔
357
        obj_set.insert(target_table->create_object_with_primary_key(5).set_all(5).get_key());
4✔
358
        obj_set.insert(target_table->create_object_with_primary_key(6).set_all(6).get_key());
4✔
359

360
        auto obj_dict = obj.get_dictionary("object dictionary");
4✔
361
        obj_dict.insert("a", target_table->create_object_with_primary_key(7).set_all(7).get_key());
4✔
362
        obj_dict.insert("b", target_table->create_object_with_primary_key(8).set_all(8).get_key());
4✔
363
        obj_dict.insert("c", target_table->create_object_with_primary_key(9).set_all(9).get_key());
4✔
364

365
        auto embedded_list = obj.get_linklist("embedded object list");
4✔
366
        embedded_list.create_and_insert_linked_object(0).set_all(1);
4✔
367
        embedded_list.create_and_insert_linked_object(1).set_all(2);
4✔
368
        embedded_list.create_and_insert_linked_object(2).set_all(3);
4✔
369

370
        auto embedded_dict = obj.get_dictionary("embedded object dictionary");
4✔
371
        embedded_dict.create_and_insert_linked_object("d").set_all(4);
4✔
372
        embedded_dict.create_and_insert_linked_object("e").set_all(5);
4✔
373
        embedded_dict.create_and_insert_linked_object("f").set_all(6);
4✔
374

375
        return obj;
4✔
376
    };
4✔
377

378
    auto validate_default_values = [](auto& value) {
4✔
379
        REQUIRE(value.size() == 21);
4!
380
        REQUIRE(value["_id"] == 2);
4!
381
        REQUIRE(value["int"] == 1);
4!
382
        REQUIRE(value["bool"] == true);
4!
383
        REQUIRE(value["string"] == "abc");
4!
384
        REQUIRE_FALSE(value.contains("data"));
4!
385
        REQUIRE(value["date"] == "1970-01-01T00:02:03.000Z");
4!
386
        REQUIRE(value["float"] == 1.1f);
4!
387
        REQUIRE(value["double"] == 2.2);
4!
388
        REQUIRE(value["mixed"] == 10);
4!
389
        REQUIRE(value["objectid"] == "000000000000000000000001");
4!
390
        REQUIRE(value["uuid"] == "00000000-0000-0000-0000-000000000001");
4!
391
        REQUIRE_NULL(value, "object");
4!
392
        REQUIRE_NULL(value, "embedded object");
4!
393
        REQUIRE(value["int list"] == json({1, 2, 3, nullptr}));
4!
394
        REQUIRE_SET_EQUAL(value["int set"], {1, 2, 3, nullptr});
4!
395
        REQUIRE(value["int dictionary"] == json({{"1", 1}, {"2", 2}, {"3", 3}, {"4", nullptr}}));
4!
396
        REQUIRE(value["object list"] == json({1, 2, 3}));
4!
397
        REQUIRE_SET_EQUAL(value["object set"], {4, 5, 6});
4!
398
    };
4✔
399

400
    SECTION("default object serialization") {
36✔
401
        realm->begin_transaction();
1✔
402
        auto obj = table->create_object_with_primary_key(2);
1✔
403
        populate_object(obj);
1✔
404
        realm->commit_transaction();
1✔
405

406
        auto scope = audit->begin_scope("scope");
1✔
407
        Object object(realm, obj);
1✔
408
        audit->end_scope(scope, assert_no_error);
1✔
409
        audit->wait_for_completion();
1✔
410

411
        auto events = get_audit_events(test_session);
1✔
412
        REQUIRE(events.size() == 1);
1!
413
        auto& event = events[0];
1✔
414
        REQUIRE(event.event == "read");
1!
415
        REQUIRE(event.activity == "scope");
1!
416
        REQUIRE(!event.timestamp.is_null());
1!
417

418
        REQUIRE(event.data["type"] == "object");
1!
419
        auto value = event.data["value"];
1✔
420
        REQUIRE(value.size() == 1);
1!
421
        validate_default_values(value[0]);
1✔
422
    }
1✔
423

424
    SECTION("custom object serialization") {
36✔
425
        realm->begin_transaction();
1✔
426
        auto obj1 = table->create_object_with_primary_key(2);
1✔
427
        auto obj2 = table->create_object_with_primary_key(3);
1✔
428
        realm->commit_transaction();
1✔
429

430
        serializer->expected_obj = &obj1;
1✔
431

432
        auto scope = audit->begin_scope("scope 1");
1✔
433
        Object(realm, obj1);
1✔
434
        audit->end_scope(scope, assert_no_error);
1✔
435
        audit->wait_for_completion();
1✔
436
        REQUIRE(serializer->completion_count == 1);
1!
437

438
        scope = audit->begin_scope("empty scope");
1✔
439
        audit->end_scope(scope, assert_no_error);
1✔
440
        audit->wait_for_completion();
1✔
441
        REQUIRE(serializer->completion_count == 2);
1!
442

443
        serializer->expected_obj = &obj2;
1✔
444

445
        scope = audit->begin_scope("scope 2");
1✔
446
        Object(realm, obj2);
1✔
447
        audit->end_scope(scope, assert_no_error);
1✔
448
        audit->wait_for_completion();
1✔
449
        REQUIRE(serializer->completion_count == 3);
1!
450

451
        auto events = get_audit_events(test_session);
1✔
452
        REQUIRE(events.size() == 2);
1!
453

454
        REQUIRE(events[0].activity == "scope 1");
1!
455
        REQUIRE(events[1].activity == "scope 2");
1!
456
        REQUIRE(events[0].data ==
1!
457
                json({{"type", "object"},
1✔
458
                      {"value", json::array({{{"obj", obj1.get_key().value}, {"table", table->get_key().value}}})}}));
1✔
459
        REQUIRE(events[1].data ==
1!
460
                json({{"type", "object"},
1✔
461
                      {"value", json::array({{{"obj", obj2.get_key().value}, {"table", table->get_key().value}}})}}));
1✔
462
    }
1✔
463

464
    SECTION("custom serialization error reporting") {
36✔
465
        serializer->error = true;
1✔
466

467
        realm->begin_transaction();
1✔
468
        auto obj = table->create_object_with_primary_key(2);
1✔
469
        realm->commit_transaction();
1✔
470
        auto scope = audit->begin_scope("scope");
1✔
471
        Object(realm, obj);
1✔
472
        audit->end_scope(scope, [](auto error) {
1✔
473
            REQUIRE(error);
1!
474
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "custom serialization error");
1✔
475
        });
1✔
476
        audit->wait_for_completion();
1✔
477
    }
1✔
478

479
    SECTION("write transaction serialization") {
36✔
480
        SECTION("create object") {
6✔
481
            auto scope = audit->begin_scope("scope");
1✔
482
            realm->begin_transaction();
1✔
483
            auto obj = table->create_object_with_primary_key(2);
1✔
484
            populate_object(obj);
1✔
485
            realm->commit_transaction();
1✔
486
            audit->end_scope(scope, assert_no_error);
1✔
487
            audit->wait_for_completion();
1✔
488

489
            auto events = get_audit_events(test_session);
1✔
490
            REQUIRE(events.size() == 1);
1!
491
            auto& event = events[0];
1✔
492
            REQUIRE(event.event == "write");
1!
493
            REQUIRE(event.activity == "scope");
1!
494
            REQUIRE(!event.timestamp.is_null());
1!
495

496
            REQUIRE(event.data.size() == 2);
1!
497
            auto& object_changes = event.data["object"];
1✔
498
            REQUIRE(object_changes.size() == 1);
1!
499
            REQUIRE(object_changes["insertions"].size() == 1);
1!
500
            validate_default_values(object_changes["insertions"][0]);
1✔
501

502
            // target table should have 9 insertions with _id == value
503
            REQUIRE(event.data["target"]["insertions"].size() == 9);
1!
504
            for (int i = 0; i < 9; ++i) {
10✔
505
                REQUIRE(event.data["target"]["insertions"][i] == json({{"_id", i + 1}, {"value", i + 1}}));
9!
506
            }
9✔
507
        }
1✔
508

509
        SECTION("modify object") {
6✔
510
            realm->begin_transaction();
1✔
511
            auto obj = table->create_object_with_primary_key(2);
1✔
512
            populate_object(obj);
1✔
513
            realm->commit_transaction();
1✔
514

515
            auto scope = audit->begin_scope("scope");
1✔
516
            realm->begin_transaction();
1✔
517
            obj.set("int", 3);
1✔
518
            obj.set("bool", true);
1✔
519
            realm->commit_transaction();
1✔
520
            audit->end_scope(scope, assert_no_error);
1✔
521
            audit->wait_for_completion();
1✔
522

523
            auto events = get_audit_events(test_session);
1✔
524
            REQUIRE(events.size() == 1);
1!
525
            auto& event = events[0];
1✔
526
            REQUIRE(event.data.size() == 1);
1!
527
            REQUIRE(event.data["object"].size() == 1);
1!
528
            REQUIRE(event.data["object"]["modifications"].size() == 1);
1!
529
            auto& modifications = event.data["object"]["modifications"][0];
1✔
530
            REQUIRE(modifications.size() == 2);
1!
531
            REQUIRE(modifications["newValue"].size() == 1);
1!
532
            REQUIRE(modifications["newValue"]["int"] == 3);
1!
533
            // note: bool is not reported because it was assigned to itself
534
            validate_default_values(modifications["oldValue"]);
1✔
535
        }
1✔
536

537
        SECTION("delete object") {
6✔
538
            realm->begin_transaction();
1✔
539
            auto obj = table->create_object_with_primary_key(2);
1✔
540
            populate_object(obj);
1✔
541
            realm->commit_transaction();
1✔
542

543
            auto scope = audit->begin_scope("scope");
1✔
544
            realm->begin_transaction();
1✔
545
            obj.remove();
1✔
546
            realm->commit_transaction();
1✔
547
            audit->end_scope(scope, assert_no_error);
1✔
548
            audit->wait_for_completion();
1✔
549

550
            auto events = get_audit_events(test_session);
1✔
551
            REQUIRE(events.size() == 1);
1!
552
            auto& event = events[0];
1✔
553
            REQUIRE(event.data.size() == 1);
1!
554
            REQUIRE(event.data["object"].size() == 1);
1!
555
            REQUIRE(event.data["object"]["deletions"].size() == 1);
1!
556
            validate_default_values(event.data["object"]["deletions"][0]);
1✔
557
        }
1✔
558

559
        SECTION("delete embedded object") {
6✔
560
            realm->begin_transaction();
1✔
561
            auto obj = table->create_object_with_primary_key(2);
1✔
562
            obj.create_and_set_linked_object(obj.get_table()->get_column_key("embedded object")).set_all(100);
1✔
563
            realm->commit_transaction();
1✔
564

565
            auto scope = audit->begin_scope("scope");
1✔
566
            realm->begin_transaction();
1✔
567
            obj.get_linked_object("embedded object").remove();
1✔
568
            realm->commit_transaction();
1✔
569
            audit->end_scope(scope, assert_no_error);
1✔
570
            audit->wait_for_completion();
1✔
571

572
            auto events = get_audit_events(test_session);
1✔
573
            REQUIRE(events.size() == 1);
1!
574
            REQUIRE(events[0].data.size() == 1);
1!
575
            REQUIRE(events[0].data["object"].size() == 1);
1!
576
            REQUIRE(events[0].data["object"]["modifications"].size() == 1);
1!
577
            auto& modification = events[0].data["object"]["modifications"][0];
1✔
578
            REQUIRE(modification["newValue"] == json({{"embedded object", nullptr}}));
1!
579
            REQUIRE(modification["oldValue"]["embedded object"] == json({{"value", 100}}));
1!
580
        }
1✔
581

582
        SECTION("mixed changes") {
6✔
583
            realm->begin_transaction();
1✔
584
            std::vector<Obj> objects;
1✔
585
            for (int i = 0; i < 5; ++i)
6✔
586
                objects.push_back(target_table->create_object_with_primary_key(i).set_all(i));
5✔
587
            realm->commit_transaction();
1✔
588

589
            auto scope = audit->begin_scope("scope");
1✔
590
            realm->begin_transaction();
1✔
591

592
            // Mutate then delete should not report the mutate
593
            objects[0].set("value", 100);
1✔
594
            objects[1].set("value", 100);
1✔
595
            objects[2].set("value", 100);
1✔
596
            objects[1].remove();
1✔
597

598
            // Insert then mutate should not report the mutate
599
            auto obj = target_table->create_object_with_primary_key(20);
1✔
600
            obj.set("value", 100);
1✔
601

602
            // Insert then delete should not report the insert or delete
603
            auto obj2 = target_table->create_object_with_primary_key(21);
1✔
604
            obj2.remove();
1✔
605

606
            realm->commit_transaction();
1✔
607
            audit->end_scope(scope, assert_no_error);
1✔
608
            audit->wait_for_completion();
1✔
609

610
            auto events = get_audit_events(test_session);
1✔
611
            REQUIRE(events.size() == 1);
1!
612
            auto& event = events[0];
1✔
613
            REQUIRE(event.data.size() == 1);
1!
614
            auto& data = event.data["target"];
1✔
615
            REQUIRE(data.size() == 3);
1!
616
            REQUIRE(data["deletions"] == json({{{"_id", 1}, {"value", 1}}}));
1!
617
            REQUIRE(data["insertions"] == json({{{"_id", 20}, {"value", 100}}}));
1!
618
            REQUIRE(data["modifications"] ==
1!
619
                    json({{{"oldValue", {{"_id", 0}, {"value", 0}}}, {"newValue", {{"value", 100}}}},
1✔
620
                          {{"oldValue", {{"_id", 2}, {"value", 2}}}, {"newValue", {{"value", 100}}}}}));
1✔
621
        }
1✔
622

623
        SECTION("empty write transactions do not produce an event") {
6✔
624
            auto scope = audit->begin_scope("scope");
1✔
625
            realm->begin_transaction();
1✔
626
            realm->commit_transaction();
1✔
627
            audit->end_scope(scope, assert_no_error);
1✔
628
            audit->wait_for_completion();
1✔
629

630
            REQUIRE(get_audit_events(test_session).empty());
1!
631
        }
1✔
632
    }
6✔
633

634
    SECTION("empty query") {
36✔
635
        auto scope = audit->begin_scope("scope");
1✔
636
        Results(realm, table->where()).snapshot();
1✔
637
        audit->end_scope(scope, assert_no_error);
1✔
638
        audit->wait_for_completion();
1✔
639
        REQUIRE(get_audit_events(test_session).empty());
1!
640
    }
1✔
641

642
    SECTION("non-empty query") {
36✔
643
        realm->begin_transaction();
5✔
644
        for (int64_t i = 0; i < 10; ++i) {
55✔
645
            table->create_object_with_primary_key(i);
50✔
646
            target_table->create_object_with_primary_key(i);
50✔
647
        }
50✔
648
        realm->commit_transaction();
5✔
649

650
        SECTION("query counts as a read on all objects matching the query") {
5✔
651
            auto scope = audit->begin_scope("scope");
1✔
652
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
653
            audit->end_scope(scope, assert_no_error);
1✔
654
            audit->wait_for_completion();
1✔
655
            auto events = get_audit_events(test_session);
1✔
656
            REQUIRE(events.size() == 1);
1!
657
            REQUIRE(events[0].data["value"].size() == 5);
1!
658
        }
1✔
659

660
        SECTION("subsequent reads on the same table are folded into the query") {
5✔
661
            auto scope = audit->begin_scope("scope");
1✔
662
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
663
            Object(realm, table->get_object(3)); // does not produce any new audit data
1✔
664
            Object(realm, table->get_object(7)); // adds this object to the query's event
1✔
665
            audit->end_scope(scope, assert_no_error);
1✔
666
            audit->wait_for_completion();
1✔
667
            auto events = get_audit_events(test_session);
1✔
668
            REQUIRE(events.size() == 1);
1!
669
            REQUIRE(events[0].data["value"].size() == 6);
1!
670
        }
1✔
671

672
        SECTION("reads on different tables are not folded into query") {
5✔
673
            auto scope = audit->begin_scope("scope");
1✔
674
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
675
            Object(realm, target_table->get_object(3));
1✔
676
            audit->end_scope(scope, assert_no_error);
1✔
677
            audit->wait_for_completion();
1✔
678
            auto events = get_audit_events(test_session);
1✔
679
            REQUIRE(events.size() == 2);
1!
680
            REQUIRE(events[0].data["value"].size() == 5);
1!
681
            REQUIRE(events[1].data["value"].size() == 1);
1!
682
        }
1✔
683

684
        SECTION("reads on same table following a read on a different table are not folded into query") {
5✔
685
            auto scope = audit->begin_scope("scope");
1✔
686
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
687
            Object(realm, target_table->get_object(3));
1✔
688
            Object(realm, table->get_object(3));
1✔
689
            audit->end_scope(scope, assert_no_error);
1✔
690
            audit->wait_for_completion();
1✔
691
            auto events = get_audit_events(test_session);
1✔
692
            REQUIRE(events.size() == 3);
1!
693
            REQUIRE(events[0].data["value"].size() == 5);
1!
694
            REQUIRE(events[1].data["value"].size() == 1);
1!
695
            REQUIRE(events[2].data["value"].size() == 1);
1!
696
        }
1✔
697

698
        SECTION("reads with intervening writes are not combined") {
5✔
699
            auto scope = audit->begin_scope("scope");
1✔
700
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
701
            realm->begin_transaction();
1✔
702
            realm->commit_transaction();
1✔
703
            Object(realm, table->get_object(3));
1✔
704
            audit->end_scope(scope, assert_no_error);
1✔
705
            audit->wait_for_completion();
1✔
706
            auto events = get_audit_events(test_session);
1✔
707
            REQUIRE(events.size() == 2);
1!
708
            REQUIRE(events[0].data["value"].size() == 5);
1!
709
            REQUIRE(events[1].data["value"].size() == 1);
1!
710
        }
1✔
711
    }
5✔
712

713
    SECTION("query on list of objects") {
36✔
714
        realm->begin_transaction();
1✔
715
        auto obj = table->create_object_with_primary_key(2);
1✔
716
        auto list = obj.get_linklist("object list");
1✔
717
        for (int64_t i = 0; i < 10; ++i)
11✔
718
            list.add(target_table->create_object_with_primary_key(i).set_all(i * 2).get_key());
10✔
719
        realm->commit_transaction();
1✔
720

721
        auto scope = audit->begin_scope("scope");
1✔
722
        Object object(realm, obj);
1✔
723
        auto obj_list = util::any_cast<List>(object.get_property_value<std::any>(context, "object list"));
1✔
724
        obj_list.filter(target_table->where().greater(target_table->get_column_key("value"), 10)).snapshot();
1✔
725
        audit->end_scope(scope, assert_no_error);
1✔
726
        audit->wait_for_completion();
1✔
727

728
        auto events = get_audit_events(test_session);
1✔
729
        REQUIRE(events.size() == 2);
1!
730
        REQUIRE(events[0].data["type"] == "object");
1!
731
        REQUIRE(events[0].data["value"][0]["object list"] == json({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
1!
732
        REQUIRE(events[1].data["type"] == "target");
1!
733
        REQUIRE(events[1].data["value"] == json({
1!
734
                                               {{"_id", 6}, {"value", 12}},
1✔
735
                                               {{"_id", 7}, {"value", 14}},
1✔
736
                                               {{"_id", 8}, {"value", 16}},
1✔
737
                                               {{"_id", 9}, {"value", 18}},
1✔
738
                                           }));
1✔
739
    }
1✔
740

741
    SECTION("link access tracking") {
36✔
742
        realm->begin_transaction();
16✔
743
        table->create_object_with_primary_key(1);
16✔
744
        target_table->create_object_with_primary_key(0);
16✔
745
        auto obj = table->create_object_with_primary_key(2);
16✔
746
        obj.set("object", target_table->create_object_with_primary_key(1).set_all(1).get_key());
16✔
747
        obj.create_and_set_linked_object(table->get_column_key("embedded object")).set_all(200);
16✔
748

749
        auto obj_list = obj.get_linklist("object list");
16✔
750
        obj_list.add(target_table->create_object_with_primary_key(3).set_all(10).get_key());
16✔
751
        obj_list.add(target_table->create_object_with_primary_key(4).set_all(20).get_key());
16✔
752
        obj_list.add(target_table->create_object_with_primary_key(5).set_all(30).get_key());
16✔
753

754
        auto obj_set = obj.get_linkset(obj.get_table()->get_column_key("object set"));
16✔
755
        obj_set.insert(target_table->create_object_with_primary_key(6).set_all(40).get_key());
16✔
756
        obj_set.insert(target_table->create_object_with_primary_key(7).set_all(50).get_key());
16✔
757
        obj_set.insert(target_table->create_object_with_primary_key(8).set_all(60).get_key());
16✔
758

759
        auto obj_dict = obj.get_dictionary("object dictionary");
16✔
760
        obj_dict.insert("a", target_table->create_object_with_primary_key(9).set_all(90).get_key());
16✔
761
        obj_dict.insert("b", target_table->create_object_with_primary_key(10).set_all(100).get_key());
16✔
762
        obj_dict.insert("c", target_table->create_object_with_primary_key(11).set_all(110).get_key());
16✔
763
        realm->commit_transaction();
16✔
764

765
        SECTION("objects are serialized as just primary key by default") {
16✔
766
            auto scope = audit->begin_scope("scope");
1✔
767
            Object object(realm, obj);
1✔
768
            audit->end_scope(scope, assert_no_error);
1✔
769
            audit->wait_for_completion();
1✔
770

771
            auto events = get_audit_events(test_session);
1✔
772
            REQUIRE(events.size() == 1);
1!
773
            auto& value = events[0].data["value"][0];
1✔
774
            REQUIRE(value["object"] == 1);
1!
775
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
776
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
777
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
778
        }
1✔
779

780
        SECTION("embedded objects are always full object") {
16✔
781
            auto scope = audit->begin_scope("scope");
1✔
782
            Object object(realm, obj);
1✔
783
            audit->end_scope(scope, assert_no_error);
1✔
784
            audit->wait_for_completion();
1✔
785

786
            auto events = get_audit_events(test_session);
1✔
787
            REQUIRE(events.size() == 1);
1!
788
            REQUIRE(events[0].data["value"][0]["embedded object"] == json({{"value", 200}}));
1!
789
        }
1✔
790

791
        SECTION("links followed serialize the full object") {
16✔
792
            auto scope = audit->begin_scope("scope");
1✔
793
            Object object(realm, obj);
1✔
794
            object.get_property_value<std::any>(context, "object");
1✔
795
            audit->end_scope(scope, assert_no_error);
1✔
796
            audit->wait_for_completion();
1✔
797

798
            auto events = get_audit_events(test_session);
1✔
799
            REQUIRE(events.size() == 2);
1!
800
            auto& value = events[0].data["value"][0];
1✔
801
            REQUIRE(value["object"] == json({{"_id", 1}, {"value", 1}}));
1!
802
            REQUIRE(events[1].data["value"][0] == json({{"_id", 1}, {"value", 1}}));
1!
803

804
            // Other fields are left in pk form
805
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
806
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
807
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
808
        }
1✔
809

810
        SECTION("instantiating a collection accessor does not count as a read") {
16✔
811
            auto scope = audit->begin_scope("scope");
1✔
812
            Object object(realm, obj);
1✔
813
            util::any_cast<List>(object.get_property_value<std::any>(context, "object list"));
1✔
814
            util::any_cast<object_store::Set>(object.get_property_value<std::any>(context, "object set"));
1✔
815
            util::any_cast<object_store::Dictionary>(
1✔
816
                object.get_property_value<std::any>(context, "object dictionary"));
1✔
817
            audit->end_scope(scope, assert_no_error);
1✔
818
            audit->wait_for_completion();
1✔
819

820
            auto events = get_audit_events(test_session);
1✔
821
            REQUIRE(events.size() == 1);
1!
822
            auto& value = events[0].data["value"][0];
1✔
823
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
824
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
825
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
826
        }
1✔
827

828
        SECTION("accessing any value from a collection serializes full objects for the entire collection") {
16✔
829
            SECTION("list") {
8✔
830
                auto scope = audit->begin_scope("scope");
2✔
831
                Object object(realm, obj);
2✔
832
                auto list = util::any_cast<List>(object.get_property_value<std::any>(context, "object list"));
2✔
833
                SECTION("get()") {
2✔
834
                    list.get(1);
1✔
835
                }
1✔
836
                SECTION("get_any()") {
2✔
837
                    list.get_any(1);
1✔
838
                }
1✔
839
                audit->end_scope(scope, assert_no_error);
2✔
840
                audit->wait_for_completion();
2✔
841

842
                auto events = get_audit_events(test_session);
2✔
843
                REQUIRE(events.size() == 2);
2!
844
                auto& value = events[0].data["value"][0];
2✔
845
                REQUIRE(
2!
846
                    value["object list"] ==
2✔
847
                    json({{{"_id", 3}, {"value", 10}}, {{"_id", 4}, {"value", 20}}, {{"_id", 5}, {"value", 30}}}));
2✔
848
                REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
2!
849
                REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
2!
850
            }
2✔
851

852
            SECTION("set") {
8✔
853
                auto scope = audit->begin_scope("scope");
2✔
854
                Object object(realm, obj);
2✔
855
                auto set =
2✔
856
                    util::any_cast<object_store::Set>(object.get_property_value<std::any>(context, "object set"));
2✔
857
                SECTION("get()") {
2✔
858
                    set.get(1);
1✔
859
                }
1✔
860
                SECTION("get_any()") {
2✔
861
                    set.get_any(1);
1✔
862
                }
1✔
863
                audit->end_scope(scope, assert_no_error);
2✔
864
                audit->wait_for_completion();
2✔
865

866
                auto events = get_audit_events(test_session);
2✔
867
                REQUIRE(events.size() == 2);
2!
868
                auto& value = events[0].data["value"][0];
2✔
869
                REQUIRE_SET_EQUAL(
2!
870
                    value["object set"],
2✔
871
                    json({{{"_id", 6}, {"value", 40}}, {{"_id", 7}, {"value", 50}}, {{"_id", 8}, {"value", 60}}}));
2✔
872
                REQUIRE(value["object list"] == json({3, 4, 5}));
2!
873
                REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
2!
874
            }
2✔
875

876
            SECTION("dictionary") {
8✔
877
                auto scope = audit->begin_scope("scope");
4✔
878
                Object object(realm, obj);
4✔
879
                auto dict = util::any_cast<object_store::Dictionary>(
4✔
880
                    object.get_property_value<std::any>(context, "object dictionary"));
4✔
881
                SECTION("get_object()") {
4✔
882
                    dict.get_object("b");
1✔
883
                }
1✔
884
                SECTION("get_any(string)") {
4✔
885
                    dict.get_any("b");
1✔
886
                }
1✔
887
                SECTION("get_any(index)") {
4✔
888
                    const_cast<const object_store::Dictionary&>(dict).get_any(size_t(1));
1✔
889
                }
1✔
890
                SECTION("try_get_any()") {
4✔
891
                    dict.try_get_any("b");
1✔
892
                }
1✔
893
                audit->end_scope(scope, assert_no_error);
4✔
894
                audit->wait_for_completion();
4✔
895

896
                auto events = get_audit_events(test_session);
4✔
897
                REQUIRE(events.size() == 2);
4!
898
                auto& value = events[0].data["value"][0];
4✔
899
                REQUIRE(value["object list"] == json({3, 4, 5}));
4!
900
                REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
4!
901
                REQUIRE(value["object dictionary"] == json({{"a", {{"_id", 9}, {"value", 90}}},
4!
902
                                                            {"b", {{"_id", 10}, {"value", 100}}},
4✔
903
                                                            {"c", {{"_id", 11}, {"value", 110}}}}));
4✔
904
            }
4✔
905
        }
8✔
906

907
        SECTION(
16✔
908
            "link access on an object read outside of a scope does not produce a read on the parent in the scope") {
1✔
909
            Object object(realm, obj);
1✔
910
            auto scope = audit->begin_scope("scope");
1✔
911
            object.get_property_value<std::any>(context, "object");
1✔
912
            audit->end_scope(scope, assert_no_error);
1✔
913
            audit->wait_for_completion();
1✔
914

915
            auto events = get_audit_events(test_session);
1✔
916
            REQUIRE(events.size() == 1);
1!
917
            auto& event = events[0];
1✔
918
            REQUIRE(event.event == "read");
1!
919
            REQUIRE(event.data["type"] == "target");
1!
920
        }
1✔
921

922
        SECTION("link access in a different scope from the object do not expand linked object in parent read") {
16✔
923
            auto scope = audit->begin_scope("scope 1");
1✔
924
            Object object(realm, obj);
1✔
925
            audit->end_scope(scope, assert_no_error);
1✔
926

927
            scope = audit->begin_scope("scope 2");
1✔
928
            object.get_property_value<std::any>(context, "object");
1✔
929
            audit->end_scope(scope, assert_no_error);
1✔
930
            audit->wait_for_completion();
1✔
931

932
            auto events = get_audit_events(test_session);
1✔
933
            REQUIRE(events.size() == 2);
1!
934
            REQUIRE(events[0].activity == "scope 1");
1!
935
            REQUIRE(events[0].data["type"] == "object");
1!
936
            REQUIRE(events[1].activity == "scope 2");
1!
937
            REQUIRE(events[1].data["type"] == "target");
1!
938
            REQUIRE(events[0].data["value"][0]["object"] == 1);
1!
939
        }
1✔
940

941
        SECTION("link access tracking is reset between scopes") {
16✔
942
            auto scope = audit->begin_scope("scope 1");
1✔
943
            Object object(realm, obj);
1✔
944
            object.get_property_value<std::any>(context, "object");
1✔
945
            audit->end_scope(scope, assert_no_error);
1✔
946

947
            scope = audit->begin_scope("scope 2");
1✔
948
            // Perform two unrelated events so that the read on `obj` is at
949
            // an event index after the link access in the previous scope
950
            Object(realm, target_table->get_object(obj_set.get(0)));
1✔
951
            Object(realm, target_table->get_object(obj_set.get(1)));
1✔
952
            Object(realm, obj);
1✔
953
            audit->end_scope(scope, assert_no_error);
1✔
954
            audit->wait_for_completion();
1✔
955

956
            auto events = get_audit_events(test_session);
1✔
957
            REQUIRE(events.size() == 5);
1!
958
            REQUIRE(events[0].activity == "scope 1");
1!
959
            REQUIRE(events[1].activity == "scope 1");
1!
960
            REQUIRE(events[2].activity == "scope 2");
1!
961
            REQUIRE(events[3].activity == "scope 2");
1!
962
            REQUIRE(events[4].activity == "scope 2");
1!
963

964
            REQUIRE(events[0].data["type"] == "object");
1!
965
            REQUIRE(events[1].data["type"] == "target");
1!
966
            REQUIRE(events[2].data["type"] == "target");
1!
967
            REQUIRE(events[3].data["type"] == "target");
1!
968
            REQUIRE(events[4].data["type"] == "object");
1!
969

970
            // First link should be expanded, second, should not
971
            REQUIRE(events[0].data["value"][0]["object"] == json({{"_id", 1}, {"value", 1}}));
1!
972
            REQUIRE(events[4].data["value"][0]["object"] == 1);
1!
973
        }
1✔
974

975
        SECTION("read on the parent after the link access do not expand the linked object") {
16✔
976
            Object object(realm, obj);
1✔
977

978
            auto scope = audit->begin_scope("scope");
1✔
979
            object.get_property_value<std::any>(context, "object");
1✔
980
            Object(realm, obj);
1✔
981
            audit->end_scope(scope, assert_no_error);
1✔
982
            audit->wait_for_completion();
1✔
983

984
            auto events = get_audit_events(test_session);
1✔
985
            REQUIRE(events.size() == 2);
1!
986
            REQUIRE(events[1].data["value"][0]["object"] == 1);
1!
987
        }
1✔
988
    }
16✔
989

990
    SECTION("read on newly created object") {
36✔
991
        realm->begin_transaction();
1✔
992
        auto scope = audit->begin_scope("scope");
1✔
993
        Object object(realm, table->create_object_with_primary_key(100));
1✔
994
        Results(realm, table->where()).snapshot();
1✔
995
        audit->end_scope(scope, assert_no_error);
1✔
996
        realm->commit_transaction();
1✔
997
        audit->wait_for_completion();
1✔
998

999
        auto events = get_audit_events(test_session);
1✔
1000
        REQUIRE(events.empty());
1!
1001
    }
1✔
1002

1003
    SECTION("query matching both new and existing objects") {
36✔
1004
        realm->begin_transaction();
1✔
1005
        table->create_object_with_primary_key(1);
1✔
1006
        realm->commit_transaction();
1✔
1007

1008
        realm->begin_transaction();
1✔
1009
        table->create_object_with_primary_key(2);
1✔
1010
        auto scope = audit->begin_scope("scope");
1✔
1011
        Results(realm, table->where()).snapshot();
1✔
1012
        audit->end_scope(scope, assert_no_error);
1✔
1013
        realm->commit_transaction();
1✔
1014
        audit->wait_for_completion();
1✔
1015

1016
        auto events = get_audit_events(test_session);
1✔
1017
        REQUIRE(events.size() == 1);
1!
1018
        REQUIRE(events[0].data["value"].size() == 1);
1!
1019
    }
1✔
1020

1021
    SECTION("reads mixed with deletions") {
36✔
1022
        realm->begin_transaction();
2✔
1023
        table->create_object_with_primary_key(1);
2✔
1024
        auto obj2 = table->create_object_with_primary_key(2);
2✔
1025
        auto obj3 = table->create_object_with_primary_key(3);
2✔
1026
        realm->commit_transaction();
2✔
1027

1028
        SECTION("reads of objects that are subsequently deleted are still reported") {
2✔
1029
            auto scope = audit->begin_scope("scope");
1✔
1030
            realm->begin_transaction();
1✔
1031
            Object(realm, obj2);
1✔
1032
            obj2.remove();
1✔
1033
            realm->commit_transaction();
1✔
1034
            audit->end_scope(scope, assert_no_error);
1✔
1035
            audit->wait_for_completion();
1✔
1036

1037
            auto events = get_audit_events(test_session);
1✔
1038
            REQUIRE(events.size() == 2);
1!
1039
            REQUIRE(events[0].event == "read");
1!
1040
            REQUIRE(events[1].event == "write");
1!
1041
            REQUIRE(events[0].data["value"][0]["_id"] == 2);
1!
1042
        }
1✔
1043

1044
        SECTION("reads after deletions report the correct object") {
2✔
1045
            auto scope = audit->begin_scope("scope");
1✔
1046
            realm->begin_transaction();
1✔
1047
            obj2.remove();
1✔
1048
            // In the pre-core-6 version of the code this would incorrectly
1049
            // report a read on obj2
1050
            Object(realm, obj3);
1✔
1051
            realm->commit_transaction();
1✔
1052
            audit->end_scope(scope, assert_no_error);
1✔
1053
            audit->wait_for_completion();
1✔
1054

1055
            auto events = get_audit_events(test_session);
1✔
1056
            REQUIRE(events.size() == 2);
1!
1057
            REQUIRE(events[0].event == "read");
1!
1058
            REQUIRE(events[1].event == "write");
1!
1059
            REQUIRE(events[0].data["value"][0]["_id"] == 3);
1!
1060
        }
1✔
1061
    }
2✔
1062
}
36✔
1063

1064
TEST_CASE("audit management", "[sync][pbs][audit]") {
15✔
1065
    TestClock clock;
15✔
1066

1067
    TestSyncManager test_session;
15✔
1068
    SyncTestFile config(test_session, "parent");
15✔
1069
    config.automatic_change_notifications = false;
15✔
1070
    config.schema_version = 1;
15✔
1071
    config.schema = Schema{
15✔
1072
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
15✔
1073
    };
15✔
1074
    config.audit_config = std::make_shared<AuditConfig>();
15✔
1075
    auto realm = Realm::get_shared_realm(config);
15✔
1076
    auto audit = realm->audit_context();
15✔
1077
    REQUIRE(audit);
15!
1078
    auto wait_for_completion = util::make_scope_exit([=]() noexcept {
15✔
1079
        audit->wait_for_completion();
15✔
1080
    });
15✔
1081

1082
    auto table = realm->read_group().get_table("class_object");
15✔
1083

1084
    // We open in proper sync mode to let the audit context initialize from that,
1085
    // but we don't actually want the realm to be synchronizing
1086
    realm->sync_session()->close();
15✔
1087

1088
    SECTION("config validation") {
15✔
1089
        SyncTestFile config(test_session, "parent2");
2✔
1090
        config.automatic_change_notifications = false;
2✔
1091
        config.audit_config = std::make_shared<AuditConfig>();
2✔
1092
        SECTION("invalid prefix") {
2✔
1093
            config.audit_config->partition_value_prefix = "";
1✔
1094
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidName,
1✔
1095
                              "Audit partition prefix must not be empty");
1✔
1096
            config.audit_config->partition_value_prefix = "/audit";
1✔
1097
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidName,
1✔
1098
                              "Invalid audit parition prefix '/audit': prefix must not contain slashes");
1✔
1099
        }
1✔
1100
        SECTION("invalid metadata") {
2✔
1101
            config.audit_config->metadata = {{"", "a"}};
1✔
1102
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidName,
1✔
1103
                              "Invalid audit metadata key '': keys must be 1-63 characters long");
1✔
1104
            std::string long_name(64, 'a');
1✔
1105
            config.audit_config->metadata = {{long_name, "b"}};
1✔
1106
            REQUIRE_EXCEPTION(
1✔
1107
                Realm::get_shared_realm(config), InvalidName,
1✔
1108
                "Invalid audit metadata key 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': keys "
1✔
1109
                "must be 1-63 characters long");
1✔
1110
            config.audit_config->metadata = {{"activity", "c"}};
1✔
1111
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidName,
1✔
1112
                              "Invalid audit metadata key 'activity': metadata keys cannot overlap with the audit "
1✔
1113
                              "event properties");
1✔
1114
            config.audit_config->metadata = {{"a", "d"}, {"a", "e"}};
1✔
1115
            REQUIRE_EXCEPTION(Realm::get_shared_realm(config), InvalidName, "Duplicate audit metadata key 'a'");
1✔
1116
        }
1✔
1117
    }
2✔
1118

1119
    SECTION("scope names") {
15✔
1120
        realm->begin_transaction();
1✔
1121
        auto obj = table->create_object_with_primary_key(1);
1✔
1122
        realm->commit_transaction();
1✔
1123

1124
        auto scope = audit->begin_scope("scope 1");
1✔
1125
        Object(realm, obj);
1✔
1126
        audit->end_scope(scope, assert_no_error);
1✔
1127

1128
        scope = audit->begin_scope("scope 2");
1✔
1129
        Object(realm, obj);
1✔
1130
        audit->end_scope(scope, assert_no_error);
1✔
1131
        audit->wait_for_completion();
1✔
1132

1133
        auto events = get_audit_events(test_session);
1✔
1134
        REQUIRE(events.size() == 2);
1!
1135
        REQUIRE(events[0].activity == "scope 1");
1!
1136
        REQUIRE(events[1].activity == "scope 2");
1!
1137
    }
1✔
1138

1139
    SECTION("nested scopes") {
15✔
1140
        realm->begin_transaction();
1✔
1141
        auto obj1 = table->create_object_with_primary_key(1);
1✔
1142
        auto obj2 = table->create_object_with_primary_key(2);
1✔
1143
        auto obj3 = table->create_object_with_primary_key(3);
1✔
1144
        realm->commit_transaction();
1✔
1145

1146
        auto scope1 = audit->begin_scope("scope 1");
1✔
1147
        Object(realm, obj1); // read in scope 1 only
1✔
1148

1149
        auto scope2 = audit->begin_scope("scope 2");
1✔
1150
        Object(realm, obj2); // read in both scopes
1✔
1151
        audit->end_scope(scope2, assert_no_error);
1✔
1152

1153
        Object(realm, obj3); // read in scope 1 only
1✔
1154

1155
        audit->end_scope(scope1, assert_no_error);
1✔
1156
        audit->wait_for_completion();
1✔
1157

1158
        auto events = get_audit_events(test_session);
1✔
1159
        REQUIRE(events.size() == 4);
1!
1160

1161
        // scope 2 read on obj 2 comes first as it was the first scope ended
1162
        REQUIRE(events[0].activity == "scope 2");
1!
1163
        REQUIRE(events[0].data["value"][0]["_id"] == 2);
1!
1164

1165
        // scope 1 then has reads on each object in order
1166
        REQUIRE(events[1].activity == "scope 1");
1!
1167
        REQUIRE(events[1].data["value"][0]["_id"] == 1);
1!
1168
        REQUIRE(events[2].activity == "scope 1");
1!
1169
        REQUIRE(events[2].data["value"][0]["_id"] == 2);
1!
1170
        REQUIRE(events[3].activity == "scope 1");
1!
1171
        REQUIRE(events[3].data["value"][0]["_id"] == 3);
1!
1172
    }
1✔
1173

1174
    SECTION("overlapping scopes") {
15✔
1175
        realm->begin_transaction();
1✔
1176
        auto obj1 = table->create_object_with_primary_key(1);
1✔
1177
        auto obj2 = table->create_object_with_primary_key(2);
1✔
1178
        auto obj3 = table->create_object_with_primary_key(3);
1✔
1179
        realm->commit_transaction();
1✔
1180

1181
        auto scope1 = audit->begin_scope("scope 1");
1✔
1182
        Object(realm, obj1); // read in scope 1 only
1✔
1183

1184
        auto scope2 = audit->begin_scope("scope 2");
1✔
1185
        Object(realm, obj2); // read in both scopes
1✔
1186

1187
        audit->end_scope(scope1, assert_no_error);
1✔
1188
        Object(realm, obj3); // read in scope 2 only
1✔
1189

1190
        audit->end_scope(scope2, assert_no_error);
1✔
1191
        audit->wait_for_completion();
1✔
1192

1193
        auto events = get_audit_events(test_session);
1✔
1194
        REQUIRE(events.size() == 4);
1!
1195

1196
        // scope 1 only read on obj 1
1197
        REQUIRE(events[0].activity == "scope 1");
1!
1198
        REQUIRE(events[0].data["value"][0]["_id"] == 1);
1!
1199

1200
        // both scopes read on obj 2
1201
        REQUIRE(events[1].activity == "scope 1");
1!
1202
        REQUIRE(events[1].data["value"][0]["_id"] == 2);
1!
1203
        REQUIRE(events[2].activity == "scope 2");
1!
1204
        REQUIRE(events[2].data["value"][0]["_id"] == 2);
1!
1205

1206
        // scope 2 only read on obj 3
1207
        REQUIRE(events[3].activity == "scope 2");
1!
1208
        REQUIRE(events[3].data["value"][0]["_id"] == 3);
1!
1209
    }
1✔
1210

1211
    SECTION("scope cancellation") {
15✔
1212
        realm->begin_transaction();
1✔
1213
        auto obj = table->create_object_with_primary_key(1);
1✔
1214
        realm->commit_transaction();
1✔
1215

1216
        auto scope1 = audit->begin_scope("scope 1");
1✔
1217
        auto scope2 = audit->begin_scope("scope 2");
1✔
1218
        Object(realm, obj);
1✔
1219
        audit->cancel_scope(scope1);
1✔
1220
        audit->end_scope(scope2, assert_no_error);
1✔
1221
        audit->wait_for_completion();
1✔
1222

1223
        auto events = get_audit_events(test_session);
1✔
1224
        REQUIRE(events.size() == 1);
1!
1225
        REQUIRE(events[0].activity == "scope 2");
1!
1226
    }
1✔
1227

1228
    SECTION("ending invalid scopes") {
15✔
1229
        REQUIRE_FALSE(audit->is_scope_valid(0));
1!
1230
        REQUIRE_THROWS_WITH(audit->end_scope(0),
1✔
1231
                            "Cannot end event scope: scope '0' not in progress. Scope may have already been ended?");
1✔
1232

1233
        auto scope = audit->begin_scope("scope");
1✔
1234
        REQUIRE(audit->is_scope_valid(scope));
1!
1235
        REQUIRE_NOTHROW(audit->end_scope(scope));
1✔
1236

1237
        REQUIRE_FALSE(audit->is_scope_valid(scope));
1!
1238
        REQUIRE_THROWS_WITH(audit->end_scope(scope),
1✔
1239
                            "Cannot end event scope: scope '1' not in progress. Scope may have already been ended?");
1✔
1240

1241
        scope = audit->begin_scope("scope 2");
1✔
1242
        REQUIRE(audit->is_scope_valid(scope));
1!
1243
        REQUIRE_NOTHROW(audit->cancel_scope(scope));
1✔
1244

1245
        REQUIRE_FALSE(audit->is_scope_valid(scope));
1!
1246
        REQUIRE_THROWS_WITH(audit->cancel_scope(scope),
1✔
1247
                            "Cannot end event scope: scope '2' not in progress. Scope may have already been ended?");
1✔
1248
    }
1✔
1249

1250
    SECTION("event timestamps") {
15✔
1251
        std::vector<Obj> objects;
1✔
1252
        realm->begin_transaction();
1✔
1253
        for (int i = 0; i < 10; ++i)
11✔
1254
            objects.push_back(table->create_object_with_primary_key(i));
10✔
1255
        realm->commit_transaction();
1✔
1256

1257
        auto scope = audit->begin_scope("scope");
1✔
1258
        for (int i = 0; i < 10; ++i) {
11✔
1259
            Object(realm, objects[i]);
10✔
1260
            Object(realm, objects[i]);
10✔
1261
        }
10✔
1262
        audit->end_scope(scope, assert_no_error);
1✔
1263
        audit->wait_for_completion();
1✔
1264

1265
        auto events = get_audit_events(test_session);
1✔
1266
        REQUIRE(events.size() == 10);
1!
1267
        for (int i = 0; i < 10; ++i) {
11✔
1268
            // i * 2 because we generate two reads on each object and the second
1269
            // is dropped, but still should have called now().
1270
            REQUIRE(events[i].timestamp == Timestamp(1000 + i * 2, 1000 + i * 2));
10!
1271
        }
10✔
1272
    }
1✔
1273

1274
    SECTION("metadata updating") {
15✔
1275
        realm->begin_transaction();
5✔
1276
        auto obj1 = realm->read_group().get_table("class_object")->create_object_with_primary_key(1);
5✔
1277
        realm->read_group().get_table("class_object")->create_object_with_primary_key(2);
5✔
1278
        realm->read_group().get_table("class_object")->create_object_with_primary_key(3);
5✔
1279
        realm->commit_transaction();
5✔
1280

1281
        SECTION("update before scope") {
5✔
1282
            audit->update_metadata({{"a", "aa"}});
1✔
1283
            auto scope = audit->begin_scope("scope 1");
1✔
1284
            Object(realm, obj1);
1✔
1285
            audit->end_scope(scope, assert_no_error);
1✔
1286
            audit->wait_for_completion();
1✔
1287

1288
            auto events = get_audit_events(test_session);
1✔
1289
            REQUIRE(events.size() == 1);
1!
1290
            auto event = events[0];
1✔
1291
            REQUIRE(event.metadata.size() == 1);
1!
1292
            REQUIRE(event.metadata["a"] == "aa");
1!
1293
        }
1✔
1294

1295
        SECTION("update during scope") {
5✔
1296
            auto scope = audit->begin_scope("scope 1");
1✔
1297
            audit->update_metadata({{"a", "aa"}});
1✔
1298
            Object(realm, obj1);
1✔
1299
            audit->end_scope(scope, assert_no_error);
1✔
1300
            audit->wait_for_completion();
1✔
1301

1302
            auto events = get_audit_events(test_session);
1✔
1303
            REQUIRE(events.size() == 1);
1!
1304
            auto event = events[0];
1✔
1305
            REQUIRE(event.metadata.size() == 0);
1!
1306
        }
1✔
1307

1308
        SECTION("one metadata field at a time") {
5✔
1309
            for (int i = 0; i < 100; ++i) {
101✔
1310
                audit->update_metadata({{util::format("name %1", i), util::format("value %1", i)}});
100✔
1311
                auto scope = audit->begin_scope(util::format("scope %1", i));
100✔
1312
                Object(realm, obj1);
100✔
1313
                audit->end_scope(scope, assert_no_error);
100✔
1314
            }
100✔
1315
            audit->wait_for_completion();
1✔
1316

1317
            auto events = get_audit_events(test_session);
1✔
1318
            REQUIRE(events.size() == 100);
1!
1319
            for (size_t i = 0; i < 100; ++i) {
101✔
1320
                REQUIRE(events[i].metadata.size() == 1);
100!
1321
                REQUIRE(events[i].metadata[util::format("name %1", i)] == util::format("value %1", i));
100!
1322
            }
100✔
1323
        }
1✔
1324

1325
        SECTION("many metadata fields") {
5✔
1326
            std::vector<std::pair<std::string, std::string>> metadata;
1✔
1327
            for (int i = 0; i < 100; ++i) {
101✔
1328
                metadata.push_back({util::format("name %1", i), util::format("value %1", i)});
100✔
1329
                audit->update_metadata(std::vector(metadata));
100✔
1330
                auto scope = audit->begin_scope(util::format("scope %1", i));
100✔
1331
                Object(realm, obj1);
100✔
1332
                audit->end_scope(scope, assert_no_error);
100✔
1333
            }
100✔
1334
            audit->wait_for_completion();
1✔
1335

1336
            auto events = get_audit_events(test_session);
1✔
1337
            REQUIRE(events.size() == 100);
1!
1338
            for (size_t i = 0; i < 100; ++i) {
101✔
1339
                REQUIRE(events[i].metadata.size() == i + 1);
100!
1340
            }
100✔
1341
        }
1✔
1342

1343
        SECTION("update via opening new realm") {
5✔
1344
            config.audit_config->metadata = {{"a", "aa"}};
1✔
1345
            auto realm2 = Realm::get_shared_realm(config);
1✔
1346
            auto obj2 = realm2->read_group().get_table("class_object")->get_object(1);
1✔
1347

1348
            auto scope = audit->begin_scope("scope 1");
1✔
1349
            Object(realm, obj1);
1✔
1350
            Object(realm2, obj2);
1✔
1351
            audit->end_scope(scope, assert_no_error);
1✔
1352

1353
            config.audit_config->metadata = {{"a", "aaa"}, {"b", "bb"}};
1✔
1354
            auto realm3 = Realm::get_shared_realm(config);
1✔
1355
            auto obj3 = realm3->read_group().get_table("class_object")->get_object(2);
1✔
1356

1357
            scope = audit->begin_scope("scope 2");
1✔
1358
            Object(realm, obj1);
1✔
1359
            Object(realm2, obj2);
1✔
1360
            Object(realm3, obj3);
1✔
1361
            audit->end_scope(scope, assert_no_error);
1✔
1362
            audit->wait_for_completion();
1✔
1363

1364
            auto events = get_audit_events(test_session);
1✔
1365
            REQUIRE(events.size() == 5);
1!
1366
            REQUIRE(events[0].activity == "scope 1");
1!
1367
            REQUIRE(events[1].activity == "scope 1");
1!
1368
            REQUIRE(events[2].activity == "scope 2");
1!
1369
            REQUIRE(events[3].activity == "scope 2");
1!
1370
            REQUIRE(events[4].activity == "scope 2");
1!
1371
            REQUIRE(events[0].metadata.size() == 1);
1!
1372
            REQUIRE(events[1].metadata.size() == 1);
1!
1373
            REQUIRE(events[2].metadata.size() == 2);
1!
1374
            REQUIRE(events[3].metadata.size() == 2);
1!
1375
            REQUIRE(events[4].metadata.size() == 2);
1!
1376
        }
1✔
1377
    }
5✔
1378

1379
    SECTION("custom audit event") {
15✔
1380
        // Verify that each of the completion handlers is called in the expected order
1381
        std::atomic<size_t> completions = 0;
1✔
1382
        std::array<std::pair<std::atomic<size_t>, std::atomic<bool>>, 5> completion_results;
1✔
1383
        auto expect_completion = [&](size_t expected) {
5✔
1384
            return [&, expected](std::exception_ptr e) {
5✔
1385
                completion_results[expected].second = bool(e);
5✔
1386
                completion_results[expected].first = completions++;
5✔
1387
            };
5✔
1388
        };
5✔
1389

1390
        audit->record_event("event 1", "event"s, "data"s, expect_completion(0));
1✔
1391
        audit->record_event("event 2", none, "data"s, expect_completion(1));
1✔
1392
        auto scope = audit->begin_scope("scope");
1✔
1393
        // note: does not use the scope's activity
1394
        audit->record_event("event 3", none, none, expect_completion(2));
1✔
1395
        audit->end_scope(scope, expect_completion(3));
1✔
1396
        audit->record_event("event 4", none, none, expect_completion(4));
1✔
1397

1398
        util::EventLoop::main().run_until([&] {
30✔
1399
            return completions == 5;
30✔
1400
        });
30✔
1401

1402
        for (size_t i = 0; i < 5; ++i) {
6✔
1403
            REQUIRE(i == completion_results[i].first);
5!
1404
            REQUIRE_FALSE(completion_results[i].second);
5!
1405
        }
5✔
1406

1407
        auto events = get_audit_events(test_session, false);
1✔
1408
        REQUIRE(events.size() == 4);
1!
1409
        REQUIRE(events[0].activity == "event 1");
1!
1410
        REQUIRE(events[1].activity == "event 2");
1!
1411
        REQUIRE(events[2].activity == "event 3");
1!
1412
        REQUIRE(events[3].activity == "event 4");
1!
1413
        REQUIRE(events[0].event == "event"s);
1!
1414
        REQUIRE(events[1].event == none);
1!
1415
        REQUIRE(events[2].event == none);
1!
1416
        REQUIRE(events[3].event == none);
1!
1417
        REQUIRE(events[0].raw_data == "data"s);
1!
1418
        REQUIRE(events[1].raw_data == "data"s);
1!
1419
        REQUIRE(events[2].raw_data == none);
1!
1420
        REQUIRE(events[3].raw_data == none);
1!
1421
    }
1✔
1422

1423
    SECTION("read transaction version management") {
15✔
1424
        realm->begin_transaction();
1✔
1425
        auto obj = table->create_object_with_primary_key(1);
1✔
1426
        realm->commit_transaction();
1✔
1427

1428
        auto realm2 = Realm::get_shared_realm(config);
1✔
1429
        auto obj2 = realm2->read_group().get_table("class_object")->get_object(0);
1✔
1430
        auto realm3 = Realm::get_shared_realm(config);
1✔
1431
        auto obj3 = realm3->read_group().get_table("class_object")->get_object(0);
1✔
1432

1433
        realm2->begin_transaction();
1✔
1434
        obj2.set_all(1);
1✔
1435
        realm2->commit_transaction();
1✔
1436

1437
        realm3->begin_transaction();
1✔
1438
        obj3.set_all(2);
1✔
1439
        realm3->commit_transaction();
1✔
1440

1441
        auto scope = audit->begin_scope("scope");
1✔
1442
        Object(realm3, obj3); // value 2
1✔
1443
        Object(realm2, obj2); // value 1
1✔
1444
        Object(realm, obj);   // value 0
1✔
1445
        realm->refresh();
1✔
1446
        Object(realm, obj);   // value 2
1✔
1447
        Object(realm2, obj2); // value 1
1✔
1448
        realm2->refresh();
1✔
1449
        Object(realm3, obj3); // value 2
1✔
1450
        Object(realm2, obj2); // value 2
1✔
1451
        Object(realm, obj);   // value 2
1✔
1452
        audit->end_scope(scope, assert_no_error);
1✔
1453
        audit->wait_for_completion();
1✔
1454

1455
        auto events = get_audit_events(test_session);
1✔
1456
        REQUIRE(events.size() == 6);
1!
1457
        std::string str = events[0].data.dump();
1✔
1458
        // initial
1459
        REQUIRE(events[0].data["value"][0]["value"] == 2);
1!
1460
        REQUIRE(events[1].data["value"][0]["value"] == 1);
1!
1461
        REQUIRE(events[2].data["value"][0]["value"] == 0);
1!
1462

1463
        // realm->refresh()
1464
        REQUIRE(events[3].data["value"][0]["value"] == 2);
1!
1465
        REQUIRE(events[4].data["value"][0]["value"] == 1);
1!
1466

1467
        // realm2->refresh()
1468
        REQUIRE(events[5].data["value"][0]["value"] == 2);
1!
1469
    }
1✔
1470

1471
#if !REALM_DEBUG // This test is unreasonably slow in debug mode
1472
    SECTION("large audit scope") {
1473
        realm->begin_transaction();
1474
        auto obj1 = table->create_object_with_primary_key(1);
1475
        auto obj2 = table->create_object_with_primary_key(2);
1476
        realm->commit_transaction();
1477

1478
        auto scope = audit->begin_scope("large");
1479
        for (int i = 0; i < 150'000; ++i) {
1480
            Object(realm, obj1);
1481
            Object(realm, obj2);
1482
        }
1483
        audit->end_scope(scope, assert_no_error);
1484
        audit->wait_for_completion();
1485

1486
        auto events = get_audit_events(test_session);
1487
        REQUIRE(events.size() == 300'000);
1488
    }
1489
#endif
1490
}
15✔
1491

1492
TEST_CASE("audit realm sharding", "[sync][pbs][audit]") {
3✔
1493
    // Don't start the server immediately so that we're forced to accumulate
1494
    // a lot of local unuploaded data.
1495
    TestSyncManager test_session{{}, {.start_immediately = false}};
3✔
1496

1497
    SyncTestFile config(test_session, "parent");
3✔
1498
    config.automatic_change_notifications = false;
3✔
1499
    config.schema_version = 1;
3✔
1500
    config.schema = Schema{
3✔
1501
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
3✔
1502
    };
3✔
1503
    config.audit_config = std::make_shared<AuditConfig>();
3✔
1504
    config.audit_config->logger = audit_logger;
3✔
1505
    auto realm = Realm::get_shared_realm(config);
3✔
1506
    auto audit = realm->audit_context();
3✔
1507
    REQUIRE(audit);
3!
1508

1509
    auto table = realm->read_group().get_table("class_object");
3✔
1510

1511
    // We open in proper sync mode to let the audit context initialize from that,
1512
    // but we don't actually want the realm to be synchronizing
1513
    realm->sync_session()->close();
3✔
1514

1515
    // Set a small shard size so that we don't have to write an absurd
1516
    // amount of data to test this
1517
    audit_test_hooks::set_maximum_shard_size(32 * 1024);
3✔
1518
    auto cleanup = util::make_scope_exit([]() noexcept {
3✔
1519
        audit_test_hooks::set_maximum_shard_size(256 * 1024 * 1024);
3✔
1520
    });
3✔
1521

1522
    realm->begin_transaction();
3✔
1523
    std::vector<Obj> objects;
3✔
1524
    for (int i = 0; i < 2000; ++i)
6,003✔
1525
        objects.push_back(table->create_object_with_primary_key(i));
6,000✔
1526
    realm->commit_transaction();
3✔
1527

1528
    // Write a lot of audit scopes while unable to sync
1529
    for (int i = 0; i < 50; ++i) {
153✔
1530
        auto scope = audit->begin_scope(util::format("scope %1", i));
150✔
1531
        Results(realm, table->where()).snapshot();
150✔
1532
        audit->end_scope(scope, assert_no_error);
150✔
1533
    }
150✔
1534
    audit->wait_for_completion();
3✔
1535

1536
    // There should now be several unuploaded Realms in the local client
1537
    // directory
1538
    auto root = test_session.base_file_path() + "/realm-audit/app_id/test/audit";
3✔
1539
    std::string file_name;
3✔
1540
    util::DirScanner dir(root);
3✔
1541
    size_t file_count = 0;
3✔
1542
    size_t unlocked_file_count = 0;
3✔
1543
    while (dir.next(file_name)) {
84✔
1544
        if (!StringData(file_name).ends_with(".realm"))
81✔
1545
            continue;
54✔
1546
        ++file_count;
27✔
1547
        // The upper limit is a soft cap, so files might be a bit bigger
1548
        // than it. 1 MB errs on the side of never getting spurious failures.
1549
        REQUIRE(util::File::get_size_static(root + "/" + file_name) < 1024 * 1024);
27!
1550
        if (DB::call_with_lock(root + "/" + file_name, [](auto&) {})) {
27✔
1551
            ++unlocked_file_count;
21✔
1552
        }
21✔
1553
    }
27✔
1554
    // The exact number of shards is fuzzy due to the combination of the
1555
    // soft cap on size and the fact that changesets are compressed, but
1556
    // there definitely should be more than one.
1557
    REQUIRE(file_count > 2);
3!
1558
    // There should be exactly two files open still: the one we're currently
1559
    // writing to, and the first one which we wrote and are waiting for the
1560
    // upload to complete.
1561
    REQUIRE(unlocked_file_count == file_count - 2);
3!
1562

1563
    auto get_sorted_events = [&] {
3✔
1564
        auto events = get_audit_events(test_session, false);
3✔
1565
        // The events might be out of order because there's no guaranteed order
1566
        // for both uploading the Realms and for opening the uploaded Realms.
1567
        // Once sorted by timestamp the scopes should be in order, though.
1568
        sort_events(events);
3✔
1569
        return events;
3✔
1570
    };
3✔
1571
    auto close_all_sessions = [&] {
2✔
1572
        realm->close();
2✔
1573
        realm = nullptr;
2✔
1574
        auto sync_manager = test_session.sync_manager();
2✔
1575
        for (auto& session : sync_manager->get_all_sessions()) {
2✔
1576
            session->shutdown_and_wait();
2✔
1577
        }
2✔
1578
    };
2✔
1579

1580
    SECTION("start server with existing session open") {
3✔
1581
        test_session.sync_server().start();
1✔
1582
        audit->wait_for_uploads();
1✔
1583

1584
        auto events = get_sorted_events();
1✔
1585
        REQUIRE(events.size() == 50);
1!
1586
        for (int i = 0; i < 50; ++i) {
51✔
1587
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1588
        }
50✔
1589

1590
        // There should be exactly one remaining local Realm file (the currently
1591
        // open one that hasn't hit the size limit yet)
1592
        size_t remaining_realms = 0;
1✔
1593
        util::DirScanner dir(root);
1✔
1594
        while (dir.next(file_name)) {
20✔
1595
            if (StringData(file_name).ends_with(".realm"))
19✔
1596
                ++remaining_realms;
1✔
1597
        }
19✔
1598
        REQUIRE(remaining_realms == 1);
1!
1599
    }
1✔
1600

1601
    SECTION("trigger uploading by opening a new Realm") {
3✔
1602
        close_all_sessions();
1✔
1603
        test_session.sync_server().start();
1✔
1604

1605
        // Open a different Realm with the same user and audit prefix
1606
        SyncTestFile config(test_session, "other");
1✔
1607
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1608
        config.audit_config->logger = audit_logger;
1✔
1609
        auto realm = Realm::get_shared_realm(config);
1✔
1610
        auto audit2 = realm->audit_context();
1✔
1611
        REQUIRE(audit2);
1!
1612
        audit2->wait_for_uploads();
1✔
1613

1614
        auto events = get_sorted_events();
1✔
1615
        REQUIRE(events.size() == 50);
1!
1616
        for (int i = 0; i < 50; ++i) {
51✔
1617
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1618
        }
50✔
1619

1620
        // There should be no remaining local Realm files because we haven't
1621
        // made the new audit context open a Realm yet
1622
        util::DirScanner dir(root);
1✔
1623
        while (dir.next(file_name)) {
19✔
1624
            REQUIRE_FALSE(StringData(file_name).ends_with(".realm"));
18!
1625
        }
18✔
1626
    }
1✔
1627

1628
    SECTION("uploading is per audit prefix") {
3✔
1629
        close_all_sessions();
1✔
1630
        test_session.sync_server().start();
1✔
1631

1632
        // Open the same Realm with a different audit prefix
1633
        SyncTestFile config(test_session, "parent");
1✔
1634
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1635
        config.audit_config->logger = audit_logger;
1✔
1636
        config.audit_config->partition_value_prefix = "other";
1✔
1637
        auto realm = Realm::get_shared_realm(config);
1✔
1638
        auto audit2 = realm->audit_context();
1✔
1639
        REQUIRE(audit2);
1!
1640
        audit2->wait_for_uploads();
1✔
1641

1642
        // Should not have uploaded any of the old events
1643
        auto events = get_sorted_events();
1✔
1644
        REQUIRE(events.size() == 0);
1!
1645
    }
1✔
1646
}
3✔
1647

1648
#if REALM_ENABLE_AUTH_TESTS
1649
static void generate_event(std::shared_ptr<Realm> realm, int call = 0)
1650
{
31✔
1651
    auto table = realm->read_group().get_table("class_object");
31✔
1652
    auto audit = realm->audit_context();
31✔
1653

1654
    realm->begin_transaction();
31✔
1655
    table->create_object_with_primary_key(call + 1).set_all(2);
31✔
1656
    realm->commit_transaction();
31✔
1657

1658
    auto scope = audit->begin_scope("scope");
31✔
1659
    Object(realm, table->get_object(call));
31✔
1660
    audit->end_scope(scope, assert_no_error);
31✔
1661
}
31✔
1662

1663
TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") {
12✔
1664
    // None of these tests need a deterministic clock, but the server rounding
1665
    // timestamps to milliseconds can result in events not having monotonically
1666
    // increasing timestamps with an actual clock.
1667
    TestClock clock;
12✔
1668

1669
    const Schema schema{
12✔
1670
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
12✔
1671
        {"AuditEvent",
12✔
1672
         {
12✔
1673
             {"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
12✔
1674
             {"timestamp", PropertyType::Date},
12✔
1675
             {"activity", PropertyType::String},
12✔
1676
             {"event", PropertyType::String | PropertyType::Nullable},
12✔
1677
             {"data", PropertyType::String | PropertyType::Nullable},
12✔
1678
             {"metadata 1", PropertyType::String | PropertyType::Nullable},
12✔
1679
             {"metadata 2", PropertyType::String | PropertyType::Nullable},
12✔
1680
         }}};
12✔
1681
    const Schema no_audit_event_schema{
12✔
1682
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}}};
12✔
1683

1684
    auto app_create_config = default_app_config();
12✔
1685
    app_create_config.schema = schema;
12✔
1686
    app_create_config.dev_mode_enabled = false;
12✔
1687
    TestAppSession session = create_app(app_create_config);
12✔
1688

1689
    SyncTestFile config(session.app()->current_user(), bson::Bson("default"));
12✔
1690
    config.automatic_change_notifications = false;
12✔
1691
    config.schema = schema;
12✔
1692
    config.audit_config = std::make_shared<AuditConfig>();
12✔
1693
    config.audit_config->logger = audit_logger;
12✔
1694

1695
    auto expect_error = [&](auto&& config, auto&& fn) -> SyncError {
3✔
1696
        std::mutex mutex;
3✔
1697
        util::Optional<SyncError> error;
3✔
1698
        config.audit_config->sync_error_handler = [&](SyncError e) {
3✔
1699
            std::lock_guard lock(mutex);
3✔
1700
            error = e;
3✔
1701
        };
3✔
1702

1703
        auto realm = Realm::get_shared_realm(config);
3✔
1704
        fn(realm, 0);
3✔
1705

1706
        timed_wait_for(
3✔
1707
            [&] {
3,818✔
1708
                std::lock_guard lock(mutex);
3,818✔
1709
                return (bool)error;
3,818✔
1710
            },
3,818✔
1711
            std::chrono::seconds(30));
3✔
1712
        REQUIRE(bool(error));
3!
1713
        return *error;
3✔
1714
    };
3✔
1715

1716
    SECTION("basic functionality") {
12✔
1717
        auto realm = Realm::get_shared_realm(config);
1✔
1718
        realm->sync_session()->close();
1✔
1719
        generate_event(realm);
1✔
1720

1721
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1722
        REQUIRE(events.size() == 1);
1!
1723
        REQUIRE(events[0].activity == "scope");
1!
1724
        REQUIRE(events[0].event == "read");
1!
1725
        REQUIRE(!events[0].timestamp.is_null()); // FIXME
1!
1726
        REQUIRE(events[0].data == json({{"type", "object"}, {"value", {{{"_id", 1}, {"value", 2}}}}}));
1!
1727
    }
1✔
1728

1729
    SECTION("different user from parent Realm") {
12✔
1730
        auto sync_user = session.app()->current_user();
1✔
1731
        create_user_and_log_in(session.app());
1✔
1732
        auto audit_user = session.app()->current_user();
1✔
1733
        config.audit_config->audit_user = audit_user;
1✔
1734
        auto realm = Realm::get_shared_realm(config);
1✔
1735
        // If audit uses the sync user this'll make it fail as that user is logged out
1736
        sync_user->log_out();
1✔
1737

1738
        generate_event(realm);
1✔
1739
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1740
    }
1✔
1741

1742
    SECTION("different app from parent Realm") {
12✔
1743
        auto audit_user = session.app()->current_user();
1✔
1744

1745
        // Create an app which does not include AuditEvent in the schema so that
1746
        // things will break if audit tries to use it
1747
        app_create_config.schema = no_audit_event_schema;
1✔
1748
        TestAppSession session_2 = create_app(app_create_config);
1✔
1749
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1750
        config.schema = no_audit_event_schema;
1✔
1751
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1752
        config.audit_config->audit_user = audit_user;
1✔
1753

1754
        auto realm = Realm::get_shared_realm(config);
1✔
1755
        generate_event(realm);
1✔
1756
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1757
    }
1✔
1758

1759
    SECTION("valid metadata properties") {
12✔
1760
        auto realm = Realm::get_shared_realm(config);
1✔
1761
        generate_event(realm, 0);
1✔
1762
        realm->audit_context()->update_metadata({{"metadata 1", "value 1"}});
1✔
1763
        generate_event(realm, 1);
1✔
1764
        realm->audit_context()->update_metadata({{"metadata 2", "value 2"}});
1✔
1765
        generate_event(realm, 2);
1✔
1766
        realm->audit_context()->update_metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}});
1✔
1767
        generate_event(realm, 3);
1✔
1768

1769
        using Metadata = std::map<std::string, std::string>;
1✔
1770
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 4);
1✔
1771
        REQUIRE(events[0].metadata.empty());
1!
1772
        REQUIRE(events[1].metadata == Metadata({{"metadata 1", "value 1"}}));
1!
1773
        REQUIRE(events[2].metadata == Metadata({{"metadata 2", "value 2"}}));
1!
1774
        REQUIRE(events[3].metadata == Metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}}));
1!
1775
    }
1✔
1776

1777
    SECTION("invalid metadata properties") {
12✔
1778
        config.audit_config->metadata = {{"invalid key", "value"}};
1✔
1779
        auto error = expect_error(config, generate_event);
1✔
1780
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1781
        REQUIRE(error.is_fatal);
1!
1782
    }
1✔
1783

1784
    SECTION("removed sync user") {
12✔
1785
        create_user_and_log_in(session.app());
1✔
1786
        auto audit_user = session.app()->current_user();
1✔
1787
        config.audit_config->audit_user = audit_user;
1✔
1788
        auto realm = Realm::get_shared_realm(config);
1✔
1789
        session.sync_manager()->remove_user(audit_user->identity());
1✔
1790

1791
        auto audit = realm->audit_context();
1✔
1792
        auto scope = audit->begin_scope("scope");
1✔
1793
        realm->begin_transaction();
1✔
1794
        auto table = realm->read_group().get_table("class_object");
1✔
1795
        table->create_object_with_primary_key(1).set_all(2);
1✔
1796
        realm->commit_transaction();
1✔
1797

1798
        audit->end_scope(scope, [&](auto error) {
1✔
1799
            REQUIRE(error);
1!
1800
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "user has been removed");
1✔
1801
        });
1✔
1802
        audit->wait_for_completion();
1✔
1803
    }
1✔
1804

1805
    SECTION("AuditEvent missing from server schema") {
12✔
1806
        app_create_config.schema = no_audit_event_schema;
1✔
1807
        TestAppSession session_2 = create_app(app_create_config);
1✔
1808
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1809
        config.schema = no_audit_event_schema;
1✔
1810
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1811

1812
        auto error = expect_error(config, generate_event);
1✔
1813
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1814
        REQUIRE(error.is_fatal);
1!
1815
    }
1✔
1816

1817
    SECTION("incoming changesets are discarded") {
12✔
1818
        app::MongoClient remote_client = session.app()->current_user()->mongo_client("BackingDB");
2✔
1819
        app::MongoDatabase db = remote_client.db(session.app_session().config.mongo_dbname);
2✔
1820
        app::MongoCollection collection = db["AuditEvent"];
2✔
1821

1822
        SECTION("objects deleted on server") {
2✔
1823
            // Because EraseObject is idempotent, this case actually just works
1824
            // without any special logic.
1825
            auto delete_one = [&] {
10✔
1826
                uint64_t deleted = 0;
10✔
1827
                while (deleted == 0) {
75✔
1828
                    collection.delete_one({},
65✔
1829
                                          [&](util::Optional<uint64_t>&& count, util::Optional<app::AppError> error) {
65✔
1830
                                              REQUIRE_FALSE(error);
65!
1831
                                              deleted = *count;
65✔
1832
                                          });
65✔
1833
                    if (deleted == 0) {
65✔
1834
                        millisleep(100); // slow down the number of retries
55✔
1835
                    }
55✔
1836
                }
65✔
1837
            };
10✔
1838

1839
            auto realm = Realm::get_shared_realm(config);
1✔
1840
            for (int i = 0; i < 10; ++i) {
11✔
1841
                generate_event(realm, i);
10✔
1842
                delete_one();
10✔
1843
            }
10✔
1844
        }
1✔
1845

1846
        SECTION("objects modified on server") {
2✔
1847
            // UpdateObject throws bad_transaction_log() if the object doesn't
1848
            // exist locally, so this will break if we try to apply the changesets
1849
            // from the server.
1850
            const bson::BsonDocument filter{{"event", "read"}};
1✔
1851
            const bson::BsonDocument update{{"$set", bson::BsonDocument{{"event", "processed"}}}};
1✔
1852
            auto update_one = [&] {
10✔
1853
                int32_t count = 0;
10✔
1854
                while (count == 0) {
80✔
1855
                    collection.update_one(
70✔
1856
                        filter, update,
70✔
1857
                        [&](app::MongoCollection::UpdateResult result, util::Optional<app::AppError> error) {
70✔
1858
                            REQUIRE_FALSE(error);
70!
1859
                            count = result.modified_count;
70✔
1860
                        });
70✔
1861
                    if (count == 0) {
70✔
1862
                        millisleep(100); // slow down the number of retries
60✔
1863
                    }
60✔
1864
                }
70✔
1865
            };
10✔
1866

1867
            auto realm = Realm::get_shared_realm(config);
1✔
1868
            for (int i = 0; i < 10; ++i) {
11✔
1869
                generate_event(realm, i);
10✔
1870
                update_one();
10✔
1871
            }
10✔
1872
        }
1✔
1873
    }
2✔
1874

1875
    SECTION("flexible sync") {
12✔
1876
        app::FLXSyncTestHarness harness("audit");
3✔
1877
        create_user_and_log_in(harness.app());
3✔
1878

1879
        SECTION("auditing a flexible sync realm without specifying an audit user throws an exception") {
3✔
1880
            SyncTestFile config(harness.app()->current_user(), {}, SyncConfig::FLXSyncEnabled{});
1✔
1881
            config.audit_config = std::make_shared<AuditConfig>();
1✔
1882
            REQUIRE_THROWS_CONTAINING(Realm::get_shared_realm(config), "partition-based sync");
1✔
1883
        }
1✔
1884

1885
        SECTION("auditing with a flexible sync user reports a sync error") {
3✔
1886
            config.audit_config->audit_user = harness.app()->current_user();
1✔
1887
            auto error = expect_error(config, generate_event);
1✔
1888
            REQUIRE_THAT(error.status.reason(),
1✔
1889
                         Catch::Matchers::ContainsSubstring(
1✔
1890
                             "Client connected using partition-based sync when app is using flexible sync"));
1✔
1891
            REQUIRE(error.is_fatal);
1!
1892
        }
1✔
1893

1894
        SECTION("auditing a flexible sync realm with a pbs audit user works") {
3✔
1895
            config.audit_config->audit_user = config.sync_config->user;
1✔
1896
            config.sync_config->user = harness.app()->current_user();
1✔
1897
            config.sync_config->flx_sync_requested = true;
1✔
1898
            config.sync_config->partition_value.clear();
1✔
1899

1900
            auto realm = Realm::get_shared_realm(config);
1✔
1901
            {
1✔
1902
                auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
1✔
1903
                mut_subs.insert_or_assign(Query(realm->read_group().get_table("class_object")));
1✔
1904
                std::move(mut_subs).commit();
1✔
1905
            }
1✔
1906

1907
            realm->sync_session()->force_close();
1✔
1908
            generate_event(realm, 0);
1✔
1909
            get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1910
        }
1✔
1911
    }
3✔
1912

1913
#if 0 // This test takes ~10 minutes to run
1914
    SECTION("large audit scope") {
1915
        auto realm = Realm::get_shared_realm(config);
1916
        auto table = realm->read_group().get_table("class_object");
1917
        auto audit = realm->audit_context();
1918

1919
        realm->begin_transaction();
1920
        auto obj1 = table->create_object_with_primary_key(1);
1921
        auto obj2 = table->create_object_with_primary_key(2);
1922
        realm->commit_transaction();
1923

1924
        auto scope = audit->begin_scope("large");
1925
        for (int i = 0; i < 150'000; ++i) {
1926
            Object(realm, obj1);
1927
            Object(realm, obj2);
1928
        }
1929
        audit->end_scope(scope, assert_no_error);
1930

1931
        REQUIRE(get_audit_events_from_baas(session, *session.app()->current_user(), 300'000).size() == 300'000);
1932
    }
1933
#endif
1934
}
12✔
1935
#endif // REALM_ENABLE_AUTH_TESTS
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc