git » sdk » commit 72cf2ac

Add initial tests for Client

author Stephen Paul Weber
2026-03-11 03:42:29 UTC
committer Stephen Paul Weber
2026-03-11 03:42:29 UTC
parent 11d4f99fd195a67f1d6056a92230b2645b378ff4

Add initial tests for Client

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"));
+	}
+}