package build import ( "testing" "dappco.re/go/core/build/internal/ax" "dappco.re/go/core/io" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // setupTestDir creates a temporary directory with the specified marker files. func setupTestDir(t *testing.T, markers ...string) string { t.Helper() dir := t.TempDir() for _, m := range markers { path := ax.Join(dir, m) err := ax.WriteFile(path, []byte("{}"), 0644) require.NoError(t, err) } return dir } func TestDiscovery_Discover_Good(t *testing.T) { fs := io.Local t.Run("detects Go project", func(t *testing.T) { dir := setupTestDir(t, "go.mod") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, types) }) t.Run("detects Go workspace project", func(t *testing.T) { dir := setupTestDir(t, "go.work") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, types) }) t.Run("detects Wails project with priority over Go", func(t *testing.T) { dir := setupTestDir(t, "wails.json", "go.mod") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeWails, ProjectTypeGo}, types) }) t.Run("detects Node.js project", func(t *testing.T) { dir := setupTestDir(t, "package.json") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeNode}, types) }) t.Run("detects nested Node.js project", func(t *testing.T) { dir := t.TempDir() nested := ax.Join(dir, "apps", "web") require.NoError(t, ax.MkdirAll(nested, 0755)) require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644)) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeNode}, types) }) t.Run("detects PHP project", func(t *testing.T) { dir := setupTestDir(t, "composer.json") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypePHP}, types) }) t.Run("detects docs project", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, types) }) t.Run("detects docs project with mkdocs.yaml", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yaml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, types) }) t.Run("detects docs project in docs directory", func(t *testing.T) { dir := t.TempDir() require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0755)) require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yml"), []byte("site_name: Demo\n"), 0644)) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, types) }) t.Run("detects docs project in docs directory with mkdocs.yaml", func(t *testing.T) { dir := t.TempDir() require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0755)) require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yaml"), []byte("site_name: Demo\n"), 0644)) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, types) }) t.Run("detects Python project with pyproject.toml", func(t *testing.T) { dir := setupTestDir(t, "pyproject.toml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypePython}, types) }) t.Run("detects Python project with requirements.txt", func(t *testing.T) { dir := setupTestDir(t, "requirements.txt") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypePython}, types) }) t.Run("detects Python only once with both markers", func(t *testing.T) { dir := setupTestDir(t, "pyproject.toml", "requirements.txt") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypePython}, types) }) t.Run("detects Rust project", func(t *testing.T) { dir := setupTestDir(t, "Cargo.toml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeRust}, types) }) t.Run("detects Docker project", func(t *testing.T) { dir := setupTestDir(t, "Dockerfile") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocker}, types) }) t.Run("detects Containerfile project", func(t *testing.T) { dir := setupTestDir(t, "Containerfile") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocker}, types) }) t.Run("detects LinuxKit project", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) require.NoError(t, ax.WriteFile(ax.Join(lkDir, "server.yml"), []byte("kernel:\n"), 0644)) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeLinuxKit}, types) }) t.Run("detects LinuxKit project from yaml config", func(t *testing.T) { dir := t.TempDir() require.NoError(t, ax.WriteFile(ax.Join(dir, "linuxkit.yaml"), []byte("kernel:\n"), 0644)) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeLinuxKit}, types) }) t.Run("detects C++ project", func(t *testing.T) { dir := setupTestDir(t, "CMakeLists.txt") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeCPP}, types) }) t.Run("detects Taskfile project", func(t *testing.T) { dir := setupTestDir(t, "Taskfile.yml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeTaskfile}, types) }) t.Run("detects multiple project types", func(t *testing.T) { dir := setupTestDir(t, "go.mod", "package.json") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo, ProjectTypeNode}, types) }) t.Run("preserves priority when core and fallback markers overlap", func(t *testing.T) { dir := setupTestDir(t, "go.mod", "Dockerfile", "Taskfile.yml") types, err := Discover(fs, dir) assert.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo, ProjectTypeDocker, ProjectTypeTaskfile}, types) }) t.Run("empty directory returns empty slice", func(t *testing.T) { dir := t.TempDir() types, err := Discover(fs, dir) assert.NoError(t, err) assert.Empty(t, types) }) } func TestDiscovery_Discover_Bad(t *testing.T) { fs := io.Local t.Run("non-existent directory returns empty slice", func(t *testing.T) { types, err := Discover(fs, "/non/existent/path") assert.NoError(t, err) // ax.Stat fails silently in fileExists assert.Empty(t, types) }) t.Run("directory marker is ignored", func(t *testing.T) { dir := t.TempDir() // Create go.mod as a directory instead of a file err := ax.Mkdir(ax.Join(dir, "go.mod"), 0755) require.NoError(t, err) types, err := Discover(fs, dir) assert.NoError(t, err) assert.Empty(t, types) }) } func TestDiscovery_PrimaryType_Good(t *testing.T) { fs := io.Local t.Run("returns wails for wails project", func(t *testing.T) { dir := setupTestDir(t, "wails.json", "go.mod") primary, err := PrimaryType(fs, dir) assert.NoError(t, err) assert.Equal(t, ProjectTypeWails, primary) }) t.Run("returns go for go-only project", func(t *testing.T) { dir := setupTestDir(t, "go.mod") primary, err := PrimaryType(fs, dir) assert.NoError(t, err) assert.Equal(t, ProjectTypeGo, primary) }) t.Run("returns node for nested package.json project", func(t *testing.T) { dir := t.TempDir() nested := ax.Join(dir, "apps", "web") require.NoError(t, ax.MkdirAll(nested, 0755)) require.NoError(t, ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644)) primary, err := PrimaryType(fs, dir) assert.NoError(t, err) assert.Equal(t, ProjectTypeNode, primary) }) t.Run("returns empty string for empty directory", func(t *testing.T) { dir := t.TempDir() primary, err := PrimaryType(fs, dir) assert.NoError(t, err) assert.Empty(t, primary) }) } func TestDiscovery_IsGoProject_Good(t *testing.T) { fs := io.Local t.Run("true with go.mod", func(t *testing.T) { dir := setupTestDir(t, "go.mod") assert.True(t, IsGoProject(fs, dir)) }) t.Run("true with go.work", func(t *testing.T) { dir := setupTestDir(t, "go.work") assert.True(t, IsGoProject(fs, dir)) }) t.Run("true with wails.json", func(t *testing.T) { dir := setupTestDir(t, "wails.json") assert.True(t, IsGoProject(fs, dir)) }) t.Run("false without markers", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsGoProject(fs, dir)) }) } func TestDiscovery_IsWailsProject_Good(t *testing.T) { fs := io.Local t.Run("true with wails.json", func(t *testing.T) { dir := setupTestDir(t, "wails.json") assert.True(t, IsWailsProject(fs, dir)) }) t.Run("false with only go.mod", func(t *testing.T) { dir := setupTestDir(t, "go.mod") assert.False(t, IsWailsProject(fs, dir)) }) } func TestDiscovery_IsNodeProject_Good(t *testing.T) { fs := io.Local t.Run("true with package.json", func(t *testing.T) { dir := setupTestDir(t, "package.json") assert.True(t, IsNodeProject(fs, dir)) }) t.Run("false without package.json", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsNodeProject(fs, dir)) }) } func TestDiscovery_IsPHPProject_Good(t *testing.T) { fs := io.Local t.Run("true with composer.json", func(t *testing.T) { dir := setupTestDir(t, "composer.json") assert.True(t, IsPHPProject(fs, dir)) }) t.Run("false without composer.json", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsPHPProject(fs, dir)) }) } func TestDiscovery_Target_Good(t *testing.T) { target := Target{OS: "linux", Arch: "amd64"} assert.Equal(t, "linux/amd64", target.String()) } func TestDiscovery_FileExists_Good(t *testing.T) { fs := io.Local t.Run("returns true for existing file", func(t *testing.T) { dir := t.TempDir() path := ax.Join(dir, "test.txt") err := ax.WriteFile(path, []byte("content"), 0644) require.NoError(t, err) assert.True(t, fileExists(fs, path)) }) t.Run("returns false for directory", func(t *testing.T) { dir := t.TempDir() assert.False(t, fileExists(fs, dir)) }) t.Run("returns false for non-existent path", func(t *testing.T) { assert.False(t, fileExists(fs, "/non/existent/file")) }) } // TestDiscover_Testdata tests discovery using the testdata fixtures. // These serve as integration tests with realistic project structures. func TestDiscovery_DiscoverTestdata_Good(t *testing.T) { fs := io.Local testdataDir, err := ax.Abs("testdata") require.NoError(t, err) tests := []struct { name string dir string expected []ProjectType }{ {"go-project", "go-project", []ProjectType{ProjectTypeGo}}, {"wails-project", "wails-project", []ProjectType{ProjectTypeWails, ProjectTypeGo}}, {"node-project", "node-project", []ProjectType{ProjectTypeNode}}, {"php-project", "php-project", []ProjectType{ProjectTypePHP}}, {"multi-project", "multi-project", []ProjectType{ProjectTypeGo, ProjectTypeNode}}, {"empty-project", "empty-project", []ProjectType{}}, {"docs-project", "docs-project", []ProjectType{ProjectTypeDocs}}, {"python-project", "python-project", []ProjectType{ProjectTypePython}}, {"rust-project", "rust-project", []ProjectType{ProjectTypeRust}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := ax.Join(testdataDir, tt.dir) types, err := Discover(fs, dir) assert.NoError(t, err) if len(tt.expected) == 0 { assert.Empty(t, types) } else { assert.Equal(t, tt.expected, types) } }) } } func TestDiscovery_IsMkDocsProject_Good(t *testing.T) { fs := io.Local t.Run("true with mkdocs.yml", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yml") assert.True(t, IsMkDocsProject(fs, dir)) }) t.Run("true with mkdocs.yaml", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yaml") assert.True(t, IsMkDocsProject(fs, dir)) }) t.Run("false without mkdocs.yml", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsMkDocsProject(fs, dir)) }) } func TestDiscovery_IsMkDocsProject_Bad(t *testing.T) { fs := io.Local t.Run("false for non-existent directory", func(t *testing.T) { assert.False(t, IsMkDocsProject(fs, "/non/existent/path")) }) } func TestDiscovery_IsMkDocsProject_Ugly(t *testing.T) { fs := io.Local t.Run("false when mkdocs.yml is a directory", func(t *testing.T) { dir := t.TempDir() err := ax.Mkdir(ax.Join(dir, "mkdocs.yml"), 0755) require.NoError(t, err) assert.False(t, IsMkDocsProject(fs, dir)) }) } func TestDiscovery_HasSubtreeNpm_Good(t *testing.T) { fs := io.Local t.Run("true with depth 1 nested package.json", func(t *testing.T) { dir := t.TempDir() subdir := ax.Join(dir, "packages", "web") err := ax.MkdirAll(subdir, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(dir, "packages", "package.json"), []byte("{}"), 0644) require.NoError(t, err) assert.True(t, HasSubtreeNpm(fs, dir)) }) t.Run("true with depth 2 nested package.json", func(t *testing.T) { dir := t.TempDir() nested := ax.Join(dir, "apps", "web") err := ax.MkdirAll(nested, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644) require.NoError(t, err) assert.True(t, HasSubtreeNpm(fs, dir)) }) t.Run("false with only root package.json", func(t *testing.T) { dir := setupTestDir(t, "package.json") assert.False(t, HasSubtreeNpm(fs, dir)) }) t.Run("false with empty directory", func(t *testing.T) { dir := t.TempDir() assert.False(t, HasSubtreeNpm(fs, dir)) }) } func TestDiscovery_HasSubtreeNpm_Bad(t *testing.T) { fs := io.Local t.Run("false for non-existent directory", func(t *testing.T) { assert.False(t, HasSubtreeNpm(fs, "/non/existent/path")) }) t.Run("ignores node_modules at depth 1", func(t *testing.T) { dir := t.TempDir() nmDir := ax.Join(dir, "node_modules", "some-pkg") err := ax.MkdirAll(nmDir, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(nmDir, "package.json"), []byte("{}"), 0644) require.NoError(t, err) assert.False(t, HasSubtreeNpm(fs, dir)) }) t.Run("ignores node_modules at depth 2", func(t *testing.T) { dir := t.TempDir() nmDir := ax.Join(dir, "apps", "node_modules", "some-pkg") err := ax.MkdirAll(nmDir, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(nmDir, "package.json"), []byte("{}"), 0644) require.NoError(t, err) // Also need the apps dir to be listable — it is since we created nmDir inside it assert.False(t, HasSubtreeNpm(fs, dir)) }) } func TestDiscovery_HasSubtreeNpm_Ugly(t *testing.T) { fs := io.Local t.Run("false when nested package.json is beyond depth 2", func(t *testing.T) { dir := t.TempDir() deep := ax.Join(dir, "a", "b", "c") err := ax.MkdirAll(deep, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(deep, "package.json"), []byte("{}"), 0644) require.NoError(t, err) assert.False(t, HasSubtreeNpm(fs, dir)) }) } func TestDiscovery_IsPythonProject_Good(t *testing.T) { fs := io.Local t.Run("true with pyproject.toml", func(t *testing.T) { dir := setupTestDir(t, "pyproject.toml") assert.True(t, IsPythonProject(fs, dir)) }) t.Run("true with requirements.txt", func(t *testing.T) { dir := setupTestDir(t, "requirements.txt") assert.True(t, IsPythonProject(fs, dir)) }) t.Run("true with both markers", func(t *testing.T) { dir := setupTestDir(t, "pyproject.toml", "requirements.txt") assert.True(t, IsPythonProject(fs, dir)) }) t.Run("false without markers", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsPythonProject(fs, dir)) }) } func TestDiscovery_IsPythonProject_Bad(t *testing.T) { fs := io.Local t.Run("false for non-existent directory", func(t *testing.T) { assert.False(t, IsPythonProject(fs, "/non/existent/path")) }) } func TestDiscovery_IsPythonProject_Ugly(t *testing.T) { fs := io.Local t.Run("false when pyproject.toml is a directory", func(t *testing.T) { dir := t.TempDir() err := ax.Mkdir(ax.Join(dir, "pyproject.toml"), 0755) require.NoError(t, err) assert.False(t, IsPythonProject(fs, dir)) }) } func TestDiscovery_IsRustProject_Good(t *testing.T) { fs := io.Local t.Run("true with Cargo.toml", func(t *testing.T) { dir := setupTestDir(t, "Cargo.toml") assert.True(t, IsRustProject(fs, dir)) }) t.Run("false without Cargo.toml", func(t *testing.T) { dir := t.TempDir() assert.False(t, IsRustProject(fs, dir)) }) } func TestDiscovery_IsRustProject_Bad(t *testing.T) { fs := io.Local t.Run("false for non-existent directory", func(t *testing.T) { assert.False(t, IsRustProject(fs, "/non/existent/path")) }) } func TestDiscovery_IsRustProject_Ugly(t *testing.T) { fs := io.Local t.Run("false when Cargo.toml is a directory", func(t *testing.T) { dir := t.TempDir() err := ax.Mkdir(ax.Join(dir, "Cargo.toml"), 0755) require.NoError(t, err) assert.False(t, IsRustProject(fs, dir)) }) } func TestDiscovery_DiscoverFull_Good(t *testing.T) { fs := io.Local t.Run("returns complete result for Go project", func(t *testing.T) { dir := setupTestDir(t, "go.mod") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, result.Types) assert.Equal(t, "go", result.PrimaryStack) assert.False(t, result.HasFrontend) assert.False(t, result.HasSubtreeNpm) assert.True(t, result.Markers["go.mod"]) assert.False(t, result.Markers["wails.json"]) }) t.Run("returns complete result for Go workspace project", func(t *testing.T) { dir := setupTestDir(t, "go.work") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, result.Types) assert.Equal(t, "go", result.PrimaryStack) assert.True(t, result.Markers["go.work"]) }) t.Run("returns complete result for Wails project with frontend", func(t *testing.T) { dir := t.TempDir() // Create wails.json, go.mod, and frontend/package.json err := ax.WriteFile(ax.Join(dir, "wails.json"), []byte("{}"), 0644) require.NoError(t, err) err = ax.WriteFile(ax.Join(dir, "go.mod"), []byte("{}"), 0644) require.NoError(t, err) err = ax.MkdirAll(ax.Join(dir, "frontend"), 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(dir, "frontend", "package.json"), []byte("{}"), 0644) require.NoError(t, err) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeWails, ProjectTypeGo, ProjectTypeNode}, result.Types) assert.Equal(t, "wails", result.PrimaryStack) assert.True(t, result.HasFrontend) assert.True(t, result.Markers["wails.json"]) assert.True(t, result.Markers["go.mod"]) }) t.Run("detects subtree npm as frontend", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("{}"), 0644) require.NoError(t, err) nested := ax.Join(dir, "apps", "web") err = ax.MkdirAll(nested, 0755) require.NoError(t, err) err = ax.WriteFile(ax.Join(nested, "package.json"), []byte("{}"), 0644) require.NoError(t, err) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo, ProjectTypeNode}, result.Types) assert.True(t, result.HasSubtreeNpm) assert.True(t, result.HasFrontend) }) t.Run("detects root package.json as frontend", func(t *testing.T) { dir := t.TempDir() require.NoError(t, ax.WriteFile(ax.Join(dir, "package.json"), []byte("{}"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeNode}, result.Types) assert.Equal(t, "node", result.PrimaryStack) assert.True(t, result.HasFrontend) assert.False(t, result.HasSubtreeNpm) }) t.Run("detects frontend deno manifest at project root", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("{}"), 0644) require.NoError(t, err) frontendDir := ax.Join(dir, "frontend") require.NoError(t, ax.MkdirAll(frontendDir, 0755)) require.NoError(t, ax.WriteFile(ax.Join(frontendDir, "deno.json"), []byte("{}"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, result.Types) assert.True(t, result.HasFrontend) assert.False(t, result.HasSubtreeNpm) assert.True(t, result.Markers["frontend/deno.json"]) assert.False(t, result.Markers["frontend/package.json"]) }) t.Run("detects nested deno frontend manifests", func(t *testing.T) { dir := t.TempDir() err := ax.WriteFile(ax.Join(dir, "go.mod"), []byte("{}"), 0644) require.NoError(t, err) frontendDir := ax.Join(dir, "apps", "site") require.NoError(t, ax.MkdirAll(frontendDir, 0755)) require.NoError(t, ax.WriteFile(ax.Join(frontendDir, "deno.jsonc"), []byte("{}"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeGo}, result.Types) assert.True(t, result.HasFrontend) assert.False(t, result.HasSubtreeNpm) }) t.Run("records frontend package manifest markers", func(t *testing.T) { dir := t.TempDir() frontendDir := ax.Join(dir, "frontend") require.NoError(t, ax.MkdirAll(frontendDir, 0755)) require.NoError(t, ax.WriteFile(ax.Join(frontendDir, "package.json"), []byte("{}"), 0644)) require.NoError(t, ax.WriteFile(ax.Join(frontendDir, "deno.jsonc"), []byte("{}"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.True(t, result.HasFrontend) assert.True(t, result.Markers["frontend/package.json"]) assert.True(t, result.Markers["frontend/deno.jsonc"]) }) t.Run("empty directory returns empty result", func(t *testing.T) { dir := t.TempDir() result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Empty(t, result.Types) assert.Empty(t, result.PrimaryStack) assert.False(t, result.HasFrontend) assert.False(t, result.HasSubtreeNpm) }) t.Run("detects docs project markers", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, result.Types) assert.Equal(t, "docs", result.PrimaryStack) assert.True(t, result.Markers["mkdocs.yml"]) }) t.Run("detects docs project markers with mkdocs.yaml", func(t *testing.T) { dir := setupTestDir(t, "mkdocs.yaml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, result.Types) assert.Equal(t, "docs", result.PrimaryStack) assert.True(t, result.Markers["mkdocs.yaml"]) }) t.Run("detects docs project markers in docs directory", func(t *testing.T) { dir := t.TempDir() require.NoError(t, ax.MkdirAll(ax.Join(dir, "docs"), 0755)) require.NoError(t, ax.WriteFile(ax.Join(dir, "docs", "mkdocs.yaml"), []byte("site_name: Demo\n"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocs}, result.Types) assert.Equal(t, "docs", result.PrimaryStack) assert.True(t, result.Markers["docs/mkdocs.yaml"]) }) t.Run("detects Rust project markers", func(t *testing.T) { dir := setupTestDir(t, "Cargo.toml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeRust}, result.Types) assert.Equal(t, "rust", result.PrimaryStack) assert.True(t, result.Markers["Cargo.toml"]) }) t.Run("detects Python project markers", func(t *testing.T) { dir := setupTestDir(t, "pyproject.toml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypePython}, result.Types) assert.Equal(t, "python", result.PrimaryStack) assert.True(t, result.Markers["pyproject.toml"]) }) t.Run("detects Docker project markers", func(t *testing.T) { dir := setupTestDir(t, "Dockerfile") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocker}, result.Types) assert.Equal(t, "docker", result.PrimaryStack) assert.True(t, result.Markers["Dockerfile"]) }) t.Run("records alternate Docker manifest markers", func(t *testing.T) { dir := setupTestDir(t, "Containerfile", "dockerfile", "containerfile") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeDocker}, result.Types) assert.Equal(t, "docker", result.PrimaryStack) assert.True(t, result.Markers["Containerfile"]) assert.True(t, result.Markers["dockerfile"]) assert.True(t, result.Markers["containerfile"]) }) t.Run("detects LinuxKit project markers in .core/linuxkit", func(t *testing.T) { dir := t.TempDir() lkDir := ax.Join(dir, ".core", "linuxkit") require.NoError(t, ax.MkdirAll(lkDir, 0755)) require.NoError(t, ax.WriteFile(ax.Join(lkDir, "server.yml"), []byte("kernel:\n image: test"), 0644)) result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeLinuxKit}, result.Types) assert.Equal(t, "linuxkit", result.PrimaryStack) assert.True(t, result.Markers[".core/linuxkit/*.yml"]) assert.True(t, result.Markers[".core/linuxkit/*.yaml"]) }) t.Run("detects LinuxKit project markers in linuxkit.yaml", func(t *testing.T) { dir := setupTestDir(t, "linuxkit.yaml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeLinuxKit}, result.Types) assert.Equal(t, "linuxkit", result.PrimaryStack) assert.True(t, result.Markers["linuxkit.yaml"]) }) t.Run("detects C++ project markers", func(t *testing.T) { dir := setupTestDir(t, "CMakeLists.txt") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeCPP}, result.Types) assert.Equal(t, "cpp", result.PrimaryStack) assert.True(t, result.Markers["CMakeLists.txt"]) }) t.Run("detects Taskfile project markers", func(t *testing.T) { dir := setupTestDir(t, "Taskfile.yaml") result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.Equal(t, []ProjectType{ProjectTypeTaskfile}, result.Types) assert.Equal(t, "taskfile", result.PrimaryStack) assert.True(t, result.Markers["Taskfile.yaml"]) }) } func TestDiscovery_DiscoverFull_Bad(t *testing.T) { fs := io.Local t.Run("non-existent directory returns empty result", func(t *testing.T) { result, err := DiscoverFull(fs, "/non/existent/path") require.NoError(t, err) assert.Empty(t, result.Types) assert.Empty(t, result.PrimaryStack) }) } func TestDiscovery_DiscoverFull_Ugly(t *testing.T) { fs := io.Local t.Run("markers map is never nil even for empty directory", func(t *testing.T) { dir := t.TempDir() result, err := DiscoverFull(fs, dir) require.NoError(t, err) assert.NotNil(t, result.Markers) }) } func TestDiscovery_ParseOSReleaseDistro_Good(t *testing.T) { t.Run("returns ubuntu version id", func(t *testing.T) { content := ` NAME="Ubuntu" ID=ubuntu VERSION_ID="24.04" ID_LIKE=debian ` assert.Equal(t, "24.04", parseOSReleaseDistro(content)) }) t.Run("accepts ubuntu-style values without quotes", func(t *testing.T) { content := ` ID=ubuntu VERSION_ID=25.10 ` assert.Equal(t, "25.10", parseOSReleaseDistro(content)) }) } func TestDiscovery_ParseOSReleaseDistro_Bad(t *testing.T) { t.Run("returns empty for non-ubuntu distro", func(t *testing.T) { content := ` ID=fedora VERSION_ID=41 ` assert.Empty(t, parseOSReleaseDistro(content)) }) t.Run("returns empty when version missing", func(t *testing.T) { content := ` ID=ubuntu ` assert.Empty(t, parseOSReleaseDistro(content)) }) } func TestDiscovery_DetectDistroVersion_Good(t *testing.T) { fs := io.NewMockMedium() require.NoError(t, fs.Write("/etc/os-release", ` ID=ubuntu VERSION_ID="24.04" `)) assert.Equal(t, "24.04", detectDistroVersion(fs)) } func TestDiscovery_DetectDistroVersion_Bad(t *testing.T) { fs := io.NewMockMedium() require.NoError(t, fs.Write("/etc/os-release", ` ID=fedora VERSION_ID=41 `)) assert.Empty(t, detectDistroVersion(fs)) }