Skip to content

Commit

Permalink
adoptet official test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
TenCoKaciStromy committed Jun 30, 2023
1 parent 23c47ad commit 9521926
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/dotnet-typeid-tests/TypeId_Test.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Headers;
using SimpleBase;
using TcKs.TypeId;
using Xunit;

Expand Down
243 changes: 243 additions & 0 deletions src/dotnet-typeid-tests/ValidationAgainstSpec_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
using System;
using TcKs.TypeId;
using Xunit;
using Xunit.Abstractions;

namespace dotnet_typeid_tests;

/// <summary>
/// Reimplemented test cases from official repository.
/// https://github.com/jetpack-io/typeid-go/blob/main/typeid_test.go
/// </summary>
public class ValidationAgainstSpec_Tests {
private readonly ITestOutputHelper outer;

public ValidationAgainstSpec_Tests(ITestOutputHelper outer) {
this.outer = outer;
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func ExampleNew() {
/// tid := Must(New("prefix"))
/// fmt.Printf("New typeid: %s\n", tid)
/// }
/// ]]>
[Fact]
public void ExampleNew() {
// tid := Must(New("prefix"))
var tid = TypeId.NewId("prefix");

// fmt.Printf("New typeid: %s\n", tid)
outer.WriteLine($"New typeid: {tid}");
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func ExampleNew_withoutPrefix() {
/// tid := Must(New(""))
/// fmt.Printf("New typeid without prefix: %s\n", tid)
/// }
/// ]]>
[Fact]
public void ExampleNew_withoutPrefix() {
// tid := Must(New(""))
var tid = TypeId.NewId("");

// fmt.Printf("New typeid without prefix: %s\n", tid)
outer.WriteLine($"New typeid without prefix: {tid}");
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func ExampleFromString() {
/// tid := Must(FromString("prefix_00041061050r3gg28a1c60t3gg"))
/// fmt.Printf("Prefix: %s\nSuffix: %s\n", tid.Type(), tid.suffix)
/// // Output:
/// // Prefix: prefix
/// // Suffix: 00041061050r3gg28a1c60t3gg
/// }
/// ]]>
[Fact]
public void ExampleFromString() {
// tid := Must(FromString("prefix_00041061050r3gg28a1c60t3gg"))
var tid = TypeId.Parse("prefix_00041061050r3gg28a1c60t3gg");

// fmt.Printf("Prefix: %s\nSuffix: %s\n", tid.Type(), tid.suffix)
outer.WriteLine($"Prefix: {tid.Type}\nSuffix: {tid.Suffix}");

Assert.Equal("prefix", tid.Type);
Assert.Equal("00041061050r3gg28a1c60t3gg", tid.Suffix);
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func TestInvalidPrefix(t *testing.T) {
/// testdata := []struct {
/// name string
/// input string
/// }{
/// {"caps", "PREFIX"}, // Would be valid in lowercase
/// {"numeric", "12323"},
/// {"symbols", "pre.fix"},
/// {"spaces", " "},
/// {"long", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"},
/// }
///
/// for _, td := range testdata {
/// t.Run(td.name, func(t *testing.T) {
/// _, err := New(td.input)
/// if err == nil {
/// t.Errorf("Expected error for invalid prefix: %s", td.input)
/// }
/// })
/// }
/// }
/// ]]>
[Theory]
[InlineData("PREFIX")]
[InlineData("12323")]
[InlineData("pre.fix")]
[InlineData(" ")]
[InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")]
public void TestInvalidPrefix(string input) {
var text = $"{input}_00041061050r3gg28a1c60t3gg";
var success = TypeId.TryParse(text, out var result);
Assert.False(success);
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func TestInvalidSuffix(t *testing.T) {
/// testdata := []struct {
/// name string
/// input string
/// }{
/// {"spaces", " "},
/// {"short", "01234"},
/// {"long", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"},
/// {"caps", "00041061050R3GG28A1C60T3GF"}, // Would be valid in lowercase
/// {"hyphens", "00041061050-3gg28a1-60t3gf"},
/// {"crockford_ambiguous", "ooo41o61o5or3gg28a1c6ot3gi"}, // Would be valid if we followed Crocksford's substitution rules
/// {"symbols", "00041061050.3gg28a1_60t3gf"},
/// {"wrong_alphabet", "ooooooiiiiiiuuuuuuulllllll"},
/// }
///
/// for _, td := range testdata {
/// t.Run(td.name, func(t *testing.T) {
/// _, err := From("prefix", td.input)
/// if err == nil {
/// t.Errorf("Expected error for invalid suffix: %s", td.input)
/// }
/// })
/// }
/// }
/// ]]>
[Theory]
[InlineData(" ")]
[InlineData("01234")]
[InlineData("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789")]
// [InlineData("00041061050R3GG28A1C60T3GF")] // we allow this
[InlineData("00041061050-3gg28a1-60t3gf")]
// [InlineData("ooo41o61o5or3gg28a1c6ot3gi")] // we follow Crocksford's substitution rules
[InlineData("00041061050.3gg28a1_60t3gf")]
[InlineData("ooooooiiiiiiuuuuuuulllllll")]
public void TestInvalidSuffix(string input) {
var text = $"prefix_{input}";
var success = TypeId.TryParse(text, out var result);
Assert.False(success);
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func TestEncodeDecode(t *testing.T) {
/// // Generate a bunch of random typeids, encode and decode from a string
/// // and make sure the result is the same as the original.
/// for i := 0; i < 1000; i++ {
/// tid := Must(New("prefix"))
/// decoded, err := FromString(tid.String())
/// if err != nil {
/// t.Error(err)
/// }
/// if tid != decoded {
/// t.Errorf("Expected %s, got %s", tid, decoded)
/// }
/// }
///
/// // Repeat with the empty prefix:
/// for i := 0; i < 1000; i++ {
/// tid := Must(New(""))
/// decoded, err := FromString(tid.String())
/// if err != nil {
/// t.Error(err)
/// }
/// if tid != decoded {
/// t.Errorf("Expected %s, got %s", tid, decoded)
/// }
/// }
/// }
/// ]]>
[Theory]
[InlineData("")]
[InlineData("prefix")]
public void TestEncodeDecode(string prefix) {
for (var i = 0; i < 1000; i++) {
var tid = TypeId.NewId(prefix);
var text = tid.ToString();
var decoded = TypeId.Parse(text);

Assert.Equal(tid, decoded);
}
}

/// <summary>
/// Reimplementation of test function from official test cases.
/// </summary>
/// <![CDATA[
/// func TestSpecialValues(t *testing.T) {
/// testdata := []struct {
/// name string
/// tid string
/// uuid string
/// }{
/// {"nil", "00000000000000000000000000", "00000000-0000-0000-0000-000000000000"},
/// {"one", "00000000000000000000000001", "00000000-0000-0000-0000-000000000001"},
/// {"ten", "0000000000000000000000000a", "00000000-0000-0000-0000-00000000000a"},
/// {"sixteen", "0000000000000000000000000g", "00000000-0000-0000-0000-000000000010"},
/// {"thirty-two", "00000000000000000000000010", "00000000-0000-0000-0000-000000000020"},
/// }
/// for _, td := range testdata {
/// t.Run(td.name, func(t *testing.T) {
/// // Values should be equal if we start by parsing the typeid
/// tid := Must(FromString(td.tid))
/// if td.uuid != tid.UUID() {
/// t.Errorf("Expected %s, got %s", td.uuid, tid.UUID())
/// }
///
/// // Values should be equal if we start by parsing the uuid
/// tid = Must(FromUUID("", td.uuid))
/// if td.tid != tid.String() {
/// t.Errorf("Expected %s, got %s", td.tid, tid.String())
/// }
/// })
/// }
/// }
/// ]]>
[Fact(Skip = "This test is not required since we use System.Guid.")]
public void TestSpecialValues() {
Assert.True(true);
}
}

15 changes: 15 additions & 0 deletions src/dotnet-typeid/TcKs.TypeId.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion src/dotnet-typeid/TypeId.Parse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
namespace TcKs.TypeId;

partial struct TypeId {
/// <summary>
/// The maximum length of prefix.
/// </summary>
public const int MaxPrefixLength = 63;

/// <summary>
/// The separator for type part and id part.
/// </summary>
Expand All @@ -21,6 +26,11 @@ partial struct TypeId {
/// </summary>
public const int MinTotalLength = IdPartLength + 1;

/// <summary>
/// Allowed characters for type prefix.
/// </summary>
public const string AllowedPrefixCharacters = "abcdefghijklmnopqrstuvwxyz";

/// <summary>
/// Parses string to <see cref="TypeId"/>.
/// </summary>
Expand Down Expand Up @@ -62,14 +72,20 @@ public static TypeId Parse(string input)
return Fail(out result);

var ndxSeparator = input.IndexOf(PartsSeparator);
if (ndxSeparator < 0)
if (ndxSeparator is < 0 or > MaxPrefixLength)
return Fail(out result);

var ndxId = ndxSeparator + 1;
var idLength = inputLength - ndxId;
if (idLength != IdPartLength)
return Fail(out result);

var prefix = input.Substring(0, ndxSeparator);
for (var i = prefix.Length - 1; i >= 0; i--) {
if (!AllowedPrefixCharacters.Contains(prefix[i]))
return Fail(out result);
}

var idPart = input.AsMemory().Slice(ndxId).Span;
var bytes = new byte[16];
if (!Base32.Crockford.TryDecode(idPart, bytes.AsSpan(), out var numBytesWritten) || numBytesWritten != 16)
Expand Down
14 changes: 8 additions & 6 deletions src/dotnet-typeid/TypeId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public string Type
public Guid Id
=> id;

/// <summary>
/// The id encoded as a 26-character string in base32 (using Crockford's alphabet in lowercase).
/// </summary>
public string Suffix
=> Base32.Crockford.Encode(id.ToByteArray().AsSpan()).ToLower();

/// <summary>
/// Returns true if <see cref="Id"/> is empty (Guid.Empty).
/// </summary>
Expand All @@ -61,12 +67,8 @@ public bool IsEmpty
/// <returns>
/// String representation of this typed id.
/// </returns>
public override string ToString() {
var bytes = id.ToByteArray();
var text = Base32.Crockford.Encode(bytes.AsSpan());

return $"{Type}_{text}";
}
public override string ToString()
=> $"{Type}_{Suffix}";

/// <summary>
/// The type id can be casted to <see cref="Guid"/>.
Expand Down
2 changes: 2 additions & 0 deletions src/dotnet-typeid/dotnet-typeid.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<PackageTags>guid uuid id typeid type-id</PackageTags>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<DocumentationFile>TcKs.TypeId.xml</DocumentationFile>
<Version>1.0.1</Version>
<PackageReleaseNotes>Parsing is more strict now.</PackageReleaseNotes>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit 9521926

Please sign in to comment.