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

realm / realm-core / thomas.goyne_850

27 Feb 2025 12:31AM UTC coverage: 91.124% (+0.005%) from 91.119%
thomas.goyne_850

Pull #8072

Evergreen

tgoyne
Add some logging to a flakey audit test
Pull Request #8072: Enable automatic client reset handling for audit Realms

102754 of 181548 branches covered (56.6%)

77 of 79 new or added lines in 2 files covered. (97.47%)

64 existing lines in 14 files now uncovered.

217447 of 238628 relevant lines covered (91.12%)

5683102.81 hits per line

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

99.81
/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/dictionary.hpp>
26
#include <realm/list.hpp>
27
#include <realm/set.hpp>
28
#include <realm/sync/noinst/client_history_impl.hpp>
29
#include <realm/util/logger.hpp>
30

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

46
#include <catch2/catch_all.hpp>
47
#include <external/json/json.hpp>
48

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

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

61
namespace {
62

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

72
std::ostream& operator<<(std::ostream& os, const std::vector<AuditEvent>& events) {
1✔
73
    for (auto& event : events) {
6✔
74
        util::format(os, "%1: %2\n", event.event, event.data);
6✔
75
    }
6✔
76
    return os;
1✔
77
}
1✔
78

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

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

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

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

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

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

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

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

165
    return events;
50✔
166
}
50✔
167

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

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

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

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

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

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

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

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

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

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

269
} // namespace
270

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

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

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

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

301
          {"embedded object", PropertyType::Object | PropertyType::Nullable, "embedded target"},
36✔
302
          {"embedded object list", PropertyType::Object | PropertyType::Array, "embedded target"},
36✔
303
          {"embedded object dictionary", PropertyType::Object | PropertyType::Nullable | PropertyType::Dictionary,
36✔
304
           "embedded target"}}},
36✔
305
        {"target", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
36✔
306
        {"embedded target", ObjectSchema::ObjectType::Embedded, {{"value", PropertyType::Int}}}};
36✔
307
    config.audit_config = std::make_shared<AuditConfig>();
36✔
308
    config.audit_config->base_file_path = test_session.base_file_path();
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) {
36✔
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) {
36✔
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
    config.audit_config->base_file_path = test_session.base_file_path();
15✔
1083
    auto realm = Realm::get_shared_realm(config);
15✔
1084
    auto audit = realm->audit_context();
15✔
1085
    REQUIRE(audit);
15!
1086
    auto wait_for_completion = util::make_scope_exit([=]() noexcept {
15✔
1087
        audit->wait_for_completion();
15✔
1088
    });
15✔
1089

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1573
    // Create a backup copy of each of the unlocked files which should be cleaned up
1574
    for (auto& file : unlocked_files) {
21✔
1575
        BackupHandler handler(root + "/" + file, {}, {});
21✔
1576
        handler.backup_realm_if_needed(23, 24);
21✔
1577
        // Set the version field in the backup file to 23 so that opening it
1578
        // won't accidentally work
1579
        util::File(handler.get_prefix() + "v23.backup.realm", util::File::mode_Update).write(12, "\x17");
21✔
1580
    }
21✔
1581

1582
    auto get_sorted_events = [&] {
3✔
1583
        auto events = get_audit_events(test_session, false);
3✔
1584
        // The events might be out of order because there's no guaranteed order
1585
        // for both uploading the Realms and for opening the uploaded Realms.
1586
        // Once sorted by timestamp the scopes should be in order, though.
1587
        sort_events(events);
3✔
1588
        return events;
3✔
1589
    };
3✔
1590
    auto close_all_sessions = [&] {
3✔
1591
        realm->close();
2✔
1592
        realm = nullptr;
2✔
1593
        auto sync_manager = test_session.sync_manager();
2✔
1594
        for (auto& session : sync_manager->get_all_sessions()) {
2✔
1595
            session->shutdown_and_wait();
2✔
1596
        }
2✔
1597
    };
2✔
1598

1599
    SECTION("start server with existing session open") {
3✔
1600
        test_session.sync_server().start();
1✔
1601
        audit->wait_for_uploads();
1✔
1602

1603
        auto events = get_sorted_events();
1✔
1604
        REQUIRE(events.size() == 50);
1!
1605
        for (int i = 0; i < 50; ++i) {
51✔
1606
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1607
        }
50✔
1608

1609
        // There should be exactly one remaining local Realm file (the currently
1610
        // open one that hasn't hit the size limit yet)
1611
        size_t remaining_realms = 0;
1✔
1612
        util::DirScanner dir(root);
1✔
1613
        while (dir.next(file_name)) {
27✔
1614
            if (StringData(file_name).ends_with(".realm"))
26✔
1615
                ++remaining_realms;
1✔
1616
        }
26✔
1617
        REQUIRE(remaining_realms == 1);
1!
1618
    }
1✔
1619

1620
    SECTION("trigger uploading by opening a new Realm") {
3✔
1621
        close_all_sessions();
1✔
1622
        test_session.sync_server().start();
1✔
1623

1624
        // Open a different Realm with the same user and audit prefix
1625
        SyncTestFile config(test_session, "other");
1✔
1626
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1627
        config.audit_config->logger = audit_logger;
1✔
1628
        config.audit_config->base_file_path = test_session.base_file_path();
1✔
1629
        auto realm = Realm::get_shared_realm(config);
1✔
1630
        auto audit2 = realm->audit_context();
1✔
1631
        REQUIRE(audit2);
1!
1632
        audit2->wait_for_uploads();
1✔
1633

1634
        auto events = get_sorted_events();
1✔
1635
        REQUIRE(events.size() == 50);
1!
1636
        for (int i = 0; i < 50; ++i) {
51✔
1637
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
1638
        }
50✔
1639

1640
        // There should be no remaining local Realm files because we haven't
1641
        // made the new audit context open a Realm yet
1642
        util::DirScanner dir(root);
1✔
1643
        while (dir.next(file_name)) {
26✔
1644
            REQUIRE_FALSE(StringData(file_name).ends_with(".realm"));
25!
1645
        }
25✔
1646
    }
1✔
1647

1648
    SECTION("uploading is per audit prefix") {
3✔
1649
        close_all_sessions();
1✔
1650
        test_session.sync_server().start();
1✔
1651

1652
        // Open the same Realm with a different audit prefix
1653
        SyncTestFile config(test_session, "parent");
1✔
1654
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1655
        config.audit_config->base_file_path = test_session.base_file_path();
1✔
1656
        config.audit_config->logger = audit_logger;
1✔
1657
        config.audit_config->partition_value_prefix = "other";
1✔
1658
        auto realm = Realm::get_shared_realm(config);
1✔
1659
        auto audit2 = realm->audit_context();
1✔
1660
        REQUIRE(audit2);
1!
1661
        audit2->wait_for_uploads();
1✔
1662

1663
        // Should not have uploaded any of the old events
1664
        auto events = get_sorted_events();
1✔
1665
        REQUIRE(events.size() == 0);
1!
1666
    }
1✔
1667
}
3✔
1668

1669
#if REALM_ENABLE_AUTH_TESTS
1670
static void generate_event(std::shared_ptr<Realm> realm, int call = 0)
1671
{
32✔
1672
    auto table = realm->read_group().get_table("class_object");
32✔
1673
    auto audit = realm->audit_context();
32✔
1674

1675
    realm->begin_transaction();
32✔
1676
    table->create_object_with_primary_key(call + 1).set_all(2);
32✔
1677
    realm->commit_transaction();
32✔
1678

1679
    auto scope = audit->begin_scope("scope");
32✔
1680
    Object(realm, table->get_object(call));
32✔
1681
    audit->end_scope(scope, assert_no_error);
32✔
1682
}
32✔
1683

1684
TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") {
14✔
1685
    // None of these tests need a deterministic clock, but the server rounding
1686
    // timestamps to milliseconds can result in events not having monotonically
1687
    // increasing timestamps with an actual clock.
1688
    TestClock clock;
14✔
1689

1690
    const Schema schema{
14✔
1691
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}},
14✔
1692
        {"AuditEvent",
14✔
1693
         {
14✔
1694
             {"_id", PropertyType::ObjectId, Property::IsPrimary{true}},
14✔
1695
             {"timestamp", PropertyType::Date},
14✔
1696
             {"activity", PropertyType::String},
14✔
1697
             {"event", PropertyType::String | PropertyType::Nullable},
14✔
1698
             {"data", PropertyType::String | PropertyType::Nullable},
14✔
1699
             {"metadata 1", PropertyType::String | PropertyType::Nullable},
14✔
1700
             {"metadata 2", PropertyType::String | PropertyType::Nullable},
14✔
1701
         }}};
