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"), "");
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"), "");
File.WriteAllText(Path.Combine(appB, "AppB.csproj"), "");
var scan = ConfigBootstrapper.Scan(appB);
Assert.Equal("AppB", scan.DotnetWorkingDir, ignoreCase: true);
}
private static string CreateTempDir()
{
var path = Path.Combine(Path.GetTempPath(), "sdt-bootstrap-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
}