From 9d35d938d8c186494f9822b538e1c98c558dd808 Mon Sep 17 00:00:00 2001 From: JP Dillingham Date: Mon, 31 Aug 2026 09:49:52 -0500 Subject: [PATCH 1/2] read rejection detail, if present --- src/Messaging/Messages/Server/LoginResponse.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Messaging/Messages/Server/LoginResponse.cs b/src/Messaging/Messages/Server/LoginResponse.cs index 68ca33050..378a651b5 100644 --- a/src/Messaging/Messages/Server/LoginResponse.cs +++ b/src/Messaging/Messages/Server/LoginResponse.cs @@ -68,7 +68,7 @@ public LoginResponse(bool succeeded, string message, IPAddress ipAddress = null, public bool IsSupporter { get; } /// - /// Gets the reason for a login failure. + /// Gets the MOTD if is true, or the reason for a login failure if it is false. /// public string Message { get; } @@ -108,6 +108,11 @@ public static LoginResponse FromByteArray(byte[] bytes) hash = reader.ReadString(); isSupporter = reader.ReadByte() == 1; } + else if (reader.HasMoreData) + { + var detail = reader.ReadString(); + msg += string.IsNullOrWhiteSpace(detail) ? string.Empty : ": " + detail; + } return new LoginResponse(succeeded, msg, ipAddress, hash, isSupporter); } From 847f5ffb90fbe39e8da5fbbf65490ab162ee0698 Mon Sep 17 00:00:00 2001 From: JP Dillingham Date: Tue, 1 Sep 2026 09:00:48 -0500 Subject: [PATCH 2/2] add tests --- .../Messages/Server/LoginResponseTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Soulseek.Tests.Unit/Messaging/Messages/Server/LoginResponseTests.cs b/tests/Soulseek.Tests.Unit/Messaging/Messages/Server/LoginResponseTests.cs index ccb3bda4b..57679614c 100644 --- a/tests/Soulseek.Tests.Unit/Messaging/Messages/Server/LoginResponseTests.cs +++ b/tests/Soulseek.Tests.Unit/Messaging/Messages/Server/LoginResponseTests.cs @@ -93,6 +93,65 @@ public void Parse_Returns_Expected_Data_On_Failure() Assert.Equal(str, response.Message); } + [Trait("Category", "Parse")] + [Fact(DisplayName = "Parse appends rejection detail to message on failure with detail")] + public void Parse_Appends_Rejection_Detail_To_Message_On_Failure_With_Detail() + { + var detail = RandomGuid; + + var msg = new MessageBuilder() + .WriteCode(MessageCode.Server.Login) + .WriteByte(0) + .WriteString("INVALIDUSERNAME") + .WriteString(detail) + .Build(); + + var response = LoginResponse.FromByteArray(msg); + + Assert.False(response.Succeeded); + Assert.Equal($"INVALIDUSERNAME: {detail}", response.Message); + } + + [Trait("Category", "Parse")] + [Fact(DisplayName = "Parse does not throw on INVALIDUSERNAME failure without detail")] + public void Parse_Does_Not_Throw_On_Invalid_Username_Failure_Without_Detail() + { + var msg = new MessageBuilder() + .WriteCode(MessageCode.Server.Login) + .WriteByte(0) + .WriteString("INVALIDUSERNAME") + .Build(); + + LoginResponse response = null; + + var ex = Record.Exception(() => response = LoginResponse.FromByteArray(msg)); + + Assert.Null(ex); + + Assert.False(response.Succeeded); + Assert.Equal("INVALIDUSERNAME", response.Message); + } + + [Trait("Category", "Parse")] + [Theory(DisplayName = "Parse does not append rejection detail to message on failure with blank detail")] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + public void Parse_Does_Not_Append_Rejection_Detail_To_Message_On_Failure_With_Blank_Detail(string detail) + { + var msg = new MessageBuilder() + .WriteCode(MessageCode.Server.Login) + .WriteByte(0) + .WriteString("INVALIDUSERNAME") + .WriteString(detail) + .Build(); + + var response = LoginResponse.FromByteArray(msg); + + Assert.False(response.Succeeded); + Assert.Equal("INVALIDUSERNAME", response.Message); + } + [Trait("Category", "Parse")] [Fact(DisplayName = "Parse returns expected data on success")] public void Parse_Returns_Expected_Data_On_Success()