From 7cf817853cf54485a5100598962340cdeb6122ce Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 7 Apr 2020 22:19:36 -0600 Subject: [PATCH] Implement tests for List validation --- .../ABuildServerSystemTest.cs | 9 ++ .../ModListValidationTests.cs | 126 ++++++++++++++++++ .../Controllers/ListValidation.cs | 2 +- .../Models/Jobs/UpdateModLists.cs | 2 +- Wabbajack.BuildServer/Models/ModListStatus.cs | 5 +- .../Models/Sql/SqlService.cs | 8 +- Wabbajack.Common/Consts.cs | 1 - Wabbajack.Common/Json.cs | 6 + Wabbajack.Lib/FileUploader/AuthorAPI.cs | 5 +- .../ModListRegistry/ModListMetadata.cs | 11 +- Wabbajack.Lib/Validation/ValidateModlist.cs | 2 +- azure-pipelines.yml | 17 --- 12 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 Wabbajack.BuildServer.Test/ModListValidationTests.cs diff --git a/Wabbajack.BuildServer.Test/ABuildServerSystemTest.cs b/Wabbajack.BuildServer.Test/ABuildServerSystemTest.cs index cc202ed0..33e30180 100644 --- a/Wabbajack.BuildServer.Test/ABuildServerSystemTest.cs +++ b/Wabbajack.BuildServer.Test/ABuildServerSystemTest.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting; using Wabbajack.Common; using Wabbajack.Common.Http; using Wabbajack.Common.StatusFeed; +using Wabbajack.Lib.FileUploader; using Xunit; using Xunit.Abstractions; @@ -22,6 +23,8 @@ namespace Wabbajack.BuildServer.Test private bool _disposed = false; public AbsolutePath ServerTempFolder => _severTempFolder.Dir; + public AbsolutePath ServerPublicFolder => "public".RelativeTo(AbsolutePath.EntryPoint); + public BuildServerFixture() { @@ -42,6 +45,9 @@ namespace Wabbajack.BuildServer.Test _token = new CancellationTokenSource(); _task = _host.RunAsync(_token.Token); Consts.WabbajackBuildServerUri = new Uri("http://localhost:8080"); + + "ServerWhitelist.yaml".RelativeTo(ServerPublicFolder).WriteAllText( + "GoogleIDs:\nAllowedPrefixes:\n - http://localhost"); } ~BuildServerFixture() @@ -125,8 +131,11 @@ namespace Wabbajack.BuildServer.Test _authedClient = new Client(); Fixture = fixture.Deref(); _authedClient.Headers.Add(("x-api-key", Fixture.APIKey)); + AuthorAPI.ApiKeyOverride = Fixture.APIKey; _queue = new WorkQueue(); Queue = new WorkQueue(); + Consts.ModlistSummaryURL = MakeURL("lists/status.json"); + Consts.ServerWhitelistURL = MakeURL("ServerWhitelist.yaml"); } public WorkQueue Queue { get; set; } diff --git a/Wabbajack.BuildServer.Test/ModListValidationTests.cs b/Wabbajack.BuildServer.Test/ModListValidationTests.cs new file mode 100644 index 00000000..6c895424 --- /dev/null +++ b/Wabbajack.BuildServer.Test/ModListValidationTests.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using System.Security.Policy; +using System.Text; +using System.Threading.Tasks; +using Wabbajack.BuildServer.Model.Models; +using Wabbajack.BuildServer.Models.JobQueue; +using Wabbajack.BuildServer.Models.Jobs; +using Wabbajack.Common; +using Wabbajack.Lib; +using Wabbajack.Lib.Downloaders; +using Wabbajack.Lib.FileUploader; +using Wabbajack.Lib.ModListRegistry; +using Xunit; +using Xunit.Abstractions; + +namespace Wabbajack.BuildServer.Test +{ + public class ModListValidationTests : ABuildServerSystemTest + { + public ModListValidationTests(ITestOutputHelper output, SingletonAdaptor fixture) : base(output, fixture) + { + } + + [Fact] + public async Task CanLoadMetadataFromTestServer() + { + var modlist = await MakeModList(); + Consts.ModlistMetadataURL = modlist.ToString(); + var data = await ModlistMetadata.LoadFromGithub(); + Assert.Single(data); + Assert.Equal("test_list", data.First().Links.MachineURL); + } + + [Fact] + public async Task CanValidateModLists() + { + var modlists = await MakeModList(); + Consts.ModlistMetadataURL = modlists.ToString(); + Utils.Log("Updating modlists"); + var result = await AuthorAPI.UpdateServerModLists(); + Assert.NotNull(result); + + var sql = Fixture.GetService(); + var settings = Fixture.GetService(); + var job = await sql.GetJob(); + + Assert.NotNull(job); + Assert.IsType(job.Payload); + + + var jobResult = await job.Payload.Execute(sql, settings); + Assert.Equal(JobResultType.Success, jobResult.ResultType); + + Utils.Log("Checking validated results"); + var data = await ModlistMetadata.LoadFromGithub(); + + Assert.Single(data); + Assert.Equal(0, data.First().ValidationSummary.Failed); + Assert.Equal(1, data.First().ValidationSummary.Passed); + + } + + + private async Task MakeModList() + { + var archive_data = Encoding.UTF8.GetBytes("Cheese for Everyone!"); + var test_archive_path = "test_archive.txt".RelativeTo(Fixture.ServerPublicFolder); + await test_archive_path.WriteAllBytesAsync(archive_data); + + + + var modListData = new ModList + { + Archives = new List + { + new Archive + { + Hash = await test_archive_path.FileHashAsync(), + Name = "test_archive", + Size = test_archive_path.Size, + State = new HTTPDownloader.State {Url = MakeURL("test_archive.txt")} + } + } + }; + + var modListPath = "test_modlist.wabbajack".RelativeTo(Fixture.ServerPublicFolder); + + await using (var fs = modListPath.Create()) + { + using var za = new ZipArchive(fs, ZipArchiveMode.Create); + var entry = za.CreateEntry("modlist.json"); + await using var es = entry.Open(); + modListData.ToJson(es); + } + + var modListMetaData = new List + { + new ModlistMetadata + { + Official = false, + Author = "Test Suite", + Description = "A test", + DownloadMetadata = new DownloadMetadata + { + Hash = await modListPath.FileHashAsync(), + Size = modListPath.Size + }, + Links = new ModlistMetadata.LinksObject + { + MachineURL = "test_list", + Download = MakeURL("test_modlist.wabbajack") + } + } + }; + + var metadataPath = "test_mod_list_metadata.json".RelativeTo(Fixture.ServerPublicFolder); + + modListMetaData.ToJson(metadataPath); + + return new Uri(MakeURL("test_mod_list_metadata.json")); + } + } +} diff --git a/Wabbajack.BuildServer/Controllers/ListValidation.cs b/Wabbajack.BuildServer/Controllers/ListValidation.cs index a3387e13..d9fa4ba1 100644 --- a/Wabbajack.BuildServer/Controllers/ListValidation.cs +++ b/Wabbajack.BuildServer/Controllers/ListValidation.cs @@ -22,7 +22,7 @@ namespace Wabbajack.BuildServer.Controllers [HttpGet] [Route("status.json")] - public async Task> HandleGetLists() + public async Task> HandleGetLists() { return await SQL.GetModListSummaries(); } diff --git a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs index f62e0ba5..1d749211 100644 --- a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs +++ b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs @@ -92,7 +92,7 @@ namespace Wabbajack.BuildServer.Models.Jobs var dto = new ModListStatus { Id = list.Links.MachineURL, - Summary = new ModlistSummary + Summary = new ModListSummary { Name = status.Name, MachineURL = list.Links?.MachineURL ?? status.Name, diff --git a/Wabbajack.BuildServer/Models/ModListStatus.cs b/Wabbajack.BuildServer/Models/ModListStatus.cs index 1b1d90ff..228a236a 100644 --- a/Wabbajack.BuildServer/Models/ModListStatus.cs +++ b/Wabbajack.BuildServer/Models/ModListStatus.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Wabbajack.Common.Serialization.Json; using Wabbajack.Lib; using Wabbajack.Lib.ModListRegistry; @@ -10,7 +11,7 @@ namespace Wabbajack.BuildServer.Models public class ModListStatus { public string Id { get; set; } - public ModlistSummary Summary { get; set; } + public ModListSummary Summary { get; set; } public ModlistMetadata Metadata { get; set; } public DetailedStatus DetailedStatus { get; set; } @@ -24,6 +25,7 @@ namespace Wabbajack.BuildServer.Models } } + [JsonName("DetailedStatus")] public class DetailedStatus { public string Name { get; set; } @@ -34,6 +36,7 @@ namespace Wabbajack.BuildServer.Models public string MachineName { get; set; } } + [JsonName("DetailedStatusItem")] public class DetailedStatusItem { public bool IsFailing { get; set; } diff --git a/Wabbajack.BuildServer/Models/Sql/SqlService.cs b/Wabbajack.BuildServer/Models/Sql/SqlService.cs index 41b39d44..75c2be6e 100644 --- a/Wabbajack.BuildServer/Models/Sql/SqlService.cs +++ b/Wabbajack.BuildServer/Models/Sql/SqlService.cs @@ -447,11 +447,11 @@ namespace Wabbajack.BuildServer.Model.Models } #region ModLists - public async Task> GetModListSummaries() + public async Task> GetModListSummaries() { await using var conn = await Open(); var results = await conn.QueryAsync("SELECT Summary from dbo.ModLists"); - return results.Select(s => s.FromJsonString()).ToList(); + return results.Select(s => s.FromJsonString()).ToList(); } public async Task GetDetailedModlistStatus(string machineUrl) @@ -599,8 +599,8 @@ namespace Wabbajack.BuildServer.Model.Models await conn.ExecuteAsync(@"MERGE dbo.ModLists AS Target USING (SELECT @MachineUrl MachineUrl, @Metadata Metadata, @Summary Summary, @DetailedStatus DetailedStatus) AS Source ON Target.MachineUrl = Source.MachineUrl - WHEN MATCHED THEN UPDATE SET Target.Summary = Source.Summary, Target.Metadata = Source.Metadata, Target.DetailedStatus = Source.DetailedStats - WHEN NOT MATCHED THEN INSERT (MachineUrl, Summary, Metadata, DetailedStatus) VALUES (@MachineUrl, @Summary, @Metadata, @DetailedStatus)", + WHEN MATCHED THEN UPDATE SET Target.Summary = Source.Summary, Target.Metadata = Source.Metadata, Target.DetailedStatus = Source.DetailedStatus + WHEN NOT MATCHED THEN INSERT (MachineUrl, Summary, Metadata, DetailedStatus) VALUES (@MachineUrl, @Summary, @Metadata, @DetailedStatus);", new { MachineUrl = dto.Metadata.Links.MachineURL, diff --git a/Wabbajack.Common/Consts.cs b/Wabbajack.Common/Consts.cs index 2346dca0..860be9a3 100644 --- a/Wabbajack.Common/Consts.cs +++ b/Wabbajack.Common/Consts.cs @@ -81,7 +81,6 @@ namespace Wabbajack.Common }.Select(s => (RelativePath)s).ToHashSet(); - public static string ModPermissionsURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/NexusModPermissions.yml"; public static string ServerWhitelistURL = "https://raw.githubusercontent.com/wabbajack-tools/opt-out-lists/master/ServerWhitelist.yml"; public static string ModlistMetadataURL = "https://raw.githubusercontent.com/wabbajack-tools/mod-lists/master/modlists.json"; public static string ModlistSummaryURL = "http://build.wabbajack.org/lists/status.json"; diff --git a/Wabbajack.Common/Json.cs b/Wabbajack.Common/Json.cs index 56e4749f..4d2584c7 100644 --- a/Wabbajack.Common/Json.cs +++ b/Wabbajack.Common/Json.cs @@ -47,6 +47,12 @@ namespace Wabbajack.Common var ser = JsonSerializer.Create(JsonSettings); ser.Serialize(writer, obj); } + + public static void ToJson(this T obj, AbsolutePath path) + { + using var fs = path.Create(); + obj.ToJson(fs); + } public static string ToJson(this T obj) { diff --git a/Wabbajack.Lib/FileUploader/AuthorAPI.cs b/Wabbajack.Lib/FileUploader/AuthorAPI.cs index dced6a8e..149b4d35 100644 --- a/Wabbajack.Lib/FileUploader/AuthorAPI.cs +++ b/Wabbajack.Lib/FileUploader/AuthorAPI.cs @@ -21,8 +21,11 @@ namespace Wabbajack.Lib.FileUploader { public static IObservable HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable(Consts.AuthorAPIKeyFile); + public static string ApiKeyOverride = null; + public static async Task GetAPIKey(string apiKey = null) { + if (ApiKeyOverride != null) return ApiKeyOverride; return apiKey ?? (await Consts.LocalAppDataPath.Combine(Consts.AuthorAPIKeyFile).ReadAllTextAsync()).Trim(); } @@ -130,7 +133,7 @@ namespace Wabbajack.Lib.FileUploader public static async Task RunJob(string jobtype) { var client = await GetAuthorizedClient(); - return await client.GetStringAsync($"https://{Consts.WabbajackCacheHostname}/jobs/enqueue_job/{jobtype}"); + return await client.GetStringAsync($"{Consts.WabbajackBuildServerUri}jobs/enqueue_job/{jobtype}"); } diff --git a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs index a69a074e..e68df7e2 100644 --- a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs +++ b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs @@ -9,6 +9,7 @@ using Game = Wabbajack.Common.Game; namespace Wabbajack.Lib.ModListRegistry { + [JsonName("ModListMetadata")] public class ModlistMetadata { [JsonProperty("title")] @@ -35,8 +36,9 @@ namespace Wabbajack.Lib.ModListRegistry public DownloadMetadata DownloadMetadata { get; set; } [JsonIgnore] - public ModlistSummary ValidationSummary { get; set; } = new ModlistSummary(); + public ModListSummary ValidationSummary { get; set; } = new ModListSummary(); + [JsonName("Links")] public class LinksObject { [JsonProperty("image")] @@ -66,10 +68,10 @@ namespace Wabbajack.Lib.ModListRegistry var metadata = (await metadataResult).FromJsonString>(); try { - var summaries = (await summaryResult).FromJsonString>().ToDictionary(d => d.Name); + var summaries = (await summaryResult).FromJsonString>().ToDictionary(d => d.MachineURL); foreach (var data in metadata) - if (summaries.TryGetValue(data.Title, out var summary)) + if (summaries.TryGetValue(data.Links.MachineURL, out var summary)) data.ValidationSummary = summary; } catch (Exception) @@ -104,7 +106,8 @@ namespace Wabbajack.Lib.ModListRegistry } - public class ModlistSummary + [JsonName("ModListSummary")] + public class ModListSummary { [JsonProperty("name")] public string Name { get; set; } diff --git a/Wabbajack.Lib/Validation/ValidateModlist.cs b/Wabbajack.Lib/Validation/ValidateModlist.cs index bd01808d..9dafa939 100644 --- a/Wabbajack.Lib/Validation/ValidateModlist.cs +++ b/Wabbajack.Lib/Validation/ValidateModlist.cs @@ -31,7 +31,7 @@ namespace Wabbajack.Lib.Validation using (var result = await response.Content.ReadAsStreamAsync()) { ServerWhitelist = result.FromYaml(); - Utils.Log($"Loaded permissions for {ServerWhitelist.AllowedPrefixes.Count} servers and {ServerWhitelist.GoogleIDs.Count} Google Drive files"); + Utils.Log($"Loaded permissions for {ServerWhitelist.AllowedPrefixes?.Count ?? 0} servers and {ServerWhitelist.GoogleIDs?.Count ?? 0} Google Drive files"); } } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf5e7dd0..4ef5fb16 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -44,23 +44,6 @@ steps: command: 'test' projects: '**/*.Test.csproj' arguments: '/p:Platform=x64 /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(Build.SourcesDirectory)/CodeCoverage' - - # Generate the report using ReportGenerator (https://github.com/danielpalme/ReportGenerator) - # First install the tool on the machine, then run it -- script: | - dotnet tool install -g dotnet-reportgenerator-globaltool - reportgenerator -reports:$(Build.SourcesDirectory)/tests/**/coverage.cobertura.xml -targetdir:$(Build.SourcesDirectory)/CodeCoverage -reporttypes:HtmlInline_AzurePipelines;Cobertura - displayName: Create Code coverage report - - # Publish the code coverage result (summary and web site) - # The summary allows to view the coverage percentage in the summary tab - # The web site allows to view which lines are covered directly in Azure Pipeline -- task: PublishCodeCoverageResults@1 - displayName: 'Publish code coverage' - inputs: - codeCoverageTool: Cobertura - summaryFileLocation: '$(Build.SourcesDirectory)/CodeCoverage/Cobertura.xml' - reportDirectory: '$(Build.SourcesDirectory)/CodeCoverage' - task: DotNetCoreCLI@2 inputs: