Files
goclaw/internal/skills/wiring_edition_gate_test.go
Duy /zuey/ 6e5e51a18b feat(packages): Phase 2a — pip + npm update flow (#900) (#6)
* feat(packages): backend pip + npm update flow (#900)

Extend Phase 1 update infrastructure to pip + npm sources. Register
checkers/executors behind edition gate (Lite edition stays github-only).
Per-source sentinel errors + stderr classifier; strict package-name
validators reject @version suffix. Shared PackageLocker serializes
install + update paths. HTTP response surfaces per-source availability
from LookPath detection.

Closes part of #900 (Phase 2a).

* feat(packages): frontend multi-source updates UI (#900)

Unified flat updates list with source pill (github/pip/npm) + filter
dropdown. Summary bar shows per-source counts, hiding sources whose
backend availability=false. 30 i18n keys with full en/vi/zh parity.
Mobile-safe table (overflow-x-auto + min-w-[600px]).

Part of #900 (Phase 2a).

* test(packages): pip + npm integration e2e (#900)

Optional real-runtime integration test behind `pipnpm_e2e` build tag.
Skipped by default CI; exercises full check + apply cycle with real
pip3/npm in Alpine container.

Part of #900 (Phase 2a).

* docs(packages): document pip + npm update flow (#900)

Adds packages-pip-npm.md covering command matrix, exit codes, stderr
error classes, pre-release handling, availability detection, runbook
for EACCES/ERESOLVE/externally-managed, min versions, fixture regen.
Cross-link from packages-github.md. Changelogs updated.

Part of #900 (Phase 2a).

* fix(packages): set exec bit on testdata npm/pip scripts
2026-05-11 15:31:32 +07:00

82 lines
2.7 KiB
Go

package skills
import (
"testing"
"time"
"github.com/nextlevelbuilder/goclaw/internal/edition"
)
// TestEditionGate_LitePreventsRegistration mirrors the wiring logic in
// cmd/gateway_packages_wiring.go and asserts that the pip/npm checkers are
// NOT registered when edition.Current().SupportsPipNpm == false (Lite desktop).
//
// This is the unit-level guard for P2A-H6: "Lite edition runs useless pip/npm
// checkers". The wiring file gates registration like:
//
// if edition.Current().SupportsPipNpm {
// registry.RegisterChecker(NewPipUpdateChecker())
// registry.RegisterExecutor(NewPipUpdateExecutor())
// registry.RegisterChecker(NewNpmUpdateChecker())
// registry.RegisterExecutor(NewNpmUpdateExecutor())
// }
func TestEditionGate_LitePreventsRegistration(t *testing.T) {
// Temporarily set edition to Lite; restore Standard on exit.
edition.SetCurrent(edition.Lite)
t.Cleanup(func() { edition.SetCurrent(edition.Standard) })
// Replicate wiring logic.
registry := NewUpdateRegistry(nil, "", time.Hour)
// Always register github (no edition gate in wiring).
// Use a fakeChecker so we don't need a real GitHubInstaller.
registry.RegisterChecker(&fakeChecker{source: "github", available: true})
// Gate pip+npm behind edition flag — same condition as wiring.
if edition.Current().SupportsPipNpm {
registry.RegisterChecker(NewPipUpdateChecker())
registry.RegisterExecutor(NewPipUpdateExecutor())
registry.RegisterChecker(NewNpmUpdateChecker())
registry.RegisterExecutor(NewNpmUpdateExecutor())
}
sources := registry.Sources()
if len(sources) != 1 || sources[0] != "github" {
t.Errorf("Lite edition: want sources=[github], got %v", sources)
}
// pip and npm must not appear.
for _, s := range sources {
if s == "pip" || s == "npm" {
t.Errorf("Lite edition: unexpected source %q in registry", s)
}
}
}
// TestEditionGate_StandardAllowsRegistration verifies the positive case:
// Standard edition registers all three sources.
func TestEditionGate_StandardAllowsRegistration(t *testing.T) {
edition.SetCurrent(edition.Standard)
t.Cleanup(func() { edition.SetCurrent(edition.Standard) })
registry := NewUpdateRegistry(nil, "", time.Hour)
registry.RegisterChecker(&fakeChecker{source: "github", available: true})
if edition.Current().SupportsPipNpm {
registry.RegisterChecker(NewPipUpdateChecker())
registry.RegisterExecutor(NewPipUpdateExecutor())
registry.RegisterChecker(NewNpmUpdateChecker())
registry.RegisterExecutor(NewNpmUpdateExecutor())
}
sources := registry.Sources() // sorted: github, npm, pip
want := map[string]bool{"github": true, "pip": true, "npm": true}
for _, s := range sources {
delete(want, s)
}
if len(want) != 0 {
t.Errorf("Standard edition: missing sources %v in %v", want, sources)
}
}