using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Scanner.Analyzers.OS.MacOsBundle; using Xunit; using StellaOps.TestKit; namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests; public sealed class MacOsBundleAnalyzerTests { private static readonly string FixturesRoot = Path.Combine( AppContext.BaseDirectory, "Fixtures"); private readonly MacOsBundleAnalyzer _analyzer; private readonly ILogger _logger; public MacOsBundleAnalyzerTests() { _logger = NullLoggerFactory.Instance.CreateLogger(); _analyzer = new MacOsBundleAnalyzer((ILogger)_logger); } private OSPackageAnalyzerContext CreateContext(string rootPath) { return new OSPackageAnalyzerContext( rootPath, workspacePath: null, TimeProvider.System, _logger); } [Trait("Category", TestCategories.Unit)] [Fact] public void AnalyzerId_ReturnsMacosBundleIdentifier() { Assert.Equal("macos-bundle", _analyzer.AnalyzerId); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_WithValidBundles_ReturnsPackages() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert Assert.NotNull(result); Assert.Equal("macos-bundle", result.AnalyzerId); Assert.True(result.Packages.Count > 0, "Expected at least one bundle"); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_FindsTestApp() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.Equal("1.2.3", testApp.Version); Assert.Equal("Test Application", testApp.Name); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_ExtractsVersionCorrectly() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); // ShortVersion takes precedence Assert.Equal("1.2.3", testApp.Version); // Build number goes to release Assert.Equal("123", testApp.Release); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_BuildsCorrectPurl() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.Contains("pkg:generic/macos-app/com.stellaops.testapp@1.2.3", testApp.PackageUrl); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_ExtractsVendorFromBundleId() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.Equal("stellaops", testApp.SourcePackage); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_SetsEvidenceSourceToMacOsBundle() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert foreach (var package in result.Packages) { Assert.Equal(PackageEvidenceSource.MacOsBundle, package.EvidenceSource); } } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_ExtractsVendorMetadata() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.Equal("com.stellaops.testapp", testApp.VendorMetadata["macos:bundle_id"]); Assert.Equal("APPL", testApp.VendorMetadata["macos:bundle_type"]); Assert.Equal("12.0", testApp.VendorMetadata["macos:min_os_version"]); Assert.Equal("TestApp", testApp.VendorMetadata["macos:executable"]); Assert.Equal("MacOSX", testApp.VendorMetadata["macos:platforms"]); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_IncludesCodeResourcesHash() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.True(testApp.VendorMetadata.ContainsKey("macos:code_resources_hash")); var hash = testApp.VendorMetadata["macos:code_resources_hash"]; Assert.StartsWith("sha256:", hash); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_DetectsSandboxedApp() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var sandboxedApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.sandboxed"); Assert.NotNull(sandboxedApp); Assert.Equal("true", sandboxedApp.VendorMetadata["macos:sandboxed"]); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_DetectsHighRiskEntitlements() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var sandboxedApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.sandboxed"); Assert.NotNull(sandboxedApp); Assert.True(sandboxedApp.VendorMetadata.ContainsKey("macos:high_risk_entitlements")); var highRisk = sandboxedApp.VendorMetadata["macos:high_risk_entitlements"]; // Full entitlement keys are stored Assert.Contains("com.apple.security.device.camera", highRisk); Assert.Contains("com.apple.security.device.microphone", highRisk); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_DetectsCapabilityCategories() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var sandboxedApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.sandboxed"); Assert.NotNull(sandboxedApp); Assert.True(sandboxedApp.VendorMetadata.ContainsKey("macos:capability_categories")); var categories = sandboxedApp.VendorMetadata["macos:capability_categories"]; Assert.Contains("network", categories); Assert.Contains("camera", categories); Assert.Contains("microphone", categories); Assert.Contains("filesystem", categories); Assert.Contains("sandbox", categories); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_IncludesFileEvidence() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert var testApp = result.Packages.FirstOrDefault(p => p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) && id == "com.stellaops.testapp"); Assert.NotNull(testApp); Assert.True(testApp.Files.Count > 0); var executable = testApp.Files.FirstOrDefault(f => f.Path.Contains("MacOS/TestApp")); Assert.NotNull(executable); Assert.False(executable.IsConfigFile); var infoPlist = testApp.Files.FirstOrDefault(f => f.Path.Contains("Info.plist")); Assert.NotNull(infoPlist); Assert.True(infoPlist.IsConfigFile); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_ResultsAreDeterministicallySorted() { // Arrange var context = CreateContext(FixturesRoot); // Act var result1 = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); var result2 = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert Assert.Equal(result1.Packages.Count, result2.Packages.Count); for (int i = 0; i < result1.Packages.Count; i++) { Assert.Equal(result1.Packages[i].PackageUrl, result2.Packages[i].PackageUrl); } } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_NoApplicationsDirectory_ReturnsEmptyPackages() { // Arrange - use temp directory without Applications var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempPath); try { var context = CreateContext(tempPath); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert Assert.Empty(result.Packages); } finally { Directory.Delete(tempPath, recursive: true); } } [Trait("Category", TestCategories.Unit)] [Fact] public async Task AnalyzeAsync_PopulatesTelemetry() { // Arrange var context = CreateContext(FixturesRoot); // Act var result = await _analyzer.AnalyzeAsync(context, TestContext.Current.CancellationToken); // Assert Assert.NotNull(result.Telemetry); Assert.True(result.Telemetry.PackageCount > 0); Assert.True(result.Telemetry.Duration > TimeSpan.Zero); } }