using FluentAssertions; using StellaOps.Scanner.Reachability; using Xunit; namespace StellaOps.Reachability.FixtureTests; public sealed class SymbolIdTests { [Fact] public void ForJava_CreatesCanonicalSymbolId() { var id = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V"); id.Should().StartWith("sym:java:"); id.Should().HaveLength("sym:java:".Length + 43); // Base64url SHA-256 without padding = 43 chars } [Fact] public void ForJava_IsDeterministic() { var id1 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V"); var id2 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "(Ljava/lang/String;)V"); id1.Should().Be(id2); } [Fact] public void ForJava_IsCaseInsensitive() { var id1 = SymbolId.ForJava("com.example", "MyClass", "doSomething", "()V"); var id2 = SymbolId.ForJava("COM.EXAMPLE", "MYCLASS", "DOSOMETHING", "()V"); id1.Should().Be(id2); } [Fact] public void ForDotNet_CreatesCanonicalSymbolId() { var id = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "MyMethod(System.String)"); id.Should().StartWith("sym:dotnet:"); } [Fact] public void ForDotNet_DifferentSignaturesProduceDifferentIds() { var id1 = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "Method(String)"); var id2 = SymbolId.ForDotNet("MyAssembly", "MyNamespace", "MyClass", "Method(Int32)"); id1.Should().NotBe(id2); } [Fact] public void ForNode_CreatesCanonicalSymbolId() { var id = SymbolId.ForNode("express", "lib/router", "function"); id.Should().StartWith("sym:node:"); } [Fact] public void ForNode_HandlesScopedPackages() { var id1 = SymbolId.ForNode("@angular/core", "src/render", "function"); var id2 = SymbolId.ForNode("@angular/core", "src/render", "function"); id1.Should().Be(id2); id1.Should().StartWith("sym:node:"); } [Fact] public void ForGo_CreatesCanonicalSymbolId() { var id = SymbolId.ForGo("github.com/example/repo", "pkg/http", "Server", "HandleRequest"); id.Should().StartWith("sym:go:"); } [Fact] public void ForGo_FunctionWithoutReceiver() { var id = SymbolId.ForGo("github.com/example/repo", "pkg/main", "", "main"); id.Should().StartWith("sym:go:"); } [Fact] public void ForRust_CreatesCanonicalSymbolId() { var id = SymbolId.ForRust("my_crate", "foo::bar", "my_function", "_ZN8my_crate3foo3bar11my_functionE"); id.Should().StartWith("sym:rust:"); } [Fact] public void ForSwift_CreatesCanonicalSymbolId() { var id = SymbolId.ForSwift("MyModule", "MyClass", "myMethod", null); id.Should().StartWith("sym:swift:"); } [Fact] public void ForShell_CreatesCanonicalSymbolId() { var id = SymbolId.ForShell("scripts/deploy.sh", "run_migration"); id.Should().StartWith("sym:shell:"); } [Fact] public void ForBinary_CreatesCanonicalSymbolId() { var id = SymbolId.ForBinary("7f6e5d4c3b2a1908", ".text", "_start"); id.Should().StartWith("sym:binary:"); } [Fact] public void ForPython_CreatesCanonicalSymbolId() { var id = SymbolId.ForPython("requests", "requests.api", "get"); id.Should().StartWith("sym:python:"); } [Fact] public void ForRuby_CreatesCanonicalSymbolId() { var id = SymbolId.ForRuby("rails", "ActiveRecord::Base", "#save"); id.Should().StartWith("sym:ruby:"); } [Fact] public void ForPhp_CreatesCanonicalSymbolId() { var id = SymbolId.ForPhp("laravel/framework", "Illuminate\\Http", "Request::input"); id.Should().StartWith("sym:php:"); } [Fact] public void Parse_ValidSymbolId_ReturnsComponents() { var id = SymbolId.ForJava("com.example", "MyClass", "method", "()V"); var result = SymbolId.Parse(id); result.Should().NotBeNull(); result!.Value.Lang.Should().Be("java"); result.Value.Fragment.Should().HaveLength(43); } [Fact] public void Parse_InvalidSymbolId_ReturnsNull() { SymbolId.Parse("invalid").Should().BeNull(); SymbolId.Parse("sym:").Should().BeNull(); SymbolId.Parse("sym:java").Should().BeNull(); SymbolId.Parse("").Should().BeNull(); SymbolId.Parse(null!).Should().BeNull(); } [Fact] public void FromTuple_CreatesSymbolIdFromRawTuple() { var tuple = "my\0canonical\0tuple"; var id = SymbolId.FromTuple("custom", tuple); id.Should().StartWith("sym:custom:"); } [Fact] public void AllLanguagesAreDifferent() { // Same tuple data should produce different IDs for different languages var java = SymbolId.ForJava("pkg", "cls", "meth", "()V"); var dotnet = SymbolId.ForDotNet("pkg", "cls", "meth", "()V"); var node = SymbolId.ForNode("pkg", "cls", "meth"); java.Should().NotBe(dotnet); dotnet.Should().NotBe(node); java.Should().NotBe(node); } }