using Sdt.Config; using Xunit; namespace DevTool.Tests; public sealed class WorkspaceInventoryServiceTests { [Fact] public void Scan_DetectsKnownAndCandidates_WithMarkerPrecedence() { var root = CreateTempDir("sdt-inventory-"); var c = Path.Combine(root, "C"); var b = Path.Combine(root, "B"); var a = Path.Combine(root, "A"); Directory.CreateDirectory(c); Directory.CreateDirectory(b); Directory.CreateDirectory(a); File.WriteAllText(Path.Combine(c, "sdtconfig-c.json"), """{"name":"c","version":"0.1.0","workflows":[]}"""); File.WriteAllText(Path.Combine(b, "B.slnx"), ""); File.WriteAllText(Path.Combine(a, "A.csproj"), ""); var workspace = new WorkspaceConfig { Name = "test", Projects = [new WorkspaceProject { Name = "c", Path = "C" }] }; var service = new WorkspaceInventoryService(); var result = service.Scan(root, c, workspace.Inventory, workspace); Assert.Contains(result.KnownProjects, x => x.RootPath.Equals(Path.GetFullPath(c), StringComparison.OrdinalIgnoreCase)); Assert.Contains(result.Candidates, x => x.RootPath.Equals(Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase)); Assert.Contains(result.Candidates, x => x.RootPath.Equals(Path.GetFullPath(a), StringComparison.OrdinalIgnoreCase)); var bItem = result.Candidates.First(x => x.RootPath.Equals(Path.GetFullPath(b), StringComparison.OrdinalIgnoreCase)); Assert.Equal(WorkspaceInventoryKind.Slnx, bItem.PrimaryKind); } [Fact] public void Scan_DedupesSiblingCsprojRoots_ToParentCluster() { var root = CreateTempDir("sdt-inventory-cluster-"); var cluster = Path.Combine(root, "X"); var app = Path.Combine(cluster, "App"); var tests = Path.Combine(cluster, "App.Tests"); Directory.CreateDirectory(app); Directory.CreateDirectory(tests); File.WriteAllText(Path.Combine(app, "App.csproj"), ""); File.WriteAllText(Path.Combine(tests, "App.Tests.csproj"), ""); var service = new WorkspaceInventoryService(); var result = service.Scan(root, root, new WorkspaceInventorySettings(), new WorkspaceConfig()); Assert.Contains(result.Candidates, x => x.RootPath.Equals(Path.GetFullPath(cluster), StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(result.Candidates, x => x.RootPath.Equals(Path.GetFullPath(app), StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain(result.Candidates, x => x.RootPath.Equals(Path.GetFullPath(tests), StringComparison.OrdinalIgnoreCase)); } [Fact] public void Scan_RespectsExclusions() { var root = CreateTempDir("sdt-inventory-excl-"); var excluded = Path.Combine(root, "node_modules", "Pkg"); Directory.CreateDirectory(excluded); File.WriteAllText(Path.Combine(excluded, "Pkg.csproj"), ""); File.WriteAllText(Path.Combine(root, "sdtconfig-root.json"), """{"name":"root","version":"0.1.0","workflows":[]}"""); var service = new WorkspaceInventoryService(); var result = service.Scan(root, root, new WorkspaceInventorySettings(), new WorkspaceConfig()); Assert.DoesNotContain(result.Candidates, x => x.RootPath.Contains("node_modules", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Scan_IsDeterministicallyOrdered() { var root = CreateTempDir("sdt-inventory-order-"); var b = Path.Combine(root, "B"); var a = Path.Combine(root, "A"); Directory.CreateDirectory(a); Directory.CreateDirectory(b); File.WriteAllText(Path.Combine(a, "a.csproj"), ""); File.WriteAllText(Path.Combine(b, "b.csproj"), ""); File.WriteAllText(Path.Combine(root, "sdtconfig-root.json"), """{"name":"root","version":"0.1.0","workflows":[]}"""); var service = new WorkspaceInventoryService(); var first = service.Scan(root, root, new WorkspaceInventorySettings(), new WorkspaceConfig()); var second = service.Scan(root, root, new WorkspaceInventorySettings(), new WorkspaceConfig()); Assert.Equal( first.Candidates.Select(x => x.RootPath).ToArray(), second.Candidates.Select(x => x.RootPath).ToArray()); } private static string CreateTempDir(string prefix) { var path = Path.Combine(Path.GetTempPath(), prefix + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(path); return path; } }