using System.Text;
namespace DodoSSH.Client.Domain.Tests;
///
/// A Spelunk key: an API key and the one Spelunk it may be sent to.
///
///
/// What these pin is that the pair is stored as it will be compared — the address in its one normal form —
/// that an address the key could leak across a network from is refused, and that a merge never pairs one
/// device's key with another device's address.
///
public sealed class SpelunkKeySecretTests
{
private const string Api = "https://api.spelunk.example.com";
[Fact]
public void AKey_RoundTrips()
{
var key = Key();
SpelunkKeySecretCodec.TryDecode(SpelunkKeySecretCodec.Encode(key), out var document).ShouldBeTrue();
document.ShouldNotBeNull();
document.Key.ShouldBe(key);
document.SchemaVersion.ShouldBe(SpelunkKeySecretCodec.CurrentSchemaVersion);
document.IsReadOnly.ShouldBeFalse();
}
[Fact]
public void AKey_EncodesAtTheBaseVersion()
{
Encoding.UTF8.GetString(SpelunkKeySecretCodec.Encode(Key()))
.ShouldBe(
"""{"schemaVersion":1,"label":"Spelunk","apiBase":"https://api.spelunk.example.com","apiKey":"spelunk_one"}""");
}
[Theory]
[InlineData("https://api.spelunk.example.com/")]
[InlineData("HTTPS://API.SPELUNK.EXAMPLE.COM")]
public void AnAddressNotInItsNormalForm_IsRefused(string apiBase)
{
// Stored the one way or not at all: it is compared byte for byte with what a host or a link names.
(Key() with { ApiBase = apiBase }).TryValidate(out var reason).ShouldBeFalse();
reason.ShouldNotBeNull().ShouldContain("normal form");
}
[Fact]
public void PlainHttpAcrossANetwork_IsRefused()
{
(Key() with { ApiBase = "http://api.spelunk.example.com" }).TryValidate(out var reason).ShouldBeFalse();
reason.ShouldNotBeNull().ShouldContain("https");
}
[Fact]
public void PlainHttpToThisMachine_IsAllowed()
{
(Key() with { ApiBase = "http://localhost:5080" }).TryValidate(out _).ShouldBeTrue();
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("spelunk_one\r\nX-Other: header")]
public void AKeyThatIsNotAKey_IsRefused(string apiKey)
{
(Key() with { ApiKey = apiKey }).TryValidate(out _).ShouldBeFalse();
}
[Fact]
public void APayloadWithAnAddressOutOfNormalForm_DoesNotDecode()
{
// The address decides where the key may go, so a payload that disagrees with how every client
// writes it is refused rather than normalised into something nobody chose.
var payload = Encoding.UTF8.GetBytes(
"""{"schemaVersion":1,"label":"Spelunk","apiBase":"https://api.spelunk.example.com/","apiKey":"k"}""");
SpelunkKeySecretCodec.TryDecode(payload, out _).ShouldBeFalse();
}
[Fact]
public void AKeyChangedOnOneSide_AndAnAddressOnTheOther_IsKeptAsAPair()
{
// The case this merge exists for: merging field by field would send the new key to the new address,
// a pairing neither device made.
var ancestor = Key();
var local = ancestor with { ApiKey = "spelunk_two" };
var remote = ancestor with { ApiBase = "https://spelunk.other.example" };
var result = SpelunkKeySecretMerge.Merge(ancestor, local, remote);
(result.Merged.ApiBase, result.Merged.ApiKey).ShouldBeOneOf(
(local.ApiBase, local.ApiKey),
(remote.ApiBase, remote.ApiKey));
var conflict = result.Conflicts.ShouldHaveSingleItem();
conflict.Field.ShouldBe(SpelunkKeySecretMerge.PairField);
}
[Fact]
public void AKeyReplacedOnBothSides_NeverReachesTheConflictLog()
{
var ancestor = Key();
var local = ancestor with { ApiKey = "spelunk_local_secret" };
var remote = ancestor with { ApiKey = "spelunk_remote_secret" };
var result = SpelunkKeySecretMerge.Merge(ancestor, local, remote);
var conflict = result.Conflicts.ShouldHaveSingleItem();
$"{conflict.Kept} {conflict.Discarded}".ShouldNotContain("secret");
}
[Fact]
public void AKeyChangedOnOneSideOnly_IsTakenWithoutAConflict()
{
var ancestor = Key();
var local = ancestor with { ApiKey = "spelunk_two", Label = "renamed" };
var result = SpelunkKeySecretMerge.Merge(ancestor, local, ancestor);
result.Merged.ShouldBe(local);
result.HasConflicts.ShouldBeFalse();
}
private static SpelunkKeySecret Key() => new()
{
Label = "Spelunk",
ApiBase = Api,
ApiKey = "spelunk_one",
};
}