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

realm / realm-core / 2130

13 Mar 2024 04:51AM UTC coverage: 92.078% (+0.2%) from 91.833%
2130

push

Evergreen

web-flow
Merge pull request #7402 from realm/tg/obj-perf

Make Obj trivial and add a separate ObjCollectionParent type

94732 of 174812 branches covered (54.19%)

531 of 559 new or added lines in 22 files covered. (94.99%)

45 existing lines in 13 files now uncovered.

244506 of 265543 relevant lines covered (92.08%)

5982312.84 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
namespace {
56
class NullLogger : public util::Logger {
57
    // Since we don't want to log anything, do_log() does nothing
58
    void do_log(const util::LogCategory&, Level, const std::string&) override {}
1,702✔
59
};
60
} // namespace
61

62
static auto audit_logger =
63
#ifdef AUDIT_LOG_LEVEL
64
    std::make_shared<util::StderrLogger>(AUDIT_LOG_LEVEL);
65
#else
66
    std::make_shared<NullLogger>();
67
#endif
68

69
namespace {
70

71
struct AuditEvent {
72
    std::string activity;
73
    util::Optional<std::string> event;
74
    json data;
75
    util::Optional<std::string> raw_data;
76
    Timestamp timestamp;
77
    std::map<std::string, std::string> metadata;
78
};
79

80
util::Optional<std::string> to_optional_string(StringData sd)
81
{
393✔
82
    return sd ? util::Optional<std::string>(sd) : none;
390✔
83
}
393✔
84

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

106
    // Stop the sync server so that we can safely inspect its Realm files
107
    auto& server = manager.sync_server();
50✔
108
    server.stop();
50✔
109

110
    std::vector<AuditEvent> events;
50✔
111

112
    // Iterate over all of the audit Realm files in the server's storage
113
    // directory, opening them in read-only mode (as they use Server history),
114
    // and slurp the audit events out of them.
115
    auto root = server.local_root_dir();
50✔
116
    std::string file_name;
50✔
117
    util::DirScanner dir(root);
50✔
118
    while (dir.next(file_name)) {
368✔
119
        StringData sd(file_name);
318✔
120
        if (!sd.begins_with("audit-") || !sd.ends_with(".realm"))
318✔
121
            continue;
254✔
122

123
        Group g(root + "/" + file_name);
64✔
124
        auto table = g.get_table("class_AuditEvent");
64✔
125
        if (!table)
64✔
UNCOV
126
            continue;
×
127

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

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

166
    return events;
50✔
167
}
50✔
168

169
void sort_events(std::vector<AuditEvent>& events)
170
{
8✔
171
    std::sort(events.begin(), events.end(), [](auto& a, auto& b) {
537✔
172
        return a.timestamp < b.timestamp;
537✔
173
    });
537✔
174
}
8✔
175

176
#if REALM_ENABLE_AUTH_TESTS
177
static std::vector<AuditEvent> get_audit_events_from_baas(TestAppSession& session, SyncUser& user,
178
                                                          size_t expected_count)
179
{
5✔
180
    static const std::set<std::string> nonmetadata_fields = {"activity", "event", "data", "realm_id"};
5✔
181

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

206
// Check that the given key is present and the value is null
207
#define REQUIRE_NULL(v, k)                                                                                           \
208
    do {                                                                                                             \
8✔
209
        REQUIRE(v.contains(k));                                                                                      \
8✔
210
        REQUIRE(v[k] == nullptr);                                                                                    \
8✔
211
    } while (0)
8✔
212

213
#define REQUIRE_SET_EQUAL(a, ...)                                                                                    \
214
    do {                                                                                                             \
19✔
215
        json actual = (a);                                                                                           \
19✔
216
        json expected = __VA_ARGS__;                                                                                 \
19✔
217
        std::sort(actual.begin(), actual.end());                                                                     \
19✔
218
        std::sort(expected.begin(), expected.end());                                                                 \
19✔
219
        REQUIRE(actual == expected);                                                                                 \
19✔
220
    } while (0)
19✔
221

222
class CustomSerializer : public AuditObjectSerializer {
223
public:
224
    const Obj* expected_obj = nullptr;
225
    bool error = false;
226
    size_t completion_count = 0;
227

228
    void to_json(json& out, const Obj& obj) override
229
    {
169✔
230
        if (error) {
169✔
231
            throw std::runtime_error("custom serialization error");
1✔
232
        }
1✔
233
        if (expected_obj) {
168✔
234
            REQUIRE(expected_obj->get_key() == obj.get_key());
2!
235
            REQUIRE(expected_obj->get_table()->get_key() == obj.get_table()->get_key());
2!
236
            out["obj"] = obj.get_key().value;
2✔
237
            out["table"] = obj.get_table()->get_key().value;
2✔
238
        }
2✔
239
        else {
166✔
240
            AuditObjectSerializer::to_json(out, obj);
166✔
241
        }
166✔
242
    }
168✔
243

244
    void scope_complete() override
245
    {
40✔
246
        ++completion_count;
40✔
247
    }
40✔
248
};
249

250
void assert_no_error(std::exception_ptr e)
251
{
433✔
252
    REALM_ASSERT(!e);
433✔
253
}
433✔
254

255
struct TestClock {
256
    std::atomic<int32_t> timestamp = 1000;
257
    TestClock()
258
    {
27✔
259
        audit_test_hooks::set_clock([&] {
280✔
260
            auto now = timestamp.fetch_add(1);
280✔
261
            return Timestamp(now, now);
280✔
262
        });
280✔
263
    }
27✔
264
    ~TestClock()
265
    {
27✔
266
        audit_test_hooks::set_clock(nullptr);
27✔
267
    }
27✔
268
};
269

270
} // namespace
271

272
TEST_CASE("audit object serialization", "[sync][pbs][audit]") {
36✔
273
    TestSyncManager test_session;
36✔
274
    SyncTestFile config(test_session, "parent");
36✔
275
    config.automatic_change_notifications = false;
36✔
276
    config.schema_version = 1;
36✔
277
    config.schema = Schema{
36✔
278
        {"object",
36✔
279
         {{"_id", PropertyType::Int, Property::IsPrimary{true}},
36✔
280

281
          {"int", PropertyType::Int | PropertyType::Nullable},
36✔
282
          {"bool", PropertyType::Bool | PropertyType::Nullable},
36✔
283
          {"string", PropertyType::String | PropertyType::Nullable},
36✔
284
          {"data", PropertyType::Data | PropertyType::Nullable},
36✔
285
          {"date", PropertyType::Date | PropertyType::Nullable},
36✔
286
          {"float", PropertyType::Float | PropertyType::Nullable},
36✔
287
          {"double", PropertyType::Double | PropertyType::Nullable},
36✔
288
          {"mixed", PropertyType::Mixed | PropertyType::Nullable},
36✔
289
          {"objectid", PropertyType::ObjectId | PropertyType::Nullable},
36✔
290
          {"decimal", PropertyType::Decimal | PropertyType::Nullable},
36✔
291
          {"uuid", PropertyType::UUID | PropertyType::Nullable},
36✔
292

293
          {"int list", PropertyType::Int | PropertyType::Nullable | PropertyType::Array},
36✔
294
          {"int set", PropertyType::Int | PropertyType::Nullable | PropertyType::Set},
36✔
295
          {"int dictionary", PropertyType::Int | PropertyType::Nullable | PropertyType::Dictionary},
36✔
296

297
          {"object", PropertyType::Object | PropertyType::Nullable, "target"},
36✔
298
          {"object list", PropertyType::Object | PropertyType::Array, "target"},
36✔
299
          {"object set", PropertyType::Object | PropertyType::Set, "target"},
36✔
300
          {"object dictionary", PropertyType::Object | PropertyType::Nullable | PropertyType::Dictionary, "target"},
36✔
301

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

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

323
    auto table = realm->read_group().get_table("class_object");
36✔
324
    auto target_table = realm->read_group().get_table("class_target");
36✔
325
    CppContext context(realm);
36✔
326

327
    auto populate_object = [&](Obj& obj) {
4✔
328
        obj.set("int", 1);
4✔
329
        obj.set("bool", true);
4✔
330
        obj.set("string", "abc");
4✔
331
        obj.set("data", BinaryData("abc", 3));
4✔
332
        obj.set("date", Timestamp(123, 456));
4✔
333
        obj.set("float", 1.1f);
4✔
334
        obj.set("double", 2.2);
4✔
335
        obj.set("mixed", Mixed(10));
4✔
336
        obj.set("objectid", ObjectId("000000000000000000000001"));
4✔
337
        obj.set("uuid", UUID("00000000-0000-0000-0000-000000000001"));
4✔
338

339
        auto int_list = obj.get_list<util::Optional<int64_t>>("int list");
4✔
340
        int_list.add(1);
4✔
341
        int_list.add(2);
4✔
342
        int_list.add(3);
4✔
343
        int_list.add(none);
4✔
344

345
        auto int_set = obj.get_set<util::Optional<int64_t>>("int set");
4✔
346
        int_set.insert(1);
4✔
347
        int_set.insert(2);
4✔
348
        int_set.insert(3);
4✔
349
        int_set.insert(none);
4✔
350

351
        auto int_dictionary = obj.get_dictionary("int dictionary");
4✔
352
        int_dictionary.insert("1", 1);
4✔
353
        int_dictionary.insert("2", 2);
4✔
354
        int_dictionary.insert("3", 3);
4✔
355
        int_dictionary.insert("4", none);
4✔
356

357
        auto obj_list = obj.get_linklist("object list");
4✔
358
        obj_list.add(target_table->create_object_with_primary_key(1).set_all(1).get_key());
4✔
359
        obj_list.add(target_table->create_object_with_primary_key(2).set_all(2).get_key());
4✔
360
        obj_list.add(target_table->create_object_with_primary_key(3).set_all(3).get_key());
4✔
361

362
        auto obj_set = obj.get_linkset(obj.get_table()->get_column_key("object set"));
4✔
363
        obj_set.insert(target_table->create_object_with_primary_key(4).set_all(4).get_key());
4✔
364
        obj_set.insert(target_table->create_object_with_primary_key(5).set_all(5).get_key());
4✔
365
        obj_set.insert(target_table->create_object_with_primary_key(6).set_all(6).get_key());
4✔
366

367
        auto obj_dict = obj.get_dictionary("object dictionary");
4✔
368
        obj_dict.insert("a", target_table->create_object_with_primary_key(7).set_all(7).get_key());
4✔
369
        obj_dict.insert("b", target_table->create_object_with_primary_key(8).set_all(8).get_key());
4✔
370
        obj_dict.insert("c", target_table->create_object_with_primary_key(9).set_all(9).get_key());
4✔
371

372
        auto embedded_list = obj.get_linklist("embedded object list");
4✔
373
        embedded_list.create_and_insert_linked_object(0).set_all(1);
4✔
374
        embedded_list.create_and_insert_linked_object(1).set_all(2);
4✔
375
        embedded_list.create_and_insert_linked_object(2).set_all(3);
4✔
376

377
        auto embedded_dict = obj.get_dictionary("embedded object dictionary");
4✔
378
        embedded_dict.create_and_insert_linked_object("d").set_all(4);
4✔
379
        embedded_dict.create_and_insert_linked_object("e").set_all(5);
4✔
380
        embedded_dict.create_and_insert_linked_object("f").set_all(6);
4✔
381

382
        return obj;
4✔
383
    };
4✔
384

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

407
    SECTION("default object serialization") {
36✔
408
        realm->begin_transaction();
1✔
409
        auto obj = table->create_object_with_primary_key(2);
1✔
410
        populate_object(obj);
1✔
411
        realm->commit_transaction();
1✔
412

413
        auto scope = audit->begin_scope("scope");
1✔
414
        Object object(realm, obj);
1✔
415
        audit->end_scope(scope, assert_no_error);
1✔
416
        audit->wait_for_completion();
1✔
417

418
        auto events = get_audit_events(test_session);
1✔
419
        REQUIRE(events.size() == 1);
1!
420
        auto& event = events[0];
1✔
421
        REQUIRE(event.event == "read");
1!
422
        REQUIRE(event.activity == "scope");
1!
423
        REQUIRE(!event.timestamp.is_null());
1!
424

425
        REQUIRE(event.data["type"] == "object");
1!
426
        auto value = event.data["value"];
1✔
427
        REQUIRE(value.size() == 1);
1!
428
        validate_default_values(value[0]);
1✔
429
    }
1✔
430

431
    SECTION("custom object serialization") {
36✔
432
        realm->begin_transaction();
1✔
433
        auto obj1 = table->create_object_with_primary_key(2);
1✔
434
        auto obj2 = table->create_object_with_primary_key(3);
1✔
435
        realm->commit_transaction();
1✔
436

437
        serializer->expected_obj = &obj1;
1✔
438

439
        auto scope = audit->begin_scope("scope 1");
1✔
440
        Object(realm, obj1);
1✔
441
        audit->end_scope(scope, assert_no_error);
1✔
442
        audit->wait_for_completion();
1✔
443
        REQUIRE(serializer->completion_count == 1);
1!
444

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

450
        serializer->expected_obj = &obj2;
1✔
451

452
        scope = audit->begin_scope("scope 2");
1✔
453
        Object(realm, obj2);
1✔
454
        audit->end_scope(scope, assert_no_error);
1✔
455
        audit->wait_for_completion();
1✔
456
        REQUIRE(serializer->completion_count == 3);
1!
457

458
        auto events = get_audit_events(test_session);
1✔
459
        REQUIRE(events.size() == 2);
1!
460

461
        REQUIRE(events[0].activity == "scope 1");
1!
462
        REQUIRE(events[1].activity == "scope 2");
1!
463
        REQUIRE(events[0].data ==
1!
464
                json({{"type", "object"},
1✔
465
                      {"value", json::array({{{"obj", obj1.get_key().value}, {"table", table->get_key().value}}})}}));
1✔
466
        REQUIRE(events[1].data ==
1!
467
                json({{"type", "object"},
1✔
468
                      {"value", json::array({{{"obj", obj2.get_key().value}, {"table", table->get_key().value}}})}}));
1✔
469
    }
1✔
470

471
    SECTION("custom serialization error reporting") {
36✔
472
        serializer->error = true;
1✔
473

474
        realm->begin_transaction();
1✔
475
        auto obj = table->create_object_with_primary_key(2);
1✔
476
        realm->commit_transaction();
1✔
477
        auto scope = audit->begin_scope("scope");
1✔
478
        Object(realm, obj);
1✔
479
        audit->end_scope(scope, [](auto error) {
1✔
480
            REQUIRE(error);
1!
481
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "custom serialization error");
1✔
482
        });
1✔
483
        audit->wait_for_completion();
1✔
484
    }
1✔
485

486
    SECTION("write transaction serialization") {
36✔
487
        SECTION("create object") {
6✔
488
            auto scope = audit->begin_scope("scope");
1✔
489
            realm->begin_transaction();
1✔
490
            auto obj = table->create_object_with_primary_key(2);
1✔
491
            populate_object(obj);
1✔
492
            realm->commit_transaction();
1✔
493
            audit->end_scope(scope, assert_no_error);
1✔
494
            audit->wait_for_completion();
1✔
495

496
            auto events = get_audit_events(test_session);
1✔
497
            REQUIRE(events.size() == 1);
1!
498
            auto& event = events[0];
1✔
499
            REQUIRE(event.event == "write");
1!
500
            REQUIRE(event.activity == "scope");
1!
501
            REQUIRE(!event.timestamp.is_null());
1!
502

503
            REQUIRE(event.data.size() == 2);
1!
504
            auto& object_changes = event.data["object"];
1✔
505
            REQUIRE(object_changes.size() == 1);
1!
506
            REQUIRE(object_changes["insertions"].size() == 1);
1!
507
            validate_default_values(object_changes["insertions"][0]);
1✔
508

509
            // target table should have 9 insertions with _id == value
510
            REQUIRE(event.data["target"]["insertions"].size() == 9);
1!
511
            for (int i = 0; i < 9; ++i) {
10✔
512
                REQUIRE(event.data["target"]["insertions"][i] == json({{"_id", i + 1}, {"value", i + 1}}));
9!
513
            }
9✔
514
        }
1✔
515

516
        SECTION("modify object") {
6✔
517
            realm->begin_transaction();
1✔
518
            auto obj = table->create_object_with_primary_key(2);
1✔
519
            populate_object(obj);
1✔
520
            realm->commit_transaction();
1✔
521

522
            auto scope = audit->begin_scope("scope");
1✔
523
            realm->begin_transaction();
1✔
524
            obj.set("int", 3);
1✔
525
            obj.set("bool", true);
1✔
526
            realm->commit_transaction();
1✔
527
            audit->end_scope(scope, assert_no_error);
1✔
528
            audit->wait_for_completion();
1✔
529

530
            auto events = get_audit_events(test_session);
1✔
531
            REQUIRE(events.size() == 1);
1!
532
            auto& event = events[0];
1✔
533
            REQUIRE(event.data.size() == 1);
1!
534
            REQUIRE(event.data["object"].size() == 1);
1!
535
            REQUIRE(event.data["object"]["modifications"].size() == 1);
1!
536
            auto& modifications = event.data["object"]["modifications"][0];
1✔
537
            REQUIRE(modifications.size() == 2);
1!
538
            REQUIRE(modifications["newValue"].size() == 1);
1!
539
            REQUIRE(modifications["newValue"]["int"] == 3);
1!
540
            // note: bool is not reported because it was assigned to itself
541
            validate_default_values(modifications["oldValue"]);
1✔
542
        }
1✔
543

544
        SECTION("delete object") {
6✔
545
            realm->begin_transaction();
1✔
546
            auto obj = table->create_object_with_primary_key(2);
1✔
547
            populate_object(obj);
1✔
548
            realm->commit_transaction();
1✔
549

550
            auto scope = audit->begin_scope("scope");
1✔
551
            realm->begin_transaction();
1✔
552
            obj.remove();
1✔
553
            realm->commit_transaction();
1✔
554
            audit->end_scope(scope, assert_no_error);
1✔
555
            audit->wait_for_completion();
1✔
556

557
            auto events = get_audit_events(test_session);
1✔
558
            REQUIRE(events.size() == 1);
1!
559
            auto& event = events[0];
1✔
560
            REQUIRE(event.data.size() == 1);
1!
561
            REQUIRE(event.data["object"].size() == 1);
1!
562
            REQUIRE(event.data["object"]["deletions"].size() == 1);
1!
563
            validate_default_values(event.data["object"]["deletions"][0]);
1✔
564
        }
1✔
565

566
        SECTION("delete embedded object") {
6✔
567
            realm->begin_transaction();
1✔
568
            auto obj = table->create_object_with_primary_key(2);
1✔
569
            obj.create_and_set_linked_object(obj.get_table()->get_column_key("embedded object")).set_all(100);
1✔
570
            realm->commit_transaction();
1✔
571

572
            auto scope = audit->begin_scope("scope");
1✔
573
            realm->begin_transaction();
1✔
574
            obj.get_linked_object("embedded object").remove();
1✔
575
            realm->commit_transaction();
1✔
576
            audit->end_scope(scope, assert_no_error);
1✔
577
            audit->wait_for_completion();
1✔
578

579
            auto events = get_audit_events(test_session);
1✔
580
            REQUIRE(events.size() == 1);
1!
581
            REQUIRE(events[0].data.size() == 1);
1!
582
            REQUIRE(events[0].data["object"].size() == 1);
1!
583
            REQUIRE(events[0].data["object"]["modifications"].size() == 1);
1!
584
            auto& modification = events[0].data["object"]["modifications"][0];
1✔
585
            REQUIRE(modification["newValue"] == json({{"embedded object", nullptr}}));
1!
586
            REQUIRE(modification["oldValue"]["embedded object"] == json({{"value", 100}}));
1!
587
        }
1✔
588

589
        SECTION("mixed changes") {
6✔
590
            realm->begin_transaction();
1✔
591
            std::vector<Obj> objects;
1✔
592
            for (int i = 0; i < 5; ++i)
6✔
593
                objects.push_back(target_table->create_object_with_primary_key(i).set_all(i));
5✔
594
            realm->commit_transaction();
1✔
595

596
            auto scope = audit->begin_scope("scope");
1✔
597
            realm->begin_transaction();
1✔
598

599
            // Mutate then delete should not report the mutate
600
            objects[0].set("value", 100);
1✔
601
            objects[1].set("value", 100);
1✔
602
            objects[2].set("value", 100);
1✔
603
            objects[1].remove();
1✔
604

605
            // Insert then mutate should not report the mutate
606
            auto obj = target_table->create_object_with_primary_key(20);
1✔
607
            obj.set("value", 100);
1✔
608

609
            // Insert then delete should not report the insert or delete
610
            auto obj2 = target_table->create_object_with_primary_key(21);
1✔
611
            obj2.remove();
1✔
612

613
            realm->commit_transaction();
1✔
614
            audit->end_scope(scope, assert_no_error);
1✔
615
            audit->wait_for_completion();
1✔
616

617
            auto events = get_audit_events(test_session);
1✔
618
            REQUIRE(events.size() == 1);
1!
619
            auto& event = events[0];
1✔
620
            REQUIRE(event.data.size() == 1);
1!
621
            auto& data = event.data["target"];
1✔
622
            REQUIRE(data.size() == 3);
1!
623
            REQUIRE(data["deletions"] == json({{{"_id", 1}, {"value", 1}}}));
1!
624
            REQUIRE(data["insertions"] == json({{{"_id", 20}, {"value", 100}}}));
1!
625
            REQUIRE(data["modifications"] ==
1!
626
                    json({{{"oldValue", {{"_id", 0}, {"value", 0}}}, {"newValue", {{"value", 100}}}},
1✔
627
                          {{"oldValue", {{"_id", 2}, {"value", 2}}}, {"newValue", {{"value", 100}}}}}));
1✔
628
        }
1✔
629

630
        SECTION("empty write transactions do not produce an event") {
6✔
631
            auto scope = audit->begin_scope("scope");
1✔
632
            realm->begin_transaction();
1✔
633
            realm->commit_transaction();
1✔
634
            audit->end_scope(scope, assert_no_error);
1✔
635
            audit->wait_for_completion();
1✔
636

637
            REQUIRE(get_audit_events(test_session).empty());
1!
638
        }
1✔
639
    }
6✔
640

641
    SECTION("empty query") {
36✔
642
        auto scope = audit->begin_scope("scope");
1✔
643
        Results(realm, table->where()).snapshot();
1✔
644
        audit->end_scope(scope, assert_no_error);
1✔
645
        audit->wait_for_completion();
1✔
646
        REQUIRE(get_audit_events(test_session).empty());
1!
647
    }
1✔
648

649
    SECTION("non-empty query") {
36✔
650
        realm->begin_transaction();
5✔
651
        for (int64_t i = 0; i < 10; ++i) {
55✔
652
            table->create_object_with_primary_key(i);
50✔
653
            target_table->create_object_with_primary_key(i);
50✔
654
        }
50✔
655
        realm->commit_transaction();
5✔
656

657
        SECTION("query counts as a read on all objects matching the query") {
5✔
658
            auto scope = audit->begin_scope("scope");
1✔
659
            Results(realm, table->where().less(table->get_column_key("_id"), 5)).snapshot();
1✔
660
            audit->end_scope(scope, assert_no_error);
1✔
661
            audit->wait_for_completion();
1✔
662
            auto events = get_audit_events(test_session);
1✔
663
            REQUIRE(events.size() == 1);
1!
664
            REQUIRE(events[0].data["value"].size() == 5);
1!
665
        }
1✔
666

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

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

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

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

720
    SECTION("query on list of objects") {
36✔
721
        realm->begin_transaction();
1✔
722
        auto obj = table->create_object_with_primary_key(2);
1✔
723
        auto list = obj.get_linklist("object list");
1✔
724
        for (int64_t i = 0; i < 10; ++i)
11✔
725
            list.add(target_table->create_object_with_primary_key(i).set_all(i * 2).get_key());
10✔
726
        realm->commit_transaction();
1✔
727

728
        auto scope = audit->begin_scope("scope");
1✔
729
        Object object(realm, obj);
1✔
730
        auto obj_list = util::any_cast<List>(object.get_property_value<std::any>(context, "object list"));
1✔
731
        obj_list.filter(target_table->where().greater(target_table->get_column_key("value"), 10)).snapshot();
1✔
732
        audit->end_scope(scope, assert_no_error);
1✔
733
        audit->wait_for_completion();
1✔
734

735
        auto events = get_audit_events(test_session);
1✔
736
        REQUIRE(events.size() == 2);
1!
737
        REQUIRE(events[0].data["type"] == "object");
1!
738
        REQUIRE(events[0].data["value"][0]["object list"] == json({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}));
1!
739
        REQUIRE(events[1].data["type"] == "target");
1!
740
        REQUIRE(events[1].data["value"] == json({
1!
741
                                               {{"_id", 6}, {"value", 12}},
1✔
742
                                               {{"_id", 7}, {"value", 14}},
1✔
743
                                               {{"_id", 8}, {"value", 16}},
1✔
744
                                               {{"_id", 9}, {"value", 18}},
1✔
745
                                           }));
1✔
746
    }
1✔
747

748
    SECTION("link access tracking") {
36✔
749
        realm->begin_transaction();
16✔
750
        table->create_object_with_primary_key(1);
16✔
751
        target_table->create_object_with_primary_key(0);
16✔
752
        auto obj = table->create_object_with_primary_key(2);
16✔
753
        obj.set("object", target_table->create_object_with_primary_key(1).set_all(1).get_key());
16✔
754
        obj.create_and_set_linked_object(table->get_column_key("embedded object")).set_all(200);
16✔
755

756
        auto obj_list = obj.get_linklist("object list");
16✔
757
        obj_list.add(target_table->create_object_with_primary_key(3).set_all(10).get_key());
16✔
758
        obj_list.add(target_table->create_object_with_primary_key(4).set_all(20).get_key());
16✔
759
        obj_list.add(target_table->create_object_with_primary_key(5).set_all(30).get_key());
16✔
760

761
        auto obj_set = obj.get_linkset(obj.get_table()->get_column_key("object set"));
16✔
762
        obj_set.insert(target_table->create_object_with_primary_key(6).set_all(40).get_key());
16✔
763
        obj_set.insert(target_table->create_object_with_primary_key(7).set_all(50).get_key());
16✔
764
        obj_set.insert(target_table->create_object_with_primary_key(8).set_all(60).get_key());
16✔
765

766
        auto obj_dict = obj.get_dictionary("object dictionary");
16✔
767
        obj_dict.insert("a", target_table->create_object_with_primary_key(9).set_all(90).get_key());
16✔
768
        obj_dict.insert("b", target_table->create_object_with_primary_key(10).set_all(100).get_key());
16✔
769
        obj_dict.insert("c", target_table->create_object_with_primary_key(11).set_all(110).get_key());
16✔
770
        realm->commit_transaction();
16✔
771

772
        SECTION("objects are serialized as just primary key by default") {
16✔
773
            auto scope = audit->begin_scope("scope");
1✔
774
            Object object(realm, obj);
1✔
775
            audit->end_scope(scope, assert_no_error);
1✔
776
            audit->wait_for_completion();
1✔
777

778
            auto events = get_audit_events(test_session);
1✔
779
            REQUIRE(events.size() == 1);
1!
780
            auto& value = events[0].data["value"][0];
1✔
781
            REQUIRE(value["object"] == 1);
1!
782
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
783
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
784
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
785
        }
1✔
786

787
        SECTION("embedded objects are always full object") {
16✔
788
            auto scope = audit->begin_scope("scope");
1✔
789
            Object object(realm, obj);
1✔
790
            audit->end_scope(scope, assert_no_error);
1✔
791
            audit->wait_for_completion();
1✔
792

793
            auto events = get_audit_events(test_session);
1✔
794
            REQUIRE(events.size() == 1);
1!
795
            REQUIRE(events[0].data["value"][0]["embedded object"] == json({{"value", 200}}));
1!
796
        }
1✔
797

798
        SECTION("links followed serialize the full object") {
16✔
799
            auto scope = audit->begin_scope("scope");
1✔
800
            Object object(realm, obj);
1✔
801
            object.get_property_value<std::any>(context, "object");
1✔
802
            audit->end_scope(scope, assert_no_error);
1✔
803
            audit->wait_for_completion();
1✔
804

805
            auto events = get_audit_events(test_session);
1✔
806
            REQUIRE(events.size() == 2);
1!
807
            auto& value = events[0].data["value"][0];
1✔
808
            REQUIRE(value["object"] == json({{"_id", 1}, {"value", 1}}));
1!
809
            REQUIRE(events[1].data["value"][0] == json({{"_id", 1}, {"value", 1}}));
1!
810

811
            // Other fields are left in pk form
812
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
813
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
814
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
815
        }
1✔
816

817
        SECTION("instantiating a collection accessor does not count as a read") {
16✔
818
            auto scope = audit->begin_scope("scope");
1✔
819
            Object object(realm, obj);
1✔
820
            util::any_cast<List>(object.get_property_value<std::any>(context, "object list"));
1✔
821
            util::any_cast<object_store::Set>(object.get_property_value<std::any>(context, "object set"));
1✔
822
            util::any_cast<object_store::Dictionary>(
1✔
823
                object.get_property_value<std::any>(context, "object dictionary"));
1✔
824
            audit->end_scope(scope, assert_no_error);
1✔
825
            audit->wait_for_completion();
1✔
826

827
            auto events = get_audit_events(test_session);
1✔
828
            REQUIRE(events.size() == 1);
1!
829
            auto& value = events[0].data["value"][0];
1✔
830
            REQUIRE(value["object list"] == json({3, 4, 5}));
1!
831
            REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
1!
832
            REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
1!
833
        }
1✔
834

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

849
                auto events = get_audit_events(test_session);
2✔
850
                REQUIRE(events.size() == 2);
2!
851
                auto& value = events[0].data["value"][0];
2✔
852
                REQUIRE(
2!
853
                    value["object list"] ==
2✔
854
                    json({{{"_id", 3}, {"value", 10}}, {{"_id", 4}, {"value", 20}}, {{"_id", 5}, {"value", 30}}}));
2✔
855
                REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
2!
856
                REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
2!
857
            }
2✔
858

859
            SECTION("set") {
8✔
860
                auto scope = audit->begin_scope("scope");
2✔
861
                Object object(realm, obj);
2✔
862
                auto set =
2✔
863
                    util::any_cast<object_store::Set>(object.get_property_value<std::any>(context, "object set"));
2✔
864
                SECTION("get()") {
2✔
865
                    set.get(1);
1✔
866
                }
1✔
867
                SECTION("get_any()") {
2✔
868
                    set.get_any(1);
1✔
869
                }
1✔
870
                audit->end_scope(scope, assert_no_error);
2✔
871
                audit->wait_for_completion();
2✔
872

873
                auto events = get_audit_events(test_session);
2✔
874
                REQUIRE(events.size() == 2);
2!
875
                auto& value = events[0].data["value"][0];
2✔
876
                REQUIRE_SET_EQUAL(
2!
877
                    value["object set"],
2✔
878
                    json({{{"_id", 6}, {"value", 40}}, {{"_id", 7}, {"value", 50}}, {{"_id", 8}, {"value", 60}}}));
2✔
879
                REQUIRE(value["object list"] == json({3, 4, 5}));
2!
880
                REQUIRE(value["object dictionary"] == json({{"a", 9}, {"b", 10}, {"c", 11}}));
2!
881
            }
2✔
882

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

903
                auto events = get_audit_events(test_session);
4✔
904
                REQUIRE(events.size() == 2);
4!
905
                auto& value = events[0].data["value"][0];
4✔
906
                REQUIRE(value["object list"] == json({3, 4, 5}));
4!
907
                REQUIRE_SET_EQUAL(value["object set"], {6, 7, 8});
4!
908
                REQUIRE(value["object dictionary"] == json({{"a", {{"_id", 9}, {"value", 90}}},
4!
909
                                                            {"b", {{"_id", 10}, {"value", 100}}},
4✔
910
                                                            {"c", {{"_id", 11}, {"value", 110}}}}));
4✔
911
            }
4✔
912
        }
8✔
913

914
        SECTION(
16✔
915
            "link access on an object read outside of a scope does not produce a read on the parent in the scope") {
1✔
916
            Object object(realm, obj);
1✔
917
            auto scope = audit->begin_scope("scope");
1✔
918
            object.get_property_value<std::any>(context, "object");
1✔
919
            audit->end_scope(scope, assert_no_error);
1✔
920
            audit->wait_for_completion();
1✔
921

922
            auto events = get_audit_events(test_session);
1✔
923
            REQUIRE(events.size() == 1);
1!
924
            auto& event = events[0];
1✔
925
            REQUIRE(event.event == "read");
1!
926
            REQUIRE(event.data["type"] == "target");
1!
927
        }
1✔
928

929
        SECTION("link access in a different scope from the object do not expand linked object in parent read") {
16✔
930
            auto scope = audit->begin_scope("scope 1");
1✔
931
            Object object(realm, obj);
1✔
932
            audit->end_scope(scope, assert_no_error);
1✔
933

934
            scope = audit->begin_scope("scope 2");
1✔
935
            object.get_property_value<std::any>(context, "object");
1✔
936
            audit->end_scope(scope, assert_no_error);
1✔
937
            audit->wait_for_completion();
1✔
938

939
            auto events = get_audit_events(test_session);
1✔
940
            REQUIRE(events.size() == 2);
1!
941
            REQUIRE(events[0].activity == "scope 1");
1!
942
            REQUIRE(events[0].data["type"] == "object");
1!
943
            REQUIRE(events[1].activity == "scope 2");
1!
944
            REQUIRE(events[1].data["type"] == "target");
1!
945
            REQUIRE(events[0].data["value"][0]["object"] == 1);
1!
946
        }
1✔
947

948
        SECTION("link access tracking is reset between scopes") {
16✔
949
            auto scope = audit->begin_scope("scope 1");
1✔
950
            Object object(realm, obj);
1✔
951
            object.get_property_value<std::any>(context, "object");
1✔
952
            audit->end_scope(scope, assert_no_error);
1✔
953

954
            scope = audit->begin_scope("scope 2");
1✔
955
            // Perform two unrelated events so that the read on `obj` is at
956
            // an event index after the link access in the previous scope
957
            Object(realm, target_table->get_object(obj_set.get(0)));
1✔
958
            Object(realm, target_table->get_object(obj_set.get(1)));
1✔
959
            Object(realm, obj);
1✔
960
            audit->end_scope(scope, assert_no_error);
1✔
961
            audit->wait_for_completion();
1✔
962

963
            auto events = get_audit_events(test_session);
1✔
964
            REQUIRE(events.size() == 5);
1!
965
            REQUIRE(events[0].activity == "scope 1");
1!
966
            REQUIRE(events[1].activity == "scope 1");
1!
967
            REQUIRE(events[2].activity == "scope 2");
1!
968
            REQUIRE(events[3].activity == "scope 2");
1!
969
            REQUIRE(events[4].activity == "scope 2");
1!
970

971
            REQUIRE(events[0].data["type"] == "object");
1!
972
            REQUIRE(events[1].data["type"] == "target");
1!
973
            REQUIRE(events[2].data["type"] == "target");
1!
974
            REQUIRE(events[3].data["type"] == "target");
1!
975
            REQUIRE(events[4].data["type"] == "object");
1!
976

977
            // First link should be expanded, second, should not
978
            REQUIRE(events[0].data["value"][0]["object"] == json({{"_id", 1}, {"value", 1}}));
1!
979
            REQUIRE(events[4].data["value"][0]["object"] == 1);
1!
980
        }
1✔
981

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

985
            auto scope = audit->begin_scope("scope");
1✔
986
            object.get_property_value<std::any>(context, "object");
1✔
987
            Object(realm, obj);
1✔
988
            audit->end_scope(scope, assert_no_error);
1✔
989
            audit->wait_for_completion();
1✔
990

991
            auto events = get_audit_events(test_session);
1✔
992
            REQUIRE(events.size() == 2);
1!
993
            REQUIRE(events[1].data["value"][0]["object"] == 1);
1!
994
        }
1✔
995
    }
16✔
996

997
    SECTION("read on newly created object") {
36✔
998
        realm->begin_transaction();
1✔
999
        auto scope = audit->begin_scope("scope");
1✔
1000
        Object object(realm, table->create_object_with_primary_key(100));
1✔
1001
        Results(realm, table->where()).snapshot();
1✔
1002
        audit->end_scope(scope, assert_no_error);
1✔
1003
        realm->commit_transaction();
1✔
1004
        audit->wait_for_completion();
1✔
1005

1006
        auto events = get_audit_events(test_session);
1✔
1007
        REQUIRE(events.empty());
1!
1008
    }
1✔
1009

1010
    SECTION("query matching both new and existing objects") {
36✔
1011
        realm->begin_transaction();
1✔
1012
        table->create_object_with_primary_key(1);
1✔
1013
        realm->commit_transaction();
1✔
1014

1015
        realm->begin_transaction();
1✔
1016
        table->create_object_with_primary_key(2);
1✔
1017
        auto scope = audit->begin_scope("scope");
1✔
1018
        Results(realm, table->where()).snapshot();
1✔
1019
        audit->end_scope(scope, assert_no_error);
1✔
1020
        realm->commit_transaction();
1✔
1021
        audit->wait_for_completion();
1✔
1022

1023
        auto events = get_audit_events(test_session);
1✔
1024
        REQUIRE(events.size() == 1);
1!
1025
        REQUIRE(events[0].data["value"].size() == 1);
1!
1026
    }
1✔
1027

1028
    SECTION("reads mixed with deletions") {
36✔
1029
        realm->begin_transaction();
2✔
1030
        table->create_object_with_primary_key(1);
2✔
1031
        auto obj2 = table->create_object_with_primary_key(2);
2✔
1032
        auto obj3 = table->create_object_with_primary_key(3);
2✔
1033
        realm->commit_transaction();
2✔
1034

1035
        SECTION("reads of objects that are subsequently deleted are still reported") {
2✔
1036
            auto scope = audit->begin_scope("scope");
1✔
1037
            realm->begin_transaction();
1✔
1038
            Object(realm, obj2);
1✔
1039
            obj2.remove();
1✔
1040
            realm->commit_transaction();
1✔
1041
            audit->end_scope(scope, assert_no_error);
1✔
1042
            audit->wait_for_completion();
1✔
1043

1044
            auto events = get_audit_events(test_session);
1✔
1045
            REQUIRE(events.size() == 2);
1!
1046
            REQUIRE(events[0].event == "read");
1!
1047
            REQUIRE(events[1].event == "write");
1!
1048
            REQUIRE(events[0].data["value"][0]["_id"] == 2);
1!
1049
        }
1✔
1050

1051
        SECTION("reads after deletions report the correct object") {
2✔
1052
            auto scope = audit->begin_scope("scope");
1✔
1053
            realm->begin_transaction();
1✔
1054
            obj2.remove();
1✔
1055
            // In the pre-core-6 version of the code this would incorrectly
1056
            // report a read on obj2
1057
            Object(realm, obj3);
1✔
1058
            realm->commit_transaction();
1✔
1059
            audit->end_scope(scope, assert_no_error);
1✔
1060
            audit->wait_for_completion();
1✔
1061

1062
            auto events = get_audit_events(test_session);
1✔
1063
            REQUIRE(events.size() == 2);
1!
1064
            REQUIRE(events[0].event == "read");
1!
1065
            REQUIRE(events[1].event == "write");
1!
1066
            REQUIRE(events[0].data["value"][0]["_id"] == 3);
1!
1067
        }
1✔
1068
    }
2✔
1069
}
36✔
1070

