Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
95 lines
3.2 KiB
Go
95 lines
3.2 KiB
Go
package agenthost
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
storesqlite "github.com/tutti-os/tutti/packages/agent/store-sqlite"
|
|
)
|
|
|
|
func TestAgentRailPlacementEnvironmentSuppliesCanonicalValuesLast(t *testing.T) {
|
|
env, err := withAgentRailPlacementEnvironment([]string{
|
|
"KEEP=value",
|
|
AgentCWDEnvironmentVariable + "=/stale",
|
|
"tutti_agent_cwd=/case-distinct-cwd",
|
|
AgentRailPlacementEnvironmentVariable + "={\"version\":1,\"kind\":\"conversations\",\"sectionKey\":\"conversations\"}",
|
|
"tutti_agent_rail_placement=case-distinct-placement",
|
|
AgentRailPlacementEnvironmentVariable + "=duplicate",
|
|
"TAIL=value",
|
|
}, "/workspace/app/pkg", &RailPlacement{
|
|
Version: 1, Kind: RailPlacementKindProject, ProjectPath: "/workspace/app",
|
|
SectionKey: "project:/ignored",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("withAgentRailPlacementEnvironment() error = %v", err)
|
|
}
|
|
if got := countEnvironmentKey(env, AgentCWDEnvironmentVariable); got != 1 {
|
|
t.Fatalf("cwd environment count = %d, env=%#v", got, env)
|
|
}
|
|
if got := countEnvironmentKey(env, AgentRailPlacementEnvironmentVariable); got != 1 {
|
|
t.Fatalf("rail environment count = %d, env=%#v", got, env)
|
|
}
|
|
cwd, _ := testEnvironmentValue(env, AgentCWDEnvironmentVariable)
|
|
if cwd != "/workspace/app/pkg" {
|
|
t.Fatalf("cwd environment = %q", cwd)
|
|
}
|
|
encoded, _ := testEnvironmentValue(env, AgentRailPlacementEnvironmentVariable)
|
|
placement, err := ParseAgentRailPlacementEnvironment(encoded)
|
|
if err != nil {
|
|
t.Fatalf("ParseAgentRailPlacementEnvironment() error = %v", err)
|
|
}
|
|
if placement.Kind != RailPlacementKindProject ||
|
|
placement.ProjectPath != storesqlite.NormalizeProjectPath("/workspace/app") ||
|
|
placement.SectionKey != storesqlite.RailSectionKeyForProject("/workspace/app") {
|
|
t.Fatalf("parsed placement = %#v", placement)
|
|
}
|
|
if keep, _ := testEnvironmentValue(env, "KEEP"); keep != "value" {
|
|
t.Fatalf("unrelated environment was not preserved: %#v", env)
|
|
}
|
|
want := []string{
|
|
"KEEP=value",
|
|
"tutti_agent_cwd=/case-distinct-cwd",
|
|
"tutti_agent_rail_placement=case-distinct-placement",
|
|
"TAIL=value",
|
|
AgentCWDEnvironmentVariable + "=/workspace/app/pkg",
|
|
AgentRailPlacementEnvironmentVariable + "=" + encoded,
|
|
}
|
|
if !reflect.DeepEqual(env, want) {
|
|
t.Fatalf("environment = %#v, want canonical values last after %#v", env, want)
|
|
}
|
|
}
|
|
|
|
func TestParseAgentRailPlacementEnvironmentRejectsUntrustedShapes(t *testing.T) {
|
|
for _, value := range []string{
|
|
"",
|
|
`{"version":2,"kind":"conversations","sectionKey":"conversations"}`,
|
|
`{"version":1,"kind":"conversations","sectionKey":"conversations","unknown":true}`,
|
|
`{"version":1,"kind":"conversations","sectionKey":"conversations"} {}`,
|
|
} {
|
|
if placement, err := ParseAgentRailPlacementEnvironment(value); err == nil {
|
|
t.Fatalf("ParseAgentRailPlacementEnvironment(%q) = %#v, want error", value, placement)
|
|
}
|
|
}
|
|
}
|
|
|
|
func countEnvironmentKey(env []string, key string) int {
|
|
count := 0
|
|
for _, entry := range env {
|
|
if strings.HasPrefix(entry, key+"=") {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func testEnvironmentValue(env []string, key string) (string, bool) {
|
|
prefix := key + "="
|
|
for _, entry := range env {
|
|
if strings.HasPrefix(entry, prefix) {
|
|
return strings.TrimPrefix(entry, prefix), true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|