chore: add hrynco common library solution
- add the standalone HrynCo.Common solution and projects - include the shared common library source and tests - add package metadata and a repo gitignore
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
namespace HrynCo.Common.Tests;
|
||||
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
public sealed class TimeApiUsageGuardTests
|
||||
{
|
||||
private static readonly string[] ForbiddenPatterns =
|
||||
[
|
||||
"DateTime.Now",
|
||||
"DateTimeOffset.Now"
|
||||
];
|
||||
|
||||
[Fact]
|
||||
public void ProductionCode_ShouldNotUseLocalNowApis()
|
||||
{
|
||||
string solutionRoot = GetSolutionRoot();
|
||||
|
||||
var violations = Directory
|
||||
.EnumerateFiles(solutionRoot, "*.cs", SearchOption.AllDirectories)
|
||||
.Where(IsProductionSourceFile)
|
||||
.SelectMany(file => FindViolations(solutionRoot, file))
|
||||
.ToArray();
|
||||
|
||||
violations.Should().BeEmpty(
|
||||
"production code should use UTC-based APIs for persisted and serialized timestamps.{0}{1}",
|
||||
Environment.NewLine,
|
||||
string.Join(Environment.NewLine, violations));
|
||||
}
|
||||
|
||||
private static string GetSolutionRoot()
|
||||
{
|
||||
string? current = AppContext.BaseDirectory;
|
||||
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current, "HrynCo.Common.sln")))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Directory.GetParent(current)?.FullName;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not locate the solution root from the test assembly output.");
|
||||
}
|
||||
|
||||
private static bool IsProductionSourceFile(string filePath)
|
||||
{
|
||||
string normalizedPath = filePath.Replace('\\', '/');
|
||||
|
||||
return !normalizedPath.Contains("/bin/", StringComparison.OrdinalIgnoreCase)
|
||||
&& !normalizedPath.Contains("/obj/", StringComparison.OrdinalIgnoreCase)
|
||||
&& !normalizedPath.Contains("/Migrations/", StringComparison.OrdinalIgnoreCase)
|
||||
&& !normalizedPath.Contains(".Tests/", StringComparison.OrdinalIgnoreCase)
|
||||
&& !normalizedPath.Contains(".IntegrationTests/", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> FindViolations(string solutionRoot, string filePath)
|
||||
{
|
||||
string relativePath = Path.GetRelativePath(solutionRoot, filePath).Replace('\\', '/');
|
||||
string[] lines = File.ReadAllLines(filePath);
|
||||
|
||||
for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++)
|
||||
{
|
||||
string line = lines[lineIndex];
|
||||
|
||||
foreach (string forbiddenPattern in ForbiddenPatterns)
|
||||
{
|
||||
if (line.Contains(forbiddenPattern, StringComparison.Ordinal))
|
||||
{
|
||||
yield return $"{relativePath}:{lineIndex + 1} contains forbidden API `{forbiddenPattern}`";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user