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

realm / realm-core / 2657

03 Mar 2025 05:12PM UTC coverage: 91.122% (+0.003%) from 91.119%
2657

push

Evergreen

web-flow
Enable automatic client reset handling for audit Realms (#8072)

Audit Realms typically don't get client resets due to being very short lived, but restarting sync while one is partially uploaded can result in a bad client file ident error which also blocks uploading any other unuploaded files. Standard automatic client reset is pretty inefficient for these files, but it works fine and the optimal thing would be fairly complicated.

102750 of 181548 branches covered (56.6%)

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

61 existing lines in 11 files now uncovered.

217450 of 238635 relevant lines covered (91.12%)

5986068.35 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)
73
{
1✔
74
    for (auto& event : events) {
6✔
75
        util::format(os, "%1: %2\n", event.event, event.data);
6✔
76
    }
6✔
77
    return os;
1✔
78
}
1✔
79

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

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

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

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

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

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

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

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

166
    return events;
50✔
167
}
50✔
168

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

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

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

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

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

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

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

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

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

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

270
} // namespace
271

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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