using ClawdDotNet.Core.Config; using ClawdDotNet.Core.Organisation; using Shouldly; namespace ClawdDotNet.Core.Tests.Organisation; /// /// #684: Who may use which workspace. The right stands at the workspace — a member list of /// units and agents with a level — and the tree decides what a unit entry covers. /// public sealed class WorkspaceAccessTests { private static readonly UnitConfig[] Units = [ new() { Id = "purchasing", Name = "Purchasing", LeaderAgentId = "head" }, new() { Id = "suppliers", Name = "Suppliers", ParentId = "purchasing" }, new() { Id = "marketing", Name = "Marketing" } ]; private static readonly AgentConfig[] Agents = [ new() { AgentId = "head", UnitId = "purchasing" }, new() { AgentId = "scout", UnitId = "suppliers" }, new() { AgentId = "ads", UnitId = "marketing" }, new() { AgentId = "ceo" } ]; private static OrgChart Chart(params WorkspaceConfig[] workspaces) => new(Units, Agents, workspaces); private static WorkspaceConfig Workspace(string id, string? owner, params WorkspaceMember[] members) => new() { Id = id, Name = id, OwnerUnitId = owner, Members = [.. members] }; [Fact] public void A_unit_entry_covers_the_units_below() { var org = Chart(Workspace("purchasing", "purchasing", new WorkspaceMember { UnitId = "purchasing", Access = WorkspaceAccess.Write })); org.AccessTo("head", "purchasing").ShouldBe(WorkspaceAccess.Write); org.AccessTo("scout", "purchasing").ShouldBe(WorkspaceAccess.Write); org.AccessTo("ads", "purchasing").ShouldBe(WorkspaceAccess.None); org.AccessTo("ceo", "purchasing").ShouldBe(WorkspaceAccess.None, "the top level is not a member of every unit"); } [Fact] public void Without_sub_units_the_entry_covers_the_unit_alone() { var org = Chart(Workspace("round", null, new WorkspaceMember { UnitId = "purchasing", Access = WorkspaceAccess.Read, IncludeSubUnits = false })); org.AccessTo("head", "round").ShouldBe(WorkspaceAccess.Read); org.AccessTo("scout", "round").ShouldBe(WorkspaceAccess.None); } [Fact] public void The_highest_level_of_all_matching_entries_counts() { var org = Chart(Workspace("articles", "purchasing", new WorkspaceMember { UnitId = "purchasing", Access = WorkspaceAccess.Read }, new WorkspaceMember { AgentId = "scout", Access = WorkspaceAccess.Manage })); org.AccessTo("scout", "articles").ShouldBe(WorkspaceAccess.Manage); org.AccessTo("head", "articles").ShouldBe(WorkspaceAccess.Read); } [Fact] public void An_agent_on_the_top_level_can_be_named_directly() => Chart(Workspace("board", null, new WorkspaceMember { AgentId = "ceo", Access = WorkspaceAccess.Write })) .AccessTo("ceo", "board").ShouldBe(WorkspaceAccess.Write); [Fact] public void The_own_unit_s_workspace_comes_first_and_only_visible_ones_are_listed() { var org = Chart( Workspace("articles", "purchasing", new WorkspaceMember { UnitId = "marketing", Access = WorkspaceAccess.Read }), Workspace("marketing", "marketing", new WorkspaceMember { UnitId = "marketing", Access = WorkspaceAccess.Write }), Workspace("purchasing", "purchasing", new WorkspaceMember { UnitId = "purchasing", Access = WorkspaceAccess.Write })); var grants = org.WorkspacesFor("ads"); grants.Select(g => g.Workspace.Id).ShouldBe(["marketing", "articles"]); grants[0].IsOwnUnit.ShouldBeTrue(); org.UnitWorkspace("marketing")!.Id.ShouldBe("marketing"); } [Fact] public void A_workspace_carrying_a_unit_s_id_but_owned_by_another_is_not_that_unit_s() => Chart(Workspace("marketing", "purchasing")).UnitWorkspace("marketing").ShouldBeNull(); [Fact] public void Broken_references_and_reserved_names_are_named() { var problems = Chart( Workspace("shared", null), Workspace("orphan", "gone", new WorkspaceMember { UnitId = "nowhere" }, new WorkspaceMember { AgentId = "ghost" })).Problems(); problems.ShouldContain(p => p.Contains("'shared' has an invalid id")); problems.ShouldContain(p => p.Contains("'gone'")); problems.ShouldContain(p => p.Contains("'nowhere'")); problems.ShouldContain(p => p.Contains("'ghost'")); } [Fact] public void Workspaces_live_next_to_the_one_shared_folder() { var shared = Path.Combine(Path.GetTempPath(), "inst", "Agents", "SharedWorkspace"); WorkspaceLayout.PathOf(shared, "purchasing") .ShouldBe(Path.Combine(Path.GetTempPath(), "inst", "Agents", "SharedWorkspaces", "purchasing")); WorkspaceLayout.PathOf(shared + Path.DirectorySeparatorChar, "purchasing") .ShouldBe(Path.Combine(Path.GetTempPath(), "inst", "Agents", "SharedWorkspaces", "purchasing"), "a trailing separator changes nothing"); WorkspaceLayout.PathOf(shared, "../escape").ShouldBeNull(); WorkspaceLayout.PathOf(null, "purchasing").ShouldBeNull(); } [Fact] public void The_briefing_names_the_workspaces_and_what_the_agent_may_do_there() { var org = Chart( Workspace("marketing", "marketing", new WorkspaceMember { UnitId = "marketing", Access = WorkspaceAccess.Write }), new WorkspaceConfig { Id = "articles", Name = "Article master", OwnerUnitId = "purchasing", Purpose = "One file per article.", Members = [new WorkspaceMember { UnitId = "marketing", Access = WorkspaceAccess.Read }] }); var section = OrgBriefing.For(org, "ads", DateTime.UtcNow)!; section.ShouldContain("\"unit\" — your unit's workspace (also \"marketing\"), read and write"); section.ShouldContain("\"articles\" (Article master), read only — One file per article."); OrgBriefing.For(org, "scout", DateTime.UtcNow)!.ShouldNotContain("workspaces", Case.Insensitive, "the scout is in none of them — and must not learn that they exist"); } }