diff --git a/Directory.Packages.props b/Directory.Packages.props index 56b21debb7..5a890837dc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -42,6 +42,7 @@ + diff --git a/src/Elastic.Codex/Building/CodexBuildService.cs b/src/Elastic.Codex/Building/CodexBuildService.cs index 71d6a5a43b..d82ce54115 100644 --- a/src/Elastic.Codex/Building/CodexBuildService.cs +++ b/src/Elastic.Codex/Building/CodexBuildService.cs @@ -25,6 +25,7 @@ using Elastic.Markdown.Exporters; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Codex.Building; @@ -46,7 +47,7 @@ public class CodexBuildService( public async Task BuildAll( CodexContext context, CodexCloneResult cloneResult, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, Cancel ctx, IReadOnlySet? exporters = null) { @@ -66,7 +67,7 @@ public async Task BuildAll( var buildContexts = new List(); var environment = context.Configuration.Environment ?? "internal"; - using var codexLinkIndexReader = new GitLinkIndexReader(environment, context.ReadFileSystem, skipFetch: true); + using var codexLinkIndexReader = new GitLinkIndexReader(environment, FileSystemFactory.AppData, skipFetch: true); // Phase 1: Load and parse all documentation sets foreach (var checkout in cloneResult.Checkouts) @@ -136,7 +137,7 @@ public async Task BuildAll( private async Task LoadDocumentationSet( CodexContext context, CodexCheckout checkout, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, ILinkIndexReader codexLinkIndexReader, Cancel ctx) { @@ -401,10 +402,10 @@ internal sealed class CodexDocumentationContext(CodexContext codexContext) : ICo public IDiagnosticsCollector Collector => codexContext.Collector; /// - public IFileSystem ReadFileSystem => codexContext.ReadFileSystem; + public ScopedFileSystem ReadFileSystem => codexContext.ReadFileSystem; /// - public IFileSystem WriteFileSystem => codexContext.WriteFileSystem; + public ScopedFileSystem WriteFileSystem => codexContext.WriteFileSystem; /// public IDirectoryInfo OutputDirectory => codexContext.OutputDirectory; diff --git a/src/Elastic.Codex/CodexContext.cs b/src/Elastic.Codex/CodexContext.cs index f8ff2104e9..0f2f544d5c 100644 --- a/src/Elastic.Codex/CodexContext.cs +++ b/src/Elastic.Codex/CodexContext.cs @@ -6,6 +6,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Codex; using Elastic.Documentation.Diagnostics; +using Nullean.ScopedFileSystem; namespace Elastic.Codex; @@ -14,8 +15,8 @@ namespace Elastic.Codex; /// public class CodexContext { - public IFileSystem ReadFileSystem { get; } - public IFileSystem WriteFileSystem { get; } + public ScopedFileSystem ReadFileSystem { get; } + public ScopedFileSystem WriteFileSystem { get; } public IDiagnosticsCollector Collector { get; } public CodexConfiguration Configuration { get; } public IFileInfo ConfigurationPath { get; } @@ -34,8 +35,8 @@ public CodexContext( CodexConfiguration configuration, IFileInfo configurationPath, IDiagnosticsCollector collector, - IFileSystem readFileSystem, - IFileSystem writeFileSystem, + ScopedFileSystem readFileSystem, + ScopedFileSystem writeFileSystem, string? checkoutDirectory, string? outputDirectory) { @@ -45,10 +46,12 @@ public CodexContext( ReadFileSystem = readFileSystem; WriteFileSystem = writeFileSystem; - var defaultCheckoutDirectory = Path.Join(Paths.GitCommonRoot.FullName, ".artifacts", "codex", "clone"); - CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? defaultCheckoutDirectory); + var defaultCheckoutDirectory = Path.Join(Paths.ApplicationData.FullName, "codex", "clone"); + CheckoutDirectory = checkoutDirectory is null + ? FileSystemFactory.AppData.DirectoryInfo.New(defaultCheckoutDirectory) + : ReadFileSystem.DirectoryInfo.New(checkoutDirectory); var defaultOutputDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "codex", "docs"); - OutputDirectory = ReadFileSystem.DirectoryInfo.New(outputDirectory ?? defaultOutputDirectory); + OutputDirectory = WriteFileSystem.DirectoryInfo.New(outputDirectory ?? defaultOutputDirectory); } } diff --git a/src/Elastic.Codex/Indexing/CodexIndexService.cs b/src/Elastic.Codex/Indexing/CodexIndexService.cs index 5a4de17046..c1235c58be 100644 --- a/src/Elastic.Codex/Indexing/CodexIndexService.cs +++ b/src/Elastic.Codex/Indexing/CodexIndexService.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Isolated; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Codex.Indexing; @@ -30,7 +31,7 @@ IsolatedBuildService isolatedBuildService public async Task Index( CodexContext codexContext, CodexCloneResult cloneResult, - FileSystem fileSystem, + ScopedFileSystem fileSystem, ElasticsearchIndexOptions esOptions, Cancel ctx = default) { diff --git a/src/Elastic.Documentation.Configuration/BuildContext.cs b/src/Elastic.Documentation.Configuration/BuildContext.cs index 64301d8333..85db4b731a 100644 --- a/src/Elastic.Documentation.Configuration/BuildContext.cs +++ b/src/Elastic.Documentation.Configuration/BuildContext.cs @@ -13,6 +13,7 @@ using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Configuration; @@ -21,8 +22,8 @@ public record BuildContext : IDocumentationSetContext, IDocumentationConfigurati public static string Version { get; } = Assembly.GetExecutingAssembly().GetCustomAttributes() .FirstOrDefault()?.InformationalVersion ?? "0.0.0"; - public IFileSystem ReadFileSystem { get; } - public IFileSystem WriteFileSystem { get; } + public ScopedFileSystem ReadFileSystem { get; } + public ScopedFileSystem WriteFileSystem { get; } public IReadOnlySet AvailableExporters { get; } public IDirectoryInfo? DocumentationCheckoutDirectory { get; } @@ -72,7 +73,7 @@ public string? UrlPathPrefix public BuildContext( IDiagnosticsCollector collector, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, IConfigurationContext configurationContext ) : this(collector, fileSystem, fileSystem, configurationContext, ExportOptions.Default, null, null) @@ -81,8 +82,8 @@ IConfigurationContext configurationContext public BuildContext( IDiagnosticsCollector collector, - IFileSystem readFileSystem, - IFileSystem writeFileSystem, + ScopedFileSystem readFileSystem, + ScopedFileSystem writeFileSystem, IConfigurationContext configurationContext, IReadOnlySet availableExporters, string? source = null, @@ -107,7 +108,7 @@ public BuildContext( (DocumentationSourceDirectory, ConfigurationPath) = Paths.FindDocsFolderFromRoot(ReadFileSystem, rootFolder); - DocumentationCheckoutDirectory = Paths.DetermineSourceDirectoryRoot(DocumentationSourceDirectory); + DocumentationCheckoutDirectory = Paths.FindGitRoot(DocumentationSourceDirectory); OutputDirectory = !string.IsNullOrWhiteSpace(output) ? WriteFileSystem.DirectoryInfo.New(output) diff --git a/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs index c9eb037037..4fd9970baf 100644 --- a/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs +++ b/src/Elastic.Documentation.Configuration/ConfigurationFileProvider.cs @@ -45,7 +45,11 @@ public ConfigurationFileProvider( _fileSystem = fileSystem; _assemblyName = typeof(ConfigurationFileProvider).Assembly.GetName().Name!; SkipPrivateRepositories = skipPrivateRepositories; - TemporaryDirectory = fileSystem.Directory.CreateTempSubdirectory("docs-builder-config"); + // Use a unique subdirectory per instance to avoid file-locking collisions when + // multiple processes or parallel tests share the same ApplicationData path. + var configRuntimeDir = Path.Join(Paths.ApplicationData.FullName, "config-runtime", Guid.NewGuid().ToString("N")); + TemporaryDirectory = fileSystem.DirectoryInfo.New(configRuntimeDir); + TemporaryDirectory.Create(); // TODO: This doesn't work as expected if a github actions consumer repo has a `config` directory. // ConfigurationSource = configurationSource ?? ( @@ -267,7 +271,7 @@ public static IServiceCollection AddConfigurationFileProvider(this IServiceColle { using var sp = services.BuildServiceProvider(); var logFactory = sp.GetRequiredService(); - var provider = new ConfigurationFileProvider(logFactory, new FileSystem(), skipPrivateRepositories, configurationSource); + var provider = new ConfigurationFileProvider(logFactory, FileSystemFactory.RealRead, skipPrivateRepositories, configurationSource); _ = services.AddSingleton(provider); configure(services, provider); return services; diff --git a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj index ff9d136a49..83887a031e 100644 --- a/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj +++ b/src/Elastic.Documentation.Configuration/Elastic.Documentation.Configuration.csproj @@ -9,15 +9,17 @@ - + + + - - + + diff --git a/src/Elastic.Documentation.Configuration/FileSystemFactory.cs b/src/Elastic.Documentation.Configuration/FileSystemFactory.cs new file mode 100644 index 0000000000..50870d5d53 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/FileSystemFactory.cs @@ -0,0 +1,211 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using Nullean.ScopedFileSystem; + +namespace Elastic.Documentation.Configuration; + +public static class FileSystemFactory +{ + // Read options: workspace + app data, all confirmed hidden names allowed. + // Includes .git (GitCheckoutInformation reads it) and .artifacts/.doc.state + // (incremental build reads existing output state). + private static readonly ScopedFileSystemOptions WorkingDirectoryReadOptions = new( + [Paths.WorkingDirectoryRoot.FullName, Paths.ApplicationData.FullName]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".doc.state" } + }; + + // Write options: same scope roots but no .git — nothing in the build output + // pipeline should ever write into the git repository metadata. + // Temp is allowed because deploy operations (e.g. S3 sync) stage files there. + private static readonly ScopedFileSystemOptions WorkingDirectoryWriteOptions = new( + [Paths.WorkingDirectoryRoot.FullName, Paths.ApplicationData.FullName]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".doc.state" }, + AllowedSpecialFolders = AllowedSpecialFolder.Temp + }; + + // AppData-only options: for components that only access caches/state files. + private static readonly ScopedFileSystemOptions AppDataOptions = new([Paths.ApplicationData.FullName]) + { + // .git needed for codex-link-index clone directory inside ApplicationData + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git" } + }; + + /// + /// A pre-allocated for reading workspace files. + /// Scoped to the working directory root and per-user app data; allows .git + /// (read by GitCheckoutInformation), .artifacts and .doc.state + /// (read for incremental build state). + /// + public static ScopedFileSystem RealRead { get; } = new(new FileSystem(), WorkingDirectoryReadOptions); + + /// + /// A pre-allocated for writing build output. + /// Same scope as but without .git access — + /// nothing in the output pipeline should write into git repository metadata. + /// + public static ScopedFileSystem RealWrite { get; } = new(new FileSystem(), WorkingDirectoryWriteOptions); + + /// + /// A pre-allocated scoped only to the per-user + /// elastic/docs-builder application data folder. Use for components that + /// access caches or state and have no need for workspace files + /// (e.g. CrossLinkFetcher, CheckForUpdatesFilter, GitLinkIndexReader). + /// + public static ScopedFileSystem AppData { get; } = new(new FileSystem(), AppDataOptions); + + /// + /// Creates a new wrapping a fresh , + /// using the working-directory read options. Each call returns a new independent in-memory file system. + /// + public static ScopedFileSystem InMemory() => new(new MockFileSystem(), WorkingDirectoryReadOptions); + + /// + /// Scopes to and + /// for reading. Use when the inner FS contains files + /// that live within the current working-directory tree (e.g. a test MockFileSystem + /// seeded with workspace-relative paths). + /// + public static ScopedFileSystem ScopeCurrentWorkingDirectory(IFileSystem inner) => + new(inner, WorkingDirectoryReadOptions); + + /// + /// Scopes to and + /// for reading, extended by + /// (e.g. detection-rules folders declared via + /// ). + /// + public static ScopedFileSystem ScopeCurrentWorkingDirectory(IFileSystem inner, IEnumerable? extensionRoots) + { + if (extensionRoots is null) + return ScopeCurrentWorkingDirectory(inner); + + var roots = new[] { Paths.WorkingDirectoryRoot.FullName, Paths.ApplicationData.FullName } + .Concat(extensionRoots) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + if (roots.Length == 2) + return ScopeCurrentWorkingDirectory(inner); + + return new ScopedFileSystem(inner, new ScopedFileSystemOptions(roots) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".doc.state" } + }); + } + + // Builds write options that include AllowedSpecialFolders.Temp PLUS the inner FS's own + // GetTempPath() as an explicit root — but only when the inner FS is MockFileSystem. + // + // On non-Windows MockFileSystem hardcodes a Unix-ified path ("/temp/", derived from "C:\temp") + // instead of calling System.IO.Path.GetTempPath(). AllowedSpecialFolder.Temp uses the real + // GetTempPath() (e.g. "/tmp/" on Linux), so the two diverge and scope validation fails for any + // path created via mockFs.Path.GetTempPath(). + // + // Fix tracked upstream: https://github.com/TestableIO/System.IO.Abstractions/pull/1454 + // Once that ships and we update the package reference we can drop this workaround. + // + // We use ScopedFileSystem.InnerType (added in Nullean.ScopedFileSystem 0.4.0) to avoid a + // fragile string-based type check. + private static ScopedFileSystemOptions BuildWriteOptions(IFileSystem inner, params string[] roots) + { + var allRoots = roots.ToList(); + var innerType = inner is ScopedFileSystem sf ? sf.InnerType : inner.GetType(); + if (!OperatingSystem.IsWindows() && innerType.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)) + { + // Cover MockFileSystem's unixified hardcoded temp path + var innerTemp = inner.Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + if (!string.IsNullOrEmpty(innerTemp) && !allRoots.Contains(innerTemp, StringComparer.OrdinalIgnoreCase)) + allRoots.Add(innerTemp); + } + return new ScopedFileSystemOptions([.. allRoots]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".doc.state" }, + AllowedSpecialFolders = AllowedSpecialFolder.Temp + }; + } + + /// + /// Scopes to and + /// for writing (.git not allowed). Use when + /// the inner FS writes into the working-directory tree. + /// + public static ScopedFileSystem ScopeCurrentWorkingDirectoryForWrite(IFileSystem inner) => + new(inner, BuildWriteOptions( + inner, Paths.WorkingDirectoryRoot.FullName, Paths.ApplicationData.FullName)); + + /// + /// Scopes to an explicit and + /// for reading. Use when the files to be read live under + /// a specific known root that is not — for example + /// test fixtures with assembler-checkout paths or service code operating on a given directory. + /// + public static ScopedFileSystem ScopeSourceDirectory(IFileSystem inner, string sourceRoot) => + new(inner, new ScopedFileSystemOptions([sourceRoot, Paths.ApplicationData.FullName]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".doc.state" } + }); + + /// + /// Scopes to an explicit and + /// for writing (.git not allowed). Write variant + /// of . + /// + public static ScopedFileSystem ScopeSourceDirectoryForWrite(IFileSystem inner, string sourceRoot) => + new(inner, BuildWriteOptions(inner, sourceRoot, Paths.ApplicationData.FullName)); + + /// + /// Creates a read scoped to the git root of + /// . Falls back to when + /// is . Use in commands that accept an explicit --path argument. + /// + /// Suitable for command-layer code. Service-layer tests use directly + /// and do not exercise this method. + /// + /// + public static ScopedFileSystem RealGitRootForPath(string? path) + { + if (path is null) + return RealRead; + var root = Paths.FindGitRoot(path); + return new ScopedFileSystem(new FileSystem(), new ScopedFileSystemOptions([root, Paths.ApplicationData.FullName]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".doc.state" } + }); + } + + /// + /// Creates a write scoped to the git root of + /// (and if it falls outside that root). + /// Falls back to when both are . + /// Use in commands that accept explicit --path and/or --output arguments. + /// + public static ScopedFileSystem RealGitRootForPathWrite(string? path, string? output = null) + { + if (path is null && output is null) + return RealWrite; + + var gitRoot = path is not null ? Paths.FindGitRoot(path) : Paths.WorkingDirectoryRoot.FullName; + var roots = new List { gitRoot, Paths.ApplicationData.FullName }; + + var plain = new FileSystem(); + if (output is not null) + { + var absOutput = Path.IsPathRooted(output) ? output : Path.GetFullPath(output); + if (!plain.DirectoryInfo.New(absOutput).IsSubPathOf(plain.DirectoryInfo.New(gitRoot))) + roots.Add(absOutput); + } + + return new ScopedFileSystem(plain, BuildWriteOptions(plain, [.. roots])); + } +} diff --git a/src/Elastic.Documentation.Configuration/Paths.cs b/src/Elastic.Documentation.Configuration/Paths.cs index 47d2468074..bd3e2085be 100644 --- a/src/Elastic.Documentation.Configuration/Paths.cs +++ b/src/Elastic.Documentation.Configuration/Paths.cs @@ -11,79 +11,113 @@ public static class Paths { public static readonly DirectoryInfo WorkingDirectoryRoot = DetermineWorkingDirectoryRoot(); - public static readonly DirectoryInfo GitCommonRoot = InitGitCommonRoot(); - public static readonly DirectoryInfo ApplicationData = GetApplicationFolder(); - private static DirectoryInfo DetermineWorkingDirectoryRoot() + /// + /// Walks up from until a .git directory or file + /// (worktree pointer) is found and returns that ancestor. Returns + /// itself when no git root is found within the allowed depth. + /// + /// + /// Depth protection: in release builds the .git anchor must be at most 1 directory + /// above — documentation is not expected to live deep inside + /// a repo. In debug builds a deeper .git is accepted when a *.slnx file is + /// adjacent (developer running the binary from an IDE output directory). + /// + public static string FindGitRoot(string startPath) { - var directory = new DirectoryInfo(Directory.GetCurrentDirectory()); - while (directory != null) + var resolved = Path.IsPathRooted(startPath) ? startPath : Path.GetFullPath(startPath); + var dir = Directory.Exists(resolved) + ? new DirectoryInfo(resolved) + : new DirectoryInfo(Path.GetDirectoryName(resolved) ?? resolved); + var startDir = dir.FullName; // always a directory, used as fallback + var depth = 0; + while (dir != null) { - if (directory.GetFiles("*.slnx").Length > 0) - break; - if (directory.GetDirectories(".git").Length > 0) - break; - // support for git worktrees - if (directory.GetFiles(".git").Length > 0) - break; - directory = directory.Parent; + var hasGit = dir.GetDirectories(".git").Length > 0 || dir.GetFiles(".git").Length > 0; + if (hasGit) + { +#if DEBUG + if (depth <= 1 || dir.GetFiles("*.slnx").Length > 0) + return dir.FullName; +#else + if (depth <= 1) + return dir.FullName; +#endif + // .git found but too deep — stop searching + return startDir; + } + depth++; + dir = dir.Parent; } - return directory ?? new DirectoryInfo(Directory.GetCurrentDirectory()); + return startDir; } - public static IDirectoryInfo? DetermineSourceDirectoryRoot(IDirectoryInfo sourceDirectory) + /// + /// Walks up from via until + /// a .git directory or file (worktree pointer) is found. + /// Returns if no git root is found within the allowed depth. + /// + /// Same depth protection as . + public static IDirectoryInfo? FindGitRoot(IDirectoryInfo startDirectory) { - IDirectoryInfo? sourceRoot = null; - var directory = sourceDirectory; - while (directory != null && directory.GetDirectories(".git").Length == 0) + var directory = startDirectory; + var depth = 0; + while (directory != null) { - if (directory.GetDirectories(".git").Length > 0) - break; - // support for git worktrees - if (directory.GetFiles(".git").Length > 0) - break; - + var hasGit = directory.GetDirectories(".git").Length > 0 + || directory.GetFiles(".git").Length > 0; + if (hasGit) + { +#if DEBUG + if (depth <= 1 || directory.GetFiles("*.slnx").Length > 0) + return directory; +#else + if (depth <= 1) + return directory; +#endif + // .git found but too deep + return null; + } + depth++; directory = directory.Parent; } - sourceRoot ??= directory; - return sourceRoot; - } - - /// Resolves the root of the main git repository, following worktree links when present. Disabled on CI. - public static IDirectoryInfo ResolveGitCommonRoot(IFileSystem fileSystem, IDirectoryInfo workingDirectoryRoot, bool? isCI = null) - { - if (isCI ?? !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"))) - return workingDirectoryRoot; - - var gitPath = Path.Join(workingDirectoryRoot.FullName, ".git"); - - if (fileSystem.Directory.Exists(gitPath)) - return workingDirectoryRoot; - - if (!fileSystem.File.Exists(gitPath)) - return workingDirectoryRoot; - - var content = fileSystem.File.ReadAllText(gitPath).Trim(); - if (!content.StartsWith("gitdir:", StringComparison.OrdinalIgnoreCase)) - return workingDirectoryRoot; - - var gitDirPath = content["gitdir:".Length..].Trim(); - if (!Path.IsPathRooted(gitDirPath)) - gitDirPath = Path.GetFullPath(gitDirPath, workingDirectoryRoot.FullName); - - var dir = fileSystem.DirectoryInfo.New(gitDirPath); - while (dir != null && dir.Name != ".git") - dir = dir.Parent; - - return dir?.Parent ?? workingDirectoryRoot; + return null; } - private static DirectoryInfo InitGitCommonRoot() + private static DirectoryInfo DetermineWorkingDirectoryRoot() { - var fs = new FileSystem(); - var root = fs.DirectoryInfo.New(WorkingDirectoryRoot.FullName); - return new DirectoryInfo(ResolveGitCommonRoot(fs, root).FullName); + var cwd = new DirectoryInfo(Directory.GetCurrentDirectory()); + var directory = cwd; + var depth = 0; + while (directory != null) + { + // *.slnx is the primary anchor: always adopt it at any depth. + // This covers both the local developer case (running from the IDE output directory + // such as bin/Debug/net10.0/) and CI (Aspire starts the binary from the project + // directory, which is several levels below the solution root). + if (directory.GetFiles("*.slnx").Length > 0) + return directory; + var hasGit = directory.GetDirectories(".git").Length > 0 + || directory.GetFiles(".git").Length > 0; + if (hasGit) + { + // Only accept .git beyond 1 level up in debug when a *.slnx is adjacent + // (developer running from IDE output directory such as bin/Debug/net10.0/). +#if DEBUG + if (depth <= 1 || directory.GetFiles("*.slnx").Length > 0) + return directory; +#else + if (depth <= 1) + return directory; +#endif + // .git found but too deep — stop without adopting it + return cwd; + } + depth++; + directory = directory.Parent; + } + return cwd; } /// Used in debug to locate static folder, so we can change js/css files while the server is running @@ -101,6 +135,14 @@ private static DirectoryInfo InitGitCommonRoot() private static DirectoryInfo GetApplicationFolder() { var localPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if (string.IsNullOrEmpty(localPath)) + { + // Docker / CI containers often have no XDG_DATA_HOME or HOME configured, + // causing LocalApplicationData to return "". Path.Join("", ...) produces a + // relative path that resolves under CWD, becoming a subdirectory of + // WorkingDirectoryRoot and breaking the disjoint-scope-roots requirement. + localPath = Path.GetTempPath(); + } var elasticPath = Path.Join(localPath, "elastic", "docs-builder"); return new DirectoryInfo(elasticPath); } diff --git a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs index 9ba12a78bf..c2bf385b95 100644 --- a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs +++ b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs @@ -7,6 +7,7 @@ using Elastic.Documentation.Configuration.Toc.DetectionRules; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Extensions; +using Nullean.ScopedFileSystem; using YamlDotNet.Serialization; using static Elastic.Documentation.Configuration.SymlinkValidator; @@ -96,9 +97,9 @@ public static DocumentationSetFile LoadMetadata(IFileInfo file) /// replacing them with their resolved children and ensuring file paths carry over parent paths. /// Validates the table of contents structure and emits diagnostics for issues. /// - public static DocumentationSetFile LoadAndResolve(IDiagnosticsCollector collector, IFileInfo docsetPath, IFileSystem? fileSystem = null, HashSet? noSuppress = null) + public static DocumentationSetFile LoadAndResolve(IDiagnosticsCollector collector, IFileInfo docsetPath, ScopedFileSystem? fileSystem = null, HashSet? noSuppress = null) { - fileSystem ??= docsetPath.FileSystem; + fileSystem ??= FileSystemFactory.ScopeSourceDirectory(docsetPath.FileSystem, docsetPath.Directory!.FullName); // Validate that the docset.yml is not a symlink (security: prevents path traversal attacks) EnsureNotSymlink(docsetPath); var yaml = fileSystem.File.ReadAllText(docsetPath.FullName); @@ -118,9 +119,9 @@ public static DocumentationSetFile LoadAndResolve(IDiagnosticsCollector collecto /// replacing them with their resolved children and ensuring file paths carry over parent paths. /// Validates the table of contents structure and emits diagnostics for issues. /// - public static DocumentationSetFile LoadAndResolve(IDiagnosticsCollector collector, string yaml, IDirectoryInfo sourceDirectory, IFileSystem? fileSystem = null, HashSet? noSuppress = null) + public static DocumentationSetFile LoadAndResolve(IDiagnosticsCollector collector, string yaml, IDirectoryInfo sourceDirectory, ScopedFileSystem? fileSystem = null, HashSet? noSuppress = null) { - fileSystem ??= sourceDirectory.FileSystem; + fileSystem ??= FileSystemFactory.ScopeSourceDirectory(sourceDirectory.FileSystem, sourceDirectory.FullName); var docSet = Deserialize(yaml); var docsetPath = fileSystem.Path.Join(sourceDirectory.FullName, "docset.yml").OptionalWindowsReplace(); docSet.SuppressDiagnostics.ExceptWith(noSuppress ?? []); diff --git a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj index e86f0f7745..1fef2ce2a0 100644 --- a/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj +++ b/src/Elastic.Documentation.LinkIndex/Elastic.Documentation.LinkIndex.csproj @@ -8,10 +8,12 @@ + + diff --git a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs index 901aff790c..d302caa764 100644 --- a/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs +++ b/src/Elastic.Documentation.LinkIndex/GitLinkIndexReader.cs @@ -4,7 +4,9 @@ using System.Diagnostics; using System.IO.Abstractions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Links; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.LinkIndex; @@ -16,8 +18,7 @@ public class GitLinkIndexReader : ILinkIndexReader, IDisposable { private const string LinkIndexOrigin = "elastic/codex-link-index"; private static readonly string CloneDirectory = Path.Join( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".docs-builder", + Paths.ApplicationData.FullName, "codex-link-index"); private readonly string _environment; @@ -26,13 +27,13 @@ public class GitLinkIndexReader : ILinkIndexReader, IDisposable private readonly SemaphoreSlim _cloneLock = new(1, 1); private bool _ensuredClone; - public GitLinkIndexReader(string environment, IFileSystem? fileSystem = null, bool skipFetch = false) + public GitLinkIndexReader(string environment, ScopedFileSystem? fileSystem = null, bool skipFetch = false) { if (string.IsNullOrWhiteSpace(environment)) throw new ArgumentException("Environment must be specified in the codex configuration (e.g., 'internal', 'security').", nameof(environment)); _environment = environment; - _fileSystem = fileSystem ?? new FileSystem(); + _fileSystem = fileSystem ?? FileSystemFactory.AppData; _skipFetch = skipFetch; } diff --git a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs index f48379cde9..c0c7d21e76 100644 --- a/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs +++ b/src/Elastic.Documentation.Links/CrossLinks/CrossLinkFetcher.cs @@ -4,11 +4,13 @@ using System.Collections.Concurrent; using System.Collections.Frozen; +using System.IO.Abstractions; using System.Text.Json; using Elastic.Documentation.Configuration; using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Serialization; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Links.CrossLinks; @@ -42,9 +44,10 @@ public record FetchedCrossLinks }; } -public abstract class CrossLinkFetcher(ILoggerFactory logFactory, ILinkIndexReader linkIndexProvider) : IDisposable +public abstract class CrossLinkFetcher(ILoggerFactory logFactory, ILinkIndexReader linkIndexProvider, ScopedFileSystem? fileSystem = null) : IDisposable { protected ILogger Logger { get; } = logFactory.CreateLogger(nameof(CrossLinkFetcher)); + private readonly IFileSystem _fileSystem = fileSystem ?? FileSystemFactory.AppData; private LinkRegistry? _linkIndex; public static RepositoryLinks Deserialize(string json) => @@ -146,12 +149,12 @@ private void WriteLinksJsonCachedFile(string repository, LinkRegistryEntry linkR { var cachedFileName = $"links-elastic-{repository}-{linkRegistryEntry.Branch}-{linkRegistryEntry.ETag}.json"; var cachedPath = Path.Join(Paths.ApplicationData.FullName, "links", cachedFileName); - if (File.Exists(cachedPath)) + if (_fileSystem.File.Exists(cachedPath)) return; try { - _ = Directory.CreateDirectory(Path.GetDirectoryName(cachedPath)!); - File.WriteAllText(cachedPath, RepositoryLinks.Serialize(linkReference)); + _ = _fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(cachedPath)!); + _fileSystem.File.WriteAllText(cachedPath, RepositoryLinks.Serialize(linkReference)); } catch (Exception e) { @@ -168,11 +171,11 @@ private void WriteLinksJsonCachedFile(string repository, LinkRegistryEntry linkR if (_cachedLinkReferences.TryGetValue(cachedFileName, out var cachedLinkReference)) return cachedLinkReference; - if (File.Exists(cachedPath)) + if (_fileSystem.File.Exists(cachedPath)) { try { - var json = await File.ReadAllTextAsync(cachedPath); + var json = await _fileSystem.File.ReadAllTextAsync(cachedPath); var linkReference = Deserialize(json); _ = _cachedLinkReferences.TryAdd(cachedFileName, linkReference); return linkReference; diff --git a/src/Elastic.Documentation/Elastic.Documentation.csproj b/src/Elastic.Documentation/Elastic.Documentation.csproj index fbc2f8c723..a6a49d02a2 100644 --- a/src/Elastic.Documentation/Elastic.Documentation.csproj +++ b/src/Elastic.Documentation/Elastic.Documentation.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Elastic.Documentation/GitCheckoutInformation.cs b/src/Elastic.Documentation/GitCheckoutInformation.cs index 6981b40ef8..e08b965101 100644 --- a/src/Elastic.Documentation/GitCheckoutInformation.cs +++ b/src/Elastic.Documentation/GitCheckoutInformation.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using Elastic.Documentation.Extensions; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; using SoftCircuits.IniFileParser; namespace Elastic.Documentation; @@ -45,7 +46,11 @@ public static GitCheckoutInformation Create(IDirectoryInfo? source, IFileSystem if (source is null) return Unavailable; - if (fileSystem is not FileSystem) + // Return test data for in-memory (mock) file systems. Use ScopedFileSystem.InnerType + // (available since Nullean.ScopedFileSystem 0.4.0) to inspect through the scope wrapper + // rather than relying on the outer type name. + var fsType = fileSystem is ScopedFileSystem sf ? sf.InnerType : fileSystem.GetType(); + if (fsType.Name.Contains("Mock", StringComparison.OrdinalIgnoreCase)) { return new GitCheckoutInformation { diff --git a/src/Elastic.Documentation/IDocumentationContext.cs b/src/Elastic.Documentation/IDocumentationContext.cs index cf7daaddd1..72512d4c09 100644 --- a/src/Elastic.Documentation/IDocumentationContext.cs +++ b/src/Elastic.Documentation/IDocumentationContext.cs @@ -4,14 +4,15 @@ using System.IO.Abstractions; using Elastic.Documentation.Diagnostics; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation; public interface IDocumentationContext { IDiagnosticsCollector Collector { get; } - IFileSystem ReadFileSystem { get; } - IFileSystem WriteFileSystem { get; } + ScopedFileSystem ReadFileSystem { get; } + ScopedFileSystem WriteFileSystem { get; } IDirectoryInfo OutputDirectory { get; } IFileInfo ConfigurationPath { get; } BuildType BuildType { get; } diff --git a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs index dc99b1c1d7..82924f929b 100644 --- a/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/DetectionRules/DetectionRulesDocsBuilderExtension.cs @@ -19,6 +19,14 @@ public class DetectionRulesDocsBuilderExtension(BuildContext build) : IDocsBuild private BuildContext Build { get; } = build; private bool _versionLockInitialized; + public IEnumerable ExternalScopeRoots => + Build.ConfigurationYaml.TableOfContents + .OfType() + .SelectMany(f => f.Children.OfType()) + .SelectMany(r => r.DetectionRuleFolders) + .Select(f => Path.GetFullPath(f, Build.DocumentationSourceDirectory.FullName)) + .Distinct(); + public IDocumentationFileExporter? FileExporter { get; } = new RuleDocumentationFileExporter(build.ReadFileSystem, build.WriteFileSystem); public DocumentationFile? CreateDocumentationFile(IFileInfo file, MarkdownParser markdownParser) diff --git a/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs index 1dcba78d65..a1d5af396d 100644 --- a/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/IDocsBuilderExtension.cs @@ -15,6 +15,12 @@ public interface IDocsBuilderExtension { IDocumentationFileExporter? FileExporter { get; } + /// + /// Directories outside the working directory root that this extension requires read access to. + /// These are collected and added as additional ScopedFileSystem roots when building. + /// + IEnumerable ExternalScopeRoots => []; + /// Create an instance of if it matches the . /// Return `null` to let another extension handle this. DocumentationFile? CreateDocumentationFile(IFileInfo file, MarkdownParser markdownParser); diff --git a/src/Elastic.Markdown/Myst/Directives/CsvInclude/CsvReader.cs b/src/Elastic.Markdown/Myst/Directives/CsvInclude/CsvReader.cs index 50b9f85e7e..12f6b1d68f 100644 --- a/src/Elastic.Markdown/Myst/Directives/CsvInclude/CsvReader.cs +++ b/src/Elastic.Markdown/Myst/Directives/CsvInclude/CsvReader.cs @@ -3,15 +3,17 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using Elastic.Documentation.Configuration; using nietras.SeparatedValues; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Myst.Directives.CsvInclude; public static class CsvReader { - public static IEnumerable ReadCsvFile(string filePath, string separator, IFileSystem? fileSystem = null) + public static IEnumerable ReadCsvFile(string filePath, string separator, ScopedFileSystem? fileSystem = null) { - var fs = fileSystem ?? new FileSystem(); + var fs = fileSystem ?? FileSystemFactory.RealRead; return ReadWithSep(filePath, separator, fs); } @@ -20,35 +22,16 @@ private static IEnumerable ReadWithSep(string filePath, string separat var separatorChar = separator == "," ? ',' : separator[0]; var spec = Sep.New(separatorChar); - // Sep works with actual file paths, not virtual file systems - // For testing with MockFileSystem, we'll read content first - if (fileSystem.GetType().Name == "MockFileSystem") - { - var content = fileSystem.File.ReadAllText(filePath); - using var reader = spec.Reader(o => o with { HasHeader = false, Unescape = true }).FromText(content); + // Always read via IFileSystem so that scoped and mock file systems are respected. + var content = fileSystem.File.ReadAllText(filePath); + using var reader = spec.Reader(o => o with { HasHeader = false, Unescape = true }).FromText(content); - foreach (var row in reader) - { - var rowData = new string[row.ColCount]; - for (var i = 0; i < row.ColCount; i++) - rowData[i] = row[i].ToString(); - yield return rowData; - } - } - else + foreach (var row in reader) { - using var reader = spec.Reader(o => o with { HasHeader = false, Unescape = true }).FromFile(filePath); - - foreach (var row in reader) - { - var rowData = new string[row.ColCount]; - for (var i = 0; i < row.ColCount; i++) - { - rowData[i] = row[i].ToString(); - } - yield return rowData; - } + var rowData = new string[row.ColCount]; + for (var i = 0; i < row.ColCount; i++) + rowData[i] = row[i].ToString(); + yield return rowData; } } - } diff --git a/src/authoring/Elastic.Documentation.Refactor/FormatService.cs b/src/authoring/Elastic.Documentation.Refactor/FormatService.cs index 785089c7bc..591a8c8da9 100644 --- a/src/authoring/Elastic.Documentation.Refactor/FormatService.cs +++ b/src/authoring/Elastic.Documentation.Refactor/FormatService.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Services; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Refactor; @@ -34,7 +35,7 @@ public async Task Format( IDiagnosticsCollector collector, string? path, bool checkOnly, - IFileSystem fs, + ScopedFileSystem fs, Cancel ctx ) { diff --git a/src/authoring/Elastic.Documentation.Refactor/MoveFileService.cs b/src/authoring/Elastic.Documentation.Refactor/MoveFileService.cs index 20de1525d7..91fbaedd56 100644 --- a/src/authoring/Elastic.Documentation.Refactor/MoveFileService.cs +++ b/src/authoring/Elastic.Documentation.Refactor/MoveFileService.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Services; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Refactor; @@ -23,7 +24,7 @@ public async Task Move( string target, bool? dryRun, string? path, - IFileSystem fs, + ScopedFileSystem fs, Cancel ctx ) { diff --git a/src/authoring/Elastic.Documentation.Refactor/Tracking/LocalChangesService.cs b/src/authoring/Elastic.Documentation.Refactor/Tracking/LocalChangesService.cs index da1e910935..530e46a021 100644 --- a/src/authoring/Elastic.Documentation.Refactor/Tracking/LocalChangesService.cs +++ b/src/authoring/Elastic.Documentation.Refactor/Tracking/LocalChangesService.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Extensions; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Refactor.Tracking; @@ -19,7 +20,7 @@ IConfigurationContext configurationContext { private readonly ILogger _logger = logFactory.CreateLogger(); - public Task ValidateRedirects(IDiagnosticsCollector collector, string? path, FileSystem fs) + public Task ValidateRedirects(IDiagnosticsCollector collector, string? path, ScopedFileSystem fs) { var runningOnCi = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")); @@ -38,7 +39,7 @@ public Task ValidateRedirects(IDiagnosticsCollector collector, string? pat return Task.FromResult(false); } - var root = Paths.DetermineSourceDirectoryRoot(buildContext.DocumentationSourceDirectory); + var root = Paths.FindGitRoot(buildContext.DocumentationSourceDirectory); if (root is null) { collector.EmitError(redirectFile.Source, $"Unable to determine the root of the source directory {buildContext.DocumentationSourceDirectory}."); diff --git a/src/services/Elastic.Changelog/Bundling/ChangelogBundleAmendService.cs b/src/services/Elastic.Changelog/Bundling/ChangelogBundleAmendService.cs index e23300dee3..4e8e3f44e8 100644 --- a/src/services/Elastic.Changelog/Bundling/ChangelogBundleAmendService.cs +++ b/src/services/Elastic.Changelog/Bundling/ChangelogBundleAmendService.cs @@ -16,6 +16,7 @@ using Elastic.Documentation.ReleaseNotes; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Bundling; @@ -46,13 +47,13 @@ public record AmendBundleArguments /// public partial class ChangelogBundleAmendService( ILoggerFactory logFactory, - IFileSystem? fileSystem = null, + ScopedFileSystem? fileSystem = null, IConfigurationContext? configurationContext = null) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); + private readonly IFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; private readonly ChangelogConfigurationLoader? _configLoader = configurationContext != null - ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? new FileSystem()) + ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead) : null; [GeneratedRegex(@"\.amend-(\d+)\.ya?ml$", RegexOptions.IgnoreCase)] diff --git a/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs b/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs index c425b3771f..f113e08e7a 100644 --- a/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs +++ b/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs @@ -18,6 +18,7 @@ using Elastic.Documentation.ReleaseNotes; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Bundling; @@ -105,15 +106,15 @@ public record BundleChangelogsArguments public partial class ChangelogBundlingService( ILoggerFactory logFactory, IConfigurationContext? configurationContext = null, - IFileSystem? fileSystem = null, + ScopedFileSystem? fileSystem = null, IGitHubReleaseService? releaseService = null) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); + private readonly ScopedFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; private readonly IGitHubReleaseService _releaseService = releaseService ?? new GitHubReleaseService(logFactory); private readonly ChangelogConfigurationLoader? _configLoader = configurationContext != null - ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? new FileSystem()) + ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead) : null; [GeneratedRegex(@"(\s+)version:", RegexOptions.Multiline)] diff --git a/src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs b/src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs index 1589c4e418..3f66938565 100644 --- a/src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs +++ b/src/services/Elastic.Changelog/Bundling/ChangelogRemoveService.cs @@ -12,6 +12,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Bundling; @@ -62,15 +63,15 @@ public record BundleDependency(string ChangelogFile, string BundleFile); public class ChangelogRemoveService( ILoggerFactory logFactory, IConfigurationContext? configurationContext = null, - IFileSystem? fileSystem = null, + ScopedFileSystem? fileSystem = null, IGitHubReleaseService? releaseService = null) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); + private readonly ScopedFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; private readonly IGitHubReleaseService _releaseService = releaseService ?? new GitHubReleaseService(logFactory); private readonly ChangelogConfigurationLoader? _configLoader = configurationContext != null - ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? new FileSystem()) + ? new ChangelogConfigurationLoader(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead) : null; public async Task RemoveChangelogs(IDiagnosticsCollector collector, ChangelogRemoveArguments input, Cancel ctx) diff --git a/src/services/Elastic.Changelog/Bundling/ProfileFilterResolver.cs b/src/services/Elastic.Changelog/Bundling/ProfileFilterResolver.cs index a08dced8f8..a8b39073ed 100644 --- a/src/services/Elastic.Changelog/Bundling/ProfileFilterResolver.cs +++ b/src/services/Elastic.Changelog/Bundling/ProfileFilterResolver.cs @@ -11,6 +11,7 @@ using Elastic.Documentation.ReleaseNotes; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Bundling; @@ -79,7 +80,7 @@ public static partial class ProfileFilterResolver string profileName, string? profileArgument, ChangelogConfiguration? config, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, ILogger? logger, Cancel ctx, string? profileReport = null, @@ -192,7 +193,7 @@ public static partial class ProfileFilterResolver string profileArgument, string profileReport, BundleProfile profile, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, ILogger? logger, Cancel ctx) { @@ -270,7 +271,7 @@ public static partial class ProfileFilterResolver internal static async Task ResolveUrlListFileAsync( IDiagnosticsCollector collector, string filePath, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, Cancel ctx) { var content = await fileSystem.File.ReadAllTextAsync(filePath, ctx); @@ -326,7 +327,7 @@ public static partial class ProfileFilterResolver return hasPrs ? new UrlListFileResult(lines, null) : new UrlListFileResult(null, lines); } - private static ProfileArgumentType DetectLocalFileType(IFileSystem fileSystem, string path) => + private static ProfileArgumentType DetectLocalFileType(ScopedFileSystem fileSystem, string path) => fileSystem.Path.GetExtension(path).ToLowerInvariant() is ".html" or ".htm" ? ProfileArgumentType.PromotionReportFile : ProfileArgumentType.UrlListFile; diff --git a/src/services/Elastic.Changelog/Bundling/PromotionReportParser.cs b/src/services/Elastic.Changelog/Bundling/PromotionReportParser.cs index b1ed95d351..aeda3acf34 100644 --- a/src/services/Elastic.Changelog/Bundling/PromotionReportParser.cs +++ b/src/services/Elastic.Changelog/Bundling/PromotionReportParser.cs @@ -5,18 +5,20 @@ using System.IO.Abstractions; using System.Net.Http.Headers; using System.Text.RegularExpressions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Bundling; /// /// Parser for promotion report HTML files to extract PR lists /// -public partial class PromotionReportParser(ILoggerFactory logFactory, IFileSystem? fileSystem = null) +public partial class PromotionReportParser(ILoggerFactory logFactory, ScopedFileSystem? fileSystem = null) { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); + private readonly IFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; private static readonly HttpClient HttpClient = new(); static PromotionReportParser() diff --git a/src/services/Elastic.Changelog/Creation/ChangelogCreationService.cs b/src/services/Elastic.Changelog/Creation/ChangelogCreationService.cs index 2ef0ce90dc..a1a091db81 100644 --- a/src/services/Elastic.Changelog/Creation/ChangelogCreationService.cs +++ b/src/services/Elastic.Changelog/Creation/ChangelogCreationService.cs @@ -11,6 +11,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Creation; @@ -61,16 +62,16 @@ public class ChangelogCreationService( ILoggerFactory logFactory, IConfigurationContext configurationContext, IGitHubPrService? githubPrService = null, -IFileSystem? fileSystem = null, +ScopedFileSystem? fileSystem = null, IEnvironmentVariables? env = null ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? new FileSystem()); + private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead); private readonly CreateChangelogArgumentsValidator _validator = new(configurationContext); private readonly PrInfoProcessor _prProcessor = new(githubPrService, logFactory.CreateLogger()); private readonly IssueInfoProcessor _issueProcessor = new(githubPrService, logFactory.CreateLogger()); - private readonly ChangelogFileWriter _fileWriter = new(fileSystem ?? new FileSystem(), logFactory.CreateLogger()); + private readonly ChangelogFileWriter _fileWriter = new(fileSystem ?? FileSystemFactory.RealRead, logFactory.CreateLogger()); private readonly ProductInferService _productInferService = new( configurationContext.ProductsConfiguration); diff --git a/src/services/Elastic.Changelog/Evaluation/ChangelogPrEvaluationService.cs b/src/services/Elastic.Changelog/Evaluation/ChangelogPrEvaluationService.cs index 34f06d539a..cc92d1dc51 100644 --- a/src/services/Elastic.Changelog/Evaluation/ChangelogPrEvaluationService.cs +++ b/src/services/Elastic.Changelog/Evaluation/ChangelogPrEvaluationService.cs @@ -14,6 +14,7 @@ using Elastic.Documentation.ReleaseNotes; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Evaluation; @@ -23,12 +24,12 @@ public class ChangelogPrEvaluationService( IConfigurationContext configurationContext, IGitHubPrService gitHubPrService, ICoreService coreService, - IFileSystem? fileSystem = null + ScopedFileSystem? fileSystem = null ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); - private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? new FileSystem()); + private readonly IFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; + private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead); public async Task EvaluatePr(IDiagnosticsCollector collector, EvaluatePrArguments input, Cancel ctx) { diff --git a/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs b/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs index 4d07ae3980..95d98324f6 100644 --- a/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs +++ b/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs @@ -15,6 +15,7 @@ using Elastic.Documentation.ReleaseNotes; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.GithubRelease; @@ -68,13 +69,13 @@ public class GitHubReleaseChangelogService( IConfigurationContext configurationContext, IGitHubReleaseService? releaseService = null, IGitHubPrService? prService = null, - IFileSystem? fileSystem = null, + ScopedFileSystem? fileSystem = null, ChangelogBundlingService? bundlingService = null ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); - private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? new FileSystem()); + private readonly IFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealRead; + private readonly ChangelogConfigurationLoader _configLoader = new(logFactory, configurationContext, fileSystem ?? FileSystemFactory.RealRead); private readonly IGitHubReleaseService _releaseService = releaseService ?? new GitHubReleaseService(logFactory); private readonly IGitHubPrService _prService = prService ?? new GitHubPrService(logFactory); private readonly ChangelogBundlingService _bundlingService = bundlingService ?? new ChangelogBundlingService(logFactory, configurationContext, fileSystem); diff --git a/src/services/Elastic.Changelog/Rendering/ChangelogRenderer.cs b/src/services/Elastic.Changelog/Rendering/ChangelogRenderer.cs index 19b55cb506..2f901c29d4 100644 --- a/src/services/Elastic.Changelog/Rendering/ChangelogRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/ChangelogRenderer.cs @@ -6,13 +6,14 @@ using Elastic.Changelog.Rendering.Asciidoc; using Elastic.Changelog.Rendering.Markdown; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Rendering; /// /// Coordinates rendering of changelog output to different formats. /// -public class ChangelogRenderer(IFileSystem fileSystem, ILogger logger) +public class ChangelogRenderer(ScopedFileSystem fileSystem, ILogger logger) { /// /// Renders changelog output based on the specified file type. diff --git a/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs b/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs index af1942494c..099ea5587b 100644 --- a/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs +++ b/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs @@ -14,6 +14,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using NetEscapades.EnumGenerators; +using Nullean.ScopedFileSystem; using YamlDotNet.Core; namespace Elastic.Changelog.Rendering; @@ -65,11 +66,11 @@ public enum ChangelogFileType public class ChangelogRenderingService( ILoggerFactory logFactory, IConfigurationContext? configurationContext = null, - IFileSystem? fileSystem = null + ScopedFileSystem? fileSystem = null ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); - private readonly IFileSystem _fileSystem = fileSystem ?? new FileSystem(); + private readonly ScopedFileSystem _fileSystem = fileSystem ?? FileSystemFactory.RealWrite; public async Task RenderChangelogs( IDiagnosticsCollector collector, diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/BreakingChangesMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/BreakingChangesMarkdownRenderer.cs index cdaf94fc43..7cace43f70 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/BreakingChangesMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/BreakingChangesMarkdownRenderer.cs @@ -6,6 +6,7 @@ using System.Text; using Elastic.Documentation; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; using static System.Globalization.CultureInfo; using static Elastic.Documentation.ChangelogEntryType; @@ -14,7 +15,7 @@ namespace Elastic.Changelog.Rendering.Markdown; /// /// Renderer for the breaking-changes.md changelog file /// -public class BreakingChangesMarkdownRenderer(IFileSystem fileSystem) : MarkdownRendererBase(fileSystem) +public class BreakingChangesMarkdownRenderer(ScopedFileSystem fileSystem) : MarkdownRendererBase(fileSystem) { /// public override string OutputFileName => "breaking-changes.md"; diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/ChangelogMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/ChangelogMarkdownRenderer.cs index 2d512ace8d..c57d7b1162 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/ChangelogMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/ChangelogMarkdownRenderer.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.IO.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Rendering.Markdown; /// /// Coordinates rendering of all markdown changelog files. /// -public class ChangelogMarkdownRenderer(IFileSystem fileSystem) +public class ChangelogMarkdownRenderer(ScopedFileSystem fileSystem) { /// /// Renders all markdown changelog files (index, breaking changes, deprecations, known issues, highlights). diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/DeprecationsMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/DeprecationsMarkdownRenderer.cs index 0a31bd1709..78dcf5fbd5 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/DeprecationsMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/DeprecationsMarkdownRenderer.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions; using System.Text; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; using static System.Globalization.CultureInfo; using static Elastic.Documentation.ChangelogEntryType; @@ -13,7 +14,7 @@ namespace Elastic.Changelog.Rendering.Markdown; /// /// Renderer for the deprecations.md changelog file /// -public class DeprecationsMarkdownRenderer(IFileSystem fileSystem) : MarkdownRendererBase(fileSystem) +public class DeprecationsMarkdownRenderer(ScopedFileSystem fileSystem) : MarkdownRendererBase(fileSystem) { /// public override string OutputFileName => "deprecations.md"; diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/HighlightsMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/HighlightsMarkdownRenderer.cs index f13394286f..f55a3c19a3 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/HighlightsMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/HighlightsMarkdownRenderer.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions; using System.Text; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; using static System.Globalization.CultureInfo; namespace Elastic.Changelog.Rendering.Markdown; @@ -12,7 +13,7 @@ namespace Elastic.Changelog.Rendering.Markdown; /// /// Renderer for the highlights.md changelog file /// -public class HighlightsMarkdownRenderer(IFileSystem fileSystem) : MarkdownRendererBase(fileSystem) +public class HighlightsMarkdownRenderer(ScopedFileSystem fileSystem) : MarkdownRendererBase(fileSystem) { /// public override string OutputFileName => "highlights.md"; diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs index 84221cac0d..e00af54ca9 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs @@ -6,6 +6,7 @@ using System.IO.Abstractions; using System.Text; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; using static System.Globalization.CultureInfo; using static Elastic.Documentation.ChangelogEntryType; @@ -14,7 +15,7 @@ namespace Elastic.Changelog.Rendering.Markdown; /// /// Renderer for the index.md changelog file containing features, enhancements, fixes, docs, regressions, and other changes /// -public class IndexMarkdownRenderer(IFileSystem fileSystem) : MarkdownRendererBase(fileSystem) +public class IndexMarkdownRenderer(ScopedFileSystem fileSystem) : MarkdownRendererBase(fileSystem) { /// public override string OutputFileName => "index.md"; diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/KnownIssuesMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/KnownIssuesMarkdownRenderer.cs index bddadcf74d..a8640f14b3 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/KnownIssuesMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/KnownIssuesMarkdownRenderer.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions; using System.Text; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; using static System.Globalization.CultureInfo; using static Elastic.Documentation.ChangelogEntryType; @@ -13,7 +14,7 @@ namespace Elastic.Changelog.Rendering.Markdown; /// /// Renderer for the known-issues.md changelog file /// -public class KnownIssuesMarkdownRenderer(IFileSystem fileSystem) : MarkdownRendererBase(fileSystem) +public class KnownIssuesMarkdownRenderer(ScopedFileSystem fileSystem) : MarkdownRendererBase(fileSystem) { /// public override string OutputFileName => "known-issues.md"; diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/MarkdownRendererBase.cs b/src/services/Elastic.Changelog/Rendering/Markdown/MarkdownRendererBase.cs index 7c22e23532..6e7cc94473 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/MarkdownRendererBase.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/MarkdownRendererBase.cs @@ -6,15 +6,16 @@ using System.IO.Abstractions; using System.Text; using Elastic.Documentation.ReleaseNotes; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Rendering.Markdown; /// /// Abstract base class for changelog markdown renderers /// -public abstract class MarkdownRendererBase(IFileSystem fileSystem) : IChangelogMarkdownRenderer +public abstract class MarkdownRendererBase(ScopedFileSystem fileSystem) : IChangelogMarkdownRenderer { - protected IFileSystem FileSystem { get; } = fileSystem; + protected ScopedFileSystem FileSystem { get; } = fileSystem; /// public abstract string OutputFileName { get; } diff --git a/src/services/Elastic.Documentation.Assembler/AssembleContext.cs b/src/services/Elastic.Documentation.Assembler/AssembleContext.cs index 99ceba0b37..29ec966254 100644 --- a/src/services/Elastic.Documentation.Assembler/AssembleContext.cs +++ b/src/services/Elastic.Documentation.Assembler/AssembleContext.cs @@ -10,13 +10,14 @@ using Elastic.Documentation.Configuration.Search; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler; public class AssembleContext : IDocumentationConfigurationContext { - public IFileSystem ReadFileSystem { get; } - public IFileSystem WriteFileSystem { get; } + public ScopedFileSystem ReadFileSystem { get; } + public ScopedFileSystem WriteFileSystem { get; } public IDiagnosticsCollector Collector { get; } @@ -61,8 +62,8 @@ public AssembleContext( IConfigurationContext configurationContext, string environment, IDiagnosticsCollector collector, - IFileSystem readFileSystem, - IFileSystem writeFileSystem, + ScopedFileSystem readFileSystem, + ScopedFileSystem writeFileSystem, string? checkoutDirectory, string? output ) @@ -88,10 +89,12 @@ public AssembleContext( Endpoints.Environment = environment; var contentSource = Environment.ContentSource.ToStringFast(true); - var defaultCheckoutDirectory = Path.Join(Paths.GitCommonRoot.FullName, ".artifacts", "checkouts", contentSource); - CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? defaultCheckoutDirectory); + var defaultCheckoutDirectory = Path.Join(Paths.ApplicationData.FullName, "checkouts", contentSource); + CheckoutDirectory = checkoutDirectory is null + ? FileSystemFactory.AppData.DirectoryInfo.New(defaultCheckoutDirectory) + : ReadFileSystem.DirectoryInfo.New(checkoutDirectory); var defaultOutputDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"); - OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? defaultOutputDirectory); + OutputDirectory = WriteFileSystem.DirectoryInfo.New(output ?? defaultOutputDirectory); // Calculate the output directory with path prefix once var pathPrefix = Environment.PathPrefix; diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs index 3e2e77e6be..5dc0898e04 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerBuildService.cs @@ -15,6 +15,7 @@ using Elastic.Documentation.Navigation.Assembler; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.Building; @@ -36,7 +37,8 @@ public async Task BuildAll( bool? showHints, IReadOnlySet? exporters, bool? assumeBuild, - FileSystem fs, + ScopedFileSystem readFs, + ScopedFileSystem writeFs, Cancel ctx ) { @@ -56,7 +58,7 @@ Cancel ctx _logger.LogInformation("Creating assemble context"); - var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, fs, fs, null, null); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, readFs, writeFs, null, null); // --assume-build is not allowed on CI: it could serve stale content from a previous/cached build // CI builds must always produce fresh, reproducible output @@ -67,7 +69,7 @@ Cancel ctx if (assumeBuild.GetValueOrDefault(false)) { var indexHtmlPath = Path.Join(assembleContext.OutputDirectory.FullName, "docs", "index.html"); - if (assembleContext.OutputDirectory.Exists && fs.File.Exists(indexHtmlPath)) + if (assembleContext.OutputDirectory.Exists && readFs.File.Exists(indexHtmlPath)) { _logger.LogInformation("Assuming build already exists (--assume-build). Found index.html at {Path}. Skipping build.", indexHtmlPath); return true; @@ -100,7 +102,7 @@ Cancel ctx var assembleSources = await AssembleSources.AssembleAsync(logFactory, assembleContext, checkouts, configurationContext, exporters, ctx); var navigationFileInfo = configurationContext.ConfigurationFileProvider.NavigationFile; - var siteNavigationFile = SiteNavigationFile.Deserialize(await fs.File.ReadAllTextAsync(navigationFileInfo.FullName, ctx)); + var siteNavigationFile = SiteNavigationFile.Deserialize(await readFs.File.ReadAllTextAsync(navigationFileInfo.FullName, ctx)); var documentationSets = assembleSources.AssembleSets.Values.Select(s => s.DocumentationSet.Navigation).ToArray(); var navigation = new SiteNavigation(siteNavigationFile, assembleContext, documentationSets, assembleContext.Environment.PathPrefix); @@ -122,7 +124,7 @@ Cancel ctx await cloner.WriteLinkRegistrySnapshot(checkoutResult.LinkRegistrySnapshot, ctx); var redirectsPath = Path.Join(assembleContext.OutputDirectory.FullName, "redirects.json"); - if (File.Exists(redirectsPath)) + if (writeFs.File.Exists(redirectsPath)) await githubActionsService.SetOutputAsync("redirects-artifact-path", redirectsPath); if (exporters.Contains(Exporter.Html)) diff --git a/src/services/Elastic.Documentation.Assembler/Building/AssemblerSitemapService.cs b/src/services/Elastic.Documentation.Assembler/Building/AssemblerSitemapService.cs index 8cd5be0f66..84843a2c37 100644 --- a/src/services/Elastic.Documentation.Assembler/Building/AssemblerSitemapService.cs +++ b/src/services/Elastic.Documentation.Assembler/Building/AssemblerSitemapService.cs @@ -11,6 +11,7 @@ using Elastic.Documentation.Services; using Elastic.Markdown.Exporters.Elasticsearch; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.Building; @@ -25,7 +26,7 @@ ICoreService githubActionsService public async Task GenerateSitemapAsync( IDiagnosticsCollector collector, - FileSystem fileSystem, + ScopedFileSystem fileSystem, string? endpoint = null, string? environment = null, string? apiKey = null, diff --git a/src/services/Elastic.Documentation.Assembler/Configuration/ConfigurationCloneService.cs b/src/services/Elastic.Documentation.Assembler/Configuration/ConfigurationCloneService.cs index 621fecaf42..5dc7daf087 100644 --- a/src/services/Elastic.Documentation.Assembler/Configuration/ConfigurationCloneService.cs +++ b/src/services/Elastic.Documentation.Assembler/Configuration/ConfigurationCloneService.cs @@ -15,7 +15,7 @@ namespace Elastic.Documentation.Assembler.Configuration; public class ConfigurationCloneService( ILoggerFactory logFactory, AssemblyConfiguration assemblyConfiguration, - FileSystem fs + IFileSystem fs ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); diff --git a/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryBuildMatchingService.cs b/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryBuildMatchingService.cs index 2ca860f1ec..7e83293103 100644 --- a/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryBuildMatchingService.cs +++ b/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryBuildMatchingService.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.ContentSources; @@ -18,7 +19,7 @@ public class RepositoryBuildMatchingService( AssemblyConfiguration configuration, IConfigurationContext configurationContext, ICoreService githubActionsService, - FileSystem fileSystem + ScopedFileSystem fileSystem ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); diff --git a/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs b/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs index 2499b886ce..8f11b65b68 100644 --- a/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs +++ b/src/services/Elastic.Documentation.Assembler/ContentSources/RepositoryPublishValidationService.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.LinkIndex; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.ContentSources; @@ -17,7 +18,7 @@ public class RepositoryPublishValidationService( ILoggerFactory logFactory, AssemblyConfiguration configuration, IConfigurationContext configurationContext, - FileSystem fileSystem + ScopedFileSystem fileSystem ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); diff --git a/src/services/Elastic.Documentation.Assembler/Deploying/DeployUpdateRedirectsService.cs b/src/services/Elastic.Documentation.Assembler/Deploying/DeployUpdateRedirectsService.cs index c1e5675ab3..01af135702 100644 --- a/src/services/Elastic.Documentation.Assembler/Deploying/DeployUpdateRedirectsService.cs +++ b/src/services/Elastic.Documentation.Assembler/Deploying/DeployUpdateRedirectsService.cs @@ -12,7 +12,7 @@ namespace Elastic.Documentation.Assembler.Deploying; -public class DeployUpdateRedirectsService(ILoggerFactory logFactory, FileSystem fileSystem) : IService +public class DeployUpdateRedirectsService(ILoggerFactory logFactory, IFileSystem fileSystem) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); @@ -42,7 +42,7 @@ public async Task UpdateRedirects( } var kvsName = $"{kvsNamePrefix}-{environment}-redirects-kvs"; - var cloudFrontClient = new AwsCloudFrontKeyValueStoreProxy(collector, logFactory, fileSystem.DirectoryInfo.New(Directory.GetCurrentDirectory())); + var cloudFrontClient = new AwsCloudFrontKeyValueStoreProxy(collector, logFactory, fileSystem.DirectoryInfo.New(fileSystem.Directory.GetCurrentDirectory())); cloudFrontClient.UpdateRedirects(kvsName, sourcedRedirects); return collector.Errors == 0; diff --git a/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs b/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs index 52ac1160e9..6167412be6 100644 --- a/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs +++ b/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs @@ -12,6 +12,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.Deploying; @@ -20,7 +21,7 @@ public class IncrementalDeployService( AssemblyConfiguration assemblyConfiguration, IConfigurationContext configurationContext, ICoreService githubActionsService, - FileSystem fileSystem + ScopedFileSystem fileSystem ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); @@ -50,7 +51,7 @@ public async Task Plan(IDiagnosticsCollector collector, string environment if (!string.IsNullOrEmpty(@out)) { var output = SyncPlan.Serialize(plan); - await using var fileStream = new FileStream(@out, FileMode.Create, FileAccess.Write); + await using var fileStream = fileSystem.File.Create(@out); await using var writer = new StreamWriter(fileStream); await writer.WriteAsync(output); _logger.LogInformation("Plan written to {OutputFile}", @out); @@ -73,7 +74,7 @@ public async Task Apply(IDiagnosticsCollector collector, string environmen collector.EmitError(planFile, "Plan file does not exist."); return false; } - var planJson = await File.ReadAllTextAsync(planFile, ctx); + var planJson = await fileSystem.File.ReadAllTextAsync(planFile, ctx); var plan = SyncPlan.Deserialize(planJson); _logger.LogInformation("Remote listing completed: {RemoteListingCompleted}", plan.RemoteListingCompleted); _logger.LogInformation("Total files to sync: {TotalFiles}", plan.TotalSyncRequests); diff --git a/src/services/Elastic.Documentation.Assembler/Elastic.Documentation.Assembler.csproj b/src/services/Elastic.Documentation.Assembler/Elastic.Documentation.Assembler.csproj index 175061e07c..82408eef33 100644 --- a/src/services/Elastic.Documentation.Assembler/Elastic.Documentation.Assembler.csproj +++ b/src/services/Elastic.Documentation.Assembler/Elastic.Documentation.Assembler.csproj @@ -15,6 +15,7 @@ + diff --git a/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs b/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs index 50d94e245f..7a3953704d 100644 --- a/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs +++ b/src/services/Elastic.Documentation.Assembler/Indexing/AssemblerIndexService.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Diagnostics; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; using static Elastic.Documentation.Exporter; namespace Elastic.Documentation.Assembler.Indexing; @@ -52,7 +53,8 @@ IEnvironmentVariables environmentVariables /// /// public async Task Index(IDiagnosticsCollector collector, - FileSystem fileSystem, + ScopedFileSystem readFs, + ScopedFileSystem writeFs, string? endpoint = null, string? environment = null, string? apiKey = null, @@ -105,10 +107,10 @@ public async Task Index(IDiagnosticsCollector collector, CertificatePath = certificatePath, CertificateNotRoot = certificateNotRoot }; - await ElasticsearchEndpointConfigurator.ApplyAsync(cfg, options, collector, fileSystem, ctx); + await ElasticsearchEndpointConfigurator.ApplyAsync(cfg, options, collector, readFs, ctx); var exporters = new HashSet { Elasticsearch }; - return await BuildAll(collector, strict: false, environment, metadataOnly: true, showHints: false, exporters, assumeBuild: false, fileSystem, ctx); + return await BuildAll(collector, strict: false, environment, metadataOnly: true, showHints: false, exporters, assumeBuild: false, readFs, writeFs, ctx); } } diff --git a/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationService.cs b/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationService.cs index 5ec78d011b..56da04d68b 100644 --- a/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationService.cs +++ b/src/services/Elastic.Documentation.Assembler/Navigation/GlobalNavigationService.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Assembler.Navigation; @@ -16,7 +17,7 @@ public class GlobalNavigationService( ILoggerFactory logFactory, AssemblyConfiguration configuration, IConfigurationContext configurationContext, - IFileSystem fileSystem + ScopedFileSystem fileSystem ) : IService { public async Task Validate(IDiagnosticsCollector collector, Cancel ctx) diff --git a/src/services/Elastic.Documentation.Assembler/Sourcing/AssemblerCloneService.cs b/src/services/Elastic.Documentation.Assembler/Sourcing/AssemblerCloneService.cs index 92501c490f..f819ebe18a 100644 --- a/src/services/Elastic.Documentation.Assembler/Sourcing/AssemblerCloneService.cs +++ b/src/services/Elastic.Documentation.Assembler/Sourcing/AssemblerCloneService.cs @@ -25,7 +25,7 @@ public async Task CloneAll(IDiagnosticsCollector collector, bool? strict, var githubEnvironmentInput = githubActionsService.GetInput("environment"); environment ??= !string.IsNullOrEmpty(githubEnvironmentInput) ? githubEnvironmentInput : "dev"; - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, fs, fs, null, null); var cloner = new AssemblerRepositorySourcer(logFactory, assembleContext); diff --git a/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs b/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs index bff142bfe5..180e315b37 100644 --- a/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs +++ b/src/services/Elastic.Documentation.Isolated/IsolatedBuildService.cs @@ -20,6 +20,7 @@ using Elastic.Markdown.IO; using Elastic.Markdown.Page; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; using static System.StringComparison; namespace Elastic.Documentation.Isolated; @@ -43,7 +44,7 @@ public bool IsStrict(bool? strict) public async Task Build( IDiagnosticsCollector collector, - IFileSystem fileSystem, + ScopedFileSystem fileSystem, string? path = null, string? output = null, string? pathPrefix = null, @@ -53,7 +54,7 @@ public async Task Build( bool? metadataOnly = null, IReadOnlySet? exporters = null, string? canonicalBaseUrl = null, - IFileSystem? writeFileSystem = null, + ScopedFileSystem? writeFileSystem = null, bool skipOpenApi = false, bool skipCrossLinks = false, Cancel ctx = default @@ -99,11 +100,15 @@ public async Task Build( // At some point in the future we can remove this try catch catch (Exception e) when (runningOnCi && e.Message.StartsWith("Can not locate docset.yml file in", OrdinalIgnoreCase)) { + // Derive the default output from `path` so it stays within the write FS scope. + // Using Paths.WorkingDirectoryRoot would be wrong when --path points to a different repo. + var rootFolder = !string.IsNullOrWhiteSpace(path) ? path : Paths.WorkingDirectoryRoot.FullName; + var writeFs = writeFileSystem ?? fileSystem; var outputDirectory = !string.IsNullOrWhiteSpace(output) - ? fileSystem.DirectoryInfo.New(output) - : fileSystem.DirectoryInfo.New(Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts/docs/html")); + ? writeFs.DirectoryInfo.New(output) + : writeFs.DirectoryInfo.New(Path.Join(rootFolder, ".artifacts/docs/html")); // we temporarily do not error when pointed to a non-documentation folder. - _ = fileSystem.Directory.CreateDirectory(outputDirectory.FullName); + _ = writeFs.Directory.CreateDirectory(outputDirectory.FullName); _logger.LogInformation("Skipping build as we are running on a merge commit and the docs folder is out of date and has no docset.yml. {Message}", e.Message); @@ -124,7 +129,7 @@ public async Task Build( else { using var codexReader = context.Configuration.Registry != DocSetRegistry.Public - ? new GitLinkIndexReader(context.Configuration.Registry.ToStringFast(true), fileSystem) + ? new GitLinkIndexReader(context.Configuration.Registry.ToStringFast(true), FileSystemFactory.AppData) : null; var crossLinkFetcher = new DocSetConfigurationCrossLinkFetcher( diff --git a/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs b/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs index 0f710c3fd8..b20980e233 100644 --- a/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs +++ b/src/services/Elastic.Documentation.Isolated/IsolatedIndexService.cs @@ -7,6 +7,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; using static Elastic.Documentation.Exporter; namespace Elastic.Documentation.Isolated; @@ -49,7 +50,7 @@ IEnvironmentVariables environmentVariables /// /// public async Task Index(IDiagnosticsCollector collector, - FileSystem fileSystem, + ScopedFileSystem fileSystem, string? path = null, string? endpoint = null, string? apiKey = null, diff --git a/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs b/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs index 831eb5434c..5c3cf9f25b 100644 --- a/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs @@ -63,10 +63,11 @@ static async (s, collector, state, ctx) => await s.CloneAll(collector, state.str ); var buildService = new AssemblerBuildService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, environmentVariables); - var fs = new FileSystem(); - serviceInvoker.AddCommand(buildService, (strict, environment, metadataOnly, showHints, exporters, assumeBuild, fs), strict ?? false, + var readFs = FileSystemFactory.RealRead; + var writeFs = FileSystemFactory.RealWrite; + serviceInvoker.AddCommand(buildService, (strict, environment, metadataOnly, showHints, exporters, assumeBuild, readFs, writeFs), strict ?? false, static async (s, collector, state, ctx) => - await s.BuildAll(collector, state.strict, state.environment, state.metadataOnly, state.showHints, state.exporters, state.assumeBuild, state.fs, ctx) + await s.BuildAll(collector, state.strict, state.environment, state.metadataOnly, state.showHints, state.exporters, state.assumeBuild, state.readFs, state.writeFs, ctx) ); var result = await serviceInvoker.InvokeAsync(ctx); @@ -141,11 +142,12 @@ public async Task BuildAll( { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var readFs = FileSystemFactory.RealRead; + var writeFs = FileSystemFactory.RealWrite; var service = new AssemblerBuildService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, environmentVariables); - serviceInvoker.AddCommand(service, (strict, environment, assumeBuild, metadataOnly, showHints, exporters, fs), strict ?? false, + serviceInvoker.AddCommand(service, (strict, environment, assumeBuild, metadataOnly, showHints, exporters, readFs, writeFs), strict ?? false, static async (s, collector, state, ctx) => - await s.BuildAll(collector, state.strict, state.environment, state.metadataOnly, state.showHints, state.exporters, state.assumeBuild, state.fs, ctx) + await s.BuildAll(collector, state.strict, state.environment, state.metadataOnly, state.showHints, state.exporters, state.assumeBuild, state.readFs, state.writeFs, ctx) ); return await serviceInvoker.InvokeAsync(ctx); diff --git a/src/tooling/docs-builder/Commands/Assembler/AssemblerIndexCommand.cs b/src/tooling/docs-builder/Commands/Assembler/AssemblerIndexCommand.cs index 21470e3bc3..7a8a5a9316 100644 --- a/src/tooling/docs-builder/Commands/Assembler/AssemblerIndexCommand.cs +++ b/src/tooling/docs-builder/Commands/Assembler/AssemblerIndexCommand.cs @@ -88,9 +88,10 @@ public async Task Index( ) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var readFs = FileSystemFactory.RealRead; + var writeFs = FileSystemFactory.RealWrite; var service = new AssemblerIndexService(logFactory, configuration, configurationContext, githubActionsService, environmentVariables); - var state = (fs, + var state = (readFs, writeFs, // endpoint options endpoint, environment, apiKey, username, password, // inference options @@ -103,7 +104,7 @@ public async Task Index( disableSslVerification, certificateFingerprint, certificatePath, certificateNotRoot ); serviceInvoker.AddCommand(service, state, - static async (s, collector, state, ctx) => await s.Index(collector, state.fs, + static async (s, collector, state, ctx) => await s.Index(collector, state.readFs, state.writeFs, // endpoint options state.endpoint, state.environment, state.apiKey, state.username, state.password, // inference options diff --git a/src/tooling/docs-builder/Commands/Assembler/AssemblerSitemapCommand.cs b/src/tooling/docs-builder/Commands/Assembler/AssemblerSitemapCommand.cs index eefc396f46..da074c606a 100644 --- a/src/tooling/docs-builder/Commands/Assembler/AssemblerSitemapCommand.cs +++ b/src/tooling/docs-builder/Commands/Assembler/AssemblerSitemapCommand.cs @@ -59,7 +59,7 @@ public async Task Sitemap( ) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealWrite; var service = new AssemblerSitemapService(logFactory, configuration, configurationContext, githubActionsService); var state = (fs, endpoint, environment, apiKey, username, password, diff --git a/src/tooling/docs-builder/Commands/Assembler/ConfigurationCommands.cs b/src/tooling/docs-builder/Commands/Assembler/ConfigurationCommands.cs index bb6c9f8602..0cea37f2cb 100644 --- a/src/tooling/docs-builder/Commands/Assembler/ConfigurationCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/ConfigurationCommands.cs @@ -28,7 +28,7 @@ public async Task CloneConfigurationFolder(string? gitRef = null, bool loca { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new ConfigurationCloneService(logFactory, assemblyConfiguration, fs); serviceInvoker.AddCommand(service, (gitRef, local), static async (s, collector, state, ctx) => await s.InitConfigurationToApplicationData(collector, state.gitRef, state.local, ctx)); diff --git a/src/tooling/docs-builder/Commands/Assembler/ContentSourceCommands.cs b/src/tooling/docs-builder/Commands/Assembler/ContentSourceCommands.cs index f602232a00..21080a6fac 100644 --- a/src/tooling/docs-builder/Commands/Assembler/ContentSourceCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/ContentSourceCommands.cs @@ -27,7 +27,7 @@ public async Task Validate(Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new RepositoryPublishValidationService(logFactory, configuration, configurationContext, fs); serviceInvoker.AddCommand(service, static async (s, collector, ctx) => await s.ValidatePublishStatus(collector, ctx)); @@ -43,7 +43,7 @@ public async Task Match([Argument] string? repository = null, [Argument] st { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new RepositoryBuildMatchingService(logFactory, configuration, configurationContext, githubActionsService, fs); serviceInvoker.AddCommand(service, (repository, branchOrTag), static async (s, collector, state, ctx) => diff --git a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs index c6a6d40644..daf4eec188 100644 --- a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs @@ -33,7 +33,7 @@ public async Task Plan(string environment, string s3BucketName, string @out { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, fs); serviceInvoker.AddCommand(service, (environment, s3BucketName, @out, deleteThreshold), static async (s, collector, state, ctx) => await s.Plan(collector, state.environment, state.s3BucketName, state.@out, state.deleteThreshold, ctx) @@ -51,7 +51,7 @@ public async Task Apply(string environment, string s3BucketName, string pla { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, fs); serviceInvoker.AddCommand(service, (environment, s3BucketName, planFile), static async (s, collector, state, ctx) => await s.Apply(collector, state.environment, state.s3BucketName, state.planFile, ctx) @@ -68,7 +68,7 @@ public async Task UpdateRedirects(string environment, string? redirectsFile { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var service = new DeployUpdateRedirectsService(logFactory, fs); serviceInvoker.AddCommand(service, (environment, redirectsFile), static async (s, collector, state, ctx) => await s.UpdateRedirects(collector, state.environment, state.redirectsFile, ctx: ctx) diff --git a/src/tooling/docs-builder/Commands/Assembler/NavigationCommands.cs b/src/tooling/docs-builder/Commands/Assembler/NavigationCommands.cs index 59df5cd89a..d6ca3075f9 100644 --- a/src/tooling/docs-builder/Commands/Assembler/NavigationCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/NavigationCommands.cs @@ -26,7 +26,7 @@ IConfigurationContext configurationContext public async Task Validate(Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var service = new GlobalNavigationService(logFactory, configuration, configurationContext, new FileSystem()); + var service = new GlobalNavigationService(logFactory, configuration, configurationContext, FileSystemFactory.RealRead); serviceInvoker.AddCommand(service, static async (s, collector, ctx) => await s.Validate(collector, ctx)); return await serviceInvoker.InvokeAsync(ctx); } @@ -38,7 +38,7 @@ public async Task Validate(Cancel ctx = default) public async Task ValidateLocalLinkReference([Argument] string? file = null, Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var service = new GlobalNavigationService(logFactory, configuration, configurationContext, new FileSystem()); + var service = new GlobalNavigationService(logFactory, configuration, configurationContext, FileSystemFactory.RealRead); serviceInvoker.AddCommand(service, file, static async (s, collector, file, ctx) => await s.ValidateLocalLinkReference(collector, file, ctx)); return await serviceInvoker.InvokeAsync(ctx); } diff --git a/src/tooling/docs-builder/Commands/ChangelogCommand.cs b/src/tooling/docs-builder/Commands/ChangelogCommand.cs index afb3180d3f..28b52241af 100644 --- a/src/tooling/docs-builder/Commands/ChangelogCommand.cs +++ b/src/tooling/docs-builder/Commands/ChangelogCommand.cs @@ -39,7 +39,7 @@ IEnvironmentVariables environmentVariables [GeneratedRegex(@"^( *output_directory:\s*).+$", RegexOptions.Multiline)] private static partial Regex BundleOutputDirectoryRegex(); - private readonly IFileSystem _fileSystem = new FileSystem(); + private readonly IFileSystem _fileSystem = FileSystemFactory.RealRead; private readonly ILogger _logger = logFactory.CreateLogger(); /// /// Changelog commands. Use 'changelog add' to create a new changelog or 'changelog bundle' to create a consolidated list of changelogs. @@ -91,7 +91,7 @@ public Task Init( var useNonDefaultChangelogDir = changelogDir != null; var useNonDefaultBundlesDir = bundlesDir != null; - var repoRoot = Paths.DetermineSourceDirectoryRoot(docsFolder)?.FullName ?? docsFolder.FullName; + var repoRoot = Paths.FindGitRoot(docsFolder)?.FullName ?? docsFolder.FullName; // Create changelog.yml from example if it does not exist if (!_fileSystem.File.Exists(configPath)) @@ -288,7 +288,7 @@ public async Task Create( // Load changelog config and apply fallbacks for all modes. // Precedence: CLI option > bundle section in changelog.yml > built-in default. // This applies to --prs, --issues, and --release-version alike. - var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, new System.IO.Abstractions.FileSystem()) + var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, _fileSystem) .LoadChangelogConfiguration(collector, config, ctx); var resolvedRepo = !string.IsNullOrWhiteSpace(repo) ? repo : bundleConfig?.Bundle?.Repo; var resolvedOwner = owner ?? bundleConfig?.Bundle?.Owner ?? "elastic"; @@ -546,7 +546,7 @@ public async Task Bundle( } // Precedence: --repo CLI > bundle.repo config; --owner CLI > bundle.owner config > "elastic" - var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, new System.IO.Abstractions.FileSystem()) + var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, _fileSystem) .LoadChangelogConfiguration(collector, config, ctx); var resolvedRepo = !string.IsNullOrWhiteSpace(repo) ? repo : bundleConfig?.Bundle?.Repo; var resolvedOwner = owner ?? bundleConfig?.Bundle?.Owner ?? "elastic"; @@ -855,7 +855,7 @@ public async Task Remove( } // Precedence: --repo CLI > bundle.repo config; --owner CLI > bundle.owner config > "elastic" - var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, new System.IO.Abstractions.FileSystem()) + var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, _fileSystem) .LoadChangelogConfiguration(collector, config, ctx); var resolvedRepo = !string.IsNullOrWhiteSpace(repo) ? repo : bundleConfig?.Bundle?.Repo; var resolvedOwner = owner ?? bundleConfig?.Bundle?.Owner ?? "elastic"; @@ -1114,7 +1114,7 @@ public async Task GitHubRelease( await using var serviceInvoker = new ServiceInvoker(collector); // --output CLI > bundle.directory config > ./changelogs (service default) - var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, new System.IO.Abstractions.FileSystem()) + var bundleConfig = await new ChangelogConfigurationLoader(logFactory, configurationContext, _fileSystem) .LoadChangelogConfiguration(collector, config, ctx); var resolvedOutput = !string.IsNullOrWhiteSpace(output) ? output : bundleConfig?.Bundle?.Directory; diff --git a/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs b/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs index 6f736a25aa..a5d62a18cb 100644 --- a/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs +++ b/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs @@ -51,7 +51,7 @@ public async Task CloneAndBuild( Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; // Load codex configuration var configPath = fs.Path.GetFullPath(config); @@ -125,7 +125,7 @@ public async Task Clone( Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var configPath = fs.Path.GetFullPath(config); var configFile = fs.FileInfo.New(configPath); @@ -173,7 +173,7 @@ public async Task Build( Cancel ctx = default) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var configPath = fs.Path.GetFullPath(config); var configFile = fs.FileInfo.New(configPath); @@ -226,7 +226,7 @@ public async Task Serve( string? path = null, Cancel ctx = default) { - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var servePath = path ?? fs.Path.Join( Environment.CurrentDirectory, ".artifacts", "codex", "docs"); diff --git a/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs b/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs index c3e638b836..65d2b08ea9 100644 --- a/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs +++ b/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs @@ -94,7 +94,7 @@ public async Task Index( ) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var configPath = fs.Path.GetFullPath(config); var configFile = fs.FileInfo.New(configPath); diff --git a/src/tooling/docs-builder/Commands/Codex/CodexUpdateRedirectsCommand.cs b/src/tooling/docs-builder/Commands/Codex/CodexUpdateRedirectsCommand.cs index 3b8e3ec3d5..d888afcfc7 100644 --- a/src/tooling/docs-builder/Commands/Codex/CodexUpdateRedirectsCommand.cs +++ b/src/tooling/docs-builder/Commands/Codex/CodexUpdateRedirectsCommand.cs @@ -5,6 +5,7 @@ using System.IO.Abstractions; using ConsoleAppFramework; using Elastic.Documentation.Assembler.Deploying; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Codex; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Services; @@ -34,7 +35,7 @@ public async Task Run( { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealRead; var configPath = fs.Path.GetFullPath(config); var configFile = fs.FileInfo.New(configPath); diff --git a/src/tooling/docs-builder/Commands/DiffCommands.cs b/src/tooling/docs-builder/Commands/DiffCommands.cs index 895040cf8e..c3d1d01097 100644 --- a/src/tooling/docs-builder/Commands/DiffCommands.cs +++ b/src/tooling/docs-builder/Commands/DiffCommands.cs @@ -29,7 +29,7 @@ public async Task ValidateRedirects(string? path = null, Cancel ctx = defau await using var serviceInvoker = new ServiceInvoker(collector); var service = new LocalChangeTrackingService(logFactory, configurationContext); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealGitRootForPath(path); serviceInvoker.AddCommand(service, (path, fs), async static (s, collector, state, _) => await s.ValidateRedirects(collector, state.path, state.fs) diff --git a/src/tooling/docs-builder/Commands/FormatCommand.cs b/src/tooling/docs-builder/Commands/FormatCommand.cs index 7672c74583..2849bd3901 100644 --- a/src/tooling/docs-builder/Commands/FormatCommand.cs +++ b/src/tooling/docs-builder/Commands/FormatCommand.cs @@ -43,7 +43,7 @@ public async Task Format( await using var serviceInvoker = new ServiceInvoker(collector); var service = new FormatService(logFactory, configurationContext); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealGitRootForPath(path); serviceInvoker.AddCommand(service, (path, check, fs), async static (s, collector, state, ctx) => await s.Format(collector, state.path, state.check, state.fs, ctx) diff --git a/src/tooling/docs-builder/Commands/InboundLinkCommands.cs b/src/tooling/docs-builder/Commands/InboundLinkCommands.cs index 65e20a419d..1bdc3220a7 100644 --- a/src/tooling/docs-builder/Commands/InboundLinkCommands.cs +++ b/src/tooling/docs-builder/Commands/InboundLinkCommands.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using ConsoleAppFramework; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Links.InboundLinks; using Elastic.Documentation.Services; @@ -13,7 +14,7 @@ namespace Documentation.Builder.Commands; internal sealed class InboundLinkCommands(ILoggerFactory logFactory, IDiagnosticsCollector collector) { - private readonly LinkIndexService _linkIndexService = new(logFactory, new FileSystem()); + private readonly LinkIndexService _linkIndexService = new(logFactory, FileSystemFactory.RealRead); /// Validate all published cross_links in all published links.json files. /// diff --git a/src/tooling/docs-builder/Commands/IndexCommand.cs b/src/tooling/docs-builder/Commands/IndexCommand.cs index d7c27ea86b..556489eb0f 100644 --- a/src/tooling/docs-builder/Commands/IndexCommand.cs +++ b/src/tooling/docs-builder/Commands/IndexCommand.cs @@ -86,7 +86,7 @@ public async Task Index( ) { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealGitRootForPath(path); var service = new IsolatedIndexService(logFactory, configurationContext, githubActionsService, environmentVariables); var state = (fs, path, // endpoint options diff --git a/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs b/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs index 4b84218ec3..a1474e3505 100644 --- a/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs +++ b/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; using Actions.Core.Services; using ConsoleAppFramework; using Documentation.Builder.Arguments; @@ -63,15 +62,17 @@ public async Task Build( await using var serviceInvoker = new ServiceInvoker(collector); var service = new IsolatedBuildService(logFactory, configurationContext, githubActionsService, environmentVariables); - IFileSystem fs = inMemory ? new MockFileSystem() : new FileSystem(); + var readFs = inMemory ? FileSystemFactory.InMemory() : FileSystemFactory.RealGitRootForPath(path); + // For real builds supply an explicit write FS without .git access; for in-memory null falls back to readFs + var writeFs = inMemory ? null : FileSystemFactory.RealGitRootForPathWrite(path, output); var strictCommand = service.IsStrict(strict); serviceInvoker.AddCommand(service, - (path, output, pathPrefix, force, strict, allowIndexing, metadataOnly, exporters, canonicalBaseUrl, fs, skipApi), strictCommand, + (path, output, pathPrefix, force, strict, allowIndexing, metadataOnly, exporters, canonicalBaseUrl, readFs, writeFs, skipApi), strictCommand, async static (s, collector, state, ctx) => await s.Build( - collector, state.fs, state.path, state.output, state.pathPrefix, + collector, state.readFs, state.path, state.output, state.pathPrefix, state.force, state.strict, state.allowIndexing, state.metadataOnly, - state.exporters, state.canonicalBaseUrl, null, state.skipApi, false, ctx + state.exporters, state.canonicalBaseUrl, state.writeFs, state.skipApi, false, ctx ) ); return await serviceInvoker.InvokeAsync(ctx); diff --git a/src/tooling/docs-builder/Commands/MoveCommand.cs b/src/tooling/docs-builder/Commands/MoveCommand.cs index 2ce50e6afd..540e7da3a7 100644 --- a/src/tooling/docs-builder/Commands/MoveCommand.cs +++ b/src/tooling/docs-builder/Commands/MoveCommand.cs @@ -38,7 +38,7 @@ public async Task Move( await using var serviceInvoker = new ServiceInvoker(collector); var service = new MoveFileService(logFactory, configurationContext); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealGitRootForPath(path); serviceInvoker.AddCommand(service, (source, target, dryRun, path, fs), async static (s, collector, state, ctx) => await s.Move(collector, state.source, state.target, state.dryRun, state.path, state.fs, ctx) diff --git a/src/tooling/docs-builder/Commands/ServeCommand.cs b/src/tooling/docs-builder/Commands/ServeCommand.cs index afc2b1e066..6b3b3ba883 100644 --- a/src/tooling/docs-builder/Commands/ServeCommand.cs +++ b/src/tooling/docs-builder/Commands/ServeCommand.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; using ConsoleAppFramework; using Documentation.Builder.Http; using Elastic.Documentation.Configuration; @@ -28,7 +27,7 @@ internal sealed class ServeCommand(ILoggerFactory logFactory, IConfigurationCont [Command("")] public async Task Serve(string? path = null, int port = 3000, bool watch = false, Cancel ctx = default) { - var host = new DocumentationWebHost(logFactory, path, port, new FileSystem(), new MockFileSystem(), configurationContext, watch); + var host = new DocumentationWebHost(logFactory, path, port, FileSystemFactory.RealGitRootForPath(path), FileSystemFactory.InMemory(), configurationContext, watch); await host.RunAsync(ctx); _logger.LogInformation("Find your documentation at http://localhost:{Port}/{Path}", port, host.GeneratorState.Generator.DocumentationSet.FirstInterestingUrl.TrimStart('/') diff --git a/src/tooling/docs-builder/Filters/CheckForUpdatesFilter.cs b/src/tooling/docs-builder/Filters/CheckForUpdatesFilter.cs index 0a246d3090..23eb693ad2 100644 --- a/src/tooling/docs-builder/Filters/CheckForUpdatesFilter.cs +++ b/src/tooling/docs-builder/Filters/CheckForUpdatesFilter.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.IO.Abstractions; using System.Reflection; using ConsoleAppFramework; using Elastic.Documentation; @@ -11,7 +12,9 @@ namespace Documentation.Builder.Filters; internal sealed class CheckForUpdatesFilter(ConsoleAppFilter next, GlobalCliArgs cli) : ConsoleAppFilter(next) { - private readonly FileInfo _stateFile = new(Path.Join(Paths.ApplicationData.FullName, "docs-build-check.state")); + // Only accesses ApplicationData — no workspace access needed + private static readonly IFileSystem Fs = FileSystemFactory.AppData; + private readonly IFileInfo _stateFile = Fs.FileInfo.New(Path.Join(Paths.ApplicationData.FullName, "docs-build-check.state")); public override async Task InvokeAsync(ConsoleAppContext context, Cancel ctx) { @@ -63,7 +66,7 @@ private static void CompareWithAssemblyVersion(Uri latestVersionUrl) // only check for new versions once per hour if (_stateFile.Exists && _stateFile.LastWriteTimeUtc >= DateTime.UtcNow.Subtract(TimeSpan.FromHours(1))) { - var url = await File.ReadAllTextAsync(_stateFile.FullName, ctx); + var url = await Fs.File.ReadAllTextAsync(_stateFile.FullName, ctx); if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) return uri; } @@ -76,9 +79,9 @@ private static void CompareWithAssemblyVersion(Uri latestVersionUrl) if (redirectUrl is not null && _stateFile.Directory is not null) { // ensure the 'elastic' folder exists. - if (!Directory.Exists(_stateFile.Directory.FullName)) - _ = Directory.CreateDirectory(_stateFile.Directory.FullName); - await File.WriteAllTextAsync(_stateFile.FullName, redirectUrl.ToString(), ctx); + if (!Fs.Directory.Exists(_stateFile.Directory.FullName)) + _ = Fs.Directory.CreateDirectory(_stateFile.Directory.FullName); + await Fs.File.WriteAllTextAsync(_stateFile.FullName, redirectUrl.ToString(), ctx); } return redirectUrl; } diff --git a/src/tooling/docs-builder/Http/DocumentationWebHost.cs b/src/tooling/docs-builder/Http/DocumentationWebHost.cs index d85303c585..9fe23eda14 100644 --- a/src/tooling/docs-builder/Http/DocumentationWebHost.cs +++ b/src/tooling/docs-builder/Http/DocumentationWebHost.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using System.Net; +using Nullean.ScopedFileSystem; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; @@ -34,15 +35,15 @@ public class DocumentationWebHost private readonly WebApplication _webApplication; private readonly IHostedService _hostedService; - private readonly IFileSystem _writeFileSystem; + private readonly ScopedFileSystem _writeFileSystem; public InMemoryBuildState InMemoryBuildState { get; } public DocumentationWebHost(ILoggerFactory logFactory, string? path, int port, - IFileSystem readFs, - IFileSystem writeFs, + ScopedFileSystem readFs, + ScopedFileSystem writeFs, IConfigurationContext configurationContext, bool isWatchBuild ) diff --git a/src/tooling/docs-builder/Http/InMemoryBuildState.cs b/src/tooling/docs-builder/Http/InMemoryBuildState.cs index cced222bc1..43154396c9 100644 --- a/src/tooling/docs-builder/Http/InMemoryBuildState.cs +++ b/src/tooling/docs-builder/Http/InMemoryBuildState.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; -using System.IO.Abstractions.TestingHelpers; using System.Text.Json.Serialization; using System.Threading.Channels; using Actions.Core; @@ -15,6 +14,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Isolated; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Documentation.Builder.Http; @@ -54,7 +54,7 @@ public class InMemoryBuildState(ILoggerFactory loggerFactory, IConfigurationCont private readonly List _diagnostics = []; // Reuse MockFileSystem across builds to benefit from caching - private readonly MockFileSystem _writeFs = new(); + private readonly ScopedFileSystem _writeFs = FileSystemFactory.InMemory(); // Broadcast: maintain list of connected client channels private readonly Lock _clientsLock = new(); @@ -169,7 +169,7 @@ private async Task ExecuteBuildAsync(string sourcePath, Cancel ct) // Create a diagnostics collector that streams to our channel var streamingCollector = new StreamingDiagnosticsCollector(_loggerFactory, this); - var readFs = new FileSystem(); + var readFs = FileSystemFactory.RealGitRootForPath(sourcePath); var service = new IsolatedBuildService(_loggerFactory, _configurationContext, new NullCoreService(), SystemEnvironmentVariables.Instance); _logger.LogInformation("Starting in-memory validation build for {Path}", sourcePath); diff --git a/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs b/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs index 5f9ac5a992..104e0aa740 100644 --- a/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs +++ b/src/tooling/docs-builder/Http/ReloadableGeneratorState.cs @@ -45,7 +45,7 @@ bool isWatchBuild ApiPath = context.WriteFileSystem.DirectoryInfo.New(Path.Join(outputPath.FullName, "api")); if (context.Configuration.Registry != DocSetRegistry.Public) - _codexReader = new GitLinkIndexReader(context.Configuration.Registry.ToStringFast(true), context.ReadFileSystem); + _codexReader = new GitLinkIndexReader(context.Configuration.Registry.ToStringFast(true), FileSystemFactory.AppData); _crossLinkFetcher = new DocSetConfigurationCrossLinkFetcher(logFactory, _context.Configuration, codexLinkIndexReader: _codexReader); // we pass NoopCrossLinkResolver.Instance here because `ReloadAsync` will always be called when the is started. diff --git a/src/tooling/docs-builder/Http/StaticWebHost.cs b/src/tooling/docs-builder/Http/StaticWebHost.cs index 9aa420920b..788509e6bc 100644 --- a/src/tooling/docs-builder/Http/StaticWebHost.cs +++ b/src/tooling/docs-builder/Http/StaticWebHost.cs @@ -25,7 +25,7 @@ public class StaticWebHost public StaticWebHost(int port, string? path) { _contentRoot = path ?? Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"); - var fs = new FileSystem(); + var fs = FileSystemFactory.RealGitRootForPath(_contentRoot); var dir = fs.DirectoryInfo.New(_contentRoot); if (!dir.Exists) throw new Exception($"Can not serve empty directory: {_contentRoot}"); diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/AssemblerConfigurationTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/AssemblerConfigurationTests.cs index 9b0581b53c..61775c3e74 100644 --- a/tests-integration/Elastic.Assembler.IntegrationTests/AssemblerConfigurationTests.cs +++ b/tests-integration/Elastic.Assembler.IntegrationTests/AssemblerConfigurationTests.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Diagnostics; using Microsoft.Extensions.Logging.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.Assembler.IntegrationTests; @@ -28,7 +29,8 @@ public PublicOnlyAssemblerConfigurationTests() var configurationFileProvider = new ConfigurationFileProvider(NullLoggerFactory.Instance, FileSystem, skipPrivateRepositories: true); var configurationContext = TestHelpers.CreateConfigurationContext(FileSystem, configurationFileProvider: configurationFileProvider); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - Context = new AssembleContext(config, configurationContext, "dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + var scopedFs = FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem); + Context = new AssembleContext(config, configurationContext, "dev", Collector, scopedFs, scopedFs, CheckoutDirectory.FullName, null); } [Fact] @@ -64,7 +66,8 @@ public AssemblerConfigurationTests(DocumentationFixture fixture, ITestOutputHelp Collector = new DiagnosticsCollector([]); var configurationContext = TestHelpers.CreateConfigurationContext(FileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - Context = new AssembleContext(config, configurationContext, "dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + var scopedFs = FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem); + Context = new AssembleContext(config, configurationContext, "dev", Collector, scopedFs, scopedFs, CheckoutDirectory.FullName, null); } [Fact] diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/DocsSyncTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/DocsSyncTests.cs index 4b6d0750db..cb8525a4d6 100644 --- a/tests-integration/Elastic.Assembler.IntegrationTests/DocsSyncTests.cs +++ b/tests-integration/Elastic.Assembler.IntegrationTests/DocsSyncTests.cs @@ -16,6 +16,7 @@ using Elastic.Documentation.ServiceDefaults.Telemetry; using FakeItEasy; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; using OpenTelemetry; using OpenTelemetry.Trace; @@ -39,12 +40,14 @@ public async Task TestPlan() { "docs/update.md", new MockFileData("# Existing Document") }, }, new MockFileSystemOptions { - CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly") + CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"), }); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - var context = new AssembleContext(config, configurationContext, "dev", collector, fileSystem, fileSystem, null, Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); + var scopedFs = FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem); + var scopedWriteFs = FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(fileSystem); + var context = new AssembleContext(config, configurationContext, "dev", collector, scopedFs, scopedWriteFs, null, Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); A.CallTo(() => mockS3Client.ListObjectsV2Async(A._, A._)) .Returns(new ListObjectsV2Response { @@ -178,14 +181,16 @@ bool valid var mockS3Client = A.Fake(); var fileSystem = new MockFileSystem(new MockFileSystemOptions { - CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly") + CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"), }); foreach (var i in Enumerable.Range(0, localFiles)) fileSystem.AddFile($"docs/file-{i}.md", new MockFileData($"# Local Document {i}")); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - var context = new AssembleContext(config, configurationContext, "dev", collector, fileSystem, fileSystem, null, Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); + var scopedFs2 = FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem); + var scopedWriteFs2 = FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(fileSystem); + var context = new AssembleContext(config, configurationContext, "dev", collector, scopedFs2, scopedWriteFs2, null, Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly")); var s3Objects = new List(); foreach (var i in Enumerable.Range(0, remoteFiles)) @@ -230,12 +235,14 @@ public async Task TestApply() { "docs/update.md", new MockFileData("# Existing Document") }, }, new MockFileSystemOptions { - CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly") + CurrentDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"), }); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); var checkoutDirectory = Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts", "assembly"); - var context = new AssembleContext(config, configurationContext, "dev", collector, fileSystem, fileSystem, null, checkoutDirectory); + var scopedFs3 = FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem); + var scopedWriteFs3 = FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(fileSystem); + var context = new AssembleContext(config, configurationContext, "dev", collector, scopedFs3, scopedWriteFs3, null, checkoutDirectory); var plan = new SyncPlan { RemoteListingCompleted = true, diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/NavigationBuildingTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/NavigationBuildingTests.cs index 64e26b91af..7f7276a627 100644 --- a/tests-integration/Elastic.Assembler.IntegrationTests/NavigationBuildingTests.cs +++ b/tests-integration/Elastic.Assembler.IntegrationTests/NavigationBuildingTests.cs @@ -21,6 +21,7 @@ using Elastic.Documentation.Site.Navigation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Nullean.ScopedFileSystem; using RazorSlices; namespace Elastic.Assembler.IntegrationTests; @@ -46,7 +47,7 @@ public async Task AssertRealNavigation() var assemblyConfiguration = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); var collector = new TestDiagnosticsCollector(TestContext.Current.TestOutputHelper); var fs = new FileSystem(); - var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, "dev", collector, fs, new MockFileSystem(), null, null); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, "dev", collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fs), FileSystemFactory.ScopeCurrentWorkingDirectory(new MockFileSystem()), null, null); var logFactory = new TestLoggerFactory(TestContext.Current.TestOutputHelper); var cloner = new AssemblerRepositorySourcer(logFactory, assembleContext); var checkoutResult = cloner.GetAll(); diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/NavigationRootTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/NavigationRootTests.cs index 35dcbda1fc..9db9a751e8 100644 --- a/tests-integration/Elastic.Assembler.IntegrationTests/NavigationRootTests.cs +++ b/tests-integration/Elastic.Assembler.IntegrationTests/NavigationRootTests.cs @@ -21,6 +21,7 @@ using Elastic.Documentation.Site.Navigation; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Nullean.ScopedFileSystem; using RazorSlices; namespace Elastic.Assembler.IntegrationTests; @@ -46,7 +47,7 @@ public async Task AssertRealNavigation() var assemblyConfiguration = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); var collector = new TestDiagnosticsCollector(TestContext.Current.TestOutputHelper); var fs = new FileSystem(); - var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, "dev", collector, fs, new MockFileSystem(), null, null); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, "dev", collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fs), FileSystemFactory.ScopeCurrentWorkingDirectory(new MockFileSystem()), null, null); var logFactory = new TestLoggerFactory(TestContext.Current.TestOutputHelper); var cloner = new AssemblerRepositorySourcer(logFactory, assembleContext); var checkoutResult = cloner.GetAll(); diff --git a/tests-integration/Elastic.Assembler.IntegrationTests/SiteNavigationTests.cs b/tests-integration/Elastic.Assembler.IntegrationTests/SiteNavigationTests.cs index a47e727e68..d89cd503a7 100644 --- a/tests-integration/Elastic.Assembler.IntegrationTests/SiteNavigationTests.cs +++ b/tests-integration/Elastic.Assembler.IntegrationTests/SiteNavigationTests.cs @@ -15,6 +15,7 @@ using Elastic.Documentation.Navigation.Assembler; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.Assembler.IntegrationTests; @@ -43,7 +44,8 @@ public SiteNavigationTests(DocumentationFixture fixture, ITestOutputHelper outpu Collector = new DiagnosticsCollector([]); var configurationContext = TestHelpers.CreateConfigurationContext(FileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - Context = new AssembleContext(config, configurationContext, "dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + var scopedFs = FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem); + Context = new AssembleContext(config, configurationContext, "dev", Collector, scopedFs, scopedFs, CheckoutDirectory.FullName, null); } private Checkout CreateCheckout(IFileSystem fs, Repository repository) @@ -96,7 +98,8 @@ public async Task ReadAllPathPrefixes() var fileSystem = new FileSystem(); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - var context = new AssembleContext(config, configurationContext, "dev", collector, fileSystem, fileSystem, null, null); + var scopedFileSystem = FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem); + var context = new AssembleContext(config, configurationContext, "dev", collector, scopedFileSystem, scopedFileSystem, null, null); var navigationFileInfo = configurationContext.ConfigurationFileProvider.NavigationFile; var siteNavigationFile = SiteNavigationFile.Deserialize(await FileSystem.File.ReadAllTextAsync(navigationFileInfo.FullName, TestContext.Current.CancellationToken)); @@ -188,7 +191,8 @@ public async Task UriResolving() var fs = new FileSystem(); var configurationContext = TestHelpers.CreateConfigurationContext(fs); var config = AssemblyConfiguration.Create(configurationContext.ConfigurationFileProvider); - var assembleContext = new AssembleContext(config, configurationContext, "prod", collector, fs, fs, null, null); + var scopedFs = FileSystemFactory.ScopeCurrentWorkingDirectory(fs); + var assembleContext = new AssembleContext(config, configurationContext, "prod", collector, scopedFs, scopedFs, null, null); var repos = assembleContext.Configuration.AvailableRepositories .Where(kv => !kv.Value.Skip) .Select(kv => kv.Value) @@ -223,7 +227,7 @@ public ValueTask DisposeAsync() GC.SuppressFinalize(this); if (TestContext.Current.TestState?.Result is TestResult.Passed) return default; - foreach (var resource in _fixture.InMemoryLogger.RecordedLogs) + foreach (var resource in _fixture.InMemoryLogger.RecordedLogs.ToList()) _output.WriteLine(resource.Message); return default; } diff --git a/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj b/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj index f326f6d0cf..047a2e7df2 100644 --- a/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj +++ b/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj @@ -9,4 +9,8 @@ + + + + diff --git a/tests/Elastic.ApiExplorer.Tests/ReaderTests.cs b/tests/Elastic.ApiExplorer.Tests/ReaderTests.cs index 5decade2f6..7b7946c0a8 100644 --- a/tests/Elastic.ApiExplorer.Tests/ReaderTests.cs +++ b/tests/Elastic.ApiExplorer.Tests/ReaderTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Microsoft.Extensions.Logging.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.ApiExplorer.Tests; @@ -19,7 +20,7 @@ public async Task Reads() { var collector = new DiagnosticsCollector([]); var configurationContext = TestHelpers.CreateConfigurationContext(new FileSystem()); - var context = new BuildContext(collector, new FileSystem(), configurationContext); + var context = new BuildContext(collector, FileSystemFactory.RealRead, configurationContext); context.Configuration.OpenApiSpecifications.Should().NotBeNull().And.NotBeEmpty(); @@ -34,7 +35,7 @@ public async Task Navigation() { var collector = new DiagnosticsCollector([]); var configurationContext = TestHelpers.CreateConfigurationContext(new FileSystem()); - var context = new BuildContext(collector, new FileSystem(), configurationContext); + var context = new BuildContext(collector, FileSystemFactory.RealRead, configurationContext); var generator = new OpenApiGenerator(NullLoggerFactory.Instance, context, NoopMarkdownStringRenderer.Instance); context.Configuration.OpenApiSpecifications.Should().NotBeNull().And.NotBeEmpty(); diff --git a/tests/Elastic.Changelog.Tests/Changelogs/BundleAmendTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/BundleAmendTests.cs index 78eb99c0c2..13f6cbb465 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/BundleAmendTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/BundleAmendTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.ReleaseNotes; namespace Elastic.Changelog.Tests.Changelogs; @@ -23,7 +24,7 @@ public BundleAmendTests(ITestOutputHelper output) : base(output) private string CreateChangelogDir() { - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); return changelogDir; } @@ -48,7 +49,7 @@ private async Task CreateResolvedBundle(CancellationToken ct) var changelogFile = FileSystem.Path.Join(_changelogDir, "1755268130-existing.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, ct); - var bundlePath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundlePath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); var input = new BundleChangelogsArguments { Directory = _changelogDir, @@ -87,7 +88,7 @@ private async Task CreateUnresolvedBundle(CancellationToken ct) var changelogFile = FileSystem.Path.Join(_changelogDir, "1755268130-existing.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, ct); - var bundlePath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundlePath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); var input = new BundleChangelogsArguments { Directory = _changelogDir, @@ -112,7 +113,7 @@ private async Task CreateUnresolvedBundle(CancellationToken ct) /// private async Task CreateNewChangelogFile(CancellationToken ct) { - var newDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var newDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(newDir); // language=yaml diff --git a/tests/Elastic.Changelog.Tests/Changelogs/BundleChangelogsTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/BundleChangelogsTests.cs index bd2d9eebf9..d508f5271f 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/BundleChangelogsTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/BundleChangelogsTests.cs @@ -5,6 +5,7 @@ using System.Text; using AwesomeAssertions; using Elastic.Changelog.Bundling; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs; @@ -24,7 +25,7 @@ public BundleChangelogsTests(ITestOutputHelper output) : base(output) private string CreateChangelogDir() { - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); return changelogDir; } @@ -65,7 +66,7 @@ public async Task BundleChangelogs_WithAllOption_CreatesValidBundle() { Directory = _changelogDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -125,7 +126,7 @@ public async Task BundleChangelogs_WithProductsFilter_FiltersCorrectly() { Directory = _changelogDir, InputProducts = [new ProductArgument { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "ga" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -192,7 +193,7 @@ public async Task BundleChangelogs_WithPrsFilter_FiltersCorrectly() { Directory = _changelogDir, Prs = ["https://github.com/elastic/elasticsearch/pull/100", "https://github.com/elastic/elasticsearch/pull/200"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -257,7 +258,7 @@ public async Task BundleChangelogs_WithIssuesFilter_FiltersCorrectly() { Directory = _changelogDir, Issues = ["https://github.com/elastic/elasticsearch/issues/100", "https://github.com/elastic/elasticsearch/issues/200"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -295,7 +296,7 @@ public async Task BundleChangelogs_WithOldPrFormat_StillMatchesWhenFilteringByPr { Directory = _changelogDir, Prs = ["https://github.com/elastic/elasticsearch/pull/999"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; var result = await Service.BundleChangelogs(Collector, input, TestContext.Current.CancellationToken); @@ -336,7 +337,7 @@ public async Task BundleChangelogs_WithPrsFilterAndUnmatchedPrs_EmitsWarnings() "https://github.com/elastic/elasticsearch/pull/200", "https://github.com/elastic/elasticsearch/pull/300" ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -388,7 +389,7 @@ public async Task BundleChangelogs_WithPrsFileFilter_FiltersCorrectly() await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); // Create PRs file - var prsFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var prsFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(prsFile)!); // language=yaml var prsContent = @@ -402,7 +403,7 @@ public async Task BundleChangelogs_WithPrsFileFilter_FiltersCorrectly() { Directory = _changelogDir, Prs = [prsFile], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -443,7 +444,7 @@ public async Task BundleChangelogs_WithPrNumberAndOwnerRepo_FiltersCorrectly() Prs = ["100"], Owner = "elastic", Repo = "elasticsearch", - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -481,7 +482,7 @@ public async Task BundleChangelogs_WithShortPrFormat_FiltersCorrectly() { Directory = _changelogDir, Prs = ["elastic/elasticsearch#133609"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -504,7 +505,7 @@ public async Task BundleChangelogs_WithNoMatchingFiles_ReturnsError() { Directory = _changelogDir, InputProducts = [new ProductArgument { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "ga" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -520,13 +521,13 @@ public async Task BundleChangelogs_WithNoMatchingFiles_ReturnsError() public async Task BundleChangelogs_WithInvalidDirectory_ReturnsError() { // Arrange - var invalidDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "nonexistent"); + var invalidDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "nonexistent"); var input = new BundleChangelogsArguments { Directory = invalidDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -576,7 +577,7 @@ public async Task BundleChangelogs_WithNoFilterOption_ReturnsError() var input = new BundleChangelogsArguments { Directory = _changelogDir, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -598,7 +599,7 @@ public async Task BundleChangelogs_WithMultipleFilterOptions_ReturnsError() Directory = _changelogDir, All = true, InputProducts = [new ProductArgument { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "ga" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -651,7 +652,7 @@ public async Task BundleChangelogs_WithMultipleProducts_CreatesValidBundle() new ProductArgument { Product = "cloud-serverless", Target = "2025-12-02", Lifecycle = "*" }, new ProductArgument { Product = "cloud-serverless", Target = "2025-12-06", Lifecycle = "*" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -708,7 +709,7 @@ public async Task BundleChangelogs_WithWildcardProductFilter_MatchesAllProducts( { Directory = _changelogDir, InputProducts = [new ProductArgument { Product = "*", Target = "9.2.0", Lifecycle = "ga" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -762,7 +763,7 @@ public async Task BundleChangelogs_WithWildcardAllParts_EquivalentToAll() { Directory = _changelogDir, InputProducts = [new ProductArgument { Product = "*", Target = "*", Lifecycle = "*" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -830,7 +831,7 @@ public async Task BundleChangelogs_WithPrefixWildcardTarget_MatchesCorrectly() { Directory = _changelogDir, InputProducts = [new ProductArgument { Product = "elasticsearch", Target = "9.3.*", Lifecycle = "*" }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -852,12 +853,12 @@ public async Task BundleChangelogs_WithNonExistentFileAsPrs_ReturnsError() // Arrange // Provide a non-existent file path - should return error since there are no other PRs - var nonexistentFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "nonexistent.txt"); + var nonexistentFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "nonexistent.txt"); var input = new BundleChangelogsArguments { Directory = _changelogDir, Prs = [nonexistentFile], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -894,7 +895,7 @@ public async Task BundleChangelogs_WithUrlAsPrs_TreatsAsPrIdentifier() { Directory = _changelogDir, Prs = ["https://github.com/elastic/elasticsearch/pull/123"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -930,12 +931,12 @@ public async Task BundleChangelogs_WithNonExistentFileAndOtherPrs_EmitsWarning() await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); // Provide a non-existent file path along with a valid PR - should emit warning for file but continue with PR - var nonexistentFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "nonexistent.txt"); + var nonexistentFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "nonexistent.txt"); var input = new BundleChangelogsArguments { Directory = _changelogDir, Prs = [nonexistentFile, "https://github.com/elastic/elasticsearch/pull/123"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -996,7 +997,7 @@ public async Task BundleChangelogs_WithOutputProducts_OverridesChangelogProducts new ProductArgument { Product = "cloud-serverless", Target = "2025-12-02", Lifecycle = "ga" }, new ProductArgument { Product = "cloud-serverless", Target = "2025-12-06", Lifecycle = "beta" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1074,7 +1075,7 @@ public async Task BundleChangelogs_WithMultipleProducts_IncludesAllProducts() { Directory = _changelogDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1136,7 +1137,7 @@ public async Task BundleChangelogs_WithInputProducts_IncludesLifecycleInProducts new ProductArgument { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "ga" }, new ProductArgument { Product = "elasticsearch", Target = "9.3.0", Lifecycle = "beta" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1184,7 +1185,7 @@ public async Task BundleChangelogs_WithOutputProducts_IncludesLifecycleInProduct new ProductArgument { Product = "cloud-serverless", Target = "2025-12-02", Lifecycle = "ga" }, new ProductArgument { Product = "cloud-serverless", Target = "2025-12-06", Lifecycle = "beta" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1242,7 +1243,7 @@ public async Task BundleChangelogs_ExtractsLifecycleFromChangelogEntries() { Directory = _changelogDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1290,7 +1291,7 @@ public async Task BundleChangelogs_WithInputProductsWildcardLifecycle_ExtractsAc [ new ProductArgument { Product = "elasticsearch", Target = "9.2.0", Lifecycle = "*" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1364,7 +1365,7 @@ public async Task BundleChangelogs_WithMultipleTargets_WarningIncludesLifecycle( { Directory = _changelogDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1410,7 +1411,7 @@ public async Task BundleChangelogs_WithResolve_CopiesChangelogContents() Directory = _changelogDir, All = true, Resolve = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1448,7 +1449,7 @@ public async Task BundleChangelogs_WithExplicitResolveFalse_OverridesConfigResol resolve: true """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -1473,7 +1474,7 @@ public async Task BundleChangelogs_WithExplicitResolveFalse_OverridesConfigResol All = true, Resolve = false, Config = configPath, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1519,7 +1520,7 @@ public async Task BundleChangelogs_WithResolve_PreservesSpecialCharactersInUtf8( var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-special-chars.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, Encoding.UTF8, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); var input = new BundleChangelogsArguments { Directory = _changelogDir, @@ -1600,7 +1601,7 @@ public async Task BundleChangelogs_WithDirectoryOutputPath_CreatesDefaultFilenam await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); // Use a directory path with default filename (simulating command layer processing) - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var outputPath = FileSystem.Path.Join(outputDir, "changelog-bundle.yaml"); var input = new BundleChangelogsArguments @@ -1647,7 +1648,7 @@ public async Task BundleChangelogs_WithResolveAndMissingTitle_ReturnsError() Directory = _changelogDir, All = true, Resolve = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1681,7 +1682,7 @@ public async Task BundleChangelogs_WithResolveAndMissingType_ReturnsError() Directory = _changelogDir, All = true, Resolve = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1713,7 +1714,7 @@ public async Task BundleChangelogs_WithResolveAndMissingProducts_ReturnsError() Directory = _changelogDir, All = true, Resolve = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1747,7 +1748,7 @@ public async Task BundleChangelogs_WithResolveAndInvalidProduct_ReturnsError() Directory = _changelogDir, All = true, Resolve = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1785,7 +1786,7 @@ public async Task BundleChangelogs_WithHideFeaturesOption_IncludesHideFeaturesIn Directory = _changelogDir, All = true, HideFeatures = ["feature:hidden-api", "feature:another-hidden"], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1827,7 +1828,7 @@ public async Task BundleChangelogs_WithoutHideFeaturesOption_OmitsHideFeaturesFi Directory = _changelogDir, All = true, // No HideFeatures - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1864,7 +1865,7 @@ public async Task BundleChangelogs_WithHideFeaturesFromFile_IncludesHideFeatures await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); // Create feature IDs file - var featureIdsFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "feature-ids.txt"); + var featureIdsFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "feature-ids.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(featureIdsFile)!); await FileSystem.File.WriteAllTextAsync(featureIdsFile, "feature:from-file\nfeature:another", TestContext.Current.CancellationToken); @@ -1873,7 +1874,7 @@ public async Task BundleChangelogs_WithHideFeaturesFromFile_IncludesHideFeatures Directory = _changelogDir, All = true, HideFeatures = [featureIdsFile], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1915,7 +1916,7 @@ public async Task BundleChangelogs_WithRepoOption_IncludesRepoInBundleProducts() Directory = _changelogDir, All = true, Repo = "cloud", // Set repo to "cloud" - different from product ID "cloud-serverless" - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1956,7 +1957,7 @@ public async Task BundleChangelogs_WithoutRepoOption_OmitsRepoFieldInOutput() Directory = _changelogDir, All = true, // No --repo option - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -1985,7 +1986,7 @@ public async Task BundleChangelogs_WithBundleLevelRepoConfig_UsesConfigRepoWhenO owner: elastic """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2005,7 +2006,7 @@ public async Task BundleChangelogs_WithBundleLevelRepoConfig_UsesConfigRepoWhenO var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-serverless-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -2040,7 +2041,7 @@ public async Task BundleChangelogs_WithRepoOptionAndBundleLevelConfig_CliOptionT repo: wrong-repo """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2060,7 +2061,7 @@ public async Task BundleChangelogs_WithRepoOptionAndBundleLevelConfig_CliOptionT var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-serverless-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -2114,7 +2115,7 @@ public async Task BundleChangelogs_WithOutputProductsAndRepo_IncludesRepoInAllPr new ProductArgument { Product = "cloud-serverless", Target = "2025-12-02", Lifecycle = "ga" }, new ProductArgument { Product = "elasticsearch-serverless", Target = "2025-12-02", Lifecycle = "ga" } ], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -2137,7 +2138,7 @@ public async Task BundleChangelogs_WithConfigOutputDirectory_WhenOutputNotSpecif { // Arrange - When --output is not specified, use bundle.output_directory from config if set - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); // language=yaml @@ -2148,7 +2149,7 @@ public async Task BundleChangelogs_WithConfigOutputDirectory_WhenOutputNotSpecif output_directory: "{outputDir.Replace("\\", "/")}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config-output-dir", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config-output-dir", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2195,7 +2196,7 @@ public async Task BundleChangelogs_WithConfigDirectory_WhenDirectoryNotSpecified { // Arrange - When --directory is not specified (null), use bundle.directory from config if set - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); // language=yaml @@ -2206,7 +2207,7 @@ public async Task BundleChangelogs_WithConfigDirectory_WhenDirectoryNotSpecified output_directory: "{outputDir.Replace("\\", "/")}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config-dir", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config-dir", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2254,9 +2255,9 @@ public async Task BundleChangelogs_WithExplicitDirectory_OverridesConfigDirector // Arrange - config has directory pointing elsewhere, but CLI passes --directory explicitly. // The explicit CLI value must win (e.g. --directory . when cwd has changelogs). - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(configDir); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); // language=yaml @@ -2267,7 +2268,7 @@ public async Task BundleChangelogs_WithExplicitDirectory_OverridesConfigDirector output_directory: "{outputDir.Replace("\\", "/")}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config-dir-override", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config-dir-override", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2324,7 +2325,7 @@ public async Task BundleChangelogs_WithProfileHideFeatures_IncludesHideFeaturesI - feature:another-profile-hidden """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2345,7 +2346,7 @@ public async Task BundleChangelogs_WithProfileHideFeatures_IncludesHideFeaturesI var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -2394,7 +2395,7 @@ public async Task BundleChangelogs_WithProfile_OnlyProfileHideFeaturesAreUsed() - feature:from-profile """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config2", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config2", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2414,7 +2415,7 @@ public async Task BundleChangelogs_WithProfile_OnlyProfileHideFeaturesAreUsed() var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -2461,7 +2462,7 @@ public async Task BundleChangelogs_WithProfileMultipleHideFeatures_AllProfileFea - feature:profile-two """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "config3", "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "config3", "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2481,7 +2482,7 @@ public async Task BundleChangelogs_WithProfileMultipleHideFeatures_AllProfileFea var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -2547,7 +2548,7 @@ public async Task BundleChangelogs_WithComments_ProducesNormalizedChecksum() { Directory = _changelogDir, All = true, - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml") }; // Act @@ -2603,24 +2604,24 @@ public async Task BundleChangelogs_WithAndWithoutComments_ProduceSameChecksum() """; // Bundle with comments - var dir1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var dir1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(dir1); var file1 = FileSystem.Path.Join(dir1, "1755268130-shared.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelogWithComments, TestContext.Current.CancellationToken); - var output1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle1.yaml"); + var output1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle1.yaml"); var result1 = await Service.BundleChangelogs(Collector, new BundleChangelogsArguments { Directory = dir1, All = true, Output = output1 }, TestContext.Current.CancellationToken); // Bundle without comments - var dir2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var dir2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(dir2); var file2 = FileSystem.Path.Join(dir2, "1755268130-shared.yaml"); await FileSystem.File.WriteAllTextAsync(file2, changelogWithoutComments, TestContext.Current.CancellationToken); - var output2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle2.yaml"); + var output2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle2.yaml"); var result2 = await Service.BundleChangelogs(Collector, new BundleChangelogsArguments { Directory = dir2, All = true, Output = output2 @@ -2671,22 +2672,22 @@ public async Task BundleChangelogs_WithDifferentData_ProducesDifferentChecksum() """; // Bundle first file - var dir1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var dir1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(dir1); await FileSystem.File.WriteAllTextAsync(FileSystem.Path.Join(dir1, "1755268130-a.yaml"), changelog1, TestContext.Current.CancellationToken); - var output1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle1.yaml"); + var output1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle1.yaml"); await Service.BundleChangelogs(Collector, new BundleChangelogsArguments { Directory = dir1, All = true, Output = output1 }, TestContext.Current.CancellationToken); // Bundle second file - var dir2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var dir2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(dir2); await FileSystem.File.WriteAllTextAsync(FileSystem.Path.Join(dir2, "1755268130-b.yaml"), changelog2, TestContext.Current.CancellationToken); - var output2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle2.yaml"); + var output2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle2.yaml"); await Service.BundleChangelogs(Collector, new BundleChangelogsArguments { Directory = dir2, All = true, Output = output2 @@ -2709,7 +2710,7 @@ public async Task AmendBundle_WithComments_ProducesNormalizedChecksum() // Arrange - Amend service should also use normalized checksums // Create a base bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -2724,7 +2725,7 @@ public async Task AmendBundle_WithComments_ProducesNormalizedChecksum() await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); // Create a changelog file with comments - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -2777,7 +2778,7 @@ public async Task AmendBundle_WithResolve_ProducesNormalizedChecksum() // Arrange - Amend with --resolve should also use normalized checksums // Create a base bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -2792,7 +2793,7 @@ public async Task AmendBundle_WithResolve_ProducesNormalizedChecksum() await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); // Create a changelog file with comments - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -2860,7 +2861,7 @@ public async Task BundleChangelogs_WithProfile_OutputProducts_OverridesProductsA output_products: "elasticsearch {version} ga" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2880,7 +2881,7 @@ public async Task BundleChangelogs_WithProfile_OutputProducts_OverridesProductsA var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -2921,7 +2922,7 @@ public async Task BundleChangelogs_WithProfile_MalformedOutputProducts_EmitsErro output_products: "elasticsearch {version} ga extra-token" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2940,7 +2941,7 @@ public async Task BundleChangelogs_WithProfile_MalformedOutputProducts_EmitsErro var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -2973,7 +2974,7 @@ public async Task BundleChangelogs_WithProfile_MalformedProductsPattern_EmitsErr output: "elasticsearch-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -2992,7 +2993,7 @@ public async Task BundleChangelogs_WithProfile_MalformedProductsPattern_EmitsErr var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3034,7 +3035,7 @@ public async Task BundleChangelogs_WithProfile_RepoAndOwner_WritesValuesToProduc owner: elastic """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3054,7 +3055,7 @@ public async Task BundleChangelogs_WithProfile_RepoAndOwner_WritesValuesToProduc var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-serverless-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3098,7 +3099,7 @@ public async Task BundleChangelogs_WithProfile_BundleLevelRepo_AppliesWhenProfil output: "elasticsearch-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3118,7 +3119,7 @@ public async Task BundleChangelogs_WithProfile_BundleLevelRepo_AppliesWhenProfil var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3161,7 +3162,7 @@ public async Task BundleChangelogs_WithProfile_ProfileRepoOverridesBundleRepo() repo: elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3181,7 +3182,7 @@ public async Task BundleChangelogs_WithProfile_ProfileRepoOverridesBundleRepo() var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3224,7 +3225,7 @@ public async Task BundleChangelogs_WithProfile_NoRepoOwner_PreservesExistingFall output: "elasticsearch-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3244,7 +3245,7 @@ public async Task BundleChangelogs_WithProfile_NoRepoOwner_PreservesExistingFall var file1 = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3281,7 +3282,7 @@ public async Task BundleChangelogs_WithProfileMode_MissingConfig_ReturnsErrorWit currentDirectory: "/empty-project" ); cwdFs.Directory.CreateDirectory("/empty-project"); - var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, cwdFs); + var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, FileSystemFactory.ScopeCurrentWorkingDirectory(cwdFs)); var input = new BundleChangelogsArguments { @@ -3306,25 +3307,26 @@ public async Task BundleChangelogs_WithProfileMode_MissingConfig_ReturnsErrorWit public async Task BundleChangelogs_WithProfileMode_ConfigAtCurrentDir_LoadsSuccessfully() { // Arrange - changelog.yml is at ./changelog.yml (in the current working directory) + var root = Paths.WorkingDirectoryRoot.FullName; var cwdFs = new System.IO.Abstractions.TestingHelpers.MockFileSystem( null, - currentDirectory: "/test-root" + currentDirectory: root ); - cwdFs.Directory.CreateDirectory("/test-root"); - cwdFs.Directory.CreateDirectory("/test-root/changelogs"); - cwdFs.Directory.CreateDirectory("/test-root/output"); + cwdFs.Directory.CreateDirectory(root); + cwdFs.Directory.CreateDirectory(Path.Join(root, "changelogs")); + cwdFs.Directory.CreateDirectory(Path.Join(root, "output")); // language=yaml var configContent = - """ + $$""" bundle: - directory: /test-root/changelogs + directory: {{Path.Join(root, "changelogs")}} profiles: es-release: products: "elasticsearch {version} {lifecycle}" output: "elasticsearch-{version}.yaml" """; - await cwdFs.File.WriteAllTextAsync("/test-root/changelog.yml", configContent, TestContext.Current.CancellationToken); + await cwdFs.File.WriteAllTextAsync(Path.Join(root, "changelog.yml"), configContent, TestContext.Current.CancellationToken); // language=yaml var changelogContent = @@ -3338,16 +3340,16 @@ public async Task BundleChangelogs_WithProfileMode_ConfigAtCurrentDir_LoadsSucce prs: - https://github.com/elastic/elasticsearch/pull/100 """; - await cwdFs.File.WriteAllTextAsync("/test-root/changelogs/1755268130-feature.yaml", changelogContent, TestContext.Current.CancellationToken); + await cwdFs.File.WriteAllTextAsync(Path.Join(root, "changelogs/1755268130-feature.yaml"), changelogContent, TestContext.Current.CancellationToken); - var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, cwdFs); + var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, FileSystemFactory.ScopeCurrentWorkingDirectory(cwdFs)); var input = new BundleChangelogsArguments { Profile = "es-release", ProfileArgument = "9.2.0", - OutputDirectory = "/test-root/output" - // Config intentionally omitted — should discover /test-root/changelog.yml + OutputDirectory = Path.Join(root, "output") + // Config intentionally omitted — should discover changelog.yml in CWD }; // Act @@ -3356,34 +3358,35 @@ public async Task BundleChangelogs_WithProfileMode_ConfigAtCurrentDir_LoadsSucce // Assert result.Should().BeTrue($"Expected bundling to succeed. Errors: {string.Join("; ", Collector.Diagnostics.Where(d => d.Severity == Severity.Error).Select(d => d.Message))}"); Collector.Errors.Should().Be(0); - cwdFs.Directory.GetFiles("/test-root/output", "*.yaml").Should().NotBeEmpty("Expected output file to be created"); + cwdFs.Directory.GetFiles(Path.Join(root, "output"), "*.yaml").Should().NotBeEmpty("Expected output file to be created"); } [Fact] public async Task BundleChangelogs_WithProfileMode_ConfigAtDocsSubdir_LoadsSuccessfully() { // Arrange - changelog.yml is at ./docs/changelog.yml (the second discovery candidate) + var root = Paths.WorkingDirectoryRoot.FullName; var cwdFs = new System.IO.Abstractions.TestingHelpers.MockFileSystem( null, - currentDirectory: "/test-root" + currentDirectory: root ); - cwdFs.Directory.CreateDirectory("/test-root"); - cwdFs.Directory.CreateDirectory("/test-root/docs"); - cwdFs.Directory.CreateDirectory("/test-root/changelogs"); - cwdFs.Directory.CreateDirectory("/test-root/output"); + cwdFs.Directory.CreateDirectory(root); + cwdFs.Directory.CreateDirectory(Path.Join(root, "docs")); + cwdFs.Directory.CreateDirectory(Path.Join(root, "changelogs")); + cwdFs.Directory.CreateDirectory(Path.Join(root, "output")); // language=yaml var configContent = - """ + $$""" bundle: - directory: /test-root/changelogs + directory: {{Path.Join(root, "changelogs")}} profiles: es-release: products: "elasticsearch {version} {lifecycle}" output: "elasticsearch-{version}.yaml" """; // Config is in docs/ subdir, not in CWD directly - await cwdFs.File.WriteAllTextAsync("/test-root/docs/changelog.yml", configContent, TestContext.Current.CancellationToken); + await cwdFs.File.WriteAllTextAsync(Path.Join(root, "docs/changelog.yml"), configContent, TestContext.Current.CancellationToken); // language=yaml var changelogContent = @@ -3397,16 +3400,16 @@ public async Task BundleChangelogs_WithProfileMode_ConfigAtDocsSubdir_LoadsSucce prs: - https://github.com/elastic/elasticsearch/pull/100 """; - await cwdFs.File.WriteAllTextAsync("/test-root/changelogs/1755268130-feature.yaml", changelogContent, TestContext.Current.CancellationToken); + await cwdFs.File.WriteAllTextAsync(Path.Join(root, "changelogs/1755268130-feature.yaml"), changelogContent, TestContext.Current.CancellationToken); - var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, cwdFs); + var service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, FileSystemFactory.ScopeCurrentWorkingDirectory(cwdFs)); var input = new BundleChangelogsArguments { Profile = "es-release", ProfileArgument = "9.2.0", - OutputDirectory = "/test-root/output" - // Config intentionally omitted — should discover /test-root/docs/changelog.yml + OutputDirectory = Path.Join(root, "output") + // Config intentionally omitted — should discover docs/changelog.yml in CWD }; // Act @@ -3415,7 +3418,7 @@ public async Task BundleChangelogs_WithProfileMode_ConfigAtDocsSubdir_LoadsSucce // Assert result.Should().BeTrue($"Expected bundling to succeed. Errors: {string.Join("; ", Collector.Diagnostics.Where(d => d.Severity == Severity.Error).Select(d => d.Message))}"); Collector.Errors.Should().Be(0); - cwdFs.Directory.GetFiles("/test-root/output", "*.yaml").Should().NotBeEmpty("Expected output file to be created"); + cwdFs.Directory.GetFiles(Path.Join(root, "output"), "*.yaml").Should().NotBeEmpty("Expected output file to be created"); } // ─── Phase 3: URL list file and combined version+report ───────────────────────────── @@ -3431,7 +3434,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_PrUrls_FiltersCorrect release: output: "bundle.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3465,7 +3468,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_PrUrls_FiltersCorrect await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -3506,7 +3509,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_IssueUrls_FiltersCorr release: output: "bundle.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3540,7 +3543,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_IssueUrls_FiltersCorr await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "issues.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "issues.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -3582,7 +3585,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_Numbers_ReturnsError( release: output: "bundle.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3599,7 +3602,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, - https://github.com/elastic/elasticsearch/pull/100 """, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync(urlFile, "100\n200\n", TestContext.Current.CancellationToken); @@ -3635,7 +3638,7 @@ public async Task BundleChangelogs_WithProfile_UrlListFile_MixedPrsAndIssues_Ret release: output: "bundle.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3652,7 +3655,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, - https://github.com/elastic/elasticsearch/pull/100 """, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "mixed.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "mixed.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -3693,7 +3696,7 @@ public async Task BundleChangelogs_WithProfile_CombinedVersionAndReport_Substitu output_products: "cloud-serverless {version}" output: "serverless-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -3727,7 +3730,7 @@ public async Task BundleChangelogs_WithProfile_CombinedVersionAndReport_Substitu await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -3735,7 +3738,7 @@ await FileSystem.File.WriteAllTextAsync( TestContext.Current.CancellationToken ); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -3781,16 +3784,16 @@ public async Task BundleChangelogs_WithProfile_CombinedVersion_ReportArgLooksLik serverless-release: output: "serverless-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); // A "fake" HTML file to act as the profile arg (simulating user accidentally reversing the order) - var reportFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "report.html"); + var reportFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "report.html"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(reportFile)!); await FileSystem.File.WriteAllTextAsync(reportFile, "", TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync(urlFile, "https://github.com/elastic/cloud/pull/100\n", TestContext.Current.CancellationToken); @@ -3828,11 +3831,11 @@ public async Task BundleChangelogs_WithProfile_CombinedVersion_ProfileHasProduct products: "elasticsearch 9.2.0 ga" output: "bundle.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync(urlFile, "https://github.com/elastic/elasticsearch/pull/100\n", TestContext.Current.CancellationToken); @@ -3869,7 +3872,7 @@ public async Task BundleChangelogs_WithReportOption_ParsesPromotionReportAndFilt PR #200 """; - var reportFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "report.html"); + var reportFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "report.html"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(reportFile)!); await FileSystem.File.WriteAllTextAsync(reportFile, htmlReportContent, TestContext.Current.CancellationToken); @@ -3903,7 +3906,7 @@ public async Task BundleChangelogs_WithReportOption_ParsesPromotionReportAndFilt await FileSystem.File.WriteAllTextAsync(file1, changelog1, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -3946,7 +3949,7 @@ public async Task BundleChangelogs_WithReportOption_FileNotFound_ReturnsError() public async Task BundleChangelogs_WithPrsFile_ContainingNumbers_ReturnsError() { // Arrange - prs file contains bare numbers (not fully-qualified URLs) - var prsFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var prsFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(prsFile)!); await FileSystem.File.WriteAllTextAsync(prsFile, "100\n200\n", TestContext.Current.CancellationToken); @@ -3967,7 +3970,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, { Directory = _changelogDir, Prs = [prsFile], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "bundle.yaml") }; // Act @@ -3987,7 +3990,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, public async Task BundleChangelogs_WithIssuesFile_ContainingShortForms_ReturnsError() { // Arrange - issues file contains short forms (not fully-qualified URLs) - var issuesFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "issues.txt"); + var issuesFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "issues.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(issuesFile)!); await FileSystem.File.WriteAllTextAsync(issuesFile, "elastic/elasticsearch#100\n", TestContext.Current.CancellationToken); @@ -4008,7 +4011,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, { Directory = _changelogDir, Issues = [issuesFile], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "bundle.yaml") + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "bundle.yaml") }; // Act @@ -4028,7 +4031,7 @@ await FileSystem.File.WriteAllTextAsync(changelogFile, public async Task BundleChangelogs_WithPrsFile_ContainingValidUrls_FiltersCorrectly() { // Verify that a prs file with valid fully-qualified URLs still works correctly - var prsFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var prsFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(prsFile)!); await FileSystem.File.WriteAllTextAsync( prsFile, @@ -4051,7 +4054,7 @@ await FileSystem.File.WriteAllTextAsync( var file = FileSystem.Path.Join(_changelogDir, "1755268130-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file, changelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4079,7 +4082,7 @@ public async Task BundleChangelogs_WithRulesBundleExclude_ExcludesMatchingProduc exclude_products: cloud-hosted """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4114,7 +4117,7 @@ public async Task BundleChangelogs_WithRulesBundleExclude_ExcludesMatchingProduc await FileSystem.File.WriteAllTextAsync(file1, elasticsearchChangelog, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, cloudChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4151,7 +4154,7 @@ public async Task BundleChangelogs_WithRulesBundleInclude_IncludesOnlyMatchingPr include_products: elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4186,7 +4189,7 @@ public async Task BundleChangelogs_WithRulesBundleInclude_IncludesOnlyMatchingPr await FileSystem.File.WriteAllTextAsync(file1, elasticsearchChangelog, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, kibanaChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4222,7 +4225,7 @@ public async Task BundleChangelogs_WithAllFilter_AppliesRulesBundle() exclude_products: kibana """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4257,7 +4260,7 @@ public async Task BundleChangelogs_WithAllFilter_AppliesRulesBundle() await FileSystem.File.WriteAllTextAsync(file1, elasticsearchChangelog, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, kibanaChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4293,7 +4296,7 @@ public async Task BundleChangelogs_WithGlobalExcludeProductsMatchConjunction_Exc match_products: conjunction """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4330,7 +4333,7 @@ await FileSystem.File.WriteAllTextAsync( esAndKibana, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4364,7 +4367,7 @@ public async Task BundleChangelogs_WithGlobalIncludeProductsMatchConjunction_Req match_products: conjunction """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4400,7 +4403,7 @@ await FileSystem.File.WriteAllTextAsync( esSec, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4433,7 +4436,7 @@ public async Task BundleChangelogs_WithInputProducts_AppliesBundleRules() exclude_products: elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4454,7 +4457,7 @@ public async Task BundleChangelogs_WithInputProducts_AppliesBundleRules() var file1 = FileSystem.Path.Join(changelogDir, "1755268130-elasticsearch-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, elasticsearchChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); // Use InputProducts as primary filter — rules.bundle.exclude_products should still apply @@ -4487,7 +4490,7 @@ public async Task BundleChangelogs_WithRulesBundleExcludeType_ExcludesMatchingTy exclude_types: enhancement """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4522,7 +4525,7 @@ public async Task BundleChangelogs_WithRulesBundleExcludeType_ExcludesMatchingTy await FileSystem.File.WriteAllTextAsync(file1, featureChangelog, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, enhancementChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4558,7 +4561,7 @@ public async Task BundleChangelogs_WithRulesBundleIncludeArea_ExcludesNonMatchin include_areas: "Search" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4597,7 +4600,7 @@ public async Task BundleChangelogs_WithRulesBundleIncludeArea_ExcludesNonMatchin await FileSystem.File.WriteAllTextAsync(file1, searchChangelog, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, internalChangelog, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4635,7 +4638,7 @@ public async Task BundleChangelogs_WithRulesBundlePerProductOverride_AppliesProd include_areas: "Search" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4688,7 +4691,7 @@ public async Task BundleChangelogs_WithRulesBundlePerProductOverride_AppliesProd await FileSystem.File.WriteAllTextAsync(file2, serverlessSearch, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file3, serverlessOther, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4733,7 +4736,7 @@ public async Task BundleChangelogs_WithOutputProducts_SingleProductEntry_UsesMat include_areas: "Detection rules and alerts" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4788,7 +4791,7 @@ public async Task BundleChangelogs_WithOutputProducts_SingleProductEntry_UsesMat await FileSystem.File.WriteAllTextAsync(file2, securityEntry, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file3, securityOtherArea, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4834,7 +4837,7 @@ public async Task BundleChangelogs_WithOutputProducts_SharedProductEntry_UsesAlp include_areas: "Detection rules and alerts" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4874,7 +4877,7 @@ public async Task BundleChangelogs_WithOutputProducts_SharedProductEntry_UsesAlp await FileSystem.File.WriteAllTextAsync(file1, sharedEntry, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, kibanaOtherEntry, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4913,7 +4916,7 @@ public async Task BundleChangelogs_WithoutOutputProducts_FallsBackToEntryProduct exclude_types: docs """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -4947,7 +4950,7 @@ public async Task BundleChangelogs_WithoutOutputProducts_FallsBackToEntryProduct await FileSystem.File.WriteAllTextAsync(file1, kibanaDoc, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(file2, esDoc, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -4986,7 +4989,7 @@ public async Task BundleChangelogs_WithOutputProducts_EntryNotInContext_FallsBac exclude_types: feature """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5006,7 +5009,7 @@ public async Task BundleChangelogs_WithOutputProducts_EntryNotInContext_FallsBac var file1 = FileSystem.Path.Join(changelogDir, "1755268190-es-feature.yaml"); await FileSystem.File.WriteAllTextAsync(file1, esFeature, TestContext.Current.CancellationToken); - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); var input = new BundleChangelogsArguments @@ -5049,7 +5052,7 @@ public async Task BundleChangelogs_WithPerProductIncludeProducts_IncludesOnlyCon match_products: any """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5100,7 +5103,7 @@ public async Task BundleChangelogs_WithPerProductExcludeProducts_ExcludesContext match_products: any """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5172,7 +5175,7 @@ public async Task BundleChangelogs_WithPerProductRules_FallsBackToGlobalWhenNoCo - kibana """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5228,7 +5231,7 @@ public async Task BundleChangelogs_WithPerProductRules_ContextRulesTakePrecedenc - kibana """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5282,7 +5285,7 @@ private async Task CreateTestEntry(string changelogDir, string filename, string private string CreateTempFilePath(string filename) { - var outputPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), filename); + var outputPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), filename); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(outputPath)!); return outputPath; } @@ -5309,7 +5312,7 @@ public async Task BundleChangelogs_WithNoProductsField_FallsBackToGlobalRules() - "docs" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5363,7 +5366,7 @@ public async Task BundleChangelogs_GlobalMode_IncludeProductsAny_IncludesEntryMa - elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5429,7 +5432,7 @@ public async Task BundleChangelogs_GlobalMode_EmptyProducts_IncludesFeatureEntry - elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5476,7 +5479,7 @@ public async Task BundleChangelogs_WithEmptyProductsYamlMap_UsesGlobalRulesWhenG exclude_products: kibana """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5543,7 +5546,7 @@ public async Task BundleChangelogs_WithEmptyProductsList_FallsBackToGlobalRules( - "feature" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5608,7 +5611,7 @@ public async Task BundleChangelogs_WithMultipleProducts_UnifiedProductFiltering_ - "security" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5679,7 +5682,7 @@ public async Task BundleChangelogs_DisjointBundleContext_ProductFilteringFollows - "elasticsearch" # This should NOT apply to disjoint entry (would exclude if used) """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5751,7 +5754,7 @@ public async Task BundleChangelogs_MultiProductDisjoint_UsesGlobalRules() - "cloud-serverless" # This should NOT apply either """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5821,7 +5824,7 @@ public async Task BundleChangelogs_BundleAll_DisjointUsesOwnProductRules() - "kibana" # This SHOULD apply (excludes kibana entries) """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -5914,7 +5917,7 @@ public async Task BundleChangelogs_PartialPerProductRules_AllOrNothingReplacemen # No type/area rules here - global type exclusions are NOT inherited """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); diff --git a/tests/Elastic.Changelog.Tests/Changelogs/BundleProfileGitHubReleaseTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/BundleProfileGitHubReleaseTests.cs index 02ec3279a6..6efc646c74 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/BundleProfileGitHubReleaseTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/BundleProfileGitHubReleaseTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.GitHub; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using FakeItEasy; @@ -24,13 +25,13 @@ public BundleProfileGitHubReleaseTests(ITestOutputHelper output) : base(output) _mockReleaseService = A.Fake(); _service = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, FileSystem, _mockReleaseService); - _changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + _changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(_changelogDir); } private async Task CreateConfigAsync(string configContent) { - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); return configPath; @@ -100,7 +101,7 @@ public async Task ProfileGitHubRelease_BundlesMatchingChangelogs() A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "elasticsearch", "9.2.0", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v9.2.0", Name = "9.2.0", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -169,7 +170,7 @@ public async Task ProfileGitHubRelease_AutoInfersVersionAndLifecycle_FromRelease A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "elasticsearch", "9.2.0", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v9.2.0", Name = "9.2.0", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -320,7 +321,7 @@ public async Task ProfileGitHubRelease_Latest_CallsFetchWithLatestTag() A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "elasticsearch", "latest", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v9.2.0", Name = "9.2.0", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -499,7 +500,7 @@ public async Task ProfileGitHubRelease_InfersBetaLifecycle_FromTagSuffix() A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "elasticsearch", "9.2.0-beta.1", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v9.2.0-beta.1", Name = "9.2.0 beta 1", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -568,7 +569,7 @@ public async Task ProfileGitHubRelease_InfersPreviewLifecycle_FromTagSuffix() A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "apm-agent-dotnet", "v1.34.1-preview.1", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v1.34.1-preview.1", Name = "1.34.1 preview 1", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments @@ -637,7 +638,7 @@ public async Task ProfileGitHubRelease_BundleLevelRepo_UsedWhenProfileOmitsRepo( A.CallTo(() => _mockReleaseService.FetchReleaseAsync("elastic", "elasticsearch", "9.2.0", TestContext.Current.CancellationToken)) .Returns(new GitHubReleaseInfo { TagName = "v9.2.0", Name = "9.2.0", Body = releaseBody }); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(outputDir); var input = new BundleChangelogsArguments diff --git a/tests/Elastic.Changelog.Tests/Changelogs/BundleReleaseVersionTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/BundleReleaseVersionTests.cs index 05dd3d7781..e130c3cc5e 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/BundleReleaseVersionTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/BundleReleaseVersionTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.GitHub; +using Elastic.Documentation.Configuration; using Elastic.Documentation.ReleaseNotes; using FakeItEasy; using Xunit; @@ -26,12 +27,12 @@ public class BundleReleaseVersionTests : ChangelogTestBase public BundleReleaseVersionTests(ITestOutputHelper output) : base(output) { _bundlingService = new ChangelogBundlingService(LoggerFactory, ConfigurationContext, FileSystem); - _changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + _changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(_changelogDir); } private string BundleOutputPath() => - FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); // ----------------------------------------------------------------------- // Core flow: release → PR list → bundle diff --git a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogConfigurationTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogConfigurationTests.cs index 4d0176fa0f..78ea1ed0eb 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogConfigurationTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogConfigurationTests.cs @@ -6,6 +6,7 @@ using Elastic.Changelog.Configuration; using Elastic.Changelog.Serialization; using Elastic.Documentation; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Changelog; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.ReleaseNotes; @@ -19,7 +20,7 @@ public async Task LoadChangelogConfiguration_WithoutPivot_UsesDefaults() { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -59,7 +60,7 @@ public async Task LoadChangelogConfiguration_WithPivotTypes_UsesConfiguredTypes( { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -109,7 +110,7 @@ public async Task LoadChangelogConfiguration_WithoutAvailableLifecycles_UsesDefa { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -153,7 +154,7 @@ public async Task LoadChangelogConfiguration_WithPivotAreas_ComputesLabelToAreas { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -206,7 +207,7 @@ public async Task LoadChangelogConfiguration_WithPivotTypesLabels_ComputesLabelT { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -255,7 +256,7 @@ public async Task LoadChangelogConfiguration_WithInvalidPivotType_ReturnsError() { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -299,7 +300,7 @@ public async Task LoadChangelogConfiguration_WithMissingRequiredTypes_ReturnsErr { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -340,7 +341,7 @@ public async Task LoadChangelogConfiguration_WithSubtypesOnNonBreakingChange_Ret { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -386,7 +387,7 @@ public async Task LoadChangelogConfiguration_WithSubtypesOnBreakingChange_Succee { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -430,7 +431,7 @@ public async Task LoadChangelogConfiguration_WithInvalidSubtype_ReturnsError() { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -874,7 +875,7 @@ public async Task LoadChangelogConfiguration_MixedStringAndListForms_ParsesCorre private async Task LoadConfig(string yamlContent) { var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -902,7 +903,7 @@ public async Task LoadChangelogConfiguration_BundleSection_ParsesRepoOwnerDirect { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -943,7 +944,7 @@ public async Task LoadChangelogConfiguration_BundleSectionAbsent_BundleIsNull() { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -979,7 +980,7 @@ public async Task LoadChangelogConfiguration_NoConfigFile_ReturnsDefaultWithNull { // Arrange – no changelog.yml on disk; simulates running from a directory without a config var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(configDir); var originalDir = FileSystem.Directory.GetCurrentDirectory(); @@ -1005,7 +1006,7 @@ public async Task LoadChangelogConfiguration_WithPivotProducts_ComputesLabelToPr { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -1053,7 +1054,7 @@ public async Task LoadChangelogConfiguration_WithPivotProducts_ProductSpecWithTa { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -1099,7 +1100,7 @@ public async Task LoadChangelogConfiguration_WithPivotProducts_InvalidProductId_ { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var docsDir = FileSystem.Path.Join(configDir, "docs"); FileSystem.Directory.CreateDirectory(docsDir); var configPath = FileSystem.Path.Join(docsDir, "changelog.yml"); @@ -1140,7 +1141,7 @@ public async Task LoadChangelogConfiguration_WithRulesBundle_LoadsCorrectly() { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); // language=yaml var configContent = @@ -1172,7 +1173,7 @@ public async Task LoadChangelogConfiguration_WithRulesBundle_LoadsCorrectly() public async Task LoadChangelogConfiguration_WithRulesBundle_MatchProductsConjunction_LoadsCorrectly() { var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = """ @@ -1197,7 +1198,7 @@ public async Task LoadChangelogConfiguration_WithRulesBundle_BothExcludeAndInclu { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); // language=yaml var configContent = @@ -1268,7 +1269,7 @@ public async Task LoadChangelogConfiguration_WithRulesBundle_UnknownProductId_Re { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); // language=yaml var configContent = @@ -1293,7 +1294,7 @@ public async Task LoadChangelogConfiguration_WithRulesPublish_EmitsDeprecationWa { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); // language=yaml var configContent = @@ -1318,7 +1319,7 @@ public async Task LoadChangelogConfiguration_WithRulesBundle_TypeAreaAndProducts { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); // language=yaml var configContent = @@ -1442,7 +1443,7 @@ public async Task LoadChangelogConfiguration_WithPerProductProductFiltering_Load { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = @@ -1490,7 +1491,7 @@ public async Task LoadChangelogConfiguration_WithPerProductProductFiltering_Mutu { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = @@ -1520,7 +1521,7 @@ public async Task LoadChangelogConfiguration_WithPerProductProductFiltering_Mode { // Arrange — Mode 3 ignores global rules.bundle product lists; per-product lists need not align with globals. var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = @@ -1553,7 +1554,7 @@ public async Task LoadChangelogConfiguration_WithPerProductProductFiltering_Prod { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = @@ -1586,7 +1587,7 @@ public async Task LoadChangelogConfiguration_WithPerProductProductFiltering_Inva { // Arrange var configLoader = new ChangelogConfigurationLoader(LoggerFactory, ConfigurationContext, FileSystem); - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); var configContent = diff --git a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogRemoveTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogRemoveTests.cs index f9ae194f1d..3f9e016501 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogRemoveTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogRemoveTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs; @@ -77,7 +78,7 @@ public ChangelogRemoveTests(ITestOutputHelper output) : base(output) private string CreateChangelogDir() { - var dir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var dir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(dir); return dir; } @@ -382,7 +383,7 @@ public async Task Remove_WithBundlesDirOverride_UsesSpecifiedPath() await WriteFile("1001-es-feature.yaml", ElasticsearchFeatureYaml); // Create a bundles dir in a custom location - var customBundlesDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var customBundlesDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(customBundlesDir); var checksum = ComputeSha1(ElasticsearchFeatureYaml); await FileSystem.File.WriteAllTextAsync( @@ -473,7 +474,7 @@ public async Task Remove_WithProfileAndVersion_DeletesMatchingProducts() products: "elasticsearch {version} {lifecycle}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -504,7 +505,7 @@ public async Task Remove_WithProfileAndPromotionReport_DeletesMatchingPrs() await WriteFile("2001-kibana-feature.yaml", KibanaFeatureYaml); var reportContent = "https://github.com/elastic/elasticsearch/pull/1001"; - var reportPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), "report.html"); + var reportPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, $"report-{Guid.NewGuid()}.html"); await FileSystem.File.WriteAllTextAsync(reportPath, reportContent, TestContext.Current.CancellationToken); // language=yaml @@ -516,7 +517,7 @@ public async Task Remove_WithProfileAndPromotionReport_DeletesMatchingPrs() products: "elasticsearch {version} {lifecycle}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -550,7 +551,7 @@ public async Task Remove_WithProfile_UnknownProfile_ReturnsError() products: "elasticsearch {version} {lifecycle}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -585,7 +586,7 @@ public async Task Remove_WithProfile_MissingProfileArg_ReturnsError() products: "elasticsearch {version} {lifecycle}" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -616,7 +617,7 @@ public async Task Remove_WithProfileMode_MissingConfig_ReturnsErrorWithAdvice() currentDirectory: "/empty-project" ); cwdFs.Directory.CreateDirectory("/empty-project"); - var service = new ChangelogRemoveService(LoggerFactory, ConfigurationContext, cwdFs); + var service = new ChangelogRemoveService(LoggerFactory, ConfigurationContext, FileSystemFactory.ScopeCurrentWorkingDirectory(cwdFs)); var input = new ChangelogRemoveArguments { @@ -653,7 +654,7 @@ public async Task Remove_WithProfile_NoProductsAndVersionArg_ReturnsSpecificErro output: "release-{version}.yaml" """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -690,12 +691,12 @@ public async Task Remove_WithProfile_UrlListFile_PrUrls_RemovesMatchedFiles() release: """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); // URL file contains only the ES PR - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -735,11 +736,11 @@ public async Task Remove_WithProfile_CombinedVersionAndReport_UsesReportForFilte release: """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); - var urlFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "prs.txt"); + var urlFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "prs.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(urlFile)!); await FileSystem.File.WriteAllTextAsync( urlFile, @@ -777,7 +778,7 @@ public async Task Remove_WithReportOption_ParsesPromotionReportAndFilters() PR 1001 """; - var reportFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "report.html"); + var reportFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "report.html"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(reportFile)!); await FileSystem.File.WriteAllTextAsync(reportFile, htmlReport, TestContext.Current.CancellationToken); @@ -825,7 +826,7 @@ public async Task Remove_WithBundleOwnerConfig_UsesConfigOwnerWhenOptionNotSpeci repo: myrepo """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -873,7 +874,7 @@ public async Task Remove_WithBundleRepoConfig_UsesConfigRepoWhenOptionNotSpecifi owner: myorg """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -908,7 +909,7 @@ public async Task Remove_WithBundleOwnerConfig_CliOwnerTakesPrecedence() repo: elasticsearch """; - var configPath = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "changelog.yml"); + var configPath = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "changelog.yml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(configPath)!); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); diff --git a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogTestBase.cs b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogTestBase.cs index 1bc0e1eb7c..a52eccddcf 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/ChangelogTestBase.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/ChangelogTestBase.cs @@ -13,12 +13,13 @@ using Elastic.Documentation.Configuration.Versions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Nullean.ScopedFileSystem; namespace Elastic.Changelog.Tests.Changelogs; public abstract class ChangelogTestBase : IDisposable { - protected MockFileSystem FileSystem { get; } + protected ScopedFileSystem FileSystem { get; } protected IConfigurationContext ConfigurationContext { get; } protected TestDiagnosticsCollector Collector { get; } protected ILoggerFactory LoggerFactory { get; } @@ -27,7 +28,8 @@ public abstract class ChangelogTestBase : IDisposable protected ChangelogTestBase(ITestOutputHelper output) { Output = output; - FileSystem = new MockFileSystem(); + var mockFileSystem = new MockFileSystem(new MockFileSystemOptions { CurrentDirectory = Paths.WorkingDirectoryRoot.FullName }); + FileSystem = FileSystemFactory.ScopeCurrentWorkingDirectory(mockFileSystem); Collector = new TestDiagnosticsCollector(output); LoggerFactory = new TestLoggerFactory(output); diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Create/CreateChangelogTestBase.cs b/tests/Elastic.Changelog.Tests/Changelogs/Create/CreateChangelogTestBase.cs index 41e47e6899..56571efb93 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Create/CreateChangelogTestBase.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Create/CreateChangelogTestBase.cs @@ -4,6 +4,7 @@ using Elastic.Changelog.Creation; using Elastic.Changelog.GitHub; +using Elastic.Documentation.Configuration; using FakeItEasy; namespace Elastic.Changelog.Tests.Changelogs.Create; @@ -17,7 +18,7 @@ protected ChangelogCreationService CreateService() => protected async Task CreateConfigDirectory(string configContent) { - var configDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var configDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(configDir); var configPath = FileSystem.Path.Join(configDir, "changelog.yml"); await FileSystem.File.WriteAllTextAsync(configPath, configContent, TestContext.Current.CancellationToken); @@ -25,5 +26,5 @@ protected async Task CreateConfigDirectory(string configContent) } protected string CreateOutputDirectory() => - FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); } diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Create/PrIntegrationTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Create/PrIntegrationTests.cs index cdf0672ae5..0bc0eb04d3 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Create/PrIntegrationTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Create/PrIntegrationTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Creation; using Elastic.Changelog.GitHub; +using Elastic.Documentation.Configuration; using FakeItEasy; namespace Elastic.Changelog.Tests.Changelogs.Create; @@ -420,7 +421,7 @@ public async Task CreateChangelog_WithPrsFromFile_ProcessesAllPrsFromFile() A._)) .Returns(pr3Info); - var tempDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(tempDir); // Create a file with newline-delimited PRs (simulating what ChangelogCommand would read) @@ -518,7 +519,7 @@ public async Task CreateChangelog_WithMixedPrsFromFileAndCommaSeparated_Processe A._)) .Returns(pr2Info); - var tempDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var tempDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(tempDir); // Create a file with PRs diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Create/ReleaseVersionTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Create/ReleaseVersionTests.cs index 69c58507e4..aa50df84f7 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Create/ReleaseVersionTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Create/ReleaseVersionTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.GitHub; using Elastic.Changelog.GithubRelease; +using Elastic.Documentation.Configuration; using FakeItEasy; using Xunit; @@ -23,7 +24,7 @@ private GitHubReleaseChangelogService CreateService() => new(LoggerFactory, ConfigurationContext, _mockReleaseService, _mockPrService, FileSystem); private string CreateOutputDirectory() => - FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); // ----------------------------------------------------------------------- // Validation: no PR refs in release notes @@ -268,7 +269,7 @@ public async Task ReleaseVersion_OutputNull_ServiceUsesChangelogsDefault() A.CallTo(() => _mockPrService.FetchPrInfoAsync(A._, A._, A._, A._)) .Returns(new GitHubPrInfo { Title = "Fix something", Labels = [] }); - var workDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var workDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(workDir); var originalDir = FileSystem.Directory.GetCurrentDirectory(); try diff --git a/tests/Elastic.Changelog.Tests/Changelogs/RemoveReleaseVersionTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/RemoveReleaseVersionTests.cs index 14efc00f71..f64aeebf19 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/RemoveReleaseVersionTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/RemoveReleaseVersionTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.GitHub; +using Elastic.Documentation.Configuration; using Elastic.Documentation.ReleaseNotes; using FakeItEasy; @@ -25,7 +26,7 @@ public class RemoveReleaseVersionTests : ChangelogTestBase public RemoveReleaseVersionTests(ITestOutputHelper output) : base(output) { _removeService = new ChangelogRemoveService(LoggerFactory, ConfigurationContext, FileSystem); - _changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + _changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(_changelogDir); } diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/BasicRenderTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/BasicRenderTests.cs index 49d97479af..a3378ce3c7 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/BasicRenderTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/BasicRenderTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -14,7 +15,7 @@ public class BasicRenderTests(ITestOutputHelper output) : RenderChangelogTestBas public async Task RenderChangelogs_WithValidBundle_CreatesMarkdownFiles() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file @@ -35,7 +36,7 @@ public async Task RenderChangelogs_WithValidBundle_CreatesMarkdownFiles() await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -51,7 +52,7 @@ public async Task RenderChangelogs_WithValidBundle_CreatesMarkdownFiles() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -79,8 +80,8 @@ public async Task RenderChangelogs_WithValidBundle_CreatesMarkdownFiles() public async Task RenderChangelogs_WithMultipleBundles_MergesAndRenders() { // Arrange - var changelogDir1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); - var changelogDir2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); + var changelogDir2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir1); FileSystem.Directory.CreateDirectory(changelogDir2); @@ -114,7 +115,7 @@ public async Task RenderChangelogs_WithMultipleBundles_MergesAndRenders() await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); // Create bundle files - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundle1 = FileSystem.Path.Join(bundleDir, "bundle1.yaml"); @@ -145,7 +146,7 @@ public async Task RenderChangelogs_WithMultipleBundles_MergesAndRenders() """; await FileSystem.File.WriteAllTextAsync(bundle2, bundleContent2, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/BundleValidationTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/BundleValidationTests.cs index 5d105e1595..c3d0c6d41f 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/BundleValidationTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/BundleValidationTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -252,8 +253,8 @@ public async Task AmendFileEntry_CommentedFile_NormalizedChecksum_NoWarning() private (string BundleDir, string ChangelogDir) CreateTestDirs() { - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); FileSystem.Directory.CreateDirectory(changelogDir); return (bundleDir, changelogDir); @@ -283,7 +284,7 @@ private RenderChangelogsArguments CreateRenderInput(string bundleFile, string ch new() { Bundles = [new BundleInput { BundleFile = bundleFile, Directory = changelogDir }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()), + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()), Title = "9.2.0" }; } diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/ChecksumValidationTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/ChecksumValidationTests.cs index db6d9063b4..1ff1e6cab9 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/ChecksumValidationTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/ChecksumValidationTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -118,7 +119,7 @@ public async Task ValidateBundle_FileDataChanged_EmitsWarning() public async Task ValidateBundle_ResolvedEntry_SkipsChecksumValidation() { // Arrange — resolved entry has inline data, no file reference needed - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -164,14 +165,14 @@ public void Checksums_WithAndWithoutComments_AreEqual() string fileOnDisk, string storedChecksum) { - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); var changelogFileName = "1755268130-feature.yaml"; var changelogFile = FileSystem.Path.Join(changelogDir, changelogFileName); await FileSystem.File.WriteAllTextAsync(changelogFile, fileOnDisk, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -195,7 +196,7 @@ private RenderChangelogsArguments CreateRenderInput(string bundleFile, string ch new() { Bundles = [new BundleInput { BundleFile = bundleFile, Directory = changelogDir }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()), + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()), Title = "9.2.0" }; } diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/DuplicateHandlingTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/DuplicateHandlingTests.cs index 07096b230f..65281e0089 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/DuplicateHandlingTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/DuplicateHandlingTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -15,8 +16,8 @@ public class DuplicateHandlingTests(ITestOutputHelper output) : RenderChangelogT public async Task RenderChangelogs_WithDuplicateFileName_EmitsWarning() { // Arrange - var changelogDir1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); - var changelogDir2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); + var changelogDir2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir1); FileSystem.Directory.CreateDirectory(changelogDir2); @@ -40,7 +41,7 @@ public async Task RenderChangelogs_WithDuplicateFileName_EmitsWarning() await FileSystem.File.WriteAllTextAsync(file2, changelog, TestContext.Current.CancellationToken); // Create bundle files - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundle1 = FileSystem.Path.Join(bundleDir, "bundle1.yaml"); @@ -71,7 +72,7 @@ public async Task RenderChangelogs_WithDuplicateFileName_EmitsWarning() """; await FileSystem.File.WriteAllTextAsync(bundle2, bundleContent2, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -99,7 +100,7 @@ public async Task RenderChangelogs_WithDuplicateFileName_EmitsWarning() public async Task RenderChangelogs_WithDuplicateFileNameInSameBundle_EmitsWarning() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create changelog file @@ -120,7 +121,7 @@ public async Task RenderChangelogs_WithDuplicateFileNameInSameBundle_EmitsWarnin await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); // Create bundle file with the same file referenced twice - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -140,7 +141,7 @@ public async Task RenderChangelogs_WithDuplicateFileNameInSameBundle_EmitsWarnin """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -168,8 +169,8 @@ public async Task RenderChangelogs_WithDuplicateFileNameInSameBundle_EmitsWarnin public async Task RenderChangelogs_WithDuplicatePr_EmitsWarning() { // Arrange - var changelogDir1 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); - var changelogDir2 = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir1 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); + var changelogDir2 = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir1); FileSystem.Directory.CreateDirectory(changelogDir2); @@ -203,7 +204,7 @@ public async Task RenderChangelogs_WithDuplicatePr_EmitsWarning() await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); // Create bundle files - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundle1 = FileSystem.Path.Join(bundleDir, "bundle1.yaml"); @@ -234,7 +235,7 @@ public async Task RenderChangelogs_WithDuplicatePr_EmitsWarning() """; await FileSystem.File.WriteAllTextAsync(bundle2, bundleContent2, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/ErrorHandlingTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/ErrorHandlingTests.cs index 976ce6e65c..bd72bf9c67 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/ErrorHandlingTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/ErrorHandlingTests.cs @@ -6,6 +6,7 @@ using Elastic.Changelog.Bundling; using Elastic.Changelog.Configuration; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -16,12 +17,12 @@ public class ErrorHandlingTests(ITestOutputHelper output) : RenderChangelogTestB public async Task RenderChangelogs_WithMissingBundleFile_ReturnsError() { // Arrange - var missingBundle = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "nonexistent.yaml"); + var missingBundle = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "nonexistent.yaml"); var input = new RenderChangelogsArguments { Bundles = [new BundleInput { BundleFile = missingBundle }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()) + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()) }; // Act @@ -37,7 +38,7 @@ public async Task RenderChangelogs_WithMissingBundleFile_ReturnsError() public async Task RenderChangelogs_WithMissingChangelogFile_ReturnsError() { // Arrange - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -57,7 +58,7 @@ public async Task RenderChangelogs_WithMissingChangelogFile_ReturnsError() var input = new RenderChangelogsArguments { Bundles = [new BundleInput { BundleFile = bundleFile, Directory = bundleDir }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()) + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()) }; // Act @@ -73,7 +74,7 @@ public async Task RenderChangelogs_WithMissingChangelogFile_ReturnsError() public async Task RenderChangelogs_WithInvalidBundleStructure_ReturnsError() { // Arrange - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -87,7 +88,7 @@ public async Task RenderChangelogs_WithInvalidBundleStructure_ReturnsError() var input = new RenderChangelogsArguments { Bundles = [new BundleInput { BundleFile = bundleFile }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()) + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()) }; // Act @@ -103,7 +104,7 @@ public async Task RenderChangelogs_WithInvalidBundleStructure_ReturnsError() public async Task RenderChangelogs_WithInvalidChangelogFile_ReturnsError() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create invalid changelog file (missing required fields) @@ -118,7 +119,7 @@ public async Task RenderChangelogs_WithInvalidChangelogFile_ReturnsError() await FileSystem.File.WriteAllTextAsync(changelogFile, invalidChangelog, TestContext.Current.CancellationToken); // Create bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -138,7 +139,7 @@ public async Task RenderChangelogs_WithInvalidChangelogFile_ReturnsError() var input = new RenderChangelogsArguments { Bundles = [new BundleInput { BundleFile = bundleFile, Directory = changelogDir }], - Output = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()) + Output = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()) }; // Act @@ -154,7 +155,7 @@ public async Task RenderChangelogs_WithInvalidChangelogFile_ReturnsError() public async Task RenderChangelogs_WithResolvedEntry_ValidatesAndRenders() { // Arrange - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -175,7 +176,7 @@ public async Task RenderChangelogs_WithResolvedEntry_ValidatesAndRenders() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -204,7 +205,7 @@ public async Task RenderChangelogs_WithUnknownType_EmitsError() // Arrange // When an unknown type string is encountered during YAML deserialization, // it should be parsed as Invalid and an error should be emitted. - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create changelog with an unknown type that will be marked as Invalid @@ -223,7 +224,7 @@ public async Task RenderChangelogs_WithUnknownType_EmitsError() await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); // language=yaml @@ -239,7 +240,7 @@ public async Task RenderChangelogs_WithUnknownType_EmitsError() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/HideFeaturesTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/HideFeaturesTests.cs index 65ac13b633..020799bc4f 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/HideFeaturesTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/HideFeaturesTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -15,7 +16,7 @@ public class HideFeaturesTests(ITestOutputHelper output) : RenderChangelogTestBa public async Task RenderChangelogs_WithHideFeatures_CommentsOutMatchingEntries() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create changelog with feature-id @@ -53,7 +54,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommentsOutMatchingEntries() await FileSystem.File.WriteAllTextAsync(changelogFile2, changelog2, TestContext.Current.CancellationToken); // Create bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -73,7 +74,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommentsOutMatchingEntries() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -111,7 +112,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommentsOutMatchingEntries() public async Task RenderChangelogs_WithHideFeatures_BreakingChange_UsesBlockComments() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -133,7 +134,7 @@ public async Task RenderChangelogs_WithHideFeatures_BreakingChange_UsesBlockComm var changelogFile = FileSystem.Path.Join(changelogDir, "1755268130-breaking.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -150,7 +151,7 @@ public async Task RenderChangelogs_WithHideFeatures_BreakingChange_UsesBlockComm """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -186,7 +187,7 @@ public async Task RenderChangelogs_WithHideFeatures_BreakingChange_UsesBlockComm public async Task RenderChangelogs_WithHideFeatures_Deprecation_UsesBlockComments() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -206,7 +207,7 @@ public async Task RenderChangelogs_WithHideFeatures_Deprecation_UsesBlockComment var changelogFile = FileSystem.Path.Join(changelogDir, "1755268130-deprecation.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -223,7 +224,7 @@ public async Task RenderChangelogs_WithHideFeatures_Deprecation_UsesBlockComment """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -254,7 +255,7 @@ public async Task RenderChangelogs_WithHideFeatures_Deprecation_UsesBlockComment public async Task RenderChangelogs_WithHideFeatures_CommaSeparated_CommentsOutMatchingEntries() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -302,7 +303,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommaSeparated_CommentsOutMa await FileSystem.File.WriteAllTextAsync(changelogFile2, changelog2, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(changelogFile3, changelog3, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -325,7 +326,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommaSeparated_CommentsOutMa """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -354,7 +355,7 @@ public async Task RenderChangelogs_WithHideFeatures_CommaSeparated_CommentsOutMa public async Task RenderChangelogs_WithHideFeatures_FromFile_CommentsOutMatchingEntries() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -373,7 +374,7 @@ public async Task RenderChangelogs_WithHideFeatures_FromFile_CommentsOutMatching var changelogFile = FileSystem.Path.Join(changelogDir, "1755268130-hidden.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -391,11 +392,11 @@ public async Task RenderChangelogs_WithHideFeatures_FromFile_CommentsOutMatching await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); // Create feature IDs file - var featureIdsFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "feature-ids.txt"); + var featureIdsFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "feature-ids.txt"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(featureIdsFile)!); await FileSystem.File.WriteAllTextAsync(featureIdsFile, "feature:from-file\nfeature:another", TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -421,7 +422,7 @@ public async Task RenderChangelogs_WithHideFeatures_FromFile_CommentsOutMatching public async Task RenderChangelogs_WithHideFeatures_CaseInsensitive_MatchesFeatureIds() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -440,7 +441,7 @@ public async Task RenderChangelogs_WithHideFeatures_CaseInsensitive_MatchesFeatu var changelogFile = FileSystem.Path.Join(changelogDir, "1755268130-hidden.yaml"); await FileSystem.File.WriteAllTextAsync(changelogFile, changelog, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -457,7 +458,7 @@ public async Task RenderChangelogs_WithHideFeatures_CaseInsensitive_MatchesFeatu """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -484,7 +485,7 @@ public async Task RenderChangelogs_WithHideFeatures_CaseInsensitive_MatchesFeatu public async Task RenderChangelogs_WithBundleHideFeatures_CommentsOutMatchingEntries() { // Arrange - Test that hide-features from bundle metadata are used to hide entries - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -517,7 +518,7 @@ public async Task RenderChangelogs_WithBundleHideFeatures_CommentsOutMatchingEnt await FileSystem.File.WriteAllTextAsync(changelogFile1, changelog1, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(changelogFile2, changelog2, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -540,7 +541,7 @@ public async Task RenderChangelogs_WithBundleHideFeatures_CommentsOutMatchingEnt """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -570,7 +571,7 @@ public async Task RenderChangelogs_WithBundleHideFeatures_CommentsOutMatchingEnt public async Task RenderChangelogs_MergesCLIAndBundleHideFeatures() { // Arrange - Test that CLI and bundle hide-features are merged - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // language=yaml @@ -618,7 +619,7 @@ public async Task RenderChangelogs_MergesCLIAndBundleHideFeatures() await FileSystem.File.WriteAllTextAsync(changelogFile2, changelog2, TestContext.Current.CancellationToken); await FileSystem.File.WriteAllTextAsync(changelogFile3, changelog3, TestContext.Current.CancellationToken); - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -644,7 +645,7 @@ public async Task RenderChangelogs_MergesCLIAndBundleHideFeatures() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/HighlightsRenderTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/HighlightsRenderTests.cs index 4cb3b2197b..2a878fbd1a 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/HighlightsRenderTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/HighlightsRenderTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -14,7 +15,7 @@ public class HighlightsRenderTests(ITestOutputHelper output) : RenderChangelogTe public async Task RenderChangelogs_WithHighlightedEntries_CreatesHighlightsFile() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file with highlight @@ -37,7 +38,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_CreatesHighlightsFile( await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -53,7 +54,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_CreatesHighlightsFile( """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -89,7 +90,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_CreatesHighlightsFile( public async Task RenderChangelogs_WithoutHighlightedEntries_DoesNotCreateHighlightsFile() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file without highlight @@ -109,7 +110,7 @@ public async Task RenderChangelogs_WithoutHighlightedEntries_DoesNotCreateHighli await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -125,7 +126,7 @@ public async Task RenderChangelogs_WithoutHighlightedEntries_DoesNotCreateHighli """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -149,7 +150,7 @@ public async Task RenderChangelogs_WithoutHighlightedEntries_DoesNotCreateHighli public async Task RenderChangelogs_WithHighlightedEntries_IncludesHighlightsInAsciidoc() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file with highlight @@ -171,7 +172,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_IncludesHighlightsInAs await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -187,7 +188,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_IncludesHighlightsInAs """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -218,7 +219,7 @@ public async Task RenderChangelogs_WithHighlightedEntries_IncludesHighlightsInAs public async Task RenderChangelogs_WithMultipleHighlightedEntries_GroupsByArea() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog files with highlights @@ -258,7 +259,7 @@ public async Task RenderChangelogs_WithMultipleHighlightedEntries_GroupsByArea() await FileSystem.File.WriteAllTextAsync(file2, changelog2, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -277,7 +278,7 @@ public async Task RenderChangelogs_WithMultipleHighlightedEntries_GroupsByArea() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/OutputFormatTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/OutputFormatTests.cs index edb68c912c..2fe22fb331 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/OutputFormatTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/OutputFormatTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -15,7 +16,7 @@ public class OutputFormatTests(ITestOutputHelper output) : RenderChangelogTestBa public async Task RenderChangelogs_WithCustomConfigPath_UsesSpecifiedConfigFile() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create changelog @@ -36,7 +37,7 @@ public async Task RenderChangelogs_WithCustomConfigPath_UsesSpecifiedConfigFile( await FileSystem.File.WriteAllTextAsync(changelogFile1, changelog1, TestContext.Current.CancellationToken); // Create config file in a custom location (not in docs/ subdirectory) - var customConfigDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var customConfigDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(customConfigDir); var customConfigPath = FileSystem.Path.Join(customConfigDir, "custom-changelog.yml"); // language=yaml @@ -53,7 +54,7 @@ public async Task RenderChangelogs_WithCustomConfigPath_UsesSpecifiedConfigFile( await FileSystem.File.WriteAllTextAsync(customConfigPath, configContent, TestContext.Current.CancellationToken); // Create bundle file - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -71,7 +72,7 @@ public async Task RenderChangelogs_WithCustomConfigPath_UsesSpecifiedConfigFile( await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); // Don't change directory - use custom config path via Config property - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -100,7 +101,7 @@ public async Task RenderChangelogs_WithCustomConfigPath_UsesSpecifiedConfigFile( public async Task RenderChangelogs_WithAsciidocFileType_CreatesSingleAsciidocFile() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file @@ -121,7 +122,7 @@ public async Task RenderChangelogs_WithAsciidocFileType_CreatesSingleAsciidocFil await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -137,7 +138,7 @@ public async Task RenderChangelogs_WithAsciidocFileType_CreatesSingleAsciidocFil """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -178,7 +179,7 @@ public async Task RenderChangelogs_WithAsciidocFileType_CreatesSingleAsciidocFil public async Task RenderChangelogs_WithAsciidocFileType_ValidatesAsciidocFormat() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog files with different types @@ -232,7 +233,7 @@ public async Task RenderChangelogs_WithAsciidocFileType_ValidatesAsciidocFormat( await FileSystem.File.WriteAllTextAsync(breakingFile, breakingChangeChangelog, TestContext.Current.CancellationToken); // Create bundle file - var bundleFile = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml"); + var bundleFile = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString(), "bundle.yaml"); FileSystem.Directory.CreateDirectory(FileSystem.Path.GetDirectoryName(bundleFile)!); // language=yaml @@ -254,7 +255,7 @@ public async Task RenderChangelogs_WithAsciidocFileType_ValidatesAsciidocFormat( """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Changelogs/Render/TitleTargetTests.cs b/tests/Elastic.Changelog.Tests/Changelogs/Render/TitleTargetTests.cs index 4c90885704..b6bf4c5767 100644 --- a/tests/Elastic.Changelog.Tests/Changelogs/Render/TitleTargetTests.cs +++ b/tests/Elastic.Changelog.Tests/Changelogs/Render/TitleTargetTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Changelog.Bundling; using Elastic.Changelog.Rendering; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; namespace Elastic.Changelog.Tests.Changelogs.Render; @@ -15,7 +16,7 @@ public class TitleTargetTests(ITestOutputHelper output) : RenderChangelogTestBas public async Task RenderChangelogs_WithoutTitleAndNoTargets_EmitsWarning() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file without target @@ -34,7 +35,7 @@ public async Task RenderChangelogs_WithoutTitleAndNoTargets_EmitsWarning() await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file without target - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -50,7 +51,7 @@ public async Task RenderChangelogs_WithoutTitleAndNoTargets_EmitsWarning() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { @@ -76,7 +77,7 @@ public async Task RenderChangelogs_WithoutTitleAndNoTargets_EmitsWarning() public async Task RenderChangelogs_WithTitleAndNoTargets_NoWarning() { // Arrange - var changelogDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var changelogDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(changelogDir); // Create test changelog file without target @@ -95,7 +96,7 @@ public async Task RenderChangelogs_WithTitleAndNoTargets_NoWarning() await FileSystem.File.WriteAllTextAsync(changelogFile, changelog1, TestContext.Current.CancellationToken); // Create bundle file without target - var bundleDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var bundleDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); FileSystem.Directory.CreateDirectory(bundleDir); var bundleFile = FileSystem.Path.Join(bundleDir, "bundle.yaml"); @@ -111,7 +112,7 @@ public async Task RenderChangelogs_WithTitleAndNoTargets_NoWarning() """; await FileSystem.File.WriteAllTextAsync(bundleFile, bundleContent, TestContext.Current.CancellationToken); - var outputDir = FileSystem.Path.Join(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString()); + var outputDir = FileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, Guid.NewGuid().ToString()); var input = new RenderChangelogsArguments { diff --git a/tests/Elastic.Changelog.Tests/Creation/ChangelogCreationServiceTests.cs b/tests/Elastic.Changelog.Tests/Creation/ChangelogCreationServiceTests.cs index 45d1b68437..eecde62fbe 100644 --- a/tests/Elastic.Changelog.Tests/Creation/ChangelogCreationServiceTests.cs +++ b/tests/Elastic.Changelog.Tests/Creation/ChangelogCreationServiceTests.cs @@ -33,8 +33,9 @@ public class ChangelogCreationServiceTests(ITestOutputHelper output) : Changelog cloud-serverless: "@Product:ESS" """; - private async Task WriteConfig(string content, string path = "/tmp/config/changelog.yml") + private async Task WriteConfig(string content, string? path = null) { + path ??= Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"); var dir = FileSystem.Path.GetDirectoryName(path)!; FileSystem.Directory.CreateDirectory(dir); await FileSystem.File.WriteAllTextAsync(path, content); @@ -67,7 +68,7 @@ private static IEnvironmentVariables FakeCIEnv( public async Task CreateChangelog_CIWithProducts_SkipsPrFetchAndSucceeds() { await WriteConfig(ConfigWithProductLabels); - FileSystem.Directory.CreateDirectory("/tmp/output"); + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "output")); var env = FakeCIEnv( prNumber: "153344", @@ -82,8 +83,8 @@ public async Task CreateChangelog_CIWithProducts_SkipsPrFetchAndSucceeds() var input = new CreateChangelogArguments { Products = [], - Config = "/tmp/config/changelog.yml", - Output = "/tmp/output", + Config = Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), + Output = Path.Join(Paths.WorkingDirectoryRoot.FullName, "output"), Concise = true }; @@ -104,7 +105,7 @@ public async Task CreateChangelog_CIWithProducts_SkipsPrFetchAndSucceeds() public async Task CreateChangelog_CIWithoutProducts_FallsBackToPrFetchForProducts() { await WriteConfig(ConfigWithProductLabels); - FileSystem.Directory.CreateDirectory("/tmp/output"); + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "output")); A.CallTo(() => _mockGitHub.FetchPrInfoAsync("153344", "elastic", "cloud", A._)) .Returns(new GitHubPrInfo @@ -125,8 +126,8 @@ public async Task CreateChangelog_CIWithoutProducts_FallsBackToPrFetchForProduct var input = new CreateChangelogArguments { Products = [], - Config = "/tmp/config/changelog.yml", - Output = "/tmp/output", + Config = Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), + Output = Path.Join(Paths.WorkingDirectoryRoot.FullName, "output"), Concise = true }; @@ -147,7 +148,7 @@ public async Task CreateChangelog_CIWithoutProducts_FallsBackToPrFetchForProduct public async Task CreateChangelog_CIWithoutProducts_NoPrProductLabels_FailsWithProductRequired() { await WriteConfig(ConfigWithProductLabels); - FileSystem.Directory.CreateDirectory("/tmp/output"); + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "output")); A.CallTo(() => _mockGitHub.FetchPrInfoAsync("153344", "elastic", "cloud", A._)) .Returns(new GitHubPrInfo @@ -168,8 +169,8 @@ public async Task CreateChangelog_CIWithoutProducts_NoPrProductLabels_FailsWithP var input = new CreateChangelogArguments { Products = [], - Config = "/tmp/config/changelog.yml", - Output = "/tmp/output", + Config = Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), + Output = Path.Join(Paths.WorkingDirectoryRoot.FullName, "output"), Concise = true }; diff --git a/tests/Elastic.Changelog.Tests/Elastic.Changelog.Tests.csproj b/tests/Elastic.Changelog.Tests/Elastic.Changelog.Tests.csproj index 3f27faeed0..20cfca0e69 100644 --- a/tests/Elastic.Changelog.Tests/Elastic.Changelog.Tests.csproj +++ b/tests/Elastic.Changelog.Tests/Elastic.Changelog.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrEvaluationServiceTests.cs b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrEvaluationServiceTests.cs index c32283c9f0..38f626af09 100644 --- a/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrEvaluationServiceTests.cs +++ b/tests/Elastic.Changelog.Tests/Evaluation/ChangelogPrEvaluationServiceTests.cs @@ -7,6 +7,7 @@ using Elastic.Changelog.Evaluation; using Elastic.Changelog.GitHub; using Elastic.Changelog.Tests.Changelogs; +using Elastic.Documentation.Configuration; using FakeItEasy; namespace Elastic.Changelog.Tests.Evaluation; @@ -75,7 +76,7 @@ private EvaluatePrArguments DefaultArgs( string? config = null ) { - config ??= "/tmp/config/changelog.yml"; + config ??= Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"); return new() { Config = config, @@ -93,8 +94,9 @@ private EvaluatePrArguments DefaultArgs( }; } - private async Task WriteMinimalConfig(string configPath = "/tmp/config/changelog.yml", string? content = null) + private async Task WriteMinimalConfig(string? configPath = null, string? content = null) { + configPath ??= Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"); var dir = FileSystem.Path.GetDirectoryName(configPath)!; FileSystem.Directory.CreateDirectory(dir); await FileSystem.File.WriteAllTextAsync(configPath, content ?? MinimalConfig); @@ -162,8 +164,8 @@ public async Task EvaluatePr_BotCommit_ReturnsSkipped() [Fact] public async Task EvaluatePr_ManuallyEdited_PrFilename_ReturnsManuallyEdited() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - await FileSystem.File.WriteAllTextAsync("docs/changelog/42.yaml", "title: test", TestContext.Current.CancellationToken); + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog")); + await FileSystem.File.WriteAllTextAsync(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog/42.yaml"), "title: test", TestContext.Current.CancellationToken); A.CallTo(() => _mockGitHub.FetchLastFileCommitAuthorAsync( "elastic", "test-repo", "docs/changelog/42.yaml", "feature/test", A._)) @@ -181,8 +183,8 @@ public async Task EvaluatePr_ManuallyEdited_PrFilename_ReturnsManuallyEdited() [Fact] public async Task EvaluatePr_ManuallyEdited_TimestampFilename_ReturnsManuallyEdited() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - await FileSystem.File.WriteAllTextAsync("docs/changelog/1735689600-fix-something.yaml", + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog")); + await FileSystem.File.WriteAllTextAsync(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog/1735689600-fix-something.yaml"), "title: Fix something\nprs:\n - \"42\"", TestContext.Current.CancellationToken); A.CallTo(() => _mockGitHub.FetchLastFileCommitAuthorAsync( @@ -245,9 +247,9 @@ public async Task EvaluatePr_NoTypeLabel_ReturnsNoLabel() [Fact] public async Task EvaluatePr_NoTypeLabel_WithProductConfig_OutputsProductLabelTable() { - await WriteMinimalConfig("/tmp/config/changelog.yml", ConfigWithProducts); + await WriteMinimalConfig(Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), ConfigWithProducts); var service = CreateService(); - var args = DefaultArgs(prLabels: ["unrelated-label"], config: "/tmp/config/changelog.yml"); + var args = DefaultArgs(prLabels: ["unrelated-label"], config: Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml")); var result = await service.EvaluatePr(Collector, args, CancellationToken.None); @@ -260,9 +262,9 @@ public async Task EvaluatePr_NoTypeLabel_WithProductConfig_OutputsProductLabelTa [Fact] public async Task EvaluatePr_NoTypeLabel_WithProductLabels_DoesNotOutputProductLabelTable() { - await WriteMinimalConfig("/tmp/config/changelog.yml", ConfigWithProducts); + await WriteMinimalConfig(Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), ConfigWithProducts); var service = CreateService(); - var args = DefaultArgs(prLabels: ["@Product:ECH"], config: "/tmp/config/changelog.yml"); + var args = DefaultArgs(prLabels: ["@Product:ECH"], config: Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml")); var result = await service.EvaluatePr(Collector, args, CancellationToken.None); @@ -376,8 +378,8 @@ public void BuildMappingTable_UsesCustomHeaders() public async Task EvaluatePr_ExistingTimestampFile_OutputsFilename() { await WriteMinimalConfig(); - FileSystem.Directory.CreateDirectory("docs/changelog"); - await FileSystem.File.WriteAllTextAsync("docs/changelog/1735689600-fix-something.yaml", + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog")); + await FileSystem.File.WriteAllTextAsync(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog/1735689600-fix-something.yaml"), "title: Fix something\nprs:\n - \"42\"", TestContext.Current.CancellationToken); var service = CreateService(); @@ -394,8 +396,8 @@ await FileSystem.File.WriteAllTextAsync("docs/changelog/1735689600-fix-something public async Task EvaluatePr_ExistingPrFile_OutputsFilename() { await WriteMinimalConfig(); - FileSystem.Directory.CreateDirectory("docs/changelog"); - await FileSystem.File.WriteAllTextAsync("docs/changelog/42.yaml", "title: Fix something", TestContext.Current.CancellationToken); + FileSystem.Directory.CreateDirectory(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog")); + await FileSystem.File.WriteAllTextAsync(Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog/42.yaml"), "title: Fix something", TestContext.Current.CancellationToken); var service = CreateService(); var args = DefaultArgs(); @@ -410,11 +412,12 @@ public async Task EvaluatePr_ExistingPrFile_OutputsFilename() [Fact] public void FindExistingChangelog_PrFilename_FindsByName() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - FileSystem.File.WriteAllText("docs/changelog/42.yaml", "title: test"); + var dir = Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog"); + FileSystem.Directory.CreateDirectory(dir); + FileSystem.File.WriteAllText(Path.Join(dir, "42.yaml"), "title: test"); var service = CreateService(); - var result = service.FindExistingChangelog("docs/changelog", 42); + var result = service.FindExistingChangelog(dir, 42); result.Should().Be("42.yaml"); } @@ -422,12 +425,13 @@ public void FindExistingChangelog_PrFilename_FindsByName() [Fact] public void FindExistingChangelog_TimestampFilename_FindsByContent() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - FileSystem.File.WriteAllText("docs/changelog/1735689600-fix.yaml", + var dir = Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog"); + FileSystem.Directory.CreateDirectory(dir); + FileSystem.File.WriteAllText(Path.Join(dir, "1735689600-fix.yaml"), "title: Fix\nprs:\n - \"42\""); var service = CreateService(); - var result = service.FindExistingChangelog("docs/changelog", 42); + var result = service.FindExistingChangelog(dir, 42); result.Should().Be("1735689600-fix.yaml"); } @@ -435,12 +439,13 @@ public void FindExistingChangelog_TimestampFilename_FindsByContent() [Fact] public void FindExistingChangelog_GitHubUrl_FindsByContent() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - FileSystem.File.WriteAllText("docs/changelog/1735689600-fix.yaml", + var dir = Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog"); + FileSystem.Directory.CreateDirectory(dir); + FileSystem.File.WriteAllText(Path.Join(dir, "1735689600-fix.yaml"), "title: Fix\nprs:\n - \"https://github.com/elastic/test-repo/pull/42\""); var service = CreateService(); - var result = service.FindExistingChangelog("docs/changelog", 42); + var result = service.FindExistingChangelog(dir, 42); result.Should().Be("1735689600-fix.yaml"); } @@ -448,11 +453,12 @@ public void FindExistingChangelog_GitHubUrl_FindsByContent() [Fact] public void FindExistingChangelog_NoMatch_ReturnsNull() { - FileSystem.Directory.CreateDirectory("docs/changelog"); - FileSystem.File.WriteAllText("docs/changelog/99.yaml", "title: other PR"); + var dir = Path.Join(Paths.WorkingDirectoryRoot.FullName, "docs/changelog"); + FileSystem.Directory.CreateDirectory(dir); + FileSystem.File.WriteAllText(Path.Join(dir, "99.yaml"), "title: other PR"); var service = CreateService(); - var result = service.FindExistingChangelog("docs/changelog", 42); + var result = service.FindExistingChangelog(dir, 42); result.Should().BeNull(); } @@ -461,7 +467,7 @@ public void FindExistingChangelog_NoMatch_ReturnsNull() public void FindExistingChangelog_DirectoryMissing_ReturnsNull() { var service = CreateService(); - var result = service.FindExistingChangelog("nonexistent/path", 42); + var result = service.FindExistingChangelog(Path.Join(Paths.WorkingDirectoryRoot.FullName, "nonexistent/path"), 42); result.Should().BeNull(); } @@ -479,11 +485,11 @@ public void ContentReferencesPr_MatchesPrNumberCorrectly(string content, bool ex [Fact] public async Task EvaluatePr_WithProductLabels_OutputsProductsAndNoTable() { - await WriteMinimalConfig("/tmp/config/changelog.yml", ConfigWithProducts); + await WriteMinimalConfig(Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), ConfigWithProducts); var service = CreateService(); var args = DefaultArgs( prLabels: [">enhancement", "@Product:ECH", "@Product:ESS"], - config: "/tmp/config/changelog.yml" + config: Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml") ); var result = await service.EvaluatePr(Collector, args, CancellationToken.None); @@ -498,11 +504,11 @@ public async Task EvaluatePr_WithProductLabels_OutputsProductsAndNoTable() [Fact] public async Task EvaluatePr_WithoutProductLabels_OutputsProductLabelTable() { - await WriteMinimalConfig("/tmp/config/changelog.yml", ConfigWithProducts); + await WriteMinimalConfig(Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml"), ConfigWithProducts); var service = CreateService(); var args = DefaultArgs( prLabels: ["type:feature"], - config: "/tmp/config/changelog.yml" + config: Path.Join(Paths.WorkingDirectoryRoot.FullName, "config", "changelog.yml") ); var result = await service.EvaluatePr(Collector, args, CancellationToken.None); diff --git a/tests/Elastic.Documentation.Configuration.Tests/CrossLinkRegistryTests.cs b/tests/Elastic.Documentation.Configuration.Tests/CrossLinkRegistryTests.cs index 8f4331dfdc..d1cf66a8f6 100644 --- a/tests/Elastic.Documentation.Configuration.Tests/CrossLinkRegistryTests.cs +++ b/tests/Elastic.Documentation.Configuration.Tests/CrossLinkRegistryTests.cs @@ -6,12 +6,12 @@ using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; -using Elastic.Documentation; using Elastic.Documentation.Configuration.Builder; using Elastic.Documentation.Configuration.Products; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Configuration.Versions; using Elastic.Documentation.Diagnostics; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Configuration.Tests; @@ -132,8 +132,8 @@ private sealed class MockDocumentationSetContext( : IDocumentationSetContext { public IDiagnosticsCollector Collector => collector; - public IFileSystem ReadFileSystem => fileSystem; - public IFileSystem WriteFileSystem => fileSystem; + public ScopedFileSystem ReadFileSystem => WriteFileSystem; + public ScopedFileSystem WriteFileSystem { get; } = FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(fileSystem); public IDirectoryInfo OutputDirectory => fileSystem.DirectoryInfo.New(Path.Join(Paths.WorkingDirectoryRoot.FullName, ".artifacts")); public IFileInfo ConfigurationPath => configurationPath; public BuildType BuildType => BuildType.Isolated; diff --git a/tests/Elastic.Documentation.Configuration.Tests/DocumentationSetFileTests.cs b/tests/Elastic.Documentation.Configuration.Tests/DocumentationSetFileTests.cs index e5d8896596..4d537ef51b 100644 --- a/tests/Elastic.Documentation.Configuration.Tests/DocumentationSetFileTests.cs +++ b/tests/Elastic.Documentation.Configuration.Tests/DocumentationSetFileTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Extensions; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Configuration.Tests; @@ -633,7 +634,7 @@ public void LoadAndResolveResolvesIsolatedTocReferences() var docsetPath = fileSystem.FileInfo.New("/docs/docset.yml"); var collector = new DiagnosticsCollector([]); - var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, fileSystem); + var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, new ScopedFileSystem(fileSystem, "/docs")); // Verify TOC references have been preserved (not flattened) // We have 3 top-level items: index.md, development TOC, and guides folder @@ -704,7 +705,7 @@ public void LoadAndResolvePrependsParentPathsToFileReferences() var docsetPath = fileSystem.FileInfo.New("/docs/docset.yml"); var collector = new DiagnosticsCollector([]); - var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, fileSystem); + var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, new ScopedFileSystem(fileSystem, "/docs")); result.TableOfContents.Should().HaveCount(2); @@ -760,7 +761,7 @@ public void LoadAndResolveSetsContextForAllItems() var docsetPath = fileSystem.FileInfo.New("/docs/docset.yml"); var collector = new DiagnosticsCollector([]); - var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, fileSystem); + var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, new ScopedFileSystem(fileSystem, "/docs")); var docset = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:/docs/docset.yml" : "/docs/docset.yml"; var toc = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:/docs/development/toc.yml" : "/docs/development/toc.yml"; @@ -824,7 +825,7 @@ public void LoadAndResolveSetsPathRelativeToContainerCorrectly() var docsetPath = fileSystem.FileInfo.New("/docs/docset.yml"); var collector = new DiagnosticsCollector([]); - var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, fileSystem); + var result = DocumentationSetFile.LoadAndResolve(collector, docsetPath, new ScopedFileSystem(fileSystem, "/docs")); // Items in docset.yml: PathRelativeToContainer should equal PathRelativeToDocumentationSet result.TableOfContents.ElementAt(0).Should().BeOfType() diff --git a/tests/Elastic.Documentation.Configuration.Tests/GitCommonRootTests.cs b/tests/Elastic.Documentation.Configuration.Tests/GitCommonRootTests.cs deleted file mode 100644 index ad5d3b0dce..0000000000 --- a/tests/Elastic.Documentation.Configuration.Tests/GitCommonRootTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.IO.Abstractions.TestingHelpers; -using AwesomeAssertions; - -namespace Elastic.Documentation.Configuration.Tests; - -public class GitCommonRootTests -{ - [Fact] - public void NormalRepo_ReturnsWorkingDirectoryRoot() - { - var fs = new MockFileSystem(); - fs.AddDirectory("/repo/.git"); - var root = fs.DirectoryInfo.New("/repo"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(root.FullName); - } - - [Fact] - public void Worktree_AbsoluteGitDir_ReturnsMainRepoRoot() - { - var fs = new MockFileSystem(); - fs.AddFile("/worktree/.git", new MockFileData("gitdir: /main-repo/.git/worktrees/feature-branch")); - fs.AddDirectory("/main-repo/.git/worktrees/feature-branch"); - var root = fs.DirectoryInfo.New("/worktree"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(fs.DirectoryInfo.New("/main-repo").FullName); - } - - [Fact] - public void Worktree_RelativeGitDir_ReturnsMainRepoRoot() - { - var fs = new MockFileSystem(); - fs.AddFile("/repos/worktree/.git", new MockFileData("gitdir: ../main-repo/.git/worktrees/feature-branch")); - fs.AddDirectory("/repos/main-repo/.git/worktrees/feature-branch"); - var root = fs.DirectoryInfo.New("/repos/worktree"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(fs.DirectoryInfo.New("/repos/main-repo").FullName); - } - - [Fact] - public void NoGitPresent_ReturnsWorkingDirectoryRoot() - { - var fs = new MockFileSystem(); - fs.AddDirectory("/repo"); - var root = fs.DirectoryInfo.New("/repo"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(root.FullName); - } - - [Fact] - public void MalformedGitFile_ReturnsWorkingDirectoryRoot() - { - var fs = new MockFileSystem(); - fs.AddFile("/repo/.git", new MockFileData("not a valid gitdir reference")); - var root = fs.DirectoryInfo.New("/repo"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(root.FullName); - } - - [Fact] - public void Worktree_GitDirPathHasNoGitAncestor_ReturnsWorkingDirectoryRoot() - { - var fs = new MockFileSystem(); - fs.AddFile("/worktree/.git", new MockFileData("gitdir: /some/path/without/git/ancestor")); - var root = fs.DirectoryInfo.New("/worktree"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: false); - - result.FullName.Should().Be(root.FullName); - } - - [Fact] - public void OnCI_Worktree_ReturnsWorkingDirectoryRoot() - { - var fs = new MockFileSystem(); - fs.AddFile("/worktree/.git", new MockFileData("gitdir: /main-repo/.git/worktrees/feature-branch")); - fs.AddDirectory("/main-repo/.git/worktrees/feature-branch"); - var root = fs.DirectoryInfo.New("/worktree"); - - var result = Paths.ResolveGitCommonRoot(fs, root, isCI: true); - - result.FullName.Should().Be(root.FullName); - } -} diff --git a/tests/Elastic.Markdown.Tests/Assembler/AssemblerHtmxMarkdownLinkTests.cs b/tests/Elastic.Markdown.Tests/Assembler/AssemblerHtmxMarkdownLinkTests.cs index e48a189b79..9698c3f306 100644 --- a/tests/Elastic.Markdown.Tests/Assembler/AssemblerHtmxMarkdownLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Assembler/AssemblerHtmxMarkdownLinkTests.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Markdown.IO; using Elastic.Markdown.Tests.Inline; +using Nullean.ScopedFileSystem; using Xunit; namespace Elastic.Markdown.Tests.Assembler; @@ -20,7 +21,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler @@ -56,7 +57,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler @@ -87,7 +88,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs", BuildType = BuildType.Assembler @@ -118,7 +119,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler @@ -149,7 +150,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler @@ -188,7 +189,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler @@ -218,7 +219,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs/platform/elasticsearch", BuildType = BuildType.Assembler diff --git a/tests/Elastic.Markdown.Tests/Codex/CodexHtmxCrossLinkTests.cs b/tests/Elastic.Markdown.Tests/Codex/CodexHtmxCrossLinkTests.cs index 427484cc39..36554b11a5 100644 --- a/tests/Elastic.Markdown.Tests/Codex/CodexHtmxCrossLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Codex/CodexHtmxCrossLinkTests.cs @@ -9,6 +9,7 @@ using Elastic.Documentation.Links.CrossLinks; using Elastic.Markdown.IO; using Elastic.Markdown.Tests.Inline; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests.Codex; @@ -19,7 +20,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/r/codex-environments", BuildType = BuildType.Codex @@ -62,7 +63,7 @@ protected override BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs", BuildType = BuildType.Isolated diff --git a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs index f9ed48745c..0307f5ff3d 100644 --- a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Configuration; using Elastic.Markdown.Myst.Directives.CsvInclude; namespace Elastic.Markdown.Tests.Directives; @@ -37,7 +38,7 @@ public CsvIncludeTests(ITestOutputHelper output) : base(output, [Fact] public void ParsesCsvDataCorrectly() { - var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystem).ToList(); + var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem)).ToList(); csvData.Should().HaveCount(4); csvData[0].Should().BeEquivalentTo(["Name", "Age", "City"]); csvData[1].Should().BeEquivalentTo(["John Doe", "30", "New York"]); @@ -71,7 +72,7 @@ public CsvIncludeWithOptionsTests(ITestOutputHelper output) : base(output, [Fact] public void ParsesWithCustomSeparator() { - var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystem).ToList(); + var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem)).ToList(); csvData.Should().HaveCount(3); csvData[0].Should().BeEquivalentTo(["Name", "Age", "City"]); csvData[1].Should().BeEquivalentTo(["John Doe", "30", "New York"]); @@ -93,7 +94,7 @@ public CsvIncludeWithQuotesTests(ITestOutputHelper output) : base(output, [Fact] public void HandlesQuotedFieldsWithCommas() { - var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystem).ToList(); + var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem)).ToList(); csvData.Should().HaveCount(3); csvData[0].Should().BeEquivalentTo(["Name", "Description", "Location"]); csvData[1].Should().BeEquivalentTo(["John Doe", "Software Engineer, Senior", "New York"]); @@ -115,7 +116,7 @@ public CsvIncludeWithEscapedQuotesTests(ITestOutputHelper output) : base(output, [Fact] public void HandlesEscapedQuotes() { - var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystem).ToList(); + var csvData = CsvReader.ReadCsvFile(Block!.CsvFilePath!, Block.Separator, FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem)).ToList(); csvData.Should().HaveCount(3); csvData[0].Should().BeEquivalentTo(["Name", "Description"]); csvData[1].Should().BeEquivalentTo(["John Doe", "He said \"Hello World\" today"]); diff --git a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs index a538b23d87..62389d448b 100644 --- a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs @@ -8,6 +8,7 @@ using Elastic.Markdown.Myst.Directives; using JetBrains.Annotations; using Markdig.Syntax; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests.Directives; @@ -70,7 +71,7 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown") Collector = new TestDiagnosticsCollector(output); var configurationContext = TestHelpers.CreateConfigurationContext(FileSystem); - var context = new BuildContext(Collector, FileSystem, configurationContext); + var context = new BuildContext(Collector, FileSystemFactory.ScopeCurrentWorkingDirectory(FileSystem), configurationContext); var linkResolver = new TestCrossLinkResolver(); Set = new DocumentationSet(context, logger, linkResolver); File = Set.TryFindDocument(FileSystem.FileInfo.New("docs/index.md")) as MarkdownFile ?? throw new NullReferenceException(); diff --git a/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs index f89b75668b..42ea8a5ff3 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTestsBase.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Configuration.Builder; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests.DocSet; @@ -18,11 +19,12 @@ public class NavigationTestsBase : IAsyncLifetime protected NavigationTestsBase(ITestOutputHelper output) { LoggerFactory = new TestLoggerFactory(output); - ReadFileSystem = new FileSystem(); //use real IO to read docs. - WriteFileSystem = new MockFileSystem(new MockFileSystemOptions //use in memory mock fs to test generation + var mockWriteFs = new MockFileSystem(new MockFileSystemOptions //use in memory mock fs to test generation { CurrentDirectory = Paths.WorkingDirectoryRoot.FullName }); + ReadFileSystem = FileSystemFactory.RealRead; + WriteFileSystem = FileSystemFactory.ScopeCurrentWorkingDirectory(mockWriteFs); var collector = new TestDiagnosticsCollector(output); var configurationContext = TestHelpers.CreateConfigurationContext(ReadFileSystem); var context = new BuildContext(collector, ReadFileSystem, WriteFileSystem, configurationContext, ExportOptions.Default) @@ -40,8 +42,8 @@ protected NavigationTestsBase(ITestOutputHelper output) protected ILoggerFactory LoggerFactory { get; } - protected FileSystem ReadFileSystem { get; set; } - protected IFileSystem WriteFileSystem { get; set; } + protected ScopedFileSystem ReadFileSystem { get; set; } + protected ScopedFileSystem WriteFileSystem { get; set; } protected DocumentationSet Set { get; } protected DocumentationGenerator Generator { get; } protected ConfigurationFile? Configuration { get; set; } diff --git a/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj b/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj index 3c3a200e32..a2a90aa4d3 100644 --- a/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj +++ b/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/tests/Elastic.Markdown.Tests/Inline/ImagePathResolutionTests.cs b/tests/Elastic.Markdown.Tests/Inline/ImagePathResolutionTests.cs index 385e737789..07b4a2cd26 100644 --- a/tests/Elastic.Markdown.Tests/Inline/ImagePathResolutionTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/ImagePathResolutionTests.cs @@ -15,6 +15,7 @@ using Elastic.Markdown.Myst; using Elastic.Markdown.Myst.InlineParsers; using Elastic.Markdown.Tests; +using Nullean.ScopedFileSystem; using Xunit; namespace Elastic.Markdown.Tests.Inline; @@ -92,7 +93,7 @@ private async Task ResolveUrlForBuildMode(string relativeAssetPath, Bu _ = collector.StartAsync(TestContext.Current.CancellationToken); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); - var buildContext = new BuildContext(collector, fileSystem, configurationContext) + var buildContext = new BuildContext(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs", BuildType = buildType diff --git a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs index 9053344ac2..a42ee0ea4e 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using Markdig.Syntax; using Markdig.Syntax.Inlines; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests.Inline; @@ -132,7 +133,7 @@ protected virtual BuildContext CreateBuildContext( TestDiagnosticsCollector collector, MockFileSystem fileSystem, IConfigurationContext configurationContext) => - new(collector, fileSystem, configurationContext) + new(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext) { UrlPathPrefix = "/docs" }; diff --git a/tests/Elastic.Markdown.Tests/OutputDirectoryTests.cs b/tests/Elastic.Markdown.Tests/OutputDirectoryTests.cs index 013366b3e4..82a69f20a5 100644 --- a/tests/Elastic.Markdown.Tests/OutputDirectoryTests.cs +++ b/tests/Elastic.Markdown.Tests/OutputDirectoryTests.cs @@ -7,6 +7,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Elastic.Markdown.IO; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests; @@ -32,7 +33,7 @@ public async Task CreatesDefaultOutputDirectory() }); await using var collector = new DiagnosticsCollector([]).StartAsync(TestContext.Current.CancellationToken); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); - var context = new BuildContext(collector, fileSystem, configurationContext); + var context = new BuildContext(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext); var linkResolver = new TestCrossLinkResolver(); var set = new DocumentationSet(context, logger, linkResolver); var generator = new DocumentationGenerator(set, logger); diff --git a/tests/Elastic.Markdown.Tests/RootIndexValidationTests.cs b/tests/Elastic.Markdown.Tests/RootIndexValidationTests.cs index e3a29bad57..4c29e609e7 100644 --- a/tests/Elastic.Markdown.Tests/RootIndexValidationTests.cs +++ b/tests/Elastic.Markdown.Tests/RootIndexValidationTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Elastic.Markdown.IO; +using Nullean.ScopedFileSystem; namespace Elastic.Markdown.Tests; @@ -33,7 +34,7 @@ public void InternalRegistry_MissingIndexMd_EmitsError() var collector = new TestDiagnosticsCollector(output); _ = collector.StartAsync(TestContext.Current.CancellationToken); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); - var context = new BuildContext(collector, fileSystem, configurationContext); + var context = new BuildContext(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext); _ = new DocumentationSet(context, logger, new TestCrossLinkResolver()); collector.Errors.Should().BeGreaterThan(0); @@ -64,7 +65,7 @@ public void InternalRegistry_WithIndexMd_NoError() var collector = new TestDiagnosticsCollector(output); _ = collector.StartAsync(TestContext.Current.CancellationToken); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); - var context = new BuildContext(collector, fileSystem, configurationContext); + var context = new BuildContext(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext); _ = new DocumentationSet(context, logger, new TestCrossLinkResolver()); collector.Diagnostics @@ -92,7 +93,7 @@ public void PublicRegistry_MissingIndexMd_NoError() var collector = new TestDiagnosticsCollector(output); _ = collector.StartAsync(TestContext.Current.CancellationToken); var configurationContext = TestHelpers.CreateConfigurationContext(fileSystem); - var context = new BuildContext(collector, fileSystem, configurationContext); + var context = new BuildContext(collector, FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext); _ = new DocumentationSet(context, logger, new TestCrossLinkResolver()); collector.Diagnostics diff --git a/tests/Navigation.Tests/Assembler/ComplexSiteNavigationTests.cs b/tests/Navigation.Tests/Assembler/ComplexSiteNavigationTests.cs index 33807f5bf5..b1b1b1eb97 100644 --- a/tests/Navigation.Tests/Assembler/ComplexSiteNavigationTests.cs +++ b/tests/Navigation.Tests/Assembler/ComplexSiteNavigationTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Navigation.Assembler; using Elastic.Documentation.Navigation.Isolated; @@ -51,7 +52,7 @@ public void ComplexNavigationWithMultipleNestedTocsAppliesPathPrefixToRootUrls() ? $"{repo.FullName}/docs/docset.yml" : $"{repo.FullName}/docs/_docset.yml"; - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); documentationSets.Add(navigation); @@ -129,7 +130,7 @@ public void DeeplyNestedNavigationMaintainsPathPrefixThroughoutHierarchy() var platformContext = SiteNavigationTestFixture.CreateAssemblerContext(fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, - fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { @@ -180,7 +181,7 @@ public void FileNavigationLeafUrlsReflectPathPrefixInDeeplyNestedStructures() var platformContext = SiteNavigationTestFixture.CreateAssemblerContext(fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, - fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { @@ -242,7 +243,7 @@ public void FolderNavigationWithinNestedTocsHasCorrectPathPrefix() var platformContext = SiteNavigationTestFixture.CreateContext( fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, - fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { diff --git a/tests/Navigation.Tests/Assembler/IdentifierCollectionTests.cs b/tests/Navigation.Tests/Assembler/IdentifierCollectionTests.cs index 8e59182b75..9936f9f569 100644 --- a/tests/Navigation.Tests/Assembler/IdentifierCollectionTests.cs +++ b/tests/Navigation.Tests/Assembler/IdentifierCollectionTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Navigation.Isolated; using Elastic.Documentation.Navigation.Isolated.Node; @@ -20,7 +21,7 @@ public void DocumentationSetNavigationCollectsRootIdentifier() var platformContext = SiteNavigationTestFixture.CreateContext( fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve( - platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); // Root identifier should be :// @@ -35,7 +36,7 @@ public void DocumentationSetNavigationCollectsNestedTocIdentifiers() // Test platform repository with nested TOCs var platformContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/platform", output); - var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, platformContext.ConfigurationPath, fileSystem); + var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, platformContext.ConfigurationPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); // Should collect identifiers from nested TOCs @@ -56,7 +57,7 @@ public void DocumentationSetNavigationWithSimpleStructure() // Test observability repository (no nested TOCs) var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); // Should only have root identifier @@ -73,7 +74,7 @@ public void TableOfContentsNavigationHasCorrectIdentifier() var platformContext = SiteNavigationTestFixture.CreateContext( fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve( - platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); // Get the deployment-guide TOC @@ -96,13 +97,13 @@ public void MultipleDocumentationSetsHaveDistinctIdentifiers() var platformContext = SiteNavigationTestFixture.CreateContext( fileSystem, "/checkouts/current/platform", output); var platformDocset = DocumentationSetFile.LoadAndResolve( - platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); var observabilityContext = SiteNavigationTestFixture.CreateContext( fileSystem, "/checkouts/current/observability", output); var observabilityDocset = DocumentationSetFile.LoadAndResolve( - observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); // Each should have its own set of identifiers diff --git a/tests/Navigation.Tests/Assembler/SiteDocumentationSetsTests.cs b/tests/Navigation.Tests/Assembler/SiteDocumentationSetsTests.cs index 256d8087d1..821abe8497 100644 --- a/tests/Navigation.Tests/Assembler/SiteDocumentationSetsTests.cs +++ b/tests/Navigation.Tests/Assembler/SiteDocumentationSetsTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Navigation.Assembler; using Elastic.Documentation.Navigation.Isolated; @@ -46,7 +47,7 @@ public void CreatesDocumentationSetNavigationsFromCheckoutFolders() ? $"{repo.FullName}/docs/docset.yml" : $"{repo.FullName}/docs/_docset.yml"; - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); documentationSets.Add(navigation); @@ -83,15 +84,15 @@ public void SiteNavigationIntegratesWithDocumentationSets() var documentationSets = new List(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); documentationSets.Add(new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance)); var searchContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-search", output); - var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), fileSystem); + var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); documentationSets.Add(new DocumentationSetNavigation(searchDocset, searchContext, GenericDocumentationFileFactory.Instance)); var securityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-security", output); - var securityDocset = DocumentationSetFile.LoadAndResolve(securityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-security/docs/_docset.yml"), fileSystem); + var securityDocset = DocumentationSetFile.LoadAndResolve(securityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-security/docs/_docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); documentationSets.Add(new DocumentationSetNavigation(securityDocset, securityContext, GenericDocumentationFileFactory.Instance)); // Create site navigation context (using any repository's filesystem) @@ -133,7 +134,7 @@ public void SiteNavigationWithNestedTocs() // Create DocumentationSetNavigation for platform var platformContext = SiteNavigationTestFixture.CreateAssemblerContext(fileSystem, "/checkouts/current/platform", output); - var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); platformNav.Url.Should().Be("/"); platformNav.Index.Url.Should().Be("/"); @@ -200,7 +201,7 @@ public void SiteNavigationWithAllRepositories() ? $"{repo.FullName}/docs/docset.yml" : $"{repo.FullName}/docs/_docset.yml"; - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); documentationSets.Add(navigation); @@ -240,7 +241,7 @@ public void DocumentationSetNavigationHasCorrectStructure() // Test observability repository structure var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); observabilityNav.NavigationTitle.Should().Be(observabilityNav.NavigationTitle); @@ -269,7 +270,7 @@ public void DocumentationSetWithNestedTocs() // Test platform repository with nested TOCs var platformContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/platform", output); - var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); platformNav.NavigationTitle.Should().Be("Platform"); @@ -299,7 +300,7 @@ public void DocumentationSetWithUnderscoreDocset() // Test serverless-security repository with _docset.yml var securityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-security", output); - var securityDocset = DocumentationSetFile.LoadAndResolve(securityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-security/docs/_docset.yml"), fileSystem); + var securityDocset = DocumentationSetFile.LoadAndResolve(securityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-security/docs/_docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var securityNav = new DocumentationSetNavigation(securityDocset, securityContext, GenericDocumentationFileFactory.Instance); securityNav.NavigationTitle.Should().Be("Serverless Security"); @@ -330,7 +331,7 @@ public void SiteNavigationAppliesPathPrefixToAllUrls() var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance) }; var siteContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); @@ -367,7 +368,7 @@ public void SiteNavigationWithNestedTocsAppliesCorrectPathPrefixes() var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var platformContext = SiteNavigationTestFixture.CreateAssemblerContext(fileSystem, "/checkouts/current/platform", output); - var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance) }; var siteContext = SiteNavigationTestFixture.CreateAssemblerContext(fileSystem, "/checkouts/current/platform", output); @@ -398,7 +399,7 @@ public void SiteNavigationRequiresPathPrefix() var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var documentationSets = new List { new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance) }; var siteContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); @@ -421,7 +422,7 @@ public void ObservabilityDocumentationSetNavigationHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -441,7 +442,7 @@ public void ServerlessSearchDocumentationSetNavigationHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-search", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -461,7 +462,7 @@ public void ServerlessSecurityDocumentationSetNavigationHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-security", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/serverless-security/docs/_docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -481,7 +482,7 @@ public void PlatformDocumentationSetNavigationHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/platform", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -501,7 +502,7 @@ public void ElasticsearchReferenceDocumentationSetNavigationHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/elasticsearch-reference", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/elasticsearch-reference/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -532,7 +533,7 @@ public void AllDocumentationSetsHaveNoDiagnostics() ? $"{repo.FullName}/docs/docset.yml" : $"{repo.FullName}/docs/_docset.yml"; - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, fileSystem.FileInfo.New(docsetPath), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -553,7 +554,7 @@ public void DocumentationSetNavigationWithNestedTocsHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/platform", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); @@ -579,7 +580,7 @@ public void DocumentationSetNavigationWithFoldersHasNoDiagnostics() var context = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); var docsetPath = fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"); - var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, fileSystem); + var docset = DocumentationSetFile.LoadAndResolve(context.Collector, docsetPath, FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var navigation = new DocumentationSetNavigation(docset, context, GenericDocumentationFileFactory.Instance); diff --git a/tests/Navigation.Tests/Assembler/SiteNavigationTests.cs b/tests/Navigation.Tests/Assembler/SiteNavigationTests.cs index d798a88fde..c79316636d 100644 --- a/tests/Navigation.Tests/Assembler/SiteNavigationTests.cs +++ b/tests/Navigation.Tests/Assembler/SiteNavigationTests.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Navigation.Assembler; using Elastic.Documentation.Navigation.Isolated; @@ -41,11 +42,11 @@ public void ConstructorCreatesSiteNavigation() // Create DocumentationSetNavigation instances for the referenced repos var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); var searchContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-search", output); - var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), fileSystem); + var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var searchNav = new DocumentationSetNavigation(searchDocset, searchContext, GenericDocumentationFileFactory.Instance); var documentationSets = new List { observabilityNav, searchNav }; @@ -79,7 +80,7 @@ public void SiteNavigationWithNestedChildren() // Create DocumentationSetNavigation for platform var platformContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/platform", output); - var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), fileSystem); + var platformDocset = DocumentationSetFile.LoadAndResolve(platformContext.Collector, fileSystem.FileInfo.New("/checkouts/current/platform/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var platformNav = new DocumentationSetNavigation(platformDocset, platformContext, GenericDocumentationFileFactory.Instance); var documentationSets = new List { platformNav }; @@ -117,7 +118,7 @@ public void SitePrefixNormalizesSlashes(string? sitePrefix, string expectedRootU var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); var documentationSets = new List { observabilityNav }; @@ -153,7 +154,7 @@ public void SitePrefixAppliedToNavigationItemUrls(string? sitePrefix, string exp var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); var documentationSets = new List { observabilityNav }; @@ -212,11 +213,11 @@ public void NavigationNodeIdsAreUniqueAcrossDocsets() // Create navigation for both docsets var productAContext = SiteNavigationTestFixture.CreateContext(fileSystem, productADir, output); - var productADocsetFile = DocumentationSetFile.LoadAndResolve(productAContext.Collector, fileSystem.FileInfo.New($"{productADir}/docs/docset.yml"), fileSystem); + var productADocsetFile = DocumentationSetFile.LoadAndResolve(productAContext.Collector, fileSystem.FileInfo.New($"{productADir}/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var productANav = new DocumentationSetNavigation(productADocsetFile, productAContext, GenericDocumentationFileFactory.Instance); var productBContext = SiteNavigationTestFixture.CreateContext(fileSystem, productBDir, output); - var productBDocsetFile = DocumentationSetFile.LoadAndResolve(productBContext.Collector, fileSystem.FileInfo.New($"{productBDir}/docs/docset.yml"), fileSystem); + var productBDocsetFile = DocumentationSetFile.LoadAndResolve(productBContext.Collector, fileSystem.FileInfo.New($"{productBDir}/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var productBNav = new DocumentationSetNavigation(productBDocsetFile, productBContext, GenericDocumentationFileFactory.Instance); // Get the "getting-started" folders from each docset @@ -284,11 +285,11 @@ public void SitePrefixAppliedToMultipleNavigationItems(string? sitePrefix, strin var fileSystem = SiteNavigationTestFixture.CreateMultiRepositoryFileSystem(); var observabilityContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/observability", output); - var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), fileSystem); + var observabilityDocset = DocumentationSetFile.LoadAndResolve(observabilityContext.Collector, fileSystem.FileInfo.New("/checkouts/current/observability/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var observabilityNav = new DocumentationSetNavigation(observabilityDocset, observabilityContext, GenericDocumentationFileFactory.Instance); var searchContext = SiteNavigationTestFixture.CreateContext(fileSystem, "/checkouts/current/serverless-search", output); - var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), fileSystem); + var searchDocset = DocumentationSetFile.LoadAndResolve(searchContext.Collector, fileSystem.FileInfo.New("/checkouts/current/serverless-search/docs/docset.yml"), FileSystemFactory.ScopeSourceDirectory(fileSystem, "/checkouts")); var searchNav = new DocumentationSetNavigation(searchDocset, searchContext, GenericDocumentationFileFactory.Instance); var documentationSets = new List { observabilityNav, searchNav }; diff --git a/tests/Navigation.Tests/Codex/CodexNavigationTestBase.cs b/tests/Navigation.Tests/Codex/CodexNavigationTestBase.cs index d424570325..fc346ce03a 100644 --- a/tests/Navigation.Tests/Codex/CodexNavigationTestBase.cs +++ b/tests/Navigation.Tests/Codex/CodexNavigationTestBase.cs @@ -5,11 +5,13 @@ using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using Elastic.Codex.Navigation; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Codex; using Elastic.Documentation.Configuration.Toc; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Navigation; using Elastic.Documentation.Navigation.Isolated.Node; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Navigation.Tests.Codex; @@ -74,13 +76,13 @@ private static DocumentationSetFile CreateMockDocumentationSet(MockFileSystem fi internal sealed class TestCodexDocumentationContext(IDiagnosticsCollector collector) : ICodexDocumentationContext { - private readonly MockFileSystem _fileSystem = new(); + private readonly MockFileSystem _fileSystem = new(new Dictionary(), Paths.WorkingDirectoryRoot.FullName); - public IFileInfo ConfigurationPath => _fileSystem.FileInfo.New("/codex.yml"); + public IFileInfo ConfigurationPath => _fileSystem.FileInfo.New(_fileSystem.Path.Join(Paths.WorkingDirectoryRoot.FullName, "codex.yml")); public IDiagnosticsCollector Collector => collector; - public IFileSystem ReadFileSystem => _fileSystem; - public IFileSystem WriteFileSystem => _fileSystem; - public IDirectoryInfo OutputDirectory => _fileSystem.DirectoryInfo.New("/output"); + public ScopedFileSystem ReadFileSystem => FileSystemFactory.ScopeCurrentWorkingDirectory(_fileSystem); + public ScopedFileSystem WriteFileSystem => FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(_fileSystem); + public IDirectoryInfo OutputDirectory => _fileSystem.DirectoryInfo.New(_fileSystem.Path.Join(Paths.ApplicationData.FullName, "codex", "output")); public BuildType BuildType => BuildType.Codex; public void EmitError(string message) => collector.EmitError(ConfigurationPath, message); diff --git a/tests/Navigation.Tests/Codex/GroupNavigationTests.cs b/tests/Navigation.Tests/Codex/GroupNavigationTests.cs index b75b1573f5..f692922cdd 100644 --- a/tests/Navigation.Tests/Codex/GroupNavigationTests.cs +++ b/tests/Navigation.Tests/Codex/GroupNavigationTests.cs @@ -4,6 +4,8 @@ using AwesomeAssertions; using Elastic.Codex.Navigation; +using Elastic.Documentation.Configuration; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Navigation.Tests.Codex; @@ -136,8 +138,8 @@ private sealed class MinimalCodexContext : ICodexDocumentationContext private readonly System.IO.Abstractions.TestingHelpers.MockFileSystem _fs = new(); public System.IO.Abstractions.IFileInfo ConfigurationPath => _fs.FileInfo.New("/codex.yml"); public Elastic.Documentation.Diagnostics.IDiagnosticsCollector Collector => new Elastic.Documentation.Diagnostics.DiagnosticsCollector([]); - public System.IO.Abstractions.IFileSystem ReadFileSystem => _fs; - public System.IO.Abstractions.IFileSystem WriteFileSystem => _fs; + public ScopedFileSystem ReadFileSystem => FileSystemFactory.ScopeCurrentWorkingDirectory(_fs); + public ScopedFileSystem WriteFileSystem => FileSystemFactory.ScopeCurrentWorkingDirectoryForWrite(_fs); public System.IO.Abstractions.IDirectoryInfo OutputDirectory => _fs.DirectoryInfo.New("/output"); public BuildType BuildType => BuildType.Codex; public void EmitError(string message) { } diff --git a/tests/Navigation.Tests/Isolation/PhysicalDocsetTests.cs b/tests/Navigation.Tests/Isolation/PhysicalDocsetTests.cs index 7bc9903e3a..4f16c0fcdf 100644 --- a/tests/Navigation.Tests/Isolation/PhysicalDocsetTests.cs +++ b/tests/Navigation.Tests/Isolation/PhysicalDocsetTests.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Navigation.Isolated.Leaf; using Elastic.Documentation.Navigation.Isolated.Node; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Navigation.Tests.Isolation; @@ -27,7 +28,7 @@ public async Task PhysicalDocsetCanBeNavigated() var configPath = fileSystem.FileInfo.New(docsetPath); var context = new TestDocumentationSetContext(fileSystem, docsDir, outputDir, configPath, output, "docs-builder"); - var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, fileSystem, noSuppress: [HintType.DeepLinkingVirtualFile]); + var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, context.ReadFileSystem, noSuppress: [HintType.DeepLinkingVirtualFile]); _ = context.Collector.StartAsync(TestContext.Current.CancellationToken); @@ -74,7 +75,7 @@ public async Task PhysicalDocsetNavigationHasCorrectUrls() var configPath = fileSystem.FileInfo.New(docsetPath); var context = new TestDocumentationSetContext(fileSystem, docsDir, outputDir, configPath, output, "docs-builder"); - var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, fileSystem); + var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, FileSystemFactory.RealRead); _ = context.Collector.StartAsync(TestContext.Current.CancellationToken); var navigation = new DocumentationSetNavigation(docSet, context, TestDocumentationFileFactory.Instance); @@ -100,7 +101,7 @@ public async Task PhysicalDocsetNavigationIncludesNestedTocs() var configPath = fileSystem.FileInfo.New(docsetPath); var context = new TestDocumentationSetContext(fileSystem, docsDir, outputDir, configPath, output, "docs-builder"); - var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, fileSystem); + var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, FileSystemFactory.RealRead); _ = context.Collector.StartAsync(TestContext.Current.CancellationToken); var navigation = new DocumentationSetNavigation(docSet, context, TestDocumentationFileFactory.Instance); @@ -146,7 +147,7 @@ public async Task PhysicalDocsetNavigationHandlesHiddenFiles() var configPath = fileSystem.FileInfo.New(docsetPath); var context = new TestDocumentationSetContext(fileSystem, docsDir, outputDir, configPath, output, "docs-builder"); - var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, fileSystem); + var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, configPath, FileSystemFactory.RealRead); _ = context.Collector.StartAsync(TestContext.Current.CancellationToken); var navigation = new DocumentationSetNavigation(docSet, context, TestDocumentationFileFactory.Instance); diff --git a/tests/Navigation.Tests/Navigation.Tests.csproj b/tests/Navigation.Tests/Navigation.Tests.csproj index 8f968026ee..8d5c0bca73 100644 --- a/tests/Navigation.Tests/Navigation.Tests.csproj +++ b/tests/Navigation.Tests/Navigation.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/tests/Navigation.Tests/TestDocumentationSetContext.cs b/tests/Navigation.Tests/TestDocumentationSetContext.cs index 2bba18396e..288dae691f 100644 --- a/tests/Navigation.Tests/TestDocumentationSetContext.cs +++ b/tests/Navigation.Tests/TestDocumentationSetContext.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; using Elastic.Documentation; +using Elastic.Documentation.Configuration; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Extensions; using Elastic.Documentation.Links.CrossLinks; @@ -13,6 +14,7 @@ using Markdig.Parsers; using Markdig.Syntax; using Markdig.Syntax.Inlines; +using Nullean.ScopedFileSystem; namespace Elastic.Documentation.Navigation.Tests; @@ -81,8 +83,8 @@ public TestDocumentationSetContext(IFileSystem fileSystem, TestDiagnosticsCollector? collector = null ) { - ReadFileSystem = fileSystem; - WriteFileSystem = fileSystem; + ReadFileSystem = FileSystemFactory.ScopeSourceDirectory(fileSystem, sourceDirectory.FullName); + WriteFileSystem = FileSystemFactory.ScopeSourceDirectoryForWrite(fileSystem, outputDirectory.FullName); DocumentationSourceDirectory = sourceDirectory; OutputDirectory = outputDirectory; ConfigurationPath = configPath; @@ -100,8 +102,8 @@ public TestDocumentationSetContext(IFileSystem fileSystem, } public IDiagnosticsCollector Collector { get; } - public IFileSystem ReadFileSystem { get; } - public IFileSystem WriteFileSystem { get; } + public ScopedFileSystem ReadFileSystem { get; } + public ScopedFileSystem WriteFileSystem { get; } public IDirectoryInfo OutputDirectory { get; } public IDirectoryInfo DocumentationSourceDirectory { get; } public GitCheckoutInformation Git { get; } diff --git a/tests/authoring/Framework/CrossLinkResolverAssertions.fs b/tests/authoring/Framework/CrossLinkResolverAssertions.fs index 8871376619..0171ace235 100644 --- a/tests/authoring/Framework/CrossLinkResolverAssertions.fs +++ b/tests/authoring/Framework/CrossLinkResolverAssertions.fs @@ -13,6 +13,7 @@ open Elastic.Documentation.Links open Elastic.Documentation.Links.CrossLinks open Elastic.Documentation open Swensen.Unquote +open Elastic.Documentation.Configuration open Elastic.Documentation.Configuration.Builder open authoring @@ -30,8 +31,8 @@ module CrossLinkResolverAssertions = member _.Collector = collector member _.DocumentationSourceDirectory = mockFileSystem.DirectoryInfo.New("/docs") member _.Git = GitCheckoutInformation.Unavailable - member _.ReadFileSystem = mockFileSystem - member _.WriteFileSystem = mockFileSystem + member _.ReadFileSystem = FileSystemFactory.ScopeCurrentWorkingDirectory(mockFileSystem) + member _.WriteFileSystem = FileSystemFactory.ScopeCurrentWorkingDirectory(mockFileSystem) member _.ConfigurationPath = mockFileSystem.FileInfo.New("mock_docset.yml") member _.OutputDirectory = mockFileSystem.DirectoryInfo.New(".artifacts") member _.BuildType = BuildType.Isolated diff --git a/tests/authoring/Framework/Setup.fs b/tests/authoring/Framework/Setup.fs index 33bb1f2f57..c997400169 100644 --- a/tests/authoring/Framework/Setup.fs +++ b/tests/authoring/Framework/Setup.fs @@ -305,7 +305,7 @@ type Setup = ) let context = BuildContext( collector, - fileSystem, + FileSystemFactory.ScopeCurrentWorkingDirectory(fileSystem), configurationContext, UrlPathPrefix = (options.UrlPathPrefix |> Option.defaultValue ""), CanonicalBaseUrl = Uri("https://www.elastic.co/")