// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.Text; using System.Text.Encodings.Web; using System.Text.Json; namespace Geekeey.SemVer.Tests; internal sealed class SemanticVersionTests { private static readonly JsonSerializerOptions RelaxedOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, }; [Test] [Arguments(0, 0, 0, 0)] [Arguments(1, 1, 1, 0)] [Arguments(1, 2, 0, 0)] [Arguments(45, 2, 4, 0)] public async Task I_can_create_semver_from_version(int major, int minor, int build, int revision) { var version = new Version(major, minor, build, revision); var v = new SemanticVersion(version); await Assert.That(v.Major).IsEqualTo((ulong)major); await Assert.That(v.Minor).IsEqualTo((ulong)minor); await Assert.That(v.Patch).IsEqualTo((ulong)build); await Assert.That(v.Prerelease).IsNull(); await Assert.That(v.Metadata).IsNull(); } [Test] [Arguments(1UL)] [Arguments(0UL)] public async Task I_can_create_semver_with_major(ulong major) { var v = new SemanticVersion(major); await Assert.That(v.Major).IsEqualTo(major); await Assert.That(v.Minor).IsEqualTo(0UL); await Assert.That(v.Patch).IsEqualTo(0UL); await Assert.That(v.Prerelease).IsNull(); await Assert.That(v.Metadata).IsNull(); } [Test] [Arguments(1UL, 2UL)] public async Task I_can_create_semver_with_major_and_minor(ulong major, ulong minor) { var v = new SemanticVersion(major, minor); await Assert.That(v.Major).IsEqualTo(major); await Assert.That(v.Minor).IsEqualTo(minor); await Assert.That(v.Patch).IsEqualTo(0UL); await Assert.That(v.Prerelease).IsNull(); await Assert.That(v.Metadata).IsNull(); } [Test] [Arguments(1UL, 2UL, 3UL)] public async Task I_can_create_semver_with_major_minor_and_patch(ulong major, ulong minor, ulong patch) { var v = new SemanticVersion(major, minor, patch); await Assert.That(v.Major).IsEqualTo(major); await Assert.That(v.Minor).IsEqualTo(minor); await Assert.That(v.Patch).IsEqualTo(patch); await Assert.That(v.Prerelease).IsNull(); await Assert.That(v.Metadata).IsNull(); } [Test] [Arguments(1UL, 2UL, 3UL, "alpha", "build.1")] [Arguments(1UL, 2UL, 3UL, null, null)] public async Task I_can_create_semver_with_all_components(ulong major, ulong minor, ulong patch, string? prerelease, string? metadata) { var v = new SemanticVersion(major, minor, patch, prerelease, metadata); await Assert.That(v.Major).IsEqualTo(major); await Assert.That(v.Minor).IsEqualTo(minor); await Assert.That(v.Patch).IsEqualTo(patch); await Assert.That(v.Prerelease).IsEqualTo(prerelease); await Assert.That(v.Metadata).IsEqualTo(metadata); } [Test] public async Task I_can_compare_identical_versions() { var v1 = new SemanticVersion(1, 2, 3); var v2 = new SemanticVersion(1, 2, 3); await Assert.That(v1.CompareTo(v2)).IsEqualTo(0); } [Test] public async Task I_can_compare_with_objects() { var v1 = new SemanticVersion(1, 2, 3); object v2 = new SemanticVersion(1, 2, 3); await Assert.That(v1.CompareTo(v2)).IsEqualTo(0); await Assert.That(v1.CompareTo(null)).IsEqualTo(1); await Assert.That(v1.CompareTo("not a version")).IsEqualTo(1); } [Test] public async Task I_can_confirm_operators_work() { var v1 = new SemanticVersion(1, 2, 3); var v2 = new SemanticVersion(2, 0, 0); await Assert.That(v1 < v2).IsTrue(); await Assert.That(v1 <= v2).IsTrue(); await Assert.That(v1 > v2).IsFalse(); await Assert.That(v1 >= v2).IsFalse(); } [Test] public async Task I_can_parse_from_string() { var result = SemanticVersion.Parse("1.2.3"); await Assert.That(result.Major).IsEqualTo(1UL); await Assert.That(result.Minor).IsEqualTo(2UL); await Assert.That(result.Patch).IsEqualTo(3UL); } [Test] public async Task I_can_try_parse_from_string() { var success = SemanticVersion.TryParse("1.2.3-alpha+build.1", out var result); await Assert.That(success).IsTrue(); await Assert.That(result.Major).IsEqualTo(1UL); await Assert.That(result.Minor).IsEqualTo(2UL); await Assert.That(result.Patch).IsEqualTo(3UL); await Assert.That(result.Prerelease).IsEqualTo("alpha"); await Assert.That(result.Metadata).IsEqualTo("build.1"); } [Test] public async Task I_can_parse_from_utf8() { var utf8 = "2.0.0-rc.1"u8; var result = SemanticVersion.Parse(utf8); await Assert.That(result.Major).IsEqualTo(2UL); await Assert.That(result.Minor).IsEqualTo(0UL); await Assert.That(result.Patch).IsEqualTo(0UL); await Assert.That(result.Prerelease).IsEqualTo("rc.1"); } [Test] [Arguments(1, 2, 3, null, null, "1.2.3")] [Arguments(1, 2, 3, "alpha", null, "1.2.3-alpha")] [Arguments(1, 2, 3, "alpha", "build.1", "1.2.3-alpha+build.1")] public async Task I_can_get_string_representation(ulong major, ulong minor, ulong patch, string? pre, string? meta, string expected) { var v = new SemanticVersion(major, minor, patch, pre, meta); // Note: format "f" is needed for full version including metadata based on code var format = string.IsNullOrEmpty(meta) ? (string.IsNullOrEmpty(pre) ? null : "s") : "f"; await Assert.That(v.ToString(format, null)).IsEqualTo(expected); } [Test] public async Task I_can_format_to_chars() { var v = new SemanticVersion(1, 2, 3, "beta", "456"); var dest = new char[32]; var success = v.TryFormat(dest, out var charsWritten, "f", null); await Assert.That(success).IsTrue(); await Assert.That(new string(dest[..charsWritten])).IsEqualTo("1.2.3-beta+456"); } [Test] public async Task I_can_format_to_utf8() { var v = new SemanticVersion(1, 2, 3, "beta", "456"); var dest = new byte[32]; var success = v.TryFormat(dest, out var bytesWritten, "f", null); await Assert.That(success).IsTrue(); await Assert.That(Encoding.UTF8.GetString(dest[..bytesWritten])).IsEqualTo("1.2.3-beta+456"); } [Test] [Arguments(1, 2, 3, null, null, "\"1.2.3\"")] [Arguments(1, 2, 3, "alpha", null, "\"1.2.3-alpha\"")] [Arguments(1, 2, 3, "alpha", "build.1", "\"1.2.3-alpha+build.1\"")] public async Task I_can_serialize_to_json(ulong major, ulong minor, ulong patch, string? pre, string? meta, string expectedJson) { var v = new SemanticVersion(major, minor, patch, pre, meta); var json = JsonSerializer.Serialize(v, RelaxedOptions); await Assert.That(json).IsEqualTo(expectedJson); } [Test] public async Task I_can_deserialize_from_json() { var json = "\"1.2.3-beta+789\""; var v = JsonSerializer.Deserialize(json); await Assert.That(v.Major).IsEqualTo(1UL); await Assert.That(v.Minor).IsEqualTo(2UL); await Assert.That(v.Patch).IsEqualTo(3UL); await Assert.That(v.Prerelease).IsEqualTo("beta"); await Assert.That(v.Metadata).IsEqualTo("789"); } [Test] public async Task I_can_handle_invalid_json_token() { var json = "123"; // Number instead of string await Assert.That(() => JsonSerializer.Deserialize(json)) .Throws() .WithMessage("Expected string"); } [Test] public async Task I_can_serialize_as_part_of_object() { var obj = new { Version = new SemanticVersion(1, 0, 0, "rc.1", "metadata") }; var json = JsonSerializer.Serialize(obj, RelaxedOptions); await Assert.That(json).IsEqualTo("{\"Version\":\"1.0.0-rc.1+metadata\"}"); } [Test] public async Task I_can_deserialize_null() { var json = "null"; var v = JsonSerializer.Deserialize(json); await Assert.That(v).IsEqualTo(default(SemanticVersion)); } [Test] public async Task I_can_deserialize_empty_string() { var json = "\"\""; await Assert.That(() => JsonSerializer.Deserialize(json)) .Throws(); } }