using System.Text.Json.Nodes; using Microsoft.Extensions.Configuration; using w4c_workflows.Models.Nodes; using w4c_workflows.Services; using w4c_workflows.Services.Execution; using w4c_workflows.Services.Nodes; using w4c_workflows.Services.Nodes.Executors; using Xunit; namespace w4c_workflows.Tests; /// /// The core.code node bridges the node kernel to the language runtimes: /// input items go in as JSON, the script's JSON output comes back as items. /// public class CodeNodeExecutorTests { private static CodeNodeExecutor Executor() { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Workflows:TaskTimeoutSeconds"] = "60", }) .Build(); var runtimes = new RuntimeRegistry( new LanguageRegistry(), new IScriptExecutor[] { new SubprocessScriptExecutor("shell", "sh", config) }); return new CodeNodeExecutor(runtimes); } private static NodeExecutionContext Context( string? language, string? entryFile, IReadOnlyList input, string? workingDirectory) => new() { Blueprint = NodeTestData.CoreCatalog().Get("core.code")!, Parameters = new JsonObject { ["language"] = language, ["entryFile"] = entryFile, }, Inputs = new IReadOnlyList[] { input }, WorkingDirectory = workingDirectory, TenantId = "tenant-1", RunId = "run-1", TaskId = "task-1", NodeName = "code", }; private static FlowItem Item(string json) => FlowItem.FromJson(JsonNode.Parse(json)!.AsObject()); [Fact] public async Task Runs_a_shell_script_and_maps_its_json_output_to_items() { using var dir = new TempDir(); dir.Write("task.sh", "#!/bin/sh\nread input\necho '{\"ok\":true,\"seen\":\"input\"}'\n"); var outcome = await Executor().RunAsync( Context("shell", "task.sh", new[] { Item("""{"x":1}""") }, dir.Path), default); Assert.True(outcome.Succeeded, outcome.Failure?.Message); var item = Assert.Single(outcome.Outputs[0]); Assert.True(item.Json["ok"]!.GetValue()); } [Fact] public async Task Missing_language_fails_with_an_invalid_parameter_code() { var outcome = await Executor().RunAsync( Context(null, "task.sh", Array.Empty(), null), default); Assert.False(outcome.Succeeded); Assert.Equal("invalid_parameter", outcome.Failure!.Code); } [Fact] public async Task Unknown_language_fails_with_a_clear_code() { var outcome = await Executor().RunAsync( Context("cobol", "task.cob", Array.Empty(), null), default); Assert.False(outcome.Succeeded); Assert.Equal("unknown_language", outcome.Failure!.Code); } [Fact] public async Task A_non_zero_exit_fails_the_node() { using var dir = new TempDir(); dir.Write("task.sh", "#!/bin/sh\necho boom 1>&2\nexit 2\n"); var outcome = await Executor().RunAsync( Context("shell", "task.sh", Array.Empty(), dir.Path), default); Assert.False(outcome.Succeeded); Assert.Equal("script_failed", outcome.Failure!.Code); Assert.Contains("boom", outcome.Failure.Message); } }