diff --git a/sample/SampleApplication/SampleApplication.csproj b/sample/SampleApplication/SampleApplication.csproj index a40c100..c332fcf 100644 --- a/sample/SampleApplication/SampleApplication.csproj +++ b/sample/SampleApplication/SampleApplication.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/PetroGlyph.Games.EawFoc/src/PG.StarWarsGame.Infrastructure.csproj b/src/PetroGlyph.Games.EawFoc/src/PG.StarWarsGame.Infrastructure.csproj index fb3fc10..7097e61 100644 --- a/src/PetroGlyph.Games.EawFoc/src/PG.StarWarsGame.Infrastructure.csproj +++ b/src/PetroGlyph.Games.EawFoc/src/PG.StarWarsGame.Infrastructure.csproj @@ -22,10 +22,10 @@ true - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/PetroGlyph.Games.EawFoc/src/PetroglyphGameInfrastructure.cs b/src/PetroGlyph.Games.EawFoc/src/PetroglyphGameInfrastructure.cs index 9222120..78d7078 100644 --- a/src/PetroGlyph.Games.EawFoc/src/PetroglyphGameInfrastructure.cs +++ b/src/PetroGlyph.Games.EawFoc/src/PetroglyphGameInfrastructure.cs @@ -28,7 +28,6 @@ public static void InitializeServices(IServiceCollection serviceCollection) serviceCollection.AddSingleton(sp => new SteamGameHelpers(sp)); serviceCollection.AddSingleton(sp => new GameFactory(sp)); //serviceCollection.AddSingleton(sp => new ModFactory(sp)); - serviceCollection.AddSingleton(sp => new ModIdentifierBuilder(sp)); serviceCollection.AddSingleton(sp => new ModFinder(sp)); serviceCollection.AddSingleton(sp => new ModReferenceLocationResolver(sp)); diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/DetectedModReference.cs b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/DetectedModReference.cs deleted file mode 100644 index 7be0be4..0000000 --- a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/DetectedModReference.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.IO.Abstractions; -using EawModinfo.Spec; - -namespace PG.StarWarsGame.Infrastructure.Services.Detection; - -/// -/// Represents a detected mod reference with the optional associated . -/// -public sealed class DetectedModReference -{ - /// - /// Initializes a new instance of the with the specified mod reference and modinfo. - /// - /// The detected mod reference. - /// The directory of the mod. - /// The optional detected modinfo. - /// is . - public DetectedModReference(IModReference modReference, IDirectoryInfo directory, IModinfo? modInfo) - { - ModReference = modReference ?? throw new ArgumentNullException(nameof(modReference)); - ModInfo = modInfo; - Directory = directory; - } - - /// - /// Gets the detected mod reference. - /// - public IModReference ModReference { get; } - - /// - /// Gets the detected directory of the mod. - /// - /// The value can be , if the detected mod reference is a virtual mod. - public IDirectoryInfo? Directory { get; } - - /// - /// Gets the detected modinfo or if none was detected. - /// - public IModinfo? ModInfo { get; } -} \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModIdentifierBuilder.cs b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModIdentifierBuilder.cs deleted file mode 100644 index 6acc291..0000000 --- a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModIdentifierBuilder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.IO.Abstractions; -using EawModinfo.Model; -using EawModinfo.Spec; -using PG.StarWarsGame.Infrastructure.Mods; - -namespace PG.StarWarsGame.Infrastructure.Services.Detection; - -internal interface IModIdentifierBuilder -{ - string Build(IDirectoryInfo modDirectory, bool isWorkshop); - - string Build(IMod mod); - - ModReference Normalize(IModReference modReference); -} \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModReferenceFinder.cs b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModReferenceFinder.cs index 7940ab4..36d5485 100644 --- a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModReferenceFinder.cs +++ b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/IModReferenceFinder.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using EawModinfo.Spec; +using EawModinfo.Utilities; using PG.StarWarsGame.Infrastructure.Games; namespace PG.StarWarsGame.Infrastructure.Services.Detection; @@ -10,9 +12,11 @@ namespace PG.StarWarsGame.Infrastructure.Services.Detection; public interface IModFinder { /// - /// Searches for for a given game. + /// Searches for physically installed mods for the specified game. /// /// The game to search mods for. - /// A set of detected mod references. - ISet FindMods(IGame game); + /// A collection of installed mods of . + /// is . + /// does not exist. + ICollection FindMods(IGame game); } \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModFinder.cs b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModFinder.cs index 4e3972f..d70cb8a 100644 --- a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModFinder.cs +++ b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModFinder.cs @@ -3,8 +3,8 @@ using System.IO.Abstractions; using System.Linq; using EawModinfo.File; -using EawModinfo.Model; using EawModinfo.Spec; +using EawModinfo.Utilities; using Microsoft.Extensions.DependencyInjection; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Services.Steam; @@ -14,19 +14,15 @@ namespace PG.StarWarsGame.Infrastructure.Services.Detection; internal class ModFinder : IModFinder { private readonly ISteamGameHelpers _steamHelper; - private readonly IModIdentifierBuilder _idBuilder; private readonly IModGameTypeResolver _gameTypeResolver; public ModFinder(IServiceProvider serviceProvider) { - if (serviceProvider == null) - throw new ArgumentNullException(nameof(serviceProvider)); - _idBuilder = serviceProvider.GetRequiredService(); _steamHelper = serviceProvider.GetRequiredService(); _gameTypeResolver = serviceProvider.GetRequiredService(); } - public ISet FindMods(IGame game) + public ICollection FindMods(IGame game) { if (game == null) throw new ArgumentNullException(nameof(game)); @@ -34,57 +30,36 @@ public ISet FindMods(IGame game) if (!game.Exists()) throw new GameException("The game does not exist"); - var mods = new HashSet(); - foreach (var modReference in GetNormalMods(game).Union(GetWorkshopsMods(game))) - mods.Add(modReference); - return mods; + return GetNormalMods(game).Union(GetWorkshopsMods(game)).ToList(); } private IEnumerable GetNormalMods(IGame game) { - return GetAllModsFromPath(game.ModsLocation, false, game.Type); + return GetAllModsFromPath(game.ModsLocation, ModReferenceBuilder.ModLocationKind.GameModsDirectory, game.Type); } private IEnumerable GetWorkshopsMods(IGame game) { return game.Platform != GamePlatform.SteamGold ? [] - : GetAllModsFromPath(_steamHelper.GetWorkshopsLocation(game), true, game.Type); + : GetAllModsFromPath(_steamHelper.GetWorkshopsLocation(game), ModReferenceBuilder.ModLocationKind.SteamWorkshops, game.Type); } - private IEnumerable GetAllModsFromPath(IDirectoryInfo lookupDirectory, bool isWorkshopsPath, GameType requestedGameType) + private IEnumerable GetAllModsFromPath(IDirectoryInfo lookupDirectory, ModReferenceBuilder.ModLocationKind locationKind, GameType requestedGameType) { if (!lookupDirectory.Exists) yield break; - var type = isWorkshopsPath ? ModType.Workshops : ModType.Default; - foreach (var modDirectory in lookupDirectory.EnumerateDirectories()) { - var modinfoFiles = ModinfoFileFinder.FindModinfoFiles(modDirectory); - - IModinfo? mainModinfo = null; - - // Since the concept of modinfo files is completely optional, - // a malformed modinfo file should not cause a crash. The mod in question gets fond without a modinfo. - modinfoFiles.MainModinfo?.TryGetModinfo(out mainModinfo); - - if (IsDefinitelyNotGameType(requestedGameType, modDirectory, type, mainModinfo)) - continue; + ModinfoFinderCollection modinfoFiles; + modinfoFiles = ModinfoFileFinder.FindModinfoFiles(modDirectory); - var id = _idBuilder.Build(modDirectory, isWorkshopsPath); - yield return new DetectedModReference(new ModReference(id, type), modDirectory, mainModinfo); - - foreach (var variantModinfoFile in modinfoFiles.Variants) + foreach (var modRef in ModReferenceBuilder.CreateIdentifiers(modinfoFiles, locationKind)) { - if (!variantModinfoFile.TryGetModinfo(out var variantModinfo)) - continue; - - if (IsDefinitelyNotGameType(requestedGameType, modDirectory, type, variantModinfo)) + if (IsDefinitelyNotGameType(requestedGameType, modDirectory, modRef.ModReference.Type, modRef.Modinfo)) continue; - - id = _idBuilder.Build(modDirectory, isWorkshopsPath); - yield return new DetectedModReference(new ModReference(id, type), modDirectory, variantModinfo); + yield return modRef; } } } @@ -94,6 +69,6 @@ private bool IsDefinitelyNotGameType(GameType expected, IDirectoryInfo modDirect // If the type resolver was unable to find the type, we have to assume that the current mod matches to the game. // Otherwise, we'd produce false negatives. Only if the resolver was able to determine a result, we use that finding. return _gameTypeResolver.TryGetGameType(modDirectory, modType, modinfo, out var variantGameType) && - variantGameType.Contains(expected); + !variantGameType.Contains(expected); } } \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModIdentifierBuilder.cs b/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModIdentifierBuilder.cs deleted file mode 100644 index d4560c4..0000000 --- a/src/PetroGlyph.Games.EawFoc/src/Services/Detection/Mods/ModIdentifierBuilder.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.IO.Abstractions; -using AnakinRaW.CommonUtilities.FileSystem.Normalization; -using EawModinfo.Model; -using EawModinfo.Spec; -using Microsoft.Extensions.DependencyInjection; -using PG.StarWarsGame.Infrastructure.Mods; -using PG.StarWarsGame.Infrastructure.Services.Steam; - -namespace PG.StarWarsGame.Infrastructure.Services.Detection; - -internal class ModIdentifierBuilder : IModIdentifierBuilder -{ - private readonly ISteamGameHelpers _steamGameHelper; - - private static readonly PathNormalizeOptions PathNormalizeOptions = new() - { - UnifyCase = UnifyCasingKind.UpperCase, - TrailingDirectorySeparatorBehavior = TrailingDirectorySeparatorBehavior.Trim - }; - - /// - /// Creates a new instance. - /// - /// The service provider. - public ModIdentifierBuilder(IServiceProvider serviceProvider) - { - _steamGameHelper = serviceProvider.GetRequiredService(); - } - - public string Build(IDirectoryInfo modDirectory, bool isWorkshop) - { - return isWorkshop ? BuildWorkshopsModId(modDirectory) : BuildDefaultModId(modDirectory); - } - - /// - public string Build(IMod mod) - { - if (mod.Type == ModType.Default) - { - if (mod is not IPhysicalMod physicalMod) - throw new InvalidOperationException(); - return BuildDefaultModId(physicalMod.Directory); - } - - if (mod.Type == ModType.Workshops) - { - if (mod is not IPhysicalMod physicalMod) - throw new InvalidOperationException(); - return BuildWorkshopsModId(physicalMod.Directory); - } - - if (mod.Type == ModType.Virtual) - { - if (mod is not IVirtualMod virtualMod) - throw new InvalidOperationException(); - return BuildVirtualModId(virtualMod); - } - - throw new NotSupportedException($"Cannot create identifier for unsupported mod type {mod.Type}."); - } - - private static string BuildDefaultModId(IDirectoryInfo modDir) - { - return BuildDefaultModId(modDir.FullName); - } - - private static string BuildDefaultModId(string modDirPath) - { - // NB: We don't want to resolve the path, cause the string is user data which could be abused to traverse paths. - // Though, this is not a complete fix. Consumers of a mod's Identifier property still must validate the data. - return PathNormalizer.Normalize(modDirPath, PathNormalizeOptions); - } - - private string BuildWorkshopsModId(IDirectoryInfo modDir) - { - if (!_steamGameHelper.ToSteamWorkshopsId(modDir.Name, out _)) - throw new InvalidOperationException($"{modDir} is not a valid Steam Workshop directory."); - return modDir.Name; - } - - private static string BuildVirtualModId(IVirtualMod mod) - { - if (mod.ModInfo is null) - throw new NotSupportedException("Virtual mods without modinfo data are not supported."); - return mod.ModInfo.ToJson(); - } - - public ModReference Normalize(IModReference modReference) - { - var id = modReference.Type switch - { - ModType.Default => BuildDefaultModId(modReference.Identifier), - ModType.Workshops => modReference.Identifier, - ModType.Virtual => modReference.Identifier, - _ => throw new ArgumentOutOfRangeException() - }; - return new ModReference(id, modReference.Type, modReference.VersionRange); - } -} \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/src/Services/IModFactory.cs b/src/PetroGlyph.Games.EawFoc/src/Services/IModFactory.cs index cb2164b..95ae35c 100644 --- a/src/PetroGlyph.Games.EawFoc/src/Services/IModFactory.cs +++ b/src/PetroGlyph.Games.EawFoc/src/Services/IModFactory.cs @@ -1,9 +1,9 @@ using System.Globalization; using System.IO; using EawModinfo.Spec; +using EawModinfo.Utilities; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Mods; -using PG.StarWarsGame.Infrastructure.Services.Detection; namespace PG.StarWarsGame.Infrastructure.Services; diff --git a/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/DirectoryGameDetectorTest.cs b/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/DirectoryGameDetectorTest.cs index a173353..ea186ca 100644 --- a/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/DirectoryGameDetectorTest.cs +++ b/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/DirectoryGameDetectorTest.cs @@ -1,6 +1,5 @@ using System; using System.IO.Abstractions; -using Moq; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Services.Detection; using PG.StarWarsGame.Infrastructure.Testing.Game.Installation; @@ -28,10 +27,9 @@ protected override GameDetectorTestInfo SetupGame(GameIdentity game [Fact] public void InvalidArgs_Throws() { - var sp = new Mock(); Assert.Throws(() => new DirectoryGameDetector(null!, null!)); Assert.Throws(() => new DirectoryGameDetector(FileSystem.DirectoryInfo.New("Game"), null!)); - Assert.Throws(() => new DirectoryGameDetector(null!, sp.Object)); + Assert.Throws(() => new DirectoryGameDetector(null!, ServiceProvider)); } [Theory] diff --git a/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/RegistryGameDetectorTest.cs b/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/RegistryGameDetectorTest.cs index 2e9a821..d8728a7 100644 --- a/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/RegistryGameDetectorTest.cs +++ b/src/PetroGlyph.Games.EawFoc/test/GameServices/Detection/RegistryGameDetectorTest.cs @@ -107,7 +107,7 @@ public void Dispose_ShallDisposeRegistries(GameIdentity identity) false, ServiceProvider); detector.Dispose(); - Assert.Throws(() => info.DetectorSetupInfo.EawRegistry.CdKey); - Assert.Throws(() => info.DetectorSetupInfo.FocRegistry.CdKey); + Assert.Throws(() => info.DetectorSetupInfo.EawRegistry!.CdKey); + Assert.Throws(() => info.DetectorSetupInfo.FocRegistry!.CdKey); } } \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/test/ModServices/ModFinderTest.cs b/src/PetroGlyph.Games.EawFoc/test/ModServices/ModFinderTest.cs index 703096f..32bb9ff 100644 --- a/src/PetroGlyph.Games.EawFoc/test/ModServices/ModFinderTest.cs +++ b/src/PetroGlyph.Games.EawFoc/test/ModServices/ModFinderTest.cs @@ -1,302 +1,277 @@ using System; -using System.IO.Abstractions; +using System.Collections.Generic; +using System.Linq; +using EawModinfo.Model; +using EawModinfo.Spec.Steam; using Microsoft.Extensions.DependencyInjection; -using Moq; using PG.StarWarsGame.Infrastructure.Games; using PG.StarWarsGame.Infrastructure.Services.Detection; using PG.StarWarsGame.Infrastructure.Services.Steam; +using PG.StarWarsGame.Infrastructure.Testing; using PG.StarWarsGame.Infrastructure.Testing.Game.Installation; -using Testably.Abstractions.Testing; +using PG.StarWarsGame.Infrastructure.Testing.Mods; +using PG.TestingUtilities; using Xunit; namespace PG.StarWarsGame.Infrastructure.Test.ModServices; -public class ModFinderTest +public class ModFinderTest : CommonTestBase { - private readonly IServiceProvider _serviceProvider; - private readonly ModFinder _service; - private readonly Mock _steamHelper; - private readonly MockFileSystem _fileSystem; - private readonly Mock _idBuilder; - private readonly Mock _gameTypeResolver; + private readonly ModFinder _modFinder; + private readonly ISteamGameHelpers _steamGameHelpers; public ModFinderTest() { - var sc = new ServiceCollection(); - _steamHelper = new Mock(); - _fileSystem = new MockFileSystem(); - _idBuilder = new Mock(); - _gameTypeResolver = new Mock(); - sc.AddSingleton(_ => _steamHelper.Object); - sc.AddSingleton(_ => _fileSystem); - sc.AddSingleton(_ => _idBuilder.Object); - sc.AddSingleton(_ => _gameTypeResolver.Object); - - _serviceProvider = sc.BuildServiceProvider(); - _service = new ModFinder(_serviceProvider); + _modFinder = new ModFinder(ServiceProvider); + _steamGameHelpers = ServiceProvider.GetRequiredService(); } [Fact] - public void GameNotExists_Throws() + public void FindMods_NullArg_Throws() { - var game = new Mock(); - Assert.Throws(() => _service.FindMods(game.Object)); + Assert.Throws(() => _modFinder.FindMods(null!)); } - [Fact] - public void TestNoMods_Normal() + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_GameNotExists_Throws(GameIdentity gameIdentity) { - var game = _fileSystem.InstallGame(new GameIdentity(GameType.Foc, GamePlatform.Disk), _serviceProvider); - var mods = _service.FindMods(game); - Assert.Empty(mods); + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + game.Directory.Delete(true); + Assert.Throws(() => _modFinder.FindMods(game)); } - [Fact] - public void TestNoMods_Normal_NoFolder() + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_NoModsDirectory_ShouldNotFindMods(GameIdentity gameIdentity) { - _fileSystem.Initialize().WithSubdirectory("Game"); - var game = new Mock(); - game.Setup(g => g.Exists()).Returns(true); - game.Setup(g => g.Platform).Returns(GamePlatform.Disk); - game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Game")); - game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Game/Mods")); - var mods = _service.FindMods(game.Object); + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + game.ModsLocation.Delete(true); + + if (game.Platform is GamePlatform.SteamGold) + { + var wsDir = _steamGameHelpers.GetWorkshopsLocation(game); + wsDir.Delete(true); + } + + var mods = _modFinder.FindMods(game); Assert.Empty(mods); } - [Fact] - public void TestNoMods_Steam() + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_EmptyModsDirectory_ShouldNotFindMods(GameIdentity gameIdentity) { - _fileSystem.Initialize().WithSubdirectory("Lib/Game/Eaw/Mods"); - var game = new Mock(); - game.Setup(g => g.Exists()).Returns(true); - game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - .Returns(_fileSystem.DirectoryInfo.New("wsDir")); - var mods = _service.FindMods(game.Object); + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + game.ModsLocation.Create(); + + if (game.Platform is GamePlatform.SteamGold) + { + var wsDir = _steamGameHelpers.GetWorkshopsLocation(game); + wsDir.Create(); + } + + var mods = _modFinder.FindMods(game); Assert.Empty(mods); } - //[Fact] - //public void TestOneMods_Normal() - //{ - // _fileSystem.Initialize().WithSubdirectory("Game/Mods/ModA"); - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.Disk); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Game")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Game/Mods")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), false)) - // .Returns("somePath"); - - // var mods = _service.FindMods(game.Object); - // var mod = Assert.Single(mods); - // Assert.Equal("somePath", mod.Identifier); - // Assert.Equal(ModType.Default, mod.Type); - //} - - //[Fact] - //public void TestNoModOfPlatform_Normal() - //{ - // _fileSystem.Initialize().WithSubdirectory("Game/Mods/ModA"); - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.Disk); - // game.Setup(g => g.Type).Returns(GameType.Eaw); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Game")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Game/Mods")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), false)) - // .Returns("somePath"); - - // var resolverResult = GameType.Foc; - // _gameTypeResolver - // .Setup(r => r.TryGetGameType(It.IsAny(), ModType.Default, true, out resolverResult)) - // .Returns(true); - - // var mods = _service.FindMods(game.Object); - // Assert.Empty(mods); - //} - - //[Fact] - //public void TestModOfCorrectPlatform_Normal() - //{ - // _fileSystem.Initialize().WithSubdirectory("Game/Mods/ModA"); - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.Disk); - // game.Setup(g => g.Type).Returns(GameType.Eaw); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Game")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Game/Mods")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), false)) - // .Returns("somePath"); - - // var resolverResult = GameType.Eaw; - // _gameTypeResolver - // .Setup(r => r.TryGetGameType(It.IsAny(), ModType.Default, true, out resolverResult)) - // .Returns(true); - - // var mods = _service.FindMods(game.Object); - // var mod = Assert.Single(mods); - // Assert.Equal("somePath", mod.Identifier); - // Assert.Equal(ModType.Default, mod.Type); - //} - - //[Fact] - //public void TestTwoMods_Normal() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Game/Mods/ModA") - // .WithSubdirectory("Game/Mods/ModB"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.Disk); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Game")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Game/Mods")); - - // _idBuilder.SetupSequence(ib => ib.Build(It.IsAny(), false)) - // .Returns("somePath1") - // .Returns("somePath2"); - - // var mods = _service.FindMods(game.Object); - // Assert.Equal(2, mods.Count); - //} - - //[Fact] - //public void TestOneDefaultMod_Steam() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Lib/Game/Eaw/Mods/ModA"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - // .Returns(_fileSystem.DirectoryInfo.New("path")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), false)) - // .Returns("builderPath"); - - // var mods = _service.FindMods(game.Object); - // var mod = Assert.Single(mods); - - // Assert.Equal("builderPath", mod.Identifier); - // Assert.Equal(ModType.Default, mod.Type); - //} - - //[Fact] - //public void TestOneDefaultModOneWsMod_Steam() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Lib/Game/Eaw/Mods/ModA") - // .WithSubdirectory("Lib/workshop/content/32470/12345678"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - // .Returns(_fileSystem.DirectoryInfo.New("Lib/workshop/content/32470/")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), false)) - // .Returns("defaultPath"); - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), true)) - // .Returns("workshopPath"); - - // var mods = _service.FindMods(game.Object); - // Assert.Equal(2, mods.Count); - - // var wsMod = mods.First(m => m.Type == ModType.Workshops); - // Assert.Equal("workshopPath", wsMod.Identifier); - - // var defaultMod = mods.First(m => m.Type == ModType.Default); - // Assert.Equal("defaultPath", defaultMod.Identifier); - //} - - //[Fact] - //public void TestOneWsMod_Steam() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Lib/workshop/content/32470/12345678"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - // .Returns(_fileSystem.DirectoryInfo.New("Lib/workshop/content/32470/")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), true)) - // .Returns("workshopPath"); - - // var mods = _service.FindMods(game.Object); - // var mod = Assert.Single(mods); - // Assert.Equal("workshopPath", mod.Identifier); - // Assert.Equal(ModType.Workshops, mod.Type); - //} - - //[Fact] - //public void TestNoWsModMatchingType_Steam() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Lib/workshop/content/32470/12345678"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - // game.Setup(g => g.Type).Returns(GameType.Foc); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - // .Returns(_fileSystem.DirectoryInfo.New("Lib/workshop/content/32470/")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), true)) - // .Returns("workshopPath"); - - // var resolverResult = GameType.Eaw; - // _gameTypeResolver - // .Setup(r => r.TryGetGameType(It.IsAny(), ModType.Workshops, true, out resolverResult)) - // .Returns(true); - - // var mods = _service.FindMods(game.Object); - // Assert.Empty(mods); - //} - - //[Fact] - //public void TestNoWsMatchesType_Steam() - //{ - // _fileSystem.Initialize() - // .WithSubdirectory("Lib/workshop/content/32470/12345678"); - - // var game = new Mock(); - // game.Setup(g => g.Exists()).Returns(true); - // game.Setup(g => g.Platform).Returns(GamePlatform.SteamGold); - // game.Setup(g => g.Type).Returns(GameType.Foc); - // game.Setup(g => g.Directory).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // game.Setup(g => g.ModsLocation).Returns(_fileSystem.DirectoryInfo.New("Lib/Game/Eaw/Mods")); - // _steamHelper.Setup(h => h.GetWorkshopsLocation(game.Object)) - // .Returns(_fileSystem.DirectoryInfo.New("Lib/workshop/content/32470/")); - - // _idBuilder.Setup(ib => ib.Build(It.IsAny(), true)) - // .Returns("workshopPath"); - - // var resolverResult = GameType.Foc; - // _gameTypeResolver - // .Setup(r => r.TryGetGameType(It.IsAny(), ModType.Workshops, true, out resolverResult)) - // .Returns(true); - - // var mods = _service.FindMods(game.Object); - // var mod = Assert.Single(mods); - // Assert.Equal("workshopPath", mod.Identifier); - // Assert.Equal(ModType.Workshops, mod.Type); - //} + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_OneMod_WithoutModinfo(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + var expectedMod = game.InstallMod("MyMod", GITestUtilities.GetRandomWorkshopFlag(game), ServiceProvider); + + var installedMods = _modFinder.FindMods(game); + + var foundMod = Assert.Single(installedMods); + + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + Assert.Equal(expectedMod.ModInfo, foundMod.Modinfo); + + // Cannot assert on expectedMod.Identifier, cause this test framework builds the wrong identifiers. + Assert.Equal(expectedMod.Directory.Name, foundMod.ModReference.Identifier); + } + + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_OneMod_WithInvalidModinfo(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + + var expectedMod = game.InstallMod("MyMod", GITestUtilities.GetRandomWorkshopFlag(game), ServiceProvider); + expectedMod.InstallInvalidModinfoFile(); + + + var installedMods = _modFinder.FindMods(game); + + var foundMod = Assert.Single(installedMods); + + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + Assert.Equal(expectedMod.ModInfo, foundMod.Modinfo); + + // Cannot assert on expectedMod.Identifier, cause this test framework builds the wrong identifiers. + Assert.Equal(expectedMod.Directory.Name, foundMod.ModReference.Identifier); + } + + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_OneMod_WithOneInvalidModinfoVariant(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + + var expectedMod = game.InstallMod("MyMod", GITestUtilities.GetRandomWorkshopFlag(game), ServiceProvider); + expectedMod.InstallModinfoFile(new ModinfoData("Variant1"), "variant1"); + expectedMod.InstallInvalidModinfoFile("variant2"); + + + var installedMods = _modFinder.FindMods(game); + + var foundMod = Assert.Single(installedMods); + + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + Assert.Equal("Variant1", foundMod.Modinfo!.Name); + + // Cannot assert on expectedMod.Identifier, cause this test framework builds the wrong identifiers. + Assert.Equal($"{expectedMod.Directory.Name}:Variant1", foundMod.ModReference.Identifier); + } + + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_OneMod_WithMainModinfo(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + + var modinfoData = new ModinfoData("MyMod"); + var expectedMod = game.InstallMod(GITestUtilities.GetRandomWorkshopFlag(game), modinfoData, ServiceProvider); + expectedMod.InstallModinfoFile(modinfoData); + + var installedMods = _modFinder.FindMods(game); + + var foundMod = Assert.Single(installedMods); + + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + + // Cannot assert on expectedMod.Identifier, cause this test framework builds the wrong identifiers. + Assert.Equal(expectedMod.Directory.Name, foundMod.ModReference.Identifier); + } + + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_WithOnlyManyVariantModinfos(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + + var info1 = new ModinfoData("MyName1"); + var info2 = new ModinfoData("MyName2"); + var expectedMod = game.InstallMod("DirName", GITestUtilities.GetRandomWorkshopFlag(game), ServiceProvider); + expectedMod.InstallModinfoFile(info1, "variant1"); + expectedMod.InstallModinfoFile(info2, "variant2"); + + var installedMods = _modFinder.FindMods(game); + + Assert.Equal(2, installedMods.Count); + + foreach (var foundMod in installedMods) + { + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + } + + Assert.Equivalent(new List{ "MyName1", "MyName2" }, installedMods.Select(x => x.Modinfo!.Name), true); + Assert.Equivalent( + new List { $"{expectedMod.Directory.Name}:MyName1", $"{expectedMod.Directory.Name}:MyName2" }, + installedMods.Select(x => x.ModReference.Identifier), true); + } + + [Theory] + [MemberData(nameof(RealGameIdentities))] + public void FindMods_WithMainAndManyVariantModinfos(GameIdentity gameIdentity) + { + var game = FileSystem.InstallGame(gameIdentity, ServiceProvider); + + var main = new ModinfoData("Main"); + var info1 = new ModinfoData("MyName1"); + var info2 = new ModinfoData("MyName2"); + var expectedMod = game.InstallMod("DirName", GITestUtilities.GetRandomWorkshopFlag(game), ServiceProvider); + expectedMod.InstallModinfoFile(main); + expectedMod.InstallModinfoFile(info1, "variant1"); + expectedMod.InstallModinfoFile(info2, "variant2"); + + var installedMods = _modFinder.FindMods(game); + + Assert.Equal(3, installedMods.Count); + + foreach (var foundMod in installedMods) + { + Assert.Equal(expectedMod.Directory.FullName, foundMod.Directory.FullName); + Assert.Equal(expectedMod.Type, foundMod.ModReference.Type); + } + + Assert.Equivalent(new List { "Main", "MyName1", "MyName2" }, installedMods.Select(x => x.Modinfo!.Name), + true); + + Assert.Equivalent( + new List { expectedMod.Directory.Name, $"{expectedMod.Directory.Name}:MyName1", $"{expectedMod.Directory.Name}:MyName2" }, + installedMods.Select(x => x.ModReference.Identifier), true); + } + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void FindMods_Steam_ShouldAddWorkshopsAndMods(GameType type) + { + var game = FileSystem.InstallGame(new GameIdentity(type, GamePlatform.SteamGold), ServiceProvider); + + var steamMod = game.InstallMod("SteamMod", true, ServiceProvider); + + var modinfo = new ModinfoData("Name"); + var defaultMod = game.InstallMod(false, modinfo, ServiceProvider); + defaultMod.InstallModinfoFile(modinfo); + + + var installedMods = _modFinder.FindMods(game); + + Assert.Equal(2, installedMods.Count); + + Assert.Contains(installedMods, + x => x.ModReference.Type == steamMod.Type && steamMod.Directory.FullName.Equals(x.Directory.FullName) && + x.Modinfo is null && x.ModReference.Identifier.Equals(steamMod.Directory.Name)); + + Assert.Contains(installedMods, x => x.ModReference.Type == defaultMod.Type && + defaultMod.Directory.FullName.Equals(x.Directory.FullName) + && x.Modinfo is not null && x.Modinfo.Name.Equals(defaultMod.Name) && + x.ModReference.Identifier.Equals(defaultMod.Directory.Name)); + } + + [Theory] + [InlineData(GameType.Eaw)] + [InlineData(GameType.Foc)] + public void FindMods_Steam_ShouldNotContainModOfWrongGame(GameType type) + { + var game = FileSystem.InstallGame(new GameIdentity(type, GamePlatform.SteamGold), ServiceProvider); + + var oppositeGameType = type is GameType.Eaw ? GameType.Foc : GameType.Eaw; + + var steamData = new SteamData( + new Random().Next(0, int.MaxValue).ToString(), + "path", + TestHelpers.GetRandomEnum(), + "Title", + [$"{oppositeGameType.ToString().ToUpper()}"]); + var modinfo = new ModinfoData("Name") + { + SteamData = steamData + }; + + + var defaultMod = game.InstallMod(true, modinfo, ServiceProvider); + defaultMod.InstallModinfoFile(modinfo); + + Assert.Empty(_modFinder.FindMods(game)); + } } \ No newline at end of file diff --git a/src/PetroGlyph.Games.EawFoc/test/ModServices/ModIdentifierBuilderTest.cs b/src/PetroGlyph.Games.EawFoc/test/ModServices/ModIdentifierBuilderTest.cs deleted file mode 100644 index a79db5e..0000000 --- a/src/PetroGlyph.Games.EawFoc/test/ModServices/ModIdentifierBuilderTest.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using EawModinfo.Model; -using EawModinfo.Spec; -using EawModinfo.Spec.Equality; -using PG.StarWarsGame.Infrastructure.Games; -using PG.StarWarsGame.Infrastructure.Mods; -using PG.StarWarsGame.Infrastructure.Services.Detection; -using PG.StarWarsGame.Infrastructure.Testing; -using PG.StarWarsGame.Infrastructure.Testing.Game.Installation; -using PG.StarWarsGame.Infrastructure.Testing.Mods; -using Semver; -using Xunit; - -namespace PG.StarWarsGame.Infrastructure.Test.ModServices; - -public class ModIdentifierBuilderTest : CommonTestBaseWithRandomGame -{ - private readonly ModIdentifierBuilder _idBuilder; - - public ModIdentifierBuilderTest() - { - _idBuilder = new ModIdentifierBuilder(ServiceProvider); - } - - [Fact] - public void Build_DefaultMod() - { - var mod = Game.InstallMod("Mod", false, ServiceProvider); - - var expected = mod.Directory.FullName.TrimEnd(FileSystem.Path.DirectorySeparatorChar, FileSystem.Path.AltDirectorySeparatorChar); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - expected = expected.ToUpperInvariant(); - - Assert.Equal(expected, _idBuilder.Build(mod)); - Assert.Equal(expected, _idBuilder.Build(mod.Directory, false)); - } - - [Fact] - public void Build_SteamWorkshopMod() - { - var steamGame = FileSystem.InstallGame( - new GameIdentity(TestingUtilities.TestHelpers.GetRandomEnum(), GamePlatform.SteamGold), - ServiceProvider); - - var mod = steamGame.InstallMod("Mod", true, ServiceProvider); - - var expected = mod.Directory.Name; - _ = ulong.Parse(expected); // Check this conversion does not throw - - Assert.Equal(expected, _idBuilder.Build(mod)); - Assert.Equal(expected, _idBuilder.Build(mod.Directory, true)); - } - - [Fact] - public void Build_SteamWorkshopMod_InvalidPathCannotBeConvertedToSteamWSId_Throws() - { - var mod = Game.InstallMod("Mod", false, ServiceProvider); - Assert.Throws(() => _idBuilder.Build(mod.Directory, true)); - } - - [Fact] - public void Build_VirtualMod() - { - var dep = CreateAndAddMod("Dep"); - - var modinfo = new ModinfoData("Name") - { - Dependencies = new DependencyList(new List - { - dep - }, DependencyResolveLayout.FullResolved), - Languages = [new LanguageInfo("de", LanguageSupportLevel.FullLocalized)] - }; - var mod = new VirtualMod(Game, "VirtualModId", modinfo, ServiceProvider); - - var id = _idBuilder.Build(mod); - var parsedModInfo = ModinfoData.Parse(id); - - Assert.Equal(modinfo, parsedModInfo); - Assert.Equal(modinfo.Languages, parsedModInfo.Languages, LanguageInfoEqualityComparer.WithSupportLevel); - } - - [Fact] - public void TestNormalizeWorkshops() - { - var mRef = new ModReference("123456", ModType.Workshops, SemVersionRange.Parse("1.*")); - var expected = new ModReference("123456", ModType.Workshops, SemVersionRange.Parse("1.*")); - var normalizedRef = _idBuilder.Normalize(mRef); - - Assert.Equal(expected, normalizedRef, ModReferenceEqualityComparer.Default); - Assert.Equal(SemVersionRange.Parse("1.*"), normalizedRef.VersionRange); - } - - [Fact] - public void Normalize_VirtualMod() - { - var modinfoString = @"{""name"":""Mod""}"; - var mRef = new ModReference(modinfoString, ModType.Virtual, SemVersionRange.Parse("1.*")); - var expected = new ModReference(modinfoString, ModType.Virtual); - var normalizedRef = _idBuilder.Normalize(mRef); - - Assert.Equal(expected, normalizedRef); - Assert.Equal(SemVersionRange.Parse("1.*"), normalizedRef.VersionRange); - } -} \ No newline at end of file diff --git a/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.Modinfo.cs b/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.Modinfo.cs new file mode 100644 index 0000000..819f7e0 --- /dev/null +++ b/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.Modinfo.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using EawModinfo.File; +using EawModinfo.Spec; +using PG.StarWarsGame.Infrastructure.Mods; + +namespace PG.StarWarsGame.Infrastructure.Testing.Mods; + +public static partial class ModInstallations +{ + public static IModinfoFile InstallInvalidModinfoFile(this IPhysicalMod mod, string? variantSubFileName = null) + { + return InstallModinfoFile(mod, stream => { stream.WriteByte(0); }, variantSubFileName); + } + + public static IModinfoFile InstallModinfoFile( + this IPhysicalMod mod, + IModinfo modinfo, + string? variantSubFileName = null) + { + return InstallModinfoFile(mod, modinfo.ToJson, variantSubFileName); + } + + private static IModinfoFile InstallModinfoFile( + this IPhysicalMod mod, + Action writeAction, + string? variantSubFileName) + { + var dir = mod.Directory; + dir.Create(); + + var fs = dir.FileSystem; + + var modinfoFilePath = fs.Path.Combine(dir.FullName, + variantSubFileName != null + ? $"{variantSubFileName}-modinfo.json" + : "modinfo.json"); + + using var fileStream = fs.FileStream.New(modinfoFilePath, FileMode.Create); + writeAction(fileStream); + + var fileInfo = fs.FileInfo.New(modinfoFilePath); + + if (variantSubFileName is null) + return new ModinfoVariantFile(fileInfo); + return new ModinfoVariantFile(fileInfo); + } + +} \ No newline at end of file diff --git a/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.cs b/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.cs index 7f276d3..3b1ac5b 100644 --- a/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.cs +++ b/src/Testing/PG.StarWarsGame.Infrastructure.Testing/Mods/ModInstallations.cs @@ -9,7 +9,7 @@ namespace PG.StarWarsGame.Infrastructure.Testing.Mods; -public static class ModInstallations +public static partial class ModInstallations { public static Mod InstallAndAddMod(this IGame game, string name, bool workshop, IServiceProvider serviceProvider) {