220 lines
6.6 KiB
C#
220 lines
6.6 KiB
C#
// Copyright 2012-2026 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
using Shouldly;
|
|
using System.Text.Json;
|
|
using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider;
|
|
using ZB.MOM.NatsNet.Server.Auth.Ocsp;
|
|
|
|
namespace ZB.MOM.NatsNet.Server.Tests.Auth;
|
|
|
|
public sealed class OcspResponseCacheTests
|
|
{
|
|
[Fact]
|
|
public void LocalDirCache_PutReplaceDelete_AdjustsStats()
|
|
{
|
|
var cache = new LocalDirCache(Path.Combine(Path.GetTempPath(), $"ocsp-{Guid.NewGuid():N}"));
|
|
cache.Start();
|
|
|
|
cache.Put("k1", CreateResponse(OcspStatusAssertion.Good, [1, 2, 3]), "subj");
|
|
cache.Put("k1", CreateResponse(OcspStatusAssertion.Revoked, [4, 5, 6]), "subj");
|
|
cache.Put("k2", CreateResponse(OcspStatusAssertion.Unknown, [7, 8]), "subj");
|
|
|
|
var statsAfterPut = cache.Stats();
|
|
statsAfterPut.ShouldNotBeNull();
|
|
statsAfterPut.Responses.ShouldBe(2);
|
|
statsAfterPut.Goods.ShouldBe(0);
|
|
statsAfterPut.Revokes.ShouldBe(1);
|
|
statsAfterPut.Unknowns.ShouldBe(1);
|
|
|
|
cache.Delete("k1", wasMiss: false);
|
|
|
|
var statsAfterDelete = cache.Stats();
|
|
statsAfterDelete.ShouldNotBeNull();
|
|
statsAfterDelete.Responses.ShouldBe(1);
|
|
statsAfterDelete.Revokes.ShouldBe(0);
|
|
statsAfterDelete.Unknowns.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_DeletePreserveRevoked_WithMiss_AdjustsHitToMiss()
|
|
{
|
|
var config = OcspHandler.NewOCSPResponseCacheConfig();
|
|
config.PreserveRevoked = true;
|
|
|
|
var cache = new LocalDirCache(config);
|
|
cache.Start();
|
|
cache.Put("k1", CreateResponse(OcspStatusAssertion.Revoked, [9, 9, 9]), "subj");
|
|
cache.Get("k1").ShouldNotBeNull();
|
|
|
|
cache.Delete("k1", wasMiss: true);
|
|
|
|
var stats = cache.Stats();
|
|
stats.ShouldNotBeNull();
|
|
stats.Responses.ShouldBe(1);
|
|
stats.Revokes.ShouldBe(1);
|
|
stats.Hits.ShouldBe(0);
|
|
stats.Misses.ShouldBe(1);
|
|
cache.Get("k1").ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_OnlineTypeConfigStats_FollowLifecycle()
|
|
{
|
|
var config = OcspHandler.NewOCSPResponseCacheConfig();
|
|
var cache = new LocalDirCache(config);
|
|
|
|
cache.Online().ShouldBeFalse();
|
|
cache.Type().ShouldBe("local");
|
|
cache.Config().LocalStore.ShouldBe(config.LocalStore);
|
|
cache.Stats().ShouldBeNull();
|
|
|
|
cache.Start();
|
|
cache.Online().ShouldBeTrue();
|
|
cache.Stats().ShouldNotBeNull();
|
|
|
|
cache.Stop();
|
|
cache.Online().ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_CompressDecompress_RoundTripsPayload()
|
|
{
|
|
var cache = new LocalDirCache(Path.Combine(Path.GetTempPath(), $"ocsp-{Guid.NewGuid():N}"));
|
|
var payload = "ocsp-cache-roundtrip-data"u8.ToArray();
|
|
|
|
var (compressed, compressError) = cache.Compress(payload);
|
|
compressError.ShouldBeNull();
|
|
compressed.ShouldNotBeNull();
|
|
|
|
var (decompressed, decompressError) = cache.Decompress(compressed!);
|
|
decompressError.ShouldBeNull();
|
|
decompressed.ShouldNotBeNull();
|
|
decompressed.ShouldBe(payload);
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_LoadCache_MissingFile_LeavesCacheEmpty()
|
|
{
|
|
var dir = CreateTempDir();
|
|
try
|
|
{
|
|
var cache = new LocalDirCache(dir);
|
|
var server = NewServer(new ServerOptions { NoSystemAccount = true });
|
|
|
|
cache.LoadCache(server);
|
|
cache.Start();
|
|
|
|
var stats = cache.Stats();
|
|
stats.ShouldNotBeNull();
|
|
stats.Responses.ShouldBe(0);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(dir, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_LoadCache_ValidFile_PopulatesCache()
|
|
{
|
|
var dir = CreateTempDir();
|
|
try
|
|
{
|
|
var cache = new LocalDirCache(dir);
|
|
var cacheFile = Path.Combine(dir, "cache.json");
|
|
var seed = new Dictionary<string, OcspResponseCacheItem>
|
|
{
|
|
["k1"] = new()
|
|
{
|
|
Subject = "subj",
|
|
CachedAt = DateTime.UtcNow,
|
|
RespStatus = OcspStatusAssertion.Good,
|
|
RespExpires = DateTime.UtcNow.AddMinutes(5),
|
|
Resp = cache.Compress([1, 2, 3]).compressed!,
|
|
},
|
|
};
|
|
|
|
File.WriteAllBytes(cacheFile, JsonSerializer.SerializeToUtf8Bytes(seed));
|
|
|
|
var server = NewServer(new ServerOptions { NoSystemAccount = true });
|
|
cache.LoadCache(server);
|
|
cache.Start();
|
|
|
|
cache.Get("k1").ShouldBe([1, 2, 3]);
|
|
cache.Stats()!.Responses.ShouldBe(1);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(dir, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void LocalDirCache_SaveCache_DirtyWritesCacheFile()
|
|
{
|
|
var dir = CreateTempDir();
|
|
try
|
|
{
|
|
var cache = new LocalDirCache(dir);
|
|
var server = NewServer(new ServerOptions { NoSystemAccount = true });
|
|
cache.Start();
|
|
cache.Put("k1", CreateResponse(OcspStatusAssertion.Good, [4, 5, 6]), "subj");
|
|
|
|
cache.SaveCache(server);
|
|
|
|
File.Exists(Path.Combine(dir, "cache.json")).ShouldBeTrue();
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(dir, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void NoOpCache_LifecycleAndStats_ShouldNoOpSafely()
|
|
{
|
|
var noOp = new NoOpCache();
|
|
noOp.Online().ShouldBeFalse();
|
|
noOp.Type().ShouldBe("none");
|
|
noOp.Config().ShouldNotBeNull();
|
|
noOp.Stats().ShouldBeNull();
|
|
|
|
noOp.Start();
|
|
noOp.Online().ShouldBeTrue();
|
|
noOp.Stats().ShouldNotBeNull();
|
|
|
|
noOp.Put("k", [5]);
|
|
noOp.Get("k").ShouldBeNull();
|
|
noOp.Remove("k");
|
|
noOp.Delete("k");
|
|
|
|
noOp.Stop();
|
|
noOp.Online().ShouldBeFalse();
|
|
}
|
|
|
|
private static OcspResponse CreateResponse(OcspStatusAssertion status, byte[] raw) =>
|
|
new()
|
|
{
|
|
Status = status,
|
|
ThisUpdate = DateTime.UtcNow.AddMinutes(-1),
|
|
NextUpdate = DateTime.UtcNow.AddMinutes(10),
|
|
Raw = raw,
|
|
};
|
|
|
|
private static NatsServer NewServer(ServerOptions options)
|
|
{
|
|
var (server, err) = NatsServer.NewServer(options);
|
|
err.ShouldBeNull();
|
|
server.ShouldNotBeNull();
|
|
return server!;
|
|
}
|
|
|
|
private static string CreateTempDir()
|
|
{
|
|
var dir = Path.Combine(Path.GetTempPath(), "ocsp-cache-" + Path.GetRandomFileName());
|
|
Directory.CreateDirectory(dir);
|
|
return dir;
|
|
}
|
|
}
|