Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using WheelWizard.GameBanana.InstallRequests;
using WheelWizard.Launching;
using WheelWizard.Mods;
using WheelWizard.RrRooms;
using WheelWizard.Settings;
using WheelWizard.WheelWizardData;

namespace WheelWizard.Test.Features.ApplicationLifecycle;

Expand Down Expand Up @@ -143,7 +143,7 @@ private sealed class Fixture
public IModManager Mods { get; } = Substitute.For<IModManager>();
public IModInstallRequestHandler Requests { get; } = Substitute.For<IModInstallRequestHandler>();
public IAutoUpdaterSingletonService Updates { get; } = Substitute.For<IAutoUpdaterSingletonService>();
public IWhWzDataSingletonService Badges { get; } = Substitute.For<IWhWzDataSingletonService>();
public IRrBadgeSingletonService Badges { get; } = Substitute.For<IRrBadgeSingletonService>();
public IApplicationLiveUpdates Live { get; } = Substitute.For<IApplicationLiveUpdates>();
public ISettingsManager Settings { get; } = Substitute.For<ISettingsManager>();
public IRetroRewindLaunchService Launch { get; } = Substitute.For<IRetroRewindLaunchService>();
Expand Down
7 changes: 3 additions & 4 deletions WheelWizard.Test/Features/RrRooms/RoomPresenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using WheelWizard.RrRooms;
using WheelWizard.Settings;
using WheelWizard.Shared.Polling;
using WheelWizard.WheelWizardData;
using WheelWizard.WiiManagement.GameLicense;
using WheelWizard.WiiManagement.GameLicense.Domain;
using WheelWizard.WiiManagement.MiiManagement;
Expand Down Expand Up @@ -34,7 +33,7 @@ public void Refresh_UpdatesBothLicenseAndFriends_AndOnlyNotifiesChanges()
var service = new GameLicenseSingletonService(
Substitute.For<IMiiDbService>(),
Substitute.For<IFileSystem>(),
Substitute.For<IWhWzDataSingletonService>(),
Substitute.For<IRrBadgeSingletonService>(),
Substitute.For<IRrRatingReader>(),
Substitute.For<ISettingsManager>(),
Substitute.For<ISaveRegionService>(),
Expand Down Expand Up @@ -104,7 +103,7 @@ public async Task FailedRoomRefresh_ClearsPresenceBeforeRefreshingProfiles()
leaderboard.GetTopPlayersAsync(50).Returns(Ok(new List<RwfcLeaderboardEntry>()));
var licenses = Substitute.For<IGameLicenseSingletonService>();
licenses.ActiveCurrentFriends.Returns([]);
var service = new TestRooms(Substitute.For<IWhWzDataSingletonService>(), rooms, leaderboard, licenses, presence);
var service = new TestRooms(Substitute.For<IRrBadgeSingletonService>(), rooms, leaderboard, licenses, presence);
rooms
.GetRoomsAsync()
.Returns(
Expand Down Expand Up @@ -136,7 +135,7 @@ public async Task FailedRoomRefresh_ClearsPresenceBeforeRefreshingProfiles()
}

private sealed class TestRooms(
IWhWzDataSingletonService data,
IRrBadgeSingletonService data,
IRrRoomsSingletonService rooms,
IRrLeaderboardSingletonService leaderboard,
IGameLicenseSingletonService licenses,
Expand Down
112 changes: 112 additions & 0 deletions WheelWizard.Test/Features/RrRooms/RrBadgeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Linq.Expressions;
using WheelWizard.RrRooms;
using WheelWizard.Shared;
using WheelWizard.Shared.Services;
using WheelWizard.WheelWizardData.Domain;

namespace WheelWizard.Test.Features.RrRooms;

public class RrBadgeTests
{
// Real pid / friend code pairs from the RWFC leaderboard.
private const string Pid1 = "198760529";
private const string FriendCode1 = "3137-3137-3137";
private const string Pid2 = "4544000";
private const string FriendCode2 = "0000-0454-4000";

private readonly IApiCaller<IRwfcApi> _apiCaller;
private readonly RrBadgeSingletonService _service;

public RrBadgeTests()
{
_apiCaller = Substitute.For<IApiCaller<IRwfcApi>>();
_service = new(_apiCaller);
}

private void SetupResponse(Dictionary<string, int[]?> badges) =>
_apiCaller
.CallApiAsync(Arg.Any<Expression<Func<IRwfcApi, Task<RwfcBadgesResponse>>>>())
.Returns(Ok(new RwfcBadgesResponse { Badges = badges }));

[Fact]
public async Task LoadBadgesAsync_MapsProfileIdsToFriendCodes()
{
SetupResponse(new() { { Pid1, [1, 1001] }, { Pid2, [2003] } });

var result = await _service.LoadBadgesAsync();

Assert.True(result.IsSuccess);
Assert.Equal([BadgeVariant.WheelWizardDeveloper, BadgeVariant.Translator], _service.GetBadges(FriendCode1));
Assert.Equal([BadgeVariant.LeafStruckGold], _service.GetBadges(FriendCode2));
}

[Fact]
public async Task LoadBadgesAsync_ReturnsFailure_WhenApiCallFails()
{
var expectedError = new OperationError { Message = "API call failed" };
_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IRwfcApi, Task<RwfcBadgesResponse>>>>()).Returns(expectedError);

var result = await _service.LoadBadgesAsync();

Assert.True(result.IsFailure);
Assert.Equal(expectedError.Message, result.Error.Message);
Assert.Empty(_service.GetBadges(FriendCode1));
}

[Fact]
public async Task LoadBadgesAsync_SortsAndDeduplicatesBadges()
{
SetupResponse(new() { { Pid1, [2015, 1002, 100, 1002] } });

await _service.LoadBadgesAsync();

Assert.Equal([BadgeVariant.RWFCModerator, BadgeVariant.Supporter, BadgeVariant.EarthboundGold], _service.GetBadges(FriendCode1));
}

[Fact]
public async Task LoadBadgesAsync_SkipsUnknownBadgesAndInvalidEntries()
{
SetupResponse(
new()
{
{ Pid1, [-1, 0, 999, 9999] },
{ Pid2, null },
{ "not-a-pid", [0] },
{ "0", [0] },
}
);

var result = await _service.LoadBadgesAsync();

Assert.True(result.IsSuccess);
Assert.Equal([BadgeVariant.RetroRewindDeveloper], _service.GetBadges(FriendCode1));
Assert.Empty(_service.GetBadges(FriendCode2));
}

[Theory]
[InlineData("")]
[InlineData("not a friend code")]
[InlineData("3137-3137-3138")] // checksum does not match the pid
[InlineData("0000-0000-0000")]
public async Task GetBadges_ReturnsEmpty_ForUnknownOrInvalidFriendCodes(string friendCode)
{
SetupResponse(new() { { Pid1, [1] } });
await _service.LoadBadgesAsync();

Assert.Empty(_service.GetBadges(friendCode));
}

[Fact]
public async Task LoadBadgesAsync_ReplacesPreviousBadges()
{
SetupResponse(new() { { Pid1, [1] } });
await _service.LoadBadgesAsync();
Assert.Equal([BadgeVariant.WheelWizardDeveloper], _service.GetBadges(FriendCode1));

SetupResponse(new() { { Pid2, [1001] } });
await _service.LoadBadgesAsync();

Assert.Empty(_service.GetBadges(FriendCode1));
Assert.Equal([BadgeVariant.Translator], _service.GetBadges(FriendCode2));
}
}
129 changes: 0 additions & 129 deletions WheelWizard.Test/Features/WhWzDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,133 +50,4 @@ public async Task GetStatusAsync_ReturnsFailure_WhenApiCallFails()
Assert.True(result.IsFailure);
Assert.Equal(expectedError.Message, result.Error.Message);
}

[Fact]
public async Task LoadBadgesAsync_ReturnsSuccess_WhenApiCallSucceeds()
{
// Arrange
var badgeData = new Dictionary<string, BadgeVariant[]>
{
{ "FC1", [BadgeVariant.WhWzDev, BadgeVariant.Translator] },
{ "FC2", [BadgeVariant.RrDev] },
{ "FC3", [BadgeVariant.None, BadgeVariant.Firestarter_GoldWinner] },
};

_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>()).Returns(Ok(badgeData));

// Act
var result = await _service.LoadBadgesAsync();

// Assert
Assert.True(result.IsSuccess);
}

[Fact]
public async Task LoadBadgesAsync_ReturnsFailure_WhenApiCallFails()
{
// Arrange
var expectedError = new OperationError { Message = "API call failed" };

_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>()).Returns(expectedError);

// Act
var result = await _service.LoadBadgesAsync();

// Assert
Assert.True(result.IsFailure);
Assert.Equal(expectedError.Message, result.Error.Message);
}

[Fact]
public async Task GetBadges_ReturnsEmptyArray_WhenFriendCodeNotFound()
{
// Arrange
var badgeData = new Dictionary<string, BadgeVariant[]> { { "FC1", [BadgeVariant.WhWzDev, BadgeVariant.Translator] } };

_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>()).Returns(Ok(badgeData));

await _service.LoadBadgesAsync();

// Act
var result = _service.GetBadges("NonExistentFC");

// Assert
Assert.Empty(result);
}

[Fact]
public async Task GetBadges_ReturnsBadges_WhenFriendCodeExists()
{
// Arrange
var badgeData = new Dictionary<string, BadgeVariant[]> { { "FC1", [BadgeVariant.WhWzDev, BadgeVariant.Translator] } };

_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>()).Returns(Ok(badgeData));

await _service.LoadBadgesAsync();

// Act
var result = _service.GetBadges("FC1");

// Assert
Assert.Equal(2, result.Length);
Assert.Contains(BadgeVariant.WhWzDev, result);
Assert.Contains(BadgeVariant.Translator, result);
}

[Fact]
public async Task GetBadges_FiltersOutNoneBadges_WhenLoadingBadges()
{
// Arrange
var badgeData = new Dictionary<string, BadgeVariant[]> { { "FC1", [BadgeVariant.None, BadgeVariant.WhWzDev, BadgeVariant.None] } };

_apiCaller.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>()).Returns(Ok(badgeData));

await _service.LoadBadgesAsync();

// Act
var result = _service.GetBadges("FC1");

// Assert
Assert.Single(result);
Assert.Contains(BadgeVariant.WhWzDev, result);
Assert.DoesNotContain(BadgeVariant.None, result);
}

[Fact]
public async Task LoadBadgesAsync_OverwritesExistingBadges_WhenCalledMultipleTimes()
{
// Arrange - First load
var initialBadgeData = new Dictionary<string, BadgeVariant[]> { { "FC1", [BadgeVariant.WhWzDev] } };

_apiCaller
.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>())
.Returns(Ok(initialBadgeData));

await _service.LoadBadgesAsync();

// Verify initial state
var initialBadges = _service.GetBadges("FC1");
Assert.Single(initialBadges);
Assert.Contains(BadgeVariant.WhWzDev, initialBadges);

// Arrange - Second load with different data
var updatedBadgeData = new Dictionary<string, BadgeVariant[]>
{
{ "FC1", [BadgeVariant.Translator, BadgeVariant.Firestarter_GoldWinner] },
};

_apiCaller
.CallApiAsync(Arg.Any<Expression<Func<IWhWzDataApi, Task<Dictionary<string, BadgeVariant[]>>>>>())
.Returns(Ok(updatedBadgeData));

// Act
await _service.LoadBadgesAsync();

// Assert
var updatedBadges = _service.GetBadges("FC1");
Assert.Equal(2, updatedBadges.Length);
Assert.Contains(BadgeVariant.Translator, updatedBadges);
Assert.Contains(BadgeVariant.Firestarter_GoldWinner, updatedBadges);
Assert.DoesNotContain(BadgeVariant.WhWzDev, updatedBadges);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using WheelWizard.Settings;
using WheelWizard.Shared.Binary;
using WheelWizard.Shared.Polling;
using WheelWizard.WheelWizardData;
using WheelWizard.WiiManagement.GameLicense;
using WheelWizard.WiiManagement.MiiManagement;

Expand Down Expand Up @@ -78,7 +77,7 @@ private static (GameLicenseSingletonService, MockFileSystem, ISettingsManager) C
var service = new GameLicenseSingletonService(
miis,
fs,
Substitute.For<IWhWzDataSingletonService>(),
Substitute.For<IRrBadgeSingletonService>(),
Substitute.For<IRrRatingReader>(),
settings,
Substitute.For<ISaveRegionService>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void StoppedRooms_DoNotPublishCompletedOrPendingRequest(bool completeBefo
var completion = new TaskCompletionSource<OperationResult<List<RwfcRoomStatusRoom>>>();
rooms.GetRoomsAsync().Returns(completion.Task);
using var service = new LiveRoomsService(
Substitute.For<IWhWzDataSingletonService>(),
Substitute.For<IRrBadgeSingletonService>(),
rooms,
rankings,
licenses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using WheelWizard.GameBanana.InstallRequests;
using WheelWizard.Launching;
using WheelWizard.Mods;
using WheelWizard.RrRooms;
using WheelWizard.Settings;
using WheelWizard.WheelWizardData;

namespace WheelWizard.ApplicationLifecycle;

Expand All @@ -17,7 +17,7 @@ public sealed class ApplicationStartup(
IModManager mods,
IModInstallRequestHandler installRequests,
IAutoUpdaterSingletonService updates,
IWhWzDataSingletonService badges,
IRrBadgeSingletonService badges,
IApplicationLiveUpdates liveUpdates,
ISettingsManager settings,
IRetroRewindLaunchService launch,
Expand Down
3 changes: 3 additions & 0 deletions WheelWizard/Features/RrRooms/Domain/IRwfcApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ public interface IRwfcApi

[Get("/api/leaderboard/player/{friendCode}/history")]
Task<RwfcPlayerVrHistoryResponse> GetPlayerVrHistoryAsync(string friendCode, [AliasAs("days")] int days);

[Get("/api/badges/all")]
Task<RwfcBadgesResponse> GetAllBadgesAsync();
}
7 changes: 7 additions & 0 deletions WheelWizard/Features/RrRooms/Domain/RwfcBadgesResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace WheelWizard.RrRooms;

public sealed class RwfcBadgesResponse
{
// Keyed by profile id (pid); values are RWFC badge ids.
public Dictionary<string, int[]?> Badges { get; set; } = [];
}
Loading
Loading