Skip to content

Commit 05aa060

Browse files
Enhance game maintenance and duplicate handling features (#375)
* Add cleanup for incomplete game entries without platform definition in RunDailyMaintenance * Add logging for deletion of incomplete game entries without platform definition in RunDailyMaintenance * Refactor SQL query in RunDailyMaintenance to use parameters for object type and attribute name * feat: Add number to words and words to number conversion functionality - Introduced a new `Numbers` class in `Common.cs` for converting integers to their English word representation and vice versa. - Implemented methods `NumberToWords` and `WordsToNumbers` for handling conversions. - Enhanced `GetSearchCandidates` method in `DataObjects.cs` to include number to word and word to number conversions. - Added new API endpoint in `DataObjectController.cs` to retrieve duplicate data objects. - Updated localization files to include new terms related to duplicates and search aliases. - Modified the data object detail page to display possible duplicates and provide a merge functionality. - Added unit tests for `GetSearchCandidates` to ensure expected behavior for various input cases. * feat: Enable/disable merge button based on select all checkbox state in renderContent function * feat: Refactor merge duplicates functionality to use Promise.all for concurrent requests
1 parent ec222d8 commit 05aa060

File tree

13 files changed

+967
-65
lines changed

13 files changed

+967
-65
lines changed

Hasheous.sln

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,90 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "hasheous-lib", "hasheous-li
1313
EndProject
1414
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "service-host", "service-host\service-host.csproj", "{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}"
1515
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "hasheous-lib.Tests", "hasheous-lib.Tests\hasheous-lib.Tests.csproj", "{48D0391E-5FB8-4508-A850-C96A814864CF}"
17+
EndProject
1618
Global
1719
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1820
Debug|Any CPU = Debug|Any CPU
21+
Debug|x64 = Debug|x64
22+
Debug|x86 = Debug|x86
1923
Release|Any CPU = Release|Any CPU
24+
Release|x64 = Release|x64
25+
Release|x86 = Release|x86
2026
EndGlobalSection
2127
GlobalSection(ProjectConfigurationPlatforms) = postSolution
2228
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2329
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|Any CPU.Build.0 = Debug|Any CPU
30+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|x64.ActiveCfg = Debug|Any CPU
31+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|x64.Build.0 = Debug|Any CPU
32+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|x86.ActiveCfg = Debug|Any CPU
33+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Debug|x86.Build.0 = Debug|Any CPU
2434
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|Any CPU.ActiveCfg = Release|Any CPU
2535
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|Any CPU.Build.0 = Release|Any CPU
36+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|x64.ActiveCfg = Release|Any CPU
37+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|x64.Build.0 = Release|Any CPU
38+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|x86.ActiveCfg = Release|Any CPU
39+
{37509913-50FD-4865-888A-F5ABBE6C5D05}.Release|x86.Build.0 = Release|Any CPU
2640
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
2741
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|Any CPU.Build.0 = Debug|Any CPU
42+
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|x64.ActiveCfg = Debug|Any CPU
43+
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|x64.Build.0 = Debug|Any CPU
44+
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|x86.ActiveCfg = Debug|Any CPU
45+
{D708CF76-505B-4954-911F-A535F5E18047}.Debug|x86.Build.0 = Debug|Any CPU
2846
{D708CF76-505B-4954-911F-A535F5E18047}.Release|Any CPU.ActiveCfg = Release|Any CPU
2947
{D708CF76-505B-4954-911F-A535F5E18047}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{D708CF76-505B-4954-911F-A535F5E18047}.Release|x64.ActiveCfg = Release|Any CPU
49+
{D708CF76-505B-4954-911F-A535F5E18047}.Release|x64.Build.0 = Release|Any CPU
50+
{D708CF76-505B-4954-911F-A535F5E18047}.Release|x86.ActiveCfg = Release|Any CPU
51+
{D708CF76-505B-4954-911F-A535F5E18047}.Release|x86.Build.0 = Release|Any CPU
3052
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3153
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
54+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|x64.ActiveCfg = Debug|Any CPU
55+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|x64.Build.0 = Debug|Any CPU
56+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|x86.ActiveCfg = Debug|Any CPU
57+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Debug|x86.Build.0 = Debug|Any CPU
3258
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
3359
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|Any CPU.Build.0 = Release|Any CPU
60+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|x64.ActiveCfg = Release|Any CPU
61+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|x64.Build.0 = Release|Any CPU
62+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|x86.ActiveCfg = Release|Any CPU
63+
{E925C33C-E514-4351-B709-13FFBF3F6CC9}.Release|x86.Build.0 = Release|Any CPU
3464
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3565
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|Any CPU.Build.0 = Debug|Any CPU
66+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|x64.ActiveCfg = Debug|Any CPU
67+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|x64.Build.0 = Debug|Any CPU
68+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|x86.ActiveCfg = Debug|Any CPU
69+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Debug|x86.Build.0 = Debug|Any CPU
3670
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|Any CPU.ActiveCfg = Release|Any CPU
3771
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|Any CPU.Build.0 = Release|Any CPU
72+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|x64.ActiveCfg = Release|Any CPU
73+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|x64.Build.0 = Release|Any CPU
74+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|x86.ActiveCfg = Release|Any CPU
75+
{DCDE72D0-5B03-4B5C-B85C-DCFEA3A93119}.Release|x86.Build.0 = Release|Any CPU
3876
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
3977
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|Any CPU.Build.0 = Debug|Any CPU
78+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|x64.ActiveCfg = Debug|Any CPU
79+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|x64.Build.0 = Debug|Any CPU
80+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|x86.ActiveCfg = Debug|Any CPU
81+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Debug|x86.Build.0 = Debug|Any CPU
4082
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|Any CPU.ActiveCfg = Release|Any CPU
4183
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|Any CPU.Build.0 = Release|Any CPU
84+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|x64.ActiveCfg = Release|Any CPU
85+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|x64.Build.0 = Release|Any CPU
86+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|x86.ActiveCfg = Release|Any CPU
87+
{EE31901D-72C9-48DF-B6A6-FDDBABE1F01C}.Release|x86.Build.0 = Release|Any CPU
88+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
89+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
90+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|x64.ActiveCfg = Debug|Any CPU
91+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|x64.Build.0 = Debug|Any CPU
92+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|x86.ActiveCfg = Debug|Any CPU
93+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Debug|x86.Build.0 = Debug|Any CPU
94+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
95+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|Any CPU.Build.0 = Release|Any CPU
96+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|x64.ActiveCfg = Release|Any CPU
97+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|x64.Build.0 = Release|Any CPU
98+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|x86.ActiveCfg = Release|Any CPU
99+
{48D0391E-5FB8-4508-A850-C96A814864CF}.Release|x86.Build.0 = Release|Any CPU
42100
EndGlobalSection
43101
GlobalSection(SolutionProperties) = preSolution
44102
HideSolutionNode = FALSE

hasheous-lib.Tests/UnitTest1.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using hasheous_server.Classes;
2+
3+
namespace hasheous_lib.Tests;
4+
5+
public class GetSearchCandidatesTests
6+
{
7+
private static List<string> GetCandidates(string name)
8+
{
9+
return DataObjects.GetSearchCandidates(name);
10+
}
11+
12+
[Theory]
13+
[InlineData("The Legend of Zelda", "The Legend of Zelda", "Legend of Zelda", "Legend of Zelda, The")]
14+
[InlineData("Legend of Zelda, The", "Legend of Zelda, The", "Legend of Zelda", "The Legend of Zelda")]
15+
[InlineData("Final Fantasy IV", "Final Fantasy IV", "Final Fantasy 4", "Final Fantasy IV")]
16+
[InlineData("Resident Evil - Code: Veronica", "Resident Evil - Code: Veronica", "Resident Evil: Code: Veronica", "Resident Evil - Code: Veronica")]
17+
[InlineData("Sonic (USA)", "Sonic (USA)", "Sonic", "Sonic (USA)")]
18+
[InlineData("Mega Man v1.2", "Mega Man v1.2", "Mega Man", "Mega Man v1.2")]
19+
[InlineData("Street Fighter Rev A", "Street Fighter Rev A", "Street Fighter", "Street Fighter Rev A")]
20+
public void GeneratesExpectedCandidates(string input, string expected1, string expected2, string expected3)
21+
{
22+
List<string> candidates = GetCandidates(input);
23+
24+
Assert.Contains(expected1, candidates);
25+
Assert.Contains(expected2, candidates);
26+
Assert.Contains(expected3, candidates);
27+
}
28+
29+
[Fact]
30+
public void ReturnsEmptyListForBlankName()
31+
{
32+
List<string> candidates = GetCandidates(" ");
33+
Assert.Empty(candidates);
34+
}
35+
36+
[Theory]
37+
[InlineData("Resident Evil - Code: Veronica", "Resident Evil Code: Veronica")]
38+
[InlineData("Metal Gear Solid: The Twin Snakes", "Metal Gear Solid The Twin Snakes")]
39+
[InlineData("Prince of Persia - The Sands of Time", "Prince of Persia The Sands of Time")]
40+
public void DropsDelimitersCorrectly(string input, string expectedWithDelimiterDrop)
41+
{
42+
List<string> candidates = GetCandidates(input);
43+
44+
Assert.Contains(expectedWithDelimiterDrop, candidates);
45+
}
46+
47+
[Theory]
48+
[InlineData("Game 1", "Game One")]
49+
[InlineData("Mega Man 2", "Mega Man Two")]
50+
[InlineData("Final Fantasy VII", "Final Fantasy VII", "Final Fantasy 7", "Final Fantasy Seven")]
51+
[InlineData("Take 2", "Take Two")]
52+
[InlineData("Top 10", "Top Ten")]
53+
[InlineData("The Room 3", "The Room Three")]
54+
public void ConvertsNumbersToWords(string input, params string[] expectedCandidates)
55+
{
56+
List<string> candidates = GetCandidates(input);
57+
58+
foreach (string expected in expectedCandidates)
59+
{
60+
Assert.Contains(expected, candidates);
61+
}
62+
}
63+
64+
[Theory]
65+
[InlineData("Game One", "Game 1")]
66+
[InlineData("Mega Man Two", "Mega Man 2")]
67+
[InlineData("Final Fantasy Seven", "Final Fantasy 7")]
68+
[InlineData("Take Twenty One", "Take 21")]
69+
[InlineData("Top Ten", "Top 10")]
70+
[InlineData("The Room Three", "The Room 3")]
71+
public void ConvertsWordsToNumbers(string input, string expectedCandidate)
72+
{
73+
List<string> candidates = GetCandidates(input);
74+
75+
Assert.Contains(expectedCandidate, candidates);
76+
}
77+
78+
[Theory]
79+
[InlineData("Resident Evil 5", "Resident Evil Five")]
80+
[InlineData("Portal 2", "Portal Two")]
81+
[InlineData("Call of Duty Modern Warfare 3", "Call of Duty Modern Warfare Three")]
82+
public void BidirectionalNumberConversion(string input, string expectedCandidate)
83+
{
84+
List<string> candidates = GetCandidates(input);
85+
86+
Assert.Contains(expectedCandidate, candidates);
87+
}
88+
89+
[Theory]
90+
[InlineData("Star Wars: Episode 1 - Racer", "Star Wars: Episode I - Racer")]
91+
[InlineData("Game 2", "Game II")]
92+
[InlineData("Final Fantasy 7", "Final Fantasy VII")]
93+
[InlineData("Chapter 3", "Chapter III")]
94+
[InlineData("Volume 5", "Volume V")]
95+
[InlineData("Part 10", "Part X")]
96+
public void ConvertsNumbersToRomanNumerals(string input, string expectedCandidate)
97+
{
98+
List<string> candidates = GetCandidates(input);
99+
100+
Assert.Contains(expectedCandidate, candidates);
101+
}
102+
103+
[Theory]
104+
[InlineData("Star Wars: Episode I - Racer", "Star Wars: Episode 1 - Racer")]
105+
[InlineData("Game II", "Game 2")]
106+
[InlineData("Final Fantasy VII", "Final Fantasy 7")]
107+
[InlineData("Chapter III", "Chapter 3")]
108+
[InlineData("Volume V", "Volume 5")]
109+
[InlineData("Part X", "Part 10")]
110+
public void ConvertsRomanNumeralsToNumbers(string input, string expectedCandidate)
111+
{
112+
List<string> candidates = GetCandidates(input);
113+
114+
Assert.Contains(expectedCandidate, candidates);
115+
}
116+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<RootNamespace>hasheous_lib.Tests</RootNamespace>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="coverlet.collector" Version="6.0.4" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
14+
<PackageReference Include="xunit" Version="2.9.3" />
15+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<Using Include="Xunit" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\hasheous-lib\hasheous-lib.csproj" />
24+
</ItemGroup>
25+
26+
</Project>

0 commit comments

Comments
 (0)