SDT/tests/DevTool.Tests/ConfigBootstrapperTests.cs
2026-03-04 16:40:57 -06:00

188 lines
7.2 KiB
C#

using Sdt.Config;
using Xunit;
namespace DevTool.Tests;
public sealed class ConfigBootstrapperTests
{
[Fact]
public void Scan_DetectsDotnetAndNode()
{
var root = CreateTempDir();
File.WriteAllText(Path.Combine(root, "sample.sln"), "");
File.WriteAllText(Path.Combine(root, "package.json"), """{ "name": "demo", "scripts": { "build": "echo build" } }""");
var scan = ConfigBootstrapper.Scan(root);
Assert.Contains("dotnet", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Contains("node", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Contains("npm", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
}
[Fact]
public void Scan_DetectsPythonFromScriptsDirectory()
{
var root = CreateTempDir();
var scripts = Path.Combine(root, "scripts");
Directory.CreateDirectory(scripts);
File.WriteAllText(Path.Combine(scripts, "publish-app.py"), "print('ok')");
var scan = ConfigBootstrapper.Scan(root);
Assert.Contains("python", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
}
[Fact]
public void Scan_DoesNotDetectNode_ForDependencyOnlyPackageJson()
{
var root = CreateTempDir();
File.WriteAllText(Path.Combine(root, "package.json"), """{ "dependencies": { "leftpad": "1.0.0" } }""");
var scan = ConfigBootstrapper.Scan(root);
Assert.DoesNotContain("node", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.DoesNotContain("npm", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
}
[Fact]
public void BuildDefaultConfig_ProducesWorkflowsAndDebugSection()
{
var scan = new BootstrapScanResult(
ProjectRoot: Path.GetTempPath(),
ProjectName: "demo",
ProjectType: "dotnet",
ToolFamilies: ["dotnet", "git"],
DotnetWorkingDir: ".",
NodeWorkingDir: null,
PythonRequirementsFile: null,
HasDockerCompose: false,
RootHints: ["*.sln"]);
var cfg = ConfigBootstrapper.BuildDefaultConfig(scan);
Assert.NotNull(cfg.Debug);
Assert.Contains(cfg.Workflows, w => w.Id == "build");
Assert.Contains(cfg.Workflows, w => w.Id == "repo-health");
Assert.False(cfg.Debug!.Diagnostics.IncludeAllEnv);
Assert.Contains("SDT_LOG_LEVEL", cfg.Debug.Diagnostics.CaptureEnvKeys);
Assert.NotNull(cfg.EnvProfiles);
Assert.Contains(cfg.EnvProfiles!.Profiles, p => p.Id == "dev");
Assert.Contains(cfg.EnvProfiles.Profiles, p => p.Id == "ci");
Assert.Contains(cfg.EnvProfiles.Profiles, p => p.Id == "release");
}
[Fact]
public void BuildDefaultConfig_IncludesScriptDrivenWorkflow_WhenHelpersExist()
{
var root = CreateTempDir();
var scripts = Path.Combine(root, "scripts");
Directory.CreateDirectory(scripts);
File.WriteAllText(Path.Combine(scripts, "publish-app.py"), "print('ok')");
File.WriteAllText(Path.Combine(scripts, "publish-sidecar.py"), "print('ok')");
var scan = ConfigBootstrapper.Scan(root);
var cfg = ConfigBootstrapper.BuildDefaultConfig(scan);
Assert.Contains(cfg.Workflows, w => w.Id == "web");
Assert.Contains(cfg.Workflows, w => w.Id == "sidecar");
}
[Fact]
public void WriteDefaultConfig_WritesDevtoolJson()
{
var root = CreateTempDir();
var scan = new BootstrapScanResult(
ProjectRoot: root,
ProjectName: "demo",
ProjectType: "dotnet",
ToolFamilies: ["dotnet"],
DotnetWorkingDir: ".",
NodeWorkingDir: null,
PythonRequirementsFile: null,
HasDockerCompose: false,
RootHints: ["*.sln"]);
var cfg = ConfigBootstrapper.BuildDefaultConfig(scan);
var path = ConfigBootstrapper.WriteDefaultConfig(root, cfg);
Assert.True(File.Exists(path));
}
[Fact]
public void Scan_IgnoresExcludedDirectories_ForToolDetection()
{
var root = CreateTempDir();
var nodeModules = Path.Combine(root, "node_modules", "nested");
Directory.CreateDirectory(nodeModules);
File.WriteAllText(Path.Combine(nodeModules, "package.json"), "{}");
var scan = ConfigBootstrapper.Scan(root);
Assert.DoesNotContain("node", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
}
[Fact]
public void Scan_DetectsGoAndSetsProjectType()
{
var root = CreateTempDir();
File.WriteAllText(Path.Combine(root, "go.mod"), "module example.com/demo");
var scan = ConfigBootstrapper.Scan(root);
Assert.Contains("go", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Equal("go", scan.ProjectType, ignoreCase: true);
}
[Fact]
public void Scan_DetectsMavenAndGradleAsJavaProject()
{
var root = CreateTempDir();
File.WriteAllText(Path.Combine(root, "pom.xml"), "<project/>");
File.WriteAllText(Path.Combine(root, "build.gradle"), "plugins {}");
var scan = ConfigBootstrapper.Scan(root);
Assert.Contains("maven", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Contains("gradle", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Contains("java", scan.ToolFamilies, StringComparer.OrdinalIgnoreCase);
Assert.Equal("java", scan.ProjectType, ignoreCase: true);
}
[Fact]
public void Scan_ResolvesDotnetWorkingDir_FromNearestLaunchPath()
{
var workspace = CreateTempDir();
File.WriteAllText(Path.Combine(workspace, "package.json"), """{ "name": "workspace-root" }""");
var appA = Path.Combine(workspace, "AppA");
var appB = Path.Combine(workspace, "AppB");
Directory.CreateDirectory(appA);
Directory.CreateDirectory(appB);
File.WriteAllText(Path.Combine(appA, "AppA.csproj"), "<Project Sdk=\"Microsoft.NET.Sdk\" />");
File.WriteAllText(Path.Combine(appB, "AppB.csproj"), "<Project Sdk=\"Microsoft.NET.Sdk\" />");
var scan = ConfigBootstrapper.Scan(appB);
Assert.Equal("AppB", scan.DotnetWorkingDir, ignoreCase: true);
}
[Fact]
public void Scan_PrefersTauriPackageRoot_ForNodeWorkingDir()
{
var root = CreateTempDir();
var workspacePkg = Path.Combine(root, "journal");
var appPkg = Path.Combine(workspacePkg, "Journal.App");
Directory.CreateDirectory(appPkg);
Directory.CreateDirectory(Path.Combine(appPkg, "src-tauri"));
File.WriteAllText(Path.Combine(workspacePkg, "package.json"), """{ "name": "workspace", "scripts": { "build": "echo build" } }""");
File.WriteAllText(Path.Combine(appPkg, "package.json"), """{ "name": "journal-app", "scripts": { "build": "echo build", "tauri": "echo tauri" } }""");
File.WriteAllText(Path.Combine(appPkg, "src-tauri", "tauri.conf.json"), "{}");
var scan = ConfigBootstrapper.Scan(root);
Assert.Equal(Path.Combine("journal", "Journal.App"), scan.NodeWorkingDir, ignoreCase: true);
}
private static string CreateTempDir()
{
var path = Path.Combine(Path.GetTempPath(), "sdt-bootstrap-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
}