Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F139945
sync-test.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
sync-test.cpp
View Options
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include
<libkazv-config.hpp>
#include
<catch2/catch_all.hpp>
#include
<boost/asio.hpp>
#include
<zug/into_vector.hpp>
#include
<asio-promise-handler.hpp>
#include
<cursorutil.hpp>
#include
<sdk-model.hpp>
#include
<client/client.hpp>
#include
<client/actions/sync.hpp>
#include
"client-test-util.hpp"
#include
"factory.hpp"
using
namespace
Kazv
::
Factory
;
// The example response is adapted from https://matrix.org/docs/spec/client_server/latest
static
json
syncResponseJson
=
R
"
(
{
"next_batch": "s72595_4483_1934",
"presence": {
"events": [
{
"content": {
"avatar_url": "mxc://localhost:wefuiwegh8742w",
"last_active_ago": 2478593,
"presence": "online",
"currently_active": false,
"status_msg": "Making cupcakes"
},
"type": "m.presence",
"sender": "@example:localhost"
}
]
},
"account_data": {
"events": [
{
"type": "org.example.custom.config",
"content": {
"custom_config_key": "custom_config_value"
}
}
]
},
"rooms": {
"join": {
"!726s6s6q:example.com": {
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.joined_member_count": 2,
"m.invited_member_count": 1
},
"state": {
"events": [
{
"content": {
"membership": "join",
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid"
},
"type": "m.room.member",
"event_id": "$143273582443PhrSn:example.org",
"room_id": "!726s6s6q:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"age": 1234
},
"state_key": "@alice:example.org"
}
]
},
"timeline": {
"events": [
{
"content": {
"membership": "join",
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid"
},
"type": "m.room.member",
"event_id": "$143273582443PhrSn:example.org",
"room_id": "!726s6s6q:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"age": 1234
},
"state_key": "@alice:example.org"
},
{
"content": {
"body": "This is an example text message",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example text message</b>"
},
"type": "m.room.message",
"event_id": "$anothermessageevent:example.org",
"room_id": "!726s6s6q:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"age": 1234
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"type": "m.typing",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
}
]
},
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"type": "org.example.custom.room.config",
"content": {
"custom_config_key": "custom_config_value"
}
},
{
"type": "m.fully_read",
"content": {
"event_id": "$anothermessageevent:example.org"
}
}
]
}
}
},
"invite": {
"!696r7674:example.com": {
"invite_state": {
"events": [
{
"sender": "@alice:example.com",
"type": "m.room.name",
"state_key": "",
"content": {
"name": "My Room Name"
}
},
{
"sender": "@alice:example.com",
"type": "m.room.member",
"state_key": "@bob:example.com",
"content": {
"membership": "invite"
}
}
]
}
}
},
"leave": {}
},
"to_device": {
"events": [
{
"sender": "@alice:example.com",
"type": "m.new_device",
"content": {
"device_id": "XYZABCDE",
"rooms": ["!726s6s6q:example.com"]
}
}
]
}
}
)
"
_json
;
static
json
stateInTimelineResponseJson
=
R
"
(
{
"next_batch": "some-example-value",
"rooms": {
"join": {
"!exampleroomid:example.com": {
"timeline": {
"events": [
{
"content": { "example": "foo" },
"state_key": "",
"event_id": "$example:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": { "age": 1234 },
"type": "moe.kazv.mxc.custom.state.type"
}
],
"limited": false
}
}
}
}
}
)
"
_json
;
static
json
txnIdResponseJson
=
R
"
(
{
"next_batch": "some-example-value",
"rooms": {
"join": {
"!exampleroomid:example.com": {
"timeline": {
"events": [
{
"content": { "example": "foo" },
"event_id": "$example:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"unsigned": { "age": 1234, "transaction_id": "some-example-txnid" },
"type": "m.room.message"
}
],
"limited": false
}
}
}
}
}
)
"
_json
;
static
auto
addNotificationsJson
=
R
"
(
{
"next_batch": "some-example-value",
"account_data": {
"events": [
{
"type": "m.push_rules",
"content": {
"global": {
"override": [{
"rule_id": "moe.kazv.mxc.catch_all",
"default": true,
"enabled": true,
"conditions": [],
"actions": ["notify"]
}]
}
}
}
]
},
"rooms": {
"join": {
"!exampleroomid:example.com": {
"timeline": {
"events": [
{
"content": { "example": "foo" },
"event_id": "$example:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824653,
"type": "m.room.message"
},
{
"content": { "example": "foo2" },
"event_id": "$example2:example.com",
"sender": "@example:example.org",
"origin_server_ts": 1432735824953,
"type": "m.room.message"
}
],
"limited": false
}
}
}
}
}
)
"
_json
;
static
auto
receiptJson
=
R
"
(
{
"type": "m.receipt",
"content": {
"$example:example.com": {
"m.read": {
"@bob:example.com": {
"ts": 1432735824653
}
}
}
}
}
)
"
_json
;
static
auto
addAndRemoveNotificationsJson
=
[](
auto
a
,
auto
r
)
{
a
[
"rooms"
][
"join"
][
"!exampleroomid:example.com"
][
"ephemeral"
]
=
{
{
"events"
,
{
r
,
}}
};
return
a
;
}(
addNotificationsJson
,
receiptJson
);
static
auto
removeNotificationsJson
=
[](
auto
r
)
{
auto
j
=
R
"
(
{
"next_batch": "some-example-value",
"rooms": {
"join": {
"!exampleroomid:example.com": {
"timeline": {
"events": [],
"limited": false
}
}
}
}
}
)
"
_json
;
j
[
"rooms"
][
"join"
][
"!exampleroomid:example.com"
][
"ephemeral"
]
=
{
{
"events"
,
{
r
,
}}
};
return
j
;
}(
receiptJson
);
TEST_CASE
(
"use sync response to update client model"
,
"[client][sync]"
)
{
using
namespace
Kazv
::
CursorOp
;
boost
::
asio
::
io_context
io
;
AsioPromiseHandler
ph
{
io
.
get_executor
()};
auto
store
=
createTestClientStore
(
ph
);
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
syncResponseJson
)
|
withResponseDataKV
(
"is"
,
"initial"
)
);
auto
client
=
Client
(
store
.
reader
().
map
([](
auto
c
)
{
return
SdkModel
{
c
};
}),
store
,
std
::
nullopt
);
store
.
dispatch
(
ProcessResponseAction
{
resp
});
io
.
run
();
auto
rooms
=
+
client
.
rooms
();
std
::
string
roomId
=
"!726s6s6q:example.com"
;
SECTION
(
"rooms should be added"
)
{
REQUIRE
(
rooms
.
find
(
roomId
));
}
auto
r
=
client
.
room
(
roomId
);
SECTION
(
"room members should be updated"
)
{
auto
members
=
+
r
.
members
();
auto
hasAlice
=
zug
::
into_vector
(
zug
::
filter
([](
auto
id
)
{
return
id
==
"@alice:example.org"
;
}),
members
)
.
size
()
>
0
;
REQUIRE
(
hasAlice
);
}
SECTION
(
"heroes should be updated"
)
{
auto
heroIds
=
+
r
.
heroIds
();
REQUIRE
(
heroIds
==
immer
::
flex_vector
<
std
::
string
>
{
"@alice:example.com"
,
"@bob:example.com"
});
}
SECTION
(
"joined and invited member counts should be updated"
)
{
REQUIRE
(
r
.
joinedMemberCount
().
get
()
==
2
);
REQUIRE
(
r
.
invitedMemberCount
().
get
()
==
1
);
}
SECTION
(
"ephemeral events should be updated"
)
{
auto
users
=
+
r
.
typingUsers
();
REQUIRE
((
users
==
immer
::
flex_vector
<
std
::
string
>
{
"@alice:matrix.org"
,
"@bob:example.com"
}));
}
auto
eventId
=
"$anothermessageevent:example.org"
s
;
SECTION
(
"timeline should be updated"
)
{
auto
timeline
=
+
r
.
timelineEvents
();
auto
filtered
=
zug
::
into_vector
(
zug
::
filter
([
=
](
auto
event
)
{
return
event
.
id
()
==
eventId
;
}),
timeline
);
auto
hasEvent
=
filtered
.
size
()
>
0
;
REQUIRE
(
hasEvent
);
auto
onlyOneEvent
=
filtered
.
size
()
==
1
;
REQUIRE
(
onlyOneEvent
);
auto
ev
=
filtered
[
0
];
auto
eventHasRoomId
=
ev
.
originalJson
().
get
().
contains
(
"room_id"
s
);
REQUIRE
(
eventHasRoomId
);
auto
gaps
=
+
r
.
timelineGaps
();
// first event in the batch, correspond to its prevBatch
REQUIRE
(
gaps
.
at
(
"$143273582443PhrSn:example.org"
)
==
"t34-23535_0_0"
);
}
SECTION
(
"fully read marker should be updated"
)
{
auto
readMarker
=
+
r
.
readMarker
();
REQUIRE
(
readMarker
==
eventId
);
}
SECTION
(
"toDevice should be updated"
)
{
auto
toDevice
=
+
client
.
toDevice
();
REQUIRE
(
toDevice
.
size
()
==
1
);
REQUIRE
(
toDevice
[
0
].
sender
()
==
"@alice:example.com"
);
}
SECTION
(
"emits account data changes"
)
{
auto
nextTriggers
=
store
.
reader
().
get
().
nextTriggers
;
auto
triggerContains
=
[
=
](
auto
p
)
{
return
std
::
any_of
(
nextTriggers
.
begin
(),
nextTriggers
.
end
(),
[
=
](
const
KazvEvent
&
t
)
{
if
(
!
std
::
holds_alternative
<
ReceivingRoomAccountDataEvent
>
(
t
))
{
return
false
;
}
auto
e
=
std
::
get
<
ReceivingRoomAccountDataEvent
>
(
t
);
return
p
(
e
);
});
};
REQUIRE
(
triggerContains
([](
const
auto
&
e
)
{
return
e
.
event
.
type
()
==
"m.tag"
&&
e
.
roomId
==
"!726s6s6q:example.com"
;
}));
REQUIRE
(
triggerContains
([](
const
auto
&
e
)
{
return
e
.
event
.
type
()
==
"m.fully_read"
&&
e
.
roomId
==
"!726s6s6q:example.com"
;
}));
REQUIRE
(
triggerContains
([](
const
auto
&
e
)
{
return
e
.
event
.
type
()
==
"org.example.custom.room.config"
&&
e
.
roomId
==
"!726s6s6q:example.com"
;
}));
}
}
TEST_CASE
(
"Sync should record state events in timeline"
,
"[client][sync]"
)
{
using
namespace
Kazv
::
CursorOp
;
boost
::
asio
::
io_context
io
;
AsioPromiseHandler
ph
{
io
.
get_executor
()};
auto
store
=
createTestClientStore
(
ph
);
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
stateInTimelineResponseJson
)
|
withResponseDataKV
(
"is"
,
"initial"
)
);
auto
client
=
Client
(
store
.
reader
().
map
([](
auto
c
)
{
return
SdkModel
{
c
};
}),
store
,
std
::
nullopt
);
store
.
dispatch
(
ProcessResponseAction
{
resp
});
io
.
run
();
auto
r
=
client
.
room
(
"!exampleroomid:example.com"
);
auto
stateOpt
=
+
r
.
stateOpt
(
KeyOfState
{
"moe.kazv.mxc.custom.state.type"
,
""
});
REQUIRE
(
stateOpt
.
has_value
());
REQUIRE
(
stateOpt
.
value
().
content
().
get
().
at
(
"example"
)
==
"foo"
);
}
TEST_CASE
(
"Sync should remove already sent local echo"
,
"[client][sync]"
)
{
using
namespace
Kazv
::
CursorOp
;
boost
::
asio
::
io_context
io
;
AsioPromiseHandler
ph
{
io
.
get_executor
()};
ClientModel
m
=
makeClient
(
withRoom
(
makeRoom
(
withRoomId
(
"!exampleroomid:example.com"
)
|
withAttr
(
&
RoomModel
::
localEchoes
,
{
{
"some-example-txnid"
,
json
{
{
"type"
,
"m.room.message"
},
{
"content"
,
{{
"example"
,
"foo"
}}}
}},
{
"some-other-txnid"
,
json
{
{
"type"
,
"m.room.message"
},
{
"content"
,
{{
"example"
,
"foo"
}}}
}},
})
))
);
auto
store
=
createTestClientStoreFrom
(
m
,
ph
);
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
txnIdResponseJson
)
|
withResponseDataKV
(
"is"
,
"initial"
)
);
auto
client
=
Client
(
store
.
reader
().
map
([](
auto
c
)
{
return
SdkModel
{
c
};
}),
store
,
std
::
nullopt
);
store
.
dispatch
(
ProcessResponseAction
{
resp
});
io
.
run
();
auto
r
=
client
.
room
(
"!exampleroomid:example.com"
);
auto
localEchoes
=
+
r
.
localEchoes
();
REQUIRE
(
localEchoes
.
size
()
==
1
);
REQUIRE
(
localEchoes
[
0
].
txnId
==
"some-other-txnid"
);
}
TEST_CASE
(
"updating local notifications"
,
"[client][sync]"
)
{
ClientModel
m
=
makeClient
(
withRoom
(
makeRoom
(
withRoomId
(
"!exampleroomid:example.com"
))));
WHEN
(
"the receipt for the current user did not change"
)
{
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
addNotificationsJson
)
|
withResponseDataKV
(
"is"
,
"incremental"
)
);
auto
[
next
,
_
]
=
processResponse
(
m
,
SyncResponse
{
resp
});
auto
room
=
next
.
roomList
.
rooms
.
at
(
"!exampleroomid:example.com"
);
REQUIRE
(
room
.
unreadNotificationEventIds
==
immer
::
flex_vector
<
std
::
string
>
{
"$example:example.com"
,
"$example2:example.com"
});
THEN
(
"it changed later"
)
{
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
removeNotificationsJson
)
|
withResponseDataKV
(
"is"
,
"incremental"
)
);
auto
[
nextNext
,
_
]
=
processResponse
(
next
,
SyncResponse
{
resp
});
auto
room
=
nextNext
.
roomList
.
rooms
.
at
(
"!exampleroomid:example.com"
);
REQUIRE
(
room
.
unreadNotificationEventIds
==
immer
::
flex_vector
<
std
::
string
>
{
"$example2:example.com"
});
}
}
WHEN
(
"the receipt for the current user changed"
)
{
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
addAndRemoveNotificationsJson
)
|
withResponseDataKV
(
"is"
,
"incremental"
)
);
auto
[
next
,
_
]
=
processResponse
(
m
,
SyncResponse
{
resp
});
auto
room
=
next
.
roomList
.
rooms
.
at
(
"!exampleroomid:example.com"
);
REQUIRE
(
room
.
unreadNotificationEventIds
==
immer
::
flex_vector
<
std
::
string
>
{
"$example2:example.com"
});
}
}
TEST_CASE
(
"it does not add a gap when the limited field in the timeline is not present (conduwuit)"
,
"[client][sync]"
)
{
auto
body
=
R
"
(
{
"device_one_time_keys_count": {
"signed_curve25519": 721
},
"device_unused_fallback_key_types": null,
"next_batch": "some",
"rooms": {
"join": {
"!foo:example.com": {
"ephemeral": {
"events": []
},
"timeline": {
"events": [
{
"content": {
},
"event_id": "$1",
"origin_server_ts": 1723379000000,
"sender": "@foo:example.com",
"type": "m.room.message",
"unsigned": {
"age": 1,
"transaction_id": "xxxxxx"
}
}
],
"prev_batch": "prev-batch"
},
"unread_notifications": {
"highlight_count": 0,
"notification_count": 0
}
}
}
}
}
)
"
_json
;
auto
resp
=
makeResponse
(
"Sync"
,
withResponseJsonBody
(
body
)
|
withResponseDataKV
(
"is"
,
"incremental"
)
);
ClientModel
m
=
makeClient
(
withRoom
(
makeRoom
(
withRoomId
(
"!foo:example.com"
))));
auto
[
next
,
_
]
=
processResponse
(
m
,
SyncResponse
{
resp
});
auto
room
=
next
.
roomList
.
rooms
.
at
(
"!foo:example.com"
);
REQUIRE
(
room
.
timelineGaps
.
size
()
==
0
);
REQUIRE
(
room
.
messages
.
count
(
"$1"
)
!=
0
);
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sun, Jan 19, 8:36 AM (1 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55033
Default Alt Text
sync-test.cpp (17 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment