|
| 1 | +/* |
| 2 | + * Copyright (C) 2026 Velocity Contributors |
| 3 | + * |
| 4 | + * This program is free software: you can redistribute it and/or modify |
| 5 | + * it under the terms of the GNU General Public License as published by |
| 6 | + * the Free Software Foundation, either version 3 of the License, or |
| 7 | + * (at your option) any later version. |
| 8 | + * |
| 9 | + * This program is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * GNU General Public License for more details. |
| 13 | + * |
| 14 | + * You should have received a copy of the GNU General Public License |
| 15 | + * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 16 | + */ |
| 17 | + |
| 18 | +package com.velocitypowered.proxy.connection.client; |
| 19 | + |
| 20 | +import static org.mockito.ArgumentMatchers.any; |
| 21 | +import static org.mockito.Mockito.doAnswer; |
| 22 | +import static org.mockito.Mockito.mock; |
| 23 | +import static org.mockito.Mockito.never; |
| 24 | +import static org.mockito.Mockito.verify; |
| 25 | +import static org.mockito.Mockito.when; |
| 26 | + |
| 27 | +import com.velocitypowered.api.network.HandshakeIntent; |
| 28 | +import com.velocitypowered.api.network.ProtocolVersion; |
| 29 | +import com.velocitypowered.api.util.GameProfile; |
| 30 | +import com.velocitypowered.proxy.VelocityServer; |
| 31 | +import com.velocitypowered.proxy.config.PlayerInfoForwarding; |
| 32 | +import com.velocitypowered.proxy.config.VelocityConfiguration; |
| 33 | +import com.velocitypowered.proxy.connection.ConnectionTypes; |
| 34 | +import com.velocitypowered.proxy.connection.MinecraftConnection; |
| 35 | +import com.velocitypowered.proxy.event.MockEventManager; |
| 36 | +import com.velocitypowered.proxy.protocol.StateRegistry; |
| 37 | +import io.netty.channel.EventLoop; |
| 38 | +import java.lang.reflect.Field; |
| 39 | +import java.util.Optional; |
| 40 | +import java.util.UUID; |
| 41 | +import org.junit.jupiter.api.Test; |
| 42 | + |
| 43 | +class AuthSessionHandlerTest { |
| 44 | + |
| 45 | + @Test |
| 46 | + void duplicateLoginRejectedBeforeOwnershipDoesNotTeardown() { |
| 47 | + TestHarness harness = new TestHarness(); |
| 48 | + when(harness.server.canRegisterConnection(any())).thenReturn(false); |
| 49 | + |
| 50 | + harness.handler.activated(); |
| 51 | + harness.handler.disconnected(); |
| 52 | + |
| 53 | + verify(harness.server, never()).unregisterConnection(any()); |
| 54 | + verify(harness.inbound).cleanup(); |
| 55 | + } |
| 56 | + |
| 57 | + @Test |
| 58 | + void disconnectedWithOwnedPlayerStillTeardowns() throws ReflectiveOperationException { |
| 59 | + TestHarness harness = new TestHarness(); |
| 60 | + ConnectedPlayer player = mock(ConnectedPlayer.class); |
| 61 | + setConnectedPlayer(harness.handler, player); |
| 62 | + |
| 63 | + harness.handler.disconnected(); |
| 64 | + |
| 65 | + verify(player).teardown(); |
| 66 | + verify(harness.inbound).cleanup(); |
| 67 | + } |
| 68 | + |
| 69 | + private static void setConnectedPlayer(AuthSessionHandler handler, ConnectedPlayer player) |
| 70 | + throws ReflectiveOperationException { |
| 71 | + Field field = AuthSessionHandler.class.getDeclaredField("connectedPlayer"); |
| 72 | + field.setAccessible(true); |
| 73 | + field.set(handler, player); |
| 74 | + } |
| 75 | + |
| 76 | + private static EventLoop immediateEventLoop() { |
| 77 | + EventLoop eventLoop = mock(EventLoop.class); |
| 78 | + doAnswer(invocation -> { |
| 79 | + ((Runnable) invocation.getArgument(0)).run(); |
| 80 | + return null; |
| 81 | + }).when(eventLoop).execute(any(Runnable.class)); |
| 82 | + return eventLoop; |
| 83 | + } |
| 84 | + |
| 85 | + private static final class TestHarness { |
| 86 | + |
| 87 | + private final VelocityServer server = mock(VelocityServer.class); |
| 88 | + private final VelocityConfiguration configuration = mock(VelocityConfiguration.class); |
| 89 | + private final MockEventManager eventManager = new MockEventManager(); |
| 90 | + private final LoginInboundConnection inbound = mock(LoginInboundConnection.class); |
| 91 | + private final MinecraftConnection connection = mock(MinecraftConnection.class); |
| 92 | + private final AuthSessionHandler handler; |
| 93 | + |
| 94 | + private TestHarness() { |
| 95 | + EventLoop eventLoop = immediateEventLoop(); |
| 96 | + when(server.getConfiguration()).thenReturn(configuration); |
| 97 | + when(server.getEventManager()).thenReturn(eventManager); |
| 98 | + when(server.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); |
| 99 | + when(configuration.getPlayerInfoForwardingMode()).thenReturn(PlayerInfoForwarding.NONE); |
| 100 | + when(configuration.isLogPlayerConnections()).thenReturn(false); |
| 101 | + |
| 102 | + when(inbound.delegatedConnection()).thenReturn(connection); |
| 103 | + when(inbound.getVirtualHost()).thenReturn(Optional.empty()); |
| 104 | + when(inbound.getRawVirtualHost()).thenReturn(Optional.empty()); |
| 105 | + when(inbound.getHandshakeIntent()).thenReturn(HandshakeIntent.LOGIN); |
| 106 | + when(inbound.getIdentifiedKey()).thenReturn(null); |
| 107 | + |
| 108 | + when(connection.getType()).thenReturn(ConnectionTypes.VANILLA); |
| 109 | + when(connection.getProtocolVersion()).thenReturn(ProtocolVersion.MINECRAFT_1_20_2); |
| 110 | + when(connection.eventLoop()).thenReturn(eventLoop); |
| 111 | + when(connection.isClosed()).thenReturn(false); |
| 112 | + when(connection.getState()).thenReturn(StateRegistry.LOGIN); |
| 113 | + |
| 114 | + handler = new AuthSessionHandler( |
| 115 | + server, |
| 116 | + inbound, |
| 117 | + GameProfile.forOfflinePlayer("test-player"), |
| 118 | + false, |
| 119 | + null); |
| 120 | + } |
| 121 | + } |
| 122 | +} |
0 commit comments