From 8d0baf1db5e9367e1c5fcea1104062fa580aea4b Mon Sep 17 00:00:00 2001 From: isKonstantin Date: Thu, 18 Jul 2024 08:35:43 +0300 Subject: [PATCH] Added web socket support --- build.gradle | 4 +- .../java/app/finwave/api/FinWaveClient.java | 32 +++- .../api/websocket/FinWaveWebSocketClient.java | 79 ++++++++++ .../api/websocket/messages/MessageBody.java | 4 + .../websocket/messages/RequestMessage.java | 16 ++ .../websocket/messages/ResponseMessage.java | 19 +++ .../handler/AbstractWebSocketHandler.java | 39 +++++ .../handler/QueueWebSocketHandler.java | 54 +++++++ .../handler/RoutedWebSocketHandler.java | 41 +++++ .../messages/requests/AuthMessageRequest.java | 18 +++ .../requests/NewNotificationPointRequest.java | 20 +++ .../SubscribeNotificationsRequest.java | 20 +++ .../messages/response/GenericMessageBody.java | 13 ++ .../messages/response/NotifyUpdateBody.java | 12 ++ .../response/auth/AuthStatusBody.java | 12 ++ .../response/notifications/Notification.java | 69 +++++++++ .../notifications/NotificationOptions.java | 3 + .../NotificationPointRegisteredBody.java | 16 ++ .../NotificationSubscribeBody.java | 13 ++ .../java/app/finwave/api/WebSocketTest.java | 146 ++++++++++++++++++ 20 files changed, 628 insertions(+), 2 deletions(-) create mode 100644 src/main/java/app/finwave/api/websocket/FinWaveWebSocketClient.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/MessageBody.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/RequestMessage.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/ResponseMessage.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/handler/AbstractWebSocketHandler.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/handler/QueueWebSocketHandler.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/handler/RoutedWebSocketHandler.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/requests/AuthMessageRequest.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/requests/NewNotificationPointRequest.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/requests/SubscribeNotificationsRequest.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/GenericMessageBody.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/NotifyUpdateBody.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/auth/AuthStatusBody.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/notifications/Notification.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationOptions.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationPointRegisteredBody.java create mode 100644 src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationSubscribeBody.java create mode 100644 src/test/java/app/finwave/api/WebSocketTest.java diff --git a/build.gradle b/build.gradle index c834376..dd9dbba 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = "app.finwave.api" -version = "1.0.4" +version = "1.2.0" repositories { mavenCentral() @@ -13,6 +13,8 @@ repositories { dependencies { implementation 'com.google.code.gson:gson:2.11.0' + implementation 'org.java-websocket:Java-WebSocket:1.5.2' + testImplementation platform("org.junit:junit-bom:5.9.1") testImplementation "org.junit.jupiter:junit-jupiter" } diff --git a/src/main/java/app/finwave/api/FinWaveClient.java b/src/main/java/app/finwave/api/FinWaveClient.java index 2f63e5e..26a4492 100644 --- a/src/main/java/app/finwave/api/FinWaveClient.java +++ b/src/main/java/app/finwave/api/FinWaveClient.java @@ -1,12 +1,17 @@ package app.finwave.api; import app.finwave.api.tools.*; +import app.finwave.api.websocket.FinWaveWebSocketClient; +import app.finwave.api.websocket.messages.handler.AbstractWebSocketHandler; +import app.finwave.api.websocket.messages.requests.AuthMessageRequest; import com.google.gson.JsonElement; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; @@ -14,7 +19,7 @@ import java.util.concurrent.Executors; public class FinWaveClient { - protected static ExecutorService threadPool = Executors.newFixedThreadPool(4, + protected static ExecutorService threadPool = Executors.newCachedThreadPool( r -> { Thread thread = new Thread(r); thread.setDaemon(true); @@ -51,6 +56,31 @@ public void setToken(String token) { this.token = token; } + public FinWaveWebSocketClient connectToWebsocket(String url, String path, AbstractWebSocketHandler handler) throws URISyntaxException, InterruptedException { + FinWaveWebSocketClient client = new FinWaveWebSocketClient(new URI(url + path)); + client.setHandler(handler); + + if (!client.connectBlocking()) + return null; + + if (token != null && !token.isBlank()) + client.send(new AuthMessageRequest(token)); + + return client; + } + + public FinWaveWebSocketClient connectToWebsocket(String path, AbstractWebSocketHandler handler) throws URISyntaxException, InterruptedException { + return connectToWebsocket( + baseURL.replaceFirst("http://", "ws://") + .replaceFirst("https://", "wss://"), + path, handler + ); + } + + public FinWaveWebSocketClient connectToWebsocket(AbstractWebSocketHandler handler) throws URISyntaxException, InterruptedException { + return connectToWebsocket("websockets/events", handler); + } + public > CompletableFuture runRequest(T request) { CompletableFuture future = new CompletableFuture<>(); diff --git a/src/main/java/app/finwave/api/websocket/FinWaveWebSocketClient.java b/src/main/java/app/finwave/api/websocket/FinWaveWebSocketClient.java new file mode 100644 index 0000000..9dc6a55 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/FinWaveWebSocketClient.java @@ -0,0 +1,79 @@ +package app.finwave.api.websocket; + +import app.finwave.api.websocket.messages.handler.AbstractWebSocketHandler; +import app.finwave.api.websocket.messages.RequestMessage; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static app.finwave.api.tools.Misc.GSON; + +public class FinWaveWebSocketClient extends WebSocketClient { + protected AbstractWebSocketHandler handler; + + protected long lastPing = 0; + + protected static ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor( + r -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + + return thread; + }); + + public FinWaveWebSocketClient(URI uri) { + super(uri); + + scheduledExecutorService.scheduleAtFixedRate(() -> { + if (isOpen() && System.currentTimeMillis() - lastPing > 15000) { + send("ping"); + } + }, 5, 5, TimeUnit.SECONDS); + } + + public void setHandler(AbstractWebSocketHandler handler) { + handler.init(this); + + this.handler = handler; + } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + if (handler != null) + handler.opened(serverHandshake); + } + + @Override + public void onMessage(String rawMessage) { + if (rawMessage.equals("pong")) { + lastPing = System.currentTimeMillis(); + + return; + } + + if (handler != null) + handler.onMessage(rawMessage); + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (handler != null) + handler.closed(code, reason, remote); + } + + @Override + public void onError(Exception e) { + if (handler != null) + handler.onError(e); + } + + public void send(RequestMessage message) { + lastPing = System.currentTimeMillis(); + + send(GSON.toJson(message)); + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/MessageBody.java b/src/main/java/app/finwave/api/websocket/messages/MessageBody.java new file mode 100644 index 0000000..0bce958 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/MessageBody.java @@ -0,0 +1,4 @@ +package app.finwave.api.websocket.messages; + +public class MessageBody { +} diff --git a/src/main/java/app/finwave/api/websocket/messages/RequestMessage.java b/src/main/java/app/finwave/api/websocket/messages/RequestMessage.java new file mode 100644 index 0000000..7443b46 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/RequestMessage.java @@ -0,0 +1,16 @@ +package app.finwave.api.websocket.messages; + +import app.finwave.api.tools.Misc; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class RequestMessage { + public final String type; + protected final T body; + + public RequestMessage(String type, T body) { + this.type = type; + this.body = body; + } + +} diff --git a/src/main/java/app/finwave/api/websocket/messages/ResponseMessage.java b/src/main/java/app/finwave/api/websocket/messages/ResponseMessage.java new file mode 100644 index 0000000..238e627 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/ResponseMessage.java @@ -0,0 +1,19 @@ +package app.finwave.api.websocket.messages; + +import app.finwave.api.tools.Misc; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class ResponseMessage { + public final String type; + protected final JsonElement body; + + public ResponseMessage(String type, JsonObject body) { + this.type = type; + this.body = body; + } + + public T getBody(Class clazz) { + return Misc.GSON.fromJson(body, clazz); + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/handler/AbstractWebSocketHandler.java b/src/main/java/app/finwave/api/websocket/messages/handler/AbstractWebSocketHandler.java new file mode 100644 index 0000000..70c2322 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/handler/AbstractWebSocketHandler.java @@ -0,0 +1,39 @@ +package app.finwave.api.websocket.messages.handler; + +import app.finwave.api.websocket.FinWaveWebSocketClient; +import app.finwave.api.websocket.messages.ResponseMessage; +import com.google.gson.JsonSyntaxException; +import org.java_websocket.handshake.ServerHandshake; + +import static app.finwave.api.tools.Misc.GSON; + +public abstract class AbstractWebSocketHandler { + protected FinWaveWebSocketClient client; + + public void init(FinWaveWebSocketClient client) { + this.client = client; + } + + public void onMessage(String rawMessage) { + ResponseMessage message; + + try { + message = GSON.fromJson(rawMessage, ResponseMessage.class); + } catch (JsonSyntaxException e) { + unparsedMessage(rawMessage); + + return; + } + + onMessage(message); + } + + public abstract void onMessage(ResponseMessage message); + + public void unparsedMessage(String rawMessage) {} + + public abstract void opened(ServerHandshake serverHandshake); + public abstract void onError(Exception exception); + public abstract void closed(int code, String reason, boolean remote); + +} diff --git a/src/main/java/app/finwave/api/websocket/messages/handler/QueueWebSocketHandler.java b/src/main/java/app/finwave/api/websocket/messages/handler/QueueWebSocketHandler.java new file mode 100644 index 0000000..62695c3 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/handler/QueueWebSocketHandler.java @@ -0,0 +1,54 @@ +package app.finwave.api.websocket.messages.handler; + +import app.finwave.api.websocket.messages.ResponseMessage; +import org.java_websocket.handshake.ServerHandshake; + +import java.util.ArrayDeque; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; + +public abstract class QueueWebSocketHandler extends AbstractWebSocketHandler { + protected ArrayDeque> deque = new ArrayDeque<>(); + protected ReentrantLock lock = new ReentrantLock(); + + @Override + public void onMessage(ResponseMessage message) { + lock.lock(); + + try { + CompletableFuture future = deque.peek(); + + if (future == null || future.isDone()) { + deque.add(CompletableFuture.completedFuture(message)); + + return; + } + + deque.remove(future); + future.complete(message); + }finally { + lock.unlock(); + } + } + + public CompletableFuture messageExpected() { + lock.lock(); + + try { + CompletableFuture future = deque.peek(); + + if (future != null && future.isDone()) + deque.remove(future); + + if (future != null) + return future; + + future = new CompletableFuture<>(); + deque.add(future); + + return future; + }finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/handler/RoutedWebSocketHandler.java b/src/main/java/app/finwave/api/websocket/messages/handler/RoutedWebSocketHandler.java new file mode 100644 index 0000000..ef73171 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/handler/RoutedWebSocketHandler.java @@ -0,0 +1,41 @@ +package app.finwave.api.websocket.messages.handler; + +import app.finwave.api.websocket.messages.ResponseMessage; +import app.finwave.api.websocket.messages.response.GenericMessageBody; +import app.finwave.api.websocket.messages.response.NotifyUpdateBody; +import app.finwave.api.websocket.messages.response.auth.AuthStatusBody; +import app.finwave.api.websocket.messages.response.notifications.Notification; +import app.finwave.api.websocket.messages.response.notifications.NotificationPointRegisteredBody; +import app.finwave.api.websocket.messages.response.notifications.NotificationSubscribeBody; + +import java.util.UUID; + +public abstract class RoutedWebSocketHandler extends AbstractWebSocketHandler { + @Override + public void onMessage(ResponseMessage message) { + switch (message.type) { + case "generic" -> { + GenericMessageBody body = message.getBody(GenericMessageBody.class); + genericMessage(body.message, body.code); + } + case "update" -> notifyUpdate(message.getBody(NotifyUpdateBody.class).updated); + case "auth" -> authStatus(message.getBody(AuthStatusBody.class).status); + case "notification" -> notification(message.getBody(Notification.class)); + case "newNotificationRegistered" -> { + NotificationPointRegisteredBody body = message.getBody(NotificationPointRegisteredBody.class); + notificationPointRegistered(body.id, body.uuid); + } + case "subscribeNotification" -> notificationPointSubscribe(message.getBody(NotificationSubscribeBody.class).status); + default -> unknownMessage(message); + } + } + + public void unknownMessage(ResponseMessage message) {} + + public abstract void notifyUpdate(String updated); + public abstract void genericMessage(String message, int code); + public abstract void notification(Notification notification); + public abstract void notificationPointRegistered(long pointId, UUID uuid); + public abstract void notificationPointSubscribe(String status); + public abstract void authStatus(String status); +} diff --git a/src/main/java/app/finwave/api/websocket/messages/requests/AuthMessageRequest.java b/src/main/java/app/finwave/api/websocket/messages/requests/AuthMessageRequest.java new file mode 100644 index 0000000..69a4c5f --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/requests/AuthMessageRequest.java @@ -0,0 +1,18 @@ +package app.finwave.api.websocket.messages.requests; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.RequestMessage; + +public class AuthMessageRequest extends RequestMessage { + public AuthMessageRequest(String token) { + super("auth", new AuthMessageBody(token)); + } + + protected static class AuthMessageBody extends MessageBody { + public final String token; + + public AuthMessageBody(String token) { + this.token = token; + } + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/requests/NewNotificationPointRequest.java b/src/main/java/app/finwave/api/websocket/messages/requests/NewNotificationPointRequest.java new file mode 100644 index 0000000..ae0d462 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/requests/NewNotificationPointRequest.java @@ -0,0 +1,20 @@ +package app.finwave.api.websocket.messages.requests; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.RequestMessage; + +public class NewNotificationPointRequest extends RequestMessage { + public NewNotificationPointRequest(String description, boolean isPrimary) { + super("newNotification", new NewNotificationPointBody(description, isPrimary)); + } + + protected static class NewNotificationPointBody extends MessageBody { + public final String description; + public final boolean isPrimary; + + public NewNotificationPointBody(String description, boolean isPrimary) { + this.description = description; + this.isPrimary = isPrimary; + } + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/requests/SubscribeNotificationsRequest.java b/src/main/java/app/finwave/api/websocket/messages/requests/SubscribeNotificationsRequest.java new file mode 100644 index 0000000..f339683 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/requests/SubscribeNotificationsRequest.java @@ -0,0 +1,20 @@ +package app.finwave.api.websocket.messages.requests; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.RequestMessage; + +import java.util.UUID; + +public class SubscribeNotificationsRequest extends RequestMessage { + public SubscribeNotificationsRequest(UUID pointUUID) { + super("subscribeNotification", new SubscribeNotificationsBody(pointUUID)); + } + + protected static class SubscribeNotificationsBody extends MessageBody { + public final UUID pointUUID; + + public SubscribeNotificationsBody(UUID pointUUID) { + this.pointUUID = pointUUID; + } + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/GenericMessageBody.java b/src/main/java/app/finwave/api/websocket/messages/response/GenericMessageBody.java new file mode 100644 index 0000000..9d9a103 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/GenericMessageBody.java @@ -0,0 +1,13 @@ +package app.finwave.api.websocket.messages.response; + +import app.finwave.api.websocket.messages.MessageBody; + +public class GenericMessageBody extends MessageBody { // generic + public final String message; + public final int code; + + public GenericMessageBody(String message, int code) { + this.message = message; + this.code = code; + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/NotifyUpdateBody.java b/src/main/java/app/finwave/api/websocket/messages/response/NotifyUpdateBody.java new file mode 100644 index 0000000..f82df88 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/NotifyUpdateBody.java @@ -0,0 +1,12 @@ +package app.finwave.api.websocket.messages.response; + +import app.finwave.api.websocket.messages.MessageBody; + +public class NotifyUpdateBody extends MessageBody { // update + public final String updated; + + public NotifyUpdateBody(String updated) { + this.updated = updated; + } + +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/auth/AuthStatusBody.java b/src/main/java/app/finwave/api/websocket/messages/response/auth/AuthStatusBody.java new file mode 100644 index 0000000..e1c59db --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/auth/AuthStatusBody.java @@ -0,0 +1,12 @@ +package app.finwave.api.websocket.messages.response.auth; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.ResponseMessage; + +public class AuthStatusBody extends MessageBody { // auth + public final String status; + + public AuthStatusBody(String status) { + this.status = status; + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/notifications/Notification.java b/src/main/java/app/finwave/api/websocket/messages/response/notifications/Notification.java new file mode 100644 index 0000000..db6b691 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/notifications/Notification.java @@ -0,0 +1,69 @@ +package app.finwave.api.websocket.messages.response.notifications; + +import app.finwave.api.websocket.messages.MessageBody; + +import java.time.OffsetDateTime; +import java.util.Objects; + +public final class Notification extends MessageBody { + private final long id; + private final String text; + private final NotificationOptions options; + private final int userId; + private final OffsetDateTime createdAt; + + public Notification(long id, String text, NotificationOptions options, int userId, OffsetDateTime createdAt) { + this.id = id; + this.text = text; + this.options = options; + this.userId = userId; + this.createdAt = createdAt; + } + + public long id() { + return id; + } + + public String text() { + return text; + } + + public NotificationOptions options() { + return options; + } + + public int userId() { + return userId; + } + + public OffsetDateTime createdAt() { + return createdAt; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (Notification) obj; + return this.id == that.id && + Objects.equals(this.text, that.text) && + Objects.equals(this.options, that.options) && + this.userId == that.userId && + Objects.equals(this.createdAt, that.createdAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, text, options, userId, createdAt); + } + + @Override + public String toString() { + return "Notification[" + + "id=" + id + ", " + + "text=" + text + ", " + + "options=" + options + ", " + + "userId=" + userId + ", " + + "createdAt=" + createdAt + ']'; + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationOptions.java b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationOptions.java new file mode 100644 index 0000000..dc0ac34 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationOptions.java @@ -0,0 +1,3 @@ +package app.finwave.api.websocket.messages.response.notifications; + +public record NotificationOptions(boolean silent, long pointId, Object args) { } diff --git a/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationPointRegisteredBody.java b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationPointRegisteredBody.java new file mode 100644 index 0000000..3f62548 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationPointRegisteredBody.java @@ -0,0 +1,16 @@ +package app.finwave.api.websocket.messages.response.notifications; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.ResponseMessage; + +import java.util.UUID; + +public class NotificationPointRegisteredBody extends MessageBody { // newNotificationRegistered + public final long id; + public final UUID uuid; + + public NotificationPointRegisteredBody(long id, UUID uuid) { + this.id = id; + this.uuid = uuid; + } +} diff --git a/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationSubscribeBody.java b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationSubscribeBody.java new file mode 100644 index 0000000..bf0c729 --- /dev/null +++ b/src/main/java/app/finwave/api/websocket/messages/response/notifications/NotificationSubscribeBody.java @@ -0,0 +1,13 @@ +package app.finwave.api.websocket.messages.response.notifications; + +import app.finwave.api.websocket.messages.MessageBody; +import app.finwave.api.websocket.messages.ResponseMessage; + +public class NotificationSubscribeBody extends MessageBody { // subscribeNotification + public final String status; + + public NotificationSubscribeBody(String status) { + this.status = status; + } + +} diff --git a/src/test/java/app/finwave/api/WebSocketTest.java b/src/test/java/app/finwave/api/WebSocketTest.java new file mode 100644 index 0000000..04a6c7c --- /dev/null +++ b/src/test/java/app/finwave/api/WebSocketTest.java @@ -0,0 +1,146 @@ +package app.finwave.api; + +import app.finwave.api.websocket.FinWaveWebSocketClient; +import app.finwave.api.websocket.messages.handler.AbstractWebSocketHandler; +import app.finwave.api.websocket.messages.ResponseMessage; +import app.finwave.api.websocket.messages.handler.QueueWebSocketHandler; +import app.finwave.api.websocket.messages.requests.NewNotificationPointRequest; +import app.finwave.api.websocket.messages.requests.SubscribeNotificationsRequest; +import app.finwave.api.websocket.messages.response.NotifyUpdateBody; +import app.finwave.api.websocket.messages.response.auth.AuthStatusBody; +import app.finwave.api.websocket.messages.response.notifications.Notification; +import app.finwave.api.websocket.messages.response.notifications.NotificationPointRegisteredBody; +import app.finwave.api.websocket.messages.response.notifications.NotificationSubscribeBody; +import org.java_websocket.handshake.ServerHandshake; +import org.junit.jupiter.api.*; + +import java.net.URISyntaxException; +import java.util.ArrayDeque; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.locks.ReentrantLock; + +import static org.junit.jupiter.api.Assertions.*; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class WebSocketTest { + private FinWaveClient client; + private FinWaveWebSocketClient webSocketClient; + private QueueWebSocketHandler handler; + + private NotificationPointRegisteredBody registeredPoint; + + @BeforeAll + void setUp() throws ExecutionException, InterruptedException { + client = DemoLogin.createDemoAndLogin(); + + handler = new QueueWebSocketHandler() { + @Override + public void opened(ServerHandshake serverHandshake) { + + } + + @Override + public void onError(Exception exception) { + + } + + @Override + public void closed(int code, String reason, boolean remote) { + + } + }; + } + + @Test + @Order(1) + void connect() throws ExecutionException, InterruptedException, URISyntaxException { + webSocketClient = client.connectToWebsocket(handler); + + ResponseMessage message = handler.messageExpected().get(); + + assertNotNull(message); + assertEquals("auth", message.type); + + AuthStatusBody body = message.getBody(AuthStatusBody.class); + assertEquals("Successful", body.status); + } + + @Test + @Order(2) + void update() throws ExecutionException, InterruptedException { + + var tag = client.runRequest(new AccountTagApi.NewTagRequest("test", "test")).get(); + assertNotNull(tag); + + var account = client.runRequest(new AccountApi.NewAccountRequest(tag.tagId(), 1, "Test account", null)).get(); + + assertNotNull(account); + assertTrue(account.accountId() > 0); + + + ResponseMessage message = handler.messageExpected().get(); + + assertNotNull(message); + assertEquals("update", message.type); + + NotifyUpdateBody body = message.getBody(NotifyUpdateBody.class); + assertEquals("accountTags", body.updated); + + + message = handler.messageExpected().get(); + + assertNotNull(message); + assertEquals("update", message.type); + + body = message.getBody(NotifyUpdateBody.class); + assertEquals("accounts", body.updated); + } + + @Test + @Order(3) + void newNotificationPoint() throws ExecutionException, InterruptedException { + webSocketClient.send(new NewNotificationPointRequest("WebSocket Test", true)); + + ResponseMessage message = handler.messageExpected().get(); + + assertNotNull(message); + assertEquals("newNotificationRegistered", message.type); + + NotificationPointRegisteredBody body = message.getBody(NotificationPointRegisteredBody.class); + assertNotNull(body.uuid); + assertTrue(body.id > 0); + + registeredPoint = body; + } + + @Test + @Order(4) + void subscribePoint() throws ExecutionException, InterruptedException { + webSocketClient.send(new SubscribeNotificationsRequest(registeredPoint.uuid)); + + ResponseMessage message = handler.messageExpected().get(); + + assertNotNull(message); + assertEquals("subscribeNotification", message.type); + + NotificationSubscribeBody body = message.getBody(NotificationSubscribeBody.class); + assertEquals("Subscribed", body.status); + } + + @Test + @Order(5) + void notificationTest() throws ExecutionException, InterruptedException { + client.runRequest(new NotificationApi.PushNotificationRequest(registeredPoint.id, "Test notification", false)); + + ResponseMessage message = handler.messageExpected().get(); + assertEquals("notification", message.type); + + Notification notification = message.getBody(Notification.class); + + assertEquals("Test notification", notification.text()); + assertFalse(notification.options().silent()); + } +} \ No newline at end of file