From 13a5b4f6531159c8eaf2c54121545b2a29e4e20c Mon Sep 17 00:00:00 2001 From: HeyItsGilbert Date: Wed, 29 Jul 2026 04:12:29 +0000 Subject: [PATCH] fix: clear error when rule Condition is null (#83) Test-FeatureFlag/Test-Condition threw an opaque 'You cannot call a method on a null-valued expression' error when a rule's Condition was $null (omitted or misspelled key). ConditionTransformAttribute called .GetType() on the null input before any of its type checks ran. ConditionTransformAttribute.Transform() now null-guards up front and throws a clear ArgumentNullException naming the problem: the rule must define a Condition. FeatureFlagTransformAttribute gained the same null guard for consistency (same GetType()-on-null pattern for a null -FeatureFlag). Added coverage: Test-Condition -Condition $null, and a Test-FeatureFlag rule with no Condition, both assert the new clear error message. --- CHANGELOG.md | 6 ++++++ Gatekeeper/Classes/FeatureFlag.ps1 | 8 ++++++++ tests/Classes.tests.ps1 | 6 ++++++ tests/Test-FeatureFlag.tests.ps1 | 12 ++++++++++++ 4 files changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c862b0..2c4be8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). bind and always threw). `-PropertySet` is now a mandatory parameter on `Test-FeatureFlag`, matching `Test-Condition`, so the missing value is reported with a clear mandatory-parameter error instead. (#84) +- `Test-Condition`/`Test-FeatureFlag` no longer throw an opaque + "You cannot call a method on a null-valued expression" error when a rule's + `Condition` is `$null` (e.g. omitted or misspelled). `ConditionTransformAttribute` + now null-guards and raises a clear `ArgumentNullException` explaining that the + rule must define a `Condition`. `FeatureFlagTransformAttribute` gained the same + guard for a null `FeatureFlag`. (#83) ## [1.0.0] 2026-06-05 diff --git a/Gatekeeper/Classes/FeatureFlag.ps1 b/Gatekeeper/Classes/FeatureFlag.ps1 index 7bc05de..c1e718d 100644 --- a/Gatekeeper/Classes/FeatureFlag.ps1 +++ b/Gatekeeper/Classes/FeatureFlag.ps1 @@ -236,6 +236,10 @@ class GatekeeperPath { class FeatureFlagTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute { [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object] $inputData) { + if ($null -eq $inputData) { + throw [System.ArgumentNullException]::new('FeatureFlag', + "FeatureFlag cannot be null.") + } if ($inputData -is [FeatureFlag]) { return $inputData } @@ -258,6 +262,10 @@ class FeatureFlagTransformAttribute : System.Management.Automation.ArgumentTrans class ConditionTransformAttribute : System.Management.Automation.ArgumentTransformationAttribute { [object] Transform([System.Management.Automation.EngineIntrinsics]$engineIntrinsics, [object] $inputData) { + if ($null -eq $inputData) { + throw [System.ArgumentNullException]::new('Condition', + "Rule has no Condition. Each rule must define a 'Condition'.") + } if ($inputData -is [Condition]) { return $inputData } diff --git a/tests/Classes.tests.ps1 b/tests/Classes.tests.ps1 index 0b4d4b8..6c3356d 100644 --- a/tests/Classes.tests.ps1 +++ b/tests/Classes.tests.ps1 @@ -216,6 +216,12 @@ Describe 'ConditionTransformAttribute' { Test-Condition -Condition 42 -PropertySet $script:propertySet -Context $script:context } | Should -Throw -ExpectedMessage '*Cannot convert type*' } + + It 'Throws a clear, actionable error when Condition is null' { + { + Test-Condition -Condition $null -PropertySet $script:propertySet -Context $script:context + } | Should -Throw -ExpectedMessage "*Rule has no Condition*" + } } Describe 'PropertySetTransformAttribute' { diff --git a/tests/Test-FeatureFlag.tests.ps1 b/tests/Test-FeatureFlag.tests.ps1 index 6696da1..12696bc 100644 --- a/tests/Test-FeatureFlag.tests.ps1 +++ b/tests/Test-FeatureFlag.tests.ps1 @@ -85,6 +85,18 @@ Describe 'Test-FeatureFlag' { } } + Context 'Malformed rules' { + It 'Rule with a null Condition throws a clear, actionable error' { + $flag = [FeatureFlag]::new(@{ + Name = 'MissingConditionFlag'; DefaultEffect = 'Deny'; Version = '1.0.0'; Author = 'Test' + Rules = @(@{ Name = 'Allow group'; Effect = 'Allow' }) + }) + { + Test-FeatureFlag -FeatureFlag $flag -PropertySet $script:propertySet -Context $script:context + } | Should -Throw -ExpectedMessage '*Rule has no Condition*' + } + } + Context 'Ordering — first-match-wins' { It 'Deny before matching Allow returns $false (Deny wins)' { $flag = [FeatureFlag]::new(@{