refactor: extract NATS.Server.JetStream.Tests project

Move 225 JetStream-related test files from NATS.Server.Tests into a
dedicated NATS.Server.JetStream.Tests project. This includes root-level
JetStream*.cs files, storage test files (FileStore, MemStore,
StreamStoreContract), and the full JetStream/ subfolder tree (Api,
Cluster, Consumers, MirrorSource, Snapshots, Storage, Streams).

Updated all namespaces, added InternalsVisibleTo, registered in the
solution file, and added the JETSTREAM_INTEGRATION_MATRIX define.
This commit is contained in:
Joseph Doherty
2026-03-12 15:58:10 -04:00
parent 36b9dfa654
commit 78b4bc2486
228 changed files with 253 additions and 227 deletions

View File

@@ -0,0 +1,283 @@
// Reference: golang/nats-server/server/filestore_test.go
// Tests ported from: TestFileStoreEncrypted,
// TestFileStoreRestoreEncryptedWithNoKeyFuncFails,
// TestFileStoreDoubleCompactWithWriteInBetweenEncryptedBug,
// TestFileStoreEncryptedKeepIndexNeedBekResetBug,
// TestFileStoreShortIndexWriteBug (encryption variant)
using System.Text;
using NATS.Server.JetStream.Storage;
namespace NATS.Server.JetStream.Tests.JetStream.Storage;
public sealed class FileStoreEncryptionTests : IDisposable
{
private readonly string _dir;
public FileStoreEncryptionTests()
{
_dir = Path.Combine(Path.GetTempPath(), $"nats-js-fs-enc-{Guid.NewGuid():N}");
Directory.CreateDirectory(_dir);
}
public void Dispose()
{
if (Directory.Exists(_dir))
Directory.Delete(_dir, recursive: true);
}
private static byte[] TestKey => "nats-encryption-key-for-test!!"u8.ToArray();
private FileStore CreateStore(string subdirectory, bool encrypt = true, byte[]? key = null)
{
var dir = Path.Combine(_dir, subdirectory);
return new FileStore(new FileStoreOptions
{
Directory = dir,
EnableEncryption = encrypt,
EncryptionKey = key ?? TestKey,
});
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_store_and_load()
{
await using var store = CreateStore("enc-basic");
const string subject = "foo";
var payload = "aes ftw"u8.ToArray();
for (var i = 0; i < 50; i++)
await store.AppendAsync(subject, payload, default);
var state = await store.GetStateAsync(default);
state.Messages.ShouldBe((ulong)50);
var msg = await store.LoadAsync(10, default);
msg.ShouldNotBeNull();
msg!.Subject.ShouldBe(subject);
msg.Payload.ToArray().ShouldBe(payload);
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4228
[Fact]
public async Task Encrypted_store_and_recover()
{
var subDir = "enc-recover";
await using (var store = CreateStore(subDir))
{
for (var i = 0; i < 50; i++)
await store.AppendAsync("foo", "aes ftw"u8.ToArray(), default);
}
// Reopen with the same key.
await using (var store = CreateStore(subDir))
{
var msg = await store.LoadAsync(10, default);
msg.ShouldNotBeNull();
msg!.Payload.ToArray().ShouldBe("aes ftw"u8.ToArray());
var state = await store.GetStateAsync(default);
state.Messages.ShouldBe((ulong)50);
}
}
// Go: TestFileStoreRestoreEncryptedWithNoKeyFuncFails server/filestore_test.go:5134
[Fact]
public async Task Encrypted_data_without_key_throws_on_load()
{
var subDir = "enc-no-key";
var dir = Path.Combine(_dir, subDir);
// Store with encryption.
await using (var store = CreateStore(subDir))
{
await store.AppendAsync("foo", "secret data"u8.ToArray(), default);
}
// Reopen with a wrong key. The FileStore constructor calls LoadExisting()
// which calls RestorePayload(), and that throws InvalidDataException when
// the envelope key-hash does not match the configured key.
var createWithWrongKey = () => new FileStore(new FileStoreOptions
{
Directory = dir,
EnableEncryption = true,
EncryptionKey = "wrong-key-wrong-key-wrong-key!!"u8.ToArray(),
EnablePayloadIntegrityChecks = true,
});
Should.Throw<InvalidDataException>(createWithWrongKey);
await Task.CompletedTask;
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_store_remove_and_reload()
{
await using var store = CreateStore("enc-remove");
for (var i = 0; i < 10; i++)
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
await store.RemoveAsync(5, default);
var state = await store.GetStateAsync(default);
state.Messages.ShouldBe((ulong)9);
(await store.LoadAsync(5, default)).ShouldBeNull();
(await store.LoadAsync(6, default)).ShouldNotBeNull();
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_purge_and_continue()
{
await using var store = CreateStore("enc-purge");
for (var i = 0; i < 10; i++)
await store.AppendAsync("foo", "data"u8.ToArray(), default);
await store.PurgeAsync(default);
(await store.GetStateAsync(default)).Messages.ShouldBe((ulong)0);
var seq = await store.AppendAsync("foo", "after purge"u8.ToArray(), default);
seq.ShouldBeGreaterThan((ulong)0);
var msg = await store.LoadAsync(seq, default);
msg.ShouldNotBeNull();
msg!.Payload.ToArray().ShouldBe("after purge"u8.ToArray());
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_snapshot_and_restore()
{
await using var store = CreateStore("enc-snap-src");
for (var i = 0; i < 20; i++)
await store.AppendAsync("foo", Encoding.UTF8.GetBytes($"msg-{i}"), default);
var snap = await store.CreateSnapshotAsync(default);
snap.Length.ShouldBeGreaterThan(0);
await using var restored = CreateStore("enc-snap-dst");
await restored.RestoreSnapshotAsync(snap, default);
var srcState = await store.GetStateAsync(default);
var dstState = await restored.GetStateAsync(default);
dstState.Messages.ShouldBe(srcState.Messages);
for (ulong i = 1; i <= srcState.Messages; i++)
{
var original = await store.LoadAsync(i, default);
var copy = await restored.LoadAsync(i, default);
copy.ShouldNotBeNull();
copy!.Payload.ToArray().ShouldBe(original!.Payload.ToArray());
}
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_large_payload()
{
await using var store = CreateStore("enc-large");
var payload = new byte[8192];
Random.Shared.NextBytes(payload);
await store.AppendAsync("foo", payload, default);
var msg = await store.LoadAsync(1, default);
msg.ShouldNotBeNull();
msg!.Payload.ToArray().ShouldBe(payload);
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_binary_payload_round_trips()
{
await using var store = CreateStore("enc-binary");
// All byte values.
var payload = new byte[256];
for (var i = 0; i < 256; i++)
payload[i] = (byte)i;
await store.AppendAsync("foo", payload, default);
var msg = await store.LoadAsync(1, default);
msg.ShouldNotBeNull();
msg!.Payload.ToArray().ShouldBe(payload);
}
// Go: TestFileStoreEncrypted server/filestore_test.go:4204
[Fact]
public async Task Encrypted_empty_payload()
{
await using var store = CreateStore("enc-empty");
await store.AppendAsync("foo", ReadOnlyMemory<byte>.Empty, default);
var msg = await store.LoadAsync(1, default);
msg.ShouldNotBeNull();
msg!.Payload.Length.ShouldBe(0);
}
// Go: TestFileStoreDoubleCompactWithWriteInBetweenEncryptedBug server/filestore_test.go:3924
[Fact(Skip = "Compact not yet implemented in .NET FileStore")]
public async Task Encrypted_double_compact_with_write_in_between()
{
await Task.CompletedTask;
}
// Go: TestFileStoreEncryptedKeepIndexNeedBekResetBug server/filestore_test.go:3956
[Fact(Skip = "Block encryption key reset not yet implemented in .NET FileStore")]
public async Task Encrypted_keep_index_bek_reset()
{
await Task.CompletedTask;
}
// Verify encryption with no-op key (empty key) does not crash.
[Fact]
public async Task Encrypted_with_empty_key_is_noop()
{
var dir = Path.Combine(_dir, "enc-noop");
await using var store = new FileStore(new FileStoreOptions
{
Directory = dir,
EnableEncryption = true,
EncryptionKey = [],
});
await store.AppendAsync("foo", "data"u8.ToArray(), default);
var msg = await store.LoadAsync(1, default);
msg.ShouldNotBeNull();
msg!.Payload.ToArray().ShouldBe("data"u8.ToArray());
}
// Verify data at rest is not plaintext when encrypted.
[Fact]
public async Task Encrypted_data_not_plaintext_on_disk()
{
var subDir = "enc-disk-check";
var dir = Path.Combine(_dir, subDir);
await using (var store = CreateStore(subDir))
{
await store.AppendAsync("foo", "THIS IS SENSITIVE DATA"u8.ToArray(), default);
}
// Read the raw data file and verify the plaintext payload does not appear.
var dataFile = Path.Combine(dir, "messages.jsonl");
if (File.Exists(dataFile))
{
var raw = File.ReadAllText(dataFile);
// The payload is base64-encoded after encryption, so the original
// plaintext string should not appear verbatim.
raw.ShouldNotContain("THIS IS SENSITIVE DATA");
}
}
}