feat(m9/T28a): strict expression-trigger analysis kind (advisory default, strict escalates)
This commit is contained in:
+126
@@ -336,4 +336,130 @@ public class ValidationServiceTests
|
||||
var result = _sut.Validate(config);
|
||||
Assert.Contains(result.Warnings, w => w.Category == ValidationCategory.FlatteningFailure);
|
||||
}
|
||||
|
||||
// --- M9-T28a: per-trigger AnalysisKind (Advisory default | Strict escalates) ---
|
||||
// The only currently-advisory finding in CheckExpressionTrigger is the blank/empty
|
||||
// expression (which "will never fire"). Advisory keeps it a non-blocking warning;
|
||||
// Strict promotes it to a deploy-blocking error. The kind rides the existing
|
||||
// trigger-config JSON ({"expression":"...","analysisKind":"Strict"}) — no migration.
|
||||
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_BlankExpression_AdvisoryDefault_WarnsButPasses()
|
||||
{
|
||||
// No analysisKind in the config → Advisory (today's behavior): a blank expression
|
||||
// is a non-blocking warning, validation still passes.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Instance1",
|
||||
Alarms =
|
||||
[
|
||||
new ResolvedAlarm
|
||||
{
|
||||
CanonicalName = "BlankAlarm",
|
||||
TriggerType = "Expression",
|
||||
TriggerConfiguration = "{\"expression\":\"\"}"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _sut.Validate(config);
|
||||
Assert.Contains(result.Warnings, w => w.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.DoesNotContain(result.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.True(result.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_BlankExpression_StrictKind_FailsWithError()
|
||||
{
|
||||
// analysisKind:"Strict" promotes the blank-expression advisory to a deploy-blocking error.
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Instance1",
|
||||
Alarms =
|
||||
[
|
||||
new ResolvedAlarm
|
||||
{
|
||||
CanonicalName = "BlankAlarm",
|
||||
TriggerType = "Expression",
|
||||
TriggerConfiguration = "{\"expression\":\"\",\"analysisKind\":\"Strict\"}"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _sut.Validate(config);
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.DoesNotContain(result.Warnings, w => w.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ScriptExpressionTrigger_BlankExpression_StrictKind_FailsWithError()
|
||||
{
|
||||
// Strict escalation also applies to script expression triggers (mirrors the alarm path).
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Instance1",
|
||||
Scripts =
|
||||
[
|
||||
new ResolvedScript
|
||||
{
|
||||
CanonicalName = "BlankScript",
|
||||
Code = "var x = 1;",
|
||||
TriggerType = "Expression",
|
||||
TriggerConfiguration = "{\"expression\":\" \",\"analysisKind\":\"Strict\"}"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = _sut.Validate(config);
|
||||
Assert.Contains(result.Errors, e => e.Category == ValidationCategory.ScriptTriggerReference);
|
||||
Assert.False(result.IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ExpressionTrigger_ValidExpression_PassesUnderBothKinds()
|
||||
{
|
||||
// A genuinely valid expression must pass clean under Advisory AND Strict —
|
||||
// Strict only escalates the currently-advisory findings, it does not invent new ones.
|
||||
var attributes = new[]
|
||||
{
|
||||
new ResolvedAttribute { CanonicalName = "Temp", Value = "25", DataType = "Double" }
|
||||
};
|
||||
|
||||
var advisory = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = "Instance1",
|
||||
Attributes = attributes,
|
||||
Alarms =
|
||||
[
|
||||
new ResolvedAlarm
|
||||
{
|
||||
CanonicalName = "HighTemp",
|
||||
TriggerType = "Expression",
|
||||
TriggerConfiguration = "{\"expression\":\"(double)Attributes[\\\"Temp\\\"] > 50\"}"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var strict = advisory with
|
||||
{
|
||||
Alarms =
|
||||
[
|
||||
new ResolvedAlarm
|
||||
{
|
||||
CanonicalName = "HighTemp",
|
||||
TriggerType = "Expression",
|
||||
TriggerConfiguration = "{\"expression\":\"(double)Attributes[\\\"Temp\\\"] > 50\",\"analysisKind\":\"Strict\"}"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var advisoryResult = _sut.Validate(advisory);
|
||||
var strictResult = _sut.Validate(strict);
|
||||
|
||||
Assert.DoesNotContain(advisoryResult.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.DoesNotContain(advisoryResult.Warnings, w => w.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.DoesNotContain(strictResult.Errors, e => e.Category == ValidationCategory.AlarmTriggerReference);
|
||||
Assert.DoesNotContain(strictResult.Warnings, w => w.Category == ValidationCategory.AlarmTriggerReference);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user