tests/Measure-MsecDefenderTriageAgreement.Tests.ps1
|
BeforeAll { Import-Module (Join-Path $PSScriptRoot '..' 'Msec.psd1') -Force } Describe 'Measure-MsecDefenderTriageAgreement' { BeforeAll { # Deliberately imbalanced the way a real triage set is: mostly false positives, a few # true ones. That imbalance is what makes a single accuracy figure useless. $script:Alerts = @( [pscustomobject]@{ Id = '1'; Title = 'A'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '2'; Title = 'B'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '3'; Title = 'C'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '4'; Title = 'D'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '5'; Title = 'E'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '6'; Title = 'F'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '7'; Title = 'G'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '8'; Title = 'H'; Classification = 'falsePositive' } [pscustomobject]@{ Id = '9'; Title = 'I'; Classification = 'truePositive' } [pscustomobject]@{ Id = '10'; Title = 'J'; Classification = 'unknown' } ) } It 'does not let an all-falsePositive proposal look good' { # THE POINT OF THE COMMAND. Answering 'falsePositive' every time agrees with 8 of 9 # labelled alerts - 89% - while catching none of the real detections. The headline # number flatters it; TruePositiveRecall does not. $v = @{} 1..9 | ForEach-Object { $v["$_"] = 'falsePositive' } $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts -Verdict $v $r.Agreed | Should -Be 8 $r.Scored | Should -Be 9 $r.TruePositiveRecall | Should -Be 0 } It 'reports TruePositiveRecall as the proportion of real detections caught' { $v = @{ '1' = 'falsePositive'; '9' = 'truePositive' } $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts -Verdict $v $r.TruePositiveRecall | Should -Be 1 ($r.PerClass | Where-Object Class -eq 'truePositive').Actual | Should -Be 1 } It 'excludes unlabelled alerts from scoring rather than counting them as disagreements' { # Classification 'unknown' means nobody judged it. Scoring it as a miss would punish a # proposal for disagreeing with a verdict that was never given. $v = @{} 1..10 | ForEach-Object { $v["$_"] = 'falsePositive' } $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts -Verdict $v $r.Scored | Should -Be 9 $r.Unlabelled | Should -Be 1 $r.Detail.Id | Should -Not -Contain '10' } It 'counts labelled alerts the proposal declined to answer' { # A proposal that answers two alerts out of nine and gets both right is not 100% # accurate, and coverage is the only thing that shows it. $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts ` -Verdict @{ '1' = 'falsePositive'; '9' = 'truePositive' } $r.Scored | Should -Be 2 $r.LabelledNoVerdict | Should -Be 7 } It 'counts verdicts that match no alert in the set' { $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts ` -Verdict @{ '1' = 'falsePositive'; 'stale-id' = 'truePositive' } $r.OrphanVerdicts | Should -Be 1 } It 'reports the shape of each disagreement, commonest first' { # Where a proposal is wrong is more actionable than how often. $v = @{ '1' = 'truePositive'; '2' = 'truePositive'; '9' = 'falsePositive' } $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts -Verdict $v $r.Confusion[0].Shape | Should -Be 'falsePositive -> truePositive' $r.Confusion[0].Count | Should -Be 2 ($r.Confusion | Where-Object Shape -eq 'truePositive -> falsePositive').Count | Should -Be 1 } It 'returns null TruePositiveRecall when the set contains no true positives' { # Not a pass - no evidence either way, and it must not read as 1. $fpOnly = @($script:Alerts | Where-Object Classification -eq 'falsePositive') $v = @{}; $fpOnly | ForEach-Object { $v[$_.Id] = 'falsePositive' } $r = Measure-MsecDefenderTriageAgreement -Alert $fpOnly -Verdict $v $r.TruePositiveRecall | Should -BeNullOrEmpty $r.Agreed | Should -Be 8 } It 'accepts alerts from the pipeline' { $v = @{ '9' = 'truePositive' } $r = $script:Alerts | Measure-MsecDefenderTriageAgreement -Verdict $v $r.Scored | Should -Be 1 $r.TruePositiveRecall | Should -Be 1 } It 'emits the documented type' { $r = Measure-MsecDefenderTriageAgreement -Alert $script:Alerts -Verdict @{ '1' = 'falsePositive' } $r.PSObject.TypeNames | Should -Contain 'MsecDefenderTriageAgreement' } } |