using NATS.Server.Configuration; namespace NATS.Server.Tests; public class NatsConfParserTests { [Fact] public void Parse_SimpleTopLevel_ReturnsCorrectTypes() { var result = NatsConfParser.Parse("foo = '1'; bar = 2.2; baz = true; boo = 22"); result["foo"].ShouldBe("1"); result["bar"].ShouldBe(2.2); result["baz"].ShouldBe(true); result["boo"].ShouldBe(22L); } [Fact] public void Parse_Booleans_AllVariants() { foreach (var (input, expected) in new[] { ("true", true), ("TRUE", true), ("yes", true), ("on", true), ("false", false), ("FALSE", false), ("no", false), ("off", false) }) { var result = NatsConfParser.Parse($"flag = {input}"); result["flag"].ShouldBe(expected); } } [Fact] public void Parse_IntegerWithSuffix_AppliesMultiplier() { var result = NatsConfParser.Parse("a = 1k; b = 2mb; c = 3gb; d = 4kb"); result["a"].ShouldBe(1000L); result["b"].ShouldBe(2L * 1024 * 1024); result["c"].ShouldBe(3L * 1024 * 1024 * 1024); result["d"].ShouldBe(4L * 1024); } [Fact] public void Parse_NestedMap_ReturnsDictionary() { var result = NatsConfParser.Parse("auth { user: admin, pass: secret }"); var auth = result["auth"].ShouldBeOfType>(); auth["user"].ShouldBe("admin"); auth["pass"].ShouldBe("secret"); } [Fact] public void Parse_Array_ReturnsList() { var result = NatsConfParser.Parse("items = [1, 2, 3]"); var items = result["items"].ShouldBeOfType>(); items.Count.ShouldBe(3); items[0].ShouldBe(1L); } [Fact] public void Parse_Variable_ResolvesFromContext() { var result = NatsConfParser.Parse("index = 22\nfoo = $index"); result["foo"].ShouldBe(22L); } [Fact] public void Parse_NestedVariable_UsesBlockScope() { var input = "index = 22\nnest {\n index = 11\n foo = $index\n}\nbar = $index"; var result = NatsConfParser.Parse(input); var nest = result["nest"].ShouldBeOfType>(); nest["foo"].ShouldBe(11L); result["bar"].ShouldBe(22L); } [Fact] public void Parse_EnvironmentVariable_ResolvesFromEnv() { Environment.SetEnvironmentVariable("NATS_TEST_VAR_12345", "hello"); try { var result = NatsConfParser.Parse("val = $NATS_TEST_VAR_12345"); result["val"].ShouldBe("hello"); } finally { Environment.SetEnvironmentVariable("NATS_TEST_VAR_12345", null); } } [Fact] public void Parse_UndefinedVariable_Throws() { Should.Throw(() => NatsConfParser.Parse("val = $UNDEFINED_VAR_XYZZY_99999")); } [Fact] public void Parse_IncludeDirective_MergesFile() { var dir = Path.Combine(Path.GetTempPath(), $"nats_test_{Guid.NewGuid():N}"); Directory.CreateDirectory(dir); try { File.WriteAllText(Path.Combine(dir, "main.conf"), "port = 4222\ninclude \"sub.conf\""); File.WriteAllText(Path.Combine(dir, "sub.conf"), "host = \"localhost\""); var result = NatsConfParser.ParseFile(Path.Combine(dir, "main.conf")); result["port"].ShouldBe(4222L); result["host"].ShouldBe("localhost"); } finally { Directory.Delete(dir, true); } } [Fact] public void Parse_MultipleKeySeparators_AllWork() { var r1 = NatsConfParser.Parse("a = 1"); var r2 = NatsConfParser.Parse("a : 1"); var r3 = NatsConfParser.Parse("a 1"); r1["a"].ShouldBe(1L); r2["a"].ShouldBe(1L); r3["a"].ShouldBe(1L); } [Fact] public void Parse_ErrorOnInvalidInput_Throws() { Should.Throw(() => NatsConfParser.Parse("= invalid")); } [Fact] public void Parse_CommentsInsideBlocks_AreIgnored() { var input = "auth {\n # comment\n user: admin\n // another comment\n pass: secret\n}"; var result = NatsConfParser.Parse(input); var auth = result["auth"].ShouldBeOfType>(); auth["user"].ShouldBe("admin"); auth["pass"].ShouldBe("secret"); } [Fact] public void Parse_ArrayOfMaps_Works() { var input = "users = [\n { user: alice, pass: pw1 }\n { user: bob, pass: pw2 }\n]"; var result = NatsConfParser.Parse(input); var users = result["users"].ShouldBeOfType>(); users.Count.ShouldBe(2); var first = users[0].ShouldBeOfType>(); first["user"].ShouldBe("alice"); } [Fact] public void Parse_BcryptPassword_HandledAsString() { var input = "pass = $2a$04$P/.bd.7unw9Ew7yWJqXsl.f4oNRLQGvadEL2YnqQXbbb.IVQajRdK"; var result = NatsConfParser.Parse(input); ((string)result["pass"]!).ShouldStartWith("$2a$"); } [Fact] public void ParseFile_WithDigest_ReturnsStableHash() { var dir = Path.Combine(Path.GetTempPath(), $"nats_test_{Guid.NewGuid():N}"); Directory.CreateDirectory(dir); try { var conf = Path.Combine(dir, "test.conf"); File.WriteAllText(conf, "port = 4222\nhost = \"localhost\""); var (result, digest) = NatsConfParser.ParseFileWithDigest(conf); result["port"].ShouldBe(4222L); digest.ShouldStartWith("sha256:"); digest.Length.ShouldBeGreaterThan(10); var (_, digest2) = NatsConfParser.ParseFileWithDigest(conf); digest2.ShouldBe(digest); } finally { Directory.Delete(dir, true); } } }