Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2697953
local-echo-test.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
local-echo-test.cpp
View Options
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include
<libkazv-config.hpp>
#include
<tuple>
#include
<catch2/catch_test_macros.hpp>
#include
<catch2/matchers/catch_matchers_predicate.hpp>
#include
<lager/event_loop/boost_asio.hpp>
#include
<asio-promise-handler.hpp>
#include
<cursorutil.hpp>
#include
<sdk-model.hpp>
#include
<client/client.hpp>
#include
<crypto-util.hpp>
#include
<cprjobhandler.hpp>
#include
<lagerstoreeventemitter.hpp>
#include
<debug.hpp>
#include
<sdk.hpp>
#include
"client-test-util.hpp"
using
namespace
Kazv
;
using
Catch
::
Matchers
::
Predicate
;
inline
auto
eventJson
=
json
{
{
"content"
,
{
{
"foo"
,
"bar"
},
}},
{
"type"
,
"m.room.message"
},
};
template
<
class
Store
,
class
Func
>
static
auto
getMockContext
(
SingleTypePromiseInterface
<
EffectStatus
>
&
ph
,
Store
&
store
,
Func
func
)
{
return
typename
Client
::
ContextT
([
&
ph
,
&
store
,
func
](
const
auto
&
action
)
{
kzo
.
client
.
dbg
()
<<
"dispatched: index "
<<
action
.
index
()
<<
std
::
endl
;
func
(
action
);
if
(
std
::
holds_alternative
<
GetRoomStatesAction
>
(
action
)
||
std
::
holds_alternative
<
QueryKeysAction
>
(
action
)
||
std
::
holds_alternative
<
SendToDeviceMessageAction
>
(
action
)
||
std
::
holds_alternative
<
SendMessageAction
>
(
action
))
{
return
ph
.
createResolved
(
EffectStatus
(
true
,
json
::
object
()));
}
else
if
(
std
::
holds_alternative
<
ClaimKeysAction
>
(
action
))
{
return
ph
.
createResolved
(
EffectStatus
(
true
,
json
::
object
({{
"keyEvent"
,
json
::
object
({})}})));
}
else
if
(
std
::
holds_alternative
<
EncryptOlmEventAction
>
(
action
))
{
return
ph
.
createResolved
(
EffectStatus
{
true
,
json
::
object
({{
"encrypted"
,
json
::
object
({{
"type"
,
""
}})}})});
}
else
if
(
std
::
holds_alternative
<
SaveLocalEchoAction
>
(
action
)
||
std
::
holds_alternative
<
EncryptMegOlmEventAction
>
(
action
))
{
return
store
.
dispatch
(
action
);
}
else
{
kzo
.
client
.
err
()
<<
"Unhandled action: index "
<<
action
.
index
();
throw
std
::
runtime_error
{
"unhandled action"
};
}
},
ph
,
lager
::
deps
<>
{});
}
TEST_CASE
(
"Local echo"
,
"[client][room]"
)
{
RoomModel
r
;
auto
next
=
RoomModel
::
update
(
r
,
AddLocalEchoAction
{{
"txnId1"
,
Event
(
eventJson
)}});
REQUIRE
(
!
(
next
==
r
));
next
=
RoomModel
::
update
(
next
,
AddLocalEchoAction
{{
"txnId2"
,
Event
(
eventJson
)}});
REQUIRE
(
!
(
next
==
r
));
REQUIRE
(
next
.
localEchoes
.
size
()
==
2
);
REQUIRE_THAT
(
next
.
localEchoes
[
0
],
Predicate
<
LocalEchoDesc
>
([](
const
auto
&
desc
)
{
return
desc
.
txnId
==
"txnId1"
;
}));
REQUIRE_THAT
(
next
.
localEchoes
[
1
],
Predicate
<
LocalEchoDesc
>
([](
const
auto
&
desc
)
{
return
desc
.
txnId
==
"txnId2"
;
}));
}
TEST_CASE
(
"Remove local echo"
,
"[client][room]"
)
{
RoomModel
r
;
r
.
localEchoes
=
immer
::
flex_vector
<
LocalEchoDesc
>
{
{
"txnId1"
,
Event
(
eventJson
)},
{
"txnId2"
,
Event
(
eventJson
)},
};
auto
next
=
RoomModel
::
update
(
r
,
RemoveLocalEchoAction
{
"txnId1"
});
REQUIRE
(
next
.
localEchoes
.
size
()
==
1
);
REQUIRE_THAT
(
next
.
localEchoes
[
0
],
Predicate
<
LocalEchoDesc
>
([](
const
auto
&
desc
)
{
return
desc
.
txnId
==
"txnId2"
;
}));
}
TEST_CASE
(
"getLocalEchoByTxnId()"
,
"[client][room]"
)
{
RoomModel
r
;
r
.
localEchoes
=
immer
::
flex_vector
<
LocalEchoDesc
>
{
{
"txnId1"
,
Event
(
eventJson
)},
{
"txnId2"
,
Event
(
eventJson
)},
};
REQUIRE
(
r
.
getLocalEchoByTxnId
(
"txnId1"
).
value
()
==
r
.
localEchoes
[
0
]);
REQUIRE
(
!
r
.
getLocalEchoByTxnId
(
"txnId3"
).
has_value
());
}
TEST_CASE
(
"Sending a message leaves a local echo"
,
"[client][room]"
)
{
ClientModel
m
;
const
auto
roomId
=
"!foo:tusooa.xyz"
s
;
m
.
roomList
.
rooms
=
m
.
roomList
.
rooms
.
set
(
roomId
,
RoomModel
{});
auto
[
next
,
dontCareEffect
]
=
ClientModel
::
update
(
m
,
SendMessageAction
{
roomId
,
Event
(
eventJson
)});
auto
localEchoes
=
next
.
roomList
.
rooms
[
roomId
].
localEchoes
;
REQUIRE
(
localEchoes
.
size
()
==
1
);
assert1Job
(
next
);
for1stJob
(
next
,
[
localEchoes
](
const
auto
&
job
)
{
REQUIRE
(
job
.
dataStr
(
"txnId"
)
==
localEchoes
[
0
].
txnId
);
});
}
TEST_CASE
(
"Failed send changes the status of the local echo"
,
"[client][room]"
)
{
ClientModel
m
;
const
auto
roomId
=
"!foo:tusooa.xyz"
s
;
m
.
roomList
.
rooms
=
m
.
roomList
.
rooms
.
set
(
roomId
,
RoomModel
{});
auto
[
next
,
dontCareEffect
]
=
ClientModel
::
update
(
m
,
SendMessageAction
{
roomId
,
Event
(
eventJson
)});
auto
resp
=
createResponse
(
"SendMessage"
,
json
::
object
({}),
json
{
{
"roomId"
,
roomId
},
{
"txnId"
,
next
.
roomList
.
rooms
[
roomId
].
localEchoes
[
0
].
txnId
},
});
resp
.
statusCode
=
500
;
std
::
tie
(
next
,
dontCareEffect
)
=
ClientModel
::
update
(
next
,
ProcessResponseAction
{
resp
});
auto
localEchoes
=
next
.
roomList
.
rooms
[
roomId
].
localEchoes
;
REQUIRE
(
localEchoes
.
size
()
==
1
);
REQUIRE
(
localEchoes
[
0
].
status
==
LocalEchoDesc
::
Failed
);
}
TEST_CASE
(
"Local echo with encrypted event"
,
"[client][room]"
)
{
boost
::
asio
::
io_context
io
;
SingleTypePromiseInterface
<
EffectStatus
>
sgph
{
AsioPromiseHandler
{
io
.
get_executor
()}};
ClientModel
m
;
m
.
crypto
=
Crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
RoomModel
room
;
room
.
encrypted
=
true
;
room
.
roomId
=
"!exampleroomid:example.com"
;
m
.
roomList
.
rooms
=
m
.
roomList
.
rooms
.
set
(
"!exampleroomid:example.com"
,
room
);
auto
jh
=
Kazv
::
CprJobHandler
{
io
.
get_executor
()};
auto
ee
=
Kazv
::
LagerStoreEventEmitter
(
lager
::
with_boost_asio_event_loop
{
io
.
get_executor
()});
auto
sdk
=
Kazv
::
makeSdk
(
SdkModel
{
m
},
jh
,
ee
,
Kazv
::
AsioPromiseHandler
{
io
.
get_executor
()},
zug
::
identity
);
auto
ctx
=
sdk
.
context
();
auto
saveLocalEchoCalled
=
0
;
auto
sendMessageCalled
=
0
;
auto
txnId
=
std
::
string
();
auto
mockContext
=
getMockContext
(
sgph
,
ctx
,
[
&
saveLocalEchoCalled
,
&
sendMessageCalled
,
&
txnId
](
const
auto
&
a
)
{
if
(
std
::
holds_alternative
<
SaveLocalEchoAction
>
(
a
))
{
++
saveLocalEchoCalled
;
}
else
if
(
std
::
holds_alternative
<
SendMessageAction
>
(
a
))
{
++
sendMessageCalled
;
REQUIRE
(
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
has_value
());
txnId
=
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
value
();
}
});
auto
client
=
Client
(
Client
::
InEventLoopTag
{},
mockContext
,
sdk
.
context
());
auto
r
=
client
.
room
(
"!exampleroomid:example.com"
);
REQUIRE
(
r
.
encrypted
().
make
().
get
());
r
.
sendTextMessage
(
"test"
)
.
then
([
&
io
](
auto
)
{
kzo
.
client
.
dbg
()
<<
"ended"
<<
std
::
endl
;
io
.
stop
();
});
io
.
run
();
REQUIRE
(
saveLocalEchoCalled
==
2
);
REQUIRE
(
sendMessageCalled
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
().
size
()
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
()[
0
].
txnId
==
txnId
);
}
TEST_CASE
(
"Encrypted room: Resend encrypted local echo"
,
"[client][room]"
)
{
boost
::
asio
::
io_context
io
;
SingleTypePromiseInterface
<
EffectStatus
>
sgph
{
AsioPromiseHandler
{
io
.
get_executor
()}};
ClientModel
m
;
m
.
crypto
=
Crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
RoomModel
room
;
room
.
encrypted
=
true
;
room
.
roomId
=
"!exampleroomid:example.com"
;
auto
encryptedEvent
=
Event
{
json
{
{
"content"
,
{{
"foo"
,
"bar"
}}},
{
"type"
,
"m.room.encrypted"
},
}};
auto
decryptedJson
=
json
{
{
"content"
,
{{
"dec-foo"
,
"dec-bar"
}}},
{
"type"
,
"m.room.message"
},
};
auto
event
=
encryptedEvent
.
setDecryptedJson
(
decryptedJson
,
Event
::
Decrypted
);
room
.
localEchoes
=
immer
::
flex_vector
<
LocalEchoDesc
>
{
{
"some-txn-id"
,
event
,
LocalEchoDesc
::
Failed
},
};
m
.
roomList
.
rooms
=
m
.
roomList
.
rooms
.
set
(
"!exampleroomid:example.com"
,
room
);
auto
jh
=
Kazv
::
CprJobHandler
{
io
.
get_executor
()};
auto
ee
=
Kazv
::
LagerStoreEventEmitter
(
lager
::
with_boost_asio_event_loop
{
io
.
get_executor
()});
auto
sdk
=
Kazv
::
makeSdk
(
SdkModel
{
m
},
jh
,
ee
,
Kazv
::
AsioPromiseHandler
{
io
.
get_executor
()},
zug
::
identity
);
auto
ctx
=
sdk
.
context
();
auto
saveLocalEchoCalled
=
0
;
auto
sendMessageCalled
=
0
;
auto
megOlmEncryptCalled
=
0
;
auto
mockContext
=
getMockContext
(
sgph
,
ctx
,
[
&
saveLocalEchoCalled
,
&
sendMessageCalled
,
&
megOlmEncryptCalled
](
const
auto
&
a
)
{
if
(
std
::
holds_alternative
<
SaveLocalEchoAction
>
(
a
))
{
++
saveLocalEchoCalled
;
}
else
if
(
std
::
holds_alternative
<
EncryptMegOlmEventAction
>
(
a
))
{
++
megOlmEncryptCalled
;
}
else
if
(
std
::
holds_alternative
<
SendMessageAction
>
(
a
))
{
++
sendMessageCalled
;
REQUIRE
(
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
has_value
());
REQUIRE
(
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
value
()
==
"some-txn-id"
);
}
});
auto
client
=
Client
(
Client
::
InEventLoopTag
{},
mockContext
,
sdk
.
context
());
auto
r
=
client
.
room
(
"!exampleroomid:example.com"
);
REQUIRE
(
r
.
encrypted
().
make
().
get
());
r
.
resendMessage
(
"some-txn-id"
)
.
then
([
&
io
](
auto
)
{
kzo
.
client
.
dbg
()
<<
"ended"
<<
std
::
endl
;
io
.
stop
();
});
io
.
run
();
REQUIRE
(
saveLocalEchoCalled
==
0
);
REQUIRE
(
megOlmEncryptCalled
==
0
);
REQUIRE
(
sendMessageCalled
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
().
size
()
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
()[
0
].
txnId
==
"some-txn-id"
);
}
TEST_CASE
(
"Unencrypted room: Resend unencrypted local echo"
,
"[client][room]"
)
{
boost
::
asio
::
io_context
io
;
SingleTypePromiseInterface
<
EffectStatus
>
sgph
{
AsioPromiseHandler
{
io
.
get_executor
()}};
ClientModel
m
;
m
.
crypto
=
Crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
RoomModel
room
;
room
.
encrypted
=
false
;
room
.
roomId
=
"!exampleroomid:example.com"
;
auto
event
=
Event
{
json
{
{
"content"
,
{{
"dec-foo"
,
"dec-bar"
}}},
{
"type"
,
"m.room.message"
},
}};
room
.
localEchoes
=
immer
::
flex_vector
<
LocalEchoDesc
>
{
{
"some-txn-id"
,
event
,
LocalEchoDesc
::
Failed
},
};
m
.
roomList
.
rooms
=
m
.
roomList
.
rooms
.
set
(
"!exampleroomid:example.com"
,
room
);
auto
jh
=
Kazv
::
CprJobHandler
{
io
.
get_executor
()};
auto
ee
=
Kazv
::
LagerStoreEventEmitter
(
lager
::
with_boost_asio_event_loop
{
io
.
get_executor
()});
auto
sdk
=
Kazv
::
makeSdk
(
SdkModel
{
m
},
jh
,
ee
,
Kazv
::
AsioPromiseHandler
{
io
.
get_executor
()},
zug
::
identity
);
auto
ctx
=
sdk
.
context
();
auto
saveLocalEchoCalled
=
0
;
auto
sendMessageCalled
=
0
;
auto
megOlmEncryptCalled
=
0
;
auto
mockContext
=
getMockContext
(
sgph
,
ctx
,
[
&
saveLocalEchoCalled
,
&
sendMessageCalled
,
&
megOlmEncryptCalled
](
const
auto
&
a
)
{
if
(
std
::
holds_alternative
<
SaveLocalEchoAction
>
(
a
))
{
++
saveLocalEchoCalled
;
}
else
if
(
std
::
holds_alternative
<
EncryptMegOlmEventAction
>
(
a
))
{
++
megOlmEncryptCalled
;
}
else
if
(
std
::
holds_alternative
<
SendMessageAction
>
(
a
))
{
++
sendMessageCalled
;
REQUIRE
(
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
has_value
());
REQUIRE
(
std
::
get
<
SendMessageAction
>
(
a
).
txnId
.
value
()
==
"some-txn-id"
);
}
});
auto
client
=
Client
(
Client
::
InEventLoopTag
{},
mockContext
,
sdk
.
context
());
auto
r
=
client
.
room
(
"!exampleroomid:example.com"
);
REQUIRE
(
!
r
.
encrypted
().
make
().
get
());
r
.
resendMessage
(
"some-txn-id"
)
.
then
([
&
io
](
auto
)
{
kzo
.
client
.
dbg
()
<<
"ended"
<<
std
::
endl
;
io
.
stop
();
});
io
.
run
();
REQUIRE
(
saveLocalEchoCalled
==
0
);
REQUIRE
(
megOlmEncryptCalled
==
0
);
REQUIRE
(
sendMessageCalled
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
().
size
()
==
1
);
REQUIRE
(
r
.
localEchoes
().
make
().
get
()[
0
].
txnId
==
"some-txn-id"
);
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Fri, Jul 18, 7:39 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
261437
Default Alt Text
local-echo-test.cpp (11 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment