Add JSON validation test
authorLiza Carvelli <liza@carvel.li>
Sun, 18 May 2025 19:19:04 +0000 (21:19 +0200)
committerLiza Carvelli <liza@carvel.li>
Sun, 18 May 2025 19:19:04 +0000 (21:19 +0200)
.woodpecker/build.yaml
QuestPaths.JsonValidator/QuestPaths.JsonValidator.csproj [new file with mode: 0644]
QuestPaths.JsonValidator/QuestWrapper.cs [new file with mode: 0644]
QuestPaths.JsonValidator/TestQuestLoader.cs [new file with mode: 0644]
QuestPaths.JsonValidator/ValidJsonFilesTest.cs [new file with mode: 0644]
QuestPaths/QuestPaths.csproj
QuestPaths/packages.lock.json
Questionable.sln

index d10c34e..85910fb 100644 (file)
@@ -13,6 +13,11 @@ steps:
     commands:
       - dotnet restore /p:Configuration=Release --packages $CI_WORKSPACE/.nuget
 
+  - name: dotnet test
+    image: mcr.microsoft.com/dotnet/sdk:9.0-noble
+    commands:
+      - dotnet test QuestPaths.JsonValidator/QuestPaths.JsonValidator.csproj --source $CI_WORKSPACE/.nuget --no-restore
+
   - name: dotnet build
     image: mcr.microsoft.com/dotnet/sdk:9.0-noble
     commands:
