summaryrefslogtreecommitdiff
path: root/tests/SomhairlesDream.Cli.Tests
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 00:58:15 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 00:58:15 +0800
commit7b29dbff7e43e87741476f47df17ad1582b16546 (patch)
tree4c9d0832b4e4edd77e59e9cb0a88abf5e729a9f9 /tests/SomhairlesDream.Cli.Tests
parent47add3088d50f12c5f48591f26c53cdb59d8283b (diff)
downloadsomhairles-dream-fsharp-7b29dbff7e43e87741476f47df17ad1582b16546.tar.gz
feat(pipeline): add runnable artifact pipeline increment
[Change Nature] - This commit adds the first runnable increment of the F# artifact pipeline: CLI, server, frontend bundle, stage fixture, and tests. It is feature work, not a bug fix. [New Capability] - CLI `run` executes the three-step Blender fixture through the process bridge and writes GLBs, step logs, events, and a manifest; `verify` validates a manifest without Blender. - Server exposes run start, run-scoped SSE, artifact manifest/file routes, and serves the Fable frontend from its build output. - Frontend starts runs, follows run-scoped SSE progress, and links the artifact manifest on completion. [Implementation] - Shared layer holds domain, run state, replay, and artifact contracts; Modeling holds the pipeline, 120s bridge timeout, and result-file protocol; stage/blender/fixture.py owns geometry in Python (bpy owns geometry, F# owns orchestration). - Fable bundle is regenerated into public/ and copied by the server project; solution includes all projects with warnings-as-errors. - README documents build, Fable, server, interpreter/env, fixture limitations, and current verification status. [Impact] - Verified: 40/40 solution tests pass; Release build 0 warnings/errors; real CLI run with bpy 5.0.1 produced 3 GLBs, 3 logs, and a verify-clean manifest; isolated-port server smoke flow succeeded. - Not yet verified: browser end-to-end behavior and production fidelity. Artifacts and caches are gitignored.
Diffstat (limited to 'tests/SomhairlesDream.Cli.Tests')
-rw-r--r--tests/SomhairlesDream.Cli.Tests/ArgumentsTests.fs70
-rw-r--r--tests/SomhairlesDream.Cli.Tests/SomhairlesDream.Cli.Tests.fsproj21
2 files changed, 91 insertions, 0 deletions
diff --git a/tests/SomhairlesDream.Cli.Tests/ArgumentsTests.fs b/tests/SomhairlesDream.Cli.Tests/ArgumentsTests.fs
new file mode 100644
index 0000000..f55762e
--- /dev/null
+++ b/tests/SomhairlesDream.Cli.Tests/ArgumentsTests.fs
@@ -0,0 +1,70 @@
+module SomhairlesDream.Cli.Tests.ArgumentsTests
+
+open Xunit
+open SomhairlesDream.Cli
+
+let private expectOk = function
+ | Ok value -> value
+ | Error message -> failwith message
+
+let private expectError = function
+ | Error message -> message
+ | Ok _ -> failwith "expected an error"
+
+[<Fact>]
+let ``run command applies documented defaults`` () =
+ match Arguments.parse [| "run" |] |> expectOk with
+ | Run options ->
+ Assert.Equal("heritage-001", options.Ids.ProjectId)
+ Assert.Equal("run-001", options.Ids.RunId)
+ Assert.Equal("base-000", options.Ids.BaseId)
+ Assert.Equal("target-003", options.Ids.TargetId)
+ Assert.Equal(".artifacts", options.OutputRoot)
+ Assert.Equal("stage", options.StageRoot)
+ Assert.False(options.Render)
+ Assert.NotEmpty(options.Python)
+ | Verify _
+ | Help -> failwith "expected run command"
+
+[<Fact>]
+let ``run command parses explicit ids and render flag`` () =
+ match
+ Arguments.parse
+ [| "run"
+ "--project-id"
+ "project-x"
+ "--run-id"
+ "run-x"
+ "--base-id"
+ "base-x"
+ "--target-id"
+ "target-x"
+ "--output-root"
+ "out"
+ "--stage-root"
+ "fixtures"
+ "--python"
+ "/tmp/python"
+ "--render" |]
+ |> expectOk with
+ | Run options ->
+ Assert.Equal("project-x", options.Ids.ProjectId)
+ Assert.Equal("run-x", options.Ids.RunId)
+ Assert.Equal("base-x", options.Ids.BaseId)
+ Assert.Equal("target-x", options.Ids.TargetId)
+ Assert.Equal("out", options.OutputRoot)
+ Assert.Equal("fixtures", options.StageRoot)
+ Assert.Equal("/tmp/python", options.Python)
+ Assert.True(options.Render)
+ | Verify _
+ | Help -> failwith "expected run command"
+
+[<Fact>]
+let ``verify command requires exactly one manifest path`` () =
+ match Arguments.parse [| "verify"; "run"; "extra" |] |> expectError with
+ | message -> Assert.Contains("manifest", message)
+
+[<Fact>]
+let ``arguments reject unsafe resource ids`` () =
+ match Arguments.parse [| "run"; "--project-id"; "../escape" |] |> expectError with
+ | message -> Assert.Contains("resource id", message)
diff --git a/tests/SomhairlesDream.Cli.Tests/SomhairlesDream.Cli.Tests.fsproj b/tests/SomhairlesDream.Cli.Tests/SomhairlesDream.Cli.Tests.fsproj
new file mode 100644
index 0000000..461d5eb
--- /dev/null
+++ b/tests/SomhairlesDream.Cli.Tests/SomhairlesDream.Cli.Tests.fsproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>net8.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <IsTestProject>true</IsTestProject>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
+ <PackageReference Include="xunit" Version="2.9.2" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="../../src/SomhairlesDream.Cli/SomhairlesDream.Cli.fsproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ArgumentsTests.fs" />
+ </ItemGroup>
+</Project>