diff --git a/.gitattributes b/.gitattributes index a610fb23c..fa04ea09e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,6 +20,11 @@ src/Sarif/Taxonomies/CweTaxonomy.brief.md text eol=lf # Pinned: this resource is embedded and hashed; line-ending drift would change the hash. src/Test.UnitTests.Sarif/TestData/InsertOptionalDataVisitor/InsertOptionalDataVisitor.txt text eol=crlf +# Pinned: rule message strings are compiled into embedded resources and reproduced +# verbatim in functional-test baselines; LF drift would rewrite every multi-line +# message and break those baselines. +src/Sarif.Multitool.Library/Rules/RuleResources.resx text eol=crlf + ############################################################################### # Set default behavior for command prompt diff. # diff --git a/ReleaseHistory.md b/ReleaseHistory.md index 52a092201..e6df9c3e2 100644 --- a/ReleaseHistory.md +++ b/ReleaseHistory.md @@ -13,6 +13,9 @@ Each release entry below is prefixed with one of: Entries are terse by design: one line per change, present-tense behavior, complete but only essential data. No issue/PR archaeology or narrative — that history lives in the engineering system. +## **v5.4.3** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/v5.4.3) | [Driver](https://www.nuget.org/packages/Sarif.Driver/v5.4.3) | [Converters](https://www.nuget.org/packages/Sarif.Converters/v5.4.3) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/v5.4.3) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/v5.4.3) +* BUG: `GHAzDO1019.ProvidePipelineProperties`, the `emit-run` pipeline-context producer, and the `ai-sarif-log.schema.json` build contract accept `-1` as the buildDefinitionId sentinel for runs with no saved definition, still rejecting `0` and other negatives. + ## **v5.4.2** [Sdk](https://www.nuget.org/packages/Sarif.Sdk/v5.4.2) | [Driver](https://www.nuget.org/packages/Sarif.Driver/v5.4.2) | [Converters](https://www.nuget.org/packages/Sarif.Converters/v5.4.2) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/v5.4.2) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/v5.4.2) * BUG: `SARIF2012.ProvideRuleProperties` exempts `NOVEL-` rules from the missing-`helpUri` note; a novel finding has no catalog topic to cite. * BUG: `emit-invocations` stops stamping a receipt-time `endTimeUtc` default and rejects any `startTimeUtc`, `endTimeUtc`, or notification `timeUtc` more than five minutes past the emit clock. diff --git a/src/Sarif.Multitool.Library/Emit/AdoPipelineContext.cs b/src/Sarif.Multitool.Library/Emit/AdoPipelineContext.cs index 6a2a18bb3..ac5dcd1ca 100644 --- a/src/Sarif.Multitool.Library/Emit/AdoPipelineContext.cs +++ b/src/Sarif.Multitool.Library/Emit/AdoPipelineContext.cs @@ -163,7 +163,7 @@ public static DetectionState TryDetect( } int buildDefinitionIdValue = 0; - if (TryParseRequiredPositiveInt(buildDefIdPrimary, buildDefIdFallback, BuildDefinitionIdPrimaryEnvVar, BuildDefinitionIdFallbackEnvVar, present, problems, out int parsedBuildDefId)) + if (TryParseRequiredPositiveInt(buildDefIdPrimary, buildDefIdFallback, BuildDefinitionIdPrimaryEnvVar, BuildDefinitionIdFallbackEnvVar, present, problems, allowNegativeOneSentinel: true, out int parsedBuildDefId)) { buildDefinitionIdValue = parsedBuildDefId; } @@ -175,7 +175,7 @@ public static DetectionState TryDetect( } int buildIdValue = 0; - if (TryParseRequiredPositiveInt(buildId, null, BuildIdEnvVar, null, present, problems, out int parsedBuildId)) + if (TryParseRequiredPositiveInt(buildId, null, BuildIdEnvVar, null, present, problems, allowNegativeOneSentinel: false, out int parsedBuildId)) { buildIdValue = parsedBuildId; } @@ -525,7 +525,7 @@ private static bool TryParseRequiredGuid(string raw, string envName, List present, List problems, out int value) + private static bool TryParseRequiredPositiveInt(string primaryRaw, string fallbackRaw, string primaryEnvName, string fallbackEnvName, List present, List problems, bool allowNegativeOneSentinel, out int value) { value = 0; @@ -547,9 +547,18 @@ private static bool TryParseRequiredPositiveInt(string primaryRaw, string fallba return false; } - if (parsed <= 0) + // A valid build definition id is any positive integer, or the -1 sentinel ADO uses + // for runs not associated with a saved definition. Zero and any other negative value + // are rejected. Other pipeline ids (e.g. buildId) remain strictly positive. + if (parsed <= 0 && !(allowNegativeOneSentinel && parsed == -1)) { - problems.Add(string.Format(CultureInfo.InvariantCulture, "{0}='{1}' must be a positive integer", sourceEnv, raw)); + problems.Add(string.Format( + CultureInfo.InvariantCulture, + allowNegativeOneSentinel + ? "{0}='{1}' must be a positive integer or the -1 sentinel" + : "{0}='{1}' must be a positive integer", + sourceEnv, + raw)); return false; } diff --git a/src/Sarif.Multitool.Library/GetSchema/ai-sarif-log.schema.json b/src/Sarif.Multitool.Library/GetSchema/ai-sarif-log.schema.json index 9979ff87f..fbf6a1de8 100644 --- a/src/Sarif.Multitool.Library/GetSchema/ai-sarif-log.schema.json +++ b/src/Sarif.Multitool.Library/GetSchema/ai-sarif-log.schema.json @@ -298,9 +298,9 @@ ], "properties": { "azuredevops/pipeline/build/buildDefinitionId": { - "description": "Integer != 0, serialized as a string.", + "description": "Positive integer, or the -1 sentinel, serialized as a string.", "type": "string", - "pattern": "^-?(?!0+$)[0-9]+$" + "pattern": "^(-1|[1-9][0-9]*)$" }, "azuredevops/pipeline/build/buildDefinitionName": { "description": "Non-empty, non-whitespace string.", diff --git a/src/Sarif.Multitool.Library/Rules/GHAzDO1019.ProvidePipelineProperties.cs b/src/Sarif.Multitool.Library/Rules/GHAzDO1019.ProvidePipelineProperties.cs index 6793d5741..747d2cb74 100644 --- a/src/Sarif.Multitool.Library/Rules/GHAzDO1019.ProvidePipelineProperties.cs +++ b/src/Sarif.Multitool.Library/Rules/GHAzDO1019.ProvidePipelineProperties.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Multitool.Rules /// ingestion to drop the run with "SarifValidation_MissingAdoPipelineProperties". /// /// Required keys (all under run.automationDetails.properties): - /// azuredevops/pipeline/build/buildDefinitionId (int, != 0) + /// azuredevops/pipeline/build/buildDefinitionId (int; positive or the -1 sentinel) /// azuredevops/pipeline/build/buildDefinitionName (non-empty string) /// azuredevops/pipeline/build/phaseId (GUID, != Guid.Empty) /// azuredevops/pipeline/build/phaseName (non-empty string) @@ -77,7 +77,8 @@ protected override void Analyze(Run run, string runPointer) nameof(RuleResources.GHAzDO1019_ProvidePipelineProperties_Error_MissingBuildDefinitionId_Text), BuildDefinitionIdKey); } - else if (!int.TryParse(buildDefinitionIdValue, out int buildDefinitionId) || buildDefinitionId == 0) + else if (!int.TryParse(buildDefinitionIdValue, out int buildDefinitionId) + || (buildDefinitionId <= 0 && buildDefinitionId != -1)) { LogResult( automationDetailsPointer, diff --git a/src/Sarif.Multitool.Library/Rules/RuleResources.resx b/src/Sarif.Multitool.Library/Rules/RuleResources.resx index 1277ace1e..3f3bd2399 100644 --- a/src/Sarif.Multitool.Library/Rules/RuleResources.resx +++ b/src/Sarif.Multitool.Library/Rules/RuleResources.resx @@ -639,7 +639,7 @@ Required keys on 'run.automationDetails.properties': {0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition. - {0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id. + {0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition. {0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name. diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1001.ValidateWithBaseline.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1001.ValidateWithBaseline.sarif index 34f788a6a..5d0213b54 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1001.ValidateWithBaseline.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1001.ValidateWithBaseline.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1002.ValidateBaseline.NoResults.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1002.ValidateBaseline.NoResults.sarif index 34f788a6a..5d0213b54 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1002.ValidateBaseline.NoResults.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1002.ValidateBaseline.NoResults.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1003.ValidateBaseline.AbsentResults.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1003.ValidateBaseline.AbsentResults.sarif index 34f788a6a..5d0213b54 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1003.ValidateBaseline.AbsentResults.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1003.ValidateBaseline.AbsentResults.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1004.ValidateBaseline.NewResults.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1004.ValidateBaseline.NewResults.sarif index 633ec68e0..195cefca2 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1004.ValidateBaseline.NewResults.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1004.ValidateBaseline.NewResults.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1005.ValidateBaseline.UnchangedResults.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1005.ValidateBaseline.UnchangedResults.sarif index 633ec68e0..195cefca2 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1005.ValidateBaseline.UnchangedResults.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1005.ValidateBaseline.UnchangedResults.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1006.ValidateBaseline.UpdatedResults.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1006.ValidateBaseline.UpdatedResults.sarif index ccfe880ae..d677686dd 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1006.ValidateBaseline.UpdatedResults.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1006.ValidateBaseline.UpdatedResults.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1007.ValidateBaseline.LessResultsThanBaseline.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1007.ValidateBaseline.LessResultsThanBaseline.sarif index f8b6cb19e..0fe2b7007 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1007.ValidateBaseline.LessResultsThanBaseline.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1007.ValidateBaseline.LessResultsThanBaseline.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1008.ValidateBaseline.InlineUpdate.sarif b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1008.ValidateBaseline.InlineUpdate.sarif index 24bc4b1b4..65bdf4c8f 100644 --- a/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1008.ValidateBaseline.InlineUpdate.sarif +++ b/src/Test.FunctionalTests.Sarif/TestData/Multitool/BaselineOption/ExpectedOutputs/TEST1008.ValidateBaseline.InlineUpdate.sarif @@ -290,7 +290,7 @@ "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition." }, "Error_InvalidBuildDefinitionId": { - "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a non-zero integer. The {1} service requires a non-zero integer build definition id." + "text": "{0}: The 'automationDetails.properties' value for '{2}' is '{3}', which is not a positive integer or the -1 sentinel. The {1} service requires a positive build definition id, or the -1 sentinel for runs not associated with a saved definition." }, "Error_MissingBuildDefinitionName": { "text": "{0}: The 'automationDetails.properties' bag does not provide '{2}'. This property is required by the {1} service to identify the build definition by name." diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/Emit/AdoPipelineContextTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/Emit/AdoPipelineContextTests.cs index 6d39eeba7..890035df5 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/Emit/AdoPipelineContextTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/Emit/AdoPipelineContextTests.cs @@ -94,7 +94,7 @@ public void TryDetect_OneRequiredVarMissing_ReturnsPartialAndNamesIt() [Fact] public void TryDetect_RejectsMalformedOrZeroBuildDefinitionId() { - // Both non-integer values and the literal "0" must fail the positive-int guard + // Both non-integer values and the literal "0" must fail the build-definition-id guard // (zero is invalid per GHAzDO1019). FakeEnvironmentVariableGetter badInt = CompleteEnv(); badInt.With(AdoPipelineContext.BuildDefinitionIdPrimaryEnvVar, "abc"); @@ -110,6 +110,46 @@ public void TryDetect_RejectsMalformedOrZeroBuildDefinitionId() err2.Should().Contain("positive"); } + [Fact] + public void TryDetect_AcceptsNegativeOneSentinelBuildDefinitionId() + { + // -1 is ADO's sentinel for a run not associated with a saved build definition. + FakeEnvironmentVariableGetter env = CompleteEnv(); + env.With(AdoPipelineContext.BuildDefinitionIdPrimaryEnvVar, "-1"); + env.With(AdoPipelineContext.BuildDefinitionIdFallbackEnvVar, "-1"); + + AdoPipelineContext.TryDetect(env, out AdoPipelineContext ctx, out string error) + .Should().Be(AdoPipelineContext.DetectionState.Complete); + error.Should().BeNull(); + ctx.BuildDefinitionId.Should().Be(-1); + } + + [Fact] + public void TryDetect_RejectsNegativeNonSentinelBuildDefinitionId() + { + FakeEnvironmentVariableGetter env = CompleteEnv(); + env.With(AdoPipelineContext.BuildDefinitionIdPrimaryEnvVar, "-2"); + env.With(AdoPipelineContext.BuildDefinitionIdFallbackEnvVar, "-2"); + + AdoPipelineContext.TryDetect(env, out _, out string error) + .Should().Be(AdoPipelineContext.DetectionState.Partial); + error.Should().Contain(AdoPipelineContext.BuildDefinitionIdPrimaryEnvVar); + error.Should().Contain("-1 sentinel"); + } + + [Fact] + public void TryDetect_RejectsNegativeOneBuildId() + { + // The -1 sentinel allowance is specific to buildDefinitionId; buildId stays strictly positive. + FakeEnvironmentVariableGetter env = CompleteEnv(); + env.With(AdoPipelineContext.BuildIdEnvVar, "-1"); + + AdoPipelineContext.TryDetect(env, out _, out string error) + .Should().Be(AdoPipelineContext.DetectionState.Partial); + error.Should().Contain(AdoPipelineContext.BuildIdEnvVar); + error.Should().Contain("positive"); + } + [Fact] public void TryDetect_RejectsMalformedOrEmptyTeamProjectId() { diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/GetSchema/AILogSchemaDriftTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/GetSchema/AILogSchemaDriftTests.cs index b3533594d..924683ab2 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/GetSchema/AILogSchemaDriftTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/GetSchema/AILogSchemaDriftTests.cs @@ -386,11 +386,21 @@ void Expect(string label, JsonObject automationDetails, bool accept) ((JsonObject)missingProp["properties"]).Remove("azuredevops/pipeline/build/phaseName"); Expect("pipeline-missing-phaseName", missingProp, false); - // buildDefinitionId == "0": GHAzDO1019 rejects (int != 0). + // buildDefinitionId == "0": GHAzDO1019 rejects (positive or -1 sentinel only). JsonObject zeroId = CompliantPipeline(); zeroId["properties"]["azuredevops/pipeline/build/buildDefinitionId"] = "0"; Expect("pipeline-zero-buildDefinitionId", zeroId, false); + // buildDefinitionId == "-1": accepted (ADO sentinel for runs with no saved definition). + JsonObject sentinelId = CompliantPipeline(); + sentinelId["properties"]["azuredevops/pipeline/build/buildDefinitionId"] = "-1"; + Expect("pipeline-negative-one-buildDefinitionId", sentinelId, true); + + // buildDefinitionId == "-2": GHAzDO1019 rejects (negative, non-sentinel). + JsonObject negativeId = CompliantPipeline(); + negativeId["properties"]["azuredevops/pipeline/build/buildDefinitionId"] = "-2"; + Expect("pipeline-negative-buildDefinitionId", negativeId, false); + // phaseId == empty GUID: GHAzDO1019 rejects (Guid != Empty). JsonObject emptyPhase = CompliantPipeline(); emptyPhase["properties"]["azuredevops/pipeline/build/phaseId"] = "00000000-0000-0000-0000-000000000000"; diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/Rules/GHAzDOValidationRuleTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/Rules/GHAzDOValidationRuleTests.cs index da22bca1e..27509f769 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/Rules/GHAzDOValidationRuleTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/Rules/GHAzDOValidationRuleTests.cs @@ -151,16 +151,40 @@ public void GHAzDO1019_WhenAllPipelinePropertiesMissing_ReportsFourErrors() } [Fact] - public void GHAzDO1019_WhenBuildDefinitionIdNotPositiveInteger_ReportsError() + public void GHAzDO1019_WhenBuildDefinitionIdIsZero_ReportsError() + => AssertBuildDefinitionId("0", expectError: true); + + [Fact] + public void GHAzDO1019_WhenBuildDefinitionIdIsNegativeOneSentinel_NoResult() + // -1 is ADO's sentinel for a run not associated with a saved definition. + => AssertBuildDefinitionId("-1", expectError: false); + + [Fact] + public void GHAzDO1019_WhenBuildDefinitionIdIsNegativeNonSentinel_ReportsError() + => AssertBuildDefinitionId("-2", expectError: true); + + [Fact] + public void GHAzDO1019_WhenBuildDefinitionIdNotInteger_ReportsError() + => AssertBuildDefinitionId("not-an-int", expectError: true); + + private static void AssertBuildDefinitionId(string buildDefinitionId, bool expectError) { SarifLog log = CreateValidGHAzDOSarifLog(); log.Runs[0].AutomationDetails.SetProperty( - GHAzDOProvidePipelineProperties.BuildDefinitionIdKey, "0"); + GHAzDOProvidePipelineProperties.BuildDefinitionIdKey, buildDefinitionId); SarifLog output = RunGHAzDOValidation(log); + List results = ResultsFor(output, "GHAzDO1019"); - ResultsFor(output, "GHAzDO1019").Should().ContainSingle() - .Which.Level.Should().Be(FailureLevel.Error); + if (expectError) + { + results.Should().ContainSingle() + .Which.Level.Should().Be(FailureLevel.Error); + } + else + { + results.Should().BeEmpty(); + } } [Fact] diff --git a/src/build.props b/src/build.props index 8b9f21429..6049e0d24 100644 --- a/src/build.props +++ b/src/build.props @@ -14,7 +14,7 @@ Microsoft Microsoft SARIF SDK © Microsoft Corporation. All rights reserved. - 5.4.2 + 5.4.3 2.1.0