Compare commits

...
6 Commits
10 changed files with 239 additions and 32 deletions

No files matched your search

+3
View File
@@ -34,6 +34,9 @@ insert_final_newline = false
#### .NET Coding Conventions ####
[*.{cs,vb}]
# Analyzer settings
dotnet_diagnostic.CA1822.severity = none
# Organize usings
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = true
+27 -13
View File
@@ -1,22 +1,36 @@
Write-Host "Starting test execution..." -ForegroundColor Magenta
Write-Host ""
$RepoRoot = Resolve-Path (Join-Path -Path $PSScriptRoot -ChildPath "..")
dotnet test
Push-Location -Path $RepoRoot
try {
Write-Host "Starting test execution..." -ForegroundColor Magenta
Write-Host ""
$TestExitCode = $LASTEXITCODE
$TestResultsPath = Join-Path -Path $RepoRoot -ChildPath "TestResults"
if ($TestExitCode -eq 0) {
Write-Host ""
Write-Host "Tests passed. Generating code coverage HTML report..." -ForegroundColor Magenta
Write-Host ""
if (Test-Path $TestResultsPath) {
Remove-Item -Path $TestResultsPath -Recurse -Force
}
reportgenerator -reports:"**/*.cobertura.xml" -targetdir:"CoverageReport"
dotnet test
Write-Host ""
Write-Host "SUCCESS: All tests passed and coverage report generated." -ForegroundColor Green
} else {
Write-Host "FAILURE: Tests failed with exit code $TestExitCode. Skipping coverage report generation." -ForegroundColor Red
$TestExitCode = $LASTEXITCODE
if ($TestExitCode -eq 0) {
Write-Host ""
Write-Host "Tests passed. Generating code coverage HTML report..." -ForegroundColor Magenta
Write-Host ""
reportgenerator -reports:"**/*.cobertura.xml" -targetdir:"CoverageReport"
Write-Host ""
Write-Host "SUCCESS: All tests passed and coverage report generated." -ForegroundColor Green
} else {
Write-Host "FAILURE: Tests failed with exit code $TestExitCode. Skipping coverage report generation." -ForegroundColor Red
}
}
finally {
Pop-Location
}
exit $TestExitCode
@@ -0,0 +1,132 @@
namespace StevanFreeborn.Atproto.IdentityResolver;
/// <summary>
///
/// </summary>
public abstract class AtprotoId
{
/// <summary>
///
/// </summary>
public string Value { get; protected set; } = string.Empty;
/// <summary>
///
/// </summary>
public static AtprotoId From(string atprotoId)
{
if (string.IsNullOrEmpty(atprotoId))
{
throw new ArgumentException("Cannot be null or empty", nameof(atprotoId));
}
if (IsDomain(atprotoId))
{
return Handle.From(atprotoId);
}
return Did.From(atprotoId);
}
private static bool IsDomain(string did)
{
var hostType = Uri.CheckHostName(did);
return hostType == UriHostNameType.Dns && did.Contains('.', StringComparison.OrdinalIgnoreCase);
}
}
/// <summary>
///
/// </summary>
public class Did : AtprotoId
{
private static readonly string[] ValidPrefixes = ["did:web:", "did:plc:"];
/// <summary>
///
/// </summary>
public string Method { get; private set; }
private Did(string did)
{
if (string.IsNullOrEmpty(did))
{
throw new ArgumentException("Cannot be null or empty", nameof(did));
}
if (TryParseWithoutPrefix(did, out var prefix, out var didWithoutPrefix) is false)
{
throw new ArgumentException($"Must be did with one of these methods: {string.Join(',', ValidPrefixes)}", nameof(did));
}
if (string.IsNullOrEmpty(didWithoutPrefix))
{
throw new ArgumentException("Must not be null or empty without did method prefix", nameof(did));
}
// Method should probably be a strongly
// typed object
Method = prefix.Contains("web", StringComparison.OrdinalIgnoreCase) ? "web" : "plc";
Value = did;
}
/// <summary>
///
/// </summary>
public static new Did From(string did)
{
return new(did);
}
private static bool TryParseWithoutPrefix(
string did,
out string prefix,
out string didWithoutPrefix
)
{
prefix = string.Empty;
didWithoutPrefix = string.Empty;
foreach (var validPrefix in ValidPrefixes)
{
if (did.StartsWith(validPrefix, StringComparison.OrdinalIgnoreCase))
{
prefix = did[..validPrefix.Length];
didWithoutPrefix = did[validPrefix.Length..];
return true;
}
}
return false;
}
}
/// <summary>
///
/// </summary>
public class Handle : AtprotoId
{
private Handle(string handle)
{
if (string.IsNullOrEmpty(handle))
{
throw new ArgumentException("Cannot be null or empty", nameof(handle));
}
if (handle.StartsWith('@'))
{
throw new ArgumentException("Cannot start with @", nameof(handle));
}
Value = handle;
}
/// <summary>
///
/// </summary>
public static new Handle From(string handle)
{
return new(handle);
}
}
@@ -1,9 +0,0 @@
namespace StevanFreeborn.Atproto.IdentityResolver;
internal class Greeter
{
public static string SayHello()
{
return "Hi!";
}
}
@@ -0,0 +1,12 @@
namespace StevanFreeborn.Atproto.IdentityResolver;
/// <summary>
///
/// </summary>
public interface IResolver
{
/// <summary>
///
/// </summary>
Task<DidDocument> ResolveOrThrowAsync(AtprotoId atprotoId, CancellationToken ct);
}
@@ -0,0 +1,35 @@
namespace StevanFreeborn.Atproto.IdentityResolver;
/// <inheritdoc />
public class Resolver : IResolver
{
/// <inheritdoc />
public Task<DidDocument> ResolveOrThrowAsync(AtprotoId atprotoId, CancellationToken ct)
{
// you need to retrieve the did document based on what you got
// if you have domain
// you are going to try to retrieve the did
// either through using /.well-known/did.json
// or looking for _atproto.domain txt record
//
// you now for sure have a did
// check method
// if method is web
// retrieve doc from domain/.well-known/did.json
// if method is plc
// retrieve doc from plc.directory/{did}
// you now have the document
// extract alsoKnownAs which is the handle
// make sure you can get back the same did
// from the handle's domain at /.well-known/did.json
// or _atproto.domain txt record
return Task.FromResult(new DidDocument());
}
}
/// <summary>
///
/// </summary>
public class DidDocument();
+1
View File
@@ -1,2 +1,3 @@
[*.cs]
dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CA2007.severity = none
@@ -0,0 +1,19 @@
namespace StevanFreeborn.Atproto.IdentityResolver.Tests;
public class AtprotoIdTests
{
[Theory]
[InlineData("")]
[InlineData("localhost")]
[InlineData("did:web:")]
[InlineData("did:plc:")]
[InlineData("did:mu:dxbmien5xsrkov5yhywgc4zu")]
[InlineData("@stevanfreeborn.com")]
[InlineData(null)]
public void From_WhenCalledWithInvalidDid_ItShouldThrowInvalidArgumentException(string? atprotoId)
{
var act = () => AtprotoId.From(atprotoId!);
act.Should().Throw<ArgumentException>();
}
}
@@ -1,10 +0,0 @@
namespace StevanFreeborn.Atproto.IdentityResolver.Tests;
public class GreeterTests
{
[Fact]
public void SayHello_WhenCalled_ItShouldReturnHi()
{
Greeter.SayHello().Should().Be("Hi!");
}
}
@@ -0,0 +1,10 @@
namespace StevanFreeborn.Atproto.IdentityResolver.Tests;
public class ResolverTests
{
[Fact]
public async Task ResolveAsync_WhenCalledWithInvalidDid_ItShouldThrowInvalidArgumentException()
{
true.Should().BeTrue();
}
}