From 1f56dd2a28156a2c0439632a9cc9f16e4c739add Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Mon, 26 Apr 2021 04:39:51 -0400 Subject: [PATCH 01/19] Add badge forms --- .../TypeParsers/BadgeFormParser.cs | 54 +++++++++++++++ .../Commands/Definitions/BadgeCommandsTest.cs | 6 +- .../Definitions/OperatorCommandsTest.cs | 10 +-- .../Commands/Definitions/OperatorCommands.cs | 11 +-- TPP.Core/Setups.cs | 1 + .../Repos/BadgeRepoTest.cs | 69 ++++++++++--------- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 6 +- .../Serializers/BadgeFormSerializer.cs | 26 +++++++ .../Serializers/CustomSerializers.cs | 1 + TPP.Persistence/Models/Badge.cs | 23 ++++++- TPP.Persistence/Repos/IBadgeRepo.cs | 2 +- 11 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs create mode 100644 TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs diff --git a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs new file mode 100644 index 00000000..056f1139 --- /dev/null +++ b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using TPP.Persistence.Models; + +namespace TPP.ArgsParsing.TypeParsers +{ + /// + /// A parser that finds a badge form by name. + /// + public class BadgeFormParser : BaseArgumentParser + { + public override Task> Parse(IImmutableList args, Type[] genericTypes) + { + string form = args[0]; + ArgsParseResult result; + try + { + Badge.BadgeForm parsedForm = (Badge.BadgeForm)Enum.Parse(typeof(Badge.BadgeForm), form, ignoreCase: true); + if (parsedForm == Badge.BadgeForm.Shiny) + { + switch (args[1].ToLower()) + { + case "shadow": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyShadow, args.Skip(2).ToImmutableList()); + break; + case "mega": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyMega, args.Skip(2).ToImmutableList()); + break; + case "alolan": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyAlolan, args.Skip(2).ToImmutableList()); + break; + case "galarian": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyGalarian, args.Skip(2).ToImmutableList()); + break; + default: + result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); + break; + } + } + else + { + result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); + } + } + catch (ArgumentException) + { + result = ArgsParseResult.Failure($"Did not find a role named '{form}'"); + } + return Task.FromResult(result); + } + } +} diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index 171ceb98..dbfaf91b 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -264,9 +264,9 @@ public async Task TestGiftBadgeSuccessful() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); + Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index d9402d58..4250bbf5 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -158,9 +158,9 @@ public async Task TestTransferBadgeSuccessful() _messageSenderMock.Object, _badgeRepoMock.Object); _userRepoMock.Setup(repo => repo.FindBySimpleName("gifter")).Returns(Task.FromResult((User?)gifter)); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); + Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(gifter.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -256,10 +256,10 @@ public async Task TestCreateBadge() CommandResult result = await operatorCommands.CreateBadge(new CommandContext(MockMessage(user), ImmutableList.Create("recipient", "species", "123"), _argsParser)); - Assert.AreEqual("123 badges of species #001 Species created for user Recipient.", result.Response); + Assert.AreEqual("123 Normal #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index 1c362972..a69b0643 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -183,18 +183,19 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt) = - await context.ParseArgs>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt ) = + await context.ParseArgs, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); + Badge.BadgeForm form = formOpt.IsPresent ? formOpt.Value : Badge.BadgeForm.Normal; for (int i = 0; i < amount; i++) - await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation); + await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form); return new CommandResult { Response = amount > 1 - ? $"{amount} badges of species {species} created for user {recipient.Name}." - : $"Badge of species {species} created for user {recipient.Name}." + ? $"{amount} {form} {species} badges created for {recipient.Name}." + : $"{form} {species} badge created for {recipient.Name}." }; } } diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index e93417a9..107367cd 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -38,6 +38,7 @@ public static ArgsParser SetUpArgsParser(IUserRepo userRepo, PokedexData pokedex argsParser.AddArgumentParser(new SignedPokeyenParser()); argsParser.AddArgumentParser(new SignedTokensParser()); argsParser.AddArgumentParser(new PkmnSpeciesParser(pokedexData.KnownSpecies, PokedexData.NormalizeName)); + argsParser.AddArgumentParser(new BadgeFormParser()); argsParser.AddArgumentParser(new AnyOrderParser(argsParser)); argsParser.AddArgumentParser(new OneOfParser(argsParser)); diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 93190ef1..666c3e79 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -24,7 +24,7 @@ public async Task insert_then_read_are_equal() { BadgeRepo badgeRepo = CreateBadgeRepo(); // when - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); // then Assert.AreNotEqual(string.Empty, badge.Id); @@ -43,7 +43,7 @@ public async Task insert_sets_current_timestamp_as_creation_date() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), clockMock.Object); - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); Assert.AreEqual(createdAt, badge.CreatedAt); } @@ -57,7 +57,7 @@ public async Task has_expected_bson_datatypes() BadgeRepo badgeRepo = CreateBadgeRepo(); // when PkmnSpecies randomSpecies = PkmnSpecies.OfId("9001"); - Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught); + Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, Badge.BadgeForm.Normal); // then IMongoCollection badgesCollectionBson = @@ -74,10 +74,10 @@ public async Task can_find_by_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball); + Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when List resultUserA = await badgeRepo.FindByUser("userA"); @@ -95,13 +95,13 @@ public async Task can_count_by_user_and_species() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when long countHasNone = await badgeRepo.CountByUserAndSpecies("user", PkmnSpecies.OfId("1")); @@ -119,13 +119,13 @@ public async Task can_count_per_species_for_one_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when ImmutableSortedDictionary result = await badgeRepo.CountByUserPerSpecies("user"); @@ -144,12 +144,12 @@ public async Task can_check_if_user_has_badge() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when bool hasUserSpecies1 = await badgeRepo.HasUserBadge("user", PkmnSpecies.OfId("1")); @@ -173,7 +173,7 @@ public async Task returns_updated_badge_object() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); IImmutableList updatedBadges = await badgeRepo.TransferBadges( ImmutableList.Create(badge), "recipient", "reason", new Dictionary()); @@ -184,6 +184,7 @@ public async Task returns_updated_badge_object() Assert.AreEqual(badge.Source, updatedBadges[0].Source); Assert.AreEqual(badge.CreatedAt, updatedBadges[0].CreatedAt); Assert.AreEqual("recipient", updatedBadges[0].UserId); + Assert.AreEqual(badge.Form, updatedBadges[0].Form); } [Test] @@ -191,7 +192,7 @@ public async Task unmarks_as_selling() { BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge.Id), Builders.Update @@ -217,7 +218,7 @@ public async Task logs_to_badgelog() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, clockMock.Object); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); Instant timestamp = Instant.FromUnixTimeSeconds(123); clockMock.Setup(c => c.GetCurrentInstant()).Returns(timestamp); @@ -236,8 +237,8 @@ public async Task triggers_species_lost_event() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); int userLostBadgeInvocations = 0; badgeRepo.UserLostBadgeSpecies += (_, args) => { @@ -260,8 +261,8 @@ public async Task aborts_all_transfers_if_one_fails() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); // make in-memory badge reference stale to cause the transfer to fail on the second badge await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge2.Id), diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index c08ead5b..b62f13f1 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -36,6 +36,7 @@ static BadgeRepo() cm.MapProperty(b => b.Species).SetElementName("species"); cm.MapProperty(b => b.Source).SetElementName("source"); cm.MapProperty(b => b.CreatedAt).SetElementName("created_at"); + cm.MapProperty(b => b.Form).SetElementName("form"); cm.MapProperty(b => b.SellPrice).SetElementName("sell_price") .SetIgnoreIfNull(true); cm.MapProperty(b => b.SellingSince).SetElementName("selling_since") @@ -64,14 +65,15 @@ private void InitIndexes() } public async Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Instant? createdAt = null) + string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null) { var badge = new Badge( id: string.Empty, userId: userId, species: species, source: source, - createdAt: createdAt ?? _clock.GetCurrentInstant() + createdAt: createdAt ?? _clock.GetCurrentInstant(), + form: form ); await Collection.InsertOneAsync(badge); Debug.Assert(badge.Id.Length > 0, "The MongoDB driver injected a generated ID"); diff --git a/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs b/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs new file mode 100644 index 00000000..a653e8da --- /dev/null +++ b/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using TPP.Persistence.Models; + +namespace TPP.Persistence.MongoDB.Serializers +{ + class BadgeFormSerializer : EnumToStringUsingTranslationMappingSerializer + { + public static readonly BadgeFormSerializer Instance = new BadgeFormSerializer(); + + private BadgeFormSerializer() : base(new Dictionary + { + [Badge.BadgeForm.Normal] = "normal", + [Badge.BadgeForm.Shiny] = "shiny", + [Badge.BadgeForm.Shadow] = "shadow", + [Badge.BadgeForm.Mega] = "mega", + [Badge.BadgeForm.Alolan] = "alolan", + [Badge.BadgeForm.Galarian] = "galarian", + [Badge.BadgeForm.ShinyShadow] = "shiny_shadow", + [Badge.BadgeForm.ShinyMega] = "shiny_mega", + [Badge.BadgeForm.ShinyAlolan] = "shiny_alolan", + [Badge.BadgeForm.ShinyGalarian] = "shiny_galarian", + }) + { + } + } +} diff --git a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs index b6078715..1d33110b 100644 --- a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs +++ b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs @@ -15,6 +15,7 @@ public static void RegisterAll() if (_registered) return; _registered = true; BsonSerializer.RegisterSerializer(BadgeSourceSerializer.Instance); + BsonSerializer.RegisterSerializer(BadgeFormSerializer.Instance); BsonSerializer.RegisterSerializer(PkmnSpeciesSerializer.Instance); BsonSerializer.RegisterSerializer(InstantSerializer.Instance); BsonSerializer.RegisterSerializer(NullableInstantSerializer.Instance); diff --git a/TPP.Persistence/Models/Badge.cs b/TPP.Persistence/Models/Badge.cs index 2e29dd51..90df30b4 100644 --- a/TPP.Persistence/Models/Badge.cs +++ b/TPP.Persistence/Models/Badge.cs @@ -1,5 +1,6 @@ using NodaTime; using TPP.Common; +using System.ComponentModel.DataAnnotations; namespace TPP.Persistence.Models { @@ -43,6 +44,24 @@ public enum BadgeSource /// public Instant CreatedAt { get; init; } + public enum BadgeForm + { + Normal, + Shiny, + Shadow, + Mega, + Alolan, + Galarian, + ShinyShadow, + ShinyMega, + ShinyAlolan, + ShinyGalarian + } + /// + /// What form of the pokemon this badge is. + /// + public BadgeForm Form { get; init; } + /// If this badge is on sale, for how much. public long? SellPrice { get; init; } /// If this badge is on sale, since when. @@ -53,13 +72,15 @@ public Badge( string? userId, PkmnSpecies species, BadgeSource source, - Instant createdAt) + Instant createdAt, + BadgeForm form) { Id = id; UserId = userId; Species = species; Source = source; CreatedAt = createdAt; + Form = form; } public override string ToString() => $"Badge({Species}@{UserId ?? ""})"; diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index 1e2cd80b..ba7cf3a6 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -38,7 +38,7 @@ public OwnedBadgeNotFoundException(Badge badge) : public interface IBadgeRepo { public Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Instant? createdAt = null); + string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); From 570514c38f5576fa99defa3c455307fdd17838ab Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Mon, 26 Apr 2021 04:39:51 -0400 Subject: [PATCH 02/19] Add badge forms --- .../TypeParsers/BadgeFormParser.cs | 54 +++++++++++++++ .../Commands/Definitions/BadgeCommandsTest.cs | 6 +- .../Definitions/OperatorCommandsTest.cs | 10 +-- .../Commands/Definitions/OperatorCommands.cs | 11 +-- TPP.Core/Setups.cs | 1 + .../Repos/BadgeRepoTest.cs | 69 ++++++++++--------- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 6 +- .../Serializers/BadgeFormSerializer.cs | 26 +++++++ .../Serializers/CustomSerializers.cs | 1 + TPP.Persistence/Models/Badge.cs | 23 ++++++- TPP.Persistence/Repos/IBadgeRepo.cs | 2 +- 11 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs create mode 100644 TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs diff --git a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs new file mode 100644 index 00000000..056f1139 --- /dev/null +++ b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using TPP.Persistence.Models; + +namespace TPP.ArgsParsing.TypeParsers +{ + /// + /// A parser that finds a badge form by name. + /// + public class BadgeFormParser : BaseArgumentParser + { + public override Task> Parse(IImmutableList args, Type[] genericTypes) + { + string form = args[0]; + ArgsParseResult result; + try + { + Badge.BadgeForm parsedForm = (Badge.BadgeForm)Enum.Parse(typeof(Badge.BadgeForm), form, ignoreCase: true); + if (parsedForm == Badge.BadgeForm.Shiny) + { + switch (args[1].ToLower()) + { + case "shadow": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyShadow, args.Skip(2).ToImmutableList()); + break; + case "mega": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyMega, args.Skip(2).ToImmutableList()); + break; + case "alolan": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyAlolan, args.Skip(2).ToImmutableList()); + break; + case "galarian": + result = ArgsParseResult.Success(Badge.BadgeForm.ShinyGalarian, args.Skip(2).ToImmutableList()); + break; + default: + result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); + break; + } + } + else + { + result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); + } + } + catch (ArgumentException) + { + result = ArgsParseResult.Failure($"Did not find a role named '{form}'"); + } + return Task.FromResult(result); + } + } +} diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index 171ceb98..dbfaf91b 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -264,9 +264,9 @@ public async Task TestGiftBadgeSuccessful() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); + Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index d9402d58..4250bbf5 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -158,9 +158,9 @@ public async Task TestTransferBadgeSuccessful() _messageSenderMock.Object, _badgeRepoMock.Object); _userRepoMock.Setup(repo => repo.FindBySimpleName("gifter")).Returns(Task.FromResult((User?)gifter)); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); - Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue); + Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(gifter.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -256,10 +256,10 @@ public async Task TestCreateBadge() CommandResult result = await operatorCommands.CreateBadge(new CommandContext(MockMessage(user), ImmutableList.Create("recipient", "species", "123"), _argsParser)); - Assert.AreEqual("123 badges of species #001 Species created for user Recipient.", result.Response); + Assert.AreEqual("123 Normal #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index 1c362972..e286cfd4 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -183,18 +183,19 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt) = - await context.ParseArgs>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt) = + await context.ParseArgs, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); + Badge.BadgeForm form = formOpt.IsPresent ? formOpt.Value : Badge.BadgeForm.Normal; for (int i = 0; i < amount; i++) - await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation); + await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form); return new CommandResult { Response = amount > 1 - ? $"{amount} badges of species {species} created for user {recipient.Name}." - : $"Badge of species {species} created for user {recipient.Name}." + ? $"{amount} {form} {species} badges created for {recipient.Name}." + : $"{form} {species} badge created for {recipient.Name}." }; } } diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index e93417a9..107367cd 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -38,6 +38,7 @@ public static ArgsParser SetUpArgsParser(IUserRepo userRepo, PokedexData pokedex argsParser.AddArgumentParser(new SignedPokeyenParser()); argsParser.AddArgumentParser(new SignedTokensParser()); argsParser.AddArgumentParser(new PkmnSpeciesParser(pokedexData.KnownSpecies, PokedexData.NormalizeName)); + argsParser.AddArgumentParser(new BadgeFormParser()); argsParser.AddArgumentParser(new AnyOrderParser(argsParser)); argsParser.AddArgumentParser(new OneOfParser(argsParser)); diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 93190ef1..666c3e79 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -24,7 +24,7 @@ public async Task insert_then_read_are_equal() { BadgeRepo badgeRepo = CreateBadgeRepo(); // when - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); // then Assert.AreNotEqual(string.Empty, badge.Id); @@ -43,7 +43,7 @@ public async Task insert_sets_current_timestamp_as_creation_date() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), clockMock.Object); - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); Assert.AreEqual(createdAt, badge.CreatedAt); } @@ -57,7 +57,7 @@ public async Task has_expected_bson_datatypes() BadgeRepo badgeRepo = CreateBadgeRepo(); // when PkmnSpecies randomSpecies = PkmnSpecies.OfId("9001"); - Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught); + Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, Badge.BadgeForm.Normal); // then IMongoCollection badgesCollectionBson = @@ -74,10 +74,10 @@ public async Task can_find_by_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball); + Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when List resultUserA = await badgeRepo.FindByUser("userA"); @@ -95,13 +95,13 @@ public async Task can_count_by_user_and_species() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when long countHasNone = await badgeRepo.CountByUserAndSpecies("user", PkmnSpecies.OfId("1")); @@ -119,13 +119,13 @@ public async Task can_count_per_species_for_one_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when ImmutableSortedDictionary result = await badgeRepo.CountByUserPerSpecies("user"); @@ -144,12 +144,12 @@ public async Task can_check_if_user_has_badge() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); // when bool hasUserSpecies1 = await badgeRepo.HasUserBadge("user", PkmnSpecies.OfId("1")); @@ -173,7 +173,7 @@ public async Task returns_updated_badge_object() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); IImmutableList updatedBadges = await badgeRepo.TransferBadges( ImmutableList.Create(badge), "recipient", "reason", new Dictionary()); @@ -184,6 +184,7 @@ public async Task returns_updated_badge_object() Assert.AreEqual(badge.Source, updatedBadges[0].Source); Assert.AreEqual(badge.CreatedAt, updatedBadges[0].CreatedAt); Assert.AreEqual("recipient", updatedBadges[0].UserId); + Assert.AreEqual(badge.Form, updatedBadges[0].Form); } [Test] @@ -191,7 +192,7 @@ public async Task unmarks_as_selling() { BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge.Id), Builders.Update @@ -217,7 +218,7 @@ public async Task logs_to_badgelog() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, clockMock.Object); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); Instant timestamp = Instant.FromUnixTimeSeconds(123); clockMock.Setup(c => c.GetCurrentInstant()).Returns(timestamp); @@ -236,8 +237,8 @@ public async Task triggers_species_lost_event() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); int userLostBadgeInvocations = 0; badgeRepo.UserLostBadgeSpecies += (_, args) => { @@ -260,8 +261,8 @@ public async Task aborts_all_transfers_if_one_fails() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); // make in-memory badge reference stale to cause the transfer to fail on the second badge await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge2.Id), diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index c08ead5b..b62f13f1 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -36,6 +36,7 @@ static BadgeRepo() cm.MapProperty(b => b.Species).SetElementName("species"); cm.MapProperty(b => b.Source).SetElementName("source"); cm.MapProperty(b => b.CreatedAt).SetElementName("created_at"); + cm.MapProperty(b => b.Form).SetElementName("form"); cm.MapProperty(b => b.SellPrice).SetElementName("sell_price") .SetIgnoreIfNull(true); cm.MapProperty(b => b.SellingSince).SetElementName("selling_since") @@ -64,14 +65,15 @@ private void InitIndexes() } public async Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Instant? createdAt = null) + string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null) { var badge = new Badge( id: string.Empty, userId: userId, species: species, source: source, - createdAt: createdAt ?? _clock.GetCurrentInstant() + createdAt: createdAt ?? _clock.GetCurrentInstant(), + form: form ); await Collection.InsertOneAsync(badge); Debug.Assert(badge.Id.Length > 0, "The MongoDB driver injected a generated ID"); diff --git a/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs b/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs new file mode 100644 index 00000000..a653e8da --- /dev/null +++ b/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using TPP.Persistence.Models; + +namespace TPP.Persistence.MongoDB.Serializers +{ + class BadgeFormSerializer : EnumToStringUsingTranslationMappingSerializer + { + public static readonly BadgeFormSerializer Instance = new BadgeFormSerializer(); + + private BadgeFormSerializer() : base(new Dictionary + { + [Badge.BadgeForm.Normal] = "normal", + [Badge.BadgeForm.Shiny] = "shiny", + [Badge.BadgeForm.Shadow] = "shadow", + [Badge.BadgeForm.Mega] = "mega", + [Badge.BadgeForm.Alolan] = "alolan", + [Badge.BadgeForm.Galarian] = "galarian", + [Badge.BadgeForm.ShinyShadow] = "shiny_shadow", + [Badge.BadgeForm.ShinyMega] = "shiny_mega", + [Badge.BadgeForm.ShinyAlolan] = "shiny_alolan", + [Badge.BadgeForm.ShinyGalarian] = "shiny_galarian", + }) + { + } + } +} diff --git a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs index b6078715..1d33110b 100644 --- a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs +++ b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs @@ -15,6 +15,7 @@ public static void RegisterAll() if (_registered) return; _registered = true; BsonSerializer.RegisterSerializer(BadgeSourceSerializer.Instance); + BsonSerializer.RegisterSerializer(BadgeFormSerializer.Instance); BsonSerializer.RegisterSerializer(PkmnSpeciesSerializer.Instance); BsonSerializer.RegisterSerializer(InstantSerializer.Instance); BsonSerializer.RegisterSerializer(NullableInstantSerializer.Instance); diff --git a/TPP.Persistence/Models/Badge.cs b/TPP.Persistence/Models/Badge.cs index 2e29dd51..90df30b4 100644 --- a/TPP.Persistence/Models/Badge.cs +++ b/TPP.Persistence/Models/Badge.cs @@ -1,5 +1,6 @@ using NodaTime; using TPP.Common; +using System.ComponentModel.DataAnnotations; namespace TPP.Persistence.Models { @@ -43,6 +44,24 @@ public enum BadgeSource /// public Instant CreatedAt { get; init; } + public enum BadgeForm + { + Normal, + Shiny, + Shadow, + Mega, + Alolan, + Galarian, + ShinyShadow, + ShinyMega, + ShinyAlolan, + ShinyGalarian + } + /// + /// What form of the pokemon this badge is. + /// + public BadgeForm Form { get; init; } + /// If this badge is on sale, for how much. public long? SellPrice { get; init; } /// If this badge is on sale, since when. @@ -53,13 +72,15 @@ public Badge( string? userId, PkmnSpecies species, BadgeSource source, - Instant createdAt) + Instant createdAt, + BadgeForm form) { Id = id; UserId = userId; Species = species; Source = source; CreatedAt = createdAt; + Form = form; } public override string ToString() => $"Badge({Species}@{UserId ?? ""})"; diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index 1e2cd80b..ba7cf3a6 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -38,7 +38,7 @@ public OwnedBadgeNotFoundException(Badge badge) : public interface IBadgeRepo { public Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Instant? createdAt = null); + string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); From 047a245659ba89c7fd72b80209d800bd0ee6c516 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Tue, 27 Apr 2021 04:42:33 -0400 Subject: [PATCH 03/19] Expand !giftbadge args to allow specified form and source --- TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs | 1 + TPP.ArgsParsing/Types/AnyOrder.cs | 21 +++++++++++++++++++ .../Commands/Definitions/BadgeCommands.cs | 11 ++++++---- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 17 +++++++++++++++ TPP.Persistence/Repos/IBadgeRepo.cs | 1 + 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs b/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs index 484c86fb..b7ba4927 100644 --- a/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs +++ b/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs @@ -78,6 +78,7 @@ public override async Task> Parse( 2 => typeof(AnyOrder<,>), 3 => typeof(AnyOrder<,,>), 4 => typeof(AnyOrder<,,,>), + 5 => typeof(AnyOrder<,,,,>), var num => throw new InvalidOperationException( $"An implementation of {typeof(AnyOrder)} for {num} generic arguments " + "needs to be implemented and wired up where this exception is thrown. " + diff --git a/TPP.ArgsParsing/Types/AnyOrder.cs b/TPP.ArgsParsing/Types/AnyOrder.cs index b0c3cb08..035ff339 100644 --- a/TPP.ArgsParsing/Types/AnyOrder.cs +++ b/TPP.ArgsParsing/Types/AnyOrder.cs @@ -67,4 +67,25 @@ public AnyOrder(T1 item1, T2 item2, T3 item3, T4 item4) public void Deconstruct(out T1 item1, out T2 item2, out T3 item3, out T4 item4) => (item1, item2, item3, item4) = (Item1, Item2, Item3, Item4); } + + public class AnyOrder : AnyOrder + { + public T1 Item1 { get; } + public T2 Item2 { get; } + public T3 Item3 { get; } + public T4 Item4 { get; } + public T5 Item5 { get; } + + public AnyOrder(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + } + + public void Deconstruct(out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5) => + (item1, item2, item3, item4, item5) = (Item1, Item2, Item3, Item4, Item5); + } } diff --git a/TPP.Core/Commands/Definitions/BadgeCommands.cs b/TPP.Core/Commands/Definitions/BadgeCommands.cs index 42b88442..d3af33cf 100644 --- a/TPP.Core/Commands/Definitions/BadgeCommands.cs +++ b/TPP.Core/Commands/Definitions/BadgeCommands.cs @@ -48,7 +48,7 @@ public class BadgeCommands : ICommandCollection new Command("giftbadge", GiftBadge) { Description = - "Gift a badge you own to another user with no price. Arguments: (Optional) " + "Gift a badge you own to another user with no price. Arguments: (Optional) " //TODO also update this before merge }, }; @@ -271,15 +271,18 @@ public async Task Pokedex(CommandContext context) public async Task GiftBadge(CommandContext context) { User gifter = context.Message.User; - (User recipient, PkmnSpecies species, Optional amountOpt) = - await context.ParseArgs>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional sourceOpt) = + await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); + Badge.BadgeForm? form = formOpt.IsPresent ? formOpt.Value : null; + Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; if (recipient == gifter) return new CommandResult { Response = "You cannot gift to yourself" }; - List badges = await _badgeRepo.FindByUserAndSpecies(gifter.Id, species); + List badges = await _badgeRepo.FindAllByCustom(gifter.Id, species, form, source); if (badges.Count < amount) + //TODO big improve before merge return new CommandResult { Response = $"You tried to gift {amount} {species} badges, but you only have {badges.Count}." diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index b62f13f1..9bcc827f 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -86,6 +86,23 @@ public async Task> FindByUser(string? userId) => public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); + public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, Badge.BadgeForm? form = null, Badge.BadgeSource? source = null) + { + FilterDefinition filter = Builders.Filter.Empty; + if (userId != null) + filter &= Builders.Filter.Eq(b => b.UserId, userId); + else + filter &= Builders.Filter.Ne(b => b.UserId, null); + if (species != null) + filter &= Builders.Filter.Eq(b => b.Species, species); + if (form != null) + filter &= Builders.Filter.Eq(b=> b.Form, form); + if (source != null) + filter &= Builders.Filter.Eq(b => b.Source, source); + + return await Collection.Find(filter).ToListAsync(); + } + public async Task CountByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.CountDocumentsAsync(b => b.UserId == userId && b.Species == species); diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index ba7cf3a6..16585b77 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -41,6 +41,7 @@ public Task AddBadge( string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); + public Task> FindAllByCustom(string? userId=null, PkmnSpecies? species=null, Badge.BadgeForm? form=null, Badge.BadgeSource? source=null); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); public Task> CountByUserPerSpecies(string? userId); public Task HasUserBadge(string? userId, PkmnSpecies species); From 637b048a7ed64d5505ff41bfa3181b5e16e06dff Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Tue, 27 Apr 2021 05:12:41 -0400 Subject: [PATCH 04/19] add BadgeSourceParser --- .../TypeParsers/BadgeSourceParser.cs | 37 +++++++++++++++++++ TPP.Core/Setups.cs | 1 + 2 files changed, 38 insertions(+) create mode 100644 TPP.ArgsParsing/TypeParsers/BadgeSourceParser.cs diff --git a/TPP.ArgsParsing/TypeParsers/BadgeSourceParser.cs b/TPP.ArgsParsing/TypeParsers/BadgeSourceParser.cs new file mode 100644 index 00000000..a330c32c --- /dev/null +++ b/TPP.ArgsParsing/TypeParsers/BadgeSourceParser.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using TPP.Persistence.Models; + +namespace TPP.ArgsParsing.TypeParsers +{ + public class BadgeSourceParser : BaseArgumentParser + { + public override Task> Parse(IImmutableList args, Type[] genericTypes) + { + string source = args[0]; + ArgsParseResult result; + Badge.BadgeSource? parsedSource = null; + try + { + parsedSource = (Badge.BadgeSource)Enum.Parse(typeof(Badge.BadgeSource), source, ignoreCase: true); + } + catch (ArgumentException) + { + switch (args[1].ToLower()) + { + case "run": + case "caught": + parsedSource = Badge.BadgeSource.RunCaught; + break; + } + } + if (parsedSource != null) + result = ArgsParseResult.Success(parsedSource.Value, args.Skip(1).ToImmutableList()); + else + result = ArgsParseResult.Failure($"Did not find a source named '{args[0]}'"); + return Task.FromResult(result); + } + } +} diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index 107367cd..31e71388 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -39,6 +39,7 @@ public static ArgsParser SetUpArgsParser(IUserRepo userRepo, PokedexData pokedex argsParser.AddArgumentParser(new SignedTokensParser()); argsParser.AddArgumentParser(new PkmnSpeciesParser(pokedexData.KnownSpecies, PokedexData.NormalizeName)); argsParser.AddArgumentParser(new BadgeFormParser()); + argsParser.AddArgumentParser(new BadgeSourceParser()); argsParser.AddArgumentParser(new AnyOrderParser(argsParser)); argsParser.AddArgumentParser(new OneOfParser(argsParser)); From c7e3d718384e4d8a2ef57ef0b182c9a66c68712b Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 30 Apr 2021 09:45:01 -0400 Subject: [PATCH 05/19] Convert badgeform to int --- TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs | 1 + .../TypeParsers/BadgeFormParser.cs | 39 ++--------- TPP.ArgsParsing/Types/AnyOrder.cs | 22 ++++++ TPP.Common/PkmnForms.cs | 61 +++++++++++++++++ .../Commands/Definitions/BadgeCommandsTest.cs | 6 +- .../Definitions/OperatorCommandsTest.cs | 8 +-- .../Commands/Definitions/BadgeCommands.cs | 25 +++++-- .../Commands/Definitions/OperatorCommands.cs | 35 ++++++++-- .../Repos/BadgeRepoTest.cs | 68 +++++++++---------- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 6 +- .../Serializers/BadgeFormSerializer.cs | 26 ------- .../Serializers/CustomSerializers.cs | 1 - TPP.Persistence/Models/Badge.cs | 19 +----- TPP.Persistence/Repos/IBadgeRepo.cs | 4 +- 14 files changed, 189 insertions(+), 132 deletions(-) create mode 100644 TPP.Common/PkmnForms.cs delete mode 100644 TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs diff --git a/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs b/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs index b7ba4927..d01c5fcb 100644 --- a/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs +++ b/TPP.ArgsParsing/TypeParsers/AnyOrderParser.cs @@ -79,6 +79,7 @@ public override async Task> Parse( 3 => typeof(AnyOrder<,,>), 4 => typeof(AnyOrder<,,,>), 5 => typeof(AnyOrder<,,,,>), + 6 => typeof(AnyOrder<,,,,,>), var num => throw new InvalidOperationException( $"An implementation of {typeof(AnyOrder)} for {num} generic arguments " + "needs to be implemented and wired up where this exception is thrown. " + diff --git a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs index 056f1139..54ed83ef 100644 --- a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs +++ b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs @@ -2,51 +2,26 @@ using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; -using TPP.Persistence.Models; +using TPP.Common; namespace TPP.ArgsParsing.TypeParsers { /// /// A parser that finds a badge form by name. /// - public class BadgeFormParser : BaseArgumentParser + public class BadgeFormParser : BaseArgumentParser { - public override Task> Parse(IImmutableList args, Type[] genericTypes) + public override Task> Parse(IImmutableList args, Type[] genericTypes) { string form = args[0]; - ArgsParseResult result; + ArgsParseResult result; try { - Badge.BadgeForm parsedForm = (Badge.BadgeForm)Enum.Parse(typeof(Badge.BadgeForm), form, ignoreCase: true); - if (parsedForm == Badge.BadgeForm.Shiny) - { - switch (args[1].ToLower()) - { - case "shadow": - result = ArgsParseResult.Success(Badge.BadgeForm.ShinyShadow, args.Skip(2).ToImmutableList()); - break; - case "mega": - result = ArgsParseResult.Success(Badge.BadgeForm.ShinyMega, args.Skip(2).ToImmutableList()); - break; - case "alolan": - result = ArgsParseResult.Success(Badge.BadgeForm.ShinyAlolan, args.Skip(2).ToImmutableList()); - break; - case "galarian": - result = ArgsParseResult.Success(Badge.BadgeForm.ShinyGalarian, args.Skip(2).ToImmutableList()); - break; - default: - result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); - break; - } - } - else - { - result = ArgsParseResult.Success(parsedForm, args.Skip(1).ToImmutableList()); - } + result = ArgsParseResult.Success(1, args); } - catch (ArgumentException) + catch (ArgumentException e) { - result = ArgsParseResult.Failure($"Did not find a role named '{form}'"); + result = ArgsParseResult.Failure(e.Message); } return Task.FromResult(result); } diff --git a/TPP.ArgsParsing/Types/AnyOrder.cs b/TPP.ArgsParsing/Types/AnyOrder.cs index 035ff339..7f4c8a89 100644 --- a/TPP.ArgsParsing/Types/AnyOrder.cs +++ b/TPP.ArgsParsing/Types/AnyOrder.cs @@ -88,4 +88,26 @@ public AnyOrder(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) public void Deconstruct(out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5) => (item1, item2, item3, item4, item5) = (Item1, Item2, Item3, Item4, Item5); } + public class AnyOrder : AnyOrder + { + public T1 Item1 { get; } + public T2 Item2 { get; } + public T3 Item3 { get; } + public T4 Item4 { get; } + public T5 Item5 { get; } + public T6 Item6 { get; } + + public AnyOrder(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + } + + public void Deconstruct(out T1 item1, out T2 item2, out T3 item3, out T4 item4, out T5 item5, out T6 item6) => + (item1, item2, item3, item4, item5, item6) = (Item1, Item2, Item3, Item4, Item5, Item6); + } } diff --git a/TPP.Common/PkmnForms.cs b/TPP.Common/PkmnForms.cs new file mode 100644 index 00000000..7e9ce98c --- /dev/null +++ b/TPP.Common/PkmnForms.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using TPP.Common; + +namespace TPP.Common +{ + public static class PkmnForms + { + /// + /// + /// + static readonly Dictionary> Forms = new Dictionary> + { + ["unown"] = new Dictionary + { + ["a"] = 1, + ["b"] = 2, + }, + ["shellos"] = new Dictionary + { + ["west sea"] = 1, + ["west"] = 1, + ["pink"] = 1, + ["east sea"] = 2, + ["east"] = 2, + ["blue"] = 2, + }, + }; + + public static string getFormName(PkmnSpecies pokemon, int formid) + { + string pkmnName = pokemon.Name.ToLower(); + Dictionary? forms; + if (!Forms.TryGetValue(pkmnName, out forms)) + throw new ArgumentException($"{pokemon.Name} does not have alternate forms."); + string formName = forms.FirstOrDefault(p => p.Value == formid).Key; + if (formName == null) + throw new ArgumentException($"{pokemon.Name} does not have a form with id {formid}."); + return formName; + } + + public static int getFormId(PkmnSpecies pokemon, string formName) + { + string pkmnName = pokemon.Name.ToLower(); + Dictionary? forms; + if (!Forms.TryGetValue(pkmnName, out forms)) + throw new ArgumentException($"{pokemon.Name} does not have alternate forms."); + int formid = forms.GetValueOrDefault(formName.ToLower()); + if (formid == 0) + throw new ArgumentException($"{pokemon.Name} does not have a form called {formName}."); + return formid; + } + + public static bool pokemonHasForms(PkmnSpecies pokemon) + { + return Forms.ContainsKey(pokemon.Name.ToLower()); + } + } +} diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index dbfaf91b..14240d55 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -264,9 +264,9 @@ public async Task TestGiftBadgeSuccessful() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); - Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); - Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index 4250bbf5..c27921eb 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -158,9 +158,9 @@ public async Task TestTransferBadgeSuccessful() _messageSenderMock.Object, _badgeRepoMock.Object); _userRepoMock.Setup(repo => repo.FindBySimpleName("gifter")).Returns(Task.FromResult((User?)gifter)); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); - Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); - Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, Badge.BadgeForm.Normal); + Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(gifter.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -259,7 +259,7 @@ public async Task TestCreateBadge() Assert.AreEqual("123 Normal #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 0, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/BadgeCommands.cs b/TPP.Core/Commands/Definitions/BadgeCommands.cs index d3af33cf..30589dff 100644 --- a/TPP.Core/Commands/Definitions/BadgeCommands.cs +++ b/TPP.Core/Commands/Definitions/BadgeCommands.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -271,12 +272,28 @@ public async Task Pokedex(CommandContext context) public async Task GiftBadge(CommandContext context) { User gifter = context.Message.User; - (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional sourceOpt) = - await context.ParseArgs, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional formOpt, Optional formOpt2) = + await context.ParseArgs, Optional, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); - Badge.BadgeForm? form = formOpt.IsPresent ? formOpt.Value : null; Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; - + int? form = null; + if (formOpt.IsPresent) + { + string formName = formOpt.Value; + if (formOpt2.IsPresent) + formName += " " + formOpt2.Value; + try + { + form = PkmnForms.getFormId(species, formName); + } + catch (ArgumentException e) + { + return new CommandResult + { + Response = e.Message + }; + } + } if (recipient == gifter) return new CommandResult { Response = "You cannot gift to yourself" }; diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index e286cfd4..c6e1d42c 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -183,19 +184,39 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt) = - await context.ParseArgs, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional formOpt2) = + await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); - Badge.BadgeForm form = formOpt.IsPresent ? formOpt.Value : Badge.BadgeForm.Normal; - + int form = PkmnForms.pokemonHasForms(species) ? 0 : 1; // default to the first listed form if form is unspecified + if (formOpt.IsPresent) + { + string formName = formOpt.Value; + if (formOpt2.IsPresent) + formName += " " + formOpt2.Value; + try + { + form = PkmnForms.getFormId(species, formName); + } + catch (ArgumentException e) + { + return new CommandResult + { + Response = e.Message + }; + } + } for (int i = 0; i < amount; i++) await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form); return new CommandResult { - Response = amount > 1 - ? $"{amount} {form} {species} badges created for {recipient.Name}." - : $"{form} {species} badge created for {recipient.Name}." + Response = PkmnForms.pokemonHasForms(species) + ? amount > 1 + ? $"{amount} {PkmnForms.getFormName(species, form)} {species} badges created for {recipient.Name}." + : $"{PkmnForms.getFormName(species, form)} {species} badge created for {recipient.Name}." + : amount > 1 + ? $"{amount} {species} badges created for {recipient.Name}." + : $"{species} badge created for {recipient.Name}." }; } } diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 666c3e79..989c2d14 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -24,7 +24,7 @@ public async Task insert_then_read_are_equal() { BadgeRepo badgeRepo = CreateBadgeRepo(); // when - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0); // then Assert.AreNotEqual(string.Empty, badge.Id); @@ -43,7 +43,7 @@ public async Task insert_sets_current_timestamp_as_creation_date() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), clockMock.Object); - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0); Assert.AreEqual(createdAt, badge.CreatedAt); } @@ -57,7 +57,7 @@ public async Task has_expected_bson_datatypes() BadgeRepo badgeRepo = CreateBadgeRepo(); // when PkmnSpecies randomSpecies = PkmnSpecies.OfId("9001"); - Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, Badge.BadgeForm.Normal); + Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, 0); // then IMongoCollection badgesCollectionBson = @@ -74,10 +74,10 @@ public async Task can_find_by_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); + Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, 0); // when List resultUserA = await badgeRepo.FindByUser("userA"); @@ -95,13 +95,13 @@ public async Task can_count_by_user_and_species() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); // when long countHasNone = await badgeRepo.CountByUserAndSpecies("user", PkmnSpecies.OfId("1")); @@ -119,13 +119,13 @@ public async Task can_count_per_species_for_one_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); // when ImmutableSortedDictionary result = await badgeRepo.CountByUserPerSpecies("user"); @@ -144,12 +144,12 @@ public async Task can_check_if_user_has_badge() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, Badge.BadgeForm.Normal); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); // when bool hasUserSpecies1 = await badgeRepo.HasUserBadge("user", PkmnSpecies.OfId("1")); @@ -173,7 +173,7 @@ public async Task returns_updated_badge_object() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); IImmutableList updatedBadges = await badgeRepo.TransferBadges( ImmutableList.Create(badge), "recipient", "reason", new Dictionary()); @@ -192,7 +192,7 @@ public async Task unmarks_as_selling() { BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge.Id), Builders.Update @@ -218,7 +218,7 @@ public async Task logs_to_badgelog() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, clockMock.Object); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); Instant timestamp = Instant.FromUnixTimeSeconds(123); clockMock.Setup(c => c.GetCurrentInstant()).Returns(timestamp); @@ -237,8 +237,8 @@ public async Task triggers_species_lost_event() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); int userLostBadgeInvocations = 0; badgeRepo.UserLostBadgeSpecies += (_, args) => { @@ -261,8 +261,8 @@ public async Task aborts_all_transfers_if_one_fails() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, Badge.BadgeForm.Normal); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); // make in-memory badge reference stale to cause the transfer to fail on the second badge await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge2.Id), diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 9bcc827f..3b6f286c 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -65,7 +65,7 @@ private void InitIndexes() } public async Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null) + string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, Instant? createdAt = null) { var badge = new Badge( id: string.Empty, @@ -86,7 +86,7 @@ public async Task> FindByUser(string? userId) => public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); - public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, Badge.BadgeForm? form = null, Badge.BadgeSource? source = null) + public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -96,7 +96,7 @@ public async Task> FindAllByCustom(string? userId = null, PkmnSpecie if (species != null) filter &= Builders.Filter.Eq(b => b.Species, species); if (form != null) - filter &= Builders.Filter.Eq(b=> b.Form, form); + filter &= Builders.Filter.Eq(b => b.Form, form); if (source != null) filter &= Builders.Filter.Eq(b => b.Source, source); diff --git a/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs b/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs deleted file mode 100644 index a653e8da..00000000 --- a/TPP.Persistence.MongoDB/Serializers/BadgeFormSerializer.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using TPP.Persistence.Models; - -namespace TPP.Persistence.MongoDB.Serializers -{ - class BadgeFormSerializer : EnumToStringUsingTranslationMappingSerializer - { - public static readonly BadgeFormSerializer Instance = new BadgeFormSerializer(); - - private BadgeFormSerializer() : base(new Dictionary - { - [Badge.BadgeForm.Normal] = "normal", - [Badge.BadgeForm.Shiny] = "shiny", - [Badge.BadgeForm.Shadow] = "shadow", - [Badge.BadgeForm.Mega] = "mega", - [Badge.BadgeForm.Alolan] = "alolan", - [Badge.BadgeForm.Galarian] = "galarian", - [Badge.BadgeForm.ShinyShadow] = "shiny_shadow", - [Badge.BadgeForm.ShinyMega] = "shiny_mega", - [Badge.BadgeForm.ShinyAlolan] = "shiny_alolan", - [Badge.BadgeForm.ShinyGalarian] = "shiny_galarian", - }) - { - } - } -} diff --git a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs index 1d33110b..b6078715 100644 --- a/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs +++ b/TPP.Persistence.MongoDB/Serializers/CustomSerializers.cs @@ -15,7 +15,6 @@ public static void RegisterAll() if (_registered) return; _registered = true; BsonSerializer.RegisterSerializer(BadgeSourceSerializer.Instance); - BsonSerializer.RegisterSerializer(BadgeFormSerializer.Instance); BsonSerializer.RegisterSerializer(PkmnSpeciesSerializer.Instance); BsonSerializer.RegisterSerializer(InstantSerializer.Instance); BsonSerializer.RegisterSerializer(NullableInstantSerializer.Instance); diff --git a/TPP.Persistence/Models/Badge.cs b/TPP.Persistence/Models/Badge.cs index 90df30b4..cd05ff41 100644 --- a/TPP.Persistence/Models/Badge.cs +++ b/TPP.Persistence/Models/Badge.cs @@ -44,23 +44,10 @@ public enum BadgeSource /// public Instant CreatedAt { get; init; } - public enum BadgeForm - { - Normal, - Shiny, - Shadow, - Mega, - Alolan, - Galarian, - ShinyShadow, - ShinyMega, - ShinyAlolan, - ShinyGalarian - } /// - /// What form of the pokemon this badge is. + /// If this pokemon has multiple forms, which form it is. /// - public BadgeForm Form { get; init; } + public int Form { get; init; } /// If this badge is on sale, for how much. public long? SellPrice { get; init; } @@ -73,7 +60,7 @@ public Badge( PkmnSpecies species, BadgeSource source, Instant createdAt, - BadgeForm form) + int form) { Id = id; UserId = userId; diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index 16585b77..c9e50027 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -38,10 +38,10 @@ public OwnedBadgeNotFoundException(Badge badge) : public interface IBadgeRepo { public Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, Badge.BadgeForm form, Instant? createdAt = null); + string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); - public Task> FindAllByCustom(string? userId=null, PkmnSpecies? species=null, Badge.BadgeForm? form=null, Badge.BadgeSource? source=null); + public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); public Task> CountByUserPerSpecies(string? userId); public Task HasUserBadge(string? userId, PkmnSpecies species); From f706dbf73a20382d85e21f3c23f29eec93ec0d99 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Thu, 1 Jul 2021 05:16:49 -0400 Subject: [PATCH 06/19] Add shiny field to badges --- .../TypeParsers/BadgeFormParser.cs | 29 -------- TPP.ArgsParsing/TypeParsers/ShinyParser.cs | 44 ++++++++++++ TPP.Common/PkmnForms.cs | 4 +- .../Commands/Definitions/BadgeCommandsTest.cs | 6 +- .../Definitions/OperatorCommandsTest.cs | 8 +-- .../Commands/Definitions/OperatorCommands.cs | 9 ++- TPP.Core/Setups.cs | 2 +- .../Repos/BadgeRepoTest.cs | 68 +++++++++---------- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 7 +- TPP.Persistence/Models/Badge.cs | 9 ++- TPP.Persistence/Repos/IBadgeRepo.cs | 2 +- 11 files changed, 107 insertions(+), 81 deletions(-) delete mode 100644 TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs create mode 100644 TPP.ArgsParsing/TypeParsers/ShinyParser.cs diff --git a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs b/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs deleted file mode 100644 index 54ed83ef..00000000 --- a/TPP.ArgsParsing/TypeParsers/BadgeFormParser.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using TPP.Common; - -namespace TPP.ArgsParsing.TypeParsers -{ - /// - /// A parser that finds a badge form by name. - /// - public class BadgeFormParser : BaseArgumentParser - { - public override Task> Parse(IImmutableList args, Type[] genericTypes) - { - string form = args[0]; - ArgsParseResult result; - try - { - result = ArgsParseResult.Success(1, args); - } - catch (ArgumentException e) - { - result = ArgsParseResult.Failure(e.Message); - } - return Task.FromResult(result); - } - } -} diff --git a/TPP.ArgsParsing/TypeParsers/ShinyParser.cs b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs new file mode 100644 index 00000000..76220409 --- /dev/null +++ b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using TPP.Common; + +namespace TPP.ArgsParsing.TypeParsers +{ + /// + /// A parser that finds a badge form by name. + /// + public class ShinyParser : BaseArgumentParser + { + string[] shinyWords = + { + "shiny", + "shiny:true" + }; + string[] plainWords = + { + "plain", + "regular", + "shiny:false" + }; + public override Task> Parse(IImmutableList args, Type[] genericTypes) + { + string s = args[0]; + ArgsParseResult result; + if (shinyWords.Contains(s)) + { + result = ArgsParseResult.Success(true, args.Skip(1).ToImmutableList()); + } + else if (plainWords.Contains(s)) + { + result = ArgsParseResult.Success(false, args.Skip(1).ToImmutableList()); + } + else + { + result = ArgsParseResult.Failure("The argument couldn't be understood as shiny or not", ErrorRelevanceConfidence.Unlikely); + } + return Task.FromResult(result); + } + } +} diff --git a/TPP.Common/PkmnForms.cs b/TPP.Common/PkmnForms.cs index 7e9ce98c..eed3fd51 100644 --- a/TPP.Common/PkmnForms.cs +++ b/TPP.Common/PkmnForms.cs @@ -21,9 +21,11 @@ public static class PkmnForms ["shellos"] = new Dictionary { ["west sea"] = 1, + ["westsea"] = 1, ["west"] = 1, ["pink"] = 1, ["east sea"] = 2, + ["eastsea"] = 2, ["east"] = 2, ["blue"] = 2, }, @@ -31,7 +33,7 @@ public static class PkmnForms public static string getFormName(PkmnSpecies pokemon, int formid) { - string pkmnName = pokemon.Name.ToLower(); + string pkmnName = pokemon.Name.ToLower(); //TODO: use normalize_name from pkmnspecies Dictionary? forms; if (!Forms.TryGetValue(pkmnName, out forms)) throw new ArgumentException($"{pokemon.Name} does not have alternate forms."); diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index 14240d55..0b8b090a 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -264,9 +264,9 @@ public async Task TestGiftBadgeSuccessful() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); - Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); - Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index c27921eb..8fa9d2f7 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -158,9 +158,9 @@ public async Task TestTransferBadgeSuccessful() _messageSenderMock.Object, _badgeRepoMock.Object); _userRepoMock.Setup(repo => repo.FindBySimpleName("gifter")).Returns(Task.FromResult((User?)gifter)); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); - Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); - Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0); + Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(gifter.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -259,7 +259,7 @@ public async Task TestCreateBadge() Assert.AreEqual("123 Normal #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 0, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 0, false, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index c6e1d42c..3a03d5f9 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -184,15 +184,14 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional formOpt2) = - await context.ParseArgs, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional shinyOpt) = + await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); int form = PkmnForms.pokemonHasForms(species) ? 0 : 1; // default to the first listed form if form is unspecified + bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; if (formOpt.IsPresent) { string formName = formOpt.Value; - if (formOpt2.IsPresent) - formName += " " + formOpt2.Value; try { form = PkmnForms.getFormId(species, formName); @@ -206,7 +205,7 @@ public async Task CreateBadge(CommandContext context) } } for (int i = 0; i < amount; i++) - await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form); + await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form, shiny); return new CommandResult { diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index 31e71388..4e07c5bf 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -38,7 +38,7 @@ public static ArgsParser SetUpArgsParser(IUserRepo userRepo, PokedexData pokedex argsParser.AddArgumentParser(new SignedPokeyenParser()); argsParser.AddArgumentParser(new SignedTokensParser()); argsParser.AddArgumentParser(new PkmnSpeciesParser(pokedexData.KnownSpecies, PokedexData.NormalizeName)); - argsParser.AddArgumentParser(new BadgeFormParser()); + argsParser.AddArgumentParser(new ShinyParser()); argsParser.AddArgumentParser(new BadgeSourceParser()); argsParser.AddArgumentParser(new AnyOrderParser(argsParser)); diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 989c2d14..f1bbcadc 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -24,7 +24,7 @@ public async Task insert_then_read_are_equal() { BadgeRepo badgeRepo = CreateBadgeRepo(); // when - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0, false); // then Assert.AreNotEqual(string.Empty, badge.Id); @@ -43,7 +43,7 @@ public async Task insert_sets_current_timestamp_as_creation_date() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), clockMock.Object); - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0, false); Assert.AreEqual(createdAt, badge.CreatedAt); } @@ -57,7 +57,7 @@ public async Task has_expected_bson_datatypes() BadgeRepo badgeRepo = CreateBadgeRepo(); // when PkmnSpecies randomSpecies = PkmnSpecies.OfId("9001"); - Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, 0); + Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, 0, false); // then IMongoCollection badgesCollectionBson = @@ -74,10 +74,10 @@ public async Task can_find_by_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); - Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, 0); + Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, 0, false); // when List resultUserA = await badgeRepo.FindByUser("userA"); @@ -95,13 +95,13 @@ public async Task can_count_by_user_and_species() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); // when long countHasNone = await badgeRepo.CountByUserAndSpecies("user", PkmnSpecies.OfId("1")); @@ -119,13 +119,13 @@ public async Task can_count_per_species_for_one_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); // when ImmutableSortedDictionary result = await badgeRepo.CountByUserPerSpecies("user"); @@ -144,12 +144,12 @@ public async Task can_check_if_user_has_badge() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); // when bool hasUserSpecies1 = await badgeRepo.HasUserBadge("user", PkmnSpecies.OfId("1")); @@ -173,7 +173,7 @@ public async Task returns_updated_badge_object() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); IImmutableList updatedBadges = await badgeRepo.TransferBadges( ImmutableList.Create(badge), "recipient", "reason", new Dictionary()); @@ -192,7 +192,7 @@ public async Task unmarks_as_selling() { BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge.Id), Builders.Update @@ -218,7 +218,7 @@ public async Task logs_to_badgelog() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, clockMock.Object); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); Instant timestamp = Instant.FromUnixTimeSeconds(123); clockMock.Setup(c => c.GetCurrentInstant()).Returns(timestamp); @@ -237,8 +237,8 @@ public async Task triggers_species_lost_event() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); int userLostBadgeInvocations = 0; badgeRepo.UserLostBadgeSpecies += (_, args) => { @@ -261,8 +261,8 @@ public async Task aborts_all_transfers_if_one_fails() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); // make in-memory badge reference stale to cause the transfer to fail on the second badge await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge2.Id), diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 3b6f286c..197fc3fa 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -41,6 +41,8 @@ static BadgeRepo() .SetIgnoreIfNull(true); cm.MapProperty(b => b.SellingSince).SetElementName("selling_since") .SetIgnoreIfNull(true); + cm.MapProperty(b => b.Shiny).SetElementName("shiny") + .SetDefaultValue(false); }); } @@ -65,7 +67,7 @@ private void InitIndexes() } public async Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, Instant? createdAt = null) + string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null) { var badge = new Badge( id: string.Empty, @@ -73,7 +75,8 @@ public async Task AddBadge( species: species, source: source, createdAt: createdAt ?? _clock.GetCurrentInstant(), - form: form + form: form, + shiny: shiny ); await Collection.InsertOneAsync(badge); Debug.Assert(badge.Id.Length > 0, "The MongoDB driver injected a generated ID"); diff --git a/TPP.Persistence/Models/Badge.cs b/TPP.Persistence/Models/Badge.cs index cd05ff41..3a38ea1e 100644 --- a/TPP.Persistence/Models/Badge.cs +++ b/TPP.Persistence/Models/Badge.cs @@ -49,6 +49,11 @@ public enum BadgeSource /// public int Form { get; init; } + /// + /// If this badge is shiny. + /// + public bool Shiny { get; init; } + /// If this badge is on sale, for how much. public long? SellPrice { get; init; } /// If this badge is on sale, since when. @@ -60,7 +65,8 @@ public Badge( PkmnSpecies species, BadgeSource source, Instant createdAt, - int form) + int form, + bool shiny) { Id = id; UserId = userId; @@ -68,6 +74,7 @@ public Badge( Source = source; CreatedAt = createdAt; Form = form; + Shiny = shiny; } public override string ToString() => $"Badge({Species}@{UserId ?? ""})"; diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index c9e50027..e5722af5 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -38,7 +38,7 @@ public OwnedBadgeNotFoundException(Badge badge) : public interface IBadgeRepo { public Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, Instant? createdAt = null); + string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null); From 3d867e1fd54a88db41414988cc277681d7d1a1ef Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 2 Jul 2021 01:32:45 -0400 Subject: [PATCH 07/19] fix issues found in code review --- TPP.ArgsParsing/TypeParsers/ShinyParser.cs | 15 ++++++++------- TPP.ArgsParsing/Types/ImplicitBoolean.cs | 19 +++++++++++++++++++ .../Commands/Definitions/OperatorCommands.cs | 6 +++--- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 6 ++++-- TPP.Persistence/Repos/IBadgeRepo.cs | 2 +- 5 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 TPP.ArgsParsing/Types/ImplicitBoolean.cs diff --git a/TPP.ArgsParsing/TypeParsers/ShinyParser.cs b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs index 76220409..4d648473 100644 --- a/TPP.ArgsParsing/TypeParsers/ShinyParser.cs +++ b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs @@ -3,13 +3,14 @@ using System.Linq; using System.Threading.Tasks; using TPP.Common; +using TPP.ArgsParsing.Types; namespace TPP.ArgsParsing.TypeParsers { /// - /// A parser that finds a badge form by name. + /// A parser that determines if something is indicated to be shiny or not. /// - public class ShinyParser : BaseArgumentParser + public class ShinyParser : BaseArgumentParser { string[] shinyWords = { @@ -22,21 +23,21 @@ public class ShinyParser : BaseArgumentParser "regular", "shiny:false" }; - public override Task> Parse(IImmutableList args, Type[] genericTypes) + public override Task> Parse(IImmutableList args, Type[] genericTypes) { string s = args[0]; - ArgsParseResult result; + ArgsParseResult result; if (shinyWords.Contains(s)) { - result = ArgsParseResult.Success(true, args.Skip(1).ToImmutableList()); + result = ArgsParseResult.Success(new Shiny { Value = true }, args.Skip(1).ToImmutableList()); } else if (plainWords.Contains(s)) { - result = ArgsParseResult.Success(false, args.Skip(1).ToImmutableList()); + result = ArgsParseResult.Success(new Shiny { Value = false }, args.Skip(1).ToImmutableList()); } else { - result = ArgsParseResult.Failure("The argument couldn't be understood as shiny or not", ErrorRelevanceConfidence.Unlikely); + result = ArgsParseResult.Failure("The argument couldn't be understood as shiny or not", ErrorRelevanceConfidence.Unlikely); } return Task.FromResult(result); } diff --git a/TPP.ArgsParsing/Types/ImplicitBoolean.cs b/TPP.ArgsParsing/Types/ImplicitBoolean.cs new file mode 100644 index 00000000..e88de250 --- /dev/null +++ b/TPP.ArgsParsing/Types/ImplicitBoolean.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TPP.ArgsParsing.Types +{ + public class ImplicitBoolean + { + public bool Value { get; internal init; } + public static implicit operator bool(ImplicitBoolean b) => b.Value; + public override string ToString() => Value.ToString(); + } + + public class Shiny : ImplicitBoolean + { + } +} diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index 3a03d5f9..fca49ec1 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -184,11 +184,11 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional shinyOpt) = - await context.ParseArgs, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional shinyOpt) = + await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); int form = PkmnForms.pokemonHasForms(species) ? 0 : 1; // default to the first listed form if form is unspecified - bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; + bool shiny = shinyOpt.Value ?? false; if (formOpt.IsPresent) { string formName = formOpt.Value; diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 197fc3fa..203850b7 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -42,7 +42,8 @@ static BadgeRepo() cm.MapProperty(b => b.SellingSince).SetElementName("selling_since") .SetIgnoreIfNull(true); cm.MapProperty(b => b.Shiny).SetElementName("shiny") - .SetDefaultValue(false); + .SetDefaultValue(false) + .SetIgnoreIfDefault(true); }); } @@ -89,7 +90,7 @@ public async Task> FindByUser(string? userId) => public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); - public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null) + public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = false) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -102,6 +103,7 @@ public async Task> FindAllByCustom(string? userId = null, PkmnSpecie filter &= Builders.Filter.Eq(b => b.Form, form); if (source != null) filter &= Builders.Filter.Eq(b => b.Source, source); + filter &= Builders.Filter.Eq(b => b.Shiny, shiny); // always assigned, defaults to false return await Collection.Find(filter).ToListAsync(); } diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index e5722af5..26ed5f20 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -41,7 +41,7 @@ public Task AddBadge( string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null); public Task> FindByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); - public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null); + public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = false); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); public Task> CountByUserPerSpecies(string? userId); public Task HasUserBadge(string? userId, PkmnSpecies species); From 4123a3e849e6eddee972ad86caf58e3ff9dffa36 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Thu, 2 Sep 2021 16:20:27 -0400 Subject: [PATCH 08/19] Implement badge selling --- .../Commands/Definitions/BadgeCommandsTest.cs | 26 ++++++- .../Definitions/OperatorCommandsTest.cs | 4 +- .../Commands/Definitions/BadgeCommands.cs | 71 +++++++++++++++++-- .../Commands/Definitions/OperatorCommands.cs | 2 +- .../Repos/BadgeRepoTest.cs | 32 ++++++++- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 26 ++++++- TPP.Persistence.MongoDB/Repos/UserRepo.cs | 3 + TPP.Persistence/Repos/IBadgeRepo.cs | 7 +- TPP.Persistence/Repos/IUserRepo.cs | 1 + 9 files changed, 154 insertions(+), 18 deletions(-) diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index 0b8b090a..d0321570 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -267,7 +267,7 @@ public async Task TestGiftBadgeSuccessful() Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); - _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, species, null, null, false)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); CommandResult result = await _badgeCommands.GiftBadge(new CommandContext(MockMessage(user), @@ -291,7 +291,7 @@ public async Task TestGiftBadgeNotOwned() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(user.Id, species)) + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, species, null, null, false)) .Returns(Task.FromResult(new List())); CommandResult result = await _badgeCommands.GiftBadge(new CommandContext(MockMessage(user), @@ -306,5 +306,27 @@ public async Task TestGiftBadgeNotOwned() It.IsAny>()), Times.Never); } + + [Test] + public async Task ListSellBadge_lists_callers_badges_when_user_isnt_specified() + { + User user1 = MockUser("u1"); + PkmnSpecies speciesA = PkmnSpecies.RegisterName("1", "a"); + Badge badgeA = new("badgeA", user1.Id, speciesA, Badge.BadgeSource.Pinball, Instant.MinValue, 0, false) {SellPrice = 1 }; + + _badgeRepoMock.Setup(repo => repo.FindAllForSaleByCustom(user1.Id, speciesA, null, null, false)).Returns(Task.FromResult(new List() { badgeA })); + _userRepoMock.Setup(repo => repo.FindById(user1.Id)).Returns(Task.FromResult((User?)user1)); + + _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { speciesA })); + _argsParser.AddArgumentParser(new ShinyParser()); + _argsParser.AddArgumentParser(new BadgeSourceParser()); + _argsParser.AddArgumentParser(new StringParser()); + + await _badgeRepoMock.Object.SetBadgeSellPrice(badgeA, 1); + + CommandResult result = await _badgeCommands.ListSellBadge(new CommandContext(MockMessage(user1), ImmutableList.Create("a"), _argsParser)); + + Assert.AreEqual("1 badges found:\na sold by u1 for T1", result.Response); + } } } diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index 8fa9d2f7..4c7147e2 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -256,10 +256,10 @@ public async Task TestCreateBadge() CommandResult result = await operatorCommands.CreateBadge(new CommandContext(MockMessage(user), ImmutableList.Create("recipient", "species", "123"), _argsParser)); - Assert.AreEqual("123 Normal #001 Species badges created for Recipient.", result.Response); + Assert.AreEqual("123 #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 0, false, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 1, false, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/BadgeCommands.cs b/TPP.Core/Commands/Definitions/BadgeCommands.cs index 30589dff..e43f1711 100644 --- a/TPP.Core/Commands/Definitions/BadgeCommands.cs +++ b/TPP.Core/Commands/Definitions/BadgeCommands.cs @@ -51,6 +51,11 @@ public class BadgeCommands : ICommandCollection Description = "Gift a badge you own to another user with no price. Arguments: (Optional) " //TODO also update this before merge }, + new Command("listsellbadge", ListSellBadge) + { + Description = + "List the badges someone is selling. Arguments: (optional) (optional) (optional)
(optional)" + } }; private readonly IBadgeRepo _badgeRepo; @@ -272,16 +277,15 @@ public async Task Pokedex(CommandContext context) public async Task GiftBadge(CommandContext context) { User gifter = context.Message.User; - (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional formOpt, Optional formOpt2) = - await context.ParseArgs, Optional, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = + await context.ParseArgs, Optional, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; int? form = null; + bool? shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; if (formOpt.IsPresent) { string formName = formOpt.Value; - if (formOpt2.IsPresent) - formName += " " + formOpt2.Value; try { form = PkmnForms.getFormId(species, formName); @@ -297,7 +301,7 @@ public async Task GiftBadge(CommandContext context) if (recipient == gifter) return new CommandResult { Response = "You cannot gift to yourself" }; - List badges = await _badgeRepo.FindAllByCustom(gifter.Id, species, form, source); + List badges = await _badgeRepo.FindAllByCustom(gifter.Id, species, form, source, shiny); if (badges.Count < amount) //TODO big improve before merge return new CommandResult @@ -320,5 +324,62 @@ await _messageSender.SendWhisper(recipient, amount > 1 ResponseTarget = ResponseTarget.Chat }; } + + public async Task ListSellBadge(CommandContext context) + { + (Optional userOpt, PkmnSpecies species, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = + await context.ParseArgs, PkmnSpecies, Optional, Optional, Optional>>(); + + User user = userOpt.IsPresent ? userOpt.Value : context.Message.User; + Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; + bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; + int? form = null; + if (formOpt.IsPresent) + { + string formName = formOpt.Value; + try + { + form = PkmnForms.getFormId(species, formName); + } + catch (ArgumentException e) + { + return new CommandResult + { + Response = e.Message + }; + } + } + + List forSale = await _badgeRepo.FindAllForSaleByCustom(user.Id, species, form, source, shiny); + + string response; + if (forSale.Count == 0) + response = "No badges found."; + else + { + bool hasForms = PkmnForms.pokemonHasForms(species); + response = string.Format("{0} badges found:", forSale.Count); + foreach (Badge b in forSale) + { + if (b.UserId == null) + throw new OwnedBadgeNotFoundException(b); + User? seller = await _userRepo.FindById(b.UserId); + if (seller == null) + throw new OwnedBadgeNotFoundException(b); + + response += string.Format("\n{0}{1}{2} sold by {3} for T{4}", + shiny ? "Shiny " : "", + hasForms ? PkmnForms.getFormName(b.Species, b.Form) : "", + b.Species.Name, + seller.SimpleName, + b.SellPrice); + } + } + + return new CommandResult + { + Response = response, + }; + } } } diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index fca49ec1..acd0019f 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -188,7 +188,7 @@ public async Task CreateBadge(CommandContext context) await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); int form = PkmnForms.pokemonHasForms(species) ? 0 : 1; // default to the first listed form if form is unspecified - bool shiny = shinyOpt.Value ?? false; + bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; if (formOpt.IsPresent) { string formName = formOpt.Value; diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index f1bbcadc..5b2fe447 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -80,9 +80,9 @@ public async Task can_find_by_user() Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, 0, false); // when - List resultUserA = await badgeRepo.FindByUser("userA"); - List resultUserB = await badgeRepo.FindByUser("userB"); - List resultNobody = await badgeRepo.FindByUser(null); + List resultUserA = await badgeRepo.FindAllByUser("userA"); + List resultUserB = await badgeRepo.FindAllByUser("userB"); + List resultNobody = await badgeRepo.FindAllByUser(null); // then Assert.AreEqual(new List { badgeUserA1, badgeUserA2 }, resultUserA); @@ -164,6 +164,32 @@ public async Task can_check_if_user_has_badge() Assert.IsFalse(hasUserSpecies4); } + [Test] + public async Task can_set_badge_sell_price() + { + IBadgeRepo badgeRepo = CreateBadgeRepo(); + Badge badge = await badgeRepo.AddBadge("user", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + + Badge forSale = await badgeRepo.SetBadgeSellPrice(badge, 10); + + Assert.AreEqual(10, forSale.SellPrice); + } + + [Test] + public async Task FindAllForSaleByCustom_only_returns_badges_for_sale() + { + IBadgeRepo badgeRepo = CreateBadgeRepo(); + PkmnSpecies species = PkmnSpecies.OfId("1"); + Badge notForSale = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, 0, false); + + Badge forSale = await badgeRepo.SetBadgeSellPrice(notForSale, 1); + + List saleList = await badgeRepo.FindAllForSaleByCustom(null, species, null, null, null); + + Assert.AreEqual(new List() { forSale }, saleList); + } + [TestFixture] private class TransferBadge : MongoTestBase { diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 203850b7..ce565a58 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -84,13 +84,13 @@ public async Task AddBadge( return badge; } - public async Task> FindByUser(string? userId) => + public async Task> FindAllByUser(string? userId) => await Collection.Find(b => b.UserId == userId).ToListAsync(); public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); - public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = false) + public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = null) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -103,11 +103,18 @@ public async Task> FindAllByCustom(string? userId = null, PkmnSpecie filter &= Builders.Filter.Eq(b => b.Form, form); if (source != null) filter &= Builders.Filter.Eq(b => b.Source, source); - filter &= Builders.Filter.Eq(b => b.Shiny, shiny); // always assigned, defaults to false + if (shiny != null) + filter &= Builders.Filter.Eq(b => b.Shiny, shiny); return await Collection.Find(filter).ToListAsync(); } + public async Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny) + { + List all = await FindAllByCustom(userId, species, form, source, shiny); + return all.Where(b => b.SellPrice > 0).ToList(); + } + public async Task CountByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.CountDocumentsAsync(b => b.UserId == userId && b.Species == species); @@ -181,5 +188,18 @@ await _badgeLogRepo.LogWithSession( } return updatedBadges.ToImmutableList(); } + + public async Task SetBadgeSellPrice(Badge badge, int price) + { + if (price <= 0) + throw new ArgumentOutOfRangeException("price", "Price must be positive"); + return await Collection.FindOneAndUpdateAsync( + Builders.Filter + .Where(b => b.Id == badge.Id && b.UserId == badge.UserId), + Builders.Update + .Set(b => b.SellPrice, price), + new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, IsUpsert = false } + ) ?? throw new OwnedBadgeNotFoundException(badge); + } } } diff --git a/TPP.Persistence.MongoDB/Repos/UserRepo.cs b/TPP.Persistence.MongoDB/Repos/UserRepo.cs index bfe92557..430509dc 100644 --- a/TPP.Persistence.MongoDB/Repos/UserRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/UserRepo.cs @@ -137,6 +137,9 @@ public async Task RecordUser(UserInfo userInfo) public async Task FindByDisplayName(string displayName) => await Collection.Find(u => u.TwitchDisplayName == displayName).FirstOrDefaultAsync(); + public async Task FindById(string userId) => + await Collection.Find(u => u.Id == userId).FirstOrDefaultAsync(); + private async Task UpdateField(User user, Expression> field, T value) => await Collection.FindOneAndUpdateAsync( filter: u => u.Id == user.Id, diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index 26ed5f20..7c3a18a7 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -39,9 +39,9 @@ public interface IBadgeRepo { public Task AddBadge( string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null); - public Task> FindByUser(string? userId); + public Task> FindAllByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); - public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = false); + public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = null); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); public Task> CountByUserPerSpecies(string? userId); public Task HasUserBadge(string? userId, PkmnSpecies species); @@ -51,5 +51,8 @@ public Task AddBadge( public Task> TransferBadges( IImmutableList badges, string? recipientUserId, string reason, IDictionary additionalData); + + public Task SetBadgeSellPrice(Badge badge, int price); + public Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny); } } diff --git a/TPP.Persistence/Repos/IUserRepo.cs b/TPP.Persistence/Repos/IUserRepo.cs index 797fa9af..caf1e72b 100644 --- a/TPP.Persistence/Repos/IUserRepo.cs +++ b/TPP.Persistence/Repos/IUserRepo.cs @@ -9,6 +9,7 @@ public interface IUserRepo public Task RecordUser(UserInfo userInfo); public Task FindBySimpleName(string simpleName); public Task FindByDisplayName(string displayName); + public Task FindById(string userId); public Task SetSelectedBadge(User user, PkmnSpecies? badge); public Task SetSelectedEmblem(User user, int? emblem); public Task SetGlowColor(User user, string? glowColor); From be383348b145213acf4ece10b9ccafde391f5a59 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 3 Sep 2021 15:15:36 -0400 Subject: [PATCH 09/19] Add test for null shiny field on badges --- .../Repos/BadgeRepoTest.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 5b2fe447..ea8091b6 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -5,6 +5,7 @@ using MongoDB.Driver; using Moq; using NodaTime; +using NodaTime.Text; using NUnit.Framework; using TPP.Common; using TPP.Persistence.Models; @@ -19,6 +20,12 @@ public class BadgeRepoTest : MongoTestBase public BadgeRepo CreateBadgeRepo() => new BadgeRepo(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); + internal class MockClock : IClock + { + public Instant FixedCurrentInstant = Instant.FromUnixTimeSeconds(1234567890); + public Instant GetCurrentInstant() => FixedCurrentInstant; + } + [Test] public async Task insert_then_read_are_equal() { @@ -190,6 +197,33 @@ public async Task FindAllForSaleByCustom_only_returns_badges_for_sale() Assert.AreEqual(new List() { forSale }, saleList); } + [Test] + public async Task can_handle_null_shiny_fields() + { + IMongoDatabase db = CreateTemporaryDatabase(); + + const string id = "590df61373b975210006fcdf"; + Instant instant = InstantPattern.ExtendedIso.Parse("2017-05-06T16:13:07.314Z").Value; + + BadgeRepo badgeRepo = new BadgeRepo(db, new BadgeLogRepo(db), new MockClock()); + IMongoCollection bsonBadgeCollection = db.GetCollection("badges"); + await bsonBadgeCollection.InsertOneAsync(BsonDocument.Create(new Dictionary + { + ["_id"] = ObjectId.Parse(id), + ["userid"] = "mogi", + ["species"] = "1", + ["source"] = "manual_creation", + ["created_at"] = instant.ToDateTimeUtc(), + ["form"] = 0, + })); + + IMongoCollection badgeCollection = db.GetCollection("badges"); ; + + Badge b = await badgeCollection.Find(b => b.Id == id).FirstAsync(); + + Assert.AreEqual(false, b.Shiny); + } + [TestFixture] private class TransferBadge : MongoTestBase { From ff11dc9fc0cbff1cf61d0ddb10a2e0be25e9c935 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 3 Sep 2021 18:41:29 -0400 Subject: [PATCH 10/19] Extend shiny test, fix bug --- TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs | 6 ++++-- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index ea8091b6..11803073 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -210,7 +210,7 @@ public async Task can_handle_null_shiny_fields() await bsonBadgeCollection.InsertOneAsync(BsonDocument.Create(new Dictionary { ["_id"] = ObjectId.Parse(id), - ["userid"] = "mogi", + ["user"] = "mogi", ["species"] = "1", ["source"] = "manual_creation", ["created_at"] = instant.ToDateTimeUtc(), @@ -220,8 +220,10 @@ await bsonBadgeCollection.InsertOneAsync(BsonDocument.Create(new Dictionary badgeCollection = db.GetCollection("badges"); ; Badge b = await badgeCollection.Find(b => b.Id == id).FirstAsync(); - Assert.AreEqual(false, b.Shiny); + + List badges = await badgeRepo.FindAllByCustom(null, null, null, null, false); + Assert.AreEqual(1, badges.Count); } [TestFixture] diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index ce565a58..fbda640a 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -90,7 +90,7 @@ public async Task> FindAllByUser(string? userId) => public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); - public async Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = null) + public async Task> FindAllByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -103,8 +103,10 @@ public async Task> FindAllByCustom(string? userId = null, PkmnSpecie filter &= Builders.Filter.Eq(b => b.Form, form); if (source != null) filter &= Builders.Filter.Eq(b => b.Source, source); - if (shiny != null) - filter &= Builders.Filter.Eq(b => b.Shiny, shiny); + if (shiny == true) + filter &= Builders.Filter.Eq(b => b.Shiny, true); + else + filter &= Builders.Filter.Ne(b => b.Shiny, true); return await Collection.Find(filter).ToListAsync(); } From ce5c8362d6f01da0e656aa17c9a11d628adfccbc Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Wed, 8 Sep 2021 05:47:17 -0400 Subject: [PATCH 11/19] Add BadgeBuyOfferRepo --- .../Repos/BadgeBuyOfferRepoTest.cs | 56 ++++++++++++ .../Repos/BadgeBuyOfferRepo.cs | 74 +++++++++++++++ TPP.Persistence/Models/BadgeBuyOffer.cs | 90 +++++++++++++++++++ TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs | 16 ++++ 4 files changed, 236 insertions(+) create mode 100644 TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs create mode 100644 TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs create mode 100644 TPP.Persistence/Models/BadgeBuyOffer.cs create mode 100644 TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs new file mode 100644 index 00000000..ccd4744e --- /dev/null +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs @@ -0,0 +1,56 @@ +using Moq; +using NodaTime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.Persistence.Models; +using TPP.Persistence.MongoDB.Repos; +using MongoDB.Driver; +using NUnit.Framework; +using TPP.Common; +using TPP.Persistence.Repos; + +namespace TPP.Persistence.MongoDB.Tests.Repos +{ + class BadgeBuyOfferRepoTest : MongoTestBase + { + public BadgeBuyOfferRepo CreateBadgeBuyOfferRepo() + { + IMongoDatabase db = CreateTemporaryDatabase(); + BadgeRepo mockBadgeRepo = new BadgeRepo(db, Mock.Of(), Mock.Of()); + return new BadgeBuyOfferRepo(db, mockBadgeRepo, Mock.Of()); + } + + internal class MockClock : IClock + { + public Instant FixedCurrentInstant = Instant.FromUnixTimeSeconds(1234567890); + public Instant GetCurrentInstant() => FixedCurrentInstant; + } + + [Test] + public async Task write_then_read_are_equal() + { + string userId = "m4"; + PkmnSpecies species = PkmnSpecies.OfId("1"); + int form = 0; + Badge.BadgeSource source = Badge.BadgeSource.ManualCreation; + bool shiny = true; + int price = 999; + int amount = 1; + + IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + + BadgeBuyOffer offer = await badgeBuyOfferRepo.CreateBuyOffer(userId, species, form, source, shiny, price, amount, Instant.MaxValue); + + Assert.AreEqual(userId, offer.UserId); + Assert.AreEqual(species, offer.Species); + Assert.AreEqual(form, offer.Form); + Assert.AreEqual(source, offer.Source); + Assert.AreEqual(shiny, offer.Shiny); + Assert.AreEqual(price, offer.Price); + Assert.AreEqual(amount, offer.Amount); + } + } +} diff --git a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs new file mode 100644 index 00000000..611ecab2 --- /dev/null +++ b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs @@ -0,0 +1,74 @@ +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.IdGenerators; +using MongoDB.Driver; +using NodaTime; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.Common; +using TPP.Persistence.Models; +using TPP.Persistence.MongoDB.Serializers; +using TPP.Persistence.Repos; + +namespace TPP.Persistence.MongoDB.Repos +{ + public class BadgeBuyOfferRepo : IBadgeBuyOfferRepo + { + private const string CollectionName = "badgebuyoffers"; + + public readonly IMongoCollection Collection; + private readonly IBadgeRepo _badgeRepo; + private readonly IClock _clock; + + static BadgeBuyOfferRepo() + { + BsonClassMap.RegisterClassMap(cm => + { + cm.MapIdProperty(o => o.Id) + .SetIdGenerator(StringObjectIdGenerator.Instance) + .SetSerializer(ObjectIdAsStringSerializer.Instance); + cm.MapProperty(o => o.UserId).SetElementName("user"); + cm.MapProperty(o => o.Species).SetElementName("species"); + cm.MapProperty(o => o.Source).SetElementName("source"); + cm.MapProperty(o => o.CreatedAt).SetElementName("created_at"); + cm.MapProperty(o => o.Form).SetElementName("form") + .SetDefaultValue(0); + cm.MapProperty(o => o.Shiny).SetElementName("shiny") + .SetDefaultValue(false) + .SetIgnoreIfDefault(true); + }); + } + + public BadgeBuyOfferRepo(IMongoDatabase database, BadgeRepo badgeRepo, IClock clock) + { + _badgeRepo = badgeRepo; + + database.CreateCollectionIfNotExists(CollectionName).Wait(); + Collection = database.GetCollection(CollectionName); + _clock = clock; + } + + public async Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt=null) + { + BadgeBuyOffer buyOffer = new BadgeBuyOffer( + id: string.Empty, + userId: userId, + species: species, + form: form, + source: source, + shiny: shiny, + price: price, + amount: amount, + createdAt: createdAt ?? _clock.GetCurrentInstant() + ); + + await Collection.InsertOneAsync(buyOffer); + Debug.Assert(buyOffer.Id.Length > 0, "The MongoDB driver injected a generated ID"); + return buyOffer; + } + + } +} diff --git a/TPP.Persistence/Models/BadgeBuyOffer.cs b/TPP.Persistence/Models/BadgeBuyOffer.cs new file mode 100644 index 00000000..ea868c19 --- /dev/null +++ b/TPP.Persistence/Models/BadgeBuyOffer.cs @@ -0,0 +1,90 @@ +using NodaTime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.Common; + +namespace TPP.Persistence.Models +{ + public class BadgeBuyOffer + { + /// + /// Unique Id. + /// + public string Id { get; init; } + + /// + /// The ID of the user that created the buy offer. + /// + public string UserId { get; init; } + + /// + /// The species of pokemon to buy. + /// + public PkmnSpecies Species { get; init; } + + /// + /// The form of pokemon to buy. + /// + public int? Form { get; init; } + + /// + /// The source of the badge to buy. + /// + public Badge.BadgeSource? Source { get; init; } + + /// + /// Is the offer seeking shiny badges. + /// + public bool? Shiny { get; init; } + + /// + /// How much to pay for each badge. + /// + public int Price { get; init; } + + /// + /// The number of badges to buy. + /// + public int Amount { get; private set; } + + /// + /// When the buy offer was created. + /// + public Instant CreatedAt { get; init; } + + /// + /// When this offer was last updated. + /// + public Instant WaitingSince { get; private set; } + + //duration and expires_at depricated from old core + + public BadgeBuyOffer(string id, string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant createdAt) + { + Id = id; + UserId = userId; + Species = species; + Form = form; + Source = source; + Shiny = shiny; + Price = price; + Amount = amount; + CreatedAt = createdAt; + WaitingSince = createdAt; + } + + /// + /// Decrement the amount to buy. + /// + public void decrement(Instant decrementedAt) + { + if (Amount <= 0) + throw new InvalidOperationException("The buy offer has no badges remaining, and cannot be decremented further."); + Amount--; + WaitingSince = decrementedAt; + } + } +} diff --git a/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs b/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs new file mode 100644 index 00000000..ecc19cf5 --- /dev/null +++ b/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs @@ -0,0 +1,16 @@ +using NodaTime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.Common; +using TPP.Persistence.Models; + +namespace TPP.Persistence.Repos +{ + public interface IBadgeBuyOfferRepo + { + public Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); + } +} From 63cfecdb4fd513f804c4c4f19682642723253cce Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Sat, 11 Sep 2021 19:19:13 -0400 Subject: [PATCH 12/19] simplify forms, general fixes, format --- TPP.ArgsParsing/TypeParsers/FormParser.cs | 369 ++++++++++++++++++ TPP.ArgsParsing/TypeParsers/ShinyParser.cs | 4 +- TPP.ArgsParsing/Types/ImplicitBoolean.cs | 6 - TPP.ArgsParsing/Types/ImplicitString.cs | 15 + TPP.Common/PkmnForms.cs | 63 --- .../Commands/Definitions/BadgeCommandsTest.cs | 12 +- .../Definitions/OperatorCommandsTest.cs | 8 +- .../Commands/Definitions/BadgeCommands.cs | 69 ++-- .../Commands/Definitions/OperatorCommands.cs | 33 +- .../Repos/BadgeBuyOfferRepoTest.cs | 2 +- .../Repos/BadgeRepoTest.cs | 76 ++-- .../Repos/BadgeBuyOfferRepo.cs | 5 +- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 7 +- TPP.Persistence/Models/Badge.cs | 4 +- TPP.Persistence/Repos/IBadgeRepo.cs | 6 +- 15 files changed, 477 insertions(+), 202 deletions(-) create mode 100644 TPP.ArgsParsing/TypeParsers/FormParser.cs create mode 100644 TPP.ArgsParsing/Types/ImplicitString.cs delete mode 100644 TPP.Common/PkmnForms.cs diff --git a/TPP.ArgsParsing/TypeParsers/FormParser.cs b/TPP.ArgsParsing/TypeParsers/FormParser.cs new file mode 100644 index 00000000..96e85e90 --- /dev/null +++ b/TPP.ArgsParsing/TypeParsers/FormParser.cs @@ -0,0 +1,369 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.ArgsParsing.Types; + +namespace TPP.ArgsParsing.TypeParsers +{ + class FormParser : BaseArgumentParser + { + /// + /// Keys and values are in the format: lowercase unspaced name | Capitalized display name. + /// + private static readonly Dictionary Forms = new Dictionary + { + #region common forms + ["alolan"] = "Alolan", + ["alola"] = "Alolan", + ["glarian"] = "Galarian", + ["galar"] = "Galarian", + ["hisuian"] = "Hisuian", //pokemon legends: arceus + ["hisui"] = "Hisuan", + + ["mega"] = "Mega", + ["dynamax"] = "Dyanamax", + ["gigantamax"] = "Gigantamax", + ["normal"] = "Normal", + + ["red"] = "Red", + ["red-stripe"] = "Red", //basculin + ["redstripe"] = "Red", + ["redflower"] = "Red", //Flabébé, Floette, and Florges + ["redcore"] = "Red", //minior + ["blue"] = "Blue", + ["blue-stripe"] = "Blue", + ["bluestripe"] = "Blue", + ["blueflower"] = "Blue", + ["bluecore"] = "Blue", + ["white"] = "White", + ["whiteflower"] = "White", + ["black"] = "Black", + ["orange"] = "Orange", + ["orangeflower"] = "Orange", + ["orangecore"] = "Orange", + ["yellow"] = "Yellow", + ["yellowflower"] = "Yellow", + ["yellowcore"] = "Yellow", + ["green"] = "Green", + ["greencore"] = "Green", + ["indigocore"] = "Indigo", + ["indigo"] = "Indigo", + ["violet"] = "Violet", + ["violetcore"] = "Violet", + #endregion + + #region pokemon specific forms + ["primal"] = "Primal", //Kyogre, Groudon + ["fire"] = "Fire", //Arceus, Silvally + ["water"] = "Water", + ["electric"] = "Electric", + ["grass"] = "Grass", + ["ice"] = "Ice", + ["fighting"] = "fighting", + ["poison"] = "Poison", + ["ground"] = "Ground", + ["psychic"] = "Psychic", + ["flying"] = "Flying", + ["bug"] = "Bug", + ["rock"] = "Rock", + ["ghost"] = "Ghost", + ["dragon"] = "Dragon", + ["dark"] = "Dark", + ["steel"] = "Steel", + ["fairy"] = "Fairy", + ["???"] = "???", + //["normal"] = "Normal", redundant since it is defined above, but left here to show that there is completeness across types + ["cosplay"] = "Cosplay", //pikachu + ["rockstar"] = "Rock Star", + ["belle"] = "Belle", + ["popstar"] = "Pop Star", + ["phd"] = "PHD", + ["dr"] = "PHD", + ["libre"] = "Libre", + ["originalcap"] = "Original Cap", + ["hoenncap"] = "Hoenn Cap", + ["sinnohcap"] = "Sinnoh Cap", + ["unovacap"] = "Unova Cap", + ["kaloscap"] = "Kalos Cap", + ["alolacap"] = "Alola Cap", + ["partnercap"] = "Partner Cap", + ["worldcap"] = "World Cap", + ["spiky-eared"] = "Spiky-Eared", //pichu + ["spikyeared"] = "Spiky-Eared", + ["a"] = "A", //unown + ["b"] = "B", + ["c"] = "C", + ["d"] = "D", + ["e"] = "E", + ["f"] = "F", + ["g"] = "G", + ["h"] = "H", + ["i"] = "I", + ["j"] = "J", + ["k"] = "K", + ["l"] = "L", + ["m"] = "M", + ["n"] = "N", + ["o"] = "O", + ["p"] = "P", + ["q"] = "Q", + ["r"] = "R", + ["s"] = "S", + ["t"] = "T", + ["u"] = "U", + ["v"] = "V", + ["w"] = "W", + ["x"] = "X", + ["y"] = "Y", + ["z"] = "Z", + ["?"] = "?", + ["!"] = "!", + ["sunny"] = "Sunny", //castform + ["rainy"] = "Rainy", + ["rain"] = "Rainy", + ["snowy"] = "Snowy", + ["snow"] = "Snowy", + ["attack"] = "Attack", //deoxys + ["defence"] = "Defence", + ["speed"] = "Speed", + ["plantcloak"] = "Plant Cloak", //burmy & wormadam + ["plant"] = "Plant Cloak", + ["sandycloak"] = "Sandy Cloak", + ["sandcloak"] = "Sandy Cloak", + ["sand"] = "Sandy Cloak", + ["trashcloak"] = "Trash Cloak", + ["trash"] = "Trash Cloak", + ["overcast"] = "Overcast", //cherrim + ["sunshine"] = "Sunshine", + ["westsea"] = "West Sea", //shelos & gastrodon + ["west"] = "West Sea", + ["eastsea"] = "East Sea", + ["east"] = "East Sea", + ["heat"] = "Heat", //rotom + ["wash"] = "Wash", + ["frost"] = "Frost", + ["fan"] = "Fan", + ["mow"] = "Mow", + ["pokedex"] = "Pokédex", + ["phone"] = "phone", + ["origin"] = "Origin", //giritina + ["land"] = "Land", //shaymin + ["sky"] = "Sky", + ["standard"] = "Standard", //darmanitan + ["zen"] = "Zen", + ["galarianstandard"] = "Galarian Standard", + ["galarstandard"] = "Galarian Standard", + ["galarianzen"] = "Galarian Zen", + ["galarzen"] = "Galarian Zen", + ["spring"] = "Spring", //deerling & sawsbuck + ["summer"] = "Summer", + ["autumn"] = "Autumn", + ["fall"] = "Autumn", + ["winter"] = "Winter", + ["incarnate"] = "Incarnate", //tornadus, thundurus, landorus + ["therian"] = "Therian", + ["ordinary"] = "Ordinary", //keldeo + ["resolute"] = "Resoulte", + ["aria"] = "Aria", //Meloetta + ["pirouette"] = "Pirouette", + ["shockdrive"] = "Shock Drive", //genesect + ["shock"] = "Shock Drive", + ["burndrive"] = "Burn Drive", + ["burn"] = "Burn Drive", + ["chilldrive"] = "Chill Drive", + ["chill"] = "Chill Drive", + ["dousedrive"] = "Douse Drive", + ["douse"] = "Douse Drive", + ["ash-greninja"] = "Ash-Greninja", //greninja + ["ashgreninja"] = "Ash-Greninja", + ["ash"] = "Ash-Greninja", + ["archipelago"] = "Archipelago", //vivillon + ["continental"] = "Continental", + ["elegant"] = "Elegant", + ["garden"] = "Garden", + ["highplains"] = "High Plains", + ["icysnow"] = "Icy Snow", + ["icy"] = "Icy Snow", + ["jungle"] = "Jungle", + ["marine"] = "Marine", + ["meadow"] = "Meadow", + ["modern"] = "Modern", + ["monsoon"] = "Monsoon", + ["ocean"] = "Ocean", + ["polar"] = "Polar", + ["river"] = "River", + ["sandstorm"] = "Sandstorm", + ["savanna"] = "Savanna", + ["sun"] = "Sun", + ["tundra"] = "Tundra", + ["pokeball"] = "Poké Ball", + ["fancy"] = "Fancy", + ["natural"] = "Normal", //furfou + ["hearttrim"] = "Heart Trim", + ["heart"] = "Heart Trim", + ["startrim"] = "Star Trim", + ["diamondtrim"] = "Diamond Trim", + ["debutantetrim"] = "Debutante Trim", + ["debutante"] = "Debutante Trim", + ["matrontrim"] = "Matron Trim", + ["matron"] = "Matron Trim", + ["dandytrim"] = "Dandy Trim", + ["lareinetrim"] = "La Reine Trim", + ["lareine"] = "La Reine Trim", + ["kabukitrim"] = "Kabuki Trim", + ["kabuki"] = "Kabuki Trim", + ["pharohtrim"] = "Pharoh Trim", + ["pharoh"] = "Pharoh Trim", + ["shield"] = "Shield", //aegislash, zamazenta + ["crownedshield"] = "Shield", //zamazenta + ["blade"] = "Blade", //aegislash + ["sword"] = "Sword", //zacian + ["crownedsword"] = "Sword", + ["heroofmanybattles"] = "Hero of Many Battles", //zacian & zamazenta + ["hero"] = "Hero of Many Battles", + ["small"] = "Small", //pumpkaboo & gourgeist + ["average"] = "Average", + ["large"] = "Large", + ["super"] = "Super", + ["neutralmode"] = "Neutral Mode", //xerneas + ["neutral"] = "Neutral Mode", + ["activemode"] = "Active Mode", + ["active"] = "Active Mode", + ["cell"] = "Cell", //zygarde + ["core"] = "Core", + ["10%"] = "10%", + ["10percent"] = "10%", + ["50%"] = "50%", + ["50percent"] = "50%", + ["complete"] = "Complete", + ["confined"] = "Confined", //hoopa + ["unbound"] = "Unbound", + ["bailestyle"] = "Baile Style", //oricorio + ["baile"] = "Baile Style", + ["pom-pomstyle"] = "Pom-Pom Style", + ["pompomstyle"] = "Pom-Pom Style", + ["pompom"] = "Pom-Pom Style", + ["pa'ustyle"] = "Pa'u Style", + ["paustyle"] = "Pa'u Style", + ["pau"] = "Pa'u Style", + ["sensustyle"] = "Sensu Style", + ["sensu"] = "Sensu Style", + ["midday"] = "Midday", //lycanroc + ["midnight"] = "Midnight", + ["dusk"] = "Dusk", + ["solo"] = "Solo", //wishiwashi + ["school"] = "School", + ["meteor"] = "Meteor", //minor (color forms above) + ["disguised"] = "Disguised", //mimikyu + ["busted"] = "Busted", + ["duskmane"] = "Dusk", //necrozma + ["dawn"] = "Dawn", + ["dawnwings"] = "Dawn", + ["ultra"] = "Ultra", + ["originalcolor"] = "Original Color", //magearna + ["gulping"] = "Gulping", //cramorant + ["gorging"] = "Gorging", + ["amped"] = "Amped", //toxtricity + ["low-key"] = "Low Key", + ["lowkey"] = "Low Key", + ["phony"] = "Phony", //sinistea & polteageist + ["antique"] = "Antique", + ["strawberryvanilla"] = "Strawberry Vanilla", //alcremie... + ["blueberryvanilla"] = "Blueberry Vanilla", + ["lovevanilla"] = "Love Vanilla", + ["starvanilla"] = "Star Vanilla", + ["clovervanilla"] = "Clover Vanilla", + ["flowervanilla"] = "Flower Vanilla", + ["ribbonvanilla"] = "Ribbon Vanilla", + ["strawberryruby"] = "Strawberry Ruby", + ["blueberryruby"] = "Blueberry Ruby", + ["loveruby"] = "Love Ruby", + ["starruby"] = "Star Ruby", + ["cloverruby"] = "Clover Ruby", + ["flowerruby"] = "Flower Ruby", + ["ribbonruby"] = "Ribbon Ruby", + ["strawberrymatcha"] = "Strawberry Matcha", + ["blueberrymatcha"] = "Blueberry Matcha", + ["lovematcha"] = "Love Matcha", + ["starmatcha"] = "Star Matcha", + ["clovermatcha"] = "Clover Matcha", + ["flowermatcha"] = "Flower Matcha", + ["ribbonmatcha"] = "Ribbon Matcha", + ["strawberrymint"] = "Strawberry Mint", + ["blueberrymint"] = "Blueberry Mint", + ["lovemint"] = "Love Mint", + ["starmint"] = "Star Mint", + ["clovermint"] = "Clover Mint", + ["flowermint"] = "Flower Mint", + ["ribbonmint"] = "Ribbon Mint", + ["strawberrylemon"] = "Strawberry Lemon", + ["blueberrylemon"] = "Blueberry Lemon", + ["lovelemon"] = "Love Lemon", + ["starlemon"] = "Star Lemon", + ["cloverlemon"] = "Clover Lemon", + ["flowerlemon"] = "Flower Lemon", + ["ribbonlemon"] = "Ribbon Lemon", + ["strawberrysalted"] = "Strawberry Salted", + ["blueberrysalted"] = "Blueberry Salted", + ["lovesalted"] = "Love Salted", + ["starsalted"] = "Star Salted", + ["cloversalted"] = "Clover Salted", + ["flowersalted"] = "Flower Salted", + ["ribbonsalted"] = "Ribbon Salted", + ["strawberryruby"] = "Strawberry Ruby", + ["blueberryruby"] = "Blueberry Ruby", + ["loveruby"] = "Love Ruby", + ["starruby"] = "Star Ruby", + ["cloverruby"] = "Clover Ruby", + ["flowerruby"] = "Flower Ruby", + ["ribbonruby"] = "Ribbon Ruby", + ["strawberrycaramel"] = "Strawberry Caramel", + ["blueberrycaramel"] = "Blueberry Caramel", + ["lovecaramel"] = "Love Caramel", + ["starcaramel"] = "Star Caramel", + ["clovercaramel"] = "Clover Caramel", + ["flowercaramel"] = "Flower Caramel", + ["ribboncaramel"] = "Ribbon Caramel", + ["strawberryrainbow"] = "Strawberry Rainbow", + ["blueberryrainbow"] = "Blueberry Rainbow", + ["loverainbow"] = "Love Rainbow", + ["starrainbow"] = "Star Rainbow", + ["cloverrainbow"] = "Clover Rainbow", + ["flowerrainbow"] = "Flower Rainbow", + ["ribbonrainbow"] = "Ribbon Rainbow", + ["iceface"] = "Ice Face", //eiscue + ["noiceface"] = "Noice Face", + ["fullbelly"] = "Full Belly", //morepko + ["full"] = "Full Belly", + ["hangry"] = "Hangry", + ["eternamax"] = "Eternamax", //eternatus + ["singlestrike"] = "Single Strike", //urshifu + ["single"] = "Single Strike", + ["rapidstrike"] = "Rapid Strike", + ["rapid"] = "Rapid Strike", + ["dada"] = "Dada", //zarude + ["icerider"] = "Ice Rider", //calyrex + ["shadowrider"] = "Shadow Rider", + ["overdrive"] = "Overdrive", //reshiram, zerkom, kyurem + ["radientsun"] = "Sun", //solgaleo + ["fullmoon"] = "Moon", //lunala + ["moon"] = "Moon", + ["zenith"] = "Zenith", //marshadow + #endregion + // compiled from https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_with_form_differences on 11/9/21 + }; + public override Task> Parse(IImmutableList args, Type[] genericTypes) + { + string toValidate = args[0].ToLower(); + ArgsParseResult result; + if (Forms.TryGetValue(toValidate, out string? formName)) + result = ArgsParseResult.Success(new Form(formName), args.Skip(1).ToImmutableList()); + else + result = ArgsParseResult.Failure("Invalid form name"); + return Task.FromResult(result); + } + } +} diff --git a/TPP.ArgsParsing/TypeParsers/ShinyParser.cs b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs index 4d648473..53fe44ac 100644 --- a/TPP.ArgsParsing/TypeParsers/ShinyParser.cs +++ b/TPP.ArgsParsing/TypeParsers/ShinyParser.cs @@ -25,7 +25,7 @@ public class ShinyParser : BaseArgumentParser }; public override Task> Parse(IImmutableList args, Type[] genericTypes) { - string s = args[0]; + string s = args[0].ToLower(); ArgsParseResult result; if (shinyWords.Contains(s)) { @@ -38,7 +38,7 @@ public override Task> Parse(IImmutableList args, else { result = ArgsParseResult.Failure("The argument couldn't be understood as shiny or not", ErrorRelevanceConfidence.Unlikely); - } + } return Task.FromResult(result); } } diff --git a/TPP.ArgsParsing/Types/ImplicitBoolean.cs b/TPP.ArgsParsing/Types/ImplicitBoolean.cs index e88de250..6f2950d5 100644 --- a/TPP.ArgsParsing/Types/ImplicitBoolean.cs +++ b/TPP.ArgsParsing/Types/ImplicitBoolean.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace TPP.ArgsParsing.Types { public class ImplicitBoolean diff --git a/TPP.ArgsParsing/Types/ImplicitString.cs b/TPP.ArgsParsing/Types/ImplicitString.cs new file mode 100644 index 00000000..f54ef822 --- /dev/null +++ b/TPP.ArgsParsing/Types/ImplicitString.cs @@ -0,0 +1,15 @@ +namespace TPP.ArgsParsing.Types +{ + public class ImplicitString + { + public string Name { get; internal init; } + public static implicit operator string(ImplicitString f) => f.Name; + public override string ToString() => Name.ToString(); + public ImplicitString(string s) => Name = s; + } + + public class Form : ImplicitString + { + public Form(string s) : base(s) => Name = s; + } +} diff --git a/TPP.Common/PkmnForms.cs b/TPP.Common/PkmnForms.cs deleted file mode 100644 index eed3fd51..00000000 --- a/TPP.Common/PkmnForms.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using TPP.Common; - -namespace TPP.Common -{ - public static class PkmnForms - { - /// - /// - /// - static readonly Dictionary> Forms = new Dictionary> - { - ["unown"] = new Dictionary - { - ["a"] = 1, - ["b"] = 2, - }, - ["shellos"] = new Dictionary - { - ["west sea"] = 1, - ["westsea"] = 1, - ["west"] = 1, - ["pink"] = 1, - ["east sea"] = 2, - ["eastsea"] = 2, - ["east"] = 2, - ["blue"] = 2, - }, - }; - - public static string getFormName(PkmnSpecies pokemon, int formid) - { - string pkmnName = pokemon.Name.ToLower(); //TODO: use normalize_name from pkmnspecies - Dictionary? forms; - if (!Forms.TryGetValue(pkmnName, out forms)) - throw new ArgumentException($"{pokemon.Name} does not have alternate forms."); - string formName = forms.FirstOrDefault(p => p.Value == formid).Key; - if (formName == null) - throw new ArgumentException($"{pokemon.Name} does not have a form with id {formid}."); - return formName; - } - - public static int getFormId(PkmnSpecies pokemon, string formName) - { - string pkmnName = pokemon.Name.ToLower(); - Dictionary? forms; - if (!Forms.TryGetValue(pkmnName, out forms)) - throw new ArgumentException($"{pokemon.Name} does not have alternate forms."); - int formid = forms.GetValueOrDefault(formName.ToLower()); - if (formid == 0) - throw new ArgumentException($"{pokemon.Name} does not have a form called {formName}."); - return formid; - } - - public static bool pokemonHasForms(PkmnSpecies pokemon) - { - return Forms.ContainsKey(pokemon.Name.ToLower()); - } - } -} diff --git a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index d0321570..0c988306 100644 --- a/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -264,9 +264,9 @@ public async Task TestGiftBadgeSuccessful() User user = MockUser("MockUser"); User recipient = MockUser("Recipient"); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); - Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); - Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge1 = new("badge1", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); + Badge badge2 = new("badge2", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); + Badge badge3 = new("badge3", user.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, species, null, null, false)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -312,7 +312,7 @@ public async Task ListSellBadge_lists_callers_badges_when_user_isnt_specified() { User user1 = MockUser("u1"); PkmnSpecies speciesA = PkmnSpecies.RegisterName("1", "a"); - Badge badgeA = new("badgeA", user1.Id, speciesA, Badge.BadgeSource.Pinball, Instant.MinValue, 0, false) {SellPrice = 1 }; + Badge badgeA = new("badgeA", user1.Id, speciesA, Badge.BadgeSource.Pinball, Instant.MinValue, null, false) { SellPrice = 1 }; _badgeRepoMock.Setup(repo => repo.FindAllForSaleByCustom(user1.Id, speciesA, null, null, false)).Returns(Task.FromResult(new List() { badgeA })); _userRepoMock.Setup(repo => repo.FindById(user1.Id)).Returns(Task.FromResult((User?)user1)); @@ -322,11 +322,9 @@ public async Task ListSellBadge_lists_callers_badges_when_user_isnt_specified() _argsParser.AddArgumentParser(new BadgeSourceParser()); _argsParser.AddArgumentParser(new StringParser()); - await _badgeRepoMock.Object.SetBadgeSellPrice(badgeA, 1); - CommandResult result = await _badgeCommands.ListSellBadge(new CommandContext(MockMessage(user1), ImmutableList.Create("a"), _argsParser)); - Assert.AreEqual("1 badges found:\na sold by u1 for T1", result.Response); + Assert.AreEqual("1 badges found: a sold by u1 for T1", result.Response); } } } diff --git a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs index 4c7147e2..25db03ba 100644 --- a/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs +++ b/TPP.Core.Tests/Commands/Definitions/OperatorCommandsTest.cs @@ -158,9 +158,9 @@ public async Task TestTransferBadgeSuccessful() _messageSenderMock.Object, _badgeRepoMock.Object); _userRepoMock.Setup(repo => repo.FindBySimpleName("gifter")).Returns(Task.FromResult((User?)gifter)); _userRepoMock.Setup(repo => repo.FindBySimpleName("recipient")).Returns(Task.FromResult((User?)recipient)); - Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); - Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); - Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, 0, false); + Badge badge1 = new("badge1", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); + Badge badge2 = new("badge2", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); + Badge badge3 = new("badge3", gifter.Id, species, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); _badgeRepoMock.Setup(repo => repo.FindByUserAndSpecies(gifter.Id, species)) .Returns(Task.FromResult(new List { badge1, badge2, badge3, })); @@ -259,7 +259,7 @@ public async Task TestCreateBadge() Assert.AreEqual("123 #001 Species badges created for Recipient.", result.Response); Assert.AreEqual(ResponseTarget.Source, result.ResponseTarget); _badgeRepoMock.Verify(repo => - repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, 1, false, null), + repo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, null, false, null), Times.Exactly(123)); } } diff --git a/TPP.Core/Commands/Definitions/BadgeCommands.cs b/TPP.Core/Commands/Definitions/BadgeCommands.cs index e43f1711..426db957 100644 --- a/TPP.Core/Commands/Definitions/BadgeCommands.cs +++ b/TPP.Core/Commands/Definitions/BadgeCommands.cs @@ -49,12 +49,12 @@ public class BadgeCommands : ICommandCollection new Command("giftbadge", GiftBadge) { Description = - "Gift a badge you own to another user with no price. Arguments: (Optional) " //TODO also update this before merge + "Gift a badge you own to another user with no price. Arguments: (Optional) (optional) (optional) (optional)" }, new Command("listsellbadge", ListSellBadge) { Description = - "List the badges someone is selling. Arguments: (optional) (optional) (optional) (optional)" + "List the badges someone is selling. Arguments: (optional) (optional) (optional) (optional)" } }; @@ -79,6 +79,17 @@ public BadgeCommands( _whitelist = whitelist; } + /// + /// Convineintly handles many optional arguments for badge commands. May throw an exception when given faulty form informaton, so this should be surrounded by a try/catch + /// + private (Badge.BadgeSource?, string?, bool?) InterpretBadgeInfoArgs(Optional sourceOpt, Optional formOpt, Optional shinyOpt) + { + Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; + string? form = formOpt.IsPresent ? formOpt.Value.Name : null; + bool? shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; + return (source, form, shiny); + } + public async Task Badges(CommandContext context) { (Optional optionalSpecies, Optional optionalUser) = @@ -277,27 +288,11 @@ public async Task Pokedex(CommandContext context) public async Task GiftBadge(CommandContext context) { User gifter = context.Message.User; - (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = - await context.ParseArgs, Optional, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = + await context.ParseArgs, Optional, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); - Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; - int? form = null; - bool? shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; - if (formOpt.IsPresent) - { - string formName = formOpt.Value; - try - { - form = PkmnForms.getFormId(species, formName); - } - catch (ArgumentException e) - { - return new CommandResult - { - Response = e.Message - }; - } - } + (Badge.BadgeSource? source, string? form, bool? shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); + if (recipient == gifter) return new CommandResult { Response = "You cannot gift to yourself" }; @@ -327,28 +322,11 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task ListSellBadge(CommandContext context) { - (Optional userOpt, PkmnSpecies species, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = - await context.ParseArgs, PkmnSpecies, Optional, Optional, Optional>>(); + (Optional userOpt, PkmnSpecies species, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = + await context.ParseArgs, PkmnSpecies, Optional, Optional, Optional>>(); User user = userOpt.IsPresent ? userOpt.Value : context.Message.User; - Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; - bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; - int? form = null; - if (formOpt.IsPresent) - { - string formName = formOpt.Value; - try - { - form = PkmnForms.getFormId(species, formName); - } - catch (ArgumentException e) - { - return new CommandResult - { - Response = e.Message - }; - } - } + (Badge.BadgeSource? source, string? form, bool? shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); List forSale = await _badgeRepo.FindAllForSaleByCustom(user.Id, species, form, source, shiny); @@ -357,7 +335,6 @@ public async Task ListSellBadge(CommandContext context) response = "No badges found."; else { - bool hasForms = PkmnForms.pokemonHasForms(species); response = string.Format("{0} badges found:", forSale.Count); foreach (Badge b in forSale) { @@ -367,9 +344,9 @@ public async Task ListSellBadge(CommandContext context) if (seller == null) throw new OwnedBadgeNotFoundException(b); - response += string.Format("\n{0}{1}{2} sold by {3} for T{4}", - shiny ? "Shiny " : "", - hasForms ? PkmnForms.getFormName(b.Species, b.Form) : "", + response += string.Format(" {0}{1}{2} sold by {3} for T{4}", + shiny == true ? "Shiny " : "", + form ?? "", b.Species.Name, seller.SimpleName, b.SellPrice); diff --git a/TPP.Core/Commands/Definitions/OperatorCommands.cs b/TPP.Core/Commands/Definitions/OperatorCommands.cs index acd0019f..f74a359b 100644 --- a/TPP.Core/Commands/Definitions/OperatorCommands.cs +++ b/TPP.Core/Commands/Definitions/OperatorCommands.cs @@ -184,38 +184,21 @@ await _messageSender.SendWhisper(recipient, amount > 1 public async Task CreateBadge(CommandContext context) { - (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional shinyOpt) = - await context.ParseArgs, Optional, Optional>>(); + (User recipient, PkmnSpecies species, Optional amountOpt, Optional formOpt, Optional shinyOpt) = + await context.ParseArgs, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); - int form = PkmnForms.pokemonHasForms(species) ? 0 : 1; // default to the first listed form if form is unspecified + string? form = formOpt.IsPresent ? formOpt.Value.Name : null; bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; - if (formOpt.IsPresent) - { - string formName = formOpt.Value; - try - { - form = PkmnForms.getFormId(species, formName); - } - catch (ArgumentException e) - { - return new CommandResult - { - Response = e.Message - }; - } - } + for (int i = 0; i < amount; i++) await _badgeRepo.AddBadge(recipient.Id, species, Badge.BadgeSource.ManualCreation, form, shiny); + form = form == null ? "" : form + " "; return new CommandResult { - Response = PkmnForms.pokemonHasForms(species) - ? amount > 1 - ? $"{amount} {PkmnForms.getFormName(species, form)} {species} badges created for {recipient.Name}." - : $"{PkmnForms.getFormName(species, form)} {species} badge created for {recipient.Name}." - : amount > 1 - ? $"{amount} {species} badges created for {recipient.Name}." - : $"{species} badge created for {recipient.Name}." + Response = amount > 1 + ? $"{amount} {form}{species} badges created for {recipient.Name}." + : $"{form}{species} badge created for {recipient.Name}." }; } } diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs index ccd4744e..a7915fd4 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs @@ -42,7 +42,7 @@ public async Task write_then_read_are_equal() IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); - BadgeBuyOffer offer = await badgeBuyOfferRepo.CreateBuyOffer(userId, species, form, source, shiny, price, amount, Instant.MaxValue); + BadgeBuyOffer offer = await badgeBuyOfferRepo.CreateBuyOffer(userId, species, form, source, shiny, price, amount, Instant.MaxValue); Assert.AreEqual(userId, offer.UserId); Assert.AreEqual(species, offer.Species); diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs index 11803073..e12931f9 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeRepoTest.cs @@ -31,7 +31,7 @@ public async Task insert_then_read_are_equal() { BadgeRepo badgeRepo = CreateBadgeRepo(); // when - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0, false); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, null, false); // then Assert.AreNotEqual(string.Empty, badge.Id); @@ -50,7 +50,7 @@ public async Task insert_sets_current_timestamp_as_creation_date() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), clockMock.Object); - Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, 0, false); + Badge badge = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("16"), Badge.BadgeSource.ManualCreation, null, false); Assert.AreEqual(createdAt, badge.CreatedAt); } @@ -64,7 +64,7 @@ public async Task has_expected_bson_datatypes() BadgeRepo badgeRepo = CreateBadgeRepo(); // when PkmnSpecies randomSpecies = PkmnSpecies.OfId("9001"); - Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, 0, false); + Badge badge = await badgeRepo.AddBadge(null, randomSpecies, Badge.BadgeSource.RunCaught, null, false); // then IMongoCollection badgesCollectionBson = @@ -81,10 +81,10 @@ public async Task can_find_by_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); - Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, 0, false); + Badge badgeUserA1 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, null, false); + Badge badgeUserA2 = await badgeRepo.AddBadge("userA", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + Badge badgeUserB = await badgeRepo.AddBadge("userB", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + Badge badgeNobody = await badgeRepo.AddBadge(null, PkmnSpecies.OfId("4"), Badge.BadgeSource.Pinball, null, false); // when List resultUserA = await badgeRepo.FindAllByUser("userA"); @@ -102,13 +102,13 @@ public async Task can_count_by_user_and_species() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); // when long countHasNone = await badgeRepo.CountByUserAndSpecies("user", PkmnSpecies.OfId("1")); @@ -126,13 +126,13 @@ public async Task can_count_per_species_for_one_user() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); // when ImmutableSortedDictionary result = await badgeRepo.CountByUserPerSpecies("user"); @@ -151,12 +151,12 @@ public async Task can_check_if_user_has_badge() { IBadgeRepo badgeRepo = CreateBadgeRepo(); // given - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, 0, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", PkmnSpecies.OfId("3"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("userOther", PkmnSpecies.OfId("2"), Badge.BadgeSource.Pinball, null, false); // when bool hasUserSpecies1 = await badgeRepo.HasUserBadge("user", PkmnSpecies.OfId("1")); @@ -175,7 +175,7 @@ public async Task can_check_if_user_has_badge() public async Task can_set_badge_sell_price() { IBadgeRepo badgeRepo = CreateBadgeRepo(); - Badge badge = await badgeRepo.AddBadge("user", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, 0, false); + Badge badge = await badgeRepo.AddBadge("user", PkmnSpecies.OfId("1"), Badge.BadgeSource.Pinball, null, false); Badge forSale = await badgeRepo.SetBadgeSellPrice(badge, 10); @@ -187,8 +187,8 @@ public async Task FindAllForSaleByCustom_only_returns_badges_for_sale() { IBadgeRepo badgeRepo = CreateBadgeRepo(); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge notForSale = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, 0, false); - await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, 0, false); + Badge notForSale = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, null, false); + await badgeRepo.AddBadge("user", species, Badge.BadgeSource.Pinball, null, false); Badge forSale = await badgeRepo.SetBadgeSellPrice(notForSale, 1); @@ -214,7 +214,7 @@ await bsonBadgeCollection.InsertOneAsync(BsonDocument.Create(new Dictionary badgeCollection = db.GetCollection("badges"); ; @@ -235,7 +235,7 @@ public async Task returns_updated_badge_object() IBadgeRepo badgeRepo = new BadgeRepo( CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, null, false); IImmutableList updatedBadges = await badgeRepo.TransferBadges( ImmutableList.Create(badge), "recipient", "reason", new Dictionary()); @@ -254,7 +254,7 @@ public async Task unmarks_as_selling() { BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), Mock.Of(), Mock.Of()); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, null, false); await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge.Id), Builders.Update @@ -280,7 +280,7 @@ public async Task logs_to_badgelog() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, clockMock.Object); Badge badge = await badgeRepo.AddBadge( - "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, 0, false); + "user", PkmnSpecies.OfId("1"), Badge.BadgeSource.ManualCreation, null, false); Instant timestamp = Instant.FromUnixTimeSeconds(123); clockMock.Setup(c => c.GetCurrentInstant()).Returns(timestamp); @@ -299,8 +299,8 @@ public async Task triggers_species_lost_event() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, null, false); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, null, false); int userLostBadgeInvocations = 0; badgeRepo.UserLostBadgeSpecies += (_, args) => { @@ -323,8 +323,8 @@ public async Task aborts_all_transfers_if_one_fails() Mock mongoBadgeLogRepoMock = new(); BadgeRepo badgeRepo = new(CreateTemporaryDatabase(), mongoBadgeLogRepoMock.Object, Mock.Of()); PkmnSpecies species = PkmnSpecies.OfId("1"); - Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); - Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, 0, false); + Badge badge1 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, null, false); + Badge badge2 = await badgeRepo.AddBadge("user", species, Badge.BadgeSource.ManualCreation, null, false); // make in-memory badge reference stale to cause the transfer to fail on the second badge await badgeRepo.Collection.UpdateOneAsync( Builders.Filter.Where(b => b.Id == badge2.Id), diff --git a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs index 611ecab2..453aa211 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs @@ -35,7 +35,8 @@ static BadgeBuyOfferRepo() cm.MapProperty(o => o.Source).SetElementName("source"); cm.MapProperty(o => o.CreatedAt).SetElementName("created_at"); cm.MapProperty(o => o.Form).SetElementName("form") - .SetDefaultValue(0); + .SetDefaultValue(0) + .SetIgnoreIfDefault(true); cm.MapProperty(o => o.Shiny).SetElementName("shiny") .SetDefaultValue(false) .SetIgnoreIfDefault(true); @@ -51,7 +52,7 @@ public BadgeBuyOfferRepo(IMongoDatabase database, BadgeRepo badgeRepo, IClock cl _clock = clock; } - public async Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt=null) + public async Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt = null) { BadgeBuyOffer buyOffer = new BadgeBuyOffer( id: string.Empty, diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index fbda640a..7904139c 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -68,7 +68,7 @@ private void InitIndexes() } public async Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null) + string? userId, PkmnSpecies species, Badge.BadgeSource source, string? form, bool shiny, Instant? createdAt = null) { var badge = new Badge( id: string.Empty, @@ -90,7 +90,7 @@ public async Task> FindAllByUser(string? userId) => public async Task> FindByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.Find(b => b.UserId == userId && b.Species == species).ToListAsync(); - public async Task> FindAllByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny) + public async Task> FindAllByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -106,12 +106,13 @@ public async Task> FindAllByCustom(string? userId, PkmnSpecies? spec if (shiny == true) filter &= Builders.Filter.Eq(b => b.Shiny, true); else + //match everything not true, including null/nonexistance. (querying for shiny==false misses these) filter &= Builders.Filter.Ne(b => b.Shiny, true); return await Collection.Find(filter).ToListAsync(); } - public async Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny) + public async Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny) { List all = await FindAllByCustom(userId, species, form, source, shiny); return all.Where(b => b.SellPrice > 0).ToList(); diff --git a/TPP.Persistence/Models/Badge.cs b/TPP.Persistence/Models/Badge.cs index 3a38ea1e..06473430 100644 --- a/TPP.Persistence/Models/Badge.cs +++ b/TPP.Persistence/Models/Badge.cs @@ -47,7 +47,7 @@ public enum BadgeSource /// /// If this pokemon has multiple forms, which form it is. /// - public int Form { get; init; } + public string? Form { get; init; } /// /// If this badge is shiny. @@ -65,7 +65,7 @@ public Badge( PkmnSpecies species, BadgeSource source, Instant createdAt, - int form, + string? form, bool shiny) { Id = id; diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index 7c3a18a7..f86fa1a9 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -38,10 +38,10 @@ public OwnedBadgeNotFoundException(Badge badge) : public interface IBadgeRepo { public Task AddBadge( - string? userId, PkmnSpecies species, Badge.BadgeSource source, int form, bool shiny, Instant? createdAt = null); + string? userId, PkmnSpecies species, Badge.BadgeSource source, string? form, bool shiny, Instant? createdAt = null); public Task> FindAllByUser(string? userId); public Task> FindByUserAndSpecies(string? userId, PkmnSpecies species); - public Task> FindAllByCustom(string? userId = null, PkmnSpecies? species = null, int? form = null, Badge.BadgeSource? source = null, bool? shiny = null); + public Task> FindAllByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny); public Task CountByUserAndSpecies(string? userId, PkmnSpecies species); public Task> CountByUserPerSpecies(string? userId); public Task HasUserBadge(string? userId, PkmnSpecies species); @@ -53,6 +53,6 @@ public Task> TransferBadges( IDictionary additionalData); public Task SetBadgeSellPrice(Badge badge, int price); - public Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, int? form, Badge.BadgeSource? source, bool? shiny); + public Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny); } } From 15afff9b5af2ab6deae7a360b4a132a0ad187cdf Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Sun, 12 Sep 2021 10:19:25 -0400 Subject: [PATCH 13/19] add indexes --- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 7904139c..92215ca0 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -62,8 +62,11 @@ private void InitIndexes() { new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.UserId)), new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Species)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Source)), // TODO really ascending...?: new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.CreatedAt)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Form)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Shiny)), }); } From 51c6d3257c7d8d3789f8713539412dcf0cdfa449 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Mon, 13 Sep 2021 23:44:20 -0400 Subject: [PATCH 14/19] Extend BadgeBuyOfferRepo --- .../Repos/BadgeBuyOfferRepoTest.cs | 105 ++++++++++- .../Repos/BadgeBuyOfferRepo.cs | 177 +++++++++++++++++- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 12 +- TPP.Persistence/Models/BadgeBuyOffer.cs | 4 +- TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs | 4 +- TPP.Persistence/Repos/IBadgeRepo.cs | 4 +- 6 files changed, 290 insertions(+), 16 deletions(-) diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs index a7915fd4..2de0e03f 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs @@ -16,11 +16,20 @@ namespace TPP.Persistence.MongoDB.Tests.Repos { class BadgeBuyOfferRepoTest : MongoTestBase { - public BadgeBuyOfferRepo CreateBadgeBuyOfferRepo() + + private Mock _mockUserRepo = null!; + private Mock> _mockBank = null!; + private Mock _mockBadgeRepo = null!; + + public IBadgeBuyOfferRepo CreateBadgeBuyOfferRepo() { IMongoDatabase db = CreateTemporaryDatabase(); - BadgeRepo mockBadgeRepo = new BadgeRepo(db, Mock.Of(), Mock.Of()); - return new BadgeBuyOfferRepo(db, mockBadgeRepo, Mock.Of()); + _mockUserRepo = new Mock(); + _mockBank = new Mock>(); + _mockBadgeRepo = new Mock(); + + BadgeBuyOfferRepo badgeBuyOfferRepo = new BadgeBuyOfferRepo(db, _mockBadgeRepo.Object, _mockUserRepo.Object, _mockBank.Object, Mock.Of()); + return badgeBuyOfferRepo; } internal class MockClock : IClock @@ -30,11 +39,11 @@ internal class MockClock : IClock } [Test] - public async Task write_then_read_are_equal() + public async Task CreateBuyOffer_write_then_read_are_equal() { string userId = "m4"; PkmnSpecies species = PkmnSpecies.OfId("1"); - int form = 0; + string form = ""; Badge.BadgeSource source = Badge.BadgeSource.ManualCreation; bool shiny = true; int price = 999; @@ -52,5 +61,91 @@ public async Task write_then_read_are_equal() Assert.AreEqual(price, offer.Price); Assert.AreEqual(amount, offer.Amount); } + + [Test] + public async Task CreateSellOffer_sells_newest_badge() + { + string userId = "thelegend27"; + PkmnSpecies species = PkmnSpecies.OfId("1"); + string form = ""; + Badge.BadgeSource source = Badge.BadgeSource.ManualCreation; + bool shiny = true; + int price = 999; + Instant before = Instant.FromUtc(2000, 1, 1, 0, 0); + Instant after = before.PlusNanoseconds(69); + + IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + + Badge badgeA = new Badge("A", userId, species, source, before, form, shiny); + Badge badgeB = new Badge("B", userId, species, source, after, form, shiny); + Badge badgeBForSale = new Badge("B", userId, species, source, after, form, shiny) { SellPrice = price }; + List notForSale = new List { badgeA, badgeB }; + + _mockBadgeRepo.Setup(m => m.FindAllNotForSaleByCustom(userId, species, form, source, shiny)).Returns(Task.FromResult(notForSale)); + _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); + _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(badgeB, price)).Returns(Task.FromResult(badgeBForSale)); + + Badge selling = await badgeBuyOfferRepo.CreateSellOffer(userId, species, form, source, shiny, price); + + Assert.AreEqual(after, selling.CreatedAt); + } + + [Test] + public async Task CreateSellOffer_prioritizes_duplicates_of_forms_when_form_unspecified() + { + string userId = "shellosLuvr"; + PkmnSpecies species = PkmnSpecies.OfId("422"); + string formWest = "West Sea"; + string formEast = "East Sea"; + Badge.BadgeSource source = Badge.BadgeSource.RunCaught; + bool shiny = false; + Instant time = Instant.FromUtc(2006, 8, 28, 1, 0); + int price = 999999; + + Badge shellosWest = new Badge("A", userId, species, source, time.PlusNanoseconds(9000), formWest, shiny); + Badge shellosEastOlder = new Badge("B", userId, species, source, time.PlusNanoseconds(0), formEast, shiny); + Badge shellosEastNewer = new Badge("C", userId, species, source, time.PlusNanoseconds(1), formEast, shiny); + Badge shellosEastSelling = new Badge("C_sell", userId, species, source, time.PlusNanoseconds(1), formEast, shiny) { SellPrice = price }; + + IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + + List notForSale = new List() { shellosWest, shellosEastOlder, shellosEastNewer }; + + _mockBadgeRepo.Setup(m => m.FindAllNotForSaleByCustom(userId, species, null, source, shiny)).Returns(Task.FromResult(notForSale)); + _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); + _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(shellosEastNewer, price)).Returns(Task.FromResult(shellosEastSelling)); + + Badge selling = await badgeBuyOfferRepo.CreateSellOffer(userId, species, null, source, shiny, price); + + Assert.AreEqual(selling.Id, shellosEastSelling.Id); + } + + [Test] + public async Task new_buy_offer_is_filled_if_possible() + { + string sellerId = "seller"; + string buyerId = "buyer"; + PkmnSpecies species = PkmnSpecies.OfId("1"); + Badge.BadgeSource source = Badge.BadgeSource.RunCaught; + string? form = null; + bool shiny = false; + Instant time = Instant.FromUtc(1996, 2, 27, 0, 0); + int price = 1; + + Badge badgeA = new Badge("A", sellerId, species, source, time, form, shiny); + Badge badgeASelling = new Badge("A", sellerId, species, source, time, form, shiny) { SellPrice = price, SellingSince = time }; + + IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + + List sellerBadgesNotForSale = new List() { badgeA }; + _mockBadgeRepo.Setup(m => m.FindAllNotForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(sellerBadgesNotForSale)); + _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); + _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(badgeA, price)).Returns(Task.FromResult(badgeASelling)); + await badgeBuyOfferRepo.CreateSellOffer(sellerId, species, null, source, shiny, price); + await badgeBuyOfferRepo.CreateBuyOffer(buyerId, species, sellerId, source, shiny, price, 1, time.PlusNanoseconds(2)); + + int remainingBuyOffers = badgeBuyOfferRepo.FindAllByCustom(buyerId, species, null, source, shiny).Result.Count(); + Assert.AreEqual(0, remainingBuyOffers); + } } } diff --git a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs index 453aa211..31a7b32b 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using System.Collections.Immutable; using System.Threading.Tasks; using TPP.Common; using TPP.Persistence.Models; @@ -22,6 +23,10 @@ public class BadgeBuyOfferRepo : IBadgeBuyOfferRepo public readonly IMongoCollection Collection; private readonly IBadgeRepo _badgeRepo; private readonly IClock _clock; + private readonly IUserRepo _userRepo; + private readonly IBank _tokenBank; + private event EventHandler _MarketChangedEventHandler; + static BadgeBuyOfferRepo() { @@ -35,7 +40,7 @@ static BadgeBuyOfferRepo() cm.MapProperty(o => o.Source).SetElementName("source"); cm.MapProperty(o => o.CreatedAt).SetElementName("created_at"); cm.MapProperty(o => o.Form).SetElementName("form") - .SetDefaultValue(0) + .SetDefaultValue(null) .SetIgnoreIfDefault(true); cm.MapProperty(o => o.Shiny).SetElementName("shiny") .SetDefaultValue(false) @@ -43,16 +48,59 @@ static BadgeBuyOfferRepo() }); } - public BadgeBuyOfferRepo(IMongoDatabase database, BadgeRepo badgeRepo, IClock clock) + private void InitIndexes() { - _badgeRepo = badgeRepo; + Collection.Indexes.CreateMany(new[] + { + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.UserId)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Species)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Source)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.CreatedAt)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Form)), + new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Shiny)), + }); + } + public BadgeBuyOfferRepo(IMongoDatabase database, IBadgeRepo badgeRepo, IUserRepo userRepo, IBank bank, IClock clock) + { + _badgeRepo = badgeRepo; + _userRepo = userRepo; + _tokenBank = bank; database.CreateCollectionIfNotExists(CollectionName).Wait(); Collection = database.GetCollection(CollectionName); _clock = clock; + _MarketChangedEventHandler += MarketChanged; + InitIndexes(); + } + + private void OnMarketChangedEvent(MarketChangedEventArgs args) + { + EventHandler handler = _MarketChangedEventHandler; + handler.Invoke(this, args); + } + + private async void MarketChanged(object? sender, MarketChangedEventArgs args) => await ResolveBuyOffers(args.Species, args.Shiny); + + public async Task> FindAllByCustom(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny) + { + FilterDefinition filter = Builders.Filter.Empty; + if (userId != null) + filter &= Builders.Filter.Eq(b => b.UserId, userId); + if (species != null) + filter &= Builders.Filter.Eq(b => b.Species, species); + if (form != null) + filter &= Builders.Filter.Eq(b => b.Form, form); + if (source != null) + filter &= Builders.Filter.Eq(b => b.Source, source); + if (shiny == true) + filter &= Builders.Filter.Eq(b => b.Shiny, true); + else + filter &= Builders.Filter.Eq(b => b.Shiny, false); + + return await Collection.Find(filter).ToListAsync(); } - public async Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt = null) + public async Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt = null) { BadgeBuyOffer buyOffer = new BadgeBuyOffer( id: string.Empty, @@ -68,8 +116,129 @@ public async Task CreateBuyOffer(string userId, PkmnSpecies speci await Collection.InsertOneAsync(buyOffer); Debug.Assert(buyOffer.Id.Length > 0, "The MongoDB driver injected a generated ID"); + OnMarketChangedEvent(new MarketChangedEventArgs(species, shiny)); return buyOffer; } + public async Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price) + { + List notSellingOwnedByUser = await _badgeRepo.FindAllNotForSaleByCustom(userId, species, form, source, shiny); + Badge toSell = sortBySpecialness(notSellingOwnedByUser).First(); + Badge selling = await _badgeRepo.SetBadgeSellPrice(toSell, price); + OnMarketChangedEvent(new MarketChangedEventArgs(species, shiny)); + return selling; + } + + private async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) + { + List buyOffers = await FindAllByCustom(null, species, null, null, shiny); + List badgesForSale = await _badgeRepo.FindAllForSaleByCustom(null, species, null, null, shiny); + + badgesForSale = badgesForSale.OrderByDescending(b => b.SellingSince).ToList(); + + bool badgeSold = false; + foreach (Badge badge in badgesForSale) + { + foreach (BadgeBuyOffer offer in buyOffers) + { + if (offer.UserId == badge.UserId) + continue; //user shouldn't sell to themself + if (badge.UserId == null) + throw new OwnedBadgeNotFoundException(badge); + if (((offer.Form != null) && (offer.Form != badge.Form)) + || ((offer.Source != null) && (offer.Source != badge.Source))) + continue; + + User? buyer = await _userRepo.FindById(offer.UserId); + User? seller = await _userRepo.FindById(badge.UserId); + if (buyer == null) + throw new UserNotFoundException(offer.UserId); + if (seller == null) + throw new UserNotFoundException(badge.UserId); + await _tokenBank.PerformTransactions( + new Transaction[] + { + new Transaction(buyer, -1 * offer.Price, "BadgePurchase"), + new Transaction(seller, offer.Price, "BadgeSale") + } + ); + + await _badgeRepo.TransferBadges(new List { badge }.ToImmutableList(), buyer.Id, "BadgeSale", new Dictionary() { }); + badgeSold = true; + await ResetUserSellOffers(badge.UserId, badge.Species, badge.Form, badge.Shiny); + + offer.decrement(_clock.GetCurrentInstant()); + if (offer.Amount > 0) + await Collection.FindOneAndReplaceAsync(o => o.Id == offer.Id, offer); + else + { + await Collection.FindOneAndDeleteAsync(o => o.Id == offer.Id); + break; + } + } + if (badgeSold) + break; //the market info we have is now out of date + } + if (badgeSold) + await ResolveBuyOffers(species, shiny); //get new market info and continue resolving offers + } + /// + /// Sorts badges according to the priority in which they should be sold. + /// Current sorting rule: prioritize keeping 1 of each form, then sell newer badges first. Top priority badge will be selected for sale when fufilling offers. + /// + private IEnumerable sortBySpecialness(IEnumerable toSort) + { + IEnumerable duplicates; + IEnumerable uniques = new List(); + HashSet formNames = new HashSet(); + foreach (Badge b in toSort) + { + formNames.Add(b.Form); + } + + foreach (string? form in formNames) + { + IEnumerable ofSingleForm = toSort.Where(b => b.Form == form); + ofSingleForm = ofSingleForm.OrderByDescending(b => b.CreatedAt); + uniques = uniques.Append(ofSingleForm.Last()); + } + + duplicates = toSort.Except(uniques); + duplicates = duplicates.OrderByDescending(b => b.CreatedAt); + uniques = uniques.OrderByDescending(b => b.CreatedAt); + + IEnumerable result = duplicates; + foreach (Badge b in uniques) + { + result = result.Append(b); + } + return result; + } + + /// + /// Refresh user sell offers of a particular species and form. Moves them to the back of the line to fufill orders. + /// + private async Task ResetUserSellOffers(string userId, PkmnSpecies species, string? form, bool shiny) + { + List forSale = await _badgeRepo.FindAllForSaleByCustom(userId, species, form, null, shiny); + foreach (Badge b in forSale) + { + if (b.SellPrice == null) + throw new OwnedBadgeNotFoundException(b); + await _badgeRepo.SetBadgeSellPrice(b, (long)b.SellPrice); + } + } + } + + public class MarketChangedEventArgs : EventArgs + { + public PkmnSpecies Species { get; init; } + public bool? Shiny { get; init; } + + public MarketChangedEventArgs(PkmnSpecies species, bool? shiny) + { + Species = species; + Shiny = shiny; + } } } diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index 92215ca0..a96f42d2 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -121,6 +121,12 @@ public async Task> FindAllForSaleByCustom(string? userId, PkmnSpecie return all.Where(b => b.SellPrice > 0).ToList(); } + public async Task> FindAllNotForSaleByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny) + { + List all = await FindAllByCustom(userId, species, form, source, shiny); + return all.Where(b => b.SellPrice == 0).ToList(); + } + public async Task CountByUserAndSpecies(string? userId, PkmnSpecies species) => await Collection.CountDocumentsAsync(b => b.UserId == userId && b.Species == species); @@ -194,8 +200,7 @@ await _badgeLogRepo.LogWithSession( } return updatedBadges.ToImmutableList(); } - - public async Task SetBadgeSellPrice(Badge badge, int price) + public async Task SetBadgeSellPrice(Badge badge, long price) { if (price <= 0) throw new ArgumentOutOfRangeException("price", "Price must be positive"); @@ -203,7 +208,8 @@ public async Task SetBadgeSellPrice(Badge badge, int price) Builders.Filter .Where(b => b.Id == badge.Id && b.UserId == badge.UserId), Builders.Update - .Set(b => b.SellPrice, price), + .Set(b => b.SellPrice, price) + .Set(b => b.SellingSince, _clock.GetCurrentInstant()), new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, IsUpsert = false } ) ?? throw new OwnedBadgeNotFoundException(badge); } diff --git a/TPP.Persistence/Models/BadgeBuyOffer.cs b/TPP.Persistence/Models/BadgeBuyOffer.cs index ea868c19..26a287a9 100644 --- a/TPP.Persistence/Models/BadgeBuyOffer.cs +++ b/TPP.Persistence/Models/BadgeBuyOffer.cs @@ -28,7 +28,7 @@ public class BadgeBuyOffer /// /// The form of pokemon to buy. /// - public int? Form { get; init; } + public string? Form { get; init; } /// /// The source of the badge to buy. @@ -62,7 +62,7 @@ public class BadgeBuyOffer //duration and expires_at depricated from old core - public BadgeBuyOffer(string id, string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant createdAt) + public BadgeBuyOffer(string id, string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant createdAt) { Id = id; UserId = userId; diff --git a/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs b/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs index ecc19cf5..0fc7ad8e 100644 --- a/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs +++ b/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs @@ -11,6 +11,8 @@ namespace TPP.Persistence.Repos { public interface IBadgeBuyOfferRepo { - public Task CreateBuyOffer(string userId, PkmnSpecies species, int? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); + Task> FindAllByCustom(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); + Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); + Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price); } } diff --git a/TPP.Persistence/Repos/IBadgeRepo.cs b/TPP.Persistence/Repos/IBadgeRepo.cs index f86fa1a9..8fa5621e 100644 --- a/TPP.Persistence/Repos/IBadgeRepo.cs +++ b/TPP.Persistence/Repos/IBadgeRepo.cs @@ -52,7 +52,9 @@ public Task> TransferBadges( IImmutableList badges, string? recipientUserId, string reason, IDictionary additionalData); - public Task SetBadgeSellPrice(Badge badge, int price); + public Task SetBadgeSellPrice(Badge badge, long price); public Task> FindAllForSaleByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny); + + public Task> FindAllNotForSaleByCustom(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny); } } From 641bc4c45233c6bb323f42be2cbef28838467f1d Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 17 Sep 2021 03:18:39 -0400 Subject: [PATCH 15/19] Rename BadgeBuyOfferRepo -> BadgeMarketRepo, fixes --- ...fferRepoTest.cs => BadgeMarketRepoTest.cs} | 96 ++++++++++++--- ...adgeBuyOfferRepo.cs => BadgeMarketRepo.cs} | 112 +++++++++--------- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 10 +- TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs | 18 --- TPP.Persistence/Repos/IBadgeMarketRepo.cs | 22 ++++ 5 files changed, 164 insertions(+), 94 deletions(-) rename TPP.Persistence.MongoDB.Tests/Repos/{BadgeBuyOfferRepoTest.cs => BadgeMarketRepoTest.cs} (52%) rename TPP.Persistence.MongoDB/Repos/{BadgeBuyOfferRepo.cs => BadgeMarketRepo.cs} (70%) delete mode 100644 TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs create mode 100644 TPP.Persistence/Repos/IBadgeMarketRepo.cs diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs similarity index 52% rename from TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs rename to TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs index 2de0e03f..2267775f 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeBuyOfferRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs @@ -14,22 +14,22 @@ namespace TPP.Persistence.MongoDB.Tests.Repos { - class BadgeBuyOfferRepoTest : MongoTestBase + class BadgeMarketRepoTest : MongoTestBase { private Mock _mockUserRepo = null!; private Mock> _mockBank = null!; private Mock _mockBadgeRepo = null!; - public IBadgeBuyOfferRepo CreateBadgeBuyOfferRepo() + public IBadgeMarketRepo CreateBadgeMarketRepo() { IMongoDatabase db = CreateTemporaryDatabase(); _mockUserRepo = new Mock(); _mockBank = new Mock>(); _mockBadgeRepo = new Mock(); - BadgeBuyOfferRepo badgeBuyOfferRepo = new BadgeBuyOfferRepo(db, _mockBadgeRepo.Object, _mockUserRepo.Object, _mockBank.Object, Mock.Of()); - return badgeBuyOfferRepo; + BadgeMarketRepo badgeMarketRepo = new BadgeMarketRepo(db, _mockBadgeRepo.Object, _mockUserRepo.Object, _mockBank.Object, Mock.Of()); + return badgeMarketRepo; } internal class MockClock : IClock @@ -49,9 +49,11 @@ public async Task CreateBuyOffer_write_then_read_are_equal() int price = 999; int amount = 1; - IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); - BadgeBuyOffer offer = await badgeBuyOfferRepo.CreateBuyOffer(userId, species, form, source, shiny, price, amount, Instant.MaxValue); + _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); + + BadgeBuyOffer offer = await badgeMarketRepo.CreateBuyOffer(userId, species, form, source, shiny, price, amount, Instant.MaxValue); Assert.AreEqual(userId, offer.UserId); Assert.AreEqual(species, offer.Species); @@ -74,7 +76,7 @@ public async Task CreateSellOffer_sells_newest_badge() Instant before = Instant.FromUtc(2000, 1, 1, 0, 0); Instant after = before.PlusNanoseconds(69); - IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); Badge badgeA = new Badge("A", userId, species, source, before, form, shiny); Badge badgeB = new Badge("B", userId, species, source, after, form, shiny); @@ -85,7 +87,7 @@ public async Task CreateSellOffer_sells_newest_badge() _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(badgeB, price)).Returns(Task.FromResult(badgeBForSale)); - Badge selling = await badgeBuyOfferRepo.CreateSellOffer(userId, species, form, source, shiny, price); + Badge selling = await badgeMarketRepo.CreateSellOffer(userId, species, form, source, shiny, price); Assert.AreEqual(after, selling.CreatedAt); } @@ -107,7 +109,7 @@ public async Task CreateSellOffer_prioritizes_duplicates_of_forms_when_form_unsp Badge shellosEastNewer = new Badge("C", userId, species, source, time.PlusNanoseconds(1), formEast, shiny); Badge shellosEastSelling = new Badge("C_sell", userId, species, source, time.PlusNanoseconds(1), formEast, shiny) { SellPrice = price }; - IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); List notForSale = new List() { shellosWest, shellosEastOlder, shellosEastNewer }; @@ -115,13 +117,13 @@ public async Task CreateSellOffer_prioritizes_duplicates_of_forms_when_form_unsp _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(shellosEastNewer, price)).Returns(Task.FromResult(shellosEastSelling)); - Badge selling = await badgeBuyOfferRepo.CreateSellOffer(userId, species, null, source, shiny, price); + Badge selling = await badgeMarketRepo.CreateSellOffer(userId, species, null, source, shiny, price); Assert.AreEqual(selling.Id, shellosEastSelling.Id); } [Test] - public async Task new_buy_offer_is_filled_if_possible() + public async Task ResolveBuyOffers_fills_outstanding_buy_offer() { string sellerId = "seller"; string buyerId = "buyer"; @@ -131,21 +133,81 @@ public async Task new_buy_offer_is_filled_if_possible() bool shiny = false; Instant time = Instant.FromUtc(1996, 2, 27, 0, 0); int price = 1; - + int amount = 1; + User buyer = new User(buyerId, buyerId, buyerId, buyerId, null, Instant.MinValue, Instant.MinValue, null, 1000, price); + User seller = new User(sellerId, sellerId, sellerId, sellerId, null, Instant.MinValue, Instant.MinValue, null, 1000, 0); Badge badgeA = new Badge("A", sellerId, species, source, time, form, shiny); Badge badgeASelling = new Badge("A", sellerId, species, source, time, form, shiny) { SellPrice = price, SellingSince = time }; - IBadgeBuyOfferRepo badgeBuyOfferRepo = CreateBadgeBuyOfferRepo(); + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); List sellerBadgesNotForSale = new List() { badgeA }; _mockBadgeRepo.Setup(m => m.FindAllNotForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(sellerBadgesNotForSale)); _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); _mockBadgeRepo.Setup(m => m.SetBadgeSellPrice(badgeA, price)).Returns(Task.FromResult(badgeASelling)); - await badgeBuyOfferRepo.CreateSellOffer(sellerId, species, null, source, shiny, price); - await badgeBuyOfferRepo.CreateBuyOffer(buyerId, species, sellerId, source, shiny, price, 1, time.PlusNanoseconds(2)); + Badge selling = await badgeMarketRepo.CreateSellOffer(sellerId, species, form, source, shiny, price); + + _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List() { selling })); + _mockUserRepo.Setup(m => m.FindById(buyerId)).Returns(Task.FromResult((User?)buyer)); + _mockUserRepo.Setup(m => m.FindById(sellerId)).Returns(Task.FromResult((User?)seller)); + _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(sellerId, species, form, null, shiny)).Returns(Task.FromResult(new List())); + + await badgeMarketRepo.CreateBuyOffer(buyerId, species, form, source, shiny, price, amount, time.PlusNanoseconds(2)); + + List remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); + Assert.AreEqual(1, remainingBuyOffers.Count); + + await badgeMarketRepo.ResolveBuyOffers(species, shiny); + + remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); + Assert.AreEqual(0, remainingBuyOffers.Count); + } + + [Test] + public async Task DeleteBuyOffer_removes_outstanding_buy_offer() + { + string buyerId = "buyer"; + PkmnSpecies species = PkmnSpecies.OfId("1"); + Badge.BadgeSource? source = null; + string? form = null; + bool shiny = false; + Instant time = Instant.FromUtc(0,1,1,0,0); + int price = 1; + int amount = 1; + + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); + _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(null, species, null, null, shiny)).Returns(Task.FromResult(new List())); + + await badgeMarketRepo.CreateBuyOffer(buyerId, species, form, source, shiny, price, amount, time); + Assert.AreEqual(1, badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny).Result.Count); + + await badgeMarketRepo.DeleteBuyOffer(buyerId, species, form, source, shiny, amount); + Assert.AreEqual(0, badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny).Result.Count); + } + + [Test] + public async Task DeleteSellOffer_removes_outstanding_sell_offer() + { + string sellerId = "buyer"; + PkmnSpecies species = PkmnSpecies.OfId("1"); + Badge.BadgeSource source = Badge.BadgeSource.Pinball; + string? form = null; + bool shiny = false; + Instant time = Instant.FromUtc(0,1,1,0,0); + long price = 1; + Badge badgeForSale = new Badge("A", sellerId, species, source, time, form, shiny){ SellingSince=time, SellPrice=price}; + Badge badgeNotForSale = new Badge("A", sellerId, species, source, time, form, shiny); + + IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); + + _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(new List() { badgeForSale } )); + _mockBadgeRepo.Setup(r => r.SetBadgeSellPrice(badgeForSale, 0)).Returns(Task.FromResult(badgeNotForSale)); + await badgeMarketRepo.DeleteSellOffer(sellerId, species, form, source, shiny, 1); + + _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(new List())); + List badgesForSale = await badgeMarketRepo.FindAllBadgesForSale(sellerId, species, form, source, shiny); - int remainingBuyOffers = badgeBuyOfferRepo.FindAllByCustom(buyerId, species, null, source, shiny).Result.Count(); - Assert.AreEqual(0, remainingBuyOffers); + Assert.AreEqual(0, badgesForSale.Count); } } } diff --git a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs similarity index 70% rename from TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs rename to TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs index 31a7b32b..896557db 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeBuyOfferRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs @@ -16,19 +16,16 @@ namespace TPP.Persistence.MongoDB.Repos { - public class BadgeBuyOfferRepo : IBadgeBuyOfferRepo + public class BadgeMarketRepo : IBadgeMarketRepo { - private const string CollectionName = "badgebuyoffers"; - - public readonly IMongoCollection Collection; + private const string BuyOfferCollectionName = "badgebuyoffers"; + public readonly IMongoCollection BuyOfferCollection; private readonly IBadgeRepo _badgeRepo; private readonly IClock _clock; private readonly IUserRepo _userRepo; private readonly IBank _tokenBank; - private event EventHandler _MarketChangedEventHandler; - - static BadgeBuyOfferRepo() + static BadgeMarketRepo() { BsonClassMap.RegisterClassMap(cm => { @@ -39,9 +36,10 @@ static BadgeBuyOfferRepo() cm.MapProperty(o => o.Species).SetElementName("species"); cm.MapProperty(o => o.Source).SetElementName("source"); cm.MapProperty(o => o.CreatedAt).SetElementName("created_at"); + cm.MapProperty(o => o.Price).SetElementName("price"); + cm.MapProperty(o => o.Amount).SetElementName("amount"); cm.MapProperty(o => o.Form).SetElementName("form") - .SetDefaultValue(null) - .SetIgnoreIfDefault(true); + .SetIgnoreIfNull(true); cm.MapProperty(o => o.Shiny).SetElementName("shiny") .SetDefaultValue(false) .SetIgnoreIfDefault(true); @@ -50,7 +48,7 @@ static BadgeBuyOfferRepo() private void InitIndexes() { - Collection.Indexes.CreateMany(new[] + BuyOfferCollection.Indexes.CreateMany(new[] { new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.UserId)), new CreateIndexModel(Builders.IndexKeys.Ascending(u => u.Species)), @@ -61,27 +59,18 @@ private void InitIndexes() }); } - public BadgeBuyOfferRepo(IMongoDatabase database, IBadgeRepo badgeRepo, IUserRepo userRepo, IBank bank, IClock clock) + public BadgeMarketRepo(IMongoDatabase database, IBadgeRepo badgeRepo, IUserRepo userRepo, IBank bank, IClock clock) { _badgeRepo = badgeRepo; _userRepo = userRepo; _tokenBank = bank; - database.CreateCollectionIfNotExists(CollectionName).Wait(); - Collection = database.GetCollection(CollectionName); + database.CreateCollectionIfNotExists(BuyOfferCollectionName).Wait(); + BuyOfferCollection = database.GetCollection(BuyOfferCollectionName); _clock = clock; - _MarketChangedEventHandler += MarketChanged; InitIndexes(); } - private void OnMarketChangedEvent(MarketChangedEventArgs args) - { - EventHandler handler = _MarketChangedEventHandler; - handler.Invoke(this, args); - } - - private async void MarketChanged(object? sender, MarketChangedEventArgs args) => await ResolveBuyOffers(args.Species, args.Shiny); - - public async Task> FindAllByCustom(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny) + public async Task> FindAllBuyOffers(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny) { FilterDefinition filter = Builders.Filter.Empty; if (userId != null) @@ -95,9 +84,14 @@ public async Task> FindAllByCustom(string? userId, PkmnSpeci if (shiny == true) filter &= Builders.Filter.Eq(b => b.Shiny, true); else - filter &= Builders.Filter.Eq(b => b.Shiny, false); + filter &= Builders.Filter.Ne(b => b.Shiny, true); - return await Collection.Find(filter).ToListAsync(); + return await BuyOfferCollection.Find(filter).ToListAsync(); + } + + public async Task> FindAllBadgesForSale(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny) + { + return await _badgeRepo.FindAllForSaleByCustom(userId, species, form, source, shiny); } public async Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt = null) @@ -114,31 +108,56 @@ public async Task CreateBuyOffer(string userId, PkmnSpecies speci createdAt: createdAt ?? _clock.GetCurrentInstant() ); - await Collection.InsertOneAsync(buyOffer); + await BuyOfferCollection.InsertOneAsync(buyOffer); Debug.Assert(buyOffer.Id.Length > 0, "The MongoDB driver injected a generated ID"); - OnMarketChangedEvent(new MarketChangedEventArgs(species, shiny)); return buyOffer; } public async Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price) { List notSellingOwnedByUser = await _badgeRepo.FindAllNotForSaleByCustom(userId, species, form, source, shiny); - Badge toSell = sortBySpecialness(notSellingOwnedByUser).First(); + Badge toSell = SortBySpecialness(notSellingOwnedByUser).First(); Badge selling = await _badgeRepo.SetBadgeSellPrice(toSell, price); - OnMarketChangedEvent(new MarketChangedEventArgs(species, shiny)); return selling; } - private async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) + public async Task DeleteBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount) + { + List offers = await FindAllBuyOffers(userId, species, form, source, shiny); + if(offers.Count() < amount) + throw new ArgumentException(string.Format("Tried to cancel {0} offers but only {1} were found", amount, offers.Count)); + + offers = offers.OrderByDescending(o => o.CreatedAt).ToList(); + for(int i=0; i < amount; i++) + { + await BuyOfferCollection.FindOneAndDeleteAsync(o => o.Id == offers[i].Id); + } + } + public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount) + { + List badgesForSale = await _badgeRepo.FindAllForSaleByCustom(userId, species, form, source, shiny); + if (amount > badgesForSale.Count) + throw new ArgumentException(string.Format("Tried to cancel {0} offers but only {1} were found", amount, badgesForSale.Count)); + + badgesForSale = SortBySpecialness(badgesForSale); + badgesForSale.Reverse(); + for(int i=0; i buyOffers = await FindAllByCustom(null, species, null, null, shiny); List badgesForSale = await _badgeRepo.FindAllForSaleByCustom(null, species, null, null, shiny); badgesForSale = badgesForSale.OrderByDescending(b => b.SellingSince).ToList(); - bool badgeSold = false; foreach (Badge badge in badgesForSale) { + List buyOffers = await FindAllBuyOffers(null, species, null, null, shiny); + buyOffers = buyOffers.Where(o => o.Price >= badge.SellPrice).ToList(); + foreach (BadgeBuyOffer offer in buyOffers) { if (offer.UserId == badge.UserId) @@ -164,29 +183,22 @@ await _tokenBank.PerformTransactions( ); await _badgeRepo.TransferBadges(new List { badge }.ToImmutableList(), buyer.Id, "BadgeSale", new Dictionary() { }); - badgeSold = true; await ResetUserSellOffers(badge.UserId, badge.Species, badge.Form, badge.Shiny); offer.decrement(_clock.GetCurrentInstant()); if (offer.Amount > 0) - await Collection.FindOneAndReplaceAsync(o => o.Id == offer.Id, offer); + await BuyOfferCollection.FindOneAndReplaceAsync(o => o.Id == offer.Id, offer); else - { - await Collection.FindOneAndDeleteAsync(o => o.Id == offer.Id); - break; - } + await BuyOfferCollection.FindOneAndDeleteAsync(o => o.Id == offer.Id); + break; //this badge has been sold, ignore the rest of the offers } - if (badgeSold) - break; //the market info we have is now out of date } - if (badgeSold) - await ResolveBuyOffers(species, shiny); //get new market info and continue resolving offers } /// /// Sorts badges according to the priority in which they should be sold. /// Current sorting rule: prioritize keeping 1 of each form, then sell newer badges first. Top priority badge will be selected for sale when fufilling offers. /// - private IEnumerable sortBySpecialness(IEnumerable toSort) + private static List SortBySpecialness(IEnumerable toSort) { IEnumerable duplicates; IEnumerable uniques = new List(); @@ -212,7 +224,7 @@ private IEnumerable sortBySpecialness(IEnumerable toSort) { result = result.Append(b); } - return result; + return result.ToList(); } /// @@ -229,16 +241,4 @@ private async Task ResetUserSellOffers(string userId, PkmnSpecies species, strin } } } - - public class MarketChangedEventArgs : EventArgs - { - public PkmnSpecies Species { get; init; } - public bool? Shiny { get; init; } - - public MarketChangedEventArgs(PkmnSpecies species, bool? shiny) - { - Species = species; - Shiny = shiny; - } - } } diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index a96f42d2..d33f9835 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -201,15 +201,19 @@ await _badgeLogRepo.LogWithSession( return updatedBadges.ToImmutableList(); } public async Task SetBadgeSellPrice(Badge badge, long price) - { + { if (price <= 0) throw new ArgumentOutOfRangeException("price", "Price must be positive"); + + long? sellPrice = price == 0 ? null : price; + Instant? sellingSince = sellPrice == null ? null : _clock.GetCurrentInstant(); + return await Collection.FindOneAndUpdateAsync( Builders.Filter .Where(b => b.Id == badge.Id && b.UserId == badge.UserId), Builders.Update - .Set(b => b.SellPrice, price) - .Set(b => b.SellingSince, _clock.GetCurrentInstant()), + .Set(b => b.SellPrice, sellPrice) + .Set(b => b.SellingSince, sellingSince), new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, IsUpsert = false } ) ?? throw new OwnedBadgeNotFoundException(badge); } diff --git a/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs b/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs deleted file mode 100644 index 0fc7ad8e..00000000 --- a/TPP.Persistence/Repos/IBadgeBuyOfferRepo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NodaTime; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TPP.Common; -using TPP.Persistence.Models; - -namespace TPP.Persistence.Repos -{ - public interface IBadgeBuyOfferRepo - { - Task> FindAllByCustom(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); - Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); - Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price); - } -} diff --git a/TPP.Persistence/Repos/IBadgeMarketRepo.cs b/TPP.Persistence/Repos/IBadgeMarketRepo.cs new file mode 100644 index 00000000..a0113b10 --- /dev/null +++ b/TPP.Persistence/Repos/IBadgeMarketRepo.cs @@ -0,0 +1,22 @@ +using NodaTime; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TPP.Common; +using TPP.Persistence.Models; + +namespace TPP.Persistence.Repos +{ + public interface IBadgeMarketRepo + { + Task> FindAllBuyOffers(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); + Task> FindAllBadgesForSale(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); + Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); + Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price); + Task DeleteBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount); + Task DeleteSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount); + Task ResolveBuyOffers(PkmnSpecies species, bool? shiny); + } +} From a3f0c525b9f717d4ca91755905a4b1367cc8b8eb Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Fri, 17 Sep 2021 03:18:39 -0400 Subject: [PATCH 16/19] Rename BadgeBuyOfferRepo -> BadgeMarketRepo, fixes --- TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs index 896557db..5466eae3 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs @@ -135,7 +135,7 @@ public async Task DeleteBuyOffer(string userId, PkmnSpecies species, string? for } public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount) { - List badgesForSale = await _badgeRepo.FindAllForSaleByCustom(userId, species, form, source, shiny); + List badgesForSale = await FindAllBadgesForSale(userId, species, form, source, shiny); if (amount > badgesForSale.Count) throw new ArgumentException(string.Format("Tried to cancel {0} offers but only {1} were found", amount, badgesForSale.Count)); @@ -149,7 +149,7 @@ public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? fo public async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) { - List badgesForSale = await _badgeRepo.FindAllForSaleByCustom(null, species, null, null, shiny); + List badgesForSale = await FindAllBadgesForSale(null, species, null, null, shiny); badgesForSale = badgesForSale.OrderByDescending(b => b.SellingSince).ToList(); @@ -232,7 +232,7 @@ private static List SortBySpecialness(IEnumerable toSort) /// private async Task ResetUserSellOffers(string userId, PkmnSpecies species, string? form, bool shiny) { - List forSale = await _badgeRepo.FindAllForSaleByCustom(userId, species, form, null, shiny); + List forSale = await FindAllBadgesForSale(userId, species, form, null, shiny); foreach (Badge b in forSale) { if (b.SellPrice == null) From 3d0038e267bfdd239a919389c743b5b253775d6b Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Sat, 18 Sep 2021 17:00:34 -0400 Subject: [PATCH 17/19] try to fill buy offers into the highest sell offer --- TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs index 5466eae3..8f7fd1b6 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs @@ -151,12 +151,13 @@ public async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) { List badgesForSale = await FindAllBadgesForSale(null, species, null, null, shiny); - badgesForSale = badgesForSale.OrderByDescending(b => b.SellingSince).ToList(); + badgesForSale = badgesForSale.OrderByDescending(b => b.SellPrice).ThenBy(b => b.SellingSince).ToList(); foreach (Badge badge in badgesForSale) { List buyOffers = await FindAllBuyOffers(null, species, null, null, shiny); buyOffers = buyOffers.Where(o => o.Price >= badge.SellPrice).ToList(); + buyOffers = buyOffers.OrderBy(o => o.WaitingSince).ToList(); foreach (BadgeBuyOffer offer in buyOffers) { From 56cbc3607c6d1dfe3ba067a5879a10404cc17e6b Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Sat, 2 Oct 2021 07:08:33 -0400 Subject: [PATCH 18/19] BadgeMarketRepo.ResolveBuyOffers returns sales --- .../Repos/BadgeMarketRepoTest.cs | 7 ++++++- TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs | 12 ++++++++---- TPP.Persistence/Repos/IBadgeMarketRepo.cs | 4 +++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs b/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs index 2267775f..ffb965c5 100644 --- a/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs +++ b/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs @@ -157,10 +157,15 @@ public async Task ResolveBuyOffers_fills_outstanding_buy_offer() List remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); Assert.AreEqual(1, remainingBuyOffers.Count); - await badgeMarketRepo.ResolveBuyOffers(species, shiny); + var soldBadges = await badgeMarketRepo.ResolveBuyOffers(species, shiny); remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); Assert.AreEqual(0, remainingBuyOffers.Count); + Assert.AreEqual(1, soldBadges.Count); + Assert.AreEqual(seller, soldBadges[0].seller); + Assert.AreEqual(buyer, soldBadges[0].buyer); + Assert.AreEqual(badgeASelling, soldBadges[0].soldBadge); + Assert.AreEqual(price, soldBadges[0].price); } [Test] diff --git a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs index 8f7fd1b6..d00d08a9 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs @@ -147,11 +147,11 @@ public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? fo } } - public async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) + public async Task> ResolveBuyOffers(PkmnSpecies species, bool? shiny) { List badgesForSale = await FindAllBadgesForSale(null, species, null, null, shiny); - badgesForSale = badgesForSale.OrderByDescending(b => b.SellPrice).ThenBy(b => b.SellingSince).ToList(); + List soldBadges = new List(); foreach (Badge badge in badgesForSale) { @@ -159,6 +159,9 @@ public async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) buyOffers = buyOffers.Where(o => o.Price >= badge.SellPrice).ToList(); buyOffers = buyOffers.OrderBy(o => o.WaitingSince).ToList(); + if(badge.SellPrice == null) + throw new InvalidOperationException("Tried to sell a badge with no sell price"); + foreach (BadgeBuyOffer offer in buyOffers) { if (offer.UserId == badge.UserId) @@ -175,6 +178,8 @@ public async Task ResolveBuyOffers(PkmnSpecies species, bool? shiny) throw new UserNotFoundException(offer.UserId); if (seller == null) throw new UserNotFoundException(badge.UserId); + + soldBadges.Add(new IBadgeMarketRepo.BadgeSale(seller, buyer, badge, (long)badge.SellPrice)); await _tokenBank.PerformTransactions( new Transaction[] { @@ -182,10 +187,8 @@ await _tokenBank.PerformTransactions( new Transaction(seller, offer.Price, "BadgeSale") } ); - await _badgeRepo.TransferBadges(new List { badge }.ToImmutableList(), buyer.Id, "BadgeSale", new Dictionary() { }); await ResetUserSellOffers(badge.UserId, badge.Species, badge.Form, badge.Shiny); - offer.decrement(_clock.GetCurrentInstant()); if (offer.Amount > 0) await BuyOfferCollection.FindOneAndReplaceAsync(o => o.Id == offer.Id, offer); @@ -194,6 +197,7 @@ await _tokenBank.PerformTransactions( break; //this badge has been sold, ignore the rest of the offers } } + return soldBadges.ToImmutableList(); } /// /// Sorts badges according to the priority in which they should be sold. diff --git a/TPP.Persistence/Repos/IBadgeMarketRepo.cs b/TPP.Persistence/Repos/IBadgeMarketRepo.cs index a0113b10..0f830124 100644 --- a/TPP.Persistence/Repos/IBadgeMarketRepo.cs +++ b/TPP.Persistence/Repos/IBadgeMarketRepo.cs @@ -6,17 +6,19 @@ using System.Threading.Tasks; using TPP.Common; using TPP.Persistence.Models; +using System.Collections.Immutable; namespace TPP.Persistence.Repos { public interface IBadgeMarketRepo { + record BadgeSale(User seller, User buyer, Badge soldBadge, long price); Task> FindAllBuyOffers(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); Task> FindAllBadgesForSale(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price); Task DeleteBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount); Task DeleteSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount); - Task ResolveBuyOffers(PkmnSpecies species, bool? shiny); + Task> ResolveBuyOffers(PkmnSpecies species, bool? shiny); } } From 7073e32449c6ae7a7e8172de872f6c00093b6224 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Sun, 3 Oct 2021 15:26:02 -0400 Subject: [PATCH 19/19] WIP expand commands to use metadata --- TPP.ArgsParsing/TypeParsers/FormParser.cs | 2 +- .../Commands/Definitions/BadgeCommands.cs | 135 +++++++++++------- TPP.Core/Setups.cs | 11 +- .../Repos/BadgeMarketRepo.cs | 16 +-- TPP.Persistence.MongoDB/Repos/BadgeRepo.cs | 2 +- TPP.Persistence/IBadgeMarketRepo.cs | 4 +- .../Commands/Definitions/BadgeCommandsTest.cs | 98 ++++++++----- .../Repos/BadgeMarketRepoTest.cs | 14 +- 8 files changed, 174 insertions(+), 108 deletions(-) diff --git a/TPP.ArgsParsing/TypeParsers/FormParser.cs b/TPP.ArgsParsing/TypeParsers/FormParser.cs index 2b3f2b33..2ffc9310 100644 --- a/TPP.ArgsParsing/TypeParsers/FormParser.cs +++ b/TPP.ArgsParsing/TypeParsers/FormParser.cs @@ -8,7 +8,7 @@ namespace TPP.ArgsParsing.TypeParsers { - class FormParser : IArgumentParser + public class FormParser : IArgumentParser { /// /// Keys and values are in the format: lowercase unspaced name | Capitalized display name. diff --git a/TPP.Core/Commands/Definitions/BadgeCommands.cs b/TPP.Core/Commands/Definitions/BadgeCommands.cs index fc940c41..695d6063 100644 --- a/TPP.Core/Commands/Definitions/BadgeCommands.cs +++ b/TPP.Core/Commands/Definitions/BadgeCommands.cs @@ -31,7 +31,7 @@ public class BadgeCommands : ICommandCollection new Command("badges", Badges) { Aliases = new[] { "badge" }, - Description = "Show a user's badges. Argument: (optional) (optional)" + Description = "Show a user's badges. Arguments: (optional) (optional)" }, new Command("unselectbadge", UnselectBadge) @@ -55,17 +55,18 @@ public class BadgeCommands : ICommandCollection new Command("giftbadge", GiftBadge) { Description = - "Gift a badge you own to another user with no price. Arguments: (Optional) (optional) (optional) (optional)" + "Gift a badge you own to another user with no price. Arguments: (Optional) (optional) (optional) (optional)" }, new Command("listsellbadge", ListSellBadge) { Description = - "List the badges someone is selling. Arguments: (optional) (optional) (optional) (optional)" + "List the badges someone is selling. Arguments: (optional) (optional) (optional) (optional) (optional)" } }; private readonly IBadgeRepo _badgeRepo; private readonly IUserRepo _userRepo; + private readonly IBadgeMarketRepo _badgeMarketRepo; private readonly IMessageSender _messageSender; private readonly HashSet? _whitelist; private readonly IImmutableSet _knownSpecies; @@ -74,6 +75,7 @@ public class BadgeCommands : ICommandCollection public BadgeCommands( IBadgeRepo badgeRepo, IUserRepo userRepo, + IBadgeMarketRepo badgeMarketRepo, IMessageSender messageSender, IImmutableSet knownSpecies, HashSet? whitelist = null @@ -81,6 +83,7 @@ public BadgeCommands( { _badgeRepo = badgeRepo; _userRepo = userRepo; + _badgeMarketRepo = badgeMarketRepo; _messageSender = messageSender; _knownSpecies = knownSpecies; _whitelist = whitelist; @@ -102,55 +105,83 @@ public BadgeCommands( /// /// Convineintly handles many optional arguments for badge commands. May throw an exception when given faulty form informaton, so this should be surrounded by a try/catch /// - private (Badge.BadgeSource?, string?, bool?) InterpretBadgeInfoArgs(Optional sourceOpt, Optional formOpt, Optional shinyOpt) + private static (Badge.BadgeSource?, string?, bool) InterpretBadgeInfoArgs(Optional sourceOpt, Optional formOpt, Optional shinyOpt) { Badge.BadgeSource? source = sourceOpt.IsPresent ? sourceOpt.Value : null; string? form = formOpt.IsPresent ? formOpt.Value.Name : null; - bool? shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; + bool shiny = shinyOpt.IsPresent ? shinyOpt.Value : false; return (source, form, shiny); } + private static string describeBadge(PkmnSpecies? species, Badge.BadgeSource? source, string? form, bool shiny) + { + string speciesStr = species != null ? species.ToString() : ""; + string formStr = form != null ? form + " " : ""; + string shinyStr = shiny ? "shiny " : ""; + string sourceStr = ""; + switch (source) + { + case Badge.BadgeSource.Pinball: + sourceStr = "pinball caught "; + break; + case Badge.BadgeSource.RunCaught: + sourceStr = "run caught "; + break; + case Badge.BadgeSource.ManualDistribution: + sourceStr = "manually distributed "; + break; + case Badge.BadgeSource.ManualCreation: + sourceStr = "admin created "; + break; + case Badge.BadgeSource.Crate: + sourceStr = "crate dropped "; + break; + case Badge.BadgeSource.Breaking: + sourceStr = "badge breaker dropped "; + break; + case Badge.BadgeSource.Transmutation: + sourceStr = "transmuted "; + break; + default: + break; + } + return $"{sourceStr}{shinyStr}{formStr}{speciesStr}".TrimEnd(); + } public async Task Badges(CommandContext context) { - (Optional optionalSpecies, Optional optionalUser) = - await context.ParseArgs, Optional>>(); + (Optional optionalSpecies, Optional optionalUser, Optional sourceOpt, Optional formOpt, Optional shinyOpt) = + await context.ParseArgs, Optional, Optional, Optional, Optional>>(); bool isSelf = !optionalUser.IsPresent; User user = isSelf ? context.Message.User : optionalUser.Value; - if (optionalSpecies.IsPresent) + PkmnSpecies? species = optionalSpecies.IsPresent ? optionalSpecies.Value : null; + (Badge.BadgeSource? source, string? form, bool? shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); + + List badges = await _badgeRepo.FindAllByCustom(user.Id, species, form, source, shiny); + if (!badges.Any()) { - PkmnSpecies species = optionalSpecies.Value; - long numBadges = await _badgeRepo.CountByUserAndSpecies(user.Id, species); return new CommandResult { - Response = numBadges == 0 - ? isSelf - ? $"You have no {species} badges." - : $"{user.Name} has no {species} badges." - : isSelf - ? $"You have {numBadges}x {species} badges." - : $"{user.Name} has {numBadges}x {species} badges." + Response = isSelf ? "You have no badges." : $"{user.Name} has no badges." }; } - else + badges = badges.OrderBy(b => b.Species).ThenBy(b => b.Shiny).ThenBy(b => b.Source).ThenBy(b => b.Form).ToList(); + Dictionary countPerMetadata = new Dictionary(); + foreach (Badge b in badges) { - ImmutableSortedDictionary numBadgesPerSpecies = - await _badgeRepo.CountByUserPerSpecies(user.Id); - if (!numBadgesPerSpecies.Any()) - { - return new CommandResult - { - Response = isSelf ? "You have no badges." : $"{user.Name} has no badges." - }; - } - IEnumerable badgesFormatted = numBadgesPerSpecies.Select(kvp => $"{kvp.Value}x {kvp.Key}"); - return new CommandResult - { - Response = isSelf - ? $"Your badges: {string.Join(", ", badgesFormatted)}" - : $"{user.Name}'s badges: {string.Join(", ", badgesFormatted)}", - ResponseTarget = ResponseTarget.WhisperIfLong - }; + string metadadaAsString = describeBadge(b.Species, b.Source, b.Form, b.Shiny); + if (countPerMetadata.Keys.Contains(metadadaAsString)) + countPerMetadata[metadadaAsString] += 1; + else + countPerMetadata.Add(metadadaAsString, 1); } + IEnumerable badgesFormatted = countPerMetadata.Select(kvp => $"{kvp.Value}x {kvp.Key}"); + return new CommandResult + { + Response = isSelf + ? $"Your badges: {string.Join(", ", badgesFormatted)}" + : $"{user.Name}'s badges: {string.Join(", ", badgesFormatted)}", + ResponseTarget = ResponseTarget.WhisperIfLong + }; } public async Task UnselectBadge(CommandContext context) @@ -330,7 +361,7 @@ public async Task GiftBadge(CommandContext context) (User recipient, PkmnSpecies species, Optional amountOpt, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = await context.ParseArgs, Optional, Optional, Optional>>(); int amount = amountOpt.Map(i => i.Number).OrElse(1); - (Badge.BadgeSource? source, string? form, bool? shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); + (Badge.BadgeSource? source, string? form, bool shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); if (recipient == gifter) return new CommandResult { Response = "You cannot gift to yourself" }; @@ -340,7 +371,7 @@ public async Task GiftBadge(CommandContext context) //TODO big improve before merge return new CommandResult { - Response = $"You tried to gift {amount} {species} badges, but you only have {badges.Count}." + Response = $"You tried to gift {amount} {describeBadge(species, source, form, shiny)} badges, but you only have {badges.Count}." }; IImmutableList badgesToGift = badges.Take(amount).ToImmutableList(); @@ -348,33 +379,34 @@ public async Task GiftBadge(CommandContext context) await _badgeRepo.TransferBadges(badgesToGift, recipient.Id, BadgeLogType.TransferGift, data); await _messageSender.SendWhisper(recipient, amount > 1 - ? $"You have been gifted {amount} {species} badges from {gifter.Name}!" - : $"You have been gifted a {species} badge from {gifter.Name}!"); + ? $"You have been gifted {amount} {describeBadge(species, source, form, shiny)} badges from {gifter.Name}!" + : $"You have been gifted a {describeBadge(species, source, form, shiny)} badge from {gifter.Name}!"); return new CommandResult { Response = amount > 1 - ? $"has gifted {amount} {species} badges to {recipient.Name}!" - : $"has gifted a {species} badge to {recipient.Name}!", + ? $"has gifted {amount} {describeBadge(species, source, form, shiny)} badges to {recipient.Name}!" + : $"has gifted a {describeBadge(species, source, form, shiny)} badge to {recipient.Name}!", ResponseTarget = ResponseTarget.Chat }; } public async Task ListSellBadge(CommandContext context) { - (Optional userOpt, PkmnSpecies species, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = - await context.ParseArgs, PkmnSpecies, Optional, Optional, Optional>>(); + (Optional userOpt, Optional speciesOpt, Optional sourceOpt, Optional shinyOpt, Optional formOpt) = + await context.ParseArgs, Optional, Optional, Optional, Optional>>(); User user = userOpt.IsPresent ? userOpt.Value : context.Message.User; + PkmnSpecies? species = speciesOpt.IsPresent ? speciesOpt.Value : null; (Badge.BadgeSource? source, string? form, bool? shiny) = InterpretBadgeInfoArgs(sourceOpt, formOpt, shinyOpt); - List forSale = await _badgeRepo.FindAllForSaleByCustom(user.Id, species, form, source, shiny); + List forSale = await _badgeMarketRepo.FindAllBadgesForSale(user.Id, species, form, source, shiny); string response; if (forSale.Count == 0) response = "No badges found."; else { - response = string.Format("{0} badges found:", forSale.Count); + Dictionary countPerMetadata = new Dictionary(); foreach (Badge b in forSale) { if (b.UserId == null) @@ -383,13 +415,14 @@ public async Task ListSellBadge(CommandContext context) if (seller == null) throw new OwnedBadgeNotFoundException(b); - response += string.Format(" {0}{1}{2} sold by {3} for T{4}", - shiny == true ? "Shiny " : "", - form ?? "", - b.Species.Name, - seller.SimpleName, - b.SellPrice); + string metadadaAsString = describeBadge(b.Species, b.Source, b.Form, b.Shiny) + $" sold by {seller.SimpleName} for T{b.SellPrice}"; + if (countPerMetadata.Keys.Contains(metadadaAsString)) + countPerMetadata[metadadaAsString] += 1; + else + countPerMetadata.Add(metadadaAsString, 1); } + IEnumerable badgesFormatted = countPerMetadata.Select(kvp => $"{kvp.Value}x {kvp.Key}"); + response = $"{forSale.Count} badges found: " + string.Join(", ", badgesFormatted); } return new CommandResult diff --git a/TPP.Core/Setups.cs b/TPP.Core/Setups.cs index deff9f74..799ac6c6 100644 --- a/TPP.Core/Setups.cs +++ b/TPP.Core/Setups.cs @@ -42,9 +42,9 @@ public static ArgsParser SetUpArgsParser(IUserRepo userRepo, PokedexData pokedex argsParser.AddArgumentParser(new TokensParser()); argsParser.AddArgumentParser(new SignedPokeyenParser()); argsParser.AddArgumentParser(new SignedTokensParser()); - argsParser.AddArgumentParser(new PkmnSpeciesParser(pokedexData.KnownSpecies, PokedexData.NormalizeName)); argsParser.AddArgumentParser(new ShinyParser()); argsParser.AddArgumentParser(new BadgeSourceParser()); + argsParser.AddArgumentParser(new FormParser()); argsParser.AddArgumentParser(new RoleParser()); argsParser.AddArgumentParser(new PercentageParser()); argsParser.AddArgumentParser(new SideParser()); @@ -86,7 +86,7 @@ public static CommandProcessor SetUpCommandProcessor( ).Commands, new PollCommands(databases.PollRepo).Commands, new ManagePollCommands(databases.PollRepo).Commands, - new BadgeCommands(databases.BadgeRepo, databases.UserRepo, messageSender, knownSpecies).Commands, + new BadgeCommands(databases.BadgeRepo, databases.UserRepo, databases.BadgeMarketRepo, messageSender, knownSpecies).Commands, new OperatorCommands( stopToken, databases.PokeyenBank, databases.TokensBank, messageSender: messageSender, databases.BadgeRepo, databases.UserRepo @@ -119,7 +119,8 @@ public record Databases( ISubscriptionLogRepo SubscriptionLogRepo, IModLogRepo ModLogRepo, IResponseCommandRepo ResponseCommandRepo, - KeyValueStore KeyValueStore + KeyValueStore KeyValueStore, + IBadgeMarketRepo BadgeMarketRepo ); public static Databases SetUpRepositories(ILogger logger, BaseConfig baseConfig) @@ -154,6 +155,7 @@ public static Databases SetUpRepositories(ILogger logger, BaseConfig baseConfig) clock: clock); tokenBank.AddReservedMoneyChecker( new PersistedReservedMoneyCheckers(mongoDatabase).AllDatabaseReservedTokens); + IBadgeMarketRepo badgeMarketRepo = new BadgeMarketRepo(mongoDatabase, badgeRepo, userRepo, tokenBank, clock); return new Databases ( UserRepo: userRepo, @@ -168,7 +170,8 @@ public static Databases SetUpRepositories(ILogger logger, BaseConfig baseConfig) SubscriptionLogRepo: new SubscriptionLogRepo(mongoDatabase), ModLogRepo: new ModLogRepo(mongoDatabase), ResponseCommandRepo: new ResponseCommandRepo(mongoDatabase), - KeyValueStore: new KeyValueStore(mongoDatabase) + KeyValueStore: new KeyValueStore(mongoDatabase), + BadgeMarketRepo: badgeMarketRepo ); } diff --git a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs index 5abb443f..d8f8df89 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeMarketRepo.cs @@ -12,7 +12,7 @@ using TPP.Common; using TPP.Model; using TPP.Persistence.MongoDB.Serializers; -using TPP.Persistence.Repos; +using TPP.Persistence; namespace TPP.Persistence.MongoDB.Repos { @@ -89,7 +89,7 @@ public async Task> FindAllBuyOffers(string? userId, PkmnSpec return await BuyOfferCollection.Find(filter).ToListAsync(); } - public async Task> FindAllBadgesForSale(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny) + public async Task> FindAllBadgesForSale(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny) { return await _badgeRepo.FindAllForSaleByCustom(userId, species, form, source, shiny); } @@ -124,11 +124,11 @@ public async Task CreateSellOffer(string userId, PkmnSpecies species, str public async Task DeleteBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount) { List offers = await FindAllBuyOffers(userId, species, form, source, shiny); - if(offers.Count() < amount) + if (offers.Count() < amount) throw new ArgumentException(string.Format("Tried to cancel {0} offers but only {1} were found", amount, offers.Count)); - + offers = offers.OrderByDescending(o => o.CreatedAt).ToList(); - for(int i=0; i < amount; i++) + for (int i = 0; i < amount; i++) { await BuyOfferCollection.FindOneAndDeleteAsync(o => o.Id == offers[i].Id); } @@ -141,7 +141,7 @@ public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? fo badgesForSale = SortBySpecialness(badgesForSale); badgesForSale.Reverse(); - for(int i=0; i o.Price >= badge.SellPrice).ToList(); buyOffers = buyOffers.OrderBy(o => o.WaitingSince).ToList(); - if(badge.SellPrice == null) + if (badge.SellPrice == null) throw new InvalidOperationException("Tried to sell a badge with no sell price"); foreach (BadgeBuyOffer offer in buyOffers) @@ -178,7 +178,7 @@ public async Task DeleteSellOffer(string userId, PkmnSpecies species, string? fo throw new UserNotFoundException(offer.UserId); if (seller == null) throw new UserNotFoundException(badge.UserId); - + soldBadges.Add(new IBadgeMarketRepo.BadgeSale(seller, buyer, badge, (long)badge.SellPrice)); await _tokenBank.PerformTransactions( new Transaction[] diff --git a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs index cde347d4..58144d07 100644 --- a/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs +++ b/TPP.Persistence.MongoDB/Repos/BadgeRepo.cs @@ -243,7 +243,7 @@ await _badgeLogRepo.LogWithSession( return updatedBadges.ToImmutableList(); } public async Task SetBadgeSellPrice(Badge badge, long price) - { + { if (price <= 0) throw new ArgumentOutOfRangeException("price", "Price must be positive"); diff --git a/TPP.Persistence/IBadgeMarketRepo.cs b/TPP.Persistence/IBadgeMarketRepo.cs index 3b0ddfda..1897f84e 100644 --- a/TPP.Persistence/IBadgeMarketRepo.cs +++ b/TPP.Persistence/IBadgeMarketRepo.cs @@ -8,13 +8,13 @@ using TPP.Model; using System.Collections.Immutable; -namespace TPP.Persistence.Repos +namespace TPP.Persistence { public interface IBadgeMarketRepo { record BadgeSale(User seller, User buyer, Badge soldBadge, long price); Task> FindAllBuyOffers(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); - Task> FindAllBadgesForSale(string? userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny); + Task> FindAllBadgesForSale(string? userId, PkmnSpecies? species, string? form, Badge.BadgeSource? source, bool? shiny); Task CreateBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price, int amount, Instant? createdAt); Task CreateSellOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int price); Task DeleteBuyOffer(string userId, PkmnSpecies species, string? form, Badge.BadgeSource? source, bool? shiny, int amount); diff --git a/tests/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs b/tests/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs index 6f43b18a..3b18a188 100644 --- a/tests/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs +++ b/tests/TPP.Core.Tests/Commands/Definitions/BadgeCommandsTest.cs @@ -31,6 +31,7 @@ private static Message MockMessage(User user, string text = "") => private Mock _badgeRepoMock = null!; private Mock _userRepoMock = null!; + private Mock _badgeMarketRepoMock = null!; private Mock _messageSender = null!; private ArgsParser _argsParser = null!; @@ -42,13 +43,17 @@ public void SetUp() _badgeRepoMock = new Mock(); _userRepoMock = new Mock(); _messageSender = new Mock(); - _badgeCommands = new BadgeCommands(_badgeRepoMock.Object, _userRepoMock.Object, _messageSender.Object, - ImmutableHashSet.Empty); + _badgeMarketRepoMock = new Mock(); + _badgeCommands = new BadgeCommands(_badgeRepoMock.Object, _userRepoMock.Object, _badgeMarketRepoMock.Object, + _messageSender.Object, ImmutableHashSet.Empty); _argsParser = new ArgsParser(); _argsParser.AddArgumentParser(new OptionalParser(_argsParser)); _argsParser.AddArgumentParser(new UserParser(_userRepoMock.Object)); _argsParser.AddArgumentParser(new AnyOrderParser(_argsParser)); _argsParser.AddArgumentParser(new PositiveIntParser()); + _argsParser.AddArgumentParser(new BadgeSourceParser()); + _argsParser.AddArgumentParser(new FormParser()); + _argsParser.AddArgumentParser(new ShinyParser()); } [TestFixture] @@ -61,22 +66,21 @@ public async Task TestBadgesSelf() var species1 = PkmnSpecies.RegisterName("1", "Einsmon"); var species2 = PkmnSpecies.RegisterName("22", "Zwozwomon"); var species3 = PkmnSpecies.RegisterName("13", "Drölfmon"); + + Badge badge1 = new Badge("1", user.Id, species1, Badge.BadgeSource.ManualCreation, Instant.FromUtc(1, 1, 1, 1, 1), null, false); + Badge badge2 = new Badge("2", user.Id, species2, Badge.BadgeSource.ManualCreation, Instant.FromUtc(1, 1, 1, 1, 1), "hisuian", false); + Badge badge3 = new Badge("3", user.Id, species3, Badge.BadgeSource.ManualCreation, Instant.FromUtc(1, 1, 1, 1, 1), null, true); + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, null, null, null, false)).Returns( + Task.FromResult(new List() { badge1, badge2, badge2, badge3, badge3, badge3 })); + _userRepoMock .Setup(repo => repo.FindBySimpleName(user.SimpleName)) .ReturnsAsync(user); - _badgeRepoMock - .Setup(repo => repo.CountByUserPerSpecies(user.Id)) - .ReturnsAsync(new Dictionary - { - [species1] = 3, - [species2] = 6, - [species3] = 9, - }.ToImmutableSortedDictionary()); CommandResult result = await _badgeCommands.Badges(new CommandContext(MockMessage(user), ImmutableList.Empty, _argsParser)); - const string response = "Your badges: 3x #001 Einsmon, 9x #013 Drölfmon, 6x #022 Zwozwomon"; + const string response = "Your badges: 1x admin created #001 Einsmon, 3x admin created shiny #013 Drölfmon, 2x admin created hisuian #022 Zwozwomon"; Assert.That(result.Response, Is.EqualTo(response)); } @@ -84,28 +88,28 @@ public async Task TestBadgesSelf() public async Task TestBadgesOther() { User user = MockUser("MockUser"); + User otherUser = MockUser("Someone_Else"); var species1 = PkmnSpecies.RegisterName("1", "Einsmon"); var species2 = PkmnSpecies.RegisterName("22", "Zwozwomon"); var species3 = PkmnSpecies.RegisterName("13", "Drölfmon"); + + Badge badge1 = new Badge("1", otherUser.Id, species1, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, false); + Badge badge2 = new Badge("2", otherUser.Id, species2, Badge.BadgeSource.ManualCreation, Instant.MinValue, "hisuian", false); + Badge badge3 = new Badge("3", otherUser.Id, species3, Badge.BadgeSource.ManualCreation, Instant.MinValue, null, true); + _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { species1, species2, species3 })); - User otherUser = MockUser("Someone_Else"); _userRepoMock .Setup(repo => repo.FindBySimpleName(otherUser.SimpleName)) .ReturnsAsync(otherUser); - _badgeRepoMock - .Setup(repo => repo.CountByUserPerSpecies(otherUser.Id)) - .ReturnsAsync(new Dictionary - { - [species1] = 12, - [species2] = 23, - [species3] = 34, - }.ToImmutableSortedDictionary()); + + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(otherUser.Id, null, null, null, false)).Returns( + Task.FromResult(new List() { badge1, badge2, badge3 })); CommandResult result = await _badgeCommands.Badges(new CommandContext(MockMessage(user), ImmutableList.Create("sOmeOnE_eLsE"), _argsParser)); const string response = - "Someone_Else's badges: 12x #001 Einsmon, 34x #013 Drölfmon, 23x #022 Zwozwomon"; + "Someone_Else's badges: 1x admin created #001 Einsmon, 1x admin created shiny #013 Drölfmon, 1x admin created hisuian #022 Zwozwomon"; Assert.That(result.Response, Is.EqualTo(response)); } @@ -124,21 +128,21 @@ public async Task TestSpeciesOverNameIfAmbiguous() { User user = MockUser("MockUser"); PkmnSpecies species = PkmnSpecies.RegisterName("1", "PersonMon"); + Badge badge = new Badge("", user.Id, species, Badge.BadgeSource.RunCaught, Instant.MinValue, null, false); _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { species })); User otherUser = MockUser("PersonMon"); _userRepoMock .Setup(repo => repo.FindBySimpleName(otherUser.SimpleName)) .ReturnsAsync(otherUser); - _badgeRepoMock - .Setup(repo => repo.CountByUserAndSpecies(user.Id, species)) - .ReturnsAsync(1); - _badgeRepoMock - .Setup(repo => repo.CountByUserPerSpecies(otherUser.Id)) - .ReturnsAsync(ImmutableSortedDictionary.Empty); + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, species, null, null, false)).Returns( + Task.FromResult(new List() { badge })); + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(otherUser.Id, null, null, null, false)).Returns( + Task.FromResult(new List())); + CommandResult resultAmbiguous = await _badgeCommands.Badges(new CommandContext(MockMessage(user), ImmutableList.Create("PersonMon"), _argsParser)); - Assert.That(resultAmbiguous.Response, Is.EqualTo("You have 1x #001 PersonMon badges.")); + Assert.That(resultAmbiguous.Response, Is.EqualTo("Your badges: 1x run caught #001 PersonMon")); CommandResult resultDisambiguated = await _badgeCommands.Badges(new CommandContext(MockMessage(user), ImmutableList.Create("@PersonMon"), _argsParser)); @@ -150,6 +154,7 @@ public async Task TestSpeciesAndUserInAnyOrder() { User user = MockUser("User"); PkmnSpecies species = PkmnSpecies.RegisterName("1", "Species"); + Badge badge = new Badge("", user.Id, species, Badge.BadgeSource.Pinball, Instant.MinValue, null, false); _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { species })); _userRepoMock .Setup(repo => repo.FindBySimpleName(user.SimpleName)) @@ -160,6 +165,10 @@ public async Task TestSpeciesAndUserInAnyOrder() _badgeRepoMock .Setup(repo => repo.CountByUserPerSpecies(user.Id)) .ReturnsAsync(ImmutableSortedDictionary.Empty); + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, species, null, null, false)).Returns( + Task.FromResult(new List() { badge })); + _badgeRepoMock.Setup(repo => repo.FindAllByCustom(user.Id, null, null, null, false)).Returns( + Task.FromResult(new List() { badge })); CommandResult result1 = await _badgeCommands.Badges(new CommandContext(MockMessage(user), ImmutableList.Create("Species", "User"), _argsParser)); @@ -167,7 +176,7 @@ public async Task TestSpeciesAndUserInAnyOrder() ImmutableList.Create("User", "Species"), _argsParser)); Assert.That(result2.Response, Is.EqualTo(result1.Response)); - Assert.That(result1.Response, Is.EqualTo("User has 1x #001 Species badges.")); + Assert.That(result1.Response, Is.EqualTo("User's badges: 1x pinball caught #001 Species")); } } @@ -314,17 +323,38 @@ public async Task ListSellBadge_lists_callers_badges_when_user_isnt_specified() PkmnSpecies speciesA = PkmnSpecies.RegisterName("1", "a"); Badge badgeA = new("badgeA", user1.Id, speciesA, Badge.BadgeSource.Pinball, Instant.MinValue, null, false) { SellPrice = 1 }; - _badgeRepoMock.Setup(repo => repo.FindAllForSaleByCustom(user1.Id, speciesA, null, null, false)).Returns(Task.FromResult(new List() { badgeA })); + _badgeRepoMock.Setup(repo => repo.FindAllForSaleByCustom(user1.Id, speciesA, null, null, false)) + .Returns(Task.FromResult(new List() { badgeA })); _userRepoMock.Setup(repo => repo.FindById(user1.Id)).Returns(Task.FromResult((User?)user1)); _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { speciesA })); - _argsParser.AddArgumentParser(new ShinyParser()); - _argsParser.AddArgumentParser(new BadgeSourceParser()); - _argsParser.AddArgumentParser(new StringParser()); CommandResult result = await _badgeCommands.ListSellBadge(new CommandContext(MockMessage(user1), ImmutableList.Create("a"), _argsParser)); - Assert.That(result.Response, Is.EqualTo("1 badges found: a sold by u1 for T1")); + Assert.That(result.Response, Is.EqualTo("1 badges found: pinball caught #001 a sold by u1 for T1")); + } + + [Test] + public async Task ListSellBadge_lists_others_sold_badges() + { + User caller = MockUser("streamer"); + User seller = MockUser("notstreamer"); + PkmnSpecies species = PkmnSpecies.OfId("69"); + Badge.BadgeSource source = Badge.BadgeSource.Transmutation; + string? form = null; + bool shiny = false; + Badge badgeA = new Badge("A", seller.Id, species, source, Instant.MinValue, form, shiny) { SellPrice = 1 }; + Badge badgeB = new Badge("B", seller.Id, species, source, Instant.MinValue, form, shiny) { SellPrice = 1 }; + + _argsParser.AddArgumentParser(new PkmnSpeciesParser(new[] { species })); + _userRepoMock.Setup(repo => repo.FindBySimpleName("notstreamer")).Returns(Task.FromResult((User?)seller)); + _userRepoMock.Setup(repo => repo.FindById(seller.Id)).Returns(Task.FromResult((User?)seller)); + _badgeMarketRepoMock.Setup(repo => repo.FindAllBadgesForSale(seller.Id, null, null, null, shiny)) + .Returns(Task.FromResult(new List() { badgeA, badgeB })); + + CommandResult result = await _badgeCommands.ListSellBadge(new CommandContext(MockMessage(caller), ImmutableList.Create("notstreamer"), _argsParser)); + + Assert.That(result.Response, Is.EqualTo("2 badges found: 2x transmuted #069 ??? sold by notstreamer for T1")); } } } diff --git a/tests/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs b/tests/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs index f9e1e5cf..a29a710f 100644 --- a/tests/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs +++ b/tests/TPP.Persistence.MongoDB.Tests/Repos/BadgeMarketRepoTest.cs @@ -10,7 +10,7 @@ using MongoDB.Driver; using NUnit.Framework; using TPP.Common; -using TPP.Persistence.Repos; +using TPP.Persistence; namespace TPP.Persistence.MongoDB.Tests.Repos { @@ -153,12 +153,12 @@ public async Task ResolveBuyOffers_fills_outstanding_buy_offer() _mockBadgeRepo.Setup(m => m.FindAllForSaleByCustom(sellerId, species, form, null, shiny)).Returns(Task.FromResult(new List())); await badgeMarketRepo.CreateBuyOffer(buyerId, species, form, source, shiny, price, amount, time.PlusNanoseconds(2)); - + List remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); Assert.That(remainingBuyOffers.Count, Is.EqualTo(1)); var soldBadges = await badgeMarketRepo.ResolveBuyOffers(species, shiny); - + remainingBuyOffers = await badgeMarketRepo.FindAllBuyOffers(buyerId, species, form, source, shiny); Assert.That(remainingBuyOffers.Count, Is.EqualTo(0)); Assert.That(soldBadges.Count, Is.EqualTo(1)); @@ -176,7 +176,7 @@ public async Task DeleteBuyOffer_removes_outstanding_buy_offer() Badge.BadgeSource? source = null; string? form = null; bool shiny = false; - Instant time = Instant.FromUtc(0,1,1,0,0); + Instant time = Instant.FromUtc(0, 1, 1, 0, 0); int price = 1; int amount = 1; @@ -198,14 +198,14 @@ public async Task DeleteSellOffer_removes_outstanding_sell_offer() Badge.BadgeSource source = Badge.BadgeSource.Pinball; string? form = null; bool shiny = false; - Instant time = Instant.FromUtc(0,1,1,0,0); + Instant time = Instant.FromUtc(0, 1, 1, 0, 0); long price = 1; - Badge badgeForSale = new Badge("A", sellerId, species, source, time, form, shiny){ SellingSince=time, SellPrice=price}; + Badge badgeForSale = new Badge("A", sellerId, species, source, time, form, shiny) { SellingSince = time, SellPrice = price }; Badge badgeNotForSale = new Badge("A", sellerId, species, source, time, form, shiny); IBadgeMarketRepo badgeMarketRepo = CreateBadgeMarketRepo(); - _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(new List() { badgeForSale } )); + _mockBadgeRepo.Setup(r => r.FindAllForSaleByCustom(sellerId, species, form, source, shiny)).Returns(Task.FromResult(new List() { badgeForSale })); _mockBadgeRepo.Setup(r => r.SetBadgeSellPrice(badgeForSale, 0)).Returns(Task.FromResult(badgeNotForSale)); await badgeMarketRepo.DeleteSellOffer(sellerId, species, form, source, shiny, 1);