diff --git a/src/chocolatey.tests.integration/scenarios/PackScenarios.cs b/src/chocolatey.tests.integration/scenarios/PackScenarios.cs index a619831cb7..aecc02166a 100644 --- a/src/chocolatey.tests.integration/scenarios/PackScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/PackScenarios.cs @@ -439,7 +439,7 @@ public void should_throw_exception_on_all_unsupported_elements() { AddFile("myPackage.nuspec", NuspecContentWithAllUnsupportedElements); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -447,7 +447,7 @@ public void should_throw_exception_on_serviceable_element() { AddFile("myPackage.nuspec", NuspecContentWithServiceableElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -455,7 +455,7 @@ public void should_throw_exception_on_license_element() { AddFile("myPackage.nuspec", NuspecContentWithLicenseElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -463,7 +463,7 @@ public void should_throw_exception_on_repository_element() { AddFile("myPackage.nuspec", NuspecContentWithRepositoryElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -471,7 +471,7 @@ public void should_throw_exception_on_package_types_element() { AddFile("myPackage.nuspec", NuspecContentWithPackageTypesElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -479,7 +479,7 @@ public void should_throw_exception_on_framework_references_element() { AddFile("myPackage.nuspec", NuspecContentWithFrameWorkReferencesElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -487,7 +487,7 @@ public void should_throw_exception_on_readme_element() { AddFile("myPackage.nuspec", NuspecContentWithReadmeElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } [Fact] @@ -495,7 +495,7 @@ public void should_throw_exception_on_icon_element() { AddFile("myPackage.nuspec", NuspecContentWithIconElement); - ServiceAct.ShouldThrow(); + ServiceAct.ShouldThrow(); } } diff --git a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs index 0209e21140..f98487bcef 100644 --- a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs @@ -23,6 +23,7 @@ namespace chocolatey.tests.infrastructure.app.services using chocolatey.infrastructure.app.configuration; using chocolatey.infrastructure.app.domain; using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.services; using Moq; using NuGet.Common; using NuGet.Packaging; @@ -40,6 +41,7 @@ public abstract class NugetServiceSpecsBase : TinySpec protected Mock filesService = new Mock(); protected Mock package = new Mock(); protected Mock packageDownloader = new Mock(); + protected Mock ruleService = new Mock(); public override void Context() { @@ -49,7 +51,7 @@ public override void Context() filesService.ResetCalls(); package.ResetCalls(); - service = new NugetService(fileSystem.Object, nugetLogger.Object, packageInfoService.Object, filesService.Object); + service = new NugetService(fileSystem.Object, nugetLogger.Object, packageInfoService.Object, filesService.Object, ruleService.Object); } } diff --git a/src/chocolatey.tests/infrastructure/guards/EnsureSpecs.cs b/src/chocolatey.tests/infrastructure/guards/EnsureSpecs.cs index cd3c17b77e..79b04518d1 100644 --- a/src/chocolatey.tests/infrastructure/guards/EnsureSpecs.cs +++ b/src/chocolatey.tests/infrastructure/guards/EnsureSpecs.cs @@ -32,6 +32,30 @@ public override void Context() } public class when_Ensure_is_being_set_to_a_type : EnsureSpecsBase + { + private object result; + private readonly object bob = "something"; + + public override void Because() + { + result = Ensure.that(() => bob); + } + + [Fact] + public void should_return_a_type_of_object_for_ensuring() + { + result.ShouldBeType>(); + } + + [Fact] + public void should_have_the_value_specified() + { + var bobEnsure = result as Ensure; + bobEnsure.Value.ShouldEqual(bob); + } + } + + public class when_Ensure_is_a_string_type : EnsureSpecsBase { private object result; private readonly string bob = "something"; @@ -42,19 +66,80 @@ public override void Because() } [Fact] - public void should_return_a_type_of_string_for_ensuring() + public void should_return_a_ensure_string_type() { - result.ShouldBeType>(); + result.ShouldBeType(); } [Fact] public void should_have_the_value_specified() { - var bobEnsure = result as Ensure; + var bobEnsure = result as EnsureString; bobEnsure.Value.ShouldEqual(bob); } } + public class when_using_EnsureString : EnsureSpecsBase + { + public override void Because() + { + } + + [Fact] + public void when_testing_a_string_against_null_value_should_fail() + { + string test = null; + + Action a = () => Ensure.that(() => test).is_not_null_or_whitespace(); + + a.ShouldThrow(); + } + + [Fact] + public void when_testing_a_string_against_an_empty_value_should_fail() + { + Action a = () => Ensure.that(() => string.Empty).is_not_null_or_whitespace(); + + a.ShouldThrow(); + } + + [Fact] + public void when_testing_a_string_against_a_whitespace_value_should_fail() + { + var test = " "; + + Action a = () => Ensure.that(() => test).is_not_null_or_whitespace(); + + a.ShouldThrow(); + } + + [Fact] + public void when_testing_a_string_against_a_non_empty_value_should_pass() + { + var test = "some value"; + + Ensure.that(() => test).is_not_null_or_whitespace(); + } + + [Fact] + public void when_testing_a_string_without_expected_extension_should_fail() + { + var test = "some-file.png"; + + Action a = () => Ensure.that(() => test).has_any_extension(".jpg", ".bmp", ".gif"); + + a.ShouldThrow(); + } + + [Fact] + public void when_testing_a_string_with_expected_extension_should_pass() + { + var test = "some-file.png"; + + Ensure.that(() => test).has_any_extension(".jpg", ".bmp", ".gif", ".png"); + } + } + public class when_using_Ensure : EnsureSpecsBase { public override void Because() diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 08733f05b2..d4643da937 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -215,6 +215,19 @@ + + + + + + + + + + + + + @@ -251,6 +264,10 @@ + + + + diff --git a/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs index 8dc711ba28..cb5bf168b5 100644 --- a/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs +++ b/src/chocolatey/infrastructure.app/registration/ChocolateyRegistrationModule.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2017 - 2023 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,9 @@ namespace chocolatey.infrastructure.app.registration using NuGet.Common; using NuGet.PackageManagement; using NuGet.Packaging; + using chocolatey.infrastructure.rules; + using chocolatey.infrastructure.app.rules; + using System.Linq; internal class ChocolateyRegistrationModule : IExtensionModule { @@ -78,6 +81,16 @@ public void register_dependencies(IContainerRegistrator registrator, ChocolateyC registrator.register_service( typeof(GlobalConfigurationValidation), typeof(SystemStateValidation)); + + // Rule registrations + registrator.register_service(); + + var availableRules = GetType().Assembly + .GetTypes() + .Where(t => !t.IsInterface && !t.IsAbstract && typeof(IMetadataRule).IsAssignableFrom(t)) + .ToArray(); + + registrator.register_service(availableRules); } } } diff --git a/src/chocolatey/infrastructure.app/rules/EmptyOrInvalidUrlMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/EmptyOrInvalidUrlMetadataRule.cs new file mode 100644 index 0000000000..53c2f3b9e7 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/EmptyOrInvalidUrlMetadataRule.cs @@ -0,0 +1,56 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System; + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class EmptyOrInvalidUrlMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + var items = new[] + { + "projectUrl", + "projectSourceUrl", + "docsUrl", + "bugTrackerUrl", + "mailingListUrl", + "iconUrl", + "licenseUrl" + }; + + foreach (var item in items) + { + if (has_element(reader, item)) + { + var value = get_element_value(reader, item); + + if (string.IsNullOrWhiteSpace(value)) + { + yield return new RuleResult(RuleType.Error, "The {0} element in the package nuspec file cannot be empty.".format_with(item)); + } + else if (!Uri.TryCreate(value, UriKind.Absolute, out _)) + { + yield return new RuleResult(RuleType.Error, "'{0}' is not a valid URL for the {1} element in the package nuspec file.".format_with(value, item)); + } + } + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/FrameWorkReferencesMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/FrameWorkReferencesMetadataRule.cs new file mode 100644 index 0000000000..d9b0fd6dd5 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/FrameWorkReferencesMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class FrameWorkReferencesMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + if (has_element(reader, "frameworkReferences")) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/IconMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/IconMetadataRule.cs new file mode 100644 index 0000000000..d9a4600931 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/IconMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class IconMetadataRule : IMetadataRule + { + public IEnumerable validate(NuspecReader reader) + { + if (!(reader.GetIcon() is null)) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI, use instead."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/LicenseMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/LicenseMetadataRule.cs new file mode 100644 index 0000000000..9588057e33 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/LicenseMetadataRule.cs @@ -0,0 +1,33 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class LicenseMetadataRule : IMetadataRule + { + public IEnumerable validate(NuspecReader reader) + { + if (!(reader.GetLicenseMetadata() is null)) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI, use instead."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/MetadataRuleBase.cs b/src/chocolatey/infrastructure.app/rules/MetadataRuleBase.cs new file mode 100644 index 0000000000..e0a72d34f0 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/MetadataRuleBase.cs @@ -0,0 +1,55 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml.Linq; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + public abstract class MetadataRuleBase : IMetadataRule + { + public abstract IEnumerable validate(NuspecReader reader); + + protected static bool has_element(NuspecReader reader, string name) + { + var metadataNode = reader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")); + + return !(metadataNode is null || metadataNode.Elements(XName.Get(name, metadataNode.GetDefaultNamespace().NamespaceName)).FirstOrDefault() is null); + } + + protected static string get_element_value(NuspecReader reader, string name) + { + var metadataNode = reader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")); + + if (metadataNode is null) + { + return null; + } + + var element = metadataNode.Elements(XName.Get(name, metadataNode.GetDefaultNamespace().NamespaceName)).FirstOrDefault(); + + if (element is null) + { + return null; + } + + return element.Value; + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/PackageTypesMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/PackageTypesMetadataRule.cs new file mode 100644 index 0000000000..ae260842fd --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/PackageTypesMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class PackageTypesMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + if (has_element(reader, "packageTypes")) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/ReadmeMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/ReadmeMetadataRule.cs new file mode 100644 index 0000000000..f34bf52c5b --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/ReadmeMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class ReadmeMetadataRule : IMetadataRule + { + public IEnumerable validate(NuspecReader reader) + { + if (!(reader.GetReadme() is null)) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/RepositoryMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/RepositoryMetadataRule.cs new file mode 100644 index 0000000000..16b6745117 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/RepositoryMetadataRule.cs @@ -0,0 +1,36 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System; + using System.Collections.Generic; + using System.Linq; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class RepositoryMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + var metadataNode = reader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")); + + if (has_element(reader, "repository")) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI, use instead."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/RequireLicenseAcceptanceMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/RequireLicenseAcceptanceMetadataRule.cs new file mode 100644 index 0000000000..9ce1854674 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/RequireLicenseAcceptanceMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class RequireLicenseAcceptanceMetadataRule : IMetadataRule + { + public IEnumerable validate(NuspecReader reader) + { + if (string.IsNullOrWhiteSpace(reader.GetLicenseUrl()) && reader.GetRequireLicenseAcceptance()) + { + yield return new RuleResult(RuleType.Error, "Enabling license acceptance requires a license url."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/RequiredMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/RequiredMetadataRule.cs new file mode 100644 index 0000000000..e17a2219aa --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/RequiredMetadataRule.cs @@ -0,0 +1,43 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class RequiredMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + var requiredItems = new[] + { + "id", + "version", + "authors", + "description" + }; + + foreach (var item in requiredItems) + { + if (string.IsNullOrWhiteSpace(get_element_value(reader, item))) + { + yield return new RuleResult(RuleType.Error, "{0} is a required element in the package nuspec file.".format_with(item)); + } + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/ServicableMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/ServicableMetadataRule.cs new file mode 100644 index 0000000000..46c7ea4f92 --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/ServicableMetadataRule.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + + internal sealed class ServicableMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + if (has_element(reader, "serviceable")) + { + yield return new RuleResult(RuleType.Error, " elements are not supported in Chocolatey CLI."); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/rules/VersionMetadataRule.cs b/src/chocolatey/infrastructure.app/rules/VersionMetadataRule.cs new file mode 100644 index 0000000000..c5f8429fff --- /dev/null +++ b/src/chocolatey/infrastructure.app/rules/VersionMetadataRule.cs @@ -0,0 +1,37 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + using NuGet.Packaging; + using NuGet.Versioning; + + internal sealed class VersionMetadataRule : MetadataRuleBase + { + public override IEnumerable validate(NuspecReader reader) + { + var version = get_element_value(reader, "version"); + + // We need to check for the $version$ substitution value, + // as it will not be replaced before the package gets created + if (!string.IsNullOrEmpty(version) && !version.is_equal_to("$version$") && !NuGetVersion.TryParse(version, out _)) + { + yield return new RuleResult(RuleType.Error, "'{0}' is not a valid version string in the package nuspec file.".format_with(version)); + } + } + } +} diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 26c429ac6f..b5c3f4d682 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -1,4 +1,4 @@ -// Copyright © 2017 - 2022 Chocolatey Software, Inc +// Copyright © 2017 - 2023 Chocolatey Software, Inc // Copyright © 2011 - 2017 RealDimensions Software, LLC // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,6 +52,7 @@ namespace chocolatey.infrastructure.app.services using NuGet.Versioning; using System.Xml.Linq; using infrastructure.configuration; + using chocolatey.infrastructure.services; //todo: #2575 - this monolith is too large. Refactor once test coverage is up. @@ -61,6 +62,7 @@ public class NugetService : INugetService private readonly ILogger _nugetLogger; private readonly IChocolateyPackageInformationService _packageInfoService; private readonly IFilesService _filesService; + private readonly IRuleService _ruleService; //private readonly PackageDownloader _packageDownloader; private readonly Lazy datetime_initializer = new Lazy(() => new DateTime()); @@ -76,12 +78,18 @@ private IDateTime DateTime /// The nuget logger /// Package information service /// The files service - public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPackageInformationService packageInfoService, IFilesService filesService) + public NugetService( + IFileSystem fileSystem, + ILogger nugetLogger, + IChocolateyPackageInformationService packageInfoService, + IFilesService filesService, + IRuleService ruleService) { _fileSystem = fileSystem; _nugetLogger = nugetLogger; _packageInfoService = packageInfoService; _filesService = filesService; + _ruleService = ruleService; } public string SourceType @@ -298,67 +306,10 @@ public virtual string validate_and_return_package_file(ChocolateyConfiguration c return filePath; } - public virtual IEnumerable validate_nuspec_elements(string filePath) - { - var nuspecReader = new NuspecReader(filePath); - var metadataNode = nuspecReader.Xml.Root.Elements().FirstOrDefault(e => StringComparer.Ordinal.Equals(e.Name.LocalName, "metadata")); - var metadataNamespace = metadataNode.GetDefaultNamespace().NamespaceName; - - var issuesList = new List(); - - if (!(nuspecReader.GetReadme() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI"); - } - - if (!(nuspecReader.GetIcon() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI, use instead"); - } - - if (!(metadataNode.Elements(XName.Get("repository", metadataNamespace)).FirstOrDefault() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI, use instead"); - } - - if (!(nuspecReader.GetLicenseMetadata() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI, use instead"); - } - - if (string.IsNullOrWhiteSpace(nuspecReader.GetLicenseUrl()) && nuspecReader.GetRequireLicenseAcceptance()) - { - issuesList.Add("Enabling license acceptance requires a license url."); - } - - if (!(metadataNode.Elements(XName.Get("packageTypes", metadataNamespace)).FirstOrDefault() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI"); - } - - if (!(metadataNode.Elements(XName.Get("serviceable", metadataNamespace)).FirstOrDefault() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI"); - } - - if (!(metadataNode.Elements(XName.Get("frameworkReferences", metadataNamespace)).FirstOrDefault() is null)) - { - issuesList.Add(" elements are not supported in Chocolatey CLI"); - } - - return issuesList; - } - public virtual void pack_run(ChocolateyConfiguration config) { var nuspecFilePath = validate_and_return_package_file(config, PackagingConstants.ManifestExtension); - - var nuspecIssuesList = validate_nuspec_elements(nuspecFilePath); - if (nuspecIssuesList.Any() && config.PackCommand.PackThrowOnUnsupportedElements) - { - this.Log().Warn("Issues found with {0}, please remove the unsupported elements listed below".format_with(nuspecFilePath)); - throw new System.IO.FileFormatException(nuspecIssuesList.join("\r\n")); - } + validate_nuspec(nuspecFilePath, config); var nuspecDirectory = _fileSystem.get_full_path(_fileSystem.get_directory_name(nuspecFilePath)); if (string.IsNullOrWhiteSpace(nuspecDirectory)) nuspecDirectory = _fileSystem.get_current_directory(); @@ -850,7 +801,11 @@ Version was specified as '{0}'. It is possible that version packageResult.InstallLocation = installedPath; packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - var elementsList = validate_nuspec_elements(manifestPath); + var elementsList = _ruleService.validate_rules(manifestPath) + .Where(r => r.Severity == infrastructure.rules.RuleType.Error) + .Select(r => r.Message) + .ToList(); + if (elementsList.Any()) { var message = "Issues found with nuspec elements\r\n" + elementsList.join("\r\n"); @@ -1397,7 +1352,11 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate upgradePackageResult.InstallLocation = installedPath; upgradePackageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - var elementsList = validate_nuspec_elements(manifestPath); + var elementsList = _ruleService.validate_rules(manifestPath) + .Where(r => r.Severity == infrastructure.rules.RuleType.Error) + .Select(r => r.Message) + .ToList(); + if (elementsList.Any()) { var message = "Issues found with nuspec elements\r\n" + elementsList.join("\r\n"); @@ -1573,6 +1532,43 @@ protected virtual ChocolateyConfiguration set_package_config_for_upgrade(Chocola return originalConfig; } + private void validate_nuspec(string nuspecFilePath, ChocolateyConfiguration config) + { + var results = _ruleService.validate_rules(nuspecFilePath); + + if (!config.PackCommand.PackThrowOnUnsupportedElements) + { + results = results.Where(r => !r.Message.contains("not supported")); + } + + var hasErrors = false; + + foreach (var rule in results) + { + switch (rule.Severity) + { + case infrastructure.rules.RuleType.Error: + this.Log().Error("ERROR: " + rule.Message); + hasErrors = true; + break; + + case infrastructure.rules.RuleType.Warning: + this.Log().Warn("WARNING: " + rule.Message); + break; + + case infrastructure.rules.RuleType.Information: + this.Log().Info("INFO: " + rule.Message); + break; + } + } + + if (hasErrors) + { + this.Log().Info(string.Empty); + throw new InvalidDataException("One or more issues found with {0}, please fix all validation items above listed as errors.".format_with(nuspecFilePath)); + } + } + private string get_install_directory(ChocolateyConfiguration config, IPackageMetadata installedPackage) { diff --git a/src/chocolatey/infrastructure.app/services/RuleService.cs b/src/chocolatey/infrastructure.app/services/RuleService.cs new file mode 100644 index 0000000000..dad2d53321 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/RuleService.cs @@ -0,0 +1,85 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.services +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using chocolatey.infrastructure.guards; + using chocolatey.infrastructure.rules; + using chocolatey.infrastructure.services; + using NuGet.Configuration; + using NuGet.Packaging; + + public sealed class RuleService : IRuleService + { + private readonly IMetadataRule[] _rules; + + public RuleService(IMetadataRule[] rules) + { + _rules = rules; + } + + public IEnumerable validate_rules(string filePath) + { + Ensure.that(() => filePath) + .is_not_null_or_whitespace() + .has_any_extension(NuGetConstants.PackageExtension, NuGetConstants.ManifestExtension); + + var rules = filePath.EndsWith(NuGetConstants.PackageExtension) + ? get_rules_from_package_async(filePath).GetAwaiter().GetResult() + : get_rules_from_metadata(filePath); + + return rules + .OrderBy(r => r.Severity) + .ThenBy(r => r.Message); + } + + private async Task> get_rules_from_package_async(string filePath, CancellationToken token = default) + { + using (var packageReader = new PackageArchiveReader(filePath)) + { + var nuspecReader = await packageReader.GetNuspecReaderAsync(token); + + // We add ToList here to ensure that the package + // reader hasn't been disposed of before we return + // any results. + return validate_nuspec(nuspecReader, _rules).ToList(); + } + } + + private IEnumerable get_rules_from_metadata(string filePath) + { + var nuspecReader = new NuspecReader(filePath); + + return validate_nuspec(nuspecReader, _rules); + } + + private static IEnumerable validate_nuspec(NuspecReader reader, IMetadataRule[] rules) + { + foreach (var rule in rules) + { + var validationResults = rule.validate(reader); + + foreach (var result in validationResults.Where(v => v.Severity != RuleType.None)) + { + yield return result; + } + } + } + } +} diff --git a/src/chocolatey/infrastructure/guards/Ensure.cs b/src/chocolatey/infrastructure/guards/Ensure.cs index b10b6757e7..a2966f416a 100644 --- a/src/chocolatey/infrastructure/guards/Ensure.cs +++ b/src/chocolatey/infrastructure/guards/Ensure.cs @@ -17,10 +17,17 @@ namespace chocolatey.infrastructure.guards { using System; + using System.IO; using System.Linq.Expressions; public static class Ensure { + public static EnsureString that(Expression> expression) + { + var memberName = expression.get_name_on_right().Member.Name; + return new EnsureString(memberName, expression.Compile().Invoke()); + } + public static Ensure that(Expression> expression) where TypeToEnsure : class { var memberName = expression.get_name_on_right().Member.Name; @@ -52,6 +59,41 @@ private static MemberExpression get_name_on_right(this Expression e) } } + public class EnsureString : Ensure + { + public EnsureString(string name, string value) + : base(name, value) + { + } + + public EnsureString is_not_null_or_whitespace() + { + is_not_null(); + + if (string.IsNullOrWhiteSpace(Value)) + { + throw new ArgumentException(Name, "Value for {0} cannot be empty or only contain whitespace.".format_with(Name)); + } + + return this; + } + + public EnsureString has_any_extension(params string[] extensions) + { + var actualExtension = Path.GetExtension(Value); + + foreach (var extension in extensions) + { + if (extension.is_equal_to(actualExtension)) + { + return this; + } + } + + throw new ArgumentException(Name, "Value for {0} must contain one of the following extensions: {1}".format_with(Name, string.Join(", ", extensions))); + } + } + public class Ensure where EnsurableType : class { public string Name { get; private set; } diff --git a/src/chocolatey/infrastructure/rules/IMetadataRule.cs b/src/chocolatey/infrastructure/rules/IMetadataRule.cs new file mode 100644 index 0000000000..23276a0429 --- /dev/null +++ b/src/chocolatey/infrastructure/rules/IMetadataRule.cs @@ -0,0 +1,27 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.rules +{ + using System.Collections.Generic; + using chocolatey.infrastructure.app.attributes; + using NuGet.Packaging; + + [MultiService] + public interface IMetadataRule + { + IEnumerable validate(NuspecReader reader); + } +} diff --git a/src/chocolatey/infrastructure/rules/RuleResult.cs b/src/chocolatey/infrastructure/rules/RuleResult.cs new file mode 100644 index 0000000000..434c5c4c3c --- /dev/null +++ b/src/chocolatey/infrastructure/rules/RuleResult.cs @@ -0,0 +1,32 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.rules +{ + public readonly struct RuleResult + { + public static readonly RuleResult Success = new RuleResult(RuleType.None, string.Empty); + + public RuleResult(RuleType severity, string message) + { + Severity = severity; + Message = message; + } + + public readonly RuleType Severity; + + public readonly string Message; + } +} diff --git a/src/chocolatey/infrastructure/rules/RuleType.cs b/src/chocolatey/infrastructure/rules/RuleType.cs new file mode 100644 index 0000000000..b274885751 --- /dev/null +++ b/src/chocolatey/infrastructure/rules/RuleType.cs @@ -0,0 +1,25 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.rules +{ + public enum RuleType + { + None = 0, + Error, + Warning, + Information + } +} diff --git a/src/chocolatey/infrastructure/services/IRuleService.cs b/src/chocolatey/infrastructure/services/IRuleService.cs new file mode 100644 index 0000000000..3843fb9cb9 --- /dev/null +++ b/src/chocolatey/infrastructure/services/IRuleService.cs @@ -0,0 +1,25 @@ +// Copyright © 2023-Present Chocolatey Software, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.services +{ + using System.Collections.Generic; + using chocolatey.infrastructure.rules; + + public interface IRuleService + { + IEnumerable validate_rules(string filePath); + } +} diff --git a/tests/chocolatey-tests/commands/choco-pack.Tests.ps1 b/tests/chocolatey-tests/commands/choco-pack.Tests.ps1 index 8af3446bd6..48bd2f99e7 100644 --- a/tests/chocolatey-tests/commands/choco-pack.Tests.ps1 +++ b/tests/chocolatey-tests/commands/choco-pack.Tests.ps1 @@ -1,4 +1,4 @@ -Import-Module helpers/common-helpers +Import-Module helpers/common-helpers $successPack = @('basic'; 'basic-dependencies'; "cdata"; "full") # Required elements, that can also not be empty @@ -15,13 +15,17 @@ $emptyFailures = @( ) # Elements that will return an invalid failure (usually due to serialization) $invalidFailures = @( - $emptyFailures | ForEach-Object { - @{id = $_; message = "Invalid URI: The format of the URI could not be determined." } - } - @{id = "version"; message = "'INVALID' is not a valid version string." } - @{id = "no-content"; message = "Cannot create a package that has no dependencies nor content." } - @{id = "id"; message = "The package ID 'invalid id' contains invalid characters. Examples of valid package IDs include 'MyPackage' and 'MyPackage.Sample'." } - @{id = "requirelicenseacceptance"; message = "Enabling license acceptance requires a license url." } + @{id = 'projectUrl'; message = "ERROR: 'invalid project url' is not a valid URL for the projectUrl element in the package nuspec file." } + @{id = 'projectSourceUrl'; message = "ERROR: 'invalid project source url' is not a valid URL for the projectSourceUrl element in the package nuspec file." } + @{id = 'docsUrl'; message = "ERROR: 'invalid docs url' is not a valid URL for the docsUrl element in the package nuspec file." } + @{id = 'bugTrackerUrl'; message = "ERROR: 'invalid bug tracker url' is not a valid URL for the bugTrackerUrl element in the package nuspec file." } + @{id = 'mailingListUrl'; message = "ERROR: 'invalid mailing list url' is not a valid URL for the mailingListUrl element in the package nuspec file." } + @{id = 'iconUrl'; message = "ERROR: 'invalid icon url' is not a valid URL for the iconUrl element in the package nuspec file." } + @{id = 'licenseUrl'; message = "ERROR: 'invalid license url' is not a valid URL for the licenseUrl element in the package nuspec file." } + @{id = "version"; message = "ERROR: 'INVALID' is not a valid version string in the package nuspec file." } + @{id = "no-content"; message = "Cannot create a package that has no dependencies nor content." } # This is a message from NuGet.Client, we may want to take ownership of it eventually. + @{id = "id"; message = "The package ID 'invalid id' contains invalid characters. Examples of valid package IDs include 'MyPackage' and 'MyPackage.Sample'." } # This is a message from NuGet.Client, we may want to take ownership of it eventually. + @{id = "requirelicenseacceptance"; message = "ERROR: Enabling license acceptance requires a license url." } ) Describe "choco pack" -Tag Chocolatey, PackCommand { @@ -135,7 +139,7 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { } # Message has changed and is missing almost all items in the validation message - Context "Package with required elements" -Tag Broken { + Context "Package with required elements" { BeforeAll { $Output = Invoke-Choco pack "required.nuspec" } @@ -148,8 +152,8 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { $Output.Lines | Should -Contain $expectedHeader } - It "Displays required error message for <_>" -ForEach $missingFailures -Tag Broken { - $Output.Lines | Should -Contain "$_ is required." + It "Displays required error message for <_>" -ForEach $missingFailures { + $Output.Lines | Should -Contain "ERROR: $_ is a required element in the package nuspec file." } It "Does not create the nuget package" { @@ -163,10 +167,14 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + It "Should output message about validation issues found" { + $Output.Lines | Should -Contain "One or more issues found with required.nuspec, please fix all validation items above listed as errors." -Because $Output.String + } } # TODO: Validation messages are incomplete and are missing some items - Context "Package with empty elements" -Tag PartiallyBroken { + Context "Package with empty elements" { BeforeAll { $Output = Invoke-Choco pack "empty.nuspec" } @@ -179,8 +187,8 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { $Output.Lines | Should -Contain $expectedHeader } - It "Displays empty error message for <_>" -ForEach ($emptyFailures | ? { $_ -eq 'licenseUrl' }) { - $Output.Lines | Should -Contain "$_ cannot be empty." + It "Displays empty error message for <_>" -ForEach $emptyFailures { + $Output.Lines | Should -Contain "ERROR: The $_ element in the package nuspec file cannot be empty." } It "Does not create the nuget package" { @@ -194,6 +202,10 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + It "Should output message about validation issues found" { + $Output.Lines | Should -Contain "One or more issues found with empty.nuspec, please fix all validation items above listed as errors." + } } # This empty element must be in a separate nuspec file as it will be a serializing error @@ -221,6 +233,12 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + # We currently do not have a custom validation message for this element. + # As such we do not know when or if it failed because of an empty/invalid require license acceptance value. + It "Should not output message about validation issues found" { + $Output.Lines | Should -Not -Contain "One or more issues found with empty-requireLicenseAcceptance.nuspec, please fix all validation items above listed as errors." + } } Context "Package with missing elements" { @@ -236,8 +254,8 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { $Output.Lines | Should -Contain $expectedHeader } - It "Displays possible element message <_>" -ForEach $missingFailures { - $Output.String | Should -Match "The element 'metadata' in namespace.*has incomplete content\..*$_" + It "Displays empty error message for <_>" -ForEach $missingFailures { + $Output.Lines | Should -Contain "ERROR: $_ is a required element in the package nuspec file." } It "Does not create the nuget package" { @@ -251,10 +269,14 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + It "Should output message about validation issues found" { + $Output.Lines | Should -Contain "One or more issues found with missing.nuspec, please fix all validation items above listed as errors." + } } # TODO: Message about verifying version string has changed, and should be fixed - Context "Package with invalid <_.id>" -ForEach ($invalidFailures | ? id -NotIn 'version') -Tag PartiallyBroken { + Context "Package with invalid <_.id>" -ForEach $invalidFailures { BeforeAll { $Output = Invoke-Choco pack "invalid-$($_.id).nuspec" } @@ -282,6 +304,16 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + It "Should output message about validation issues found if needed" { + $message = "One or more issues found with invalid-$($_.id).nuspec, please fix all validation items above listed as errors." + + if ($_.id -in @('no-content', 'id')) { + $Output.Lines | Should -Not -Contain $message + } else { + $Output.Lines | Should -Contain $message + } + } } Context "Package with invalid character '&'" { @@ -536,7 +568,7 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { } It 'Shows an error about the unsupported nuspec metadata element "<_>"' -TestCases $testCases { - $Output.String | Should -Match "$_ elements are not supported in Chocolatey CLI" + $Output.String | Should -Match "ERROR: $_ elements are not supported in Chocolatey CLI" } It "Should not output message about license url being deprecated" { @@ -546,6 +578,10 @@ Describe "choco pack" -Tag Chocolatey, PackCommand { It "Should not output message about iconUrl being deprecated" { $Output.String | Should -Not -Match "The 'PackageIconUrl'/'iconUrl' element is deprecated" } + + It "Should output message about validation issues found" { + $Output.Lines | Should -Contain "One or more issues found with $nuspecPath, please fix all validation items above listed as errors." + } } # This needs to be the last test in this block, to ensure NuGet configurations aren't being created.