From 2e5704a99576cb908249562dcab6e8353968ac5b Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 3 Nov 2023 02:00:39 +0900 Subject: [PATCH 1/4] Introduce ClaimItems GQL endpoint --- .../GraphTypes/ActionQueryTest.cs | 78 +++++++++++++++++++ .../GraphTypes/ActionQuery.cs | 2 +- .../ActionQueryFields/ClaimItems.cs | 37 +++++++++ .../GraphTypes/Input/ClaimDataInputType.cs | 25 ++++++ 4 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 NineChronicles.Headless/GraphTypes/ActionQueryFields/ClaimItems.cs create mode 100644 NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 8e720b2ce..4f65c7e59 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -13,6 +13,7 @@ using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Assets; +using Libplanet.Types.Tx; using Nekoyume; using Nekoyume.Action; using Nekoyume.Action.Garages; @@ -23,6 +24,7 @@ using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes; using Xunit; +using Xunit.Abstractions; using static NineChronicles.Headless.Tests.GraphQLTestUtils; namespace NineChronicles.Headless.Tests.GraphTypes @@ -1340,5 +1342,81 @@ public async Task AuraSummon() Assert.Equal(groupId, action.GroupId); Assert.Equal(summonCount, action.SummonCount); } + + [Theory] + [InlineData(1)] + [InlineData(2)] + public async Task ClaimItems(int claimDataCount) + { + var random = new Random(); + var tickerList = new List { "Item_T_500000", "Item_T_400000", "Item_T_800201", "Item_NT_49900011" }; + var claimDataList = new List<(Address, List)>(); + var queryBuilder = new StringBuilder().Append("{claimItems(claimData: ["); + for (var i = 0; i < claimDataCount; i++) + { + var avatarAddress = new PrivateKey().ToAddress(); + var currencyCount = random.Next(1, tickerList.Count + 1); + var tickerCandidates = tickerList.OrderBy(i => random.Next()).Take(currencyCount); + queryBuilder.Append($@"{{ + avatarAddress: ""{avatarAddress}"", + fungibleAssetValues:[ + "); + var favList = tickerCandidates.Select( + ticker => new FungibleAssetValue( + Currency.Uncapped( + ticker: ticker, + decimalPlaces: 0, + minters: new AddressSet(new List
()) + ), + random.Next(1, 100), 0 + ) + ).ToList(); + foreach (var fav in favList) + { + queryBuilder.Append($@"{{ + ticker: ""{fav.Currency.Ticker}"", + decimalPlaces: 0, + minters: [], + quantity: {fav.MajorUnit} + }}"); + if (fav != favList[^1]) + { + queryBuilder.Append(","); + } + } + + claimDataList.Add((avatarAddress, favList)); + + queryBuilder.Append("]}"); + if (i < claimDataCount - 1) + { + queryBuilder.Append(","); + } + } + + queryBuilder.Append("])}"); + + var queryResult = + await ExecuteQueryAsync(queryBuilder.ToString(), standaloneContext: _standaloneContext); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["claimItems"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + + for (var i = 0; i < claimDataList.Count; i++) + { + var (expectedAddr, expectedFavList) = claimDataList[i]; + var (actualAddr, actualFavList) = action.ClaimData[i]; + Assert.Equal(expectedAddr, actualAddr); + Assert.Equal(expectedFavList.Count, actualFavList.Count); + for (var j = 0; j < expectedFavList.Count; j++) + { + // Assert.Equal(expectedFavList[i], actualFavList[i]); + Assert.Equal(expectedFavList[i].Currency.Ticker, actualFavList[i].Currency.Ticker); + Assert.Equal(expectedFavList[i].RawValue, actualFavList[i].RawValue); + } + } + } } } diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index a3e583954..42a06176a 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -10,7 +10,6 @@ using Libplanet.Types.Assets; using Libplanet.Explorer.GraphTypes; using Nekoyume.Action; -using Nekoyume.Action.Factory; using Nekoyume.Model; using Nekoyume.Model.State; using Nekoyume.TableData; @@ -543,6 +542,7 @@ public ActionQuery(StandaloneContext standaloneContext) RegisterMead(); RegisterGarages(); RegisterSummon(); + RegisterClaimItems(); Field>( name: "craftQuery", diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/ClaimItems.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/ClaimItems.cs new file mode 100644 index 000000000..5f34e6d36 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/ClaimItems.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using GraphQL; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Types.Assets; +using Nekoyume.Action; +using NineChronicles.Headless.GraphTypes.Input; + +namespace NineChronicles.Headless.GraphTypes +{ + public partial class ActionQuery + { + private void RegisterClaimItems() + { + Field>( + "claimItems", + arguments: new QueryArguments( + new QueryArgument>>> + { + Name = "claimData", + Description = "List of pair of avatar address, List to claim." + } + ), + resolve: context => + { + var claimData = + context.GetArgument< + List<(Address avataAddress, IReadOnlyList fungibleAssetValues)> + >("claimData").AsReadOnly(); + ActionBase action = new ClaimItems(claimData); + return Encode(context, action); + } + ); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs b/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs new file mode 100644 index 000000000..68f8856f2 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Libplanet.Types.Assets; + +namespace NineChronicles.Headless.GraphTypes.Input; + +public class ClaimDataInputType : InputObjectGraphType<(Address avatarAddress, FungibleAssetValue fav)> +{ + public ClaimDataInputType() + { + Field>("avatarAddress"); + Field>>>("fungibleAssetValues"); + } + + public override object ParseDictionary(IDictionary value) + { + return ( + (Address)value["avatarAddress"]!, + (IReadOnlyList)((object[])value["fungibleAssetValues"]!).Cast().ToList() + ); + } +} From b706167888d90e5fac2a3723166760cbee910467 Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 3 Nov 2023 12:41:28 +0900 Subject: [PATCH 2/4] Fix wrong type definition --- NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs b/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs index 68f8856f2..684e2c39a 100644 --- a/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs +++ b/NineChronicles.Headless/GraphTypes/Input/ClaimDataInputType.cs @@ -7,7 +7,7 @@ namespace NineChronicles.Headless.GraphTypes.Input; -public class ClaimDataInputType : InputObjectGraphType<(Address avatarAddress, FungibleAssetValue fav)> +public class ClaimDataInputType : InputObjectGraphType<(Address avatarAddress, IReadOnlyList favList)> { public ClaimDataInputType() { From 5d297949b6bca5e0996696fa51fc03338331cad0 Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 3 Nov 2023 12:41:56 +0900 Subject: [PATCH 3/4] Fix broken test code and add test cases --- .../GraphTypes/ActionQueryTest.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 4f65c7e59..d9b1088c9 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -24,7 +24,6 @@ using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes; using Xunit; -using Xunit.Abstractions; using static NineChronicles.Headless.Tests.GraphQLTestUtils; namespace NineChronicles.Headless.Tests.GraphTypes @@ -1346,6 +1345,8 @@ public async Task AuraSummon() [Theory] [InlineData(1)] [InlineData(2)] + [InlineData(10)] + [InlineData(100)] public async Task ClaimItems(int claimDataCount) { var random = new Random(); @@ -1366,7 +1367,7 @@ public async Task ClaimItems(int claimDataCount) Currency.Uncapped( ticker: ticker, decimalPlaces: 0, - minters: new AddressSet(new List
()) + minters: AddressSet.Empty ), random.Next(1, 100), 0 ) @@ -1413,8 +1414,8 @@ public async Task ClaimItems(int claimDataCount) for (var j = 0; j < expectedFavList.Count; j++) { // Assert.Equal(expectedFavList[i], actualFavList[i]); - Assert.Equal(expectedFavList[i].Currency.Ticker, actualFavList[i].Currency.Ticker); - Assert.Equal(expectedFavList[i].RawValue, actualFavList[i].RawValue); + Assert.Equal(expectedFavList[j].Currency.Ticker, actualFavList[j].Currency.Ticker); + Assert.Equal(expectedFavList[j].RawValue, actualFavList[j].RawValue); } } } From 1b3f712bf6b51d5c4ceaf584a68a320e35934e4e Mon Sep 17 00:00:00 2001 From: hyeon Date: Fri, 3 Nov 2023 12:42:22 +0900 Subject: [PATCH 4/4] Add explanation to FAV comparison --- .../GraphTypes/ActionQueryTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index d9b1088c9..54b0d79c5 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1413,6 +1413,13 @@ public async Task ClaimItems(int claimDataCount) Assert.Equal(expectedFavList.Count, actualFavList.Count); for (var j = 0; j < expectedFavList.Count; j++) { + /* FIXME: Make Assert.Equal(FAV1, FAV2) works. + This test will fail because: + - GQL currency type does not allow `null` as minters to you should give empty list. + - But inside `Currency`, empty list is changed to null. + - As a result, currency hash are mismatch. + - See https://github.com/planetarium/NineChronicles.Headless/pull/2282#discussion_r1380857437 + */ // Assert.Equal(expectedFavList[i], actualFavList[i]); Assert.Equal(expectedFavList[j].Currency.Ticker, actualFavList[j].Currency.Ticker); Assert.Equal(expectedFavList[j].RawValue, actualFavList[j].RawValue);