using ClawdDotNet.App.Services;
using ClawdDotNet.Core.Config;
using Shouldly;
namespace ClawdDotNet.App.Tests.Services;
///
/// #684: Workspaces in the administration. A unit gets its own with it; a workspace across
/// units is created on purpose, with an owner; nothing filed is ever deleted from here.
///
public sealed class WorkspaceAdministrationTests : IDisposable
{
private readonly string _agents = Path.Combine(Path.GetTempPath(), "clawd-tests", "ws-" + Guid.NewGuid().ToString("N")[..8], "Agents");
private readonly TestHost _test;
public WorkspaceAdministrationTests()
{
_test = new TestHost(Agent("buyer"), Agent("ads"), Agent("bookkeeper"));
}
public void Dispose()
{
_test.Dispose();
try { Directory.Delete(Path.GetDirectoryName(_agents)!, recursive: true); }
catch { /* cleaning up is secondary */ }
}
private AgentConfig Agent(string id) => new()
{
AgentId = id,
DisplayName = id,
SharedWorkspacePath = Path.Combine(_agents, "SharedWorkspace")
};
private OrgAdministration Org => _test.Host.Organisation;
private List Workspaces => _test.Host.Instance.Workspaces;
private void Setup()
{
Org.CreateUnit("purchasing", "Purchasing", null).Ok.ShouldBeTrue();
Org.CreateUnit("marketing", "Marketing", null).Ok.ShouldBeTrue();
Org.MoveAgent("buyer", "purchasing", "setup").Ok.ShouldBeTrue();
Org.MoveAgent("ads", "marketing", "setup").Ok.ShouldBeTrue();
}
private void File(string workspaceId, string name)
{
var folder = Org.WorkspaceFolder(workspaceId)!;
Directory.CreateDirectory(folder);
System.IO.File.WriteAllText(Path.Combine(folder, name), "x");
}
[Fact]
public void A_new_unit_comes_with_its_own_workspace_for_itself_and_below()
{
Setup();
var own = Workspaces.Single(w => w.Id == "purchasing");
own.OwnerUnitId.ShouldBe("purchasing");
own.Members.ShouldHaveSingleItem().UnitId.ShouldBe("purchasing");
own.Members[0].Access.ShouldBe(WorkspaceAccess.Write);
own.Members[0].IncludeSubUnits.ShouldBeTrue();
Org.Current().AccessTo("buyer", "purchasing").ShouldBe(WorkspaceAccess.Write);
Org.Current().AccessTo("ads", "purchasing").ShouldBe(WorkspaceAccess.None);
}
[Fact]
public void A_unit_cannot_take_a_reserved_name_or_the_id_of_an_existing_workspace()
{
Org.CreateUnit("shared", "Shared", null).Ok.ShouldBeFalse();
Org.CreateWorkspace("articles", "Articles", null, null, []).Ok.ShouldBeTrue();
Org.CreateUnit("articles", "Articles team", null).Ok.ShouldBeFalse();
}
[Fact]
public void A_workspace_across_units_has_an_owner_and_levels()
{
Setup();
var result = Org.CreateWorkspace("articles", "Article master", "One file per article.", "purchasing",
[
new WorkspaceMemberInput("purchasing", null, WorkspaceAccess.Manage),
new WorkspaceMemberInput("marketing", null, WorkspaceAccess.Read)
], "purchasing maintains, marketing reads");
result.Ok.ShouldBeTrue(result.Message);
Org.Current().AccessTo("buyer", "articles").ShouldBe(WorkspaceAccess.Manage);
Org.Current().AccessTo("ads", "articles").ShouldBe(WorkspaceAccess.Read);
}
[Fact]
public void Ids_of_units_are_kept_for_their_own_workspaces()
{
Setup();
Org.CreateWorkspace("marketing", "Other", null, null, []).Ok.ShouldBeFalse();
}
[Theory]
[InlineData("nowhere", null, WorkspaceAccess.Read)]
[InlineData(null, "ghost", WorkspaceAccess.Read)]
[InlineData("marketing", "ads", WorkspaceAccess.Read)]
[InlineData(null, null, WorkspaceAccess.Read)]
[InlineData("marketing", null, WorkspaceAccess.None)]
public void Members_must_be_one_existing_unit_or_agent_with_a_level(string? unit, string? agent, WorkspaceAccess access)
{
Setup();
Org.CreateWorkspace("x", "X", null, null, [new WorkspaceMemberInput(unit, agent, access)]).Ok.ShouldBeFalse();
}
[Fact]
public void The_member_list_is_replaced_and_the_old_one_is_audited_away()
{
Setup();
Org.SetWorkspaceMembers("purchasing",
[
new WorkspaceMemberInput("purchasing", null, WorkspaceAccess.Write),
new WorkspaceMemberInput(null, "ads", WorkspaceAccess.Read)
], "ads may look at the orders").Ok.ShouldBeTrue();
Org.Current().AccessTo("ads", "purchasing").ShouldBe(WorkspaceAccess.Read);
}
[Fact]
public void Protected_folders_are_folders_inside_the_workspace()
{
Setup();
Org.UpdateWorkspace("purchasing", null, null, ["prices/", "/archive"]).Ok.ShouldBeTrue();
Workspaces.Single(w => w.Id == "purchasing").ProtectedPaths.ShouldBe(["prices", "archive"]);
Org.UpdateWorkspace("purchasing", null, null, ["../outside"]).Ok.ShouldBeFalse();
}
[Fact]
public void Only_an_empty_workspace_can_be_deleted_and_never_a_unit_s_own()
{
Setup();
Org.CreateWorkspace("articles", "Articles", null, "purchasing", []).Ok.ShouldBeTrue();
File("articles", "a-100.md");
Org.DeleteWorkspace("articles").Ok.ShouldBeFalse("it holds a file");
Org.DeleteWorkspace("purchasing").Ok.ShouldBeFalse("it goes with its unit");
System.IO.File.Delete(Path.Combine(Org.WorkspaceFolder("articles")!, "a-100.md"));
Org.DeleteWorkspace("articles").Ok.ShouldBeTrue();
}
[Fact]
public void Deleting_a_unit_keeps_what_its_workspace_holds()
{
Setup();
Org.CreateWorkspace("articles", "Articles", null, "purchasing",
[new WorkspaceMemberInput("purchasing", null, WorkspaceAccess.Manage)]).Ok.ShouldBeTrue();
File("purchasing", "orders.md");
Org.MoveAgent("buyer", null, "leaving").Ok.ShouldBeTrue();
var result = Org.DeleteUnit("purchasing", "closed down");
result.Ok.ShouldBeTrue(result.Message);
result.Message.ShouldContain("'purchasing'");
var kept = Workspaces.Single(w => w.Id == "purchasing");
kept.OwnerUnitId.ShouldBeNull();
kept.Members.ShouldBeEmpty();
Workspaces.Single(w => w.Id == "articles").Members.ShouldBeEmpty("the unit left every member list");
System.IO.File.Exists(Path.Combine(Org.WorkspaceFolder("purchasing")!, "orders.md")).ShouldBeTrue();
}
[Fact]
public void Deleting_a_unit_with_an_empty_workspace_removes_both()
{
Setup();
Org.MoveAgent("ads", null, "leaving").Ok.ShouldBeTrue();
Org.DeleteUnit("marketing").Ok.ShouldBeTrue();
Workspaces.ShouldNotContain(w => w.Id == "marketing");
}
[Fact]
public void A_unit_from_before_workspaces_can_get_its_own()
{
_test.Host.Instance.Units.Add(new UnitConfig { Id = "legacy", Name = "Legacy" });
Org.CreateUnitWorkspace("legacy").Ok.ShouldBeTrue();
Org.Current().UnitWorkspace("legacy").ShouldNotBeNull();
Org.CreateUnitWorkspace("legacy").Message.ShouldContain("already");
}
}