1071
TEST_CASE("audit management", "[sync][pbs][audit]") {
15✔
1072
    TestClock clock;
15✔
1073

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

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

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

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

1126
    SECTION("scope names") {
15✔
1127
        realm->begin_transaction();
1✔
1128
        auto obj = table->create_object_with_primary_key(1);
1✔
1129
        realm->commit_transaction();
1✔
1130

1131
        auto scope = audit->begin_scope("scope 1");
1✔
1132
        Object(realm, obj);
1✔
1133
        audit->end_scope(scope, assert_no_error);
1✔
1134

1135
        scope = audit->begin_scope("scope 2");
1✔
1136
        Object(realm, obj);
1✔
1137
        audit->end_scope(scope, assert_no_error);
1✔
1138
        audit->wait_for_completion();
1✔
1139

1140
        auto events = get_audit_events(test_session);
1✔
1141
        REQUIRE(events.size() == 2);
1!
1142
        REQUIRE(events[0].activity == "scope 1");
1!
1143
        REQUIRE(events[1].activity == "scope 2");
1!
1144
    }
1✔
1145

1146
    SECTION("nested scopes") {
15✔
1147
        realm->begin_transaction();
1✔
1148
        auto obj1 = table->create_object_with_primary_key(1);
1✔
1149
        auto obj2 = table->create_object_with_primary_key(2);
1✔
1150
        auto obj3 = table->create_object_with_primary_key(3);
1✔
1151
        realm->commit_transaction();
1✔
1152

1153
        auto scope1 = audit->begin_scope("scope 1");
1✔
1154
        Object(realm, obj1); // read in scope 1 only
1✔
1155

1156
        auto scope2 = audit->begin_scope("scope 2");
1✔
1157
        Object(realm, obj2); // read in both scopes
1✔
1158
        audit->end_scope(scope2, assert_no_error);
1✔
1159

1160
        Object(realm, obj3); // read in scope 1 only
1✔
1161

1162
        audit->end_scope(scope1, assert_no_error);
1✔
1163
        audit->wait_for_completion();
1✔
1164

1165
        auto events = get_audit_events(test_session);
1✔
1166
        REQUIRE(events.size() == 4);
1!
1167

1168
        // scope 2 read on obj 2 comes first as it was the first scope ended
1169
        REQUIRE(events[0].activity == "scope 2");
1!
1170
        REQUIRE(events[0].data["value"][0]["_id"] == 2);
1!
1171

1172
        // scope 1 then has reads on each object in order
1173
        REQUIRE(events[1].activity == "scope 1");
1!
1174
        REQUIRE(events[1].data["value"][0]["_id"] == 1);
1!
1175
        REQUIRE(events[2].activity == "scope 1");
1!
1176
        REQUIRE(events[2].data["value"][0]["_id"] == 2);
1!
1177
        REQUIRE(events[3].activity == "scope 1");
1!
1178
        REQUIRE(events[3].data["value"][0]["_id"] == 3);
1!
1179
    }
1✔
1180

1181
    SECTION("overlapping scopes") {
15✔
1182
        realm->begin_transaction();
1✔
1183
        auto obj1 = table->create_object_with_primary_key(1);
1✔
1184
        auto obj2 = table->create_object_with_primary_key(2);
1✔
1185
        auto obj3 = table->create_object_with_primary_key(3);
1✔
1186
        realm->commit_transaction();
1✔
1187

1188
        auto scope1 = audit->begin_scope("scope 1");
1✔
1189
        Object(realm, obj1); // read in scope 1 only
1✔
1190

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

1194
        audit->end_scope(scope1, assert_no_error);
1✔
1195
        Object(realm, obj3); // read in scope 2 only
1✔
1196

1197
        audit->end_scope(scope2, assert_no_error);
1✔
1198
        audit->wait_for_completion();
1✔
1199

1200
        auto events = get_audit_events(test_session);
1✔
1201
        REQUIRE(events.size() == 4);
1!
1202

1203
        // scope 1 only read on obj 1
1204
        REQUIRE(events[0].activity == "scope 1");
1!
1205
        REQUIRE(events[0].data["value"][0]["_id"] == 1);
1!
1206

1207
        // both scopes read on obj 2
1208
        REQUIRE(events[1].activity == "scope 1");
1!
1209
        REQUIRE(events[1].data["value"][0]["_id"] == 2);
1!
1210
        REQUIRE(events[2].activity == "scope 2");
1!
1211
        REQUIRE(events[2].data["value"][0]["_id"] == 2);
1!
1212

1213
        // scope 2 only read on obj 3
1214
        REQUIRE(events[3].activity == "scope 2");
1!
1215
        REQUIRE(events[3].data["value"][0]["_id"] == 3);
1!
1216
    }
1✔
1217

1218
    SECTION("scope cancellation") {
15✔
1219
        realm->begin_transaction();
1✔
1220
        auto obj = table->create_object_with_primary_key(1);
1✔
1221
        realm->commit_transaction();
1✔
1222

1223
        auto scope1 = audit->begin_scope("scope 1");
1✔
1224
        auto scope2 = audit->begin_scope("scope 2");
1✔
1225
        Object(realm, obj);
1✔
1226
        audit->cancel_scope(scope1);
1✔
1227
        audit->end_scope(scope2, assert_no_error);
1✔
1228
        audit->wait_for_completion();
1✔
1229

1230
        auto events = get_audit_events(test_session);
1✔
1231
        REQUIRE(events.size() == 1);
1!
1232
        REQUIRE(events[0].activity == "scope 2");
1!
1233
    }
1✔
1234

1235
    SECTION("ending invalid scopes") {
15✔
1236
        REQUIRE_FALSE(audit->is_scope_valid(0));
1!
1237
        REQUIRE_THROWS_WITH(audit->end_scope(0),
1✔
1238
                            "Cannot end event scope: scope '0' not in progress. Scope may have already been ended?");
1✔
1239

1240
        auto scope = audit->begin_scope("scope");
1✔
1241
        REQUIRE(audit->is_scope_valid(scope));
1!
1242
        REQUIRE_NOTHROW(audit->end_scope(scope));
1✔
1243

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

1248
        scope = audit->begin_scope("scope 2");
1✔
1249
        REQUIRE(audit->is_scope_valid(scope));
1!
1250
        REQUIRE_NOTHROW(audit->cancel_scope(scope));
1✔
1251

1252
        REQUIRE_FALSE(audit->is_scope_valid(scope));
1!
1253
        REQUIRE_THROWS_WITH(audit->cancel_scope(scope),
1✔
1254
                            "Cannot end event scope: scope '2' not in progress. Scope may have already been ended?");
1✔
1255
    }
1✔
1256

1257
    SECTION("event timestamps") {
15✔
1258
        std::vector<Obj> objects;
1✔
1259
        realm->begin_transaction();
1✔
1260
        for (int i = 0; i < 10; ++i)
11✔
1261
            objects.push_back(table->create_object_with_primary_key(i));
10✔
1262
        realm->commit_transaction();
1✔
1263

1264
        auto scope = audit->begin_scope("scope");
1✔
1265
        for (int i = 0; i < 10; ++i) {
11✔
1266
            Object(realm, objects[i]);
10✔
1267
            Object(realm, objects[i]);
10✔
1268
        }
10✔
1269
        audit->end_scope(scope, assert_no_error);
1✔
1270
        audit->wait_for_completion();
1✔
1271

1272
        auto events = get_audit_events(test_session);
1✔
1273
        REQUIRE(events.size() == 10);
1!
1274
        for (int i = 0; i < 10; ++i) {
11✔
1275
            // i * 2 because we generate two reads on each object and the second
1276
            // is dropped, but still should have called now().
1277
            REQUIRE(events[i].timestamp == Timestamp(1000 + i * 2, 1000 + i * 2));
10!
1278
        }
10✔
1279
    }
1✔
1280

1281
    SECTION("metadata updating") {
15✔
1282
        realm->begin_transaction();
5✔
1283
        auto obj1 = realm->read_group().get_table("class_object")->create_object_with_primary_key(1);
5✔
1284
        realm->read_group().get_table("class_object")->create_object_with_primary_key(2);
5✔
1285
        realm->read_group().get_table("class_object")->create_object_with_primary_key(3);
5✔
1286
        realm->commit_transaction();
5✔
1287

1288
        SECTION("update before scope") {
5✔
1289
            audit->update_metadata({{"a", "aa"}});
1✔
1290
            auto scope = audit->begin_scope("scope 1");
1✔
1291
            Object(realm, obj1);
1✔
1292
            audit->end_scope(scope, assert_no_error);
1✔
1293
            audit->wait_for_completion();
1✔
1294

1295
            auto events = get_audit_events(test_session);
1✔
1296
            REQUIRE(events.size() == 1);
1!
1297
            auto event = events[0];
1✔
1298
            REQUIRE(event.metadata.size() == 1);
1!
1299
            REQUIRE(event.metadata["a"] == "aa");
1!
1300
        }
1✔
1301

1302
        SECTION("update during scope") {
5✔
1303
            auto scope = audit->begin_scope("scope 1");
1✔
1304
            audit->update_metadata({{"a", "aa"}});
1✔
1305
            Object(realm, obj1);
1✔
1306
            audit->end_scope(scope, assert_no_error);
1✔
1307
            audit->wait_for_completion();
1✔
1308

1309
            auto events = get_audit_events(test_session);
1✔
1310
            REQUIRE(events.size() == 1);
1!
1311
            auto event = events[0];
1✔
1312
            REQUIRE(event.metadata.size() == 0);
1!
1313
        }
1✔
1314

1315
        SECTION("one metadata field at a time") {
5✔
1316
            for (int i = 0; i < 100; ++i) {
101✔
1317
                audit->update_metadata({{util::format("name %1", i), util::format("value %1", i)}});
100✔
1318
                auto scope = audit->begin_scope(util::format("scope %1", i));
100✔
1319
                Object(realm, obj1);
100✔
1320
                audit->end_scope(scope, assert_no_error);
100✔
1321
            }
100✔
1322
            audit->wait_for_completion();
1✔
1323

1324
            auto events = get_audit_events(test_session);
1✔
1325
            REQUIRE(events.size() == 100);
1!
1326
            for (size_t i = 0; i < 100; ++i) {
101✔
1327
                REQUIRE(events[i].metadata.size() == 1);
100!
1328
                REQUIRE(events[i].metadata[util::format("name %1", i)] == util::format("value %1", i));
100!
1329
            }
100✔
1330
        }
1✔
1331

1332
        SECTION("many metadata fields") {
5✔
1333
            std::vector<std::pair<std::string, std::string>> metadata;
1✔
1334
            for (int i = 0; i < 100; ++i) {
101✔
1335
                metadata.push_back({util::format("name %1", i), util::format("value %1", i)});
100✔
1336
                audit->update_metadata(std::vector(metadata));
100✔
1337
                auto scope = audit->begin_scope(util::format("scope %1", i));
100✔
1338
                Object(realm, obj1);
100✔
1339
                audit->end_scope(scope, assert_no_error);
100✔
1340
            }
100✔
1341
            audit->wait_for_completion();
1✔
1342

1343
            auto events = get_audit_events(test_session);
1✔
1344
            REQUIRE(events.size() == 100);
1!
1345
            for (size_t i = 0; i < 100; ++i) {
101✔
1346
                REQUIRE(events[i].metadata.size() == i + 1);
100!
1347
            }
100✔
1348
        }
1✔
1349

1350
        SECTION("update via opening new realm") {
5✔
1351
            config.audit_config->metadata = {{"a", "aa"}};
1✔
1352
            auto realm2 = Realm::get_shared_realm(config);
1✔
1353
            auto obj2 = realm2->read_group().get_table("class_object")->get_object(1);
1✔
1354

1355
            auto scope = audit->begin_scope("scope 1");
1✔
1356
            Object(realm, obj1);
1✔
1357
            Object(realm2, obj2);
1✔
1358
            audit->end_scope(scope, assert_no_error);
1✔
1359

1360
            config.audit_config->metadata = {{"a", "aaa"}, {"b", "bb"}};
1✔
1361
            auto realm3 = Realm::get_shared_realm(config);
1✔
1362
            auto obj3 = realm3->read_group().get_table("class_object")->get_object(2);
1✔
1363

1364
            scope = audit->begin_scope("scope 2");
1✔
1365
            Object(realm, obj1);
1✔
1366
            Object(realm2, obj2);
1✔
1367
            Object(realm3, obj3);
1✔
1368
            audit->end_scope(scope, assert_no_error);
1✔
1369
            audit->wait_for_completion();
1✔
1370

1371
            auto events = get_audit_events(test_session);
1✔
1372
            REQUIRE(events.size() == 5);
1!
1373
            REQUIRE(events[0].activity == "scope 1");
1!
1374
            REQUIRE(events[1].activity == "scope 1");
1!
1375
            REQUIRE(events[2].activity == "scope 2");
1!
1376
            REQUIRE(events[3].activity == "scope 2");
1!
1377
            REQUIRE(events[4].activity == "scope 2");
1!
1378
            REQUIRE(events[0].metadata.size() == 1);
1!
1379
            REQUIRE(events[1].metadata.size() == 1);
1!
1380
            REQUIRE(events[2].metadata.size() == 2);
1!
1381
            REQUIRE(events[3].metadata.size() == 2);
1!
1382
            REQUIRE(events[4].metadata.size() == 2);
1!
1383
        }
1✔
1384
    }
5✔
1385

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

1397
        audit->record_event("event 1", "event"s, "data"s, expect_completion(0));
1✔
1398
        audit->record_event("event 2", none, "data"s, expect_completion(1));
1✔
1399
        auto scope = audit->begin_scope("scope");
1✔
1400
        // note: does not use the scope's activity
1401
        audit->record_event("event 3", none, none, expect_completion(2));
1✔
1402
        audit->end_scope(scope, expect_completion(3));
1✔
1403
        audit->record_event("event 4", none, none, expect_completion(4));
1✔
1404

1405
        util::EventLoop::main().run_until([&] {
30✔
1406
            return completions == 5;
30✔
1407
        });
30✔
1408

1409
        for (size_t i = 0; i < 5; ++i) {
6✔
1410
            REQUIRE(i == completion_results[i].first);
5!
1411
            REQUIRE_FALSE(completion_results[i].second);
5!
1412
        }
5✔
1413

1414
        auto events = get_audit_events(test_session, false);
1✔
1415
        REQUIRE(events.size() == 4);
1!
1416
        REQUIRE(events[0].activity == "event 1");
1!
1417
        REQUIRE(events[1].activity == "event 2");
1!
1418
        REQUIRE(events[2].activity == "event 3");
1!
1419
        REQUIRE(events[3].activity == "event 4");
1!
1420
        REQUIRE(events[0].event == "event"s);
1!
1421
        REQUIRE(events[1].event == none);
1!
1422
        REQUIRE(events[2].event == none);
1!
1423
        REQUIRE(events[3].event == none);
1!
1424
        REQUIRE(events[0].raw_data == "data"s);
1!
1425
        REQUIRE(events[1].raw_data == "data"s);
1!
1426
        REQUIRE(events[2].raw_data == none);
1!
1427
        REQUIRE(events[3].raw_data == none);
1!
1428
    }
1✔
1429

1430
    SECTION("read transaction version management") {
15✔
1431
        realm->begin_transaction();
1✔
1432
        auto obj = table->create_object_with_primary_key(1);
1✔
1433
        realm->commit_transaction();
1✔
1434

1435
        auto realm2 = Realm::get_shared_realm(config);
1✔
1436
        auto obj2 = realm2->read_group().get_table("class_object")->get_object(0);
1✔
1437
        auto realm3 = Realm::get_shared_realm(config);
1✔
1438
        auto obj3 = realm3->read_group().get_table("class_object")->get_object(0);
1✔
1439

1440
        realm2->begin_transaction();
1✔
1441
        obj2.set_all(1);
1✔
1442
        realm2->commit_transaction();
1✔
1443

1444
        realm3->begin_transaction();
1✔
1445
        obj3.set_all(2);
1✔
1446
        realm3->commit_transaction();
1✔
1447

1448
        auto scope = audit->begin_scope("scope");
1✔
1449
        Object(realm3, obj3); // value 2
1✔
1450
        Object(realm2, obj2); // value 1
1✔
1451
        Object(realm, obj);   // value 0
1✔
1452
        realm->refresh();
1✔
1453
        Object(realm, obj);   // value 2
1✔
1454
        Object(realm2, obj2); // value 1
1✔
1455
        realm2->refresh();
1✔
1456
        Object(realm3, obj3); // value 2
1✔
1457
        Object(realm2, obj2); // value 2
1✔
1458
        Object(realm, obj);   // value 2
1✔
1459
        audit->end_scope(scope, assert_no_error);
1✔
1460
        audit->wait_for_completion();
1✔
1461

1462
        auto events = get_audit_events(test_session);
1✔
1463
        REQUIRE(events.size() == 6);
1!
1464
        std::string str = events[0].data.dump();
1✔
1465
        // initial
1466
        REQUIRE(events[0].data["value"][0]["value"] == 2);
1!
1467
        REQUIRE(events[1].data["value"][0]["value"] == 1);
1!
1468
        REQUIRE(events[2].data["value"][0]["value"] == 0);
1!
1469

1470
        // realm->refresh()
1471
        REQUIRE(events[3].data["value"][0]["value"] == 2);
1!
1472
        REQUIRE(events[4].data["value"][0]["value"] == 1);
1!
1473

1474
        // realm2->refresh()
1475
        REQUIRE(events[5].data["value"][0]["value"] == 2);
1!
1476
    }
