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

- 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:
StellaOps Bot
2025-12-07 13:12:41 +02:00
parent d907729778
commit e53a282fbe
387 changed files with 21941 additions and 1518 deletions

View File

@@ -0,0 +1,203 @@
using StellaOps.Scanner.Analyzers.Lang.Php.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Php.Tests.Internal;
public sealed class PhpFfiDetectorTests
{
[Fact]
public void AnalyzeFileContent_DetectsFfiCdef()
{
var content = @"
<?php
$ffi = FFI::cdef('
int printf(const char *format, ...);
void exit(int status);
', 'libc.so.6');
$ffi->printf('Hello, World!\n');
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Cdef);
}
[Fact]
public void AnalyzeFileContent_DetectsFfiLoad()
{
var content = @"
<?php
// Load FFI from header file
$ffi = FFI::load('mylib.h');
$ffi->myFunction();
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Load);
Assert.Contains(result, u => u.LibraryName == "mylib.h");
}
[Fact]
public void AnalyzeFileContent_DetectsFfiNew()
{
var content = @"
<?php
$ffi = FFI::cdef('struct Point { int x; int y; };');
$point = FFI::new('struct Point');
$point->x = 10;
$point->y = 20;
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.New);
Assert.Contains(result, u => u.Definition == "struct Point");
}
[Fact]
public void AnalyzeFileContent_DetectsFfiType()
{
var content = @"
<?php
$type = FFI::type('uint32_t');
$arr = FFI::new($type, 10);
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Type);
}
[Fact]
public void AnalyzeFileContent_DetectsFfiCast()
{
var content = @"
<?php
$ptr = FFI::new('void*');
$intPtr = FFI::cast('int*', $ptr);
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Cast);
}
[Fact]
public void AnalyzeFileContent_DetectsFfiScope()
{
var content = @"
<?php
// Using preloaded FFI
$ffi = FFI::scope('mylib');
$result = $ffi->calculate(42);
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.NotEmpty(result);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Scope);
Assert.Contains(result, u => u.Definition == "mylib");
}
[Fact]
public void AnalyzeFileContent_NoFfiUsage_ReturnsEmpty()
{
var content = @"
<?php
function calculate($x, $y) {
return $x + $y;
}
echo calculate(10, 20);
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.Empty(result);
}
[Fact]
public void AnalyzeFileContent_MultipleFfiUsages_ReturnsAll()
{
var content = @"
<?php
$ffi = FFI::cdef('int abs(int x);', 'libc.so.6');
$ffi2 = FFI::load('custom.h');
$val = FFI::new('int');
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.Equal(3, result.Count);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Cdef);
Assert.Contains(result, u => u.Kind == FfiUsageKind.Load);
Assert.Contains(result, u => u.Kind == FfiUsageKind.New);
}
[Fact]
public void AnalyzeFileContent_CaseInsensitive_DetectsFfi()
{
var content = @"
<?php
// Various case combinations
$a = ffi::cdef('void foo();');
$b = Ffi::Load('lib.h');
$c = FFi::NEW('int');
";
var result = PhpFfiDetector.AnalyzeFileContent(content, "test.php");
Assert.Equal(3, result.Count);
}
[Fact]
public void FfiAnalysisResult_CreateMetadata_IncludesAllFields()
{
var result = new FfiAnalysisResult(
FfiEnabledSetting.On,
["file1.php", "file2.php"],
[
new FfiUsage(FfiUsageKind.Cdef, "file1.php", 10, "FFI::cdef(...)", null, "int foo();"),
new FfiUsage(FfiUsageKind.Load, "file2.php", 5, "FFI::load('lib.so')", "lib.so", null)
],
["lib.so"],
[new FfiDefinition("file1.php", "int foo();")],
["vendor/lib/native.so"]);
var metadata = result.CreateMetadata().ToDictionary(kv => kv.Key, kv => kv.Value);
Assert.Equal("true", metadata["ffi.detected"]);
Assert.Equal("on", metadata["ffi.enabled_setting"]);
Assert.Equal("2", metadata["ffi.usage_count"]);
Assert.Contains("file1.php", metadata["ffi.files_with_usage"]);
Assert.Contains("lib.so", metadata["ffi.libraries"]);
Assert.Equal("1", metadata["ffi.definition_count"]);
Assert.Equal("1", metadata["ffi.native_library_count"]);
}
[Fact]
public void FfiAnalysisResult_Empty_HasNoFfiUsage()
{
var result = FfiAnalysisResult.Empty;
Assert.False(result.HasFfiUsage);
Assert.Empty(result.Usages);
Assert.Empty(result.FilesWithFfi);
Assert.Empty(result.Libraries);
}
}

View File

@@ -0,0 +1,253 @@
using StellaOps.Scanner.Analyzers.Lang.Php.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Php.Tests.Internal;
public sealed class PhpVersionConflictDetectorTests
{
[Fact]
public void Analyze_NullInputs_ReturnsEmpty()
{
var result = PhpVersionConflictDetector.Analyze(null, null);
Assert.False(result.HasConflicts);
Assert.Empty(result.Conflicts);
}
[Fact]
public void Analyze_EmptyLockData_ReturnsEmpty()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["symfony/console"] = "^6.0"
});
var result = PhpVersionConflictDetector.Analyze(manifest, ComposerLockData.Empty);
Assert.False(result.HasConflicts);
}
[Fact]
public void Analyze_OldPhpVersion_DetectsConflict()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["php"] = "^5.6"
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c => c.PackageName == "php" && c.ConflictType == PhpConflictType.PlatformRequirement);
}
[Fact]
public void Analyze_DeprecatedExtension_DetectsConflict()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["ext-mcrypt"] = "*"
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c =>
c.PackageName == "ext-mcrypt" &&
c.ConflictType == PhpConflictType.DeprecatedExtension &&
c.Severity == PhpConflictSeverity.High);
}
[Fact]
public void Analyze_MissingPackage_DetectsConflict()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["vendor/missing-package"] = "^1.0"
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c =>
c.PackageName == "vendor/missing-package" &&
c.ConflictType == PhpConflictType.MissingPackage);
}
[Fact]
public void Analyze_DevVersionWithStableConstraint_DetectsConflict()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["vendor/package"] = "^1.0"
});
var lockData = CreateLockData([
new ComposerPackage("vendor/package", "dev-main", "library", false, null, null, null, null, ComposerAutoloadData.Empty)
]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c =>
c.PackageName == "vendor/package" &&
c.ConflictType == PhpConflictType.UnstableVersion);
}
[Fact]
public void Analyze_ZeroVersion_DetectsUnstableApi()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["vendor/package"] = "^0.1"
});
var lockData = CreateLockData([
new ComposerPackage("vendor/package", "0.1.5", "library", false, null, null, null, null, ComposerAutoloadData.Empty)
]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c =>
c.PackageName == "vendor/package" &&
c.ConflictType == PhpConflictType.UnstableApi);
}
[Fact]
public void Analyze_AbandonedPackage_DetectsConflict()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["swiftmailer/swiftmailer"] = "^6.0"
});
var lockData = CreateLockData([
new ComposerPackage("swiftmailer/swiftmailer", "6.3.0", "library", false, null, null, null, null, ComposerAutoloadData.Empty)
]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
Assert.True(result.HasConflicts);
Assert.Contains(result.Conflicts, c =>
c.PackageName == "swiftmailer/swiftmailer" &&
c.ConflictType == PhpConflictType.AbandonedPackage &&
c.Message.Contains("symfony/mailer"));
}
[Fact]
public void Analyze_NoConflicts_ReturnsEmpty()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["php"] = "^8.0",
["symfony/console"] = "^6.0"
});
var lockData = CreateLockData([
new ComposerPackage("symfony/console", "6.4.0", "library", false, null, null, null, null, ComposerAutoloadData.Empty)
]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
// No conflicts for stable packages with matching constraints
var nonLowConflicts = result.Conflicts.Where(c => c.Severity != PhpConflictSeverity.Low);
Assert.Empty(nonLowConflicts);
}
[Fact]
public void GetConflict_ReturnsConflictForPackage()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["ext-mysql"] = "*"
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
var conflict = result.GetConflict("ext-mysql");
Assert.NotNull(conflict);
Assert.Equal("ext-mysql", conflict.PackageName);
}
[Fact]
public void GetBySeverity_FiltersCorrectly()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["ext-mcrypt"] = "*", // High severity
["php"] = "^5.6" // Medium severity
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
var high = result.GetBySeverity(PhpConflictSeverity.High).ToList();
var medium = result.GetBySeverity(PhpConflictSeverity.Medium).ToList();
Assert.NotEmpty(high);
Assert.NotEmpty(medium);
Assert.All(high, c => Assert.Equal(PhpConflictSeverity.High, c.Severity));
Assert.All(medium, c => Assert.Equal(PhpConflictSeverity.Medium, c.Severity));
}
[Fact]
public void PhpConflictAnalysis_CreateMetadata_IncludesAllFields()
{
var manifest = CreateManifest(new Dictionary<string, string>
{
["ext-mcrypt"] = "*",
["php"] = "^5.6"
});
var lockData = CreateLockData([]);
var result = PhpVersionConflictDetector.Analyze(manifest, lockData);
var metadata = result.CreateMetadata().ToDictionary(kv => kv.Key, kv => kv.Value);
Assert.Equal("true", metadata["conflict.detected"]);
Assert.True(int.Parse(metadata["conflict.count"]!) > 0);
Assert.NotNull(metadata["conflict.severity"]);
Assert.Contains("conflict.types", metadata.Keys);
Assert.Contains("conflict.packages", metadata.Keys);
}
[Fact]
public void PhpConflictAnalysis_Empty_HasNoConflicts()
{
var result = PhpConflictAnalysis.Empty;
Assert.False(result.HasConflicts);
Assert.Empty(result.Conflicts);
Assert.Null(result.HighestSeverity);
}
private static PhpComposerManifest CreateManifest(IReadOnlyDictionary<string, string> require)
{
return new PhpComposerManifest(
manifestPath: "composer.json",
name: "test/project",
description: null,
type: "project",
version: null,
license: "MIT",
authors: [],
require: require,
requireDev: new Dictionary<string, string>(),
autoload: ComposerAutoloadData.Empty,
autoloadDev: ComposerAutoloadData.Empty,
scripts: new Dictionary<string, string>(),
bin: new Dictionary<string, string>(),
minimumStability: null,
sha256: null);
}
private static ComposerLockData CreateLockData(IReadOnlyList<ComposerPackage> packages)
{
return new ComposerLockData(
lockPath: "composer.lock",
contentHash: null,
pluginApiVersion: null,
packages: packages,
devPackages: [],
lockSha256: null);
}
}

View File

@@ -5,7 +5,7 @@
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>