Skip to content

Commit

Permalink
Tests for Newtonsoft.Json's handling of octal literals in version files
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Oct 9, 2024
1 parent cbb75f3 commit c71a148
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 88 deletions.
88 changes: 3 additions & 85 deletions Netkan/Sources/Avc/JsonAvcToGameVersion.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using CKAN.Versioning;

using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using CKAN.Versioning;

namespace CKAN.NetKAN.Sources.Avc
{
/// <summary>
Expand Down Expand Up @@ -97,87 +98,4 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
}
}

/// <summary>
/// Converts AVC style versions into CKAN ones.
/// </summary>
public class JsonAvcToVersion : JsonConverter
{
private static readonly ILog Log = LogManager.GetLogger(typeof (JsonAvcToGameVersion));

public override bool CanConvert(Type objectType)
{
// We trust you only to call this on things we can convert, okay?
return true;
}

public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
var major = "0";
var minor = "0";
var patch = "0";
string? build = null;

var token = JToken.Load(reader);
Log.DebugFormat("Read Token: {0}, {1}", new object[] {token.Type, token.ToString()});
switch (token.Type)
{
case JTokenType.String:
var tokenArray = token.ToString().Split('.');
if (//tokenArray is [var majorToken, ..]
tokenArray.Length > 0
&& tokenArray[0] is var majorToken)
{
major = majorToken;
if (//tokenArray is [_, var minorToken, ..]
tokenArray.Length > 1
&& tokenArray[1] is var minorToken)
{
minor = minorToken;
if (//tokenArray is [_, _, var patchToken, ..]
tokenArray.Length > 2
&& tokenArray[2] is var patchToken)
{
patch = patchToken;
if (//tokenArray is [_, _, _, var buildToken, ..]
tokenArray.Length > 3
&& tokenArray[3] is var buildToken)
{
build = buildToken;
}
}
}
}
break;
case JTokenType.Object:
major = token.Value<string>("MAJOR") ?? major;
minor = token.Value<string>("MINOR") ?? minor;
patch = token.Value<string>("PATCH") ?? patch;
build = token.Value<string>("BUILD") ?? build;
break;
default:
throw new InvalidCastException("Trying to convert non-JSON object to Version object");
}

var components = new List<string>() { major, minor, patch };

if (build != null && !string.IsNullOrWhiteSpace(build))
{
components.Add(build);
}

var version = string.Join(".", components);
Log.DebugFormat(" extracted version: {0}", version);
var result = new ModuleVersion(version);
Log.DebugFormat(" generated result: {0}", result);
return result;
}

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
97 changes: 97 additions & 0 deletions Netkan/Sources/Avc/JsonAvcToVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using log4net;

using CKAN.Versioning;

namespace CKAN.NetKAN.Sources.Avc
{
/// <summary>
/// Converts AVC style versions into CKAN ones.
/// </summary>
public class JsonAvcToVersion : JsonConverter
{
private static readonly ILog Log = LogManager.GetLogger(typeof (JsonAvcToGameVersion));

public override bool CanConvert(Type objectType)
{
// We trust you only to call this on things we can convert, okay?
return true;
}

public override object? ReadJson(JsonReader reader,
Type? objectType,
object? existingValue,
JsonSerializer? serializer)
{
var major = "0";
var minor = "0";
var patch = "0";
string? build = null;

var token = JToken.Load(reader);
Log.DebugFormat("Read Token: {0}, {1}", new object[] {token.Type, token.ToString()});
switch (token.Type)
{
case JTokenType.String:
var tokenArray = token.ToString().Split('.');
if (//tokenArray is [var majorToken, ..]
tokenArray.Length > 0
&& tokenArray[0] is var majorToken)
{
major = majorToken;
if (//tokenArray is [_, var minorToken, ..]
tokenArray.Length > 1
&& tokenArray[1] is var minorToken)
{
minor = minorToken;
if (//tokenArray is [_, _, var patchToken, ..]
tokenArray.Length > 2
&& tokenArray[2] is var patchToken)
{
patch = patchToken;
if (//tokenArray is [_, _, _, var buildToken, ..]
tokenArray.Length > 3
&& tokenArray[3] is var buildToken)
{
build = buildToken;
}
}
}
}
break;
case JTokenType.Object:
major = token.Value<string>("MAJOR") ?? major;
minor = token.Value<string>("MINOR") ?? minor;
patch = token.Value<string>("PATCH") ?? patch;
build = token.Value<string>("BUILD") ?? build;
break;
default:
throw new InvalidCastException("Trying to convert non-JSON object to Version object");
}

var components = new List<string>() { major, minor, patch };

if (build != null && !string.IsNullOrWhiteSpace(build))
{
components.Add(build);
}

var version = string.Join(".", components);
Log.DebugFormat(" extracted version: {0}", version);
var result = new ModuleVersion(version);
Log.DebugFormat(" generated result: {0}", result);
return result;
}

public override bool CanWrite => false;

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
93 changes: 90 additions & 3 deletions Tests/NetKAN/AVC.cs → Tests/NetKAN/Sources/Avc/AVC.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System;
using System.IO;
using CKAN.NetKAN.Sources.Avc;
using CKAN.Versioning;

using Newtonsoft.Json;
using NUnit.Framework;

using CKAN.NetKAN.Sources.Avc;
using CKAN.Versioning;
using Tests.Data;

namespace Tests.NetKAN
namespace Tests.NetKAN.Sources.Avc
{
[TestFixture]
public class AVC
Expand Down Expand Up @@ -94,5 +97,89 @@ public void MissingPatch_VersionOnlyHasMajorMinor()
Assert.That(result, Is.EqualTo(GameVersion.Parse("1.5")));
}

[TestCase("02024", "7", "13"),
TestCase("2024", "07", "13"),
TestCase("2024", "7", "013")]
public void ModVersionConverterReadJson_ValidOctalLiteral_DoesNotThrow(
string major, string minor, string patch)
{
// Arrange
var json = $@"{{
""MAJOR"": {major},
""MINOR"": {minor},
""PATCH"": {patch}
}}";
var reader = new JsonTextReader(new StringReader(json));
var converter = new JsonAvcToVersion();
var correct = new ModuleVersion(string.Join(".",
FromMaybeOctal(major),
FromMaybeOctal(minor),
FromMaybeOctal(patch)));

// Act / Assert
Assert.DoesNotThrow(() =>
{
var result = converter.ReadJson(reader, null, null, null);
Assert.AreEqual(correct, result);
});
}

private int FromMaybeOctal(string val)
=> val.StartsWith("0")
? Convert.ToInt32(val, 8)
: int.Parse(val);

[TestCase("02028", "10", "9"),
TestCase("2024", "080", "9"),
TestCase("2024", "10", "08"),
TestCase("02029", "10", "9"),
TestCase("2024", "090", "9"),
TestCase("2024", "10", "09")]
public void ModVersionConverterReadJson_InvalidOctalLiteral_Throws(
string major, string minor, string patch)
{
// Arrange
var json = $@"{{
""MAJOR"": {major},
""MINOR"": {minor},
""PATCH"": {patch}
}}";
var reader = new JsonTextReader(new StringReader(json));
var converter = new JsonAvcToVersion();

// Act / Assert
Assert.Throws<JsonReaderException>(() =>
{
var result = converter.ReadJson(reader, null, null, null);
});
}

[TestCase("02028", "10", "9"),
TestCase("2024", "080", "9"),
TestCase("2024", "10", "08"),
TestCase("02029", "10", "9"),
TestCase("2024", "090", "9"),
TestCase("2024", "10", "09")]
public void ModVersionConverterReadJson_QuotedInvalidOctalNumber_DoesNotThrow(
string major, string minor, string patch)
{
// Arrange
var json = $@"{{
""MAJOR"": ""{major}"",
""MINOR"": ""{minor}"",
""PATCH"": ""{patch}""
}}";
var reader = new JsonTextReader(new StringReader(json));
var converter = new JsonAvcToVersion();
var correct = new ModuleVersion(string.Join(".", major, minor, patch));

// Act / Assert
Assert.DoesNotThrow(() =>
{
var result = converter.ReadJson(reader, null, null, null);
Assert.AreEqual(correct, result);
});
}

}
}

0 comments on commit c71a148

Please sign in to comment.