diff --git a/Emulsion.Tests/Emulsion.Tests.fsproj b/Emulsion.Tests/Emulsion.Tests.fsproj index 3491ce81..2300bc39 100644 --- a/Emulsion.Tests/Emulsion.Tests.fsproj +++ b/Emulsion.Tests/Emulsion.Tests.fsproj @@ -10,7 +10,7 @@ - + @@ -20,8 +20,9 @@ - + + diff --git a/Emulsion.Tests/Settings.fs b/Emulsion.Tests/SettingsTests.fs similarity index 96% rename from Emulsion.Tests/Settings.fs rename to Emulsion.Tests/SettingsTests.fs index 2045bc31..596c1b80 100644 --- a/Emulsion.Tests/Settings.fs +++ b/Emulsion.Tests/SettingsTests.fs @@ -1,4 +1,4 @@ -module Emulsion.Tests.Settings +module Emulsion.Tests.SettingsTests open System open System.IO diff --git a/Emulsion.Tests/Xmpp/EmulsionXmppTests.fs b/Emulsion.Tests/Xmpp/EmulsionXmppTests.fs new file mode 100644 index 00000000..ac263001 --- /dev/null +++ b/Emulsion.Tests/Xmpp/EmulsionXmppTests.fs @@ -0,0 +1,170 @@ +module Emulsion.Tests.Xmpp.EmulsionXmppTests + +open System +open System.Threading.Tasks + +open JetBrains.Lifetimes +open SharpXMPP +open Xunit +open Xunit.Abstractions + +open Emulsion.Settings +open Emulsion +open Emulsion.Lifetimes +open Emulsion.Tests.TestUtils +open Emulsion.Tests.Xmpp +open Emulsion.Xmpp +open Emulsion.Xmpp.SharpXmppHelper.Elements + +let private settings = { + Login = "user@example.org" + Password = "password" + Room = "room@conference.example.org" + Nickname = "nickname" +} + +type RunTests(outputHelper: ITestOutputHelper) = + let logger = Logging.xunitLogger outputHelper + + [] + member __.``EmulsionXmpp connects the server``(): unit = + let mutable connectionFailedHandler = ignore + let disconnect() = connectionFailedHandler(ConnFailedArgs()) + let mutable connectCalled = false + let client = + XmppClientFactory.create( + addConnectionFailedHandler = (fun _ h -> connectionFailedHandler <- h), + connect = (fun () -> async { + connectCalled <- true + disconnect() + }) + ) + Assert.ThrowsAny(fun() -> Async.RunSynchronously <| EmulsionXmpp.run settings logger client ignore) + |> ignore + Assert.True connectCalled + + [] + member __.``EmulsionXmpp connects the room``(): unit = + let mutable connectionFailedHandler = ignore + let disconnect() = connectionFailedHandler(ConnFailedArgs()) + let mutable joinRoomArgs = Unchecked.defaultof<_> + let client = + XmppClientFactory.create( + addConnectionFailedHandler = (fun _ h -> connectionFailedHandler <- h), + joinMultiUserChat = (fun roomJid nickname -> + joinRoomArgs <- (roomJid.FullJid, nickname) + disconnect() + ) + ) + Assert.ThrowsAny(fun() -> Async.RunSynchronously <| EmulsionXmpp.run settings logger client ignore) + |> ignore + Assert.Equal((settings.Room, settings.Nickname), joinRoomArgs) + +type ReceiveMessageTests(outputHelper: ITestOutputHelper) = + let logger = Logging.xunitLogger outputHelper + + let runReceiveMessageTest message = + let mutable connectionFailedHandler = ignore + let receiveHandlers = ResizeArray() + + let sendMessage msg = receiveHandlers |> Seq.iter (fun h -> h msg) + let disconnect() = connectionFailedHandler(ConnFailedArgs()) + + let mutable messageReceived = None + let onMessageReceived = fun m -> messageReceived <- Some m + + let client = + XmppClientFactory.create( + addConnectionFailedHandler = (fun _ h -> connectionFailedHandler <- h), + addMessageHandler = (fun _ h -> receiveHandlers.Add h), + joinMultiUserChat = fun _ _ -> + sendMessage message + disconnect() + ) + Assert.ThrowsAny(fun() -> + Async.RunSynchronously <| EmulsionXmpp.run settings logger client onMessageReceived + ) |> ignore + + messageReceived + + [] + member __.``Ordinary message gets received by the client``(): unit = + let incomingMessage = XmppMessageFactory.create("room@conference.example.org/sender", + "test", + messageType = "groupchat") + let receivedMessage = runReceiveMessageTest incomingMessage + Assert.Equal(Some <| XmppMessage { author = "sender"; text = "test" }, receivedMessage) + + [] + member __.``Own message gets skipped by the client``(): unit = + let ownMessage = XmppMessageFactory.create("room@conference.example.org/nickname", + "test", + messageType = "groupchat") + let receivedMessage = runReceiveMessageTest ownMessage + Assert.Equal(None, receivedMessage) + + [] + member __.``Historical message gets skipped by the client``(): unit = + let historicalMessage = XmppMessageFactory.create("room@conference.example.org/sender", + "test", + messageType = "groupchat", + delayDate = "2019-01-01") + let receivedMessage = runReceiveMessageTest historicalMessage + Assert.Equal(None, receivedMessage) + + [] + member __.``Empty message gets skipped by the client``(): unit = + let emptyMessage = XmppMessageFactory.create("room@conference.example.org/sender", + "", + messageType = "groupchat") + let receivedMessage = runReceiveMessageTest emptyMessage + Assert.Equal(None, receivedMessage) + +type SendTests(outputHelper: ITestOutputHelper) = + let logger = Logging.xunitLogger outputHelper + + [] + member __.``send function calls the Send method on the client``(): unit = + use ld = Lifetime.Define() + let lt = ld.Lifetime + let mutable sentMessage = Unchecked.defaultof<_> + let client = XmppClientFactory.create(send = fun m -> + sentMessage <- m + ld.Terminate() + ) + + let outgoingMessage = { author = "author"; text = "text" } + Assert.Throws(fun () -> + Async.RunSynchronously <| EmulsionXmpp.send logger client lt settings outgoingMessage + ) |> ignore + + let text = sentMessage.Element(Body).Value + Assert.Equal(" text", text) + + [] + member __.``send function awaits the message delivery``(): Task = + upcast (async { + use ld = Lifetime.Define() + let lt = ld.Lifetime + let messageId = nestedTaskCompletionSource lt + let messageHandlers = ResizeArray() + let onMessage msg = messageHandlers |> Seq.iter (fun h -> h msg) + + let client = + XmppClientFactory.create( + addMessageHandler = (fun _ h -> messageHandlers.Add h), + send = fun m -> messageId.SetResult(SharpXmppHelper.getMessageId m) + ) + let outgoingMessage = { author = "author"; text = "text" } + + let! receival = Async.StartChild <| EmulsionXmpp.send logger client lt settings outgoingMessage + let receivalTask = Async.StartAsTask receival + let! messageId = Async.AwaitTask messageId.Task // the send has been completed + + // Wait for 100 ms to check that the receival is not completed yet: + Assert.False(receivalTask.Wait(TimeSpan.FromMilliseconds 100.0)) + + let deliveryMessage = SharpXmppHelper.message messageId "" "" + onMessage deliveryMessage + do! receival + } |> Async.StartAsTask) diff --git a/Emulsion.Tests/Xmpp/SharpXmppHelper.fs b/Emulsion.Tests/Xmpp/SharpXmppHelperTests.fs similarity index 98% rename from Emulsion.Tests/Xmpp/SharpXmppHelper.fs rename to Emulsion.Tests/Xmpp/SharpXmppHelperTests.fs index a7cb773a..68e6189e 100644 --- a/Emulsion.Tests/Xmpp/SharpXmppHelper.fs +++ b/Emulsion.Tests/Xmpp/SharpXmppHelperTests.fs @@ -1,4 +1,4 @@ -module Emulsion.Tests.Xmpp.SharpXmppHelper +module Emulsion.Tests.Xmpp.SharpXmppHelperTests open System.Xml.Linq diff --git a/Emulsion.Tests/Xmpp/XmppClientTests.fs b/Emulsion.Tests/Xmpp/XmppClientTests.fs index eb14865d..9f267dd6 100644 --- a/Emulsion.Tests/Xmpp/XmppClientTests.fs +++ b/Emulsion.Tests/Xmpp/XmppClientTests.fs @@ -1,4 +1,4 @@ -namespace Emulsion.Tests.Xmpp +module Emulsion.Tests.Xmpp.XmppClientTests open System open System.Threading.Tasks @@ -9,223 +9,218 @@ open SharpXMPP open SharpXMPP.XMPP open SharpXMPP.XMPP.Client.Elements open Xunit -open Xunit.Abstractions -open Emulsion.Tests.TestUtils open Emulsion.Xmpp open Emulsion.Xmpp.SharpXmppHelper.Attributes open Emulsion.Xmpp.SharpXmppHelper.Elements -type XmppClientTests(testOutput: ITestOutputHelper) = - let logger = Logging.xunitLogger testOutput - - let createPresenceFor (roomJid: JID) nickname = - let presence = XMPPPresence() - let participantJid = JID(roomJid.FullJid) - participantJid.Resource <- nickname - presence.SetAttributeValue(From, participantJid.FullJid) - presence - - let createSelfPresence roomJid nickname = - let presence = createPresenceFor roomJid nickname - let x = XElement X - let status = XElement Status - status.SetAttributeValue(Code, "110") - x.Add status - presence.Add x - presence - - let createErrorPresence roomJid nickname errorXml = - let presence = createPresenceFor roomJid nickname - presence.SetAttributeValue(Type, "error") - let error = XElement Error - let errorChild = XElement.Parse errorXml - error.Add errorChild - presence.Add error - presence - - let createLeavePresence roomJid nickname = - let presence = createSelfPresence roomJid nickname - presence.SetAttributeValue(Type, "unavailable") - presence - - let sendPresence presence handlers = - Seq.iter (fun h -> h presence) handlers - - let createErrorMessage (message: XMPPMessage) errorXml = - // An error message is an exact copy of the original with the "error" element added: - let errorMessage = XMPPMessage() - message.Attributes() |> Seq.iter (fun a -> errorMessage.SetAttributeValue(a.Name, a.Value)) - message.Elements() |> Seq.iter (fun e -> errorMessage.Add e) - - let error = XElement Error - let errorChild = XElement.Parse errorXml - error.Add errorChild - errorMessage.Add error - errorMessage - - [] - member __.``connect function calls the Connect method of the client passed``(): unit = - let mutable connectCalled = false - let client = XmppClientFactory.create(fun () -> async { connectCalled <- true }) - Async.RunSynchronously <| XmppClient.connect logger client |> ignore - Assert.True connectCalled - - [] - member __.``connect function returns a lifetime terminated whenever the ConnectionFailed callback is triggered``() - : unit = - let mutable callback = ignore - let client = XmppClientFactory.create(addConnectionFailedHandler = fun _ h -> callback <- h) - let lt = Async.RunSynchronously <| XmppClient.connect logger client - Assert.True lt.IsAlive - callback(ConnFailedArgs()) - Assert.False lt.IsAlive - - [] - member __.``enter function calls JoinMultiUserChat``(): unit = - let mutable called = false - let mutable presenceHandlers = ResizeArray() - let client = - XmppClientFactory.create( - addPresenceHandler = (fun _ h -> presenceHandlers.Add h), - joinMultiUserChat = fun roomJid nickname -> - called <- true - Seq.iter (fun h -> h (createSelfPresence roomJid nickname)) presenceHandlers - ) - let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } - Lifetime.Using(fun lt -> - Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo |> ignore - Assert.True called +let private createPresenceFor (roomJid: JID) nickname = + let presence = XMPPPresence() + let participantJid = JID(roomJid.FullJid) + participantJid.Resource <- nickname + presence.SetAttributeValue(From, participantJid.FullJid) + presence + +let private createSelfPresence roomJid nickname = + let presence = createPresenceFor roomJid nickname + let x = XElement X + let status = XElement Status + status.SetAttributeValue(Code, "110") + x.Add status + presence.Add x + presence + +let private createErrorPresence roomJid nickname errorXml = + let presence = createPresenceFor roomJid nickname + presence.SetAttributeValue(Type, "error") + let error = XElement Error + let errorChild = XElement.Parse errorXml + error.Add errorChild + presence.Add error + presence + +let private createLeavePresence roomJid nickname = + let presence = createSelfPresence roomJid nickname + presence.SetAttributeValue(Type, "unavailable") + presence + +let private sendPresence presence handlers = + Seq.iter (fun h -> h presence) handlers + +let private createErrorMessage (message: XMPPMessage) errorXml = + // An error message is an exact copy of the original with the "error" element added: + let errorMessage = XMPPMessage() + message.Attributes() |> Seq.iter (fun a -> errorMessage.SetAttributeValue(a.Name, a.Value)) + message.Elements() |> Seq.iter (fun e -> errorMessage.Add e) + + let error = XElement Error + let errorChild = XElement.Parse errorXml + error.Add errorChild + errorMessage.Add error + errorMessage + +[] +let ``connect function calls the Connect method of the client passed``(): unit = + let mutable connectCalled = false + let client = XmppClientFactory.create(fun () -> async { connectCalled <- true }) + Async.RunSynchronously <| XmppClient.connect client |> ignore + Assert.True connectCalled + +[] +let ``connect function returns a lifetime terminated whenever the ConnectionFailed callback is triggered``() + : unit = + let mutable callback = ignore + let client = XmppClientFactory.create(addConnectionFailedHandler = fun _ h -> callback <- h) + let lt = Async.RunSynchronously <| XmppClient.connect client + Assert.True lt.IsAlive + callback(ConnFailedArgs()) + Assert.False lt.IsAlive + +[] +let ``enter function calls JoinMultiUserChat``(): unit = + let mutable called = false + let mutable presenceHandlers = ResizeArray() + let client = + XmppClientFactory.create( + addPresenceHandler = (fun _ h -> presenceHandlers.Add h), + joinMultiUserChat = fun roomJid nickname -> + called <- true + Seq.iter (fun h -> h (createSelfPresence roomJid nickname)) presenceHandlers ) - - [] - member __.``enter throws an exception in case of an error presence``(): unit = - let mutable presenceHandlers = ResizeArray() - let client = - XmppClientFactory.create( - addPresenceHandler = (fun _ h -> presenceHandlers.Add h), - joinMultiUserChat = fun roomJid nickname -> - sendPresence (createErrorPresence roomJid nickname "") presenceHandlers - ) - let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } - Lifetime.Using(fun lt -> - let ae = Assert.Throws(fun () -> - Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo |> ignore - ) - let ex = Seq.exactlyOne ae.InnerExceptions - Assert.Contains("", ex.Message) + let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } + Lifetime.Using(fun lt -> + Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo |> ignore + Assert.True called + ) + +[] +let ``enter throws an exception in case of an error presence``(): unit = + let mutable presenceHandlers = ResizeArray() + let client = + XmppClientFactory.create( + addPresenceHandler = (fun _ h -> presenceHandlers.Add h), + joinMultiUserChat = fun roomJid nickname -> + sendPresence (createErrorPresence roomJid nickname "") presenceHandlers ) - - [] - member __.``Lifetime returned from enter terminates by a room leave presence``(): unit = - let mutable presenceHandlers = ResizeArray() - let client = - XmppClientFactory.create( - addPresenceHandler = (fun _ h -> presenceHandlers.Add h), - joinMultiUserChat = fun roomJid nickname -> - sendPresence (createSelfPresence roomJid nickname) presenceHandlers - ) - let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } - Lifetime.Using(fun lt -> - let roomLt = Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo - Assert.True roomLt.IsAlive - sendPresence (createLeavePresence roomInfo.RoomJid roomInfo.Nickname) presenceHandlers - Assert.False roomLt.IsAlive + let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } + Lifetime.Using(fun lt -> + let ae = Assert.Throws(fun () -> + Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo |> ignore ) - - [] - member __.``Lifetime returned from enter terminates by an external lifetime termination``(): unit = - let mutable presenceHandlers = ResizeArray() - let client = - XmppClientFactory.create( - addPresenceHandler = (fun _ h -> presenceHandlers.Add h), - joinMultiUserChat = fun roomJid nickname -> - sendPresence (createSelfPresence roomJid nickname) presenceHandlers - ) - let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } - use ld = Lifetime.Define() - let lt = ld.Lifetime + let ex = Seq.exactlyOne ae.InnerExceptions + Assert.Contains("", ex.Message) + ) + +[] +let ``Lifetime returned from enter terminates by a room leave presence``(): unit = + let mutable presenceHandlers = ResizeArray() + let client = + XmppClientFactory.create( + addPresenceHandler = (fun _ h -> presenceHandlers.Add h), + joinMultiUserChat = fun roomJid nickname -> + sendPresence (createSelfPresence roomJid nickname) presenceHandlers + ) + let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } + Lifetime.Using(fun lt -> let roomLt = Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo Assert.True roomLt.IsAlive - ld.Terminate() + sendPresence (createLeavePresence roomInfo.RoomJid roomInfo.Nickname) presenceHandlers Assert.False roomLt.IsAlive - - [] - member __.``sendRoomMessage calls Send method on the client``(): unit = - let mutable message = Unchecked.defaultof - let client = XmppClientFactory.create(send = fun m -> message <- m) - let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } - Lifetime.Using(fun lt -> - Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo |> ignore - Assert.Equal(messageInfo.RecipientJid.FullJid, message.To.FullJid) - Assert.Equal(messageInfo.Text, message.Text) - ) - - [] - member __.``sendRoomMessage's result gets resolved after the message receival``(): unit = - let mutable messageHandler = ignore - let mutable message = Unchecked.defaultof - let client = - XmppClientFactory.create( - addMessageHandler = (fun _ h -> messageHandler <- h), - send = fun m -> message <- m - ) - let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } - Lifetime.Using(fun lt -> - let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo - Assert.Equal(message.ID, deliveryInfo.MessageId) - let deliveryTask = Async.StartAsTask deliveryInfo.Delivery - Assert.False deliveryTask.IsCompleted - messageHandler message - deliveryTask.Wait() - ) - - [] - member __.``sendRoomMessage's result doesn't get resolved after receiving other message``(): unit = - let mutable messageHandler = ignore - let client = XmppClientFactory.create(addMessageHandler = fun _ h -> messageHandler <- h) - let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } - Lifetime.Using(fun lt -> - let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo - let deliveryTask = Async.StartAsTask deliveryInfo.Delivery - Assert.False deliveryTask.IsCompleted - - let otherMessage = SharpXmppHelper.message (Some "xxx") "nickname@example.org" "foo bar" - messageHandler otherMessage - Assert.False deliveryTask.IsCompleted + ) + +[] +let ``Lifetime returned from enter terminates by an external lifetime termination``(): unit = + let mutable presenceHandlers = ResizeArray() + let client = + XmppClientFactory.create( + addPresenceHandler = (fun _ h -> presenceHandlers.Add h), + joinMultiUserChat = fun roomJid nickname -> + sendPresence (createSelfPresence roomJid nickname) presenceHandlers ) - - [] - member __.``sendRoomMessage's result gets resolved with an error if an error response is received``(): unit = - let mutable messageHandler = ignore - let client = - XmppClientFactory.create( - addMessageHandler = (fun _ h -> messageHandler <- h), - send = fun m -> messageHandler(createErrorMessage m "") - ) - let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } - Lifetime.Using(fun lt -> - let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo - let ae = Assert.Throws(fun () -> Async.RunSynchronously deliveryInfo.Delivery) - let ex = Seq.exactlyOne ae.InnerExceptions - Assert.Contains("", ex.Message) + let roomInfo = { RoomJid = JID("room@conference.example.org"); Nickname = "testuser" } + use ld = Lifetime.Define() + let lt = ld.Lifetime + let roomLt = Async.RunSynchronously <| XmppClient.enterRoom client lt roomInfo + Assert.True roomLt.IsAlive + ld.Terminate() + Assert.False roomLt.IsAlive + +[] +let ``sendRoomMessage calls Send method on the client``(): unit = + let mutable message = Unchecked.defaultof + let client = XmppClientFactory.create(send = fun m -> message <- m) + let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } + Lifetime.Using(fun lt -> + Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo |> ignore + Assert.Equal(messageInfo.RecipientJid.FullJid, message.To.FullJid) + Assert.Equal(messageInfo.Text, message.Text) + ) + +[] +let ``sendRoomMessage's result gets resolved after the message receival``(): unit = + let mutable messageHandler = ignore + let mutable message = Unchecked.defaultof + let client = + XmppClientFactory.create( + addMessageHandler = (fun _ h -> messageHandler <- h), + send = fun m -> message <- m ) - - [] - member __.``sendRoomMessage's result gets terminated after parent lifetime termination``(): unit = - let client = XmppClientFactory.create() - let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } - use ld = Lifetime.Define() - let lt = ld.Lifetime + let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } + Lifetime.Using(fun lt -> + let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo + Assert.Equal(message.ID, deliveryInfo.MessageId) + let deliveryTask = Async.StartAsTask deliveryInfo.Delivery + Assert.False deliveryTask.IsCompleted + messageHandler message + deliveryTask.Wait() + ) + +[] +let ``sendRoomMessage's result doesn't get resolved after receiving other message``(): unit = + let mutable messageHandler = ignore + let client = XmppClientFactory.create(addMessageHandler = fun _ h -> messageHandler <- h) + let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } + Lifetime.Using(fun lt -> let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo let deliveryTask = Async.StartAsTask deliveryInfo.Delivery Assert.False deliveryTask.IsCompleted - ld.Terminate() - Assert.Throws(fun () -> deliveryTask.GetAwaiter().GetResult()) |> ignore - - [] - member __.``awaitMessageDelivery just returns an async from the delivery info``(): unit = - let async = async { return () } - let deliveryInfo = { MessageId = ""; Delivery = async } - let result = XmppClient.awaitMessageDelivery deliveryInfo - Assert.True(Object.ReferenceEquals(async, result)) + + let otherMessage = SharpXmppHelper.message (Some "xxx") "nickname@example.org" "foo bar" + messageHandler otherMessage + Assert.False deliveryTask.IsCompleted + ) + +[] +let ``sendRoomMessage's result gets resolved with an error if an error response is received``(): unit = + let mutable messageHandler = ignore + let client = + XmppClientFactory.create( + addMessageHandler = (fun _ h -> messageHandler <- h), + send = fun m -> messageHandler(createErrorMessage m "") + ) + let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } + Lifetime.Using(fun lt -> + let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo + let ae = Assert.Throws(fun () -> Async.RunSynchronously deliveryInfo.Delivery) + let ex = Seq.exactlyOne ae.InnerExceptions + Assert.Contains("", ex.Message) + ) + +[] +let ``sendRoomMessage's result gets terminated after parent lifetime termination``(): unit = + let client = XmppClientFactory.create() + let messageInfo = { RecipientJid = JID("room@conference.example.org"); Text = "foo bar" } + use ld = Lifetime.Define() + let lt = ld.Lifetime + let deliveryInfo = Async.RunSynchronously <| XmppClient.sendRoomMessage client lt messageInfo + let deliveryTask = Async.StartAsTask deliveryInfo.Delivery + Assert.False deliveryTask.IsCompleted + ld.Terminate() + Assert.Throws(fun () -> deliveryTask.GetAwaiter().GetResult()) |> ignore + +[] +let ``awaitMessageDelivery just returns an async from the delivery info``(): unit = + let async = async { return () } + let deliveryInfo = { MessageId = ""; Delivery = async } + let result = XmppClient.awaitMessageDelivery deliveryInfo + Assert.True(Object.ReferenceEquals(async, result)) diff --git a/Emulsion/Xmpp/EmulsionXmpp.fs b/Emulsion/Xmpp/EmulsionXmpp.fs index 3388b7dd..c198d8cd 100644 --- a/Emulsion/Xmpp/EmulsionXmpp.fs +++ b/Emulsion/Xmpp/EmulsionXmpp.fs @@ -1,5 +1,4 @@ /// Main business logic for an XMPP part of the Emulsion application. -/// TODO[F]: Add tests for this module. module Emulsion.Xmpp.EmulsionXmpp open JetBrains.Lifetimes @@ -42,7 +41,8 @@ let run (settings: XmppSettings) (client: IXmppClient) (messageReceiver: IncomingMessageReceiver): Async = async { logger.Information "Connecting to the server" - let! sessionLifetime = XmppClient.connect logger client + let! sessionLifetime = XmppClient.connect client + sessionLifetime.ThrowIfNotAlive() logger.Information "Connection succeeded" logger.Information "Initializing client handler" diff --git a/Emulsion/Xmpp/SharpXmppHelper.fs b/Emulsion/Xmpp/SharpXmppHelper.fs index 00fd53ee..450fe82c 100644 --- a/Emulsion/Xmpp/SharpXmppHelper.fs +++ b/Emulsion/Xmpp/SharpXmppHelper.fs @@ -105,7 +105,7 @@ let parsePresence(presence: XMPPPresence): Presence = presence.Element X |> Option.ofObj |> Option.map (fun x -> - x.Elements(Status) + x.Elements Status |> Seq.choose (fun s -> getAttributeValue s Code) |> Seq.map int ) diff --git a/Emulsion/Xmpp/XmppClient.fs b/Emulsion/Xmpp/XmppClient.fs index 4d3b6587..65b547a1 100644 --- a/Emulsion/Xmpp/XmppClient.fs +++ b/Emulsion/Xmpp/XmppClient.fs @@ -26,9 +26,9 @@ type IXmppClient = /// Establish a connection to the server and log in. Returns a connection lifetime that will terminate if the connection /// terminates. -let connect (logger: ILogger) (client: IXmppClient): Async = async { +let connect (client: IXmppClient): Async = async { let connectionLifetime = new LifetimeDefinition() - client.AddConnectionFailedHandler connectionLifetime.Lifetime <| fun error -> + client.AddConnectionFailedHandler connectionLifetime.Lifetime <| fun _ -> connectionLifetime.Terminate() do! client.Connect()