Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

366 lines
10 KiB
Go

package modules
import (
"os"
"path/filepath"
"sort"
"testing"
"github.com/zzet/gortex/internal/graph"
)
func TestParsePackageJSONWorkspaces(t *testing.T) {
cases := []struct {
name string
src string
want []string
}{
{
name: "array form",
src: `{"name":"root","workspaces":["packages/*","apps/web"]}`,
want: []string{"packages/*", "apps/web"},
},
{
name: "object form with packages",
src: `{"name":"root","workspaces":{"packages":["libs/*"],"nohoist":["**/react"]}}`,
want: []string{"libs/*"},
},
{
name: "no workspaces field",
src: `{"name":"plain","version":"1.0.0","dependencies":{"react":"^18"}}`,
want: nil,
},
{
name: "empty array",
src: `{"name":"root","workspaces":[]}`,
want: nil,
},
{
name: "whitespace-only entries dropped",
src: `{"workspaces":["packages/*"," "]}`,
want: []string{"packages/*"},
},
{
name: "malformed json",
src: `not json`,
want: nil,
},
{
name: "empty input",
src: ``,
want: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := ParsePackageJSONWorkspaces([]byte(c.src))
if !equalStrings(got, c.want) {
t.Errorf("ParsePackageJSONWorkspaces() = %v, want %v", got, c.want)
}
})
}
}
func TestParsePnpmWorkspaceYAML(t *testing.T) {
cases := []struct {
name string
src string
want []string
}{
{
name: "block sequence",
src: "packages:\n - 'packages/*'\n - \"apps/*\"\n - tools/cli\n",
want: []string{"packages/*", "apps/*", "tools/cli"},
},
{
name: "flow sequence inline",
src: "packages: ['packages/*', \"libs/*\"]\n",
want: []string{"packages/*", "libs/*"},
},
{
name: "comments and blank lines skipped",
src: "# pnpm workspace\n\npackages:\n # members\n - 'packages/*'\n",
want: []string{"packages/*"},
},
{
name: "section ends at next top-level key",
src: "packages:\n - 'packages/*'\ncatalog:\n react: ^18\n",
want: []string{"packages/*"},
},
{
name: "no packages key",
src: "catalog:\n react: ^18\n",
want: nil,
},
{
name: "empty input",
src: ``,
want: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := ParsePnpmWorkspaceYAML([]byte(c.src))
if !equalStrings(got, c.want) {
t.Errorf("ParsePnpmWorkspaceYAML() = %v, want %v", got, c.want)
}
})
}
}
func TestParseCargoWorkspace(t *testing.T) {
cases := []struct {
name string
src string
wantMembers []string
wantExclude []string
}{
{
name: "members and exclude",
src: "[workspace]\nmembers = [\"crates/*\", \"tools/gen\"]\nexclude = [\"crates/legacy\"]\n",
wantMembers: []string{"crates/*", "tools/gen"},
wantExclude: []string{"crates/legacy"},
},
{
name: "members only",
src: "[workspace]\nmembers = [\"crates/*\"]\n",
wantMembers: []string{"crates/*"},
wantExclude: nil,
},
{
name: "root crate that is also a workspace",
src: "[package]\nname = \"root\"\nversion = \"0.1.0\"\n\n[workspace]\nmembers = [\"crates/a\"]\n",
wantMembers: []string{"crates/a"},
wantExclude: nil,
},
{
name: "plain crate, no workspace table",
src: "[package]\nname = \"solo\"\n\n[dependencies]\nserde = \"1.0\"\n",
wantMembers: nil,
wantExclude: nil,
},
{
name: "empty input",
src: ``,
wantMembers: nil,
wantExclude: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
members, exclude := ParseCargoWorkspace([]byte(c.src))
if !equalStrings(members, c.wantMembers) {
t.Errorf("members = %v, want %v", members, c.wantMembers)
}
if !equalStrings(exclude, c.wantExclude) {
t.Errorf("exclude = %v, want %v", exclude, c.wantExclude)
}
})
}
}
func TestWorkspaceRootNodeID(t *testing.T) {
cases := []struct {
eco WorkspaceEcosystem
rootDir string
want string
}{
{WorkspaceNPM, ".", "pkgws::npm:."},
{WorkspaceCargo, "", "pkgws::cargo:."},
{WorkspacePnpm, "sub/dir", "pkgws::pnpm:sub/dir"},
}
for _, c := range cases {
if got := WorkspaceRootNodeID(c.eco, c.rootDir); got != c.want {
t.Errorf("WorkspaceRootNodeID(%q,%q) = %q, want %q", c.eco, c.rootDir, got, c.want)
}
}
}
// writeFiles materialises a map of repo-relative path → content under
// dir, creating parent directories as needed.
func writeFiles(t *testing.T, dir string, files map[string]string) {
t.Helper()
for rel, content := range files {
abs := filepath.Join(dir, filepath.FromSlash(rel))
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
t.Fatalf("mkdir for %s: %v", rel, err)
}
if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
t.Fatalf("write %s: %v", rel, err)
}
}
}
func TestDetectAndResolveWorkspace(t *testing.T) {
cases := []struct {
name string
files map[string]string
wantEco WorkspaceEcosystem
wantMembers []string // sorted manifest paths
}{
{
name: "npm workspace, glob plus explicit member",
files: map[string]string{
"package.json": `{"name":"root","workspaces":["packages/*","apps/web"]}`,
"packages/ui/package.json": `{"name":"ui"}`,
"packages/core/package.json": `{"name":"core"}`,
"packages/notes.md": "not a package",
"apps/web/package.json": `{"name":"web"}`,
"apps/admin/package.json": `{"name":"admin"}`, // not matched — not under a pattern
},
wantEco: WorkspaceNPM,
wantMembers: []string{
"apps/web/package.json",
"packages/core/package.json",
"packages/ui/package.json",
},
},
{
name: "pnpm workspace, glob members",
files: map[string]string{
"pnpm-workspace.yaml": "packages:\n - 'packages/*'\n",
"packages/a/package.json": `{"name":"a"}`,
"packages/b/package.json": `{"name":"b"}`,
"package.json": `{"name":"root"}`,
},
wantEco: WorkspacePnpm,
wantMembers: []string{
"packages/a/package.json",
"packages/b/package.json",
},
},
{
name: "cargo workspace, glob plus explicit, exclude honoured",
files: map[string]string{
"Cargo.toml": "[workspace]\nmembers = [\"crates/*\", \"tools/gen\"]\nexclude = [\"crates/legacy\"]\n",
"crates/engine/Cargo.toml": "[package]\nname = \"engine\"\n",
"crates/cli/Cargo.toml": "[package]\nname = \"cli\"\n",
"crates/legacy/Cargo.toml": "[package]\nname = \"legacy\"\n", // excluded
"tools/gen/Cargo.toml": "[package]\nname = \"gen\"\n",
},
wantEco: WorkspaceCargo,
wantMembers: []string{
"crates/cli/Cargo.toml",
"crates/engine/Cargo.toml",
"tools/gen/Cargo.toml",
},
},
{
name: "non-workspace repo yields no workspace",
files: map[string]string{
"package.json": `{"name":"solo","version":"1.0.0","dependencies":{"react":"^18"}}`,
},
wantEco: "",
wantMembers: nil,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
root := t.TempDir()
writeFiles(t, root, c.files)
manifest := DetectWorkspace(root)
if c.wantEco == "" {
if manifest != nil {
t.Fatalf("expected no workspace, got %+v", manifest)
}
return
}
if manifest == nil {
t.Fatalf("expected a %s workspace, got nil", c.wantEco)
}
if manifest.Ecosystem != c.wantEco {
t.Errorf("ecosystem = %q, want %q", manifest.Ecosystem, c.wantEco)
}
members := ResolveWorkspaceMembers(root, manifest)
gotPaths := make([]string, 0, len(members))
for _, m := range members {
gotPaths = append(gotPaths, m.ManifestPath)
}
sort.Strings(gotPaths)
if !equalStrings(gotPaths, c.wantMembers) {
t.Errorf("members = %v, want %v", gotPaths, c.wantMembers)
}
})
}
}
func TestBuildWorkspaceArtifacts(t *testing.T) {
root := t.TempDir()
writeFiles(t, root, map[string]string{
"package.json": `{"name":"root","workspaces":["packages/*"]}`,
"packages/a/package.json": `{"name":"a"}`,
"packages/b/package.json": `{"name":"b"}`,
})
manifest := DetectWorkspace(root)
if manifest == nil {
t.Fatal("expected an npm workspace")
}
members := ResolveWorkspaceMembers(root, manifest)
if len(members) != 2 {
t.Fatalf("expected 2 members, got %d", len(members))
}
rootNode, edges := BuildWorkspaceArtifacts(manifest, members)
if rootNode == nil {
t.Fatal("expected a workspace root node")
}
if rootNode.Kind != graph.KindPackage {
t.Errorf("root node kind = %q, want %q", rootNode.Kind, graph.KindPackage)
}
if rootNode.ID != "pkgws::npm:." {
t.Errorf("root node ID = %q, want pkgws::npm:.", rootNode.ID)
}
if v, _ := rootNode.Meta["package_workspace"].(bool); !v {
t.Errorf("root node should carry package_workspace=true meta")
}
if v, _ := rootNode.Meta["member_count"].(int); v != 2 {
t.Errorf("member_count meta = %v, want 2", rootNode.Meta["member_count"])
}
if len(edges) != 2 {
t.Fatalf("expected 2 root→member edges, got %d", len(edges))
}
gotTargets := make([]string, 0, len(edges))
for _, e := range edges {
if e.Kind != graph.EdgePackageWorkspaceMember {
t.Errorf("edge kind = %q, want %q", e.Kind, graph.EdgePackageWorkspaceMember)
}
if e.From != rootNode.ID {
t.Errorf("edge From = %q, want %q", e.From, rootNode.ID)
}
if e.Origin != graph.OriginASTResolved {
t.Errorf("edge origin = %q, want %q", e.Origin, graph.OriginASTResolved)
}
gotTargets = append(gotTargets, e.To)
}
sort.Strings(gotTargets)
want := []string{"packages/a/package.json", "packages/b/package.json"}
if !equalStrings(gotTargets, want) {
t.Errorf("edge targets = %v, want %v", gotTargets, want)
}
}
func TestBuildWorkspaceArtifacts_NoMembers(t *testing.T) {
m := &WorkspaceManifest{Ecosystem: WorkspaceNPM, ManifestPath: "package.json"}
node, edges := BuildWorkspaceArtifacts(m, nil)
if node != nil || edges != nil {
t.Errorf("a workspace with no members should yield no artifacts, got node=%v edges=%v", node, edges)
}
}
// equalStrings compares two string slices for order-sensitive equality,
// treating nil and empty as equal.
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}