157 lines
4.5 KiB
Go
157 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"agent-system-backend/internal/store"
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func accountCLI(t *testing.T, args ...string) (int, string) {
|
|
t.Helper()
|
|
out, err := os.CreateTemp(t.TempDir(), "capture-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer out.Close()
|
|
oldOut, oldErr := os.Stdout, os.Stderr
|
|
os.Stdout = out
|
|
os.Stderr = out
|
|
defer func() { os.Stdout = oldOut; os.Stderr = oldErr }()
|
|
rc := runUser(args)
|
|
out.Seek(0, 0)
|
|
raw, err := io.ReadAll(out)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return rc, string(raw)
|
|
}
|
|
func cliStore(t *testing.T) (string, *store.Store) {
|
|
t.Helper()
|
|
t.Setenv("AGENT_SYSTEM_DSN", "")
|
|
dir := t.TempDir()
|
|
s, e := store.OpenSQLite(filepath.Join(dir, "agent-system.db"))
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
t.Cleanup(func() { s.Close() })
|
|
if e := s.EnsureSystemIdentity("fixture", "sqlite"); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
return dir, s
|
|
}
|
|
func TestCreateAndInitializeAccountDoNotRequireCredentialOutput(t *testing.T) {
|
|
for _, sub := range []string{"create", "initialize-account"} {
|
|
t.Run(sub, func(t *testing.T) {
|
|
dir, s := cliStore(t)
|
|
if _, e := s.BootstrapFirstAdminWrite(store.AccountWrite{Owner: "root", Name: "Root", Actor: "test"}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
args := []string{sub, "--data", dir, "--owner", "ada"}
|
|
if sub == "initialize-account" {
|
|
a, e := s.CreatePendingAccount("root", "ada", store.AccountProfile{Name: "Ada"})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
args = append(args, "--user-id", a.ID, "--expected-state-version", "1", "--expected-generation", "0")
|
|
}
|
|
rc, _ := accountCLI(t, args...)
|
|
if rc != 0 {
|
|
t.Fatalf("command failed without credential output file: %d", rc)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
func TestExistingOutputAndConflictingFormatsRefuseBeforeMutation(t *testing.T) {
|
|
for _, kind := range []string{"existing", "conflicting"} {
|
|
t.Run(kind, func(t *testing.T) {
|
|
dir, s := cliStore(t)
|
|
out := filepath.Join(t.TempDir(), "secret")
|
|
args := []string{"create", "--data", dir, "--owner", "ada", "--out-file", out}
|
|
if kind == "existing" {
|
|
if e := os.WriteFile(out, []byte("existing"), 0600); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
} else {
|
|
args = append(args, "--out", filepath.Join(t.TempDir(), "json-secret"))
|
|
}
|
|
rc, _ := accountCLI(t, args...)
|
|
if rc == 0 {
|
|
t.Fatal("unsafe output selection succeeded")
|
|
}
|
|
if _, e := s.GetUser("ada"); e == nil {
|
|
t.Fatal("output refusal happened after account mutation")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAPIKeyCreateUsesExclusivePrivateOutput(t *testing.T) {
|
|
if dir := os.Getenv("U7_APIKEY_CHILD_DIR"); dir != "" {
|
|
rc := runAPIKey([]string{"create", "--data", dir, "--owner", "ada", "--name", "fixture", "--out-file", os.Getenv("U7_APIKEY_CHILD_OUT")})
|
|
if rc != 0 {
|
|
t.Fatal("apikey create rejected its private output destination")
|
|
}
|
|
return
|
|
}
|
|
for _, existing := range []bool{false, true} {
|
|
name := "new"
|
|
if existing {
|
|
name = "existing"
|
|
}
|
|
t.Run(name, func(t *testing.T) {
|
|
dir, st := cliStore(t)
|
|
if err := st.CreateAccount("ada", "Ada", "", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := filepath.Join(t.TempDir(), "credential")
|
|
if existing {
|
|
if err := os.WriteFile(out, []byte("preserve"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
cmd := exec.Command(os.Args[0], "-test.run=^TestAPIKeyCreateUsesExclusivePrivateOutput$")
|
|
cmd.Env = append(os.Environ(), "U7_APIKEY_CHILD_DIR="+dir, "U7_APIKEY_CHILD_OUT="+out)
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
err := cmd.Run()
|
|
if existing {
|
|
if err == nil {
|
|
t.Fatal("existing output was overwritten or mutation succeeded")
|
|
}
|
|
raw, readErr := os.ReadFile(out)
|
|
if readErr != nil || string(raw) != "preserve" {
|
|
t.Fatal("existing output was modified")
|
|
}
|
|
keys, listErr := st.ListAPIKeys("ada")
|
|
if listErr != nil || len(keys) != 0 {
|
|
t.Fatal("mutation happened before output preflight")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatal("new private output was rejected")
|
|
}
|
|
info, statErr := os.Stat(out)
|
|
if statErr != nil || info.Mode().Perm() != 0600 {
|
|
t.Fatal("credential output missing or not private")
|
|
}
|
|
raw, readErr := os.ReadFile(out)
|
|
if readErr != nil || len(raw) == 0 {
|
|
t.Fatal("credential output empty")
|
|
}
|
|
if bytes.Contains(stdout.Bytes(), bytes.TrimSpace(raw)) {
|
|
t.Fatal("credential was written to stdout")
|
|
}
|
|
keys, listErr := st.ListAPIKeys("ada")
|
|
if listErr != nil || len(keys) != 1 {
|
|
t.Fatal("successful issue was not committed")
|
|
}
|
|
})
|
|
}
|
|
}
|