1 // ----------------------------------------------------------------------------------------------
3 // / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
4 // / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
5 // / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
6 // /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
7 // ----------------------------------------------------------------------------------------------
9 // Copyright 2015-2024 Ćukasz "JustArchi" Domeradzki
10 // Contact: JustArchi@JustArchi.net
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
16 // http://www.apache.org/licenses/LICENSE-2.0
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.
25 using System
.Collections
.Generic
;
27 using ArchiSteamFarm
.Steam
.Data
;
28 using Microsoft
.VisualStudio
.TestTools
.UnitTesting
;
29 using static ArchiSteamFarm
.Steam
.Bot
;
31 namespace ArchiSteamFarm
.Tests
;
34 public sealed class Bot
{
36 public void MaxItemsBarelyEnoughForOneSet() {
37 const uint relevantAppID
= 42;
39 Dictionary
<uint, byte> itemsPerSet
= new() {
40 { relevantAppID, MinCardsPerBadge }
,
41 { 43, MinCardsPerBadge + 1 }
44 HashSet
<Asset
> items
= [];
46 foreach ((uint appID
, byte cards
) in itemsPerSet
) {
47 for (byte i
= 1; i
<= cards
; i
++) {
48 items
.Add(CreateCard(i
, realAppID
: appID
));
52 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, itemsPerSet
, MinCardsPerBadge
);
54 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= items
.Where(static item
=> item
.RealAppID
== relevantAppID
).GroupBy(static item
=> (item
.RealAppID
, item
.ContextID
, item
.ClassID
)).ToDictionary(static group => group.Key
, static group => (uint) group.Sum(static item
=> item
.Amount
));
56 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
60 public void MaxItemsTooSmall() {
61 const uint appID
= 42;
63 HashSet
<Asset
> items
= [
64 CreateCard(1, realAppID
: appID
),
65 CreateCard(2, realAppID
: appID
)
68 Assert
.ThrowsException
<ArgumentOutOfRangeException
>(() => GetItemsForFullBadge(items
, 2, appID
, MinCardsPerBadge
- 1));
72 public void MoreCardsThanNeeded() {
73 const uint appID
= 42;
75 HashSet
<Asset
> items
= [
76 CreateCard(1, realAppID
: appID
),
77 CreateCard(1, realAppID
: appID
),
78 CreateCard(2, realAppID
: appID
),
79 CreateCard(3, realAppID
: appID
)
82 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 3, appID
);
84 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
85 { (appID, Asset.SteamCommunityContextID, 1), 1 }
,
86 { (appID, Asset.SteamCommunityContextID, 2), 1 }
,
87 { (appID, Asset.SteamCommunityContextID, 3), 1 }
90 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
94 public void MultipleSets() {
95 const uint appID
= 42;
97 HashSet
<Asset
> items
= [
98 CreateCard(1, realAppID
: appID
),
99 CreateCard(1, realAppID
: appID
),
100 CreateCard(2, realAppID
: appID
),
101 CreateCard(2, realAppID
: appID
)
104 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
106 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
107 { (appID, Asset.SteamCommunityContextID, 1), 2 }
,
108 { (appID, Asset.SteamCommunityContextID, 2), 2 }
111 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
115 public void MultipleSetsDifferentAmount() {
116 const uint appID
= 42;
118 HashSet
<Asset
> items
= [
119 CreateCard(1, amount
: 2, realAppID
: appID
),
120 CreateCard(2, realAppID
: appID
),
121 CreateCard(2, realAppID
: appID
)
124 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
126 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
127 { (appID, Asset.SteamCommunityContextID, 1), 2 }
,
128 { (appID, Asset.SteamCommunityContextID, 2), 2 }
131 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
135 public void MutliRarityAndType() {
136 const uint appID
= 42;
138 HashSet
<Asset
> items
= [
139 CreateCard(1, realAppID
: appID
, type
: EAssetType
.TradingCard
, rarity
: EAssetRarity
.Common
),
140 CreateCard(2, realAppID
: appID
, type
: EAssetType
.TradingCard
, rarity
: EAssetRarity
.Common
),
142 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Uncommon
),
143 CreateCard(2, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Uncommon
),
145 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Rare
),
146 CreateCard(2, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Rare
),
148 // for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment
149 CreateCard(1, realAppID
: appID
, type
: EAssetType
.TradingCard
, rarity
: EAssetRarity
.Uncommon
),
150 CreateCard(2, realAppID
: appID
, type
: EAssetType
.TradingCard
, rarity
: EAssetRarity
.Uncommon
),
151 CreateCard(3, realAppID
: appID
, type
: EAssetType
.TradingCard
, rarity
: EAssetRarity
.Uncommon
),
153 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Common
),
154 CreateCard(3, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Common
),
155 CreateCard(7, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
, rarity
: EAssetRarity
.Common
),
157 CreateCard(2, realAppID
: appID
, type
: EAssetType
.Unknown
, rarity
: EAssetRarity
.Rare
),
158 CreateCard(3, realAppID
: appID
, type
: EAssetType
.Unknown
, rarity
: EAssetRarity
.Rare
),
159 CreateCard(4, realAppID
: appID
, type
: EAssetType
.Unknown
, rarity
: EAssetRarity
.Rare
)
162 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 3, appID
);
164 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
165 { (appID, Asset.SteamCommunityContextID, 1), 2 }
,
166 { (appID, Asset.SteamCommunityContextID, 2), 2 }
,
167 { (appID, Asset.SteamCommunityContextID, 3), 3 }
,
168 { (appID, Asset.SteamCommunityContextID, 4), 1 }
,
169 { (appID, Asset.SteamCommunityContextID, 7), 1 }
172 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
176 public void NotAllCardsPresent() {
177 const uint appID
= 42;
179 HashSet
<Asset
> items
= [
180 CreateCard(1, realAppID
: appID
),
181 CreateCard(2, realAppID
: appID
)
184 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 3, appID
);
186 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new(0);
187 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
191 public void OneSet() {
192 const uint appID
= 42;
194 HashSet
<Asset
> items
= [
195 CreateCard(1, realAppID
: appID
),
196 CreateCard(2, realAppID
: appID
)
199 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
201 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
202 { (appID, Asset.SteamCommunityContextID, 1), 1 }
,
203 { (appID, Asset.SteamCommunityContextID, 2), 1 }
206 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
210 public void OtherAppIDFullSets() {
211 const uint appID0
= 42;
212 const uint appID1
= 43;
214 HashSet
<Asset
> items
= [
215 CreateCard(1, realAppID
: appID0
),
216 CreateCard(1, realAppID
: appID1
)
219 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(
220 items
, new Dictionary
<uint, byte> {
226 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
227 { (appID0, Asset.SteamCommunityContextID, 1), 1 }
,
228 { (appID1, Asset.SteamCommunityContextID, 1), 1 }
231 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
235 public void OtherAppIDNoSets() {
236 const uint appID0
= 42;
237 const uint appID1
= 43;
239 HashSet
<Asset
> items
= [
240 CreateCard(1, realAppID
: appID0
),
241 CreateCard(1, realAppID
: appID1
)
244 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(
245 items
, new Dictionary
<uint, byte> {
251 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new(0);
253 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
257 public void OtherAppIDOneSet() {
258 const uint appID0
= 42;
259 const uint appID1
= 43;
260 const uint appID2
= 44;
262 HashSet
<Asset
> items
= [
263 CreateCard(1, realAppID
: appID0
),
264 CreateCard(2, realAppID
: appID0
),
266 CreateCard(1, realAppID
: appID1
),
267 CreateCard(2, realAppID
: appID1
),
268 CreateCard(3, realAppID
: appID1
)
271 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(
272 items
, new Dictionary
<uint, byte> {
279 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
280 { (appID1, Asset.SteamCommunityContextID, 1), 1 }
,
281 { (appID1, Asset.SteamCommunityContextID, 2), 1 }
,
282 { (appID1, Asset.SteamCommunityContextID, 3), 1 }
285 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
289 public void OtherRarityFullSets() {
290 const uint appID
= 42;
292 HashSet
<Asset
> items
= [
293 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Common
),
294 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Rare
)
297 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 1, appID
);
299 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
300 { (appID, Asset.SteamCommunityContextID, 1), 2 }
303 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
307 public void OtherRarityNoSets() {
308 const uint appID
= 42;
310 HashSet
<Asset
> items
= [
311 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Common
),
312 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Rare
)
315 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
317 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new(0);
319 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
323 public void OtherRarityOneSet() {
324 const uint appID
= 42;
326 HashSet
<Asset
> items
= [
327 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Common
),
328 CreateCard(2, realAppID
: appID
, rarity
: EAssetRarity
.Common
),
329 CreateCard(1, realAppID
: appID
, rarity
: EAssetRarity
.Uncommon
),
330 CreateCard(2, realAppID
: appID
, rarity
: EAssetRarity
.Uncommon
),
331 CreateCard(3, realAppID
: appID
, rarity
: EAssetRarity
.Uncommon
)
334 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 3, appID
);
336 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
337 { (appID, Asset.SteamCommunityContextID, 1), 1 }
,
338 { (appID, Asset.SteamCommunityContextID, 2), 1 }
,
339 { (appID, Asset.SteamCommunityContextID, 3), 1 }
342 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
346 public void OtherTypeFullSets() {
347 const uint appID
= 42;
349 HashSet
<Asset
> items
= [
350 CreateCard(1, realAppID
: appID
, type
: EAssetType
.TradingCard
),
351 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
)
354 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 1, appID
);
356 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
357 { (appID, Asset.SteamCommunityContextID, 1), 2 }
360 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
364 public void OtherTypeNoSets() {
365 const uint appID
= 42;
367 HashSet
<Asset
> items
= [
368 CreateCard(1, realAppID
: appID
, type
: EAssetType
.TradingCard
),
369 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
)
372 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
374 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new(0);
376 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
380 public void OtherTypeOneSet() {
381 const uint appID
= 42;
383 HashSet
<Asset
> items
= [
384 CreateCard(1, realAppID
: appID
, type
: EAssetType
.TradingCard
),
385 CreateCard(2, realAppID
: appID
, type
: EAssetType
.TradingCard
),
386 CreateCard(1, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
),
387 CreateCard(2, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
),
388 CreateCard(3, realAppID
: appID
, type
: EAssetType
.FoilTradingCard
)
391 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 3, appID
);
393 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
394 { (appID, Asset.SteamCommunityContextID, 1), 1 }
,
395 { (appID, Asset.SteamCommunityContextID, 2), 1 }
,
396 { (appID, Asset.SteamCommunityContextID, 3), 1 }
399 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
403 public void TooHighAmount() {
404 const uint appID0
= 42;
406 HashSet
<Asset
> items
= [
407 CreateCard(1, amount
: 2, realAppID
: appID0
),
408 CreateCard(2, realAppID
: appID0
)
411 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID0
);
413 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
= new() {
414 { (appID0, Asset.SteamCommunityContextID, 1), 1 }
,
415 { (appID0, Asset.SteamCommunityContextID, 2), 1 }
418 AssertResultMatchesExpectation(expectedResult
, itemsToSend
);
422 public void TooManyCardsForSingleTrade() {
423 const uint appID
= 42;
425 HashSet
<Asset
> items
= [];
427 for (byte i
= 0; i
< Steam
.Exchange
.Trading
.MaxItemsPerTrade
; i
++) {
428 items
.Add(CreateCard(1, realAppID
: appID
));
429 items
.Add(CreateCard(2, realAppID
: appID
));
432 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, 2, appID
);
434 Assert
.IsTrue(itemsToSend
.Count
<= Steam
.Exchange
.Trading
.MaxItemsPerTrade
);
438 public void TooManyCardsForSingleTradeMultipleAppIDs() {
439 const uint appID0
= 42;
440 const uint appID1
= 43;
442 HashSet
<Asset
> items
= [];
444 for (byte i
= 0; i
< 100; i
++) {
445 items
.Add(CreateCard(1, realAppID
: appID0
));
446 items
.Add(CreateCard(2, realAppID
: appID0
));
447 items
.Add(CreateCard(1, realAppID
: appID1
));
448 items
.Add(CreateCard(2, realAppID
: appID1
));
451 Dictionary
<uint, byte> itemsPerSet
= new() {
456 HashSet
<Asset
> itemsToSend
= GetItemsForFullBadge(items
, itemsPerSet
);
458 Assert
.IsTrue(itemsToSend
.Count
<= Steam
.Exchange
.Trading
.MaxItemsPerTrade
);
462 public void TooManyCardsPerSet() {
463 const uint appID0
= 42;
464 const uint appID1
= 43;
465 const uint appID2
= 44;
467 HashSet
<Asset
> items
= [
468 CreateCard(1, realAppID
: appID0
),
469 CreateCard(2, realAppID
: appID0
),
470 CreateCard(3, realAppID
: appID0
),
471 CreateCard(4, realAppID
: appID0
)
474 Assert
.ThrowsException
<InvalidOperationException
>(
475 () => GetItemsForFullBadge(
476 items
, new Dictionary
<uint, byte> {
485 private static void AssertResultMatchesExpectation(IReadOnlyDictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), uint> expectedResult
, IReadOnlyCollection
<Asset
> itemsToSend
) {
486 ArgumentNullException
.ThrowIfNull(expectedResult
);
487 ArgumentNullException
.ThrowIfNull(itemsToSend
);
489 Dictionary
<(uint RealAppID
, ulong ContextID
, ulong ClassID
), long> realResult
= itemsToSend
.GroupBy(static asset
=> (asset
.RealAppID
, asset
.ContextID
, asset
.ClassID
)).ToDictionary(static group => group.Key
, static group => group.Sum(static asset
=> asset
.Amount
));
490 Assert
.AreEqual(expectedResult
.Count
, realResult
.Count
);
491 Assert
.IsTrue(expectedResult
.All(expectation
=> realResult
.TryGetValue(expectation
.Key
, out long reality
) && (expectation
.Value
== reality
)));
494 private static Asset
CreateCard(ulong classID
, ulong instanceID
= 0, uint amount
= 1, bool marketable
= false, bool tradable
= false, uint realAppID
= Asset
.SteamAppID
, EAssetType type
= EAssetType
.TradingCard
, EAssetRarity rarity
= EAssetRarity
.Common
) => new(Asset
.SteamAppID
, Asset
.SteamCommunityContextID
, classID
, amount
, new InventoryDescription(Asset
.SteamAppID
, classID
, instanceID
, marketable
, tradable
, realAppID
, type
, rarity
));
496 private static HashSet
<Asset
> GetItemsForFullBadge(IReadOnlyCollection
<Asset
> inventory
, byte cardsPerSet
, uint appID
, ushort maxItems
= Steam
.Exchange
.Trading
.MaxItemsPerTrade
) => GetItemsForFullBadge(inventory
, new Dictionary
<uint, byte> { { appID, cardsPerSet }
}, maxItems
);
498 private static HashSet
<Asset
> GetItemsForFullBadge(IReadOnlyCollection
<Asset
> inventory
, IDictionary
<uint, byte> cardsPerSet
, ushort maxItems
= Steam
.Exchange
.Trading
.MaxItemsPerTrade
) {
499 Dictionary
<(uint RealAppID
, EAssetType Type
, EAssetRarity Rarity
), List
<uint>> inventorySets
= Steam
.Exchange
.Trading
.GetInventorySets(inventory
);
501 return GetItemsForFullSets(inventory
, inventorySets
.ToDictionary(static kv
=> kv
.Key
, kv
=> (SetsToExtract
: inventorySets
[kv
.Key
][0], cardsPerSet
[kv
.Key
.RealAppID
])), maxItems
).ToHashSet();