2afab22705
Co-authored-by: cybertronic <cybertronic@gmail.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
170 lines
6.0 KiB
Go
170 lines
6.0 KiB
Go
package agents
|
|
|
|
import (
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/agent/managedruntime"
|
|
)
|
|
|
|
func TestManagedNPMRuntimeContracts(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
agent ManagedNPMRuntimeAgent
|
|
wantPackage string
|
|
wantACPArgs []string
|
|
}{
|
|
{"claude", NewClaudeACP(), "@agentclientprotocol/claude-agent-acp", nil},
|
|
{"codex", NewCodexACP(), "@agentclientprotocol/codex-acp", nil},
|
|
{"opencode", NewOpenCodeACP(), "opencode-ai", []string{"acp", "--print-logs", "--log-level", "ERROR"}},
|
|
{"copilot", NewCopilotACP(), "@github/copilot", []string{"--acp"}},
|
|
{"gemini", NewGemini(), "@google/gemini-cli", []string{"--acp"}},
|
|
{"pi", NewPiACP(), "pi-acp", nil},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
spec := tt.agent.ManagedNPMRuntime()
|
|
wantDefault := spec.DefaultVersionOrPinned()
|
|
if wantDefault == "" {
|
|
t.Fatal("DefaultVersionOrPinned() is empty")
|
|
}
|
|
if got := spec.Package; got != tt.wantPackage {
|
|
t.Fatalf("Package = %q, want %q", got, tt.wantPackage)
|
|
}
|
|
if got := spec.DefaultVersion; got != wantDefault {
|
|
t.Fatalf("DefaultVersion = %q, want %q", got, wantDefault)
|
|
}
|
|
if got := spec.ACPArgs; !slices.Equal(got, tt.wantACPArgs) {
|
|
t.Fatalf("ACPArgs = %#v, want %#v", got, tt.wantACPArgs)
|
|
}
|
|
|
|
cached := spec.CachedACPCommand()
|
|
wantCached := append([]string{"npx", "--yes", "--prefer-offline", tt.wantPackage + "@" + wantDefault}, tt.wantACPArgs...)
|
|
if !slices.Equal(cached.Args(), wantCached) {
|
|
t.Fatalf("CachedACPCommand = %#v, want %#v", cached.Args(), wantCached)
|
|
}
|
|
if got := spec.PackageSpec(""); got != tt.wantPackage+"@"+wantDefault {
|
|
t.Fatalf("empty PackageSpec = %q, want exact default", got)
|
|
}
|
|
|
|
update := spec.CacheUpdateCommand()
|
|
wantUpdate := []string{
|
|
"npm", "exec", "--yes", "--prefer-online",
|
|
"--package=" + tt.wantPackage + "@" + wantDefault, "--", "node", "-e", "",
|
|
}
|
|
if !slices.Equal(update.Args(), wantUpdate) {
|
|
t.Fatalf("CacheUpdateCommand = %#v, want %#v", update.Args(), wantUpdate)
|
|
}
|
|
if strings.Contains(strings.Join(update.Args(), " "), "latest") {
|
|
t.Fatalf("CacheUpdateCommand contains explicit latest: %#v", update.Args())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagedNPMRuntimeExecutionCacheKeyMatchesNPM(t *testing.T) {
|
|
spec := ManagedNPMRuntimeSpec{Package: "opencode-ai"}
|
|
want := managedruntime.NpxExecutionCacheKey(spec.PackageSpec(""))
|
|
if got := spec.ExecutionCacheKey(); got != want {
|
|
t.Fatalf("ExecutionCacheKey = %q, want npm key %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestManagedNPMRuntimeDefaultVersionOrPinned(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
spec ManagedNPMRuntimeSpec
|
|
want string
|
|
}{
|
|
{name: "explicit default", spec: ManagedNPMRuntimeSpec{Package: "@scope/managed", DefaultVersion: "1.2.3"}, want: "1.2.3"},
|
|
{name: "built-in catalogue fallback", spec: ManagedNPMRuntimeSpec{Package: "opencode-ai"}, want: MustDefaultManagedNPMRuntimeVersion("opencode-ai")},
|
|
{name: "custom package without default", spec: ManagedNPMRuntimeSpec{Package: "@scope/managed"}, want: ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.spec.DefaultVersionOrPinned(); got != tt.want {
|
|
t.Fatalf("DefaultVersionOrPinned() = %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestManagedNPMRuntimeBuildsExactVersionCommandsAndCacheKey(t *testing.T) {
|
|
spec := ManagedNPMRuntimeSpec{
|
|
Package: "opencode-ai",
|
|
ACPArgs: []string{"acp", "--print-logs"},
|
|
}
|
|
wantACP := []string{"npx", "--yes", "--prefer-offline", "opencode-ai@1.18.5", "acp", "--print-logs"}
|
|
if got := spec.ACPCommand("1.18.5").Args(); !slices.Equal(got, wantACP) {
|
|
t.Fatalf("ACPCommand = %#v, want %#v", got, wantACP)
|
|
}
|
|
wantUpdate := []string{
|
|
"npm", "exec", "--yes", "--prefer-online",
|
|
"--package=opencode-ai@1.18.5", "--", "node", "-e", "",
|
|
}
|
|
if got := spec.CacheUpdateCommand("1.18.5").Args(); !slices.Equal(got, wantUpdate) {
|
|
t.Fatalf("CacheUpdateCommand = %#v, want %#v", got, wantUpdate)
|
|
}
|
|
if got := spec.ExecutionCacheKey("1.18.5"); got != "cd439a892fc193b3" {
|
|
t.Fatalf("versioned ExecutionCacheKey = %q, want cd439a892fc193b3", got)
|
|
}
|
|
}
|
|
|
|
func TestManagedNPMRuntimeOnlineCommandChangesOnlyNpmFreshnessFlag(t *testing.T) {
|
|
spec := ManagedNPMRuntimeSpec{
|
|
Package: "@scope/managed-acp",
|
|
ACPArgs: []string{"--acp", "--model", "fast"},
|
|
}
|
|
|
|
offline := spec.ACPCommand("1.2.3").Args()
|
|
online := spec.ACPCommandWithNpmPreference("1.2.3", true).Args()
|
|
want := []string{"npx", "--yes", "--prefer-online", "@scope/managed-acp@1.2.3", "--acp", "--model", "fast"}
|
|
if !reflect.DeepEqual(online, want) {
|
|
t.Fatalf("online argv = %#v, want %#v", online, want)
|
|
}
|
|
offline[2] = "--prefer-online"
|
|
if !reflect.DeepEqual(offline, online) {
|
|
t.Fatalf("online command changed more than npm preference: offline=%#v online=%#v", offline, online)
|
|
}
|
|
}
|
|
|
|
func TestManagedNPMRuntimeExactVersionSupportsScopedPackages(t *testing.T) {
|
|
spec := ManagedNPMRuntimeSpec{Package: "@scope/managed-acp", ACPArgs: []string{"--acp"}}
|
|
want := []string{"npx", "--yes", "--prefer-offline", "@scope/managed-acp@3.4.5", "--acp"}
|
|
if got := spec.ACPCommand("3.4.5").Args(); !slices.Equal(got, want) {
|
|
t.Fatalf("scoped ACPCommand = %#v, want %#v", got, want)
|
|
}
|
|
if spec.ExecutionCacheKey("3.4.5") == spec.ExecutionCacheKey() {
|
|
t.Fatal("versioned scoped cache key equals legacy key")
|
|
}
|
|
}
|
|
|
|
func TestManagedAgentsHonorExactVersionCommandOption(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
agent ManagedNPMRuntimeAgent
|
|
}{
|
|
{"claude", NewClaudeACP()},
|
|
{"codex", NewCodexACP()},
|
|
{"opencode", NewOpenCodeACP()},
|
|
{"copilot", NewCopilotACP()},
|
|
{"gemini", NewGemini()},
|
|
{"pi", NewPiACP()},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
version := "1.2.3"
|
|
want := tt.agent.ManagedNPMRuntime().ACPCommand(version).Args()
|
|
got := tt.agent.(interface {
|
|
BuildCommand(CommandOptions) Command
|
|
}).BuildCommand(CommandOptions{ManagedRuntimeVersion: version}).Args()
|
|
if !slices.Equal(got, want) {
|
|
t.Fatalf("exact BuildCommand = %#v, want %#v", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|