feat: Implement Wine CSP HTTP provider for GOST cryptographic operations
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled

- Added WineCspHttpProvider class to interface with Wine-hosted CryptoPro CSP.
- Implemented ICryptoProvider, ICryptoProviderDiagnostics, and IDisposable interfaces.
- Introduced WineCspHttpSigner and WineCspHttpHasher for signing and hashing operations.
- Created WineCspProviderOptions for configuration settings including service URL and key options.
- Developed CryptoProGostSigningService to handle GOST signing operations and key management.
- Implemented HTTP service for the Wine CSP with endpoints for signing, verification, and hashing.
- Added Swagger documentation for API endpoints.
- Included health checks and error handling for service availability.
- Established DTOs for request and response models in the service.
This commit is contained in:
StellaOps Bot
2025-12-07 14:02:42 +02:00
parent 965cbf9574
commit bd2529502e
56 changed files with 9438 additions and 699 deletions

View File

@@ -0,0 +1,766 @@
using StellaOps.Scanner.Analyzers.Lang.Go.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Tests.Internal;
/// <summary>
/// Tests for <see cref="GoCapabilityScanner"/>.
/// </summary>
public sealed class GoCapabilityScannerTests
{
private const string TestFile = "test.go";
#region ScanFile - General Tests
[Fact]
public void ScanFile_NullContent_ReturnsEmpty()
{
var result = GoCapabilityScanner.ScanFile(null!, TestFile);
Assert.Empty(result);
}
[Fact]
public void ScanFile_EmptyContent_ReturnsEmpty()
{
var result = GoCapabilityScanner.ScanFile("", TestFile);
Assert.Empty(result);
}
[Fact]
public void ScanFile_WhitespaceContent_ReturnsEmpty()
{
var result = GoCapabilityScanner.ScanFile(" \n\t\n ", TestFile);
Assert.Empty(result);
}
[Fact]
public void ScanFile_NoPatterns_ReturnsEmpty()
{
const string code = @"
package main
func main() {
x := 1 + 2
println(x)
}";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Empty(result);
}
[Fact]
public void ScanFile_NormalizesBackslashesInPath()
{
const string code = @"cmd := exec.Command(""ls"")";
var result = GoCapabilityScanner.ScanFile(code, @"C:\test\file.go");
Assert.Single(result);
Assert.Equal("C:/test/file.go", result[0].SourceFile);
}
[Fact]
public void ScanFile_DeduplicatesSamePattern()
{
const string code = @"
exec.Command(""ls"")
exec.Command(""pwd"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
// Two different lines = two evidences
Assert.Equal(2, result.Count);
}
[Fact]
public void ScanFile_SortsByFileLineThenKind()
{
const string code = @"
os.Getenv(""PATH"")
exec.Command(""ls"")
os.Open(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.True(result.Count >= 3);
for (int i = 1; i < result.Count; i++)
{
Assert.True(
result[i - 1].SourceLine < result[i].SourceLine ||
(result[i - 1].SourceLine == result[i].SourceLine &&
result[i - 1].Kind <= result[i].Kind));
}
}
#endregion
#region ScanFile - Comment Stripping
[Fact]
public void ScanFile_IgnoresSingleLineComments()
{
const string code = @"
package main
// exec.Command(""ls"") - this is a comment
func main() {}";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Empty(result);
}
[Fact]
public void ScanFile_IgnoresMultiLineComments()
{
const string code = @"
package main
/*
exec.Command(""ls"")
os.Remove(""file"")
*/
func main() {}";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Empty(result);
}
#endregion
#region ScanFile - Exec Patterns
[Fact]
public void ScanFile_DetectsExecCommand()
{
const string code = @"cmd := exec.Command(""ls"", ""-la"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("exec.Command", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
Assert.Equal(1.0f, result[0].Confidence);
}
[Fact]
public void ScanFile_DetectsExecCommandContext()
{
const string code = @"cmd := exec.CommandContext(ctx, ""ls"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("exec.Command", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSyscallExec()
{
const string code = @"syscall.Exec(""/bin/sh"", args, env)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("syscall.Exec", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSyscallForkExec()
{
const string code = @"syscall.ForkExec(""/bin/sh"", args, nil)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("syscall.Exec", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsOsStartProcess()
{
const string code = @"os.StartProcess(""/bin/ls"", []string{}, &attr)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Exec, result[0].Kind);
Assert.Equal("os.StartProcess", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - Filesystem Patterns
[Fact]
public void ScanFile_DetectsOsCreate()
{
const string code = @"f, err := os.Create(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Open/Create", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsOpen()
{
const string code = @"f, err := os.Open(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Open/Create", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsOsOpenFile()
{
const string code = @"f, err := os.OpenFile(""file.txt"", os.O_RDWR, 0644)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
}
[Fact]
public void ScanFile_DetectsOsRemove()
{
const string code = @"os.Remove(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Remove/RemoveAll", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsRemoveAll()
{
const string code = @"os.RemoveAll(""/tmp/dir"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Remove/RemoveAll", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsChmod()
{
const string code = @"os.Chmod(""file.txt"", 0755)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Chmod/Chown", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsChown()
{
const string code = @"os.Chown(""file.txt"", 1000, 1000)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Chmod/Chown", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsOsSymlink()
{
const string code = @"os.Symlink(""target"", ""link"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Symlink/Link", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsMkdir()
{
const string code = @"os.Mkdir(""dir"", 0755)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.Mkdir", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsReadFile()
{
const string code = @"data, _ := os.ReadFile(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("os.ReadFile/WriteFile", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsIoutilReadFile()
{
const string code = @"data, _ := ioutil.ReadFile(""file.txt"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Filesystem, result[0].Kind);
Assert.Equal("ioutil", result[0].Pattern);
}
#endregion
#region ScanFile - Network Patterns
[Fact]
public void ScanFile_DetectsNetDial()
{
const string code = @"conn, _ := net.Dial(""tcp"", ""localhost:8080"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("net.Dial", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsNetListen()
{
const string code = @"ln, _ := net.Listen(""tcp"", "":8080"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("net.Listen", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsHttpGet()
{
const string code = @"resp, _ := http.Get(""https://example.com"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("http.Get/Post", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsHttpPost()
{
const string code = @"resp, _ := http.Post(""https://example.com"", ""application/json"", body)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("http.Get/Post", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsHttpListenAndServe()
{
const string code = @"http.ListenAndServe("":8080"", nil)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("http.ListenAndServe", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsNetLookupHost()
{
const string code = @"addrs, _ := net.LookupHost(""example.com"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Network, result[0].Kind);
Assert.Equal("net.Lookup", result[0].Pattern);
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
}
#endregion
#region ScanFile - Environment Patterns
[Fact]
public void ScanFile_DetectsOsGetenv()
{
const string code = @"val := os.Getenv(""PATH"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("os.Getenv", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsLookupEnv()
{
const string code = @"val, ok := os.LookupEnv(""PATH"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("os.Getenv", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsOsSetenv()
{
const string code = @"os.Setenv(""MY_VAR"", ""value"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("os.Setenv", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsOsEnviron()
{
const string code = @"env := os.Environ()";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Environment, result[0].Kind);
Assert.Equal("os.Environ", result[0].Pattern);
}
#endregion
#region ScanFile - Serialization Patterns
[Fact]
public void ScanFile_DetectsGobDecoder()
{
const string code = @"dec := gob.NewDecoder(reader)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("gob.Decoder/Encoder", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsJsonUnmarshal()
{
const string code = @"json.Unmarshal(data, &obj)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("json", result[0].Pattern);
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsXmlUnmarshal()
{
const string code = @"xml.Unmarshal(data, &obj)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("xml.Unmarshal", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsYamlUnmarshal()
{
const string code = @"yaml.Unmarshal(data, &obj)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Serialization, result[0].Kind);
Assert.Equal("yaml.Unmarshal", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
#endregion
#region ScanFile - Crypto Patterns
[Fact]
public void ScanFile_DetectsSha256New()
{
const string code = @"h := sha256.New()";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
Assert.Equal("crypto/hash", result[0].Pattern);
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsAesNewCipher()
{
const string code = @"block, _ := aes.NewCipher(key)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
Assert.Equal("crypto/cipher", result[0].Pattern);
}
[Fact]
public void ScanFile_DetectsRsaGenerateKey()
{
const string code = @"key, _ := rsa.GenerateKey(rand.Reader, 2048)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Crypto, result[0].Kind);
Assert.Equal("crypto/rsa", result[0].Pattern);
}
#endregion
#region ScanFile - Database Patterns
[Fact]
public void ScanFile_DetectsSqlOpen()
{
const string code = @"db, _ := sql.Open(""postgres"", connStr)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Database, result[0].Kind);
Assert.Equal("sql.Open", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
#endregion
#region ScanFile - Dynamic Code Patterns
[Fact]
public void ScanFile_DetectsReflectValueCall()
{
const string code = @"
import ""reflect""
v.Call(args)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("reflect.Value.Call", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsMethodByName()
{
const string code = @"m := v.MethodByName(""Execute"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.DynamicCode, result[0].Kind);
Assert.Equal("reflect.MethodByName", result[0].Pattern);
}
#endregion
#region ScanFile - Reflection Patterns
[Fact]
public void ScanFile_DetectsReflectTypeOf()
{
const string code = @"t := reflect.TypeOf(obj)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("reflect.TypeOf/ValueOf", result[0].Pattern);
Assert.Equal(CapabilityRisk.Low, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsReflectNew()
{
const string code = @"v := reflect.New(t)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("reflect.New", result[0].Pattern);
Assert.Equal(CapabilityRisk.Medium, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsRuntimeCaller()
{
const string code = @"_, file, line, _ := runtime.Caller(0)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.Reflection, result[0].Kind);
Assert.Equal("runtime.Caller", result[0].Pattern);
}
#endregion
#region ScanFile - Native Code Patterns
[Fact]
public void ScanFile_DetectsCgoImport()
{
const string code = @"import ""C""";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Contains("C", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsUnsafePointer()
{
const string code = @"ptr := unsafe.Pointer(&x)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("unsafe.Pointer", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsGoLinknameDirective()
{
const string code = @"//go:linkname localName runtime.someInternalFunc";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("go:linkname", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsGoNoescapeDirective()
{
const string code = @"//go:noescape
func unsafeFunc(ptr *byte)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("go:noescape", result[0].Pattern);
Assert.Equal(CapabilityRisk.High, result[0].Risk);
}
[Fact]
public void ScanFile_DetectsSyscallSyscall()
{
const string code = @"r1, r2, err := syscall.Syscall(SYS_WRITE, fd, buf, count)";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.NativeCode, result[0].Kind);
Assert.Equal("syscall.Syscall", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region ScanFile - Plugin Patterns
[Fact]
public void ScanFile_DetectsPluginOpen()
{
const string code = @"p, _ := plugin.Open(""plugin.so"")";
var result = GoCapabilityScanner.ScanFile(code, TestFile);
Assert.Single(result);
Assert.Equal(CapabilityKind.PluginLoading, result[0].Kind);
Assert.Equal("plugin.Open", result[0].Pattern);
Assert.Equal(CapabilityRisk.Critical, result[0].Risk);
}
#endregion
#region GoCapabilityEvidence Tests
[Fact]
public void Evidence_DeduplicationKey_IsCorrect()
{
var evidence = new GoCapabilityEvidence(
CapabilityKind.Exec,
"test.go",
10,
"exec.Command");
Assert.Equal("Exec|test.go|10|exec.Command", evidence.DeduplicationKey);
}
[Fact]
public void Evidence_ConfidenceIsClamped()
{
var evidence1 = new GoCapabilityEvidence(
CapabilityKind.Exec, "test.go", 1, "pattern",
confidence: 2.0f);
var evidence2 = new GoCapabilityEvidence(
CapabilityKind.Exec, "test.go", 1, "pattern",
confidence: -1.0f);
Assert.Equal(1.0f, evidence1.Confidence);
Assert.Equal(0.0f, evidence2.Confidence);
}
[Fact]
public void Evidence_CreateMetadata_IncludesAllFields()
{
var evidence = new GoCapabilityEvidence(
CapabilityKind.Exec,
"test.go",
10,
"exec.Command",
snippet: "cmd := exec.Command(\"ls\")",
confidence: 0.95f,
risk: CapabilityRisk.Critical);
var metadata = evidence.CreateMetadata().ToDictionary(kv => kv.Key, kv => kv.Value);
Assert.Equal("exec", metadata["capability.kind"]);
Assert.Equal("test.go:10", metadata["capability.source"]);
Assert.Equal("exec.Command", metadata["capability.pattern"]);
Assert.Equal("critical", metadata["capability.risk"]);
Assert.Equal("0.95", metadata["capability.confidence"]);
Assert.Contains("exec.Command", metadata["capability.snippet"]);
}
[Fact]
public void Evidence_ToLanguageEvidence_ReturnsCorrectFormat()
{
var evidence = new GoCapabilityEvidence(
CapabilityKind.Exec,
"test.go",
10,
"exec.Command");
var langEvidence = evidence.ToLanguageEvidence();
Assert.Equal(LanguageEvidenceKind.Metadata, langEvidence.Kind);
Assert.Equal("test.go", langEvidence.Source);
Assert.Equal("line:10", langEvidence.Locator);
Assert.Equal("Exec:exec.Command", langEvidence.Value);
}
#endregion
}