diff --git a/QuestPaths.JsonValidator/QuestPaths.JsonValidator.csproj b/QuestPaths.JsonValidator/QuestPaths.JsonValidator.csproj
new file mode 100644 (file)
index 0000000..a2ff13b
--- /dev/null
@@ -0,0 +1,47 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net9.0</TargetFramework>
+        <Platform>x64</Platform>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+
+        <IsPackable>false</IsPackable>
+        <IsTestProject>true</IsTestProject>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="JsonSchema.Net" Version="7.3.4" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
+        <PackageReference Include="System.Text.Json" Version="9.0.5" />
+        <PackageReference Include="xunit" Version="2.5.3"/>
+        <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Questionable.Model\Questionable.Model.csproj"/>
+        <ProjectReference Include="..\QuestPaths\QuestPaths.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <EmbeddedResource Include="..\QuestPaths\2.x - A Realm Reborn\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+        <EmbeddedResource Include="..\QuestPaths\3.x - Heavensward\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+        <EmbeddedResource Include="..\QuestPaths\4.x - Stormblood\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+        <EmbeddedResource Include="..\QuestPaths\5.x - Shadowbringers\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+        <EmbeddedResource Include="..\QuestPaths\6.x - Endwalker\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+        <EmbeddedResource Include="..\QuestPaths\7.x - Dawntrail\**\*.json">
+            <LogicalName>$([System.String]::new('%(RelativeDir)').Substring(3).Replace('\','/'))%(FileName)%(Extension)</LogicalName>
+        </EmbeddedResource>
+    </ItemGroup>
+
+</Project>
diff --git a/QuestPaths.JsonValidator/QuestWrapper.cs b/QuestPaths.JsonValidator/QuestWrapper.cs
new file mode 100644 (file)
index 0000000..3184455
--- /dev/null
@@ -0,0 +1,16 @@
+namespace QuestPaths.JsonValidator;
+
+public class QuestWrapper
+{
+    public QuestWrapper(string manifestName)
+    {
+        ManifestName = manifestName;
+        ShortName = ManifestName.Split('/').Last().Replace(".json", "", StringComparison.InvariantCulture);
+    }
+
+    public string ManifestName { get; }
+    public string ShortName { get; }
+    public Stream AsStream() => typeof(QuestWrapper).Assembly.GetManifestResourceStream(ManifestName)!;
+
+    public override string ToString() => ShortName;
+}
diff --git a/QuestPaths.JsonValidator/TestQuestLoader.cs b/QuestPaths.JsonValidator/TestQuestLoader.cs
new file mode 100644 (file)
index 0000000..4d674ef
--- /dev/null
@@ -0,0 +1,20 @@
+using System.Collections;
+
+namespace QuestPaths.JsonValidator;
+
+public sealed class TestQuestLoader : IEnumerable<object[]>
+{
+    private readonly List<object[]> _data;
+
+    public TestQuestLoader()
+    {
+        var assembly = typeof(TestQuestLoader).Assembly;
+        _data = assembly.GetManifestResourceNames()
+            .Where(x => x.StartsWith("QuestPaths/") && x.EndsWith(".json"))
+            .Select(x => new object[]{new QuestWrapper(x)})
+            .ToList();
+    }
+    public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
+
+    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
diff --git a/QuestPaths.JsonValidator/ValidJsonFilesTest.cs b/QuestPaths.JsonValidator/ValidJsonFilesTest.cs
new file mode 100644 (file)
index 0000000..38ddfe8
--- /dev/null
@@ -0,0 +1,51 @@
+using System.Globalization;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using Json.Schema;
+using Questionable.Model;
+using Questionable.QuestPaths;
+using Xunit;
+
+namespace QuestPaths.JsonValidator;
+
+public sealed class ValidJsonFilesTest
+{
+    private static readonly JsonSchema QuestSchema;
+
+    static ValidJsonFilesTest()
+    {
+        SchemaRegistry.Global.Register(
+            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-aethernetshard.json"),
+            JsonSchema.FromStream(AssemblyModelLoader.CommonAethernetShard).AsTask().Result);
+        SchemaRegistry.Global.Register(
+            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-aetheryte.json"),
+            JsonSchema.FromStream(AssemblyModelLoader.CommonAetheryte).AsTask().Result);
+        SchemaRegistry.Global.Register(
+            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-classjob.json"),
+            JsonSchema.FromStream(AssemblyModelLoader.CommonClassJob).AsTask().Result);
+        SchemaRegistry.Global.Register(
+            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-completionflags.json"),
+            JsonSchema.FromStream(AssemblyModelLoader.CommonCompletionFlags).AsTask().Result);
+        SchemaRegistry.Global.Register(
+            new Uri("https://git.carvel.li/liza/Questionable/raw/branch/master/Questionable.Model/common-vector3.json"),
+            JsonSchema.FromStream(AssemblyModelLoader.CommonVector3).AsTask().Result);
+        
+        QuestSchema = JsonSchema.FromStream(AssemblyQuestLoader.QuestSchema).AsTask().Result;
+    }
+
+    [Theory]
+    [ClassData(typeof(TestQuestLoader))]
+    public void QuestShouldValidateAsJson(QuestWrapper quest)
+    {
+        JsonNode questNode = JsonNode.Parse(quest.AsStream()) ?? throw new InvalidDataException("no quest stream");
+
+        EvaluationResults evaluationResult = QuestSchema.Evaluate(questNode, new EvaluationOptions
+        {
+            Culture = CultureInfo.InvariantCulture,
+            OutputFormat = OutputFormat.List
+        });
+
+        if (!evaluationResult.IsValid)
+            Assert.Fail($"Quest '{quest.ManifestName}' validation failed");
+    }
+}
index 845355b..60526ab 100644 (file)
@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
-        <TargetFramework>net9.0-windows</TargetFramework>
+        <TargetFramework>net9.0</TargetFramework>
         <LangVersion>12</LangVersion>
         <Nullable>enable</Nullable>
         <RootNamespace>Questionable.QuestPaths</RootNamespace>
index 0d3376b..8b094b8 100644 (file)
@@ -1,7 +1,7 @@
 {
   "version": 1,
   "dependencies": {
-    "net9.0-windows7.0": {
+    "net9.0": {
       "System.Text.Json": {
         "type": "Transitive",
         "resolved": "9.0.3",
index cc77dd8..d96384b 100644 (file)
@@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pictomancy", "vendor\pictom
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Questionable.IpcTest", "Questionable.IpcTest\Questionable.IpcTest.csproj", "{8572A8B2-2F31-4D17-B207-6A7A2E0579EF}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPaths.JsonValidator", "QuestPaths.JsonValidator\QuestPaths.JsonValidator.csproj", "{2521F2BA-9647-4851-92E1-F56280D1663C}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|x64 = Debug|x64
@@ -79,10 +81,14 @@ Global
                {D1AE2F8C-BDE7-457F-A369-973101044A25}.Debug|x64.Build.0 = Debug|x64
                {D1AE2F8C-BDE7-457F-A369-973101044A25}.Release|x64.ActiveCfg = Release|x64
                {D1AE2F8C-BDE7-457F-A369-973101044A25}.Release|x64.Build.0 = Release|x64
-               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Debug|x64.Build.0 = Debug|Any CPU
-               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Release|x64.ActiveCfg = Release|Any CPU
-               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Release|x64.Build.0 = Release|Any CPU
+               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Debug|x64.ActiveCfg = Debug|x64
+               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Debug|x64.Build.0 = Debug|x64
+               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Release|x64.ActiveCfg = Release|x64
+               {8572A8B2-2F31-4D17-B207-6A7A2E0579EF}.Release|x64.Build.0 = Release|x64
+               {2521F2BA-9647-4851-92E1-F56280D1663C}.Debug|x64.ActiveCfg = Debug|x64
+               {2521F2BA-9647-4851-92E1-F56280D1663C}.Debug|x64.Build.0 = Debug|x64
+               {2521F2BA-9647-4851-92E1-F56280D1663C}.Release|x64.ActiveCfg = Release|x64
+               {2521F2BA-9647-4851-92E1-F56280D1663C}.Release|x64.Build.0 = Release|x64
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE