Merge branch 'master' into badge-in-app-checkout

This commit is contained in:
spaced4ndy committed 2026-10-09 15:00:36 +04:00
commit 57ab333a2b
94 files changed
+2255 -405

No files matched your search

+16
View File
@@ -60,6 +60,7 @@ badgeTests = do
it "should accept unknown badge types" testUnknownBadgeType
it "credential serializes to a paste-able token and back" testCredentialSerialization
it "presentation headers encode and decode" testPresHeaderEncoding
it "presentation headers with binary bytes round-trip through JSON" testPresHeaderJSON
it "should reject a proof presented under another chat binding" testOtherChatBinding
describe "redemption codes" $ do
it "a generated code reads back" testCodeRoundTrip
@@ -227,6 +228,21 @@ testPresHeaderEncoding =
PHUnknown 'Z' "payload"
]
testPresHeaderJSON :: IO ()
testPresHeaderJSON =
mapM_
( \ph -> do
J.eitherDecode (J.encode ph) `shouldBe` Right ph
J.toJSON ph `shouldBe` J.toJSON (BBSPresHeader $ strEncode ph)
)
[ PHChat binaryBytes,
PHFileInv {chatBinding = binaryBytes, fileSize = 139737},
PHFileDescr {chatBinding = binaryBytes, fileSize = 139737, descrHash = binaryBytes, fileExpires = Just futureTime},
PHUnknown 'Z' binaryBytes
]
where
binaryBytes = "\0\1\31\34\92\127\128\195\255"
testOtherChatBinding :: IO ()
testOtherChatBinding = do
let ph = PHFileInv {chatBinding = aliceBinding, fileSize = 139737}
+1 -1
View File
@@ -641,7 +641,7 @@ smpServerCfg p =
maxJournalMsgCount = 24,
maxJournalStateLines = 4,
queueIdBytes = 24,
msgIdBytes = 6,
msgIdBytes = 24,
serverStoreCfg = SSCMemory Nothing, -- $ Just StorePaths {storeLogFile = "tmp/smp-server-store.log", storeMsgsFile = Just "tmp/smp-server-messages.log"},
storeNtfsFile = Nothing,
allowNewQueues = True,
+7
View File
@@ -1158,6 +1158,13 @@ testGetSetSMPServers =
alice <## " SMP servers"
alice <## " smp://2345-w==@smp2.example.im"
alice <## " smp://3456-w==@smp3.example.im:5224"
alice #$> ("/smp smp://2345-w==@smp2.example.im,smp4.example.im", id, "ok")
alice ##> "/smp smp://2345-w==@smp2.example.im,smp4.example.im,smp5.example.im"
alice <##. "bad chat command: user servers validation error(s): [USETooManyHosts"
alice ##> "/smp"
alice <## "Your servers"
alice <## " SMP servers"
alice <## " smp://2345-w==@smp2.example.im,smp4.example.im"
testTestSMPServerConnection :: HasCallStack => TestParams -> IO ()
testTestSMPServerConnection =
+60 -3
View File
@@ -38,7 +38,7 @@ import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId)
import Simplex.Chat.Messages.Batch (encodeBinaryBatch, encodeFwdElement)
import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText)
import Simplex.Chat.Options
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XMsgUpdate, XMsgNew, XMsgDel), FwdSender (FwdMember, FwdChannel), GrpMsgForward (GrpMsgForward), MsgContainer (..), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), mcSimple, msgContentText)
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XInfo, XMsgUpdate, XMsgNew, XMsgDel), FwdSender (FwdMember, FwdChannel), GrpMsgForward (GrpMsgForward), MsgContainer (..), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), mcSimple, msgContentText)
import Simplex.Chat.Types
import Simplex.Chat.Types.MemberRelations (MemberRelation (..), getRelation, setRelation)
import Simplex.Chat.Types.Shared (GroupMemberRole (..), GroupAcceptance (..))
@@ -119,6 +119,7 @@ chatGroupTests = do
xit "create and join group when clients go offline" testGroupAsync
describe "group links" $ do
it "create group link, join via group link" testGroupLink
it "join via group link with group profile near the size limit" testGroupLinkLargeGroupProfile
it "invitees were previously connected as contacts" testGroupLinkInviteesWereConnected
it "all members were previously connected as contacts" testGroupLinkAllMembersWereConnected
it "delete group, re-join via same link" testGroupLinkDeleteGroupRejoin
@@ -176,6 +177,7 @@ chatGroupTests = do
it "manually accept contact with group member incognito" testMemberContactAcceptIncognito
describe "group message forwarding" $ do
it "forward messages between invitee and introduced (x.msg.new)" testGroupMsgForwardMessage
it "reject forwarded content attributed to own membership" testGroupMsgForwardOwnMembershipRejected
it "forward batched messages" testGroupMsgForwardBatched
it "forward reports to moderators, don't forward to members (x.msg.new, MCReport)" testGroupMsgForwardReport
it "deduplicate forwarded messages" testGroupMsgForwardDeduplicate
@@ -3042,6 +3044,39 @@ testPlanGroupLinkLeaveRejoin =
bob <## "group link: known group #team_1"
bob <## "use #team_1 <message> to send messages"
testGroupLinkLargeGroupProfile :: HasCallStack => TestParams -> IO ()
testGroupLinkLargeGroupProfile =
testChatOpts2 testOptsNoFullLinks aliceProfile bobProfile $
\alice bob -> do
threadDelay 100000
alice ##> "/g team"
alice <## "group #team is created"
alice <## "to add members use /a team <name> or /create link #team"
alice ##> "/set history #team off"
alice <## "updated group preferences:"
alice <## "Recent history: off"
withCCTransaction alice $ \db ->
DB.execute db "UPDATE group_profiles SET description = ? WHERE display_name = ?" (T.pack welcome, "team" :: T.Text)
alice ##> "/create link #team"
gLink <- getGroupLink_ alice "team" GRMember True
alice <// 100000
bob ##> ("/c " <> gLink)
bob <## "connection request sent!"
alice <## "bob (Bob): accepting request to join group #team..."
concurrentlyN_
[ alice <## "#team: bob joined the group",
do
bob <## "#team: joining the group..."
bob <## "#team: you joined the group"
]
alice #> "#team hello"
let waitHello = do
l <- getTermLine bob
if "alice> hello" `isSuffixOf` l then pure () else waitHello
waitHello
where
welcome = unwords $ replicate 1775 "welcome"
testGroupLink :: HasCallStack => TestParams -> IO ()
testGroupLink =
testChatOpts3 testOptsNoFullLinks aliceProfile bobProfile cathProfile $
@@ -5399,6 +5434,28 @@ testGroupMsgForwardMessage =
cath <# "#team bob> hi there [>>]"
cath <# "#team hey team"
testGroupMsgForwardOwnMembershipRejected :: HasCallStack => TestParams -> IO ()
testGroupMsgForwardOwnMembershipRejected =
testChat2 aliceProfile bobProfile $
\alice bob -> do
createGroup2 "team" alice bob
threadDelay 1000000
[Only bobMemId] <- withCCTransaction bob $ \db ->
DB.query db "SELECT member_id FROM group_members WHERE member_category = ?" (Only ("user" :: T.Text)) :: IO [Only ByteString]
connId <- relayConnIdToMember alice "bob"
ts <- getCurrentTime
let ChatController {smpAgent = aliceAgent} = chatController alice
forgedProfile = (bobProfile :: Profile) {displayName = "mallory", fullName = "Mallory"}
chatMsg = ChatMessage chatInitialVRange (Just $ SharedMsgId "forged_info1") (XInfo forgedProfile Nothing)
fwd = GrpMsgForward (FwdMember (MemberId bobMemId) "bob") ts
body = encodeBinaryBatch [encodeFwdElement fwd (VMUnsigned chatMsg)]
sent <- runExceptT $ sendMessages aliceAgent [(connId, PQEncOff, MsgFlags False, vrValue body)]
either (fail . show) (const $ pure ()) sent
bob <##. "error: x.grp.msg.forward: content attributed to own membership"
bob ##> "/p"
bob <## "user profile: bob (Bob)"
bob <## "use /p <name> [<bio>] to change it"
testGroupMsgForwardBatched :: HasCallStack => TestParams -> IO ()
testGroupMsgForwardBatched =
testChat3 aliceProfile bobProfile cathProfile $
@@ -7979,8 +8036,8 @@ testForwardQuoteMention =
bob <# "alice> -> forwarded"
bob <## " hello @alice @cath"
-- member renamed to duplicate name
cath ##> "/p alice_1"
cath <## "user profile is changed to alice_1 (your 1 contacts are notified)"
cath ##> "/p alice"
cath <## "user profile is changed to alice (your 1 contacts are notified)"
alice <## "contact cath changed to alice_1"
alice <## "use @alice_1 <message> to send messages"
-- mention changed in quoted mentions
+120
View File
@@ -16,6 +16,9 @@ import Test.Hspec hiding (it)
chatNamesTests :: SpecWith TestParams
chatNamesTests = do
it "connect by resolved name" testConnectByName
it "profile update without the name keeps the name" testUpdateProfileKeepsName
it "stopping address sharing removes the name" testAddressSharingOffRemovesName
it "deleting the address removes the name" testAddressDeleteRemovesName
it "connect by name not claimed in link profile is rejected" testConnectByNameNotClaimed
it "connect by name to a known contact not claimed in profile is rejected" testConnectByNameKnownContactNotClaimed
it "connect by unregistered name fails to resolve" testConnectByNameNotFound
@@ -62,6 +65,123 @@ testConnectByName ps = withSmpServerAndNames ps $ \reg ->
_ <- getTermLine bob
pure ()
testUpdateProfileKeepsName :: HasCallStack => TestParams -> IO ()
testUpdateProfileKeepsName ps = withSmpServerAndNames ps $ \reg ->
testChat2 aliceProfile bobProfile (test reg) ps
where
aliceName = SimplexNameInfo NTContact (SimplexDomain TLDSimplex "alice" [])
test reg alice bob = do
mapM_ enableNamesRole [alice, bob]
alice ##> "/ad"
(shortLink, _) <- getContactLinks alice True
registerName reg aliceName (contactNameRecord "alice.simplex" (T.pack shortLink))
alice ##> "/_set domain 1 alice.simplex"
alice <## "new contact address set"
bob ##> "/c @alice.simplex"
bob <## "alice: connection started"
alice <## "bob (Bob) wants to connect to you!"
alice <## "to accept: /ac bob"
alice <## "to reject: /rc bob (the sender will NOT be notified)"
alice ##> "/ac bob"
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")
alice ##> ("/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"\", \"shortDescr\": \"new bio\", \"contactLink\": \"" <> shortLink <> "\"}")
alice <## "user bio changed to new bio (your 1 contacts are notified)"
bob <## "contact alice updated bio: new bio"
bob ##> "/i alice"
bob <## "contact ID: 2"
bob <## "receiving messages via: localhost"
bob <## "sending messages via: localhost"
_ <- getTermLine bob
bob <## "SimpleX name: @alice.simplex (verified)"
bob <## "you've shared main profile with this contact"
bob <## "connection not verified, use /code command to see security code"
bob <## "quantum resistant end-to-end encryption"
_ <- getTermLine bob
pure ()
testAddressSharingOffRemovesName :: HasCallStack => TestParams -> IO ()
testAddressSharingOffRemovesName ps = withSmpServerAndNames ps $ \reg ->
testChat2 aliceProfile bobProfile (test reg) ps
where
aliceName = SimplexNameInfo NTContact (SimplexDomain TLDSimplex "alice" [])
test reg alice bob = do
mapM_ enableNamesRole [alice, bob]
alice ##> "/ad"
(shortLink, _) <- getContactLinks alice True
registerName reg aliceName (contactNameRecord "alice.simplex" (T.pack shortLink))
alice ##> "/_set domain 1 alice.simplex"
alice <## "new contact address set"
bob ##> "/c @alice.simplex"
bob <## "alice: connection started"
alice <## "bob (Bob) wants to connect to you!"
alice <## "to accept: /ac bob"
alice <## "to reject: /rc bob (the sender will NOT be notified)"
alice ##> "/ac bob"
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")
alice ##> "/pa off"
alice <## "contact address removed"
bob <## "alice removed contact address"
checkNoName bob
alice ##> "/pa on"
alice <## "new contact address set"
bob <## "alice set new contact address, use /info alice to view"
bob ##> "/i alice"
bob <## "contact ID: 2"
bob <## "receiving messages via: localhost"
bob <## "sending messages via: localhost"
_ <- getTermLine bob
bob <## "you've shared main profile with this contact"
bob <## "connection not verified, use /code command to see security code"
bob <## "quantum resistant end-to-end encryption"
_ <- getTermLine bob
pure ()
testAddressDeleteRemovesName :: HasCallStack => TestParams -> IO ()
testAddressDeleteRemovesName ps = withSmpServerAndNames ps $ \reg ->
testChat2 aliceProfile bobProfile (test reg) ps
where
aliceName = SimplexNameInfo NTContact (SimplexDomain TLDSimplex "alice" [])
test reg alice bob = do
mapM_ enableNamesRole [alice, bob]
alice ##> "/ad"
(shortLink, _) <- getContactLinks alice True
registerName reg aliceName (contactNameRecord "alice.simplex" (T.pack shortLink))
alice ##> "/_set domain 1 alice.simplex"
alice <## "new contact address set"
bob ##> "/c @alice.simplex"
bob <## "alice: connection started"
alice <## "bob (Bob) wants to connect to you!"
alice <## "to accept: /ac bob"
alice <## "to reject: /rc bob (the sender will NOT be notified)"
alice ##> "/ac bob"
alice <## "bob (Bob): accepting contact request, you can send messages to contact"
concurrently_
(bob <## "alice (Alice): contact is connected")
(alice <## "bob (Bob): contact is connected")
alice ##> "/da"
alice <## "Your chat address is deleted - accepted contacts will remain connected."
alice <## "To create a new chat address use /ad"
bob <## "alice removed contact address"
checkNoName bob
checkNoName :: HasCallStack => TestCC -> IO ()
checkNoName bob = do
bob ##> "/i alice"
bob <## "contact ID: 2"
bob <## "receiving messages via: localhost"
bob <## "sending messages via: localhost"
bob <## "you've shared main profile with this contact"
bob <## "connection not verified, use /code command to see security code"
bob <## "quantum resistant end-to-end encryption"
_ <- getTermLine bob
pure ()
testConnectByNameNotClaimed :: HasCallStack => TestParams -> IO ()
testConnectByNameNotClaimed ps = withSmpServerAndNames ps $ \reg ->
testChat2 aliceProfile bobProfile (test reg) ps
+115
View File
@@ -46,6 +46,10 @@ chatProfileTests :: SpecWith TestParams
chatProfileTests = do
describe "user profiles" $ do
it "update user profile and notify contacts" testUpdateProfile
it "profile is not saved when its address data upload fails" testUpdateProfileAddressDataError
it "profile name used by a contact gets a local name with suffix" testUpdateProfileNameUsedByContact
it "profile name used by another user is rejected, names with _N suffix are invalid" testUpdateProfileNameUsedByUser
it "profile name used by a hidden user is rejected" testUpdateProfileNameUsedByHiddenUser
it "profile description round-trips and shows in contact info" testProfileDescriptionShown
it "member profile description is redacted for members without a direct contact" testMemberDescriptionRedacted
it "update user profile with image" testUpdateProfileImage
@@ -210,6 +214,117 @@ testUpdateProfile =
bob <## "use @cat <message> to send messages"
]
testUpdateProfileAddressDataError :: HasCallStack => TestParams -> IO ()
testUpdateProfileAddressDataError ps = do
let cfg' = testCfg {agentConfig = testAgentCfg {persistErrorInterval = 0}}
withNewTestChatCfgOpts ps cfg' opts' "alice" aliceProfile $ \alice ->
withNewTestChatCfgOpts ps cfg' opts' "bob" bobProfile $ \bob -> do
withSmpServer' serverCfg' $ do
connectUsers alice bob
alice ##> "/ad"
_ <- getContactLinks alice True
pure ()
alice <## "disconnected 2 connections on server localhost"
bob <## "disconnected 1 connections on server localhost"
alice ##> "/p alisa"
alice <##. "smp agent error: BROKER"
alice ##> "/p"
alice <## "user profile: alice (Alice)"
alice <## "use /p <name> [<bio>] to change it"
withSmpServer' serverCfg' $ do
alice <## "subscribed 2 connections on server localhost"
bob <## "subscribed 1 connections on server localhost"
alice ##> "/p alisa"
alice <## "user profile is changed to alisa (your 1 contacts are notified)"
bob <## "contact alice changed to alisa"
bob <## "use @alisa <message> to send messages"
alice <## "disconnected 2 connections on server localhost"
bob <## "disconnected 1 connections on server localhost"
where
serverCfg' =
(smpServerCfg ps)
{ transports = [(smpTestPort2 ps, transport @TLS, False)],
serverStoreCfg = persistentServerStoreCfg (tmpPath ps)
}
opts' =
testOpts
{ coreOptions =
testCoreOpts
{ smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"]
}
}
testUpdateProfileNameUsedByContact :: HasCallStack => TestParams -> IO ()
testUpdateProfileNameUsedByContact =
testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do
connectUsers alice bob
alice ##> "/ad"
(shortLink, _) <- getContactLinks alice True
alice ##> "/p bob"
alice <## "user profile is changed to bob (your 1 contacts are notified)"
bob <## "contact alice changed to bob_1"
bob <## "use @bob_1 <message> to send messages"
alice ##> "/p"
alice <## "user profile: bob"
alice <## "use /p <name> [<bio>] to change it"
alice ##> "/users"
alice <## "bob_1 (active)"
alice #> "@bob hi"
bob <# "bob_1> hi"
cath ##> ("/_connect plan 1 " <> shortLink)
cath <## "contact address: ok to connect"
sLinkData <- getTermLine cath
sLinkData `shouldContain` "\"displayName\":\"bob\""
alice ##> "/p alice"
alice <## "user profile is changed to alice (your 1 contacts are notified)"
bob <## "contact bob_1 changed to alice"
bob <## "use @alice <message> to send messages"
alice ##> "/users"
alice <## "alice (active)"
testUpdateProfileNameUsedByHiddenUser :: HasCallStack => TestParams -> IO ()
testUpdateProfileNameUsedByHiddenUser =
testChat aliceProfile $
\alice -> do
alice ##> "/create user alisa"
showActiveUser alice "alisa"
alice ##> "/hide user my_password"
alice <## "current user alisa:"
alice <## "messages are hidden (use /tail to view)"
alice <## "profile is hidden"
alice ##> "/user alice"
showActiveUser alice "alice (Alice)"
alice ##> "/p alisa"
alice <## "invalid display name: alisa"
testUpdateProfileNameUsedByUser :: HasCallStack => TestParams -> IO ()
testUpdateProfileNameUsedByUser =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
alice ##> "/create user alisa"
showActiveUser alice "alisa"
alice ##> "/user alice"
showActiveUser alice "alice (Alice)"
alice ##> "/p alisa"
alice <## "user with the name alisa already exists"
alice ##> "/p alisa_1"
alice <## "invalid display name: alisa_1"
alice <## "you could use this one: alisa-1"
alice ##> "/p alisa-1"
alice <## "user profile is changed to alisa-1 (your 1 contacts are notified)"
bob <## "contact alice changed to alisa-1"
bob <## "use @alisa-1 <message> to send messages"
alice ##> "/create user alisa-1"
alice <## "user with the name alisa-1 already exists"
alice ##> "/create user bob_2"
alice <## "invalid display name: bob_2"
alice <## "you could use this one: bob-2"
alice ##> "/users"
alice <## "alisa"
alice <## "alisa-1 (active)"
-- Profile.description survives the connect-time round-trip and is shown in the contact /i view.
testProfileDescriptionShown :: HasCallStack => TestParams -> IO ()
testProfileDescriptionShown =
+7
View File
@@ -33,6 +33,7 @@ import Simplex.Chat.Protocol
GrpMsgForward (GrpMsgForward),
MsgContent (MCText),
VerifiedMsg (VMUnsigned),
fwdMemberName,
maxBatchElementCount,
maxEncodedMsgLength,
mcSimple,
@@ -49,6 +50,7 @@ batchingTests = describe "message batching tests" $ do
it "splits a batch that exceeds the element count limit" testBatchElementCountLimit
it "does not create a relay delivery body when every task is oversized" testRelayBatchAllLarge
it "classifies a task that fits raw but not as a framed singleton as large" testRelayBatchSingletonOverflow
it "shortens forwarded member names" testFwdMemberName
instance IsString SndMessage where
fromString s = SndMessage {msgId, sharedMsgId = SharedMsgId "", msgBody = s', signedMsg_ = Nothing}
@@ -190,6 +192,11 @@ testRelayBatchSingletonOverflow = do
map deliveryTaskId accepted `shouldBe` []
map deliveryTaskId large `shouldBe` [1]
testFwdMemberName :: IO ()
testFwdMemberName = do
fwdMemberName "sixteen_chars_ab" `shouldBe` "sixteen_chars_ab"
fwdMemberName "seventeen_chars_a" `shouldBe` "seventeen_chars_…"
runBatcherTest :: BatchMode -> Int -> [SndMessage] -> [ChatError] -> [ByteString] -> Spec
runBatcherTest mode maxLen msgs expectedErrors expectedBatches =
it
+3 -9
View File
@@ -369,17 +369,11 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
"{\"v\":\"9\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} (Just MemberRestrictions {restriction = MRSBlocked})
it "x.grp.mem.inv" $
"{\"v\":\"9\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
it "x.grp.mem.inv w/t directConnReq" $
"{\"v\":\"9\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}"
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
it "x.grp.mem.fwd" $
"{\"v\":\"9\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile, memberKey = Nothing} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
#==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq}
it "x.grp.mem.fwd with member chat version range" $
"{\"v\":\"9\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-4%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"9-21\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile, memberKey = Nothing} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile, memberKey = Nothing} IntroInvitation {groupConnReq = testConnReq}
it "x.grp.mem.info" $
"{\"v\":\"9\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XGrpMemInfo (MemberId "\1\2\3\4") testProfile
+7
View File
@@ -57,3 +57,10 @@ testMkValidName = do
mkValidName "123 alice" `shouldBe` "123 alice"
mkValidName "01234567890123456789012345678901234567890123456789extra" `shouldBe` "01234567890123456789012345678901234567890123456789"
mkValidName "0123456789012345678901234567890123456789012345678 extra" `shouldBe` "0123456789012345678901234567890123456789012345678"
mkValidName "alice_1" `shouldBe` "alice-1"
mkValidName "alice_bob_12" `shouldBe` "alice_bob-12"
mkValidName "alice _1" `shouldBe` "alice -1"
mkValidName "alice_" `shouldBe` "alice_"
mkValidName "alice_bob" `shouldBe` "alice_bob"
mkValidName "alice_1b" `shouldBe` "alice_1b"
mkValidName (mkValidName "alice_1") `shouldBe` "alice-1"