using System.Text;
using System.Text.Json;
using System.Runtime.CompilerServices;
using Xunit;
namespace StellaOps.TestKit.Assertions;
///
/// Provides snapshot testing assertions for golden master testing.
/// Snapshots are stored in the test project's `Snapshots/` directory.
///
///
/// Usage:
///
/// [Fact]
/// public void TestSbomGeneration()
/// {
/// var sbom = GenerateSbom();
///
/// // Snapshot will be stored in Snapshots/TestSbomGeneration.json
/// SnapshotAssert.MatchesSnapshot(sbom, snapshotName: "TestSbomGeneration");
/// }
///
///
/// To update snapshots (e.g., after intentional changes), set environment variable:
/// UPDATE_SNAPSHOTS=1 dotnet test
///
public static class SnapshotAssert
{
private static readonly bool UpdateSnapshotsMode =
global::System.Environment.GetEnvironmentVariable("UPDATE_SNAPSHOTS") == "1";
///
/// Resolves the default snapshot directory for a caller.
///
///
/// If the caller source file lives inside a Snapshots directory, that directory is used so
/// snapshots stay source-controlled and deterministic across environments.
/// Otherwise the legacy runtime path (current working directory/Snapshots) is used.
///
public static string ResolveDefaultSnapshotsDirectory([CallerFilePath] string callerFilePath = "")
{
if (!string.IsNullOrWhiteSpace(callerFilePath))
{
var callerDirectory = Path.GetDirectoryName(callerFilePath);
if (!string.IsNullOrWhiteSpace(callerDirectory))
{
if (string.Equals(
Path.GetFileName(callerDirectory),
"Snapshots",
StringComparison.OrdinalIgnoreCase))
{
return callerDirectory;
}
var siblingSnapshots = Path.Combine(callerDirectory, "Snapshots");
if (Directory.Exists(siblingSnapshots))
{
return siblingSnapshots;
}
}
}
return Path.Combine(Directory.GetCurrentDirectory(), "Snapshots");
}
///
/// Asserts that the value matches the stored snapshot. If UPDATE_SNAPSHOTS=1, updates the snapshot.
///
/// The value to snapshot (will be JSON-serialized).
/// The snapshot name (filename without extension).
/// Optional directory for snapshots (default: "Snapshots" in test project).
public static void MatchesSnapshot(
T value,
string snapshotName,
string? snapshotsDirectory = null,
[CallerFilePath] string callerFilePath = "")
{
snapshotsDirectory ??= ResolveDefaultSnapshotsDirectory(callerFilePath);
Directory.CreateDirectory(snapshotsDirectory);
string snapshotPath = Path.Combine(snapshotsDirectory, $"{snapshotName}.json");
// Serialize to pretty JSON for readability
string actualJson = JsonSerializer.Serialize(value, new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
if (UpdateSnapshotsMode)
{
// Update snapshot
File.WriteAllText(snapshotPath, actualJson, Encoding.UTF8);
return; // Don't assert in update mode
}
// Verify snapshot exists
Assert.True(File.Exists(snapshotPath),
$"Snapshot '{snapshotName}' not found at {snapshotPath}. Run with UPDATE_SNAPSHOTS=1 to create it.");
// Compare with stored snapshot
string expectedJson = File.ReadAllText(snapshotPath, Encoding.UTF8);
Assert.Equal(expectedJson, actualJson);
}
///
/// Asserts that the text matches the stored snapshot.
///
public static void MatchesTextSnapshot(
string value,
string snapshotName,
string? snapshotsDirectory = null,
[CallerFilePath] string callerFilePath = "")
{
snapshotsDirectory ??= ResolveDefaultSnapshotsDirectory(callerFilePath);
Directory.CreateDirectory(snapshotsDirectory);
string snapshotPath = Path.Combine(snapshotsDirectory, $"{snapshotName}.txt");
if (UpdateSnapshotsMode)
{
File.WriteAllText(snapshotPath, value, Encoding.UTF8);
return;
}
Assert.True(File.Exists(snapshotPath),
$"Snapshot '{snapshotName}' not found at {snapshotPath}. Run with UPDATE_SNAPSHOTS=1 to create it.");
string expected = File.ReadAllText(snapshotPath, Encoding.UTF8);
Assert.Equal(expected, value);
}
///
/// Asserts that binary data matches the stored snapshot.
///
public static void MatchesBinarySnapshot(
byte[] value,
string snapshotName,
string? snapshotsDirectory = null,
[CallerFilePath] string callerFilePath = "")
{
snapshotsDirectory ??= ResolveDefaultSnapshotsDirectory(callerFilePath);
Directory.CreateDirectory(snapshotsDirectory);
string snapshotPath = Path.Combine(snapshotsDirectory, $"{snapshotName}.bin");
if (UpdateSnapshotsMode)
{
File.WriteAllBytes(snapshotPath, value);
return;
}
Assert.True(File.Exists(snapshotPath),
$"Snapshot '{snapshotName}' not found at {snapshotPath}. Run with UPDATE_SNAPSHOTS=1 to create it.");
byte[] expected = File.ReadAllBytes(snapshotPath);
Assert.Equal(expected, value);
}
}