Files
Ludarium release export df869819ce
Public source validation / source (push) Successful in 2m16s
Publish Ludarium source
2026-09-03 02:08:58 +02:00

275 lines
13 KiB
C#

using Ludarium.Application;
using Ludarium.Domain;
namespace Ludarium.UnitTests;
public sealed class BrowserPlayPolicyTests
{
[Fact]
public void DeterministicNesFixtureIsAllowlistedOnlyWhenSourceIsReadOnly()
{
var fixture = Convert.FromBase64String(File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "fixtures", "minimal-ines.base64")).Trim());
Assert.Equal([0x4e, 0x45, 0x53, 0x1a], fixture[..4]);
var candidate = Candidate("nes", "roms/nes/synthetic.nes", fixture.Length, readOnly: true);
var result = BrowserPlayPolicy.Evaluate(candidate, configured: true, emulatorHealthy: true, DateTimeOffset.UnixEpoch);
Assert.True(result.Available);
Assert.Equal(BrowserPlayState.Available, result.State);
Assert.Equal("fceumm", result.Core);
Assert.True(result.AutomaticRestore);
}
[Theory]
[InlineData("pc", "PC/setup.exe", BrowserPlayState.UnsupportedPlatform)]
[InlineData("nes", "roms/nes/setup.exe", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "roms/nes/launch.bat", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "roms/nes/launch.ps1", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "roms/nes/archive.zip", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "roms/nes/../../private/game.nes", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "C:\\private\\game.nes", BrowserPlayState.UnsupportedFormat)]
[InlineData("nes", "/private/game.nes", BrowserPlayState.UnsupportedFormat)]
public void NativeExecutableScriptAndNonAllowlistedContentFailClosed(string platform, string path, BrowserPlayState state)
{
var result = BrowserPlayPolicy.Evaluate(Candidate(platform, path, 64, true), true, true, DateTimeOffset.UnixEpoch);
Assert.False(result.Available);
Assert.Equal(state, result.State);
}
[Fact]
public void WritableMissingAndOversizedSourcesFailClosed()
{
Assert.Equal(BrowserPlayState.Disabled, BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", 16, false), true, true, DateTimeOffset.UnixEpoch).State);
Assert.Equal("fceumm", BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", 16, true), false, false, DateTimeOffset.UnixEpoch).Core);
Assert.Equal(BrowserPlayState.MissingRom, BrowserPlayPolicy.Evaluate(null, true, true, DateTimeOffset.UnixEpoch).State);
Assert.Equal(BrowserPlayState.UnsupportedFormat, BrowserPlayPolicy.Evaluate(Candidate("nes", "game.nes", BrowserPlayPolicy.MaximumRomBytes + 1, true), true, true, DateTimeOffset.UnixEpoch).State);
}
[Fact]
public void SafeNameCannotExposeAStoredPath()
{
Assert.Equal("game.nes", BrowserPlayPolicy.SafeFileName("roms/nes/../../private/game.nes"));
Assert.Equal("game.nes", BrowserPlayPolicy.SafeFileName("roms\\nes\\game.nes"));
}
[Fact]
public void FixtureQualifiedCoreClaimsVerifiedAutomaticRestore()
{
var result = BrowserPlayPolicy.Evaluate(Candidate("gba", "roms/gba/synthetic.gba", 64, true),
true, true, DateTimeOffset.UnixEpoch);
Assert.True(result.Available);
Assert.Equal(BrowserPlayState.Available, result.State);
Assert.Equal("mgba", result.Core);
Assert.True(result.AutomaticRestore);
}
[Fact]
public void N64ZipContainerProceedsOnlyToTheServiceLevelContentValidation()
{
var result = BrowserPlayPolicy.Evaluate(Candidate("n64", "roms/n64/game.zip", 1024, true),
true, true, DateTimeOffset.UnixEpoch);
Assert.True(result.Available);
Assert.Equal("n64wasm", result.Core);
}
[Fact]
public void Ps2UsesItsFixtureProvenDiscLimitWithoutClaimingStateRestore()
{
var belowLimit = BrowserPlayPolicy.Evaluate(
Candidate("ps2", "roms/ps2/personal.iso", BrowserPlayPolicy.MaximumRomBytes + 1, true),
true, true, DateTimeOffset.UnixEpoch);
var aboveLimit = BrowserPlayPolicy.Evaluate(
Candidate("ps2", "roms/ps2/personal.iso", 8L * 1024 * 1024 * 1024 + 1, true),
true, true, DateTimeOffset.UnixEpoch);
Assert.True(belowLimit.Available);
Assert.Equal("playjs", belowLimit.Core);
Assert.Equal("Ludarium Play!.js", belowLimit.Emulator);
Assert.False(belowLimit.AutomaticRestore);
Assert.Equal(BrowserPlayState.UnsupportedFormat, aboveLimit.State);
}
[Fact]
public void EveryAdvertisedCoreHasAReleaseFixture()
{
var qualified = BrowserPlayPolicy.DescribePlatforms().Where(platform => platform.ReleaseQualified).ToArray();
Assert.Equal(13, qualified.Length);
Assert.Contains(qualified, platform => platform.Platform == "nes" && platform.Core == "fceumm");
Assert.Contains(qualified, platform => platform.Platform == "snes" && platform.Core == "snes9x");
Assert.Contains(qualified, platform => platform.Platform == "gb" && platform.Core == "gambatte");
Assert.Contains(qualified, platform => platform.Platform == "gbc" && platform.Core == "gambatte");
Assert.Contains(qualified, platform => platform.Platform == "gba" && platform.Core == "mgba");
Assert.Contains(qualified, platform => platform.Platform == "nds" && platform.Core == "melonds");
Assert.Contains(qualified, platform => platform.Platform == "psp" && platform.Core == "ppsspp");
Assert.Contains(qualified, platform => platform.Platform == "psx" && platform.Core == "pcsx_rearmed");
Assert.Contains(qualified, platform => platform.Platform == "ps2" && platform.Core == "playjs" &&
platform.MaximumBytes == 8L * 1024 * 1024 * 1024);
Assert.Contains(qualified, platform => platform.Platform == "n64" && platform.Core == "n64wasm" &&
platform.MaximumBytes == BrowserPlayPolicy.MaximumRomBytes);
foreach (var platform in new[] { "genesis", "mastersystem", "gamegear" })
Assert.Contains(qualified, entry => entry.Platform == platform && entry.Core == "genesis_plus_gx");
}
private static BrowserPlayCandidate Candidate(string platform, string path, long size, bool readOnly) =>
new(Guid.NewGuid(), Guid.NewGuid(), "Synthetic", platform, path, size, null, readOnly);
[Fact]
public void ACueSheetWinsOverTheTracksItDescribes()
{
var gameId = Guid.NewGuid();
var sheet = Candidate(gameId, "roms/psx/Game/Game.cue", 512);
var track = Candidate(gameId, "roms/psx/Game/Game (Track 1).bin", 700_000_000);
var audio = Candidate(gameId, "roms/psx/Game/Game (Track 2).bin", 40_000_000);
// Without this the tracks make every multi-track disc look ambiguous and unplayable.
Assert.Same(sheet, BrowserPlayPolicy.SelectCandidate([track, sheet, audio]));
Assert.Same(track, BrowserPlayPolicy.SelectCandidate([track]));
}
[Fact]
public void APlaylistWinsOverTheDiscsAndTracksItNames()
{
var gameId = Guid.NewGuid();
var playlist = Candidate(gameId, "roms/psx/Game/Game.m3u", 64);
var firstDisc = Candidate(gameId, "roms/psx/Game/Game (Disc 1).cue", 512);
var secondDisc = Candidate(gameId, "roms/psx/Game/Game (Disc 2).cue", 512);
var track = Candidate(gameId, "roms/psx/Game/Game (Disc 1) (Track 1).bin", 700_000_000);
// Descriptors outrank what they describe, most general first.
Assert.Same(playlist, BrowserPlayPolicy.SelectCandidate([firstDisc, track, playlist, secondDisc]));
Assert.Null(BrowserPlayPolicy.SelectCandidate([firstDisc, secondDisc, track]));
}
[Fact]
public void AMultiDiscGameIsDeliverableButNotYetQualified()
{
var psx = BrowserPlayPolicy.GetPlatform("psx")!;
Assert.True(psx.Accepts(".m3u"));
Assert.False(psx.IsQualified(".m3u"));
Assert.False(BrowserPlayPolicy.GetPlatform("nes")!.Accepts(".m3u"));
}
[Fact]
public void TwoSheetsOrTwoDiscsStayUnresolvable()
{
var gameId = Guid.NewGuid();
Assert.Null(BrowserPlayPolicy.SelectCandidate([
Candidate(gameId, "roms/psx/Game/Disc 1.cue", 512),
Candidate(gameId, "roms/psx/Game/Disc 2.cue", 512)]));
Assert.Null(BrowserPlayPolicy.SelectCandidate([
Candidate(gameId, "roms/psx/Game/Disc 1.chd", 500_000_000),
Candidate(gameId, "roms/psx/Game/Disc 2.chd", 500_000_000)]));
Assert.Null(BrowserPlayPolicy.SelectCandidate([]));
}
[Fact]
public void SegaPlatformsAreReleaseQualifiedByTheirDeterministicFixtures()
{
foreach (var platform in new[] { "genesis", "mastersystem", "gamegear" })
{
var mapping = BrowserPlayPolicy.GetPlatform(platform);
Assert.NotNull(mapping);
Assert.Equal("genesis_plus_gx", mapping.Core);
// Rule 10: each of these completed the live core gate on its own pinned fixture, which
// booted genesis_plus_gx, captured a savestate and restored it into a relaunched session.
Assert.True(mapping.ReleaseQualified);
Assert.Contains("genesis_plus_gx", BrowserPlayPolicy.RequiredCores);
}
// The savestate half of that evidence is what lets a session restore without being asked.
Assert.True(BrowserGameDataPolicy.SupportsAutomaticRestore("genesis_plus_gx"));
}
[Fact]
public void EveryMappedCoreIsRequiredExceptLudariumsOwnPlayers()
{
Assert.DoesNotContain("playjs", BrowserPlayPolicy.RequiredCores);
Assert.DoesNotContain("n64wasm", BrowserPlayPolicy.RequiredCores);
foreach (var platform in BrowserPlayPolicy.DescribePlatforms())
if (platform.Core is not "playjs" and not "n64wasm")
Assert.Contains(platform.Core, BrowserPlayPolicy.RequiredCores);
}
[Fact]
public void ACueSetIsDeliverableForPlayStationButNotForPlayJs()
{
var psx = BrowserPlayPolicy.GetPlatform("psx")!;
var ps2 = BrowserPlayPolicy.GetPlatform("ps2")!;
// EmulatorJS mounts the streamed archive; Play!.js boots a raw disc image and cannot.
Assert.True(psx.Accepts(".cue"));
Assert.False(ps2.Accepts(".cue"));
Assert.False(BrowserPlayPolicy.GetPlatform("nes")!.Accepts(".cue"));
}
[Fact]
public void ADeliverableButUnqualifiedFormatFailsClosedWithAnExactReason()
{
var gameId = Guid.NewGuid();
var qualified = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.chd", 500_000_000),
true, true, DateTimeOffset.UnixEpoch);
var candidate = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.cue", 512),
true, true, DateTimeOffset.UnixEpoch);
var unknown = BrowserPlayPolicy.Evaluate(Candidate(gameId, "roms/psx/Game/Game.rar", 512),
true, true, DateTimeOffset.UnixEpoch);
Assert.True(qualified.Available);
// Rule 10: an implemented delivery path is not a support claim until a fixture proves it.
Assert.False(candidate.Available);
Assert.Equal(BrowserPlayState.UnsupportedFormat, candidate.State);
Assert.Contains("not release-qualified", candidate.Message, StringComparison.Ordinal);
Assert.False(unknown.Available);
Assert.Contains("not allowlisted", unknown.Message, StringComparison.Ordinal);
}
[Fact]
public void OnlyTheFixtureProvenPlayStation2ContainerIsClaimedPlayable()
{
var ps2 = BrowserPlayPolicy.GetPlatform("ps2")!;
// Play!.js range-reads a raw block device and boots it as an ISO. Only the ISO fixture has
// completed the live gate, so a compressed container is deliverable, never claimed.
Assert.True(ps2.IsQualified(".iso"));
foreach (var candidate in new[] { ".cso", ".chd", ".isz", ".bin" })
{
Assert.True(ps2.Accepts(candidate), candidate);
Assert.False(ps2.IsQualified(candidate), candidate);
Assert.False(BrowserPlayPolicy.Evaluate(
Candidate(Guid.NewGuid(), $"roms/ps2/Game/Game{candidate}", 4_000_000_000),
true, true, DateTimeOffset.UnixEpoch).Available, candidate);
}
Assert.True(BrowserPlayPolicy.Evaluate(Candidate(Guid.NewGuid(), "roms/ps2/Game/Game.iso", 4_000_000_000),
true, true, DateTimeOffset.UnixEpoch).Available);
}
[Theory]
[InlineData("snes", ".sfc", ".smc")]
[InlineData("psx", ".chd", ".pbp")]
[InlineData("psp", ".pbp", ".iso")]
[InlineData("psp", ".pbp", ".cso")]
[InlineData("genesis", ".md", ".gen")]
[InlineData("genesis", ".md", ".smd")]
[InlineData("genesis", ".md", ".bin")]
public void ContainerClaimsStayBoundToTheExactLiveFixture(string platform, string qualifiedExtension,
string candidateExtension)
{
var mapping = BrowserPlayPolicy.GetPlatform(platform)!;
Assert.True(mapping.IsQualified(qualifiedExtension));
Assert.True(mapping.Accepts(candidateExtension));
Assert.False(mapping.IsQualified(candidateExtension));
var result = BrowserPlayPolicy.Evaluate(
Candidate(Guid.NewGuid(), $"roms/{platform}/Fixture/Game{candidateExtension}", 64 * 1024),
true, true, DateTimeOffset.UnixEpoch);
Assert.False(result.Available);
Assert.Equal(BrowserPlayState.UnsupportedFormat, result.State);
Assert.Contains("not release-qualified", result.Message, StringComparison.Ordinal);
}
private static BrowserPlayCandidate Candidate(Guid gameId, string path, long size) =>
new(gameId, Guid.NewGuid(), "Fixture", path.Split('/')[1], path, size, null, true);
}