| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-03-11 03:42:29 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-03-11 03:42:29 UTC |
| parent | 11d4f99fd195a67f1d6056a92230b2645b378ff4 |
| borogove/AttachmentSource.eval.hx | +11 | -0 |
| borogove/Client.hx | +2 | -1 |
| borogove/EventEmitter.hx | +2 | -2 |
| borogove/GenericStream.hx | +1 | -0 |
| borogove/Stream.eval.hx | +5 | -0 |
| borogove/calls/PeerConnection.eval.hx | +55 | -0 |
| borogove/streams/TestStream.hx | +30 | -0 |
| test/TestAll.hx | +1 | -0 |
| test/TestClient.hx | +182 | -0 |
diff --git a/borogove/AttachmentSource.eval.hx b/borogove/AttachmentSource.eval.hx new file mode 100644 index 0000000..2be00ea --- /dev/null +++ b/borogove/AttachmentSource.eval.hx @@ -0,0 +1,11 @@ +package borogove; + +class AttachmentSource { + public final type: String = ""; + public final name: String = ""; + public final size: Int = 0; + + public function tinkSource() { + return tink.io.Source.ofError(null); + } +} diff --git a/borogove/Client.hx b/borogove/Client.hx index 1825bb7..dd3080c 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -62,6 +62,7 @@ class Client extends EventEmitter { **/ @:allow(borogove) public var sendAvailable(null, default): Bool = true; + @:allow(test) private var stream:GenericStream; @:allow(borogove) private var jid(default,null):JID; @@ -112,7 +113,7 @@ class Client extends EventEmitter { private final omemo: OMEMO; #end - @:allow(borogove) + @:allow(borogove, test) private var inSync(default, null) = false; /** diff --git a/borogove/EventEmitter.hx b/borogove/EventEmitter.hx index 271a6ce..fb2396c 100644 --- a/borogove/EventEmitter.hx +++ b/borogove/EventEmitter.hx @@ -27,7 +27,7 @@ class EventEmitter { private function new() { } - @:allow(borogove) + @:allow(borogove, test) private function on(eventName:String, callback:EventCallback):EventHandlerToken { var handlers = eventHandlers.get(eventName); if(handlers == null) { @@ -49,7 +49,7 @@ class EventEmitter { }); } - @:allow(borogove) + @:allow(borogove, test) private function trigger(eventName:String, eventData:Dynamic):EventResult { final handlers = eventHandlers.get(eventName); if (handlers == null) return EventUnhandled; diff --git a/borogove/GenericStream.hx b/borogove/GenericStream.hx index 4404049..5e2f2d9 100644 --- a/borogove/GenericStream.hx +++ b/borogove/GenericStream.hx @@ -39,6 +39,7 @@ abstract class GenericStream extends EventEmitter { sendStanza(stanza); } + @:allow(test) private function onStanza(stanza:Stanza):Void { trace("stanza received!"); final xmlns = stanza.attr.get("xmlns"); diff --git a/borogove/Stream.eval.hx b/borogove/Stream.eval.hx new file mode 100644 index 0000000..f69ba2e --- /dev/null +++ b/borogove/Stream.eval.hx @@ -0,0 +1,5 @@ +package borogove; + +import borogove.streams.TestStream; + +typedef Stream = borogove.streams.TestStream; diff --git a/borogove/calls/PeerConnection.eval.hx b/borogove/calls/PeerConnection.eval.hx new file mode 100644 index 0000000..ede8c83 --- /dev/null +++ b/borogove/calls/PeerConnection.eval.hx @@ -0,0 +1,55 @@ +package borogove.calls; + +import thenshim.Promise; + +typedef MediaStreamTrack = Dynamic; +typedef DTMFSender = Dynamic; + +typedef Transceiver = { + receiver: Null<{ track: MediaStreamTrack }>, + sender: Null<{ track: MediaStreamTrack, dtmf: DTMFSender }> +} + +enum abstract SdpType(Int) { + var UNSPEC; + var OFFER; + var ANSWER; + var PRANSWER; + var ROLLBACK; +} + +class MediaStream { + public function getTracks() { + return []; + } +} + +class PeerConnection { + public var localDescription: { sdp: Null<String> }; + public var connectionState: String; + + public function new(?configuration : Dynamic, ?constraints : Dynamic) { } + + + public function setLocalDescription(sdpType: Null<SdpType>): Promise<Any> { + return Promise.resolve(null); + } + + public function setRemoteDescription(description: Dynamic): Promise<Any> { + return Promise.resolve(null); + } + + public function addIceCandidate(candidate: { candidate: String, sdpMid: String, sppMLineIndex: Int, usernameFragment: String }): Promise<Any> { + return Promise.resolve(null); + } + + public function addTrack(track: MediaStreamTrack, stream: MediaStream) { } + + public function getTransceivers(): Array<Transceiver> { + return []; + } + + public function close() { } + + public function addEventListener(event: String, callback: Dynamic->Void) { } +} diff --git a/borogove/streams/TestStream.hx b/borogove/streams/TestStream.hx new file mode 100644 index 0000000..f6ee0d1 --- /dev/null +++ b/borogove/streams/TestStream.hx @@ -0,0 +1,30 @@ +package borogove.streams; + +import thenshim.Promise; +import haxe.io.BytesData; + +import borogove.ID; +import borogove.GenericStream; +import borogove.Stanza; + +class TestStream extends GenericStream { + public function register(domain: String, preAuth: Null<String>) { + return Promise.resolve(null); + } + + public function connect(jid:String, sm:Null<BytesData>) { + this.trigger("connect", { jid: jid, sm: sm }); + } + + public function disconnect() { } + + public function newId() { + return ID.long(); + } + + public function sendStanza(stanza:Stanza) { + this.trigger("sendStanza", stanza); + } + + public function onIq(type:IqRequestType, tag:String, xmlns:String, handler:(Stanza)->IqResult):Void { } +} diff --git a/test/TestAll.hx b/test/TestAll.hx index 03d93a5..5a8fe65 100644 --- a/test/TestAll.hx +++ b/test/TestAll.hx @@ -10,6 +10,7 @@ class TestAll { new TestChatMessageBuilder(), new TestStanza(), new TestCaps(), + new TestClient(), new TestXEP0393(), ]); } diff --git a/test/TestClient.hx b/test/TestClient.hx new file mode 100644 index 0000000..ec50532 --- /dev/null +++ b/test/TestClient.hx @@ -0,0 +1,182 @@ +package test; + +import utest.Assert; +import utest.Async; + +import borogove.Client; +import borogove.Stanza; +import borogove.persistence.Dummy; + +class TestClient extends utest.Test { + public function testAccountId() { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + Assert.equals("test@example.com", client.accountId()); + } + + public function testDefaultDisplayName() { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + Assert.equals("test", client.displayName()); + } + + public function testDefaultDisplayNameDomain() { + final persistence = new Dummy(); + final client = new Client("example.com", persistence); + Assert.equals("example.com", client.displayName()); + } + + public function testDisplayNameFromServer() { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + Assert.equals("test", client.displayName()); + client.stream.onStanza( + new Stanza("message", { xmlns: "jabber:client", from: "test@example.com" }) + .tag("event", { xmlns: "http://jabber.org/protocol/pubsub#event" }) + .tag("items", { node: "http://jabber.org/protocol/nick" }) + .tag("item") + .textTag("nick", "Test Name", { xmlns: "http://jabber.org/protocol/nick" }) + ); + Assert.equals("Test Name", client.displayName()); + } + + public function testStart(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + + // When we try to connect, just say we're online right away + client.stream.on("connect", (data) -> { + client.stream.trigger("status/online", { jid: data.jid }); + + return EventHandled; + }); + + // When we send an iq, reply with an error + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "iq") { + client.stream.onStanza(new Stanza("iq", { xmlns: "jabber:client", type: "error", id: stanza.attr.get("id") })); + } + + return EventHandled; + }); + + client.addStatusOnlineListener(() -> { + Assert.isTrue(client.inSync); + async.done(); + }); + + client.start(); + } + + public function testUsePassword(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + + // When we try to connect, we need a password + client.stream.on("connect", (data) -> { + client.stream.trigger("auth/password-needed", { mechanisms: [{ name: "SCRAM-SHA-1", canFast: false, canOther: true }] }); + + return EventHandled; + }); + + // When we get the right password, then we are online + client.stream.on("auth/password", (data) -> { + Assert.equals("password", data.password); + Assert.equals(null, data.requestToken); + client.stream.trigger("status/online", {}); + + return EventHandled; + }); + + // When we send an iq, reply with an error + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "iq") { + client.stream.onStanza(new Stanza("iq", { xmlns: "jabber:client", type: "error", id: stanza.attr.get("id") })); + } + + return EventHandled; + }); + + client.addStatusOnlineListener(() -> { + Assert.isTrue(client.inSync); + async.done(); + }); + + client.addPasswordNeededListener(account -> { + client.usePassword("password"); + }); + + client.start(); + } + + public function testUsePasswordRequestToken(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + + // When we try to connect, we need a password + client.stream.on("connect", (data) -> { + client.stream.trigger("auth/password-needed", { + mechanisms: [ + { name: "SCRAM-SHA-1", canFast: false, canOther: true }, + { name: "FASTMECH", canFast: true, canOther: false } + ] + }); + + return EventHandled; + }); + + // When we get the right password, then we are online + client.stream.on("auth/password", (data) -> { + Assert.equals("password", data.password); + Assert.equals("FASTMECH", data.requestToken); + client.stream.trigger("status/online", {}); + + return EventHandled; + }); + + // When we send an iq, reply with an error + client.stream.on("sendStanza", (stanza: Stanza) -> { + if (stanza.name == "iq") { + client.stream.onStanza(new Stanza("iq", { xmlns: "jabber:client", type: "error", id: stanza.attr.get("id") })); + } + + return EventHandled; + }); + + client.addStatusOnlineListener(() -> { + Assert.isTrue(client.inSync); + async.done(); + }); + + client.addPasswordNeededListener(account -> { + client.usePassword("password"); + }); + + client.start(); + } + + public function testNewMessageNewChat(async: Async) { + final persistence = new Dummy(); + final client = new Client("test@example.com", persistence); + + var gotMessage = false; + + client.addChatMessageListener((message, event) -> { + Assert.equals("localid", message.localId); + Assert.equals("hi", message.text); + Assert.equals(DeliveryEvent, event); + gotMessage = true; + }); + + client.addChatsUpdatedListener(chats -> { + Assert.equals(1, chats.length); + Assert.equals(1, client.getChats().length); + Assert.equals("test2@example.com", chats[0].chatId); + Assert.equals("localid", chats[0].lastMessage.localId); + Assert.isTrue(gotMessage); + async.done(); + }); + + client.stream.onStanza(new Stanza("message", { xmlns: "jabber:client", from: "test2@example.com", id: "localid"}).textTag("body", "hi")); + } +}