85b362e8cd
- add the standalone HrynCo.Common solution and projects - include the shared common library source and tests - add package metadata and a repo gitignore
78 lines
2.6 KiB
C#
78 lines
2.6 KiB
C#
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}`";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|