Files
Anatolii Grynchuk 85b362e8cd 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
2026-05-01 00:17:34 +03:00

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}`";
}
}
}
}
}