166 lines
4.8 KiB
Go
166 lines
4.8 KiB
Go
package runtimecodex
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"inbox/internal/app/runtimeconfig"
|
|
"inbox/internal/domain/role"
|
|
"inbox/internal/domain/skill"
|
|
)
|
|
|
|
type fakeRoleCatalog struct {
|
|
roles []role.Definition
|
|
}
|
|
|
|
func (f fakeRoleCatalog) ListRoles(_ context.Context) ([]role.Definition, error) {
|
|
return append([]role.Definition(nil), f.roles...), nil
|
|
}
|
|
|
|
type fakeRoleResolver struct {
|
|
resolved map[string]runtimeconfig.ResolvedRole
|
|
}
|
|
|
|
func (f fakeRoleResolver) ResolveRole(_ context.Context, _ string, roleName string) (runtimeconfig.ResolvedRole, error) {
|
|
return f.resolved[roleName], nil
|
|
}
|
|
|
|
func TestMaterializerSyncWritesRoleConfigs(t *testing.T) {
|
|
ctx := context.Background()
|
|
root := t.TempDir()
|
|
|
|
materializer := NewMaterializer(
|
|
fakeRoleCatalog{
|
|
roles: []role.Definition{
|
|
{Name: "worker", Title: "Worker", IsEnabled: true},
|
|
{Name: "disabled_role", Title: "Disabled", IsEnabled: false},
|
|
},
|
|
},
|
|
fakeRoleResolver{
|
|
resolved: map[string]runtimeconfig.ResolvedRole{
|
|
"worker": {
|
|
Role: role.Definition{Name: "worker", Title: "Worker", IsEnabled: true},
|
|
Config: role.Config{
|
|
RoleName: "worker",
|
|
ConfigTOML: strings.Join([]string{
|
|
`model = "gpt-5.2"`,
|
|
`model_provider = "custom"`,
|
|
``,
|
|
`[shell_environment_policy]`,
|
|
`inherit = "core"`,
|
|
`exclude = [ ]`,
|
|
`include_only = [ ]`,
|
|
`experimental_use_profile = false`,
|
|
``,
|
|
`[shell_environment_policy.set]`,
|
|
`XDG_CACHE_HOME = "/tmp/codex-xdg-cache"`,
|
|
``,
|
|
`[model_providers.custom]`,
|
|
`base_url = "http://example.test/v1"`,
|
|
`wire_api = "responses"`,
|
|
``,
|
|
`[mcp_servers.playwright]`,
|
|
`enabled = false`,
|
|
`command = "npx"`,
|
|
`args = [ "-y", "@playwright/mcp@latest" ]`,
|
|
`startup_timeout_sec = 90`,
|
|
``,
|
|
`[projects."/workspace"]`,
|
|
`trust_level = "trusted"`,
|
|
}, "\n"),
|
|
AuthJSON: `{"OPENAI_API_KEY":"token-1"}`,
|
|
},
|
|
Skills: []runtimeconfig.ResolvedSkill{
|
|
{
|
|
Skill: skill.Definition{
|
|
ID: "builtin-skill-inbox",
|
|
SkillKey: "inbox",
|
|
ContentMarkdown: "# Inbox\n\nUse inbox.",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
if err := materializer.Sync(ctx, "ws_1", root); err != nil {
|
|
t.Fatalf("Sync() error = %v", err)
|
|
}
|
|
|
|
configPath := filepath.Join(root, filepath.FromSlash(WorkspaceDir), "worker", ".codex", configFilename)
|
|
authPath := filepath.Join(root, filepath.FromSlash(WorkspaceDir), "worker", ".codex", authFilename)
|
|
configBody, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(config) error = %v", err)
|
|
}
|
|
authBody, err := os.ReadFile(authPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(auth) error = %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, filepath.FromSlash(WorkspaceDir), "disabled_role")); !os.IsNotExist(err) {
|
|
t.Fatalf("disabled role should not have runtime codex home")
|
|
}
|
|
|
|
configText := string(configBody)
|
|
for _, needle := range []string{
|
|
`model = "gpt-5.2"`,
|
|
`model_provider = "custom"`,
|
|
`[model_providers.custom]`,
|
|
`base_url = "http://example.test/v1"`,
|
|
`[mcp_servers.playwright]`,
|
|
`[shell_environment_policy.set]`,
|
|
`[projects."/workspace"]`,
|
|
} {
|
|
if !strings.Contains(configText, needle) {
|
|
t.Fatalf("expected config to contain %q, got:\n%s", needle, configText)
|
|
}
|
|
}
|
|
|
|
var auth map[string]string
|
|
if err := json.Unmarshal(authBody, &auth); err != nil {
|
|
t.Fatalf("json.Unmarshal(auth) error = %v", err)
|
|
}
|
|
if auth["OPENAI_API_KEY"] != "token-1" {
|
|
t.Fatalf("expected auth OPENAI_API_KEY to be written, got %#v", auth)
|
|
}
|
|
skillBody, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(WorkspaceDir), "worker", ".codex", "skills", "inbox", "SKILL.md"))
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(skill) error = %v", err)
|
|
}
|
|
if string(skillBody) != "# Inbox\n\nUse inbox." {
|
|
t.Fatalf("unexpected skill body %q", string(skillBody))
|
|
}
|
|
}
|
|
|
|
func TestWriteResolvedRoleHomeWritesOnlyResolvedConfig(t *testing.T) {
|
|
root := t.TempDir()
|
|
roleHome, err := WriteResolvedRoleHome(root, runtimeconfig.ResolvedRole{
|
|
WorkspaceID: "ws_1",
|
|
Role: role.Definition{Name: "leader", Title: "Leader", IsEnabled: true},
|
|
Config: role.Config{
|
|
RoleName: "leader",
|
|
ConfigTOML: "model = \"gpt-5.4\"",
|
|
AuthJSON: `{"OPENAI_API_KEY":"token-2"}`,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("WriteResolvedRoleHome() error = %v", err)
|
|
}
|
|
authBody, err := os.ReadFile(filepath.Join(roleHome, ".codex", authFilename))
|
|
if err != nil {
|
|
t.Fatalf("ReadFile(auth) error = %v", err)
|
|
}
|
|
var auth map[string]string
|
|
if err := json.Unmarshal(authBody, &auth); err != nil {
|
|
t.Fatalf("json.Unmarshal(auth) error = %v", err)
|
|
}
|
|
if auth["OPENAI_API_KEY"] != "token-2" {
|
|
t.Fatalf("expected auth OPENAI_API_KEY to be written, got %#v", auth)
|
|
}
|
|
}
|