175 lines
5.4 KiB
Go
175 lines
5.4 KiB
Go
package workspaceruntime
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"inbox/internal/domain/lane"
|
|
"inbox/internal/domain/workspace"
|
|
)
|
|
|
|
func TestDriftedWhenRunnerPortIsMissing(t *testing.T) {
|
|
ws := workspace.Workspace{
|
|
ID: "ws_1",
|
|
RootPath: "/tmp/ws",
|
|
}
|
|
item := lane.Record{
|
|
ID: "chain_1",
|
|
ContainerName: "lane-demo",
|
|
WorktreePath: "/tmp/ws-chain",
|
|
}
|
|
|
|
runtime := &containerRuntime{serverPort: 3000}
|
|
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
|
|
delete(info.NetworkSettings.Ports, runnerContainerPort)
|
|
|
|
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
|
|
t.Fatalf("expected container without %s mapping to drift", runnerContainerPort)
|
|
}
|
|
}
|
|
|
|
func TestDriftedWhenRunnerPortIsPublished(t *testing.T) {
|
|
ws := workspace.Workspace{
|
|
ID: "ws_1",
|
|
RootPath: "/tmp/ws",
|
|
}
|
|
item := lane.Record{
|
|
ID: "chain_1",
|
|
ContainerName: "lane-demo",
|
|
WorktreePath: "/tmp/ws-chain",
|
|
}
|
|
|
|
runtime := &containerRuntime{serverPort: 3000}
|
|
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
|
|
|
|
if runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
|
|
t.Fatalf("expected container with %s mapping to remain reusable", runnerContainerPort)
|
|
}
|
|
}
|
|
|
|
func TestDriftedWhenContainerUserIsNotRoot(t *testing.T) {
|
|
ws := workspace.Workspace{ID: "ws_1", RootPath: "/tmp/ws"}
|
|
item := lane.Record{
|
|
ID: "chain_1",
|
|
ContainerName: "lane-demo",
|
|
WorktreePath: "/tmp/ws-chain",
|
|
}
|
|
|
|
runtime := &containerRuntime{serverPort: 3000}
|
|
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
|
|
info.Config.User = "runner"
|
|
|
|
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1") {
|
|
t.Fatalf("expected non-root container user to drift")
|
|
}
|
|
}
|
|
|
|
func TestDriftedWhenRuntimeCodexFingerprintChanges(t *testing.T) {
|
|
ws := workspace.Workspace{ID: "ws_1", RootPath: "/tmp/ws"}
|
|
item := lane.Record{
|
|
ID: "chain_1",
|
|
ContainerName: "lane-demo",
|
|
WorktreePath: "/tmp/ws-chain",
|
|
}
|
|
|
|
runtime := &containerRuntime{serverPort: 3000}
|
|
info := validInspect(ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-1")
|
|
|
|
if !runtime.laneDrifted(info, ws, item, "/tmp/lane-worker", "/tmp/inbox", "codex-sha-2") {
|
|
t.Fatalf("expected runtime codex fingerprint mismatch to drift")
|
|
}
|
|
}
|
|
|
|
func TestRuntimeCodexFingerprint(t *testing.T) {
|
|
root := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(root, "config.toml"), []byte("model = \"gpt-5.3-codex\"\n"), 0644); err != nil {
|
|
t.Fatalf("WriteFile(config.toml) error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "auth.json"), []byte("{\"OPENAI_API_KEY\":\"token\"}\n"), 0600); err != nil {
|
|
t.Fatalf("WriteFile(auth.json) error = %v", err)
|
|
}
|
|
|
|
sum1, err := runtimeCodexFingerprint(root)
|
|
if err != nil {
|
|
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(filepath.Join(root, "auth.json"), []byte("{\"OPENAI_API_KEY\":\"changed\"}\n"), 0600); err != nil {
|
|
t.Fatalf("WriteFile(auth.json) error = %v", err)
|
|
}
|
|
sum2, err := runtimeCodexFingerprint(root)
|
|
if err != nil {
|
|
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
|
|
}
|
|
|
|
if sum1 == "" || sum2 == "" {
|
|
t.Fatalf("expected non-empty fingerprints, got %q and %q", sum1, sum2)
|
|
}
|
|
if sum1 == sum2 {
|
|
t.Fatalf("expected fingerprint to change when runtime codex contents change")
|
|
}
|
|
}
|
|
|
|
func TestRuntimeCodexFingerprintChangesForNestedSkillFiles(t *testing.T) {
|
|
root := t.TempDir()
|
|
skillDir := filepath.Join(root, "skills", "inbox")
|
|
if err := os.MkdirAll(skillDir, 0755); err != nil {
|
|
t.Fatalf("MkdirAll(skillDir) error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Inbox\n"), 0644); err != nil {
|
|
t.Fatalf("WriteFile(SKILL.md) error = %v", err)
|
|
}
|
|
|
|
sum1, err := runtimeCodexFingerprint(root)
|
|
if err != nil {
|
|
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# Inbox\n\nUpdated\n"), 0644); err != nil {
|
|
t.Fatalf("WriteFile(SKILL.md) error = %v", err)
|
|
}
|
|
sum2, err := runtimeCodexFingerprint(root)
|
|
if err != nil {
|
|
t.Fatalf("runtimeCodexFingerprint() error = %v", err)
|
|
}
|
|
if sum1 == sum2 {
|
|
t.Fatalf("expected nested skill file change to affect fingerprint")
|
|
}
|
|
}
|
|
|
|
func validInspect(ws workspace.Workspace, item lane.Record, workerBinary, inboxBinary, codexFingerprint string) podmanInspect {
|
|
var info podmanInspect
|
|
info.ImageName = runtimeBaseImage
|
|
info.Path = laneWorkerContainerPath
|
|
info.Config.Image = runtimeBaseImage
|
|
info.Config.User = "root"
|
|
info.Config.WorkingDir = "/workspace"
|
|
info.Config.Env = []string{
|
|
"INBOX_WORKSPACE=/workspace",
|
|
"INBOX_WORKSPACE_ID=" + ws.ID,
|
|
"INBOX_LANE_ID=" + item.ID,
|
|
"INBOX_RUNTIME_AGENT_ID=" + item.ContainerName,
|
|
"INBOX_API_URL=http://host.containers.internal:3000",
|
|
"HOME=/root",
|
|
"INBOX_RUNTIME_CODEX_SHA=" + codexFingerprint,
|
|
}
|
|
info.NetworkSettings.Ports = map[string][]struct {
|
|
HostIP string `json:"HostIp"`
|
|
HostPort string `json:"HostPort"`
|
|
}{
|
|
runnerContainerPort: {{
|
|
HostIP: "127.0.0.1",
|
|
HostPort: "40123",
|
|
}},
|
|
}
|
|
info.Mounts = []struct {
|
|
Source string `json:"Source"`
|
|
Destination string `json:"Destination"`
|
|
}{
|
|
{Source: item.WorktreePath, Destination: "/workspace"},
|
|
{Source: workerBinary, Destination: laneWorkerContainerPath},
|
|
{Source: inboxBinary, Destination: inboxContainerPath},
|
|
}
|
|
return info
|
|
}
|