1✔
1477

1478
#if !REALM_DEBUG // This test is unreasonably slow in debug mode
1479
    SECTION("large audit scope") {
1480
        realm->begin_transaction();
1481
        auto obj1 = table->create_object_with_primary_key(1);
1482
        auto obj2 = table->create_object_with_primary_key(2);
1483
        realm->commit_transaction();
1484

1485
        auto scope = audit->begin_scope("large");
1486
        for (int i = 0; i < 150'000; ++i) {
1487
            Object(realm, obj1);
1488
            Object(realm, obj2);
1489
        }
1490
        audit->end_scope(scope, assert_no_error);
1491
        audit->wait_for_completion();
1492

1493
        auto events = get_audit_events(test_session);
1494
        REQUIRE(events.size() == 300'000);
1495
    }
1496
#endif
1497
}
15✔
1498

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

1504
    SyncTestFile config(test_session, "parent");
3✔
1505
    config.automatic_change_notifications = false;
3✔
1506
    config.schema_version = 1;
3✔
1507
    config.schema = Schema{
3✔
1508
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
3✔
1509
    };
3✔
1510
    config.audit_config = std::make_shared<AuditConfig>();
3✔
1511
    config.audit_config->logger = audit_logger;
3✔
1512
    auto realm = Realm::get_shared_realm(config);
3✔
1513
    auto audit = realm->audit_context();
3✔
1514
    REQUIRE(audit);
3!
1515

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

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

1522
    // Set a small shard size so that we don't have to write an absurd
1523
    // amount of data to test this
1524
    audit_test_hooks::set_maximum_shard_size(32 * 1024);
3✔
1525
    auto cleanup = util::make_scope_exit([]() noexcept {
3✔
1526
        audit_test_hooks::set_maximum_shard_size(256 * 1024 * 1024);
3✔
1527
    });
3✔
1528

1529
    realm->begin_transaction();
3✔
1530
    std::vector<Obj> objects;
3✔
1531
    for (int i = 0; i < 2000; ++i)
6,003✔
1532
        objects.push_back(table->create_object_with_primary_key(i));
6,000✔
1533
    realm->commit_transaction();
3✔
1534

1535
    // Write a lot of audit scopes while unable to sync
1536
    for (int i = 0; i < 50; ++i) {
153✔
1537
        auto scope = audit->begin_scope(util::format("scope %1", i));
150✔
1538
        Results(realm, table->where()).snapshot();
150✔
1539
        audit->end_scope(scope, assert_no_error);
150✔
1540
    }
150✔
1541
    audit->wait_for_completion();
3✔
1542

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

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

1587
    SECTION("start server with existing session open") {
3✔
1588
        test_session.sync_server().start();
1✔
1589
        audit->wait_for_uploads();
1✔
1590

1591
        auto events = get_sorted_events();
1✔
1592
        REQUIRE(events.size() == 50);
1!
1593
        for (int i = 0; i < 50; ++i) {
51✔
1594
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1595
        }
50✔
1596

1597
        // There should be exactly one remaining local Realm file (the currently
1598
        // open one that hasn't hit the size limit yet)
1599
        size_t remaining_realms = 0;
1✔
1600
        util::DirScanner dir(root);
1✔
1601
        while (dir.next(file_name)) {
20✔
1602
            if (StringData(file_name).ends_with(".realm"))
19✔
1603
                ++remaining_realms;
1✔
1604
        }
19✔
1605
        REQUIRE(remaining_realms == 1);
1!
1606
    }
1✔
1607

1608
    SECTION("trigger uploading by opening a new Realm") {
3✔
1609
        close_all_sessions();
1✔
1610
        test_session.sync_server().start();
1✔
1611

1612
        // Open a different Realm with the same user and audit prefix
1613
        SyncTestFile config(test_session, "other");
1✔
1614
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1615
        config.audit_config->logger = audit_logger;
1✔
1616
        auto realm = Realm::get_shared_realm(config);
1✔
1617
        auto audit2 = realm->audit_context();
1✔
1618
        REQUIRE(audit2);
1!
1619
        audit2->wait_for_uploads();
1✔
1620

1621
        auto events = get_sorted_events();
1✔
1622
        REQUIRE(events.size() == 50);
1!
1623
        for (int i = 0; i < 50; ++i) {
51✔
1624
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1625
        }
50✔
1626

1627
        // There should be no remaining local Realm files because we haven't
1628
        // made the new audit context open a Realm yet
1629
        util::DirScanner dir(root);
1✔
1630
        while (dir.next(file_name)) {
19✔
1631
            REQUIRE_FALSE(StringData(file_name).ends_with(".realm"));
18!
1632
        }
18✔
1633
    }
1✔
1634

1635
    SECTION("uploading is per audit prefix") {
3✔
1636
        close_all_sessions();
1✔
1637
        test_session.sync_server().start();
1✔
1638

1639
        // Open the same Realm with a different audit prefix
1640
        SyncTestFile config(test_session, "parent");
1✔
1641
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1642
        config.audit_config->logger = audit_logger;
1✔
1643
        config.audit_config->partition_value_prefix = "other";
1✔
1644
        auto realm = Realm::get_shared_realm(config);
1✔
1645
        auto audit2 = realm->audit_context();
1✔
1646
        REQUIRE(audit2);
1!
1647
        audit2->wait_for_uploads();
1✔
1648

1649
        // Should not have uploaded any of the old events
1650
        auto events = get_sorted_events();
1✔
1651
        REQUIRE(events.size() == 0);
1!
1652
    }
1✔
1653
}
3✔
1654

1655
#if REALM_ENABLE_AUTH_TESTS
1656
static void generate_event(std::shared_ptr<Realm> realm, int call = 0)
1657
{
31✔
1658
    auto table = realm->read_group().get_table("class_object");
31✔
1659
    auto audit = realm->audit_context();
31✔
1660

1661
    realm->begin_transaction();
31✔
1662
    table->create_object_with_primary_key(call + 1).set_all(2);
31✔
1663
    realm->commit_transaction();
31✔
1664

1665
    auto scope = audit->begin_scope("scope");
31✔
1666
    Object(realm, table->get_object(call));
31✔
1667
    audit->end_scope(scope, assert_no_error);
31✔
1668
}
31✔
1669

1670
TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") {
12✔
1671
    // None of these tests need a deterministic clock, but the server rounding
1672
    // timestamps to milliseconds can result in events not having monotonically
1673
    // increasing timestamps with an actual clock.
1674
    TestClock clock;
12✔
1675

1676
    const Schema schema{
12✔
1677
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
12✔
1678
        {"AuditEvent",
12✔
1679
         {
12✔
1680
             {"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
12✔
1681
             {"timestamp", PropertyType::Date},
12✔
1682
             {"activity", PropertyType::String},
12✔
1683
             {"event", PropertyType::String | PropertyType::Nullable},
12✔
1684
             {"data", PropertyType::String | PropertyType::Nullable},
12✔
1685
             {"metadata 1", PropertyType::String | PropertyType::Nullable},
12✔
1686
             {"metadata 2", PropertyType::String | PropertyType::Nullable},
12✔
1687
         }}};
12✔
1688
    const Schema no_audit_event_schema{
12✔
1689
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}}};
12✔
1690

1691
    auto app_create_config = default_app_config();
12✔
1692
    app_create_config.schema = schema;
12✔
1693
    app_create_config.dev_mode_enabled = false;
12✔
1694
    TestAppSession session = create_app(app_create_config);
12✔
1695

1696
    SyncTestFile config(session.app()->current_user(), bson::Bson("default"));
12✔
1697
    config.automatic_change_notifications = false;
12✔
1698
    config.schema = schema;
12✔
1699
    config.audit_config = std::make_shared<AuditConfig>();
12✔
1700
    config.audit_config->logger = audit_logger;
12✔
1701

1702
    auto expect_error = [&](auto&& config, auto&& fn) -> SyncError {
3✔
1703
        std::mutex mutex;
3✔
1704
        util::Optional<SyncError> error;
3✔
1705
        config.audit_config->sync_error_handler = [&](SyncError e) {
3✔
1706
            std::lock_guard lock(mutex);
3✔
1707
            error = e;
3✔
1708
        };
3✔
1709

1710
        auto realm = Realm::get_shared_realm(config);
3✔
1711
        fn(realm, 0);
3✔
1712

1713
        timed_wait_for(
3✔
1714
            [&] {
3,622✔
1715
                std::lock_guard lock(mutex);
3,622✔
1716
                return (bool)error;
3,622✔
1717
            },
3,622✔
1718
            std::chrono::seconds(30));
3✔
1719
        REQUIRE(bool(error));
3!
1720
        return *error;
3✔
1721
    };
3✔
1722

1723
    SECTION("basic functionality") {
12✔
1724
        auto realm = Realm::get_shared_realm(config);
1✔
1725
        realm->sync_session()->close();
1✔
1726
        generate_event(realm);
1✔
1727

1728
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1729
        REQUIRE(events.size() == 1);
1!
1730
        REQUIRE(events[0].activity == "scope");
1!
1731
        REQUIRE(events[0].event == "read");
1!
1732
        REQUIRE(!events[0].timestamp.is_null()); // FIXME
1!
1733
        REQUIRE(events[0].data == json({{"type", "object"}, {"value", {{{"_id", 1}, {"value", 2}}}}}));
1!
1734
    }
1✔
1735

1736
    SECTION("different user from parent Realm") {
12✔
1737
        auto sync_user = session.app()->current_user();
1✔
1738
        create_user_and_log_in(session.app());
1✔
1739
        auto audit_user = session.app()->current_user();
1✔
1740
        config.audit_config->audit_user = audit_user;
1✔
1741
        auto realm = Realm::get_shared_realm(config);
1✔
1742
        // If audit uses the sync user this'll make it fail as that user is logged out
1743
        sync_user->log_out();
1✔
1744

1745
        generate_event(realm);
1✔
1746
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1747
    }
1✔
1748

1749
    SECTION("different app from parent Realm") {
12✔
1750
        auto audit_user = session.app()->current_user();
1✔
1751

1752
        // Create an app which does not include AuditEvent in the schema so that
1753
        // things will break if audit tries to use it
1754
        app_create_config.schema = no_audit_event_schema;
1✔
1755
        TestAppSession session_2 = create_app(app_create_config);
1✔
1756
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1757
        config.schema = no_audit_event_schema;
1✔
1758
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1759
        config.audit_config->audit_user = audit_user;
1✔
1760

1761
        auto realm = Realm::get_shared_realm(config);
1✔
1762
        generate_event(realm);
1✔
1763
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1764
    }
1✔
1765

1766
    SECTION("valid metadata properties") {
12✔
1767
        auto realm = Realm::get_shared_realm(config);
1✔
1768
        generate_event(realm, 0);
1✔
1769
        realm->audit_context()->update_metadata({{"metadata 1", "value 1"}});
1✔
1770
        generate_event(realm, 1);
1✔
1771
        realm->audit_context()->update_metadata({{"metadata 2", "value 2"}});
1✔
1772
        generate_event(realm, 2);
1✔
1773
        realm->audit_context()->update_metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}});
1✔
1774
        generate_event(realm, 3);
1✔
1775

1776
        using Metadata = std::map<std::string, std::string>;
1✔
1777
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 4);
1✔
1778
        REQUIRE(events[0].metadata.empty());
1!
1779
        REQUIRE(events[1].metadata == Metadata({{"metadata 1", "value 1"}}));
1!
1780
        REQUIRE(events[2].metadata == Metadata({{"metadata 2", "value 2"}}));
1!
1781
        REQUIRE(events[3].metadata == Metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}}));
1!
1782
    }
1✔
1783

1784
    SECTION("invalid metadata properties") {
12✔
1785
        config.audit_config->metadata = {{"invalid key", "value"}};
1✔
1786
        auto error = expect_error(config, generate_event);
1✔
1787
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1788
        REQUIRE(error.is_fatal);
1!
1789
    }
1✔
1790

1791
    SECTION("removed sync user") {
12✔
1792
        create_user_and_log_in(session.app());
1✔
1793
        auto audit_user = session.app()->current_user();
1✔
1794
        config.audit_config->audit_user = audit_user;
1✔
1795
        auto realm = Realm::get_shared_realm(config);
1✔
1796
        session.sync_manager()->remove_user(audit_user->identity());
1✔
1797

1798
        auto audit = realm->audit_context();
1✔
1799
        auto scope = audit->begin_scope("scope");
1✔
1800
        realm->begin_transaction();
1✔
1801
        auto table = realm->read_group().get_table("class_object");
1✔
1802
        table->create_object_with_primary_key(1).set_all(2);
1✔
1803
        realm->commit_transaction();
1✔
1804

1805
        audit->end_scope(scope, [&](auto error) {
1✔
1806
            REQUIRE(error);
1!
1807
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "user has been removed");
1✔
1808
        });
1✔
1809
        audit->wait_for_completion();
1✔
1810
    }
1✔
1811

1812
    SECTION("AuditEvent missing from server schema") {
12✔
1813
        app_create_config.schema = no_audit_event_schema;
1✔
1814
        TestAppSession session_2 = create_app(app_create_config);
1✔
1815
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1816
        config.schema = no_audit_event_schema;
1✔
1817
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1818

1819
        auto error = expect_error(config, generate_event);
1✔
1820
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1821
        REQUIRE(error.is_fatal);
1!
1822
    }
1✔
1823

1824
    SECTION("incoming changesets are discarded") {
12✔
1825
        app::MongoClient remote_client = session.app()->current_user()->mongo_client("BackingDB");
2✔
1826
        app::MongoDatabase db = remote_client.db(session.app_session().config.mongo_dbname);
2✔
1827
        app::MongoCollection collection = db["AuditEvent"];
2✔
1828

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

1846
            auto realm = Realm::get_shared_realm(config);
1✔
1847
            for (int i = 0; i < 10; ++i) {
11✔
1848
                generate_event(realm, i);
10✔
1849
                delete_one();
10✔
1850
            }
10✔
1851
        }
1✔
1852

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

1874
            auto realm = Realm::get_shared_realm(config);
1✔
1875
            for (int i = 0; i < 10; ++i) {
11✔
1876
                generate_event(realm, i);
10✔
1877
                update_one();
10✔
1878
            }
10✔
1879
        }
1✔
1880
    }
2✔
1881

1882
    SECTION("flexible sync") {
12✔
1883
        app::FLXSyncTestHarness harness("audit");
3✔
1884
        create_user_and_log_in(harness.app());
3✔
1885

1886
        SECTION("auditing a flexible sync realm without specifying an audit user throws an exception") {
3✔
1887
            SyncTestFile config(harness.app()->current_user(), {}, SyncConfig::FLXSyncEnabled{});
1✔
1888
            config.audit_config = std::make_shared<AuditConfig>();
1✔
1889
            REQUIRE_THROWS_CONTAINING(Realm::get_shared_realm(config), "partition-based sync");
1✔
1890
        }
1✔
1891

1892
        SECTION("auditing with a flexible sync user reports a sync error") {
3✔
1893
            config.audit_config->audit_user = harness.app()->current_user();
1✔
1894
            auto error = expect_error(config, generate_event);
1✔
1895
            REQUIRE_THAT(error.status.reason(),
1✔
1896
                         Catch::Matchers::ContainsSubstring(
1✔
1897
                             "Client connected using partition-based sync when app is using flexible sync"));
1✔
1898
            REQUIRE(error.is_fatal);
1!
1899
        }
1✔
1900

1901
        SECTION("auditing a flexible sync realm with a pbs audit user works") {
3✔
1902
            config.audit_config->audit_user = config.sync_config->user;
1✔
1903
            config.sync_config->user = harness.app()->current_user();
1✔
1904
            config.sync_config->flx_sync_requested = true;
1✔
1905
            config.sync_config->partition_value.clear();
1✔
1906

1907
            auto realm = Realm::get_shared_realm(config);
1✔
1908
            {
1✔
1909
                auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
1✔
1910
                mut_subs.insert_or_assign(Query(realm->read_group().get_table("class_object")));
1✔
1911
                std::move(mut_subs).commit();
1✔
1912
            }
1✔
1913

1914
            realm->sync_session()->force_close();
1✔
1915
            generate_event(realm, 0);
1✔
1916
            get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1917
        }
1✔
1918
    }
3✔
1919

1920
#if 0 // This test takes ~10 minutes to run
1921
    SECTION("large audit scope") {
1922
        auto realm = Realm::get_shared_realm(config);
1923
        auto table = realm->read_group().get_table("class_object");
1924
        auto audit = realm->audit_context();
1925

1926
        realm->begin_transaction();
1927
        auto obj1 = table->create_object_with_primary_key(1);
1928
        auto obj2 = table->create_object_with_primary_key(2);
1929
        realm->commit_transaction();
1930

1931
        auto scope = audit->begin_scope("large");
1932
        for (int i = 0; i < 150'000; ++i) {
1933
            Object(realm, obj1);
1934
            Object(realm, obj2);
1935
        }
1936
        audit->end_scope(scope, assert_no_error);
1937

1938
        REQUIRE(get_audit_events_from_baas(session, *session.app()->current_user(), 300'000).size() == 300'000);
1939
    }
1940
#endif
1941
}
12✔
1942
#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