feat: Add native binary analyzer test utilities and implement SM2 signing tests
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
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover 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
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
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
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover 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
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions. - Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation. - Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks. - Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Node;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests.Node;
|
||||
|
||||
/// <summary>
|
||||
/// Tests to verify deterministic output from the Node analyzer.
|
||||
/// Output must be reproducible across multiple runs.
|
||||
/// </summary>
|
||||
public sealed class NodeDeterminismTests : IDisposable
|
||||
{
|
||||
private readonly string _tempDir;
|
||||
|
||||
public NodeDeterminismTests()
|
||||
{
|
||||
_tempDir = Path.Combine(Path.GetTempPath(), "node-determinism-tests-" + Guid.NewGuid().ToString("N")[..8]);
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(_tempDir))
|
||||
{
|
||||
Directory.Delete(_tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteFile(string relativePath, string content)
|
||||
{
|
||||
var fullPath = Path.Combine(_tempDir, relativePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
|
||||
File.WriteAllText(fullPath, content);
|
||||
}
|
||||
|
||||
#region Multiple Runs Determinism
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleRuns_ProduceIdenticalOutput()
|
||||
{
|
||||
// Arrange
|
||||
SetupComplexProject();
|
||||
|
||||
// Act - Run analyzer multiple times
|
||||
var run1 = await RunAnalyzerAsync();
|
||||
var run2 = await RunAnalyzerAsync();
|
||||
var run3 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert - All runs should produce identical output
|
||||
Assert.Equal(run1, run2);
|
||||
Assert.Equal(run2, run3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleRuns_PackageOrderIsStable()
|
||||
{
|
||||
// Arrange
|
||||
WriteFile("package.json", JsonSerializer.Serialize(new
|
||||
{
|
||||
name = "root",
|
||||
version = "1.0.0"
|
||||
}));
|
||||
|
||||
// Create packages in non-alphabetical order
|
||||
WriteFile("node_modules/zebra/package.json", JsonSerializer.Serialize(new { name = "zebra", version = "1.0.0" }));
|
||||
WriteFile("node_modules/alpha/package.json", JsonSerializer.Serialize(new { name = "alpha", version = "1.0.0" }));
|
||||
WriteFile("node_modules/mike/package.json", JsonSerializer.Serialize(new { name = "mike", version = "1.0.0" }));
|
||||
WriteFile("node_modules/beta/package.json", JsonSerializer.Serialize(new { name = "beta", version = "1.0.0" }));
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
var order1 = ExtractPackageNames(result1);
|
||||
var order2 = ExtractPackageNames(result2);
|
||||
Assert.Equal(order1, order2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Package Ordering
|
||||
|
||||
[Fact]
|
||||
public async Task PackageOrdering_IsSortedByPurl()
|
||||
{
|
||||
// Arrange
|
||||
WriteFile("package.json", JsonSerializer.Serialize(new { name = "root", version = "1.0.0" }));
|
||||
WriteFile("node_modules/z-pkg/package.json", JsonSerializer.Serialize(new { name = "z-pkg", version = "1.0.0" }));
|
||||
WriteFile("node_modules/a-pkg/package.json", JsonSerializer.Serialize(new { name = "a-pkg", version = "1.0.0" }));
|
||||
WriteFile("node_modules/m-pkg/package.json", JsonSerializer.Serialize(new { name = "m-pkg", version = "1.0.0" }));
|
||||
|
||||
// Act
|
||||
var result = await RunAnalyzerAsync();
|
||||
|
||||
// Assert - Packages should be sorted
|
||||
var names = ExtractPackageNames(result);
|
||||
var sortedNames = names.OrderBy(n => n, StringComparer.Ordinal).ToList();
|
||||
Assert.Equal(sortedNames, names);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScopedPackageOrdering_IsConsistent()
|
||||
{
|
||||
// Arrange
|
||||
WriteFile("package.json", JsonSerializer.Serialize(new { name = "root", version = "1.0.0" }));
|
||||
WriteFile("node_modules/@z-scope/pkg/package.json", JsonSerializer.Serialize(new { name = "@z-scope/pkg", version = "1.0.0" }));
|
||||
WriteFile("node_modules/@a-scope/pkg/package.json", JsonSerializer.Serialize(new { name = "@a-scope/pkg", version = "1.0.0" }));
|
||||
WriteFile("node_modules/regular-pkg/package.json", JsonSerializer.Serialize(new { name = "regular-pkg", version = "1.0.0" }));
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Entrypoint Ordering
|
||||
|
||||
[Fact]
|
||||
public async Task EntrypointOrdering_IsStable()
|
||||
{
|
||||
// Arrange - Multiple entrypoints in various fields
|
||||
var packageJson = new
|
||||
{
|
||||
name = "multi-entry-pkg",
|
||||
version = "1.0.0",
|
||||
main = "./dist/main.js",
|
||||
module = "./dist/module.mjs",
|
||||
bin = new
|
||||
{
|
||||
cli1 = "./bin/cli1.js",
|
||||
cli2 = "./bin/cli2.js"
|
||||
}
|
||||
};
|
||||
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
||||
WriteFile("dist/main.js", "// main");
|
||||
WriteFile("dist/module.mjs", "// module");
|
||||
WriteFile("bin/cli1.js", "// cli1");
|
||||
WriteFile("bin/cli2.js", "// cli2");
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportsOrdering_IsSortedAlphabetically()
|
||||
{
|
||||
// Arrange - Exports with conditions in non-alphabetical order
|
||||
var packageJsonContent = @"{
|
||||
""name"": ""exports-pkg"",
|
||||
""version"": ""1.0.0"",
|
||||
""exports"": {
|
||||
""."": {
|
||||
""require"": ""./dist/index.cjs"",
|
||||
""import"": ""./dist/index.mjs"",
|
||||
""default"": ""./dist/index.js""
|
||||
}
|
||||
}
|
||||
}";
|
||||
WriteFile("package.json", packageJsonContent);
|
||||
WriteFile("dist/index.cjs", "// cjs");
|
||||
WriteFile("dist/index.mjs", "// mjs");
|
||||
WriteFile("dist/index.js", "// js");
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert - Order should be consistent
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Evidence Ordering
|
||||
|
||||
[Fact]
|
||||
public async Task EvidenceOrdering_IsStable()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = new
|
||||
{
|
||||
name = "evidence-pkg",
|
||||
version = "1.0.0",
|
||||
main = "./index.js",
|
||||
license = "MIT",
|
||||
scripts = new
|
||||
{
|
||||
postinstall = "node setup.js"
|
||||
}
|
||||
};
|
||||
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
||||
WriteFile("index.js", "// index");
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Resolution Ordering
|
||||
|
||||
[Fact]
|
||||
public async Task DependencyIndex_ProducesDeterministicScopes()
|
||||
{
|
||||
// Arrange
|
||||
var packageJson = new
|
||||
{
|
||||
name = "deps-pkg",
|
||||
version = "1.0.0",
|
||||
dependencies = new
|
||||
{
|
||||
dep1 = "^1.0.0",
|
||||
dep2 = "^2.0.0"
|
||||
},
|
||||
devDependencies = new
|
||||
{
|
||||
devDep1 = "^3.0.0",
|
||||
devDep2 = "^4.0.0"
|
||||
},
|
||||
peerDependencies = new
|
||||
{
|
||||
peerDep1 = "^5.0.0"
|
||||
},
|
||||
optionalDependencies = new
|
||||
{
|
||||
optDep1 = "^6.0.0"
|
||||
}
|
||||
};
|
||||
WriteFile("package.json", JsonSerializer.Serialize(packageJson));
|
||||
WriteFile("node_modules/dep1/package.json", JsonSerializer.Serialize(new { name = "dep1", version = "1.0.0" }));
|
||||
WriteFile("node_modules/dep2/package.json", JsonSerializer.Serialize(new { name = "dep2", version = "2.0.0" }));
|
||||
WriteFile("node_modules/devDep1/package.json", JsonSerializer.Serialize(new { name = "devDep1", version = "3.0.0" }));
|
||||
WriteFile("node_modules/devDep2/package.json", JsonSerializer.Serialize(new { name = "devDep2", version = "4.0.0" }));
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lockfile Ordering
|
||||
|
||||
[Fact]
|
||||
public async Task LockfilePackages_ProduceDeterministicOutput()
|
||||
{
|
||||
// Arrange
|
||||
WriteFile("package.json", JsonSerializer.Serialize(new { name = "lock-pkg", version = "1.0.0" }));
|
||||
WriteFile("package-lock.json", @"{
|
||||
""name"": ""lock-pkg"",
|
||||
""version"": ""1.0.0"",
|
||||
""lockfileVersion"": 3,
|
||||
""packages"": {
|
||||
"""": { ""name"": ""lock-pkg"", ""version"": ""1.0.0"" },
|
||||
""node_modules/z-dep"": { ""version"": ""3.0.0"", ""resolved"": ""https://r.example/z"", ""integrity"": ""sha512-Z"" },
|
||||
""node_modules/a-dep"": { ""version"": ""1.0.0"", ""resolved"": ""https://r.example/a"", ""integrity"": ""sha512-A"" },
|
||||
""node_modules/m-dep"": { ""version"": ""2.0.0"", ""resolved"": ""https://r.example/m"", ""integrity"": ""sha512-M"" }
|
||||
}
|
||||
}");
|
||||
|
||||
// Act
|
||||
var result1 = await RunAnalyzerAsync();
|
||||
var result2 = await RunAnalyzerAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1, result2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void SetupComplexProject()
|
||||
{
|
||||
// Root package
|
||||
var rootPackage = new
|
||||
{
|
||||
name = "complex-app",
|
||||
version = "1.0.0",
|
||||
dependencies = new
|
||||
{
|
||||
lodash = "^4.17.21",
|
||||
express = "^4.18.0"
|
||||
},
|
||||
devDependencies = new
|
||||
{
|
||||
typescript = "^5.0.0"
|
||||
}
|
||||
};
|
||||
WriteFile("package.json", JsonSerializer.Serialize(rootPackage));
|
||||
|
||||
// Dependencies
|
||||
WriteFile("node_modules/lodash/package.json", JsonSerializer.Serialize(new { name = "lodash", version = "4.17.21" }));
|
||||
WriteFile("node_modules/express/package.json", JsonSerializer.Serialize(new { name = "express", version = "4.18.2" }));
|
||||
WriteFile("node_modules/typescript/package.json", JsonSerializer.Serialize(new { name = "typescript", version = "5.2.2" }));
|
||||
|
||||
// Nested dependencies
|
||||
WriteFile("node_modules/express/node_modules/accepts/package.json", JsonSerializer.Serialize(new { name = "accepts", version = "1.3.8" }));
|
||||
WriteFile("node_modules/express/node_modules/body-parser/package.json", JsonSerializer.Serialize(new { name = "body-parser", version = "1.20.1" }));
|
||||
}
|
||||
|
||||
private async Task<string> RunAnalyzerAsync()
|
||||
{
|
||||
var analyzers = new ILanguageAnalyzer[] { new NodeLanguageAnalyzer() };
|
||||
return await LanguageAnalyzerTestHarness.RunToJsonAsync(
|
||||
_tempDir,
|
||||
analyzers,
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private static List<string> ExtractPackageNames(string json)
|
||||
{
|
||||
var doc = JsonDocument.Parse(json);
|
||||
return doc.RootElement.EnumerateArray()
|
||||
.Select(el => el.GetProperty("name").GetString()!)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user