diff --git a/lang/csharp/common.props b/lang/csharp/common.props index e12601f3d18..f41617eadfa 100644 --- a/lang/csharp/common.props +++ b/lang/csharp/common.props @@ -48,7 +48,7 @@ Copyright © 2019 The Apache Software Foundation. logo.png - LICENSE + Apache-2.0 README.md https://avro.apache.org/ Avro;Apache;Serialization;Binary;Json;Schema diff --git a/lang/csharp/src/apache/test/NuGetPackageTests.cs b/lang/csharp/src/apache/test/NuGetPackageTests.cs new file mode 100644 index 00000000000..493cede9312 --- /dev/null +++ b/lang/csharp/src/apache/test/NuGetPackageTests.cs @@ -0,0 +1,156 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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. + */ + +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Xml.Linq; +using NUnit.Framework; + +namespace Avro.Test.Utils +{ + [TestFixture] + public class NuGetPackageTests + { + private static readonly string[] PackageIds = new[] + { + "Apache.Avro", + "Apache.Avro.Tools", + "Apache.Avro.File.Snappy", + "Apache.Avro.File.BZip2", + "Apache.Avro.File.XZ", + "Apache.Avro.File.Zstandard" + }; + + [TestCaseSource(nameof(PackageIds))] + public void PackageContainsSpdxLicenseExpression(string packageId) + { + var nupkgPath = FindPackageInBuildOutput(packageId); + if (nupkgPath == null) + { + Assert.Inconclusive($"Package {packageId} not found. Run 'dotnet pack --configuration Release' first."); + return; + } + + var nuspecXml = ExtractNuspecFromPackage(nupkgPath); + + // Get the namespace from the root element + var ns = nuspecXml.Root?.Name.Namespace ?? XNamespace.None; + var licenseElement = nuspecXml.Root?.Element(ns + "metadata")?.Element(ns + "license"); + + Assert.That(licenseElement, Is.Not.Null, + $"Package {packageId} does not contain a license element"); + + var licenseType = licenseElement?.Attribute("type")?.Value; + Assert.That(licenseType, Is.EqualTo("expression"), + $"Package {packageId} license type should be 'expression', but was '{licenseType}'"); + + var licenseValue = licenseElement?.Value; + Assert.That(licenseValue, Is.EqualTo("Apache-2.0"), + $"Package {packageId} should have SPDX license expression 'Apache-2.0', but was '{licenseValue}'"); + } + + [TestCaseSource(nameof(PackageIds))] + public void PackageContainsLicenseFile(string packageId) + { + var nupkgPath = FindPackageInBuildOutput(packageId); + if (nupkgPath == null) + { + Assert.Inconclusive($"Package {packageId} not found. Run 'dotnet pack --configuration Release' first."); + return; + } + + using (var archive = ZipFile.OpenRead(nupkgPath)) + { + var licenseEntry = archive.Entries.FirstOrDefault(e => + e.FullName.Equals("LICENSE", StringComparison.OrdinalIgnoreCase)); + + Assert.That(licenseEntry, Is.Not.Null, + $"Package {packageId} does not contain LICENSE file"); + + Assert.That(licenseEntry.Length, Is.GreaterThan(0), + $"Package {packageId} LICENSE file is empty"); + } + } + + [TestCaseSource(nameof(PackageIds))] + public void PackageLicenseFileContainsApacheLicense(string packageId) + { + var nupkgPath = FindPackageInBuildOutput(packageId); + if (nupkgPath == null) + { + Assert.Inconclusive($"Package {packageId} not found. Run 'dotnet pack --configuration Release' first."); + return; + } + + using (var archive = ZipFile.OpenRead(nupkgPath)) + { + var licenseEntry = archive.Entries.FirstOrDefault(e => + e.FullName.Equals("LICENSE", StringComparison.OrdinalIgnoreCase)); + + Assert.That(licenseEntry, Is.Not.Null); + + using (var stream = licenseEntry.Open()) + using (var reader = new StreamReader(stream)) + { + var content = reader.ReadToEnd(); + Assert.That(content, Does.Contain("Apache License"), + $"Package {packageId} LICENSE file does not contain Apache License text"); + Assert.That(content, Does.Contain("Version 2.0"), + $"Package {packageId} LICENSE file does not specify Version 2.0"); + } + } + } + + private string FindPackageInBuildOutput(string packageId) + { + // Find the lang/csharp root (4 levels up from test binary directory: bin/Release/net8.0 -> test -> apache -> src -> csharp) + var testDir = TestContext.CurrentContext.TestDirectory; + var csharpRoot = Path.GetFullPath(Path.Combine(testDir, "..", "..", "..", "..", "..")); + + // Search for the package in Release output directories + var pattern = $"{packageId}.*.nupkg"; + var files = Directory.GetFiles(csharpRoot, pattern, SearchOption.AllDirectories) + .Where(f => f.Contains($"{Path.DirectorySeparatorChar}Release{Path.DirectorySeparatorChar}")) + .OrderByDescending(f => System.IO.File.GetLastWriteTime(f)) + .ToArray(); + + return files.Length > 0 ? files[0] : null; + } + + private XDocument ExtractNuspecFromPackage(string nupkgPath) + { + using (var archive = ZipFile.OpenRead(nupkgPath)) + { + var nuspecEntry = archive.Entries.FirstOrDefault(e => + e.FullName.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase)); + + if (nuspecEntry == null) + { + Assert.Fail($"No .nuspec file found in package: {nupkgPath}"); + } + + using (var stream = nuspecEntry.Open()) + { + return XDocument.Load(stream); + } + } + } + } +}