14✔
1702
    const Schema no_audit_event_schema{
14✔
1703
        {"object", {{"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}}}};
14✔
1704

1705
    auto app_create_config = default_app_config();
14✔
1706
    app_create_config.schema = schema;
14✔
1707
    app_create_config.dev_mode_enabled = false;
14✔
1708
    TestAppSession session = create_app(app_create_config);
14✔
1709

1710
    SyncTestFile config(session.app()->current_user(), bson::Bson("default"));
14✔
1711
    config.automatic_change_notifications = false;
14✔
1712
    config.schema = schema;
14✔
1713
    config.audit_config = std::make_shared<AuditConfig>();
14✔
1714
    config.audit_config->logger = audit_logger;
14✔
1715
    config.audit_config->base_file_path = session.app()->config().base_file_path;
14✔
1716

1717
    auto expect_error = [&](auto&& config, auto&& fn) -> SyncError {
14✔
1718
        std::mutex mutex;
3✔
1719
        util::Optional<SyncError> error;
3✔
1720
        config.audit_config->sync_error_handler = [&](SyncError e) {
3✔
1721
            std::lock_guard lock(mutex);
3✔
1722
            error = e;
3✔
1723
        };
3✔
1724

1725
        auto realm = Realm::get_shared_realm(config);
3✔
1726
        fn(realm, 0);
3✔
1727

1728
        timed_wait_for(
3✔
1729
            [&] {
1,586✔
1730
                std::lock_guard lock(mutex);
1,586✔
1731
                return (bool)error;
1,586✔
1732
            },
1,586✔
1733
            std::chrono::seconds(30));
3✔
1734
        REQUIRE(bool(error));
3!
1735
        return *error;
3✔
1736
    };
3✔
1737

1738
    SECTION("basic functionality") {
14✔
1739
        auto realm = Realm::get_shared_realm(config);
1✔
1740
        realm->sync_session()->close();
1✔
1741
        generate_event(realm);
1✔
1742

1743
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1744
        REQUIRE(events.size() == 1);
1!
1745
        REQUIRE(events[0].activity == "scope");
1!
1746
        REQUIRE(events[0].event == "read");
1!
1747
        REQUIRE(!events[0].timestamp.is_null()); // FIXME
1!
1748
        REQUIRE(events[0].data == json({{"type", "object"}, {"value", {{{"_id", 1}, {"value", 2}}}}}));
1!
1749
    }
1✔
1750

1751
    SECTION("different user from parent Realm") {
14✔
1752
        auto sync_user = session.app()->current_user();
1✔
1753
        create_user_and_log_in(session.app());
1✔
1754
        auto audit_user = session.app()->current_user();
1✔
1755
        config.audit_config->audit_user = audit_user;
1✔
1756
        auto realm = Realm::get_shared_realm(config);
1✔
1757
        // If audit uses the sync user this'll make it fail as that user is logged out
1758
        sync_user->log_out();
1✔
1759

1760
        generate_event(realm);
1✔
1761
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1762
    }
1✔
1763

1764
    SECTION("different app from parent Realm") {
14✔
1765
        auto audit_user = session.app()->current_user();
1✔
1766

1767
        // Create an app which does not include AuditEvent in the schema so that
1768
        // things will break if audit tries to use it
1769
        app_create_config.schema = no_audit_event_schema;
1✔
1770
        TestAppSession session_2 = create_app(app_create_config);
1✔
1771
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1772
        config.schema = no_audit_event_schema;
1✔
1773
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1774
        config.audit_config->base_file_path = session.app()->config().base_file_path;
1✔
1775
        config.audit_config->audit_user = audit_user;
1✔
1776

1777
        auto realm = Realm::get_shared_realm(config);
1✔
1778
        generate_event(realm);
1✔
1779
        REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1);
1!
1780
    }
1✔
1781

1782
    SECTION("valid metadata properties") {
14✔
1783
        auto realm = Realm::get_shared_realm(config);
1✔
1784
        generate_event(realm, 0);
1✔
1785
        realm->audit_context()->update_metadata({{"metadata 1", "value 1"}});
1✔
1786
        generate_event(realm, 1);
1✔
1787
        realm->audit_context()->update_metadata({{"metadata 2", "value 2"}});
1✔
1788
        generate_event(realm, 2);
1✔
1789
        realm->audit_context()->update_metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}});
1✔
1790
        generate_event(realm, 3);
1✔
1791

1792
        using Metadata = std::map<std::string, std::string>;
1✔
1793
        auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 4);
1✔
1794
        REQUIRE(events[0].metadata.empty());
1!
1795
        REQUIRE(events[1].metadata == Metadata({{"metadata 1", "value 1"}}));
1!
1796
        REQUIRE(events[2].metadata == Metadata({{"metadata 2", "value 2"}}));
1!
1797
        REQUIRE(events[3].metadata == Metadata({{"metadata 1", "value 3"}, {"metadata 2", "value 4"}}));
1!
1798
    }
1✔
1799

1800
    SECTION("invalid metadata properties") {
14✔
1801
        config.audit_config->metadata = {{"invalid key", "value"}};
1✔
1802
        auto error = expect_error(config, generate_event);
1✔
1803
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1804
        REQUIRE(error.is_fatal);
1!
1805
    }
1✔
1806

1807
    SECTION("removed sync user") {
14✔
1808
        create_user_and_log_in(session.app());
1✔
1809
        auto audit_user = session.app()->current_user();
1✔
1810
        config.audit_config->audit_user = audit_user;
1✔
1811
        auto realm = Realm::get_shared_realm(config);
1✔
1812
        session.app()->remove_user(audit_user, nullptr);
1✔
1813

1814
        auto audit = realm->audit_context();
1✔
1815
        auto scope = audit->begin_scope("scope");
1✔
1816
        realm->begin_transaction();
1✔
1817
        auto table = realm->read_group().get_table("class_object");
1✔
1818
        table->create_object_with_primary_key(1).set_all(2);
1✔
1819
        realm->commit_transaction();
1✔
1820

1821
        audit->end_scope(scope, [&](auto error) {
1✔
1822
            REQUIRE(error);
1!
1823
            REQUIRE_THROWS_CONTAINING(std::rethrow_exception(error), "user has been removed");
1✔
1824
        });
1✔
1825
        audit->wait_for_completion();
1✔
1826
    }
1✔
1827

1828
    SECTION("AuditEvent missing from server schema") {
14✔
1829
        app_create_config.schema = no_audit_event_schema;
1✔
1830
        TestAppSession session_2 = create_app(app_create_config);
1✔
1831
        SyncTestFile config(session_2.app()->current_user(), bson::Bson("default"));
1✔
1832
        config.schema = no_audit_event_schema;
1✔
1833
        config.audit_config = std::make_shared<AuditConfig>();
1✔
1834
        config.audit_config->base_file_path = session.app()->config().base_file_path;
1✔
1835

1836
        auto error = expect_error(config, generate_event);
1✔
1837
        REQUIRE_THAT(error.status.reason(), StartsWith("Invalid schema change"));
1✔
1838
        REQUIRE(error.is_fatal);
1!
1839
    }
1✔
1840

1841
    SECTION("incoming changesets are discarded") {
14✔
1842
        app::MongoClient remote_client = session.app()->current_user()->mongo_client("BackingDB");
2✔
1843
        app::MongoDatabase db = remote_client.db(session.app_session().config.mongo_dbname);
2✔
1844
        app::MongoCollection collection = db["AuditEvent"];
2✔
1845

1846
        SECTION("objects deleted on server") {
2✔
1847
            // Because EraseObject is idempotent, this case actually just works
1848
            // without any special logic.
1849
            auto delete_one = [&] {
10✔
1850
                uint64_t deleted = 0;
10✔
1851
                while (deleted == 0) {
95✔
1852
                    collection.delete_one({},
85✔
1853
                                          [&](util::Optional<uint64_t>&& count, util::Optional<app::AppError> error) {
85✔
1854
                                              REQUIRE_FALSE(error);
85!
1855
                                              deleted = *count;
85✔
1856
                                          });
85✔
1857
                    if (deleted == 0) {
85✔
1858
                        millisleep(100); // slow down the number of retries
75✔
1859
                    }
75✔
1860
                }
85✔
1861
            };
10✔
1862

1863
            auto realm = Realm::get_shared_realm(config);
1✔
1864
            for (int i = 0; i < 10; ++i) {
11✔
1865
                generate_event(realm, i);
10✔
1866
                delete_one();
10✔
1867
            }
10✔
1868
        }
1✔
1869

1870
        SECTION("objects modified on server") {
2✔
1871
            // UpdateObject throws bad_transaction_log() if the object doesn't
1872
            // exist locally, so this will break if we try to apply the changesets
1873
            // from the server.
1874
            const bson::BsonDocument filter{{"event", "read"}};
1✔
1875
            const bson::BsonDocument update{{"$set", bson::BsonDocument{{"event", "processed"}}}};
1✔
1876
            auto update_one = [&] {
10✔
1877
                int32_t count = 0;
10✔
1878
                while (count == 0) {
96✔
1879
                    collection.update_one(
86✔
1880
                        filter, update,
86✔
1881
                        [&](app::MongoCollection::UpdateResult result, util::Optional<app::AppError> error) {
86✔
1882
                            REQUIRE_FALSE(error);
86!
1883
                            count = result.modified_count;
86✔
1884
                        });
86✔
1885
                    if (count == 0) {
86✔
1886
                        millisleep(100); // slow down the number of retries
76✔
1887
                    }
76✔
1888
                }
86✔
1889
            };
10✔
1890

1891
            auto realm = Realm::get_shared_realm(config);
1✔
1892
            for (int i = 0; i < 10; ++i) {
11✔
1893
                generate_event(realm, i);
10✔
1894
                update_one();
10✔
1895
            }
10✔
1896
        }
1✔
1897
    }
2✔
1898

1899
    SECTION("flexible sync") {
14✔
1900
        app::FLXSyncTestHarness harness("audit", {schema});
3✔
1901
        create_user_and_log_in(harness.app());
3✔
1902

1903
        SECTION("auditing a flexible sync realm without specifying an audit user throws an exception") {
3✔
1904
            SyncTestFile config(harness.app()->current_user(), schema, SyncConfig::FLXSyncEnabled{});
1✔
1905
            config.audit_config = std::make_shared<AuditConfig>();
1✔
1906
            REQUIRE_THROWS_CONTAINING(Realm::get_shared_realm(config), "partition-based sync");
1✔
1907
        }
1✔
1908

1909
        SECTION("auditing with a flexible sync user reports a sync error") {
3✔
1910
            config.audit_config->audit_user = harness.app()->current_user();
1✔
1911
            auto error = expect_error(config, generate_event);
1✔
1912
            REQUIRE_THAT(error.status.reason(),
1✔
1913
                         Catch::Matchers::ContainsSubstring(
1✔
1914
                             "Client connected using partition-based sync when app is using flexible sync"));
1✔
1915
            REQUIRE(error.is_fatal);
1!
1916
        }
1✔
1917

1918
        SECTION("auditing a flexible sync realm with a pbs audit user works") {
3✔
1919
            config.audit_config->audit_user = config.sync_config->user;
1✔
1920
            config.sync_config->user = harness.app()->current_user();
1✔
1921
            config.sync_config->flx_sync_requested = true;
1✔
1922
            config.sync_config->partition_value.clear();
1✔
1923
            config.schema_version = 0;
1✔
1924

1925
            auto realm = Realm::get_shared_realm(config);
1✔
1926
            {
1✔
1927
                auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
1✔
1928
                mut_subs.insert_or_assign(Query(realm->read_group().get_table("class_object")));
1✔
1929
                std::move(mut_subs).commit();
1✔
1930
            }
1✔
1931

1932
            realm->sync_session()->force_close();
1✔
1933
            generate_event(realm, 0);
1✔
1934
            get_audit_events_from_baas(session, *session.app()->current_user(), 1);
1✔
1935
        }
1✔
1936
    }
3✔
1937

1938
    SECTION("creating audit event while offline uploads event when logged back in") {
14✔
1939
        auto sync_user = session.app()->current_user();
1✔
1940
        auto creds = create_user_and_log_in(session.app());
1✔
1941
        auto audit_user = session.app()->current_user();
1✔
1942
        config.audit_config->audit_user = audit_user;
1✔
1943
        config.audit_config->sync_error_handler = [&](SyncError error) {
1✔
NEW
1944
            REALM_ASSERT(ErrorCodes::error_categories(error.status.code()).test(ErrorCategory::app_error));
×
NEW
1945
        };
×
1946
        auto realm = Realm::get_shared_realm(config);
1✔
1947

1948
        audit_user->log_out();
1✔
1949
        generate_event(realm);
1✔
1950
        log_in_user(session.app(), creds);
1✔
1951

1952
        REQUIRE(get_audit_events_from_baas(session, *sync_user, 1).size() == 1);
1!
1953
    }
1✔
1954

1955
    SECTION("files with invalid client file idents are recovered") {
14✔
1956
        auto sync_user = session.app()->current_user();
1✔
1957
        auto creds = create_user_and_log_in(session.app());
1✔
1958
        auto audit_user = session.app()->current_user();
1✔
1959
        config.audit_config->audit_user = audit_user;
1✔
1960
        config.audit_config->sync_error_handler = [&](SyncError error) {
2✔
1961
            REALM_ASSERT(ErrorCodes::error_categories(error.status.code()).test(ErrorCategory::app_error));
2✔
1962
        };
2✔
1963
        auto realm = Realm::get_shared_realm(config);
1✔
1964
        audit_user->log_out();
1✔
1965

1966
        auto audit = realm->audit_context();
1✔
1967
        REQUIRE(audit);
1!
1968

1969
        // Set a small shard size so that we don't have to write an absurd
1970
        // amount of data to test this
1971
        audit_test_hooks::set_maximum_shard_size(32 * 1024);
1✔
1972
        auto cleanup = util::make_scope_exit([]() noexcept {
1✔
1973
            audit_test_hooks::set_maximum_shard_size(256 * 1024 * 1024);
1✔
1974
        });
1✔
1975

1976
        realm->begin_transaction();
1✔
1977
        auto table = realm->read_group().get_table("class_object");
1✔
1978
        std::vector<Obj> objects;
1✔
1979
        for (int i = 0; i < 2000; ++i)
2,001✔
1980
            objects.push_back(table->create_object_with_primary_key(i));
2,000✔
1981
        realm->commit_transaction();
1✔
1982

1983
        // Write a lot of audit scopes while unable to sync
1984
        for (int i = 0; i < 50; ++i) {
51✔
1985
            auto scope = audit->begin_scope(util::format("scope %1", i));
50✔
1986
            Results(realm, table->where()).snapshot();
50✔
1987
            audit->end_scope(scope, assert_no_error);
50✔
1988
        }
50✔
1989
        audit->wait_for_completion();
1✔
1990

1991
        // Client file idents aren't reread while a session is active, so we need
1992
        // to close all of the open audit Realms awaiting upload
1993
        realm->close();
1✔
1994
        realm = nullptr;
1✔
1995
        auto sync_manager = session.sync_manager();
1✔
1996
        for (auto& session : sync_manager->get_all_sessions()) {
1✔
1997
            session->shutdown_and_wait();
1✔
1998
        }
1✔
1999

2000
        // Set the client file ident for all pending Realms to an invalid one so
2001
        // that they'll get client resets
2002
        auto root = util::format("%1/realm-audit/%2/%3/audit", *session.config().storage_path,
1✔
2003
                                 session.app()->app_id(), audit_user->user_id());
1✔
2004
        std::string file_name;
1✔
2005
        util::DirScanner dir(root);
1✔
2006
        while (dir.next(file_name)) {
28✔
2007
            if (!StringData(file_name).ends_with(".realm") || StringData(file_name).contains(".backup."))
27✔
2008
                continue;
18✔
2009
            sync::ClientReplication repl;
9✔
2010
            auto db = DB::create(repl, root + "/" + file_name);
9✔
2011
            static_cast<sync::ClientHistory*>(repl._get_history_write())->set_client_file_ident({123, 456}, false);
9✔
2012
        }
9✔
2013

2014
        // Log the user back in and reopen the parent Realm to start trying to upload the audit data
2015
        log_in_user(session.app(), creds);
1✔
2016
        realm = Realm::get_shared_realm(config);
1✔
2017
        audit = realm->audit_context();
1✔
2018
        REQUIRE(audit);
1!
2019
        audit->wait_for_uploads();
1✔
2020

2021
        auto events = get_audit_events_from_baas(session, *sync_user, 50);
1✔
2022
        REQUIRE(events.size() == 50);
1!
2023
        for (int i = 0; i < 50; ++i) {
51✔
2024
            REQUIRE(events[i].activity == util::format("scope %1", i));
50!
2025
        }
50✔
2026
    }
1✔
2027

2028
#if 0 // This test takes ~10 minutes to run
2029
    SECTION("large audit scope") {
2030
        auto realm = Realm::get_shared_realm(config);
2031
        auto table = realm->read_group().get_table("class_object");
2032
        auto audit = realm->audit_context();
2033

2034
        realm->begin_transaction();
2035
        auto obj1 = table->create_object_with_primary_key(1);
2036
        auto obj2 = table->create_object_with_primary_key(2);
2037
        realm->commit_transaction();
2038

2039
        auto scope = audit->begin_scope("large");
2040
        for (int i = 0; i < 150'000; ++i) {
2041
            Object(realm, obj1);
2042
            Object(realm, obj2);
2043
        }
2044
        audit->end_scope(scope, assert_no_error);
2045

2046
        REQUIRE(get_audit_events_from_baas(session, *session.app()->current_user(), 300'000).size() == 300'000);
2047
    }
2048
#endif
2049
}
14✔
2050
#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