Tests/DependencyAndBinary.Tests.ps1

#Requires -Module Pester
<#
.SYNOPSIS
    Pester v5 tests for Get-LGProjectDependencies and Get-LGBinaryLicenseAudit modules.
#>


Describe 'Dependency and Binary Audit' {
    BeforeAll {
        $modulePath = Join-Path (Split-Path $PSScriptRoot) 'LicenseGuard.psm1'
        Import-Module $modulePath -Force -ErrorAction Stop
        Initialize-LicenseGuard -Language en

        # Create temporary workspace for tests
        $script:tempWorkspace = Join-Path ([System.IO.Path]::GetTempPath()) "LG_TestWorkspace_$([guid]::NewGuid().Guid)"
        New-Item -ItemType Directory -Path $script:tempWorkspace -Force | Out-Null
    }

    AfterAll {
        # Clean up temporary workspace
        Remove-Item $script:tempWorkspace -Recurse -Force -ErrorAction SilentlyContinue
        Remove-Module LicenseGuard -ErrorAction SilentlyContinue
    }

    Context 'Project Dependencies (SCA) Scanning' {
        It 'scans Node.js NPM project dependencies and extracts licenses' {
            $projDir = Join-Path $script:tempWorkspace "NodeProj"
            $expressDir = Join-Path $projDir "node_modules/express"
            New-Item -ItemType Directory -Path $expressDir -Force | Out-Null

            # Create root package.json
            @'
{
  "name": "nodeproj",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.18.2"
  }
}
'@
 | Out-File (Join-Path $projDir "package.json") -Encoding UTF8

            # Create express package.json
            @'
{
  "name": "express",
  "version": "4.18.2",
  "license": "MIT"
}
'@
 | Out-File (Join-Path $expressDir "package.json") -Encoding UTF8

            # Perform scan
            $deps = Get-LGProjectDependencies -ProjectPath $projDir

            $deps | Should -Not -BeNullOrEmpty
            $deps.Count | Should -Be 1
            $deps[0].Name | Should -Be "NodeProj: express"
            $deps[0].License | Should -Be "MIT"
            $deps[0].Publisher | Should -Be "NPM"
            $deps[0].Module | Should -Be "NPM"
            $deps[0].Status | Should -Be "WARN"
            $deps[0].HasLicense | Should -Be $false  # No physical LICENSE file was created
        }

        It 'scans .NET NuGet dependencies from csproj and parses nuspec cache' {
            $projDir = Join-Path $script:tempWorkspace "NetProj"
            New-Item -ItemType Directory -Path $projDir -Force | Out-Null

            # Create csproj file
            @'
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>
</Project>
'@
 | Out-File (Join-Path $projDir "NetProj.csproj") -Encoding UTF8

            # Mock local NuGet cache for Newtonsoft.Json
            $mockNuGetCache = Join-Path $script:tempWorkspace "mock_nuget"
            $nuspecDir = Join-Path $mockNuGetCache "newtonsoft.json/13.0.3"
            New-Item -ItemType Directory -Path $nuspecDir -Force | Out-Null

            @'
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>Newtonsoft.Json</id>
    <version>13.0.3</version>
    <license type="expression">MIT</license>
  </metadata>
</package>
'@
 | Out-File (Join-Path $nuspecDir "newtonsoft.json.nuspec") -Encoding UTF8

            # Mock the nuget cache path internally by changing the environment variable or mocking Get-LGProjectDependencies
            # Since Get-LGProjectDependencies uses $env:USERPROFILE\.nuget\packages, we can temporarily redirect it in our test
            $oldProfile = $env:USERPROFILE
            $env:USERPROFILE = $script:tempWorkspace

            # Copy mock_nuget contents to fake USERPROFILE\.nuget\packages
            $fakeNugetFolder = Join-Path $script:tempWorkspace ".nuget/packages"
            New-Item -ItemType Directory -Path $fakeNugetFolder -Force | Out-Null
            Copy-Item -Path $mockNuGetCache/* -Destination $fakeNugetFolder -Recurse -Force -ErrorAction SilentlyContinue

            try {
                $deps = Get-LGProjectDependencies -ProjectPath $projDir

                $deps | Should -Not -BeNullOrEmpty
                $deps.Count | Should -Be 1
                $deps[0].Name | Should -Be "NetProj: Newtonsoft.Json"
                $deps[0].License | Should -Be "MIT"
                $deps[0].Publisher | Should -Be "NuGet"
                $deps[0].Module | Should -Be "NuGet"
                $deps[0].Status | Should -Be "WARN"
            }
            finally {
                $env:USERPROFILE = $oldProfile
            }
        }
    }

    Context 'Binary License Auditing' {
        It 'audits compiled PE binary metadata, .deps.json and SBOM files' {
            $binDir = Join-Path $script:tempWorkspace "BinOutput"
            New-Item -ItemType Directory -Path $binDir -Force | Out-Null

            # Create dummy EXE
            "MZ Dummy EXE" | Out-File (Join-Path $binDir "App.exe") -Encoding ASCII

            # Create .deps.json file
            @'
{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v8.0"
  },
  "targets": {
    ".NETCoreApp,Version=v8.0": {
      "Newtonsoft.Json/13.0.3": {
        "type": "package"
      }
    }
  }
}
'@
 | Out-File (Join-Path $binDir "App.deps.json") -Encoding UTF8

            # Create CycloneDX SBOM
            @'
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "serialNumber": "urn:uuid:3e671687-397b-437f-a30b-ac4678747dfd",
  "version": 1,
  "components": [
    {
      "type": "library",
      "name": "log4net",
      "version": "2.0.15",
      "licenses": [
        {
          "license": {
            "id": "Apache-2.0"
          }
        }
      ]
    }
  ]
}
'@
 | Out-File (Join-Path $binDir "bom.json") -Encoding UTF8

            # Mock local NuGet cache for App.deps.json resolution
            $oldProfile = $env:USERPROFILE
            $env:USERPROFILE = $script:tempWorkspace
            $nuspecDir = Join-Path $script:tempWorkspace ".nuget/packages/newtonsoft.json/13.0.3"
            New-Item -ItemType Directory -Path $nuspecDir -Force | Out-Null
            @'
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
  <metadata>
    <id>Newtonsoft.Json</id>
    <version>13.0.3</version>
    <license type="expression">MIT</license>
  </metadata>
</package>
'@
 | Out-File (Join-Path $nuspecDir "newtonsoft.json.nuspec") -Encoding UTF8

            try {
                $audit = Get-LGBinaryLicenseAudit -Path $binDir

                $audit | Should -Not -BeNullOrEmpty
                # Should have 3 results: 1 for the EXE itself, 1 from App.deps.json (Newtonsoft.Json), 1 from SBOM bom.json (log4net)
                $audit.Count | Should -Be 3

                $exeResult = $audit | Where-Object { $_.Name -eq "Binary: App.exe" }
                $exeResult | Should -Not -BeNullOrEmpty
                $exeResult.Module | Should -Be "BinaryAudit"
                $exeResult.Status | Should -Be "WARN"
                $exeResult.Detail | Should -Match "\[MISSING LICENSE/ATTRIBUTION FILE\]"

                $depResult = $audit | Where-Object { $_.Name -eq "Binary Dependency (App.exe): Newtonsoft.Json" }
                $depResult | Should -Not -BeNullOrEmpty
                $depResult.Module | Should -Be "BinaryAudit"
                $depResult.License | Should -Be "MIT"
                $depResult.Status | Should -Be "WARN"

                $sbomResult = $audit | Where-Object { $_.Name -eq "SBOM Dependency (bom.json): log4net" }
                $sbomResult | Should -Not -BeNullOrEmpty
                $sbomResult.Module | Should -Be "BinaryAudit"
                $sbomResult.License | Should -Be "Apache-2.0"
                $sbomResult.Status | Should -Be "OK"
            }
            finally {
                $env:USERPROFILE = $oldProfile
            }
        }

        It 'prefers SBOM package license rows over duplicate .deps.json package rows' {
            $binDir = Join-Path $script:tempWorkspace "BinOutputWithSbomOverlap"
            New-Item -ItemType Directory -Path $binDir -Force | Out-Null

            "MZ Dummy EXE" | Out-File (Join-Path $binDir "App.exe") -Encoding ASCII
            @'
{
  "targets": {
    ".NETCoreApp,Version=v8.0": {
      "GplLibrary/1.0.0": {
        "type": "package"
      }
    }
  }
}
'@
 | Out-File (Join-Path $binDir "App.deps.json") -Encoding UTF8

            @'
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.4",
  "version": 1,
  "components": [
    {
      "type": "library",
      "name": "GplLibrary",
      "version": "1.0.0",
      "licenses": [
        {
          "license": {
            "id": "GPL-3.0"
          }
        }
      ]
    }
  ]
}
'@
 | Out-File (Join-Path $binDir "bom.json") -Encoding UTF8

            $audit = Get-LGBinaryLicenseAudit -Path $binDir

            @($audit | Where-Object { $_.Name -eq "Binary Dependency (App.exe): GplLibrary" }).Count | Should -Be 0
            $sbomResult = $audit | Where-Object { $_.Name -eq "SBOM Dependency (bom.json): GplLibrary" }
            $sbomResult | Should -Not -BeNullOrEmpty
            $sbomResult.License | Should -Be "GPL-3.0"
            $sbomResult.Status | Should -Be "EXPIRED"
        }

        It 'audits Node.js build artifacts for bundled license hints and missing attribution files' {
            $distDir = Join-Path $script:tempWorkspace "NodeBuild/dist"
            New-Item -ItemType Directory -Path $distDir -Force | Out-Null

            @'
/*! bad-lib v1.0.0 | GPL-3.0 */
/*! express v4.18.2 | MIT */
function startDummyApp() {
  return "dummy node build output";
}
'@
 | Out-File (Join-Path $distDir "app.bundle.js") -Encoding UTF8

            $audit = Get-LGBinaryLicenseAudit -Path $distDir
            $artifact = $audit | Where-Object { $_.Name -eq "Build Artifact: app.bundle.js" }

            $artifact | Should -Not -BeNullOrEmpty
            $artifact.Module | Should -Be "BinaryAudit"
            $artifact.Publisher | Should -Be "BuildOutput"
            $artifact.License | Should -Be "GPL"
            $artifact.Status | Should -Be "EXPIRED"
            $artifact.Detail | Should -Match "\[MISSING LICENSE/ATTRIBUTION FILE\]"

            $policyJson = @'
{
  "rules": [
    {
      "id": "LIB-GPL",
      "category": "Library License",
      "matchField": "license",
      "pattern": "GPL|AGPL",
      "matchType": "regex",
      "status": "PROHIBITED",
      "reason": "Copyleft license found in build artifact",
      "severity": "HIGH",
      "alternative": "Use MIT or Apache alternatives"
    }
  ]
}
'@

            $policyPath = Join-Path $script:tempWorkspace "build-policy.json"
            $policyJson | Out-File $policyPath -Encoding UTF8

            $findings = Invoke-LGPolicyCheck -PolicyPath $policyPath -SoftwareCache $audit
            $hit = $findings | Where-Object { $_.Name -eq "Build Artifact: app.bundle.js" }

            $hit | Should -Not -BeNullOrEmpty
            $hit.PolicyStatus | Should -Be "PROHIBITED"
            $hit.Status | Should -Be "EXPIRED"
        }
    }

    Context 'HTML reporting' {
        It 'renders project license findings and project modules in the HTML report' {
            $out = Join-Path $script:tempWorkspace "license-report.html"
            $allResults = @(
                [PSCustomObject]@{
                    Module = "NPM"; Name = "dummy-node-project: bad-lib"; Version = "1.0.0"; Publisher = "NPM"
                    License = "GPL-3.0"; Detail = "Package License: GPL-3.0 (Project: dummy-node-project)"
                    Status = "OK"; ComputerName = $env:COMPUTERNAME; HasLicense = $false
                },
                [PSCustomObject]@{
                    Module = "NPM"; Name = "dummy-node-project: express"; Version = "4.18.2"; Publisher = "NPM"
                    License = "MIT"; Detail = "Package License: MIT (Project: dummy-node-project)"
                    Status = "OK"; ComputerName = $env:COMPUTERNAME; HasLicense = $false
                },
                [PSCustomObject]@{
                    Module = "NPM"; Name = "dummy-node-project: unknown-lib"; Version = "2.1.0"; Publisher = "NPM"
                    License = "Unknown"; Detail = "Package License: Unknown (Project: dummy-node-project)"
                    Status = "OK"; ComputerName = $env:COMPUTERNAME; HasLicense = $false
                }
            )
            $policyFindings = @(
                [PSCustomObject]@{
                    Module = "PolicyCheck"; RuleId = "LIB-002"; Category = "Kütüphane Lisansı"
                    Name = "dummy-node-project: bad-lib"; Version = "1.0.0"; Publisher = "NPM"
                    PolicyStatus = "PROHIBITED"; Status = "EXPIRED"; Detail = "GPL/AGPL blocked | Package License: GPL-3.0"
                    Alternative = "Use MIT or Apache alternatives"; Reference = ""; Severity = "HIGH"
                },
                [PSCustomObject]@{
                    Module = "PolicyCheck"; RuleId = "LIB-001"; Category = "Kütüphane Lisansı"
                    Name = "dummy-node-project: express"; Version = "4.18.2"; Publisher = "NPM"
                    PolicyStatus = "ALLOWED"; Status = "OK"; Detail = "Permissive license | Package License: MIT"
                    Alternative = ""; Reference = ""; Severity = "LOW"
                },
                [PSCustomObject]@{
                    Module = "PolicyCheck"; RuleId = "LIB-003"; Category = "Kütüphane Lisansı"
                    Name = "dummy-node-project: unknown-lib"; Version = "2.1.0"; Publisher = "NPM"
                    PolicyStatus = "REQUIRES_LICENSE"; Status = "WARN"; Detail = "Unknown license"
                    Alternative = "Check package source"; Reference = ""; Severity = "MEDIUM"
                }
            )

            Export-LGHtmlReport -AllResults $allResults -PolicyFindings $policyFindings -OutputPath $out -Language tr

            $html = Get-Content $out -Raw
            $html | Should -Match "dummy-node-project: bad-lib"
            $html | Should -Match "GPL-3.0"
            $html | Should -Match "YASAK"
            $html | Should -Match "dummy-node-project: express"
            $html | Should -Match "MIT"
            $html | Should -Match "dummy-node-project: unknown-lib"
            $html | Should -Match "LISANS GEREKLI"
            $html | Should -Match "data-mod='NPM'"
            $html | Should -Match "filterMod\('NPM'"
            $html | Should -Match "healthTotal=lt\+pt"
        }
    }
}