186d03e5cc
ResolveRepositoryRoot accepts an optional stopBoundary parameter that caps the upward walk; production callers pass null and behavior is unchanged. The two repository-marker tests now seal their walkers inside their own temp directories, so a redirected TMP or a co-located C:\src checkout no longer leaks ambient marker-bearing ancestors into the assertion. Regression test ResolveRepositoryRoot_StopBoundary_IsolatesWalkerFromAmbientAncestorMarkers constructs an outer ancestor that carries src/ + .git, confirms the walker leaks into it without the boundary, then asserts the same call throws with the boundary supplied. Resolved at 2026-05-24; IntegrationTestEnvironmentTests 5/5 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
6.5 KiB
C#
151 lines
6.5 KiB
C#
namespace ZB.MOM.WW.MxGateway.IntegrationTests;
|
|
|
|
public sealed class IntegrationTestEnvironmentTests
|
|
{
|
|
/// <summary>Verifies that live MXAccess tests use correct environment variable name.</summary>
|
|
[Fact]
|
|
public void LiveMxAccessTests_AreOptInByEnvironmentVariable()
|
|
{
|
|
Assert.Equal(
|
|
"MXGATEWAY_RUN_LIVE_MXACCESS_TESTS",
|
|
IntegrationTestEnvironment.LiveMxAccessVariableName);
|
|
}
|
|
|
|
/// <summary>Verifies that worker executable uses correct environment variable name.</summary>
|
|
[Fact]
|
|
public void LiveMxAccessWorkerExecutable_UsesDocumentedEnvironmentVariable()
|
|
{
|
|
Assert.Equal(
|
|
"MXGATEWAY_LIVE_MXACCESS_WORKER_EXE",
|
|
IntegrationTestEnvironment.LiveMxAccessWorkerExecutableVariableName);
|
|
}
|
|
|
|
/// <summary>Verifies that repository root resolution accepts git worktree files.</summary>
|
|
[Fact]
|
|
public void ResolveRepositoryRoot_AcceptsGitWorktreeFile()
|
|
{
|
|
string temporaryRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
|
string nestedDirectory = Path.Combine(temporaryRoot, "tests", "bin");
|
|
|
|
try
|
|
{
|
|
Directory.CreateDirectory(nestedDirectory);
|
|
Directory.CreateDirectory(Path.Combine(temporaryRoot, "src"));
|
|
File.WriteAllText(Path.Combine(temporaryRoot, ".git"), "gitdir: ../.git/worktrees/test");
|
|
|
|
// Pass temporaryRoot as the stop-boundary so the walker can never leak
|
|
// into ambient ancestors of Path.GetTempPath() (IntegrationTests-025).
|
|
string repositoryRoot = IntegrationTestEnvironment.ResolveRepositoryRoot(
|
|
nestedDirectory,
|
|
stopBoundary: temporaryRoot);
|
|
|
|
Assert.Equal(temporaryRoot, repositoryRoot);
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(temporaryRoot))
|
|
{
|
|
Directory.Delete(temporaryRoot, recursive: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that <see cref="IntegrationTestEnvironment.ResolveRepositoryRoot"/>
|
|
/// throws <see cref="InvalidOperationException"/> with a diagnostic message when
|
|
/// the walk exhausts without finding a repository root. The previous silent
|
|
/// fallback to <c>Directory.GetCurrentDirectory()</c> masked misconfiguration
|
|
/// (IntegrationTests-022); operators get a clear, actionable failure instead.
|
|
/// The <c>stopBoundary</c> isolates the walker from ambient ancestors of
|
|
/// <see cref="Path.GetTempPath"/> (a redirected <c>TMP</c>, a co-located checkout
|
|
/// at <c>C:\src</c>, etc.) that could otherwise satisfy
|
|
/// <c>IsRepositoryRoot</c> and make this assertion flake on contributor or CI
|
|
/// boxes — see IntegrationTests-025.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ResolveRepositoryRoot_NoMarkers_ThrowsInvalidOperationExceptionNamingStartAndMarkers()
|
|
{
|
|
string isolatedRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
|
string isolatedStart = Path.Combine(isolatedRoot, "nested");
|
|
|
|
try
|
|
{
|
|
Directory.CreateDirectory(isolatedStart);
|
|
|
|
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
|
|
() => IntegrationTestEnvironment.ResolveRepositoryRoot(
|
|
isolatedStart,
|
|
stopBoundary: isolatedRoot));
|
|
|
|
Assert.Contains(isolatedStart, ex.Message, StringComparison.Ordinal);
|
|
Assert.Contains(".git", ex.Message, StringComparison.Ordinal);
|
|
Assert.Contains(".sln", ex.Message, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
IntegrationTestEnvironment.LiveMxAccessWorkerExecutableVariableName,
|
|
ex.Message,
|
|
StringComparison.Ordinal);
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(isolatedRoot))
|
|
{
|
|
Directory.Delete(isolatedRoot, recursive: true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the <c>stopBoundary</c> parameter on
|
|
/// <see cref="IntegrationTestEnvironment.ResolveRepositoryRoot"/> isolates the
|
|
/// walker from ambient ancestors that happen to satisfy <c>IsRepositoryRoot</c>
|
|
/// — the precise failure mode IntegrationTests-025 describes. The test
|
|
/// deliberately constructs an outer directory that *does* carry repository-root
|
|
/// markers (<c>src/</c> + <c>.git</c>) and an inner isolated chain that does
|
|
/// not. Without the boundary the walker would happily stop at the outer
|
|
/// directory; with the boundary it must throw because the chain it can see
|
|
/// carries no markers. A future refactor that dropped or mis-honored the
|
|
/// boundary would surface here as a failed assertion instead of a silent flake
|
|
/// in CI.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ResolveRepositoryRoot_StopBoundary_IsolatesWalkerFromAmbientAncestorMarkers()
|
|
{
|
|
string outerRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
|
string innerBoundary = Path.Combine(outerRoot, "inner");
|
|
string isolatedStart = Path.Combine(innerBoundary, "nested");
|
|
|
|
try
|
|
{
|
|
// Outer directory satisfies IsRepositoryRoot — it is the "ambient
|
|
// ancestor" the production walker would otherwise stop at.
|
|
Directory.CreateDirectory(Path.Combine(outerRoot, "src"));
|
|
File.WriteAllText(Path.Combine(outerRoot, ".git"), "gitdir: ../.git/worktrees/test");
|
|
|
|
// Inner chain carries no markers. The boundary sits between the inner
|
|
// chain and the outer marker-bearing ancestor.
|
|
Directory.CreateDirectory(isolatedStart);
|
|
|
|
// Sanity: without the boundary the production walker reaches outerRoot
|
|
// and silently returns it — the exact ambient-ancestor leak.
|
|
string leakedRoot = IntegrationTestEnvironment.ResolveRepositoryRoot(isolatedStart);
|
|
Assert.Equal(outerRoot, leakedRoot);
|
|
|
|
// With the boundary the walker is sealed inside the inner chain and
|
|
// must throw — the marker on outerRoot is invisible to it.
|
|
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
|
|
() => IntegrationTestEnvironment.ResolveRepositoryRoot(
|
|
isolatedStart,
|
|
stopBoundary: innerBoundary));
|
|
|
|
Assert.Contains(isolatedStart, ex.Message, StringComparison.Ordinal);
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(outerRoot))
|
|
{
|
|
Directory.Delete(outerRoot, recursive: true);
|
|
}
|
|
}
|
|
}
|
|
}
|