Add list endpoint information

This commit is contained in:
Matt Nadareski
2026-11-11 23:08:03 +01:00
parent 8f5027a4c3
commit feec4aac91
5 changed files with 315 additions and 2 deletions

View File

@@ -921,6 +921,113 @@ namespace SabreTools.RedumpLib.Test.Data
#endregion
#region List Subpath
/// <summary>
/// Check that every ListSubpath has a long name provided
/// </summary>
/// <param name="listSubpath">ListSubpath value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateListSubpathTestData))]
public void ListSubpath_LongName(ListSubpath? listSubpath, bool expectNull)
{
var actual = listSubpath.LongName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Check that every ListSubpath has a short name provided
/// </summary>
/// <param name="listSubpath">ListSubpath value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateListSubpathTestData))]
public void ListSubpath_ShortName(ListSubpath? listSubpath, bool expectNull)
{
var actual = listSubpath.ShortName();
if (expectNull)
Assert.Null(actual);
else
Assert.NotNull(actual);
}
/// <summary>
/// Ensure that every ListSubpath that has a short name that is unique
/// </summary>
[Fact]
public void ListSubpath_ShortName_NoDuplicates()
{
var fullListSubpaths = Enum.GetValues<ListSubpath>().Cast<ListSubpath>().ToList();
var filteredListSubpaths = new Dictionary<string, ListSubpath?>();
int totalCount = 0;
foreach (ListSubpath? listSubpath in fullListSubpaths)
{
var code = listSubpath.ShortName();
if (string.IsNullOrEmpty(code))
continue;
// Throw if the code already exists
if (filteredListSubpaths.ContainsKey(code))
throw new DuplicateNameException($"Code {code} already in dictionary");
filteredListSubpaths[code] = listSubpath;
totalCount++;
}
Assert.Equal(totalCount, filteredListSubpaths.Count);
}
/// <summary>
/// Check that every ListSubpath can be mapped from a string
/// </summary>
/// <param name="listSubpath">ListSubpath value to check</param>
/// <param name="expectNull">True to expect a null value, false otherwise</param>
[Theory]
[MemberData(nameof(GenerateListSubpathTestData))]
public void ListSubpath_ToListSubpath(ListSubpath? listSubpath, bool expectNull)
{
string? longName = listSubpath.LongName();
string? longNameSpaceless = longName?.Replace(" ", string.Empty);
var actualNormal = longName.ToListSubpath();
var actualSpaceless = longNameSpaceless.ToListSubpath();
if (expectNull)
{
Assert.Null(actualNormal);
Assert.Null(actualSpaceless);
}
else
{
Assert.Equal(listSubpath, actualNormal);
Assert.Equal(listSubpath, actualSpaceless);
}
}
/// <summary>
/// Generate a test set of ListSubpath values
/// </summary>
/// <returns>MemberData-compatible list of ListSubpath values</returns>
public static TheoryData<ListSubpath?, bool> GenerateListSubpathTestData()
{
var testData = new TheoryData<ListSubpath?, bool>() { { null, true } };
foreach (ListSubpath? listSubpath in Enum.GetValues<ListSubpath>().Cast<ListSubpath?>())
{
testData.Add(listSubpath, false);
}
return testData;
}
#endregion
#region Media Type
/// <summary>

View File

@@ -0,0 +1,75 @@
using SabreTools.RedumpLib.Data;
using Xunit;
namespace SabreTools.RedumpLib.Test.Endpoints
{
public class ListTests
{
#region BuildUrl
[Fact]
public void BuildUrl_MissingUsername_DefaultEmpty()
{
var endpoint = new RedumpLib.Endpoints.List();
string actual = endpoint.BuildUrl();
Assert.Equal($"https://redump.info/list/", actual);
}
[Fact]
public void BuildUrl_Subpath_Builds()
{
// Create a base endpoint and then modify the fields
var endpoint = new RedumpLib.Endpoints.List { Username = "user" };
// Invalid subpath, no system
endpoint.Subpath = null;
endpoint.System = null;
string actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/user", actual);
// Invalid subpath, valid system
endpoint.Subpath = null;
endpoint.System = PhysicalSystem.AcornArchimedesAndRiscPC;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/user/ARCH", actual);
// Have subpath, no system
endpoint.Subpath = ListSubpath.Have;
endpoint.System = null;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/have/user", actual);
// Have subpath, valid system
endpoint.Subpath = ListSubpath.Have;
endpoint.System = PhysicalSystem.AcornArchimedesAndRiscPC;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/have/user/ARCH", actual);
// Cuesheets subpath, no system
endpoint.Subpath = ListSubpath.Cuesheets;
endpoint.System = null;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/cues/user", actual);
// Cuesheets subpath, valid system
endpoint.Subpath = ListSubpath.Cuesheets;
endpoint.System = PhysicalSystem.AcornArchimedesAndRiscPC;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/cues/user/ARCH", actual);
// Datfile subpath, no system
endpoint.Subpath = ListSubpath.Datfile;
endpoint.System = null;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/datfile/user", actual);
// Datfile subpath, valid system
endpoint.Subpath = ListSubpath.Datfile;
endpoint.System = PhysicalSystem.AcornArchimedesAndRiscPC;
actual = endpoint.BuildUrl();
Assert.Equal("https://redump.info/list/datfile/user/ARCH", actual);
}
#endregion
}
}

View File

@@ -87,6 +87,21 @@ namespace SabreTools.RedumpLib.Data
VerifiedGreen = 5,
}
/// <summary>
/// List of all list subpaths
/// </summary>
public enum ListSubpath
{
[HumanReadable(LongName = "Have", ShortName = "have")]
Have = 1,
[HumanReadable(LongName = "Cuesheets", ShortName = "cues")]
Cuesheets = 2,
[HumanReadable(LongName = "Datfile", ShortName = "datfile")]
Datfile = 3,
}
/// <summary>
/// List of all site-supported media types
/// </summary>

View File

@@ -241,10 +241,10 @@ namespace SabreTools.RedumpLib.Data
=> AttributeHelper<DiscSubpath?>.GetHumanReadableAttribute(discSubpath)?.ShortName;
/// <summary>
/// Get the Region enum value for a given string
/// Get the DiscSubpath enum value for a given string
/// </summary>
/// <param name="discSubpath">String value to convert</param>
/// <returns>Region represented by the string, if possible</returns>
/// <returns>DiscSubpath represented by the string, if possible</returns>
public static DiscSubpath? ToDiscSubpath(this string? discSubpath)
{
// No value means no match
@@ -374,6 +374,70 @@ namespace SabreTools.RedumpLib.Data
#endregion
#region List Subpath
/// <summary>
/// Get the human readable name for a ListSubpath
/// </summary>
/// <param name="listSubpath"></param>
/// <returns></returns>
public static string? LongName(this ListSubpath listSubpath)
=> AttributeHelper<ListSubpath>.GetHumanReadableAttribute(listSubpath)?.LongName;
/// <summary>
/// Get the human readable name for a ListSubpath
/// </summary>
/// <param name="listSubpath"></param>
/// <returns></returns>
public static string? LongName(this ListSubpath? listSubpath)
=> AttributeHelper<ListSubpath?>.GetHumanReadableAttribute(listSubpath)?.LongName;
/// <summary>
/// Get the URL path part for a ListSubpath
/// </summary>
/// <param name="listSubpath"></param>
/// <returns></returns>
public static string? ShortName(this ListSubpath listSubpath)
=> AttributeHelper<ListSubpath>.GetHumanReadableAttribute(listSubpath)?.ShortName;
/// <summary>
/// Get the URL path part for a ListSubpath
/// </summary>
/// <param name="listSubpath"></param>
/// <returns></returns>
public static string? ShortName(this ListSubpath? listSubpath)
=> AttributeHelper<ListSubpath?>.GetHumanReadableAttribute(listSubpath)?.ShortName;
/// <summary>
/// Get the ListSubpath enum value for a given string
/// </summary>
/// <param name="listSubpath">String value to convert</param>
/// <returns>ListSubpath represented by the string, if possible</returns>
public static ListSubpath? ToListSubpath(this string? listSubpath)
{
// No value means no match
if (listSubpath is null || listSubpath.Length == 0)
return null;
listSubpath = listSubpath.ToLowerInvariant();
var listSubpaths = (ListSubpath[])Enum.GetValues(typeof(ListSubpath));
// Check short names
int index = Array.FindIndex(listSubpaths, s => listSubpath == s.ShortName()?.ToLowerInvariant());
if (index > -1)
return listSubpaths[index];
// Check long names
index = Array.FindIndex(listSubpaths, s => listSubpath == s.LongName()?.ToLowerInvariant()
|| listSubpath == s.LongName()?.Replace(" ", string.Empty)?.ToLowerInvariant());
if (index > -1)
return listSubpaths[index];
return null;
}
#endregion
#region Media Type
/// <summary>

View File

@@ -0,0 +1,52 @@
using System;
using Newtonsoft.Json;
using SabreTools.RedumpLib.Data;
namespace SabreTools.RedumpLib.Endpoints
{
// TODO: Implement download and client extensions for these paths
[JsonObject("list")]
public class List
{
[JsonProperty("subpath")]
public ListSubpath? Subpath { get; set; }
[JsonProperty("username")]
public string Username { get; set; } = string.Empty;
[JsonProperty("syscode")]
public PhysicalSystem? System { get; set; }
/// <summary>
/// Build a /list/ path URL
/// </summary>
public string BuildUrl()
{
var ub = new UriBuilder
{
Scheme = "https",
Host = "redump.info",
Path = "list",
};
switch (Subpath)
{
case ListSubpath.Have:
case ListSubpath.Cuesheets:
case ListSubpath.Datfile:
ub.Path += $"/{Subpath.ShortName()}";
break;
// Nothing else is valid as a subpath
default:
break;
}
ub.Path += $"/{Username}";
if (System?.Code is not null)
ub.Path += $"/{System.Code}";
return ub.ToString();
}
}
}