using System.Text.Json; using w4c_workflows.Services.Execution; using Xunit; namespace w4c_workflows.Tests; public class CSharpScriptExecutorTests { private readonly CSharpScriptExecutor _executor = new(); [Fact] public async Task Executes_program_with_string_input() { using var dir = new TempDir(); dir.Write("Program.cs", """ using System.Text.Json; public static class Program { public static object Main(string input) { using var doc = JsonDocument.Parse(input); var n = doc.RootElement.GetProperty("n").GetInt32(); return new { squared = n * n }; } } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{\"n\":5}", dir.Path), default); Assert.True(result.Success, result.Error); Assert.NotNull(result.Output); using var output = JsonDocument.Parse(result.Output!); Assert.Equal(25, output.RootElement.GetProperty("squared").GetInt32()); } [Fact] public async Task Compiles_only_entry_file_not_other_cs_files() { // Only the entry file (Program.cs) is compiled. Helper.cs exists in the // directory but is NOT included, so referencing Helper.Add causes a // compilation error. This prevents duplicate-type errors when multiple // tasks each define their own Program class in the same workflow directory. using var dir = new TempDir(); dir.Write("Helper.cs", """ public static class Helper { public static int Add(int a, int b) => a + b; } """); dir.Write("Program.cs", """ using System.Text.Json; public static class Program { public static object Main(string input) { using var doc = JsonDocument.Parse(input); var a = doc.RootElement.GetProperty("a").GetInt32(); var b = doc.RootElement.GetProperty("b").GetInt32(); return new { sum = Helper.Add(a, b) }; } } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{\"a\":2,\"b\":3}", dir.Path), default); Assert.False(result.Success); Assert.Contains("compilation failed", result.Error, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task Supports_custom_entry_function_name() { using var dir = new TempDir(); dir.Write("Program.cs", """ public static class Program { public static object Transform(string input) => new { echo = input }; } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{\"x\":1}", dir.Path, entryFunction: "Transform"), default); Assert.True(result.Success, result.Error); using var output = JsonDocument.Parse(result.Output!); Assert.Equal("{\"x\":1}", output.RootElement.GetProperty("echo").GetString()); } [Fact] public async Task Supports_async_entry() { using var dir = new TempDir(); dir.Write("Program.cs", """ using System.Threading.Tasks; public static class Program { public static async Task Main(string input) { await Task.Yield(); return new { ok = true }; } } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{}", dir.Path), default); Assert.True(result.Success, result.Error); Assert.Contains("\"ok\":true", result.Output); } [Fact] public async Task Fails_with_compile_error() { using var dir = new TempDir(); dir.Write("Program.cs", "public static class Program { public static object Main(string input) { return ; } }"); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{}", dir.Path), default); Assert.False(result.Success); Assert.Contains("compilation failed", result.Error, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task Fails_when_entry_method_missing() { using var dir = new TempDir(); dir.Write("Program.cs", "public static class Program { public static int NotTheEntry() => 1; }"); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Program.cs", "{}", dir.Path), default); Assert.False(result.Success); Assert.Contains("no static entry method", result.Error); } [Fact] public async Task Runs_program_with_dynamic_input() { // Frontend stubs generate `dynamic input` (compiles to `object`), but the // executor used to only match `string` params — so a valid stub compiled // yet was never found at invoke time. using var dir = new TempDir(); dir.Write("Main.cs", """ public static class Program { public static object Main(dynamic input) { return new { ok = true, input = input }; } } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Main.cs", "{\"x\":1}", dir.Path), default); Assert.True(result.Success, result.Error); using var output = JsonDocument.Parse(result.Output!); Assert.True(output.RootElement.GetProperty("ok").GetBoolean()); Assert.Equal("{\"x\":1}", output.RootElement.GetProperty("input").GetString()); } [Fact] public async Task Reports_expression_body_followed_by_block_as_compile_error() { // Regression: an LLM/agent edit turned the stub body into `=> { ... }`, // which is invalid C#. This is exactly the reported failure // "Main.cs(5,48): error CS1525: Invalid expression term '{'". using var dir = new TempDir(); dir.Write("Main.cs", """ public static class Program { public static object Main(dynamic input) => { return new { ok = true, input = input }; } } """); var result = await _executor.ExecuteAsync( Invocation.For("csharp", "Main.cs", "{}", dir.Path), default); Assert.False(result.Success); Assert.Contains("compilation failed", result.Error, StringComparison.OrdinalIgnoreCase); Assert.Contains("CS1525", result.Error, StringComparison.OrdinalIgnoreCase); } }