Files
natsnet/docs/plans/2026-02-26-phase7-plan.md
Joseph Doherty 7750b46f9f docs: Phase 7 implementation plan — 11 tasks, 10 sessions
Covers schema migration, small-module verification, 224 stub fills,
401 new unit tests, 2126 deferred server-integration tests, and
NatsServerBehaviorTests integration baseline.
2026-02-26 18:49:24 -05:00

1325 lines
43 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 7: Porting Verification — Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development to implement this plan task-by-task.
**Goal:** Verify all ported .NET code by writing and running targeted unit tests, then mark the ~2,126 server-integration tests as `deferred` so the database reflects ground truth.
**Architecture:** Ten sequential sessions: schema migration → small-module verification → stub-filling sessions (opts, jwt, auth, signal/log) → new test writing sessions (stores, filestore, jetstream) → final deferred-marking and integration tests. Each session ends with a passing test run, DB update, and commit.
**Tech Stack:** .NET 10, xUnit 3, Shouldly, NSubstitute, SQLite (porting.db), PortTracker CLI
---
## Reference: DB State Entering Phase 7
| Category | Count |
|----------|-------|
| Small module tests (complete) | 114 |
| Server module tests (complete) | 205 |
| Server module tests (stub) | 224 — unit tests, bodies to be written |
| Server module tests (not_started → unit) | 401 — new tests to write |
| Server module tests (not_started → deferred) | 2,126 — server-integration, to be marked |
| thw benchmarks (not_started → n/a) | 6 — performance only, not applicable |
## Reference: Key File Paths
| Purpose | Path |
|---------|------|
| Go reference tests | `golang/nats-server/server/<file>_test.go` |
| .NET unit test project | `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/` |
| .NET integration test project | `dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/` |
| PortTracker CLI | `tools/NatsNet.PortTracker/` |
| SQLite database | `porting.db` |
## Reference: Useful Commands
```bash
# Run all unit tests
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
# Run tests by class
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~<ClassName>"
# PortTracker: mark test complete
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status complete --db porting.db
# PortTracker: mark module verified
dotnet run --project tools/NatsNet.PortTracker -- module update <id> --status verified --db porting.db
# PortTracker: mark all features in module verified
dotnet run --project tools/NatsNet.PortTracker -- feature update 0 --status verified --all-in-module <module_id> --db porting.db
# PortTracker: report
dotnet run --project tools/NatsNet.PortTracker -- report summary --db porting.db
# SQLite bulk update (for batch operations)
sqlite3 porting.db "UPDATE unit_tests SET status='deferred' WHERE ..."
```
---
## Task 1: Schema Migration — Add `deferred` Status
**Files:**
- Modify: `porting-schema.sql` (update CHECK constraint)
- Modify: `porting.db` (recreate unit_tests table via migration SQL)
### Step 1: Verify current constraint
```bash
sqlite3 porting.db ".schema unit_tests"
```
Expected: shows `CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a'))`.
### Step 2: Run migration SQL
```bash
sqlite3 porting.db << 'EOF'
BEGIN;
CREATE TABLE unit_tests_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
module_id INTEGER NOT NULL REFERENCES modules(id) ON DELETE CASCADE,
feature_id INTEGER REFERENCES features(id) ON DELETE SET NULL,
name TEXT NOT NULL,
description TEXT,
go_file TEXT,
go_class TEXT,
go_method TEXT,
go_line_number INTEGER,
go_line_count INTEGER,
status TEXT NOT NULL DEFAULT 'not_started'
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred')),
dotnet_project TEXT,
dotnet_class TEXT,
dotnet_method TEXT,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO unit_tests_new SELECT * FROM unit_tests;
DROP TABLE unit_tests;
ALTER TABLE unit_tests_new RENAME TO unit_tests;
-- Recreate indexes
CREATE INDEX IF NOT EXISTS idx_unit_tests_module ON unit_tests(module_id);
CREATE INDEX IF NOT EXISTS idx_unit_tests_feature ON unit_tests(feature_id);
CREATE INDEX IF NOT EXISTS idx_unit_tests_status ON unit_tests(status);
-- Recreate trigger
CREATE TRIGGER IF NOT EXISTS trg_unit_tests_updated AFTER UPDATE ON unit_tests
BEGIN
UPDATE unit_tests SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
COMMIT;
EOF
```
### Step 3: Verify migration
```bash
sqlite3 porting.db ".schema unit_tests"
# Confirm 'deferred' is in the CHECK constraint
sqlite3 porting.db "SELECT COUNT(*) FROM unit_tests;"
# Confirm row count matches original (3257)
# Verify existing data is intact
sqlite3 porting.db "SELECT status, COUNT(*) FROM unit_tests GROUP BY status;"
```
Expected status counts:
- complete: 319
- n_a: 181
- not_started: 2533
- stub: 224
### Step 4: Update porting-schema.sql
Edit `porting-schema.sql` to add `'deferred'` to the unit_tests CHECK constraint:
```sql
-- Find this line:
status TEXT NOT NULL DEFAULT 'not_started'
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a')),
-- Change to:
status TEXT NOT NULL DEFAULT 'not_started'
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred')),
```
### Step 5: Commit
```bash
git add porting-schema.sql porting.db
git commit -m "feat(p7-01): add 'deferred' status to unit_tests schema"
```
---
## Task 2: P7-01 — Small Module Verification
**Modules to verify:** ats (id=1), avl (id=2), certidp (id=3), certstore (id=4), elastic (id=5), gsl (id=6), pse (id=7), stree (id=9), sysmem (id=10), thw (id=11), tpm (id=12)
**Test counts:** 114 complete tests across all small modules. 6 thw benchmarks → mark `n_a`.
### Step 1: Run all small module tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~ZB.MOM.NatsNet.Server.Tests.Internal|FullyQualifiedName~ZB.MOM.NatsNet.Server.Tests.Auth.CertificateIdentityProvider|FullyQualifiedName~ZB.MOM.NatsNet.Server.Tests.Auth.TpmKeyProvider|FullyQualifiedName~TimeHashWheelTests|FullyQualifiedName~SubjectTreeTests|FullyQualifiedName~GenericSubjectListTests|FullyQualifiedName~ProcessStatsProviderTests|FullyQualifiedName~AccessTimeServiceTests"
```
Or run all unit tests and verify none fail:
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
```
Expected: all existing tests pass (currently ~635 tests).
### Step 2: Mark thw benchmarks as n/a
The 6 thw benchmarks (IDs 32503255) are Go benchmark functions, not correctness tests. Mark `n_a`:
```bash
for id in 3250 3251 3252 3253 3254 3255; do
dotnet run --project tools/NatsNet.PortTracker -- test update $id --status n_a --db porting.db
done
```
### Step 3: Mark all small module tests as verified
All complete tests in small modules (114 tests) pass — mark them verified. Use direct SQL for efficiency:
```bash
sqlite3 porting.db "
UPDATE unit_tests
SET status = 'verified'
WHERE status = 'complete' AND module_id IN (1,2,3,4,5,6,7,9,10,11,12);"
```
Verify:
```bash
sqlite3 porting.db "SELECT module_id, status, COUNT(*) FROM unit_tests WHERE module_id IN (1,2,3,4,5,6,7,9,10,11,12) GROUP BY module_id, status;"
```
### Step 4: Mark all features in small modules as verified
```bash
for mod_id in 1 2 3 4 5 6 7 9 10 11 12; do
dotnet run --project tools/NatsNet.PortTracker -- feature update 0 --status verified --all-in-module $mod_id --db porting.db
done
```
### Step 5: Mark all small modules as verified
```bash
for mod_id in 1 2 3 4 5 6 7 9 10 11 12; do
dotnet run --project tools/NatsNet.PortTracker -- module update $mod_id --status verified --db porting.db
done
```
### Step 6: Verify and commit
```bash
dotnet run --project tools/NatsNet.PortTracker -- report summary --db porting.db
./reports/generate-report.sh
git add -A
git commit -m "feat(p7-01): verify 11 small modules (114 tests), mark thw benchmarks n/a"
```
---
## Task 3: P7-02 — Opts Stubs (77 tests)
**Go source:** `golang/nats-server/server/opts_test.go`
**Target .NET file:** `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerOptionsTests.cs`
**DB IDs:** 25142597 (77 stubs in `server/opts_test.go`)
These test config file parsing and option binding. The Go tests at these line numbers use `ProcessConfigFile`, `MergeOptions`, and `SetBaselineOptions` — all of which are ported in `Config/ServerOptionsConfiguration.cs` and `ServerOptions.cs`.
### Step 1: Read existing file and Go reference
```bash
# View existing ServerOptionsTests.cs (333 lines, 9 complete tests already written)
cat dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerOptionsTests.cs
# List all 77 stubs from DB with Go line numbers
sqlite3 porting.db "
SELECT id, go_method, go_line_number, dotnet_method
FROM unit_tests
WHERE module_id=8 AND go_file='server/opts_test.go' AND status='stub'
ORDER BY go_line_number;"
```
### Step 2: Classify stubs
Before writing tests, check each Go test to determine if it requires a running server:
- Tests using `RunServer()` or `RunBasicJetStreamServer()` → mark `deferred` instead
- Tests that only call `ProcessConfigFile()`, `MergeOptions()`, `SetBaseline()`, or validate option structs → write as unit test
**Expected classification:** Most opts stubs are config-parsing tests (unit). Some may be server-integration (mark `deferred`).
For each stub that needs a running server, run:
```bash
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status deferred --db porting.db
```
### Step 3: Write test bodies
For each remaining stub, add a test method to `ServerOptionsTests.cs`.
Pattern for a config file test:
```csharp
/// <summary>
/// Mirrors TestConfigFile — verify ProcessConfigFile loads host/port from JSON.
/// </summary>
[Fact] // T:2514
public void ConfigFile_ShouldSucceed()
{
var json = """{"host":"localhost","port":4222}""";
var tmp = Path.GetTempFileName() + ".json";
File.WriteAllText(tmp, json);
try
{
var opts = ServerOptionsConfiguration.ProcessConfigFile(tmp);
opts.Host.ShouldBe("localhost");
opts.Port.ShouldBe(4222);
}
finally { File.Delete(tmp); }
}
```
Pattern for a flag-override test:
```csharp
/// <summary>
/// Mirrors TestRouteFlagOverride — flag opts override file opts.
/// </summary>
[Fact] // T:2517
public void RouteFlagOverride_ShouldSucceed()
{
var fileOpts = new ServerOptions { Routes = ["nats://server1:5222"] };
var flagOpts = new ServerOptions { Routes = ["nats://server2:5222"] };
fileOpts.MergeOptions(flagOpts);
fileOpts.Routes.ShouldContain("nats://server2:5222");
}
```
For Go tests that use config files, create a temp file with JSON content matching what the Go test uses, call `ProcessConfigFile`, and assert the resulting `ServerOptions` fields.
### Step 4: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~ServerOptionsTests"
```
Fix any failures by comparing Go reference at the line numbers from Step 1.
### Step 5: Mark complete in DB
For each test whose body was written and is passing:
```bash
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status complete --db porting.db
```
Or in bulk via SQLite if all 77 passed:
```bash
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE module_id=8 AND go_file='server/opts_test.go' AND status='stub';"
```
### Step 6: Run full suite and commit
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
./reports/generate-report.sh
git add dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ServerOptionsTests.cs porting.db reports/
git commit -m "feat(p7-02): fill opts_test.go stubs (77 tests) — ServerOptionsTests"
```
---
## Task 4: P7-03 — JWT Stubs (88 tests)
**Go source:** `golang/nats-server/server/jwt_test.go`
**Target .NET file:** `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs`
**DB IDs:** 18091896 (88 stubs)
These test JWT decode, validate, and claims extraction. The Go tests at these line numbers test pure JWT functions in `jwt.go` — they do NOT require a running server.
### Step 1: List stubs
```bash
sqlite3 porting.db "
SELECT id, go_method, go_line_number, dotnet_method
FROM unit_tests
WHERE module_id=8 AND go_file='server/jwt_test.go' AND status='stub'
ORDER BY go_line_number;"
```
### Step 2: Read existing test file
```bash
cat dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs
```
The file has complete tests for `JwtPrefix`, `WipeSlice`, `ValidateSrc`, etc. Stubs cover the remaining JWT functions.
### Step 3: Write test bodies
Look up each Go test by line number in `golang/nats-server/server/jwt_test.go`. Write the corresponding .NET test method. The .NET JWT functions live in `Auth/JwtProcessor.cs`.
Pattern for a claims decode test:
```csharp
/// <summary>
/// Mirrors TestJwtHeader — verify header prefix detection.
/// </summary>
[Fact] // T:1809
public void JwtHeader_ValidJwt_ReturnsTrue()
{
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.payload.sig";
JwtProcessor.IsJwt(token).ShouldBeTrue();
}
```
Pattern for a validation error test:
```csharp
/// <summary>
/// Mirrors TestDecodeAccountClaims_InvalidToken.
/// </summary>
[Fact] // T:1820
public void DecodeAccountClaims_InvalidToken_ReturnsError()
{
var (claims, err) = JwtProcessor.DecodeAccountClaims("notajwt");
claims.ShouldBeNull();
err.ShouldNotBeNull();
}
```
### Step 4: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~JwtProcessorTests"
```
### Step 5: Mark complete and commit
```bash
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE module_id=8 AND go_file='server/jwt_test.go' AND status='stub';"
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
./reports/generate-report.sh
git add dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/JwtProcessorTests.cs porting.db reports/
git commit -m "feat(p7-03): fill jwt_test.go stubs (88 tests) — JwtProcessorTests"
```
---
## Task 5: P7-04 — Auth & Config-Check Stubs (40 tests)
**Go sources:**
- `golang/nats-server/server/auth_test.go` (6 stubs, IDs 147153, class `AuthHandlerTests`)
- `golang/nats-server/server/auth_callout_test.go` (31 stubs, IDs 111141, class `AuthCalloutTests`)
- `golang/nats-server/server/config_check_test.go` (3 stubs, IDs 271273, class `ConfigCheckTests`)
**Target .NET files:**
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthHandlerTests.cs` (add 6 methods)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthCalloutTests.cs` (create or add 31 methods)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Config/ConfigCheckTests.cs` (create 3 methods)
### Step 1: List stubs per file
```bash
sqlite3 porting.db "
SELECT id, go_file, go_method, go_line_number, dotnet_class, dotnet_method
FROM unit_tests
WHERE id IN (111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,
147,148,149,150,151,152,153,
271,272,273)
ORDER BY go_file, go_line_number;"
```
### Step 2: Auth handler stubs (6 tests)
Read existing `AuthHandlerTests.cs`, then add these 6 test methods:
- **T:147** `TestUserUnknownAllowedConnectionType` → test that passing an unrecognized connection type string to `ValidateAllowedConnections` returns an error
- **T:149** `TestNoAuthUser` → test that `ProcessClientOrLeafAuthentication` with no auth configured returns true when `AuthRequired=false`
- **T:150** `TestNoAuthUserNkey` → test that an NKey user with valid credentials authenticates when NKey list has one entry
- **T:151** `TestUserConnectionDeadline` → test `User.ConnectionDeadline` parsing from Unix timestamp in claims
- **T:152** `TestNoAuthUserNoConnectProto` → test that missing connect protocol fields get default handling
- **T:153** `TestAuthProxyRequired` → test that `GetAuthErrClosedState(new AuthProxyRequiredException())` returns `ClosedState.ProxyRequired`
Pattern:
```csharp
/// <summary>
/// Mirrors TestAuthProxyRequired.
/// </summary>
[Fact] // T:153
public void AuthProxyRequired_ShouldReturnProxyRequiredState()
{
var state = AuthHandler.GetAuthErrClosedState(new AuthProxyRequiredException("proxy required"));
state.ShouldBe(ClosedState.ProxyRequired);
}
```
### Step 3: Auth callout stubs (31 tests, class `AuthCalloutTests`)
**Important:** Check Go tests at lines 212, 329, etc. in `auth_callout_test.go`. Most use `RunBasicJetStreamServer()`. If they do, mark them **`deferred`** instead:
```bash
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status deferred --db porting.db
```
For those that test pure helper types/functions (e.g., `FillClientInfo`, `FillConnectOpts`, serialization), write the unit test body.
Check which file `AuthCalloutTests` lives in:
```bash
grep -rn "AuthCalloutTests\|class AuthCallout" dotnet/tests/ | grep -v obj
```
If the file doesn't exist, create `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/AuthCalloutTests.cs`:
```csharp
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;
namespace ZB.MOM.NatsNet.Server.Tests.Auth;
/// <summary>
/// Tests for AuthCallout helper types.
/// Mirrors type/helper tests from server/auth_callout_test.go.
/// </summary>
public class AuthCalloutTests
{
// test methods here
}
```
### Step 4: Config check stubs (3 tests, class `ConfigCheckTests`)
Go tests at lines corresponding to IDs 271273:
- **T:271** `TestConfigCheck` → test that a valid config passes validation
- **T:272** `TestConfigCheckIncludes` → test that includes are resolved
- **T:273** `TestConfigCheckMultipleErrors` → test that multiple validation errors are all reported
Create `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Config/ConfigCheckTests.cs`:
```csharp
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Config;
namespace ZB.MOM.NatsNet.Server.Tests.Config;
/// <summary>
/// Tests for configuration validation.
/// Mirrors server/config_check_test.go.
/// </summary>
public class ConfigCheckTests
{
// test methods here
}
```
### Step 5: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~AuthHandlerTests|FullyQualifiedName~AuthCalloutTests|FullyQualifiedName~ConfigCheckTests"
```
### Step 6: Mark complete (or deferred) and commit
For each test marked complete:
```bash
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status complete --db porting.db
```
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-04): fill auth & config_check stubs — AuthHandlerTests, AuthCalloutTests, ConfigCheckTests"
```
---
## Task 6: P7-05 — Signal & Log Stubs (19 tests)
**Go sources:**
- `golang/nats-server/server/signal_test.go` (16 stubs, IDs 29132928, class `SignalHandlerTests`)
- `golang/nats-server/server/log_test.go` (3 stubs, IDs 20202022, class `ServerLoggerTests`)
**Target .NET files:**
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/SignalHandlerTests.cs` (add 16 methods)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/ServerLoggerTests.cs` (create 3 methods)
### Step 1: List stubs
```bash
sqlite3 porting.db "
SELECT id, go_file, go_method, go_line_number, dotnet_method
FROM unit_tests WHERE id BETWEEN 2913 AND 2928
ORDER BY go_line_number;"
sqlite3 porting.db "
SELECT id, go_file, go_method, go_line_number, dotnet_method
FROM unit_tests WHERE id IN (2020, 2021, 2022);"
```
### Step 2: Signal stubs (16 tests)
Most signal tests involve `ProcessSignal(command, pidExpression)`. Key tests:
- **T:29132928** cover: multi-process glob, pgrep errors, resolve single process, invalid command/PID, sending QUIT/TERM/Reopen/Reload to specific PID, lame duck mode, interrupt/term exit codes.
Read each Go test at the given line in `golang/nats-server/server/signal_test.go`. For tests that start a real server or spawn processes, mark `deferred`.
Pattern for pure logic tests:
```csharp
/// <summary>
/// Mirrors TestProcessSignalInvalidCommand.
/// </summary>
[Fact] // T:2919
public void ProcessSignalInvalidCommand_ShouldReturnError()
{
var err = SignalHandler.ProcessSignal((ServerCommand)99, "0");
err.ShouldNotBeNull();
}
```
Add test methods to the existing `SignalHandlerTests.cs` file.
### Step 3: Log stubs (3 tests)
The 3 stubs test password/token masking in protocol trace output:
- **T:2020** `TestNoPasswordsFromConnectTrace` — connect string with `pass=secret` should have password redacted in trace
- **T:2021** `TestRemovePassFromTrace``RemovePassFromTrace("... pass: secret ...")` returns string without the password
- **T:2022** `TestRemoveAuthTokenFromTrace``RemoveAuthTokenFromTrace("... auth_token: tok ...")` removes the token
Create `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Internal/ServerLoggerTests.cs`:
```csharp
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server.Internal;
namespace ZB.MOM.NatsNet.Server.Tests.Internal;
/// <summary>
/// Tests for password/token masking in server logging.
/// Mirrors server/log_test.go tests for trace sanitization.
/// </summary>
public class ServerLoggerTests
{
[Fact] // T:2020
public void NoPasswordsFromConnectTrace_ShouldRedactPass()
{
// See golang/nats-server/server/log_test.go:TestNoPasswordsFromConnectTrace
// Call the relevant NatsServer or ServerLogging method that strips passwords from CONNECT traces
// Assert the returned string does not contain the literal password
}
[Fact] // T:2021
public void RemovePassFromTrace_ShouldRemovePassword()
{
var input = """{"verbose":false,"pedantic":false,"user":"derek","pass":"s3cr3t!","tls_required":false,"name":""}""";
var result = ServerLogging.RemovePassFromTrace(input);
result.ShouldNotContain("s3cr3t!");
result.ShouldContain("\"pass\":\"\"");
}
[Fact] // T:2022
public void RemoveAuthTokenFromTrace_ShouldRemoveToken()
{
var input = """{"verbose":false,"auth_token":"mytoken","tls_required":false}""";
var result = ServerLogging.RemoveAuthTokenFromTrace(input);
result.ShouldNotContain("mytoken");
result.ShouldContain("\"auth_token\":\"\"");
}
}
```
Check that `ServerLogging.RemovePassFromTrace` and `RemoveAuthTokenFromTrace` exist. If not, add them to `Internal/ServerLogging.cs` by reading the Go source in `server/log.go`.
### Step 4: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~SignalHandlerTests|FullyQualifiedName~ServerLoggerTests"
```
### Step 5: Mark complete and commit
```bash
# Mark signal stubs complete (adjust IDs for any that were deferred instead)
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE id BETWEEN 2913 AND 2928 AND status='stub';"
for id in 2020 2021 2022; do
dotnet run --project tools/NatsNet.PortTracker -- test update $id --status complete --db porting.db
done
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-05): fill signal & log stubs (19 tests) — SignalHandlerTests, ServerLoggerTests"
```
---
## Task 7: P7-06 — Memory Store & Store Interface Tests (58 tests)
**Go sources:**
- `golang/nats-server/server/memstore_test.go` (41 tests, IDs 20232063)
- `golang/nats-server/server/store_test.go` (17 tests, IDs 29412957)
**Target .NET files:**
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamMemoryStoreTests.cs` (create, 41 tests)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/StorageEngineTests.cs` (create, 17 tests)
**Important:** These test the in-memory JetStream message store. The Go `memstore.go` is a pure data structure with no server dependency. Verify the .NET port exists before writing tests:
```bash
find dotnet/src -name "*.cs" | xargs grep -l "MemStore\|MemoryStore\|MsgStore" | grep -v obj
```
### Step 1: Understand memstore interface
Read the Go store interface at the top of `golang/nats-server/server/store.go`. The .NET equivalent should implement a similar `IMsgStore` interface. If it hasn't been ported yet, note the missing implementation and write tests that call methods as they should exist.
### Step 2: Classify memstore tests
From `golang/nats-server/server/memstore_test.go`, check each test:
- Tests using `NewMemStore()` only → unit tests (write .NET equivalent)
- Tests using `RunServer()` or requiring cluster → mark `deferred`
### Step 3: Create JetStreamMemoryStoreTests.cs
```csharp
// Copyright 2018-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server.JetStream;
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
/// <summary>
/// Tests for the in-memory JetStream message store.
/// Mirrors server/memstore_test.go.
/// </summary>
public class JetStreamMemoryStoreTests
{
[Fact] // T:2023
public void MemStoreBasics_ShouldStoreAndRetrieve()
{
// NewMemStore() → store message → retrieve → verify payload matches
// Reference: golang/nats-server/server/memstore_test.go:TestMemStoreBasics
}
// ... remaining test methods
}
```
### Step 4: Create StorageEngineTests.cs
```csharp
// Copyright 2018-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server.JetStream;
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
/// <summary>
/// Tests for the JetStream store interface contracts.
/// Mirrors server/store_test.go.
/// </summary>
public class StorageEngineTests
{
// test methods
}
```
### Step 5: Map tests in DB
For each new test method, update the DB to mark it complete and fill in the dotnet_method:
```bash
dotnet run --project tools/NatsNet.PortTracker -- test update <id> \
--status complete --db porting.db
# Also update dotnet_method if not already set:
dotnet run --project tools/NatsNet.PortTracker -- test map <id> \
--class JetStreamMemoryStoreTests \
--method <MethodName> --db porting.db
```
### Step 6: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~JetStreamMemoryStoreTests|FullyQualifiedName~StorageEngineTests"
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
```
### Step 7: Commit
```bash
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-06): port memstore & store interface tests (58 tests)"
```
---
## Task 8: P7-07 — File Store Unit Tests, First Half (~125 tests)
**Go source:** `golang/nats-server/server/filestore_test.go`
**Target .NET file:** `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamFileStoreTests.cs`
**DB IDs:** 351474 (first ~125 of 249 total, `server/filestore_test.go`)
### Step 1: List first-half IDs and Go line numbers
```bash
sqlite3 porting.db "
SELECT id, go_method, go_line_number, dotnet_method
FROM unit_tests
WHERE module_id=8 AND go_file='server/filestore_test.go'
ORDER BY go_line_number
LIMIT 125;"
```
The split point for P7-07 vs P7-08 is approximately test ID 474 (the first 125 tests by Go line order).
### Step 2: Verify filestore.go is ported
```bash
find dotnet/src -name "*.cs" | xargs grep -l "FileStore\|fileStore" | grep -v obj
```
If `FileStore` is not yet ported, this task must port the implementation first, then write tests. Check Phase 6 session notes for filestore porting status.
### Step 3: Classify file store tests
From `golang/nats-server/server/filestore_test.go`, each test that uses `newFileStore()` or `newFileStoreWithCreated()` is a unit test. Tests that use `RunServer()` are deferred.
### Step 4: Create JetStreamFileStoreTests.cs (first half)
```csharp
// Copyright 2018-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using Shouldly;
using ZB.MOM.NatsNet.Server.JetStream;
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
/// <summary>
/// Unit tests for JetStream file store read/write/purge operations.
/// Mirrors server/filestore_test.go (first half).
/// </summary>
public class JetStreamFileStoreTests
{
private static string TempDir() => Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
[Fact] // T:351
public void FileStoreBasics_ShouldStoreAndRetrieve()
{
// newFileStore(config) → store message → load → verify
// Reference: golang/nats-server/server/filestore_test.go:TestFileStoreBasics
}
// ... remaining first-half tests
}
```
### Step 5: Run, mark, commit
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~JetStreamFileStoreTests"
# Mark first-half tests complete via SQLite
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE module_id=8 AND go_file='server/filestore_test.go' AND status='not_started'
AND id <= 474;" -- adjust ID boundary based on actual split
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-07): port filestore tests first half (~125 tests)"
```
---
## Task 9: P7-08 — File Store Unit Tests, Second Half (~124 tests)
**Go source:** `golang/nats-server/server/filestore_test.go`
**Target .NET file:** `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamFileStoreTests.cs`
**DB IDs:** 475599 (remaining ~124 of 249 total)
### Step 1: List second-half IDs
```bash
sqlite3 porting.db "
SELECT id, go_method, go_line_number, dotnet_method
FROM unit_tests
WHERE module_id=8 AND go_file='server/filestore_test.go' AND status='not_started'
ORDER BY go_line_number;"
```
### Step 2: Add remaining test methods
Continue adding methods to `JetStreamFileStoreTests.cs` for IDs 475599.
### Step 3: Run, mark, commit
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~JetStreamFileStoreTests"
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE module_id=8 AND go_file='server/filestore_test.go' AND status='not_started';"
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-08): port filestore tests second half (~124 tests)"
```
---
## Task 10: P7-09 — JetStream Unit Tests (94 tests)
**Go sources:**
- `golang/nats-server/server/jetstream_errors_test.go` (4 tests, IDs 13811384)
- `golang/nats-server/server/jetstream_versioning_test.go` (18 tests, IDs 17911808)
- `golang/nats-server/server/jetstream_batching_test.go` (29 tests, IDs 716744)
- `golang/nats-server/server/dirstore_test.go` (12 tests, IDs 285296)
- `golang/nats-server/server/accounts_test.go` (31 tests, IDs 80110)
**Target .NET files:**
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamErrorsTests.cs` (create)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamVersioningTests.cs` (create)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/JetStream/JetStreamBatchingTests.cs` (create)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Auth/DirectoryStoreTests.cs` (create)
- `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/Accounts/AccountTests.cs` (add 31 methods to existing)
### Step 1: List all IDs
```bash
sqlite3 porting.db "
SELECT id, go_file, go_method, go_line_number, dotnet_class, dotnet_method
FROM unit_tests
WHERE id IN (
SELECT id FROM unit_tests WHERE go_file='server/jetstream_errors_test.go' AND status='not_started'
UNION SELECT id FROM unit_tests WHERE go_file='server/jetstream_versioning_test.go' AND status='not_started'
UNION SELECT id FROM unit_tests WHERE go_file='server/jetstream_batching_test.go' AND status='not_started'
UNION SELECT id FROM unit_tests WHERE go_file='server/dirstore_test.go' AND status='not_started'
UNION SELECT id FROM unit_tests WHERE go_file='server/accounts_test.go' AND status='not_started'
)
ORDER BY go_file, go_line_number;"
```
### Step 2: JetStream errors (4 tests)
These test error type checking — pure type assertions:
```csharp
// JetStreamErrorsTests.cs
[Fact] // T:1381
public void IsApiError_WithApiError_ReturnsTrue()
{
var err = new ApiError(404, "not found");
JetStreamErrors.IsApiError(err).ShouldBeTrue();
}
```
### Step 3: JetStream versioning (18 tests)
These test version compatibility checks:
```csharp
// JetStreamVersioningTests.cs
[Fact] // T:1791
public void ServerVersion_BelowMinimum_ReturnsError()
{
var err = JetStreamVersioning.CheckServerVersion("2.1.0", "2.2.0");
err.ShouldNotBeNull();
}
```
### Step 4: JetStream batching (29 tests)
These test batch message accumulation logic (pure data structure, no server):
```csharp
// JetStreamBatchingTests.cs
[Fact] // T:716
public void BatchBasics_ShouldAccumulateMessages()
{
// Reference: golang/nats-server/server/jetstream_batching_test.go
}
```
### Step 5: Directory store (12 tests)
These test the JWT directory store (reading/writing JWT files):
```csharp
// DirectoryStoreTests.cs
[Fact] // T:285
public void DirStore_StoreAndFetch_ShouldRoundtrip()
{
var dir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(dir);
try
{
// create DirStore, store a JWT, fetch it back, verify match
}
finally { Directory.Delete(dir, recursive: true); }
}
```
### Step 6: Account tests (31 tests)
These test account route mapping, imports, exports — pure account logic:
Read existing `AccountTests.cs`, then add 31 methods. Example:
```csharp
[Fact] // T:80
public void AccountMultipleServiceImports_SameSubject_ShouldSucceed()
{
// Reference: golang/nats-server/server/accounts_test.go line 2535
// Test that an account can have multiple service imports for the same subject
// from different remote accounts
}
```
### Step 7: Run tests
```bash
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~JetStreamErrorsTests|FullyQualifiedName~JetStreamVersioningTests|FullyQualifiedName~JetStreamBatchingTests|FullyQualifiedName~DirectoryStoreTests"
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ \
--filter "FullyQualifiedName~AccountTests"
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
```
### Step 8: Mark complete and commit
```bash
# Mark all P7-09 tests complete
sqlite3 porting.db "
UPDATE unit_tests SET status='complete'
WHERE module_id=8 AND status='not_started' AND go_file IN (
'server/jetstream_errors_test.go',
'server/jetstream_versioning_test.go',
'server/jetstream_batching_test.go',
'server/dirstore_test.go',
'server/accounts_test.go'
);"
./reports/generate-report.sh
git add dotnet/tests/ porting.db reports/
git commit -m "feat(p7-09): port JetStream error/versioning/batching/dirstore/accounts tests (94 tests)"
```
---
## Task 11: P7-10 — Mark Deferred, Integration Tests, Phase Close
This session has three parts: (A) mark 2,126 server-integration tests `deferred`, (B) replace integration test placeholder, (C) close DB + milestones.
### Part A: Mark server-integration tests as deferred
All `not_started` tests that require a running NATS server are marked `deferred`:
```bash
sqlite3 porting.db "
UPDATE unit_tests SET status='deferred'
WHERE status='not_started'
AND go_file NOT IN (
'server/accounts_test.go',
'server/dirstore_test.go',
'server/memstore_test.go',
'server/store_test.go',
'server/filestore_test.go',
'server/jetstream_errors_test.go',
'server/jetstream_versioning_test.go',
'server/jetstream_batching_test.go',
'server/opts_test.go'
);"
```
Verify:
```bash
sqlite3 porting.db "SELECT status, COUNT(*) FROM unit_tests GROUP BY status ORDER BY status;"
```
Expected after this update:
- complete: ~1,256 (319 old + ~937 new from P7-02 through P7-09)
- deferred: ~2,126
- n_a: ~187 (181 old + 6 thw benchmarks)
- stub: 0 (all filled)
- not_started: 0
- verified: ~114 (small modules)
### Part B: Replace integration test placeholder
Replace `dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/UnitTest1.cs` with `NatsServerBehaviorTests.cs`.
These tests run against the **Go NATS server** (not the .NET server). They establish a behavioral baseline.
**Prerequisites:** Go server must be running: `cd golang/nats-server && go run . -p 4222`
Create `dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/NatsServerBehaviorTests.cs`:
```csharp
// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0
using NATS.Client.Core;
using Shouldly;
namespace ZB.MOM.NatsNet.Server.IntegrationTests;
/// <summary>
/// Behavioral baseline tests against the reference Go NATS server.
/// These tests require a running Go NATS server on localhost:4222.
/// Start with: cd golang/nats-server && go run . -p 4222
/// </summary>
[Trait("Category", "Integration")]
public class NatsServerBehaviorTests : IAsyncLifetime
{
private NatsConnection? _nats;
public async Task InitializeAsync()
{
_nats = new NatsConnection(new NatsOpts { Url = "nats://localhost:4222" });
await _nats.ConnectAsync();
}
public async Task DisposeAsync()
{
if (_nats is not null)
await _nats.DisposeAsync();
}
[Fact]
public async Task BasicPubSub_ShouldDeliverMessage()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var received = new TaskCompletionSource<string>();
await using var sub = await _nats!.SubscribeAsync<string>("test.hello", cancellationToken: cts.Token);
_ = Task.Run(async () =>
{
await foreach (var msg in sub.Msgs.ReadAllAsync(cts.Token))
{
received.TrySetResult(msg.Data ?? "");
break;
}
}, cts.Token);
await _nats.PublishAsync("test.hello", "world");
var result = await received.Task.WaitAsync(cts.Token);
result.ShouldBe("world");
}
[Fact]
public async Task WildcardSubscription_DotStar_ShouldMatch()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var received = new TaskCompletionSource<string>();
await using var sub = await _nats!.SubscribeAsync<string>("foo.*", cancellationToken: cts.Token);
_ = Task.Run(async () =>
{
await foreach (var msg in sub.Msgs.ReadAllAsync(cts.Token))
{
received.TrySetResult(msg.Subject);
break;
}
}, cts.Token);
await _nats.PublishAsync("foo.bar", "payload");
var subject = await received.Task.WaitAsync(cts.Token);
subject.ShouldBe("foo.bar");
}
[Fact]
public async Task WildcardSubscription_GreaterThan_ShouldMatchMultiLevel()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var received = new TaskCompletionSource<string>();
await using var sub = await _nats!.SubscribeAsync<string>("foo.>", cancellationToken: cts.Token);
_ = Task.Run(async () =>
{
await foreach (var msg in sub.Msgs.ReadAllAsync(cts.Token))
{
received.TrySetResult(msg.Subject);
break;
}
}, cts.Token);
await _nats.PublishAsync("foo.bar.baz", "payload");
var subject = await received.Task.WaitAsync(cts.Token);
subject.ShouldBe("foo.bar.baz");
}
[Fact]
public async Task QueueGroup_ShouldDeliverToOnlyOneSubscriber()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var count1 = 0;
var count2 = 0;
await using var sub1 = await _nats!.SubscribeAsync<string>("qg.test", queueGroup: "workers", cancellationToken: cts.Token);
await using var sub2 = await _nats!.SubscribeAsync<string>("qg.test", queueGroup: "workers", cancellationToken: cts.Token);
_ = Task.Run(async () =>
{
await foreach (var _ in sub1.Msgs.ReadAllAsync(cts.Token))
Interlocked.Increment(ref count1);
}, cts.Token);
_ = Task.Run(async () =>
{
await foreach (var _ in sub2.Msgs.ReadAllAsync(cts.Token))
Interlocked.Increment(ref count2);
}, cts.Token);
for (var i = 0; i < 10; i++)
await _nats.PublishAsync("qg.test", $"msg{i}");
await Task.Delay(500, cts.Token);
(count1 + count2).ShouldBe(10);
count1.ShouldBeGreaterThan(0);
count2.ShouldBeGreaterThan(0);
}
[Fact]
public async Task ConnectDisconnect_ShouldNotThrow()
{
var nats2 = new NatsConnection(new NatsOpts { Url = "nats://localhost:4222" });
await Should.NotThrowAsync(async () =>
{
await nats2.ConnectAsync();
await nats2.DisposeAsync();
});
}
}
```
Delete the placeholder:
```bash
rm dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/UnitTest1.cs
```
Run the integration tests (requires Go server running):
```bash
# In terminal 1: start Go server
cd golang/nats-server && go run . -p 4222
# In terminal 2: run integration tests
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/
```
### Part C: Final DB verification and milestone close
#### Mark server module verified (features and module)
After all unit tests are written and verified:
```bash
# Mark all complete tests in server module as verified
sqlite3 porting.db "
UPDATE unit_tests SET status='verified'
WHERE module_id=8 AND status='complete';"
# Mark all features in server module verified
dotnet run --project tools/NatsNet.PortTracker -- feature update 0 --status verified --all-in-module 8 --db porting.db
# Mark server module verified
dotnet run --project tools/NatsNet.PortTracker -- module update 8 --status verified --db porting.db
```
#### Run phase check
```bash
dotnet run --project tools/NatsNet.PortTracker -- phase check 7 --db porting.db
```
#### Generate final report
```bash
./reports/generate-report.sh
dotnet run --project tools/NatsNet.PortTracker -- report summary --db porting.db
```
#### Close Gitea issues #45#52
```bash
for issue in 45 46 47 48 49 50 51 52; do
curl -s -X PATCH "https://gitea.dohertylan.com/api/v1/repos/dohertj2/natsnet/issues/${issue}" \
-H "Content-Type: application/json" \
-H "Authorization: token $GITEA_TOKEN" \
-d '{"state":"closed"}'
done
```
#### Close milestones 7 and 8
```bash
for milestone in 7 8; do
curl -s -X PATCH "https://gitea.dohertylan.com/api/v1/repos/dohertj2/natsnet/milestones/${milestone}" \
-H "Content-Type: application/json" \
-H "Authorization: token $GITEA_TOKEN" \
-d '{"state":"closed"}'
done
```
#### Final commit
```bash
git add porting.db reports/ dotnet/tests/
git commit -m "feat(p7-10): mark deferred tests, add integration tests, close Phase 7
- 2126 server-integration tests marked deferred
- NatsServerBehaviorTests.cs replaces UnitTest1.cs placeholder
- Server module and all features marked verified
- Gitea issues #45-#52 and milestones 7 & 8 closed"
```
---
## Completion Checklist
- [ ] Schema: `unit_tests.status` includes `'deferred'`
- [ ] 11 small modules: status `verified`
- [ ] All 224 stubs: status `complete` or `deferred` (none remain as `stub`)
- [ ] ~401 unit tests: status `complete` (new test methods written)
- [ ] ~2,126 server-integration tests: status `deferred`
- [ ] 6 thw benchmarks: status `n_a`
- [ ] `dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/` passes
- [ ] `dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/` passes
- [ ] `dotnet run --project tools/NatsNet.PortTracker -- phase check 7 --db porting.db` passes
- [ ] Gitea issues #45#52 closed
- [ ] Gitea milestones 7 and 8 closed