using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using w4c_workflows.Services; using w4c_workflows.Services.Runs; using Xunit; namespace w4c_workflows.Tests; /// /// Regression: the run's working directory must be resolved from the tenant's OWN /// source directory (the same one the workflow-file writer uses), never the /// global/git root. Before the fix, fell back to the /// git root when not in Forgejo-backed mode, so a freshly created workflow's entry /// file (e.g. Main.cs) was not found at run time ("entry file not found: /// Main.cs"). /// public class TaskDispatcherWorkingDirTests { private static WorkflowSourceFactory NewFactory(string copiesRoot) { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["WorkflowSource:CopiesRoot"] = copiesRoot, }) .Build(); return new( config, env: null!, // unused by the factory, keep the test free of ASP.NET types NullLoggerFactory.Instance, new GitRunner(config, NullLogger.Instance), httpFactory: null!); // Forgejo is not configured, so no client is used } private static TaskDispatcher NewDispatcher(string copiesRoot) => new( db: null!, // not used by ResolveWorkingDirAsync jobs: null!, // not used by ResolveWorkingDirAsync config: new ConfigurationBuilder().Build(), logger: NullLogger.Instance, sourceFactory: NewFactory(copiesRoot)); [Fact] public async Task ResolveWorkingDirAsync_uses_tenant_copies_root_not_git_root() { using var tmp = new TempDir(); var dispatcher = NewDispatcher(tmp.Path); // A workflow definition stored under the tenant's own dir, referenced as a // repo-relative path with the shared "workflows" prefix. var workingDir = await dispatcher.ResolveWorkingDirAsync( "tenant-1", "workflows/test/workflow.yaml", default); // The run must resolve inside the tenant's CopiesRoot directory, i.e. exactly // where WorkflowFilesController.SaveFile writes the code files. var expected = Path.Combine(tmp.Path, "tenant-1", "workflows", "test"); Assert.Equal(Path.GetFullPath(expected), Path.GetFullPath(workingDir)); // And the entry file must actually exist there — proving "entry file not // found: Main.cs" can no longer happen for a correctly scaffolded workflow. Directory.CreateDirectory(expected); var entry = Path.Combine(expected, "Main.cs"); await File.WriteAllTextAsync(entry, "public static class Program { }"); Assert.True(File.Exists(entry)); } [Fact] public async Task ResolveWorkingDirAsync_other_tenant_uses_its_own_copies_root() { using var tmp = new TempDir(); var dispatcher = NewDispatcher(tmp.Path); var workingDir = await dispatcher.ResolveWorkingDirAsync( "tenant-2", "workflows/ship/workflow.yaml", default); var expected = Path.Combine(tmp.Path, "tenant-2", "workflows", "ship"); Assert.Equal(Path.GetFullPath(expected), Path.GetFullPath(workingDir)); } [Fact] public async Task ResolveWorkingDirAsync_without_factory_falls_back_to_global_root() { var dispatcher = new TaskDispatcher( db: null!, jobs: null!, config: new ConfigurationBuilder().Build(), logger: NullLogger.Instance); // No source factory configured: must not throw, returns a deterministic path. var workingDir = await dispatcher.ResolveWorkingDirAsync( "t-1", "workflows/a/workflow.yaml", default); Assert.False(string.IsNullOrWhiteSpace(workingDir)); } }