Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,65 @@
Assert.IsTrue(content.Contains("WITHOUT WARRANTY"), "Should contain warranty disclaimer");
Assert.IsTrue(content.Contains("THE SOFTWARE IS PROVIDED"), "Should contain software provision clause");
}

[TestMethod]
public async Task GenerateAsync_NewYear_KeepsExistingCopyright()
{
// Arrange
const string Existing = "Copyright (c) 2023-2026 testowner contributors";
await File.WriteAllTextAsync(Path.Combine(_tempDir, "COPYRIGHT.md"), Existing + "\n").ConfigureAwait(false);

Check warning on line 176 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cV&open=AaEbur44dwEzUswlR8cV&pullRequest=233

// Act
await LicenseGenerator.GenerateAsync(
serverUrl: "https://github.com/testowner",
owner: "testowner",
repository: "testrepo",
outputPath: _tempDir,
lineEnding: "\n",
currentYear: 2027).ConfigureAwait(false);

Check warning on line 185 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cW&open=AaEbur44dwEzUswlR8cW&pullRequest=233

// Assert
string copyrightContent = await File.ReadAllTextAsync(Path.Combine(_tempDir, "COPYRIGHT.md")).ConfigureAwait(false);

Check warning on line 188 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cX&open=AaEbur44dwEzUswlR8cX&pullRequest=233
string licenseContent = await File.ReadAllTextAsync(Path.Combine(_tempDir, "LICENSE.md")).ConfigureAwait(false);

Check warning on line 189 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cY&open=AaEbur44dwEzUswlR8cY&pullRequest=233
Assert.AreEqual(Existing + "\n", copyrightContent, "An existing COPYRIGHT.md must not move to the new year");
Assert.IsTrue(licenseContent.Contains(Existing), "LICENSE.md should carry the existing copyright line");

Check warning on line 191 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.Contains' instead of 'Assert.IsTrue'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cZ&open=AaEbur44dwEzUswlR8cZ&pullRequest=233
Assert.IsFalse(licenseContent.Contains("2027"), "LICENSE.md should not move to the new year");

Check warning on line 192 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'Assert.DoesNotContain' instead of 'Assert.IsFalse'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8ca&open=AaEbur44dwEzUswlR8ca&pullRequest=233
}

[TestMethod]
public async Task GenerateAsync_NoCopyrightFile_GeneratesOneForTheCurrentYear()
{
// Act
await LicenseGenerator.GenerateAsync(
serverUrl: "https://github.com/testowner",
owner: "testowner",
repository: "testrepo",
outputPath: _tempDir,
lineEnding: "\n",
currentYear: 2027).ConfigureAwait(false);

Check warning on line 205 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cb&open=AaEbur44dwEzUswlR8cb&pullRequest=233

// Assert
string copyrightContent = await File.ReadAllTextAsync(Path.Combine(_tempDir, "COPYRIGHT.md")).ConfigureAwait(false);

Check warning on line 208 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cc&open=AaEbur44dwEzUswlR8cc&pullRequest=233
Assert.AreEqual("Copyright (c) 2023-2027 testowner contributors\n", copyrightContent);
}

[TestMethod]
public async Task GenerateAsync_EmptyCopyrightFile_IsRegenerated()
{
// Arrange
await File.WriteAllTextAsync(Path.Combine(_tempDir, "COPYRIGHT.md"), " \n").ConfigureAwait(false);

Check warning on line 216 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cd&open=AaEbur44dwEzUswlR8cd&pullRequest=233

// Act
await LicenseGenerator.GenerateAsync(
serverUrl: "https://github.com/testowner",
owner: "testowner",
repository: "testrepo",
outputPath: _tempDir,
lineEnding: "\n",
currentYear: 2027).ConfigureAwait(false);

Check warning on line 225 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8ce&open=AaEbur44dwEzUswlR8ce&pullRequest=233

// Assert
string copyrightContent = await File.ReadAllTextAsync(Path.Combine(_tempDir, "COPYRIGHT.md")).ConfigureAwait(false);

Check warning on line 228 in KtsuBuild.Tests/Metadata/LicenseGeneratorTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using the overload that accepts a CancellationToken and pass 'TestContext.CancellationToken'

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEbur44dwEzUswlR8cf&open=AaEbur44dwEzUswlR8cf&pullRequest=233
Assert.AreEqual("Copyright (c) 2023-2027 testowner contributors\n", copyrightContent);
}
}
38 changes: 34 additions & 4 deletions KtsuBuild/Metadata/LicenseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
/// <summary>
/// Generates LICENSE.md and COPYRIGHT.md files.
/// </summary>
/// <remarks>
/// An existing COPYRIGHT.md is kept as it is, and its text is what LICENSE.md carries. ktsu.Sdk derives
/// every source file's required header from COPYRIGHT.md, so rewriting it from the clock would fail
/// every file in the repository against IDE0073 the first time a run lands in a new year. Moving the
/// year on is a deliberate change that also rewrites the headers, not a side effect of a build.
/// </remarks>
/// <param name="serverUrl">The GitHub server URL.</param>
/// <param name="owner">The repository owner.</param>
/// <param name="repository">The repository name.</param>
Expand All @@ -29,6 +35,26 @@
string repository,
string outputPath,
string lineEnding,
CancellationToken cancellationToken = default) =>
await GenerateAsync(serverUrl, owner, repository, outputPath, lineEnding, DateTime.UtcNow.Year, cancellationToken).ConfigureAwait(false);

/// <summary>
/// Generates LICENSE.md and COPYRIGHT.md files, taking the current year from the caller.
/// </summary>
/// <param name="serverUrl">The GitHub server URL.</param>
/// <param name="owner">The repository owner.</param>
/// <param name="repository">The repository name.</param>
/// <param name="outputPath">The output directory.</param>
/// <param name="lineEnding">The line ending to use.</param>
/// <param name="currentYear">The year a newly created COPYRIGHT.md runs to.</param>
/// <param name="cancellationToken">A cancellation token.</param>
internal static async Task GenerateAsync(
string serverUrl,
string owner,
string repository,
string outputPath,
string lineEnding,
int currentYear,
CancellationToken cancellationToken = default)
{
Ensure.NotNull(serverUrl);
Expand All @@ -38,13 +64,18 @@
Ensure.NotNull(lineEnding);

string template = LicenseTemplate;
int year = DateTime.UtcNow.Year;

// Build project URL
string projectUrl = $"{serverUrl}/{repository}";

// Build copyright string
string copyright = $"Copyright (c) 2023-{year} {owner} contributors";
// Keep an existing copyright line, and make one up only for a repository that has none
string copyrightPath = Path.Combine(outputPath, "COPYRIGHT.md");
string? existingCopyright = File.Exists(copyrightPath)
? (await File.ReadAllTextAsync(copyrightPath, cancellationToken).ConfigureAwait(false)).Trim()
: null;
string copyright = string.IsNullOrEmpty(existingCopyright)
? $"Copyright (c) 2023-{currentYear} {owner} contributors"
: existingCopyright!;

Check warning on line 78 in KtsuBuild/Metadata/LicenseGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

Check warning on line 78 in KtsuBuild/Metadata/LicenseGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

Check warning on line 78 in KtsuBuild/Metadata/LicenseGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

Check warning on line 78 in KtsuBuild/Metadata/LicenseGenerator.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

Check warning on line 78 in KtsuBuild/Metadata/LicenseGenerator.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

See more on https://sonarcloud.io/project/issues?id=ktsu-dev_KtsuBuild&issues=AaEburz9dwEzUswlR8cU&open=AaEburz9dwEzUswlR8cU&pullRequest=233

// Replace placeholders
string licenseContent = template
Expand All @@ -56,7 +87,6 @@
await LineEndingHelper.WriteFileAsync(licensePath, licenseContent, lineEnding, cancellationToken).ConfigureAwait(false);

// Write COPYRIGHT.md
string copyrightPath = Path.Combine(outputPath, "COPYRIGHT.md");
await LineEndingHelper.WriteFileAsync(copyrightPath, copyright + lineEnding, lineEnding, cancellationToken).ConfigureAwait(false);
}

Expand Down
Loading