Make some inventory description properties public
[ArchiSteamFarm.git] / ArchiSteamFarm.Tests / SteamChatMessage.cs
blob1fe082a8f397f14a6d915820c6ebdfa5a7dcfa1f
1 // ----------------------------------------------------------------------------------------------
2 // _ _ _ ____ _ _____
3 // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
7 // ----------------------------------------------------------------------------------------------
8 // |
9 // Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
10 // Contact: JustArchi@JustArchi.net
11 // |
12 // Licensed under the Apache License, Version 2.0 (the "License");
13 // you may not use this file except in compliance with the License.
14 // You may obtain a copy of the License at
15 // |
16 // http://www.apache.org/licenses/LICENSE-2.0
17 // |
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the License is distributed on an "AS IS" BASIS,
20 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 // See the License for the specific language governing permissions and
22 // limitations under the License.
24 using System;
25 using System.Collections.Generic;
26 using System.Linq;
27 using System.Text;
28 using System.Threading.Tasks;
29 using Microsoft.VisualStudio.TestTools.UnitTesting;
30 using static ArchiSteamFarm.Steam.Integration.SteamChatMessage;
32 namespace ArchiSteamFarm.Tests;
34 [TestClass]
35 public sealed class SteamChatMessage {
36 [TestMethod]
37 public async Task CanSplitEvenWithStupidlyLongPrefix() {
38 string prefix = new('x', MaxMessagePrefixBytes);
40 const string emoji = "😎";
41 const string message = $"{emoji}{emoji}{emoji}{emoji}";
43 List<string> output = await GetMessageParts(message, prefix, true).ToListAsync().ConfigureAwait(false);
45 Assert.AreEqual(4, output.Count);
47 Assert.AreEqual($"{prefix}{emoji}{ContinuationCharacter}", output[0]);
48 Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}{ContinuationCharacter}", output[1]);
49 Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}{ContinuationCharacter}", output[2]);
50 Assert.AreEqual($"{prefix}{ContinuationCharacter}{emoji}", output[3]);
53 [TestMethod]
54 public void ContinuationCharacterSizeIsProperlyCalculated() => Assert.AreEqual(ContinuationCharacterBytes, Encoding.UTF8.GetByteCount(ContinuationCharacter.ToString()));
56 [TestMethod]
57 public async Task DoesntSkipEmptyNewlines() {
58 string message = $"asdf{Environment.NewLine}{Environment.NewLine}asdf";
60 List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
62 Assert.AreEqual(1, output.Count);
63 Assert.AreEqual(message, output.First());
66 [DataRow(false)]
67 [DataRow(true)]
68 [DataTestMethod]
69 public async Task DoesntSplitInTheMiddleOfMultiByteChar(bool isAccountLimited) {
70 int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
71 int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
73 const string emoji = "😎";
75 string longSequence = new('a', longLineLength - 1);
76 string message = $"{longSequence}{emoji}";
78 List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
80 Assert.AreEqual(2, output.Count);
82 Assert.AreEqual($"{longSequence}{ContinuationCharacter}", output[0]);
83 Assert.AreEqual($"{ContinuationCharacter}{emoji}", output[1]);
86 [TestMethod]
87 public async Task DoesntSplitJustBecauseOfLastEscapableCharacter() {
88 const string message = "abcdef[";
89 const string escapedMessage = @"abcdef\[";
91 List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
93 Assert.AreEqual(1, output.Count);
94 Assert.AreEqual(escapedMessage, output.First());
97 [DataRow(false)]
98 [DataRow(true)]
99 [DataTestMethod]
100 public async Task DoesntSplitOnBackslashNotUsedForEscaping(bool isAccountLimited) {
101 int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
102 int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
104 string longLine = new('a', longLineLength - 2);
105 string message = $@"{longLine}\";
107 List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
109 Assert.AreEqual(1, output.Count);
110 Assert.AreEqual($@"{message}\", output.First());
113 [DataRow(false)]
114 [DataRow(true)]
115 [DataTestMethod]
116 public async Task DoesntSplitOnEscapeCharacter(bool isAccountLimited) {
117 int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
118 int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
120 string longLine = new('a', longLineLength - 1);
121 string message = $"{longLine}[";
123 List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
125 Assert.AreEqual(2, output.Count);
127 Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
128 Assert.AreEqual($@"{ContinuationCharacter}\[", output[1]);
131 [TestMethod]
132 public async Task NoNeedForAnySplittingWithNewlines() {
133 string message = $"abcdef{Environment.NewLine}ghijkl{Environment.NewLine}mnopqr";
135 List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
137 Assert.AreEqual(1, output.Count);
138 Assert.AreEqual(message, output.First());
141 [TestMethod]
142 public async Task NoNeedForAnySplittingWithoutNewlines() {
143 const string message = "abcdef";
145 List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
147 Assert.AreEqual(1, output.Count);
148 Assert.AreEqual(message, output.First());
151 [TestMethod]
152 public void ParagraphCharacterSizeIsLessOrEqualToContinuationCharacterSize() => Assert.IsTrue(ContinuationCharacterBytes >= Encoding.UTF8.GetByteCount(ParagraphCharacter.ToString()));
154 [TestMethod]
155 public async Task ProperlyEscapesCharacters() {
156 const string message = @"[b]bold[/b] \n";
157 const string escapedMessage = @"\[b]bold\[/b] \\n";
159 List<string> output = await GetMessageParts(message).ToListAsync().ConfigureAwait(false);
161 Assert.AreEqual(1, output.Count);
162 Assert.AreEqual(escapedMessage, output.First());
165 [TestMethod]
166 public async Task ProperlyEscapesSteamMessagePrefix() {
167 const string prefix = "/pre []";
168 const string escapedPrefix = @"/pre \[]";
170 const string message = "asdf";
172 List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
174 Assert.AreEqual(1, output.Count);
175 Assert.AreEqual($"{escapedPrefix}{message}", output.First());
178 [DataRow(false)]
179 [DataRow(true)]
180 [DataTestMethod]
181 public async Task ProperlySplitsLongSingleLine(bool isAccountLimited) {
182 int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
183 int longLineLength = maxMessageBytes - ReservedContinuationMessageBytes;
185 string longLine = new('a', longLineLength);
186 string message = $"{longLine}{longLine}{longLine}{longLine}";
188 List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
190 Assert.AreEqual(4, output.Count);
192 Assert.AreEqual($"{longLine}{ContinuationCharacter}", output[0]);
193 Assert.AreEqual($"{ContinuationCharacter}{longLine}{ContinuationCharacter}", output[1]);
194 Assert.AreEqual($"{ContinuationCharacter}{longLine}{ContinuationCharacter}", output[2]);
195 Assert.AreEqual($"{ContinuationCharacter}{longLine}", output[3]);
198 [TestMethod]
199 public void ReservedSizeForEscapingIsProperlyCalculated() => Assert.AreEqual(ReservedEscapeMessageBytes, Encoding.UTF8.GetByteCount(@"\") + 4); // Maximum amount of bytes per single UTF-8 character is 4, not 6 as from Encoding.UTF8.GetMaxByteCount(1)
201 [TestMethod]
202 public async Task RyzhehvostInitialTestForSplitting() {
203 const string prefix = "/me ";
205 const string message = """
206 <XLimited5> Уже имеет: app/1493800 | Aircraft Carrier Survival: Prolouge
207 <XLimited5> Уже имеет: app/349520 | Armillo
208 <XLimited5> Уже имеет: app/346330 | BrainBread 2
209 <XLimited5> Уже имеет: app/1086690 | C-War 2
210 <XLimited5> Уже имеет: app/730 | Counter-Strike: Global Offensive
211 <XLimited5> Уже имеет: app/838380 | DEAD OR ALIVE 6
212 <XLimited5> Уже имеет: app/582890 | Estranged: The Departure
213 <XLimited5> Уже имеет: app/331470 | Everlasting Summer
214 <XLimited5> Уже имеет: app/1078000 | Gamecraft
215 <XLimited5> Уже имеет: app/266310 | GameGuru
216 <XLimited5> Уже имеет: app/275390 | Guacamelee! Super Turbo Championship Edition
217 <XLimited5> Уже имеет: app/627690 | Idle Champions of the Forgotten Realms
218 <XLimited5> Уже имеет: app/1048540 | Kao the Kangaroo: Round 2
219 <XLimited5> Уже имеет: app/370910 | Kathy Rain
220 <XLimited5> Уже имеет: app/343710 | KHOLAT
221 <XLimited5> Уже имеет: app/253900 | Knights and Merchants
222 <XLimited5> Уже имеет: app/224260 | No More Room in Hell
223 <XLimited5> Уже имеет: app/343360 | Particula
224 <XLimited5> Уже имеет: app/237870 | Planet Explorers
225 <XLimited5> Уже имеет: app/684680 | Polygoneer
226 <XLimited5> Уже имеет: app/1089130 | Quake II RTX
227 <XLimited5> Уже имеет: app/755790 | Ring of Elysium
228 <XLimited5> Уже имеет: app/1258080 | Shop Titans
229 <XLimited5> Уже имеет: app/759530 | Struckd - 3D Game Creator
230 <XLimited5> Уже имеет: app/269710 | Tumblestone
231 <XLimited5> Уже имеет: app/304930 | Unturned
232 <XLimited5> Уже имеет: app/1019250 | WWII TCG - World War 2: The Card Game
234 <ASF> 1/1 ботов уже имеют игру app/1493800 | Aircraft Carrier Survival: Prolouge.
235 <ASF> 1/1 ботов уже имеют игру app/349520 | Armillo.
236 <ASF> 1/1 ботов уже имеют игру app/346330 | BrainBread 2.
237 <ASF> 1/1 ботов уже имеют игру app/1086690 | C-War 2.
238 <ASF> 1/1 ботов уже имеют игру app/730 | Counter-Strike: Global Offensive.
239 <ASF> 1/1 ботов уже имеют игру app/838380 | DEAD OR ALIVE 6.
240 <ASF> 1/1 ботов уже имеют игру app/582890 | Estranged: The Departure.
241 <ASF> 1/1 ботов уже имеют игру app/331470 | Everlasting Summer.
242 <ASF> 1/1 ботов уже имеют игру app/1078000 | Gamecraft.
243 <ASF> 1/1 ботов уже имеют игру app/266310 | GameGuru.
244 <ASF> 1/1 ботов уже имеют игру app/275390 | Guacamelee! Super Turbo Championship Edition.
245 <ASF> 1/1 ботов уже имеют игру app/627690 | Idle Champions of the Forgotten Realms.
246 <ASF> 1/1 ботов уже имеют игру app/1048540 | Kao the Kangaroo: Round 2.
247 <ASF> 1/1 ботов уже имеют игру app/370910 | Kathy Rain.
248 <ASF> 1/1 ботов уже имеют игру app/343710 | KHOLAT.
249 <ASF> 1/1 ботов уже имеют игру app/253900 | Knights and Merchants.
250 <ASF> 1/1 ботов уже имеют игру app/224260 | No More Room in Hell.
251 <ASF> 1/1 ботов уже имеют игру app/343360 | Particula.
252 <ASF> 1/1 ботов уже имеют игру app/237870 | Planet Explorers.
253 <ASF> 1/1 ботов уже имеют игру app/684680 | Polygoneer.
254 <ASF> 1/1 ботов уже имеют игру app/1089130 | Quake II RTX.
255 <ASF> 1/1 ботов уже имеют игру app/755790 | Ring of Elysium.
256 <ASF> 1/1 ботов уже имеют игру app/1258080 | Shop Titans.
257 <ASF> 1/1 ботов уже имеют игру app/759530 | Struckd - 3D Game Creator.
258 <ASF> 1/1 ботов уже имеют игру app/269710 | Tumblestone.
259 <ASF> 1/1 ботов уже имеют игру app/304930 | Unturned.
260 """;
262 List<string> output = await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
264 Assert.AreEqual(2, output.Count);
266 foreach (string messagePart in output) {
267 if ((messagePart.Length <= prefix.Length) || !messagePart.StartsWith(prefix, StringComparison.Ordinal)) {
268 Assert.Fail();
270 return;
273 string[] lines = messagePart.Split(SharedInfo.NewLineIndicators, StringSplitOptions.None);
275 int bytes = lines.Where(static line => line.Length > 0).Sum(Encoding.UTF8.GetByteCount) + ((lines.Length - 1) * NewlineWeight);
277 if (bytes > MaxMessageBytesForUnlimitedAccounts) {
278 Assert.Fail();
280 return;
285 [DataRow(false)]
286 [DataRow(true)]
287 [DataTestMethod]
288 public async Task SplitsOnNewlinesWithParagraphCharacter(bool isAccountLimited) {
289 int maxMessageBytes = isAccountLimited ? MaxMessageBytesForLimitedAccounts : MaxMessageBytesForUnlimitedAccounts;
291 StringBuilder newlinePartBuilder = new();
293 for (ushort bytes = 0; bytes < maxMessageBytes - ReservedContinuationMessageBytes - NewlineWeight;) {
294 if (newlinePartBuilder.Length > 0) {
295 bytes += NewlineWeight;
296 newlinePartBuilder.Append(Environment.NewLine);
299 bytes++;
300 newlinePartBuilder.Append('a');
303 string newlinePart = newlinePartBuilder.ToString();
304 string message = $"{newlinePart}{Environment.NewLine}{newlinePart}{Environment.NewLine}{newlinePart}{Environment.NewLine}{newlinePart}";
306 List<string> output = await GetMessageParts(message, isAccountLimited: isAccountLimited).ToListAsync().ConfigureAwait(false);
308 Assert.AreEqual(4, output.Count);
310 Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[0]);
311 Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[1]);
312 Assert.AreEqual($"{newlinePart}{ParagraphCharacter}", output[2]);
313 Assert.AreEqual(newlinePart, output[3]);
316 [TestMethod]
317 public async Task ThrowsOnTooLongNewlinesPrefix() {
318 string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
320 const string message = "asdf";
322 await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
325 [TestMethod]
326 public async Task ThrowsOnTooLongPrefix() {
327 string prefix = new('x', MaxMessagePrefixBytes + 1);
329 const string message = "asdf";
331 await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);