Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/**
* Copyright (c) 2025 The Socketio4j Project
* Parent project : Copyright (c) 2012-2025 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.socketio4j.socketio;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

@DisplayName("Ack callback Tests")
class AckCallbackTest {

@Nested
@DisplayName("AckCallback Tests")
class AckCallbackTests {

@Test
@DisplayName("Should default to no timeout and expose the result class")
void shouldDefaultToNoTimeout() {
AckCallback<String> callback = new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
}
};

assertThat(callback.getTimeout()).isEqualTo(-1);
assertThat(callback.getResultClass()).isEqualTo(String.class);
}

@Test
@DisplayName("Should keep the configured timeout and ignore onTimeout by default")
void shouldKeepConfiguredTimeout() {
AckCallback<String> callback = new AckCallback<String>(String.class, 30) {
@Override
public void onSuccess(String result) {
}
};

callback.onTimeout();

assertThat(callback.getTimeout()).isEqualTo(30);
}
}

@Nested
@DisplayName("VoidAckCallback Tests")
class VoidAckCallbackTests {

@Test
@DisplayName("Should delegate onSuccess to the no argument variant")
void shouldDelegateOnSuccess() {
AtomicInteger invocations = new AtomicInteger();
VoidAckCallback callback = new VoidAckCallback() {
@Override
protected void onSuccess() {
invocations.incrementAndGet();
}
};

callback.onSuccess(null);

assertThat(invocations.get()).isEqualTo(1);
assertThat(callback.getResultClass()).isEqualTo(Void.class);
assertThat(callback.getTimeout()).isEqualTo(-1);
}

@Test
@DisplayName("Should keep the configured timeout")
void shouldKeepConfiguredTimeout() {
VoidAckCallback callback = new VoidAckCallback(15) {
@Override
protected void onSuccess() {
}
};

assertThat(callback.getTimeout()).isEqualTo(15);
}
}

@Nested
@DisplayName("MultiTypeAckCallback Tests")
class MultiTypeAckCallbackTests {

@Test
@DisplayName("Should expose the argument classes")
void shouldExposeResultClasses() {
MultiTypeAckCallback callback = new MultiTypeAckCallback(String.class, Integer.class) {
@Override
public void onSuccess(MultiTypeArgs result) {
}
};

assertThat(callback.getResultClasses()).containsExactly(String.class, Integer.class);
assertThat(callback.getResultClass()).isEqualTo(MultiTypeArgs.class);
}
}

@Nested
@DisplayName("MultiTypeArgs Tests")
class MultiTypeArgsTests {

@Test
@DisplayName("Should expose size, emptiness and the backing list")
void shouldExposeSizeAndArgs() {
List<Object> args = new ArrayList<>();
args.add("first");
args.add(2);
MultiTypeArgs multiTypeArgs = new MultiTypeArgs(args);

assertThat(multiTypeArgs.size()).isEqualTo(2);
assertThat(multiTypeArgs.isEmpty()).isFalse();
assertThat(multiTypeArgs.getArgs()).isSameAs(args);
assertThat(multiTypeArgs).containsExactly("first", 2);
}

@Test
@DisplayName("Should be empty for an empty argument list")
void shouldBeEmptyForEmptyList() {
MultiTypeArgs multiTypeArgs = new MultiTypeArgs(new ArrayList<>());

assertThat(multiTypeArgs.isEmpty()).isTrue();
assertThat(multiTypeArgs.size()).isZero();
}

@Test
@DisplayName("Should return null instead of throwing for out of bounds indexes")
void shouldReturnNullForOutOfBoundsIndex() {
List<Object> args = new ArrayList<>();
args.add("only");
MultiTypeArgs multiTypeArgs = new MultiTypeArgs(args);

assertThat(multiTypeArgs.<String>first()).isEqualTo("only");
assertThat(multiTypeArgs.<String>second()).isNull();
assertThat(multiTypeArgs.<String>get(10)).isNull();
}
}

@Nested
@DisplayName("BroadcastAckCallback Tests")
class BroadcastAckCallbackTests {

@Test
@DisplayName("Should notify all success once every client acknowledged after the loop finished")
void shouldNotifyAllSuccessAfterLoopFinished() {
List<SocketIOClient> successClients = new ArrayList<>();
AtomicInteger allSuccessInvocations = new AtomicInteger();
BroadcastAckCallback<String> callback = new BroadcastAckCallback<String>(String.class) {
@Override
protected void onClientSuccess(SocketIOClient client, String result) {
successClients.add(client);
}

@Override
protected void onAllSuccess() {
allSuccessInvocations.incrementAndGet();
}
};

SocketIOClient firstClient = mock(SocketIOClient.class);
SocketIOClient secondClient = mock(SocketIOClient.class);
AckCallback<String> first = callback.createClientCallback(firstClient);
AckCallback<String> second = callback.createClientCallback(secondClient);

first.onSuccess("one");
assertThat(allSuccessInvocations.get()).isZero();

callback.loopFinished();
assertThat(allSuccessInvocations.get()).isZero();

second.onSuccess("two");

assertThat(successClients).containsExactly(firstClient, secondClient);
assertThat(allSuccessInvocations.get()).isEqualTo(1);
}

@Test
@DisplayName("Should notify all success immediately when there is no client to wait for")
void shouldNotifyAllSuccessWithoutClients() {
AtomicInteger allSuccessInvocations = new AtomicInteger();
BroadcastAckCallback<String> callback = new BroadcastAckCallback<String>(String.class, 10) {
@Override
protected void onAllSuccess() {
allSuccessInvocations.incrementAndGet();
}
};

callback.loopFinished();
callback.loopFinished();

assertThat(allSuccessInvocations.get()).isEqualTo(1);
}

@Test
@DisplayName("Should propagate the timeout to the client callbacks")
void shouldPropagateTimeoutToClientCallbacks() {
List<SocketIOClient> timedOutClients = new ArrayList<>();
BroadcastAckCallback<String> callback = new BroadcastAckCallback<String>(String.class, 25) {
@Override
protected void onClientTimeout(SocketIOClient client) {
timedOutClients.add(client);
}
};

SocketIOClient client = mock(SocketIOClient.class);
AckCallback<String> clientCallback = callback.createClientCallback(client);
clientCallback.onTimeout();

assertThat(clientCallback.getTimeout()).isEqualTo(25);
assertThat(clientCallback.getResultClass()).isEqualTo(String.class);
assertThat(timedOutClients).containsExactly(client);
}

@Test
@DisplayName("Should not notify all success while a client ack is still pending")
void shouldNotNotifyAllSuccessWhileAckPending() {
AtomicInteger allSuccessInvocations = new AtomicInteger();
BroadcastAckCallback<String> callback = new BroadcastAckCallback<String>(String.class) {
@Override
protected void onAllSuccess() {
allSuccessInvocations.incrementAndGet();
}
};

callback.createClientCallback(mock(SocketIOClient.class));
callback.loopFinished();

assertThat(allSuccessInvocations.get()).isZero();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2025 The Socketio4j Project
* Parent project : Copyright (c) 2012-2025 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.socketio4j.socketio;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpHeaders;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("HandshakeData Tests")
class HandshakeDataTest {

private static final InetSocketAddress REMOTE = InetSocketAddress.createUnresolved("10.0.0.1", 5555);
private static final InetSocketAddress LOCAL = InetSocketAddress.createUnresolved("127.0.0.1", 8080);

private static HandshakeData handshakeData(Map<String, List<String>> urlParams) {
HttpHeaders headers = new DefaultHttpHeaders().add("Origin", "http://localhost");
return new HandshakeData(headers, urlParams, REMOTE, LOCAL, "/socket.io/?EIO=4", true);
}

@Test
@DisplayName("Should expose all handshake attributes")
void shouldExposeAllAttributes() {
Map<String, List<String>> urlParams = new HashMap<>();
urlParams.put("EIO", Collections.singletonList("4"));
HandshakeData data = handshakeData(urlParams);

assertThat(data.getAddress()).isEqualTo(REMOTE);
assertThat(data.getLocal()).isEqualTo(LOCAL);
assertThat(data.getUrl()).isEqualTo("/socket.io/?EIO=4");
assertThat(data.isXdomain()).isTrue();
assertThat(data.getUrlParams()).isEqualTo(urlParams);
assertThat(data.getHttpHeaders().get("Origin")).isEqualTo("http://localhost");
assertThat(data.getTime()).isNotNull();
}

@Test
@DisplayName("Should leave the local address unset when it is not provided")
void shouldLeaveLocalAddressUnset() {
HandshakeData data = new HandshakeData(new DefaultHttpHeaders(), Collections.emptyMap(),
REMOTE, "/socket.io/", false);

assertThat(data.getLocal()).isNull();
assertThat(data.isXdomain()).isFalse();
}

@Test
@DisplayName("Should return a single url param value only when it is unambiguous")
void shouldReturnSingleUrlParam() {
Map<String, List<String>> urlParams = new HashMap<>();
urlParams.put("single", Collections.singletonList("value"));
urlParams.put("multiple", Arrays.asList("first", "second"));
urlParams.put("empty", Collections.emptyList());
HandshakeData data = handshakeData(urlParams);

assertThat(data.getSingleUrlParam("single")).isEqualTo("value");
assertThat(data.getSingleUrlParam("multiple")).isNull();
assertThat(data.getSingleUrlParam("empty")).isNull();
assertThat(data.getSingleUrlParam("unknown")).isNull();
}

@Test
@DisplayName("Should store the auth token")
void shouldStoreAuthToken() {
HandshakeData data = handshakeData(Collections.emptyMap());

assertThat(data.getAuthToken()).isNull();
data.setAuthToken("token");

assertThat(data.getAuthToken()).isEqualTo("token");
}

@Test
@DisplayName("Should provide a no argument constructor for deserialization")
void shouldProvideNoArgConstructor() {
HandshakeData data = new HandshakeData();

assertThat(data.getTime()).isNotNull();
assertThat(data.getAddress()).isNull();
assertThat(data.getUrl()).isNull();
}
}
Loading
Loading