返回 DeepSeek-Reasonix
machine_identity_test.go
根目录 / internal / cli / machine_identity_test.go
1 package cli
2
3 import (
4 "bytes"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "runtime"
9 "strings"
10 "sync"
11 "testing"
12 )
13
14 func installMachineTestIdentity(t *testing.T) []byte {
15 t.Helper()
16 root := t.TempDir()
17 t.Setenv("REASONIX_HOME", root)
18 t.Setenv("REASONIX_STATE_HOME", "")
19 key := bytes.Repeat([]byte{0x5a}, machineIdentityKeyBytes)
20 if err := os.WriteFile(filepath.Join(root, machineIdentityKeyFile), key, 0o600); err != nil {
21 t.Fatalf("write machine identity key: %v", err)
22 }
23 return key
24 }
25
26 func TestMachineIdentityKeyInitializesOnceAcrossConcurrentReaders(t *testing.T) {
27 root := t.TempDir()
28 t.Setenv("REASONIX_HOME", root)
29 t.Setenv("REASONIX_STATE_HOME", "")
30
31 type result struct {
32 key []byte
33 err error
34 }
35 const readers = 16
36 results := make(chan result, readers)
37 var wg sync.WaitGroup
38 for range readers {
39 wg.Go(func() {
40 key, err := loadMachineIdentityKey()
41 results <- result{key: key, err: err}
42 })
43 }
44 wg.Wait()
45 close(results)
46
47 var want []byte
48 for result := range results {
49 if result.err != nil {
50 t.Fatalf("load machine identity key: %v", result.err)
51 }
52 if want == nil {
53 want = result.key
54 continue
55 }
56 if !bytes.Equal(result.key, want) {
57 t.Fatalf("concurrent readers observed different identity keys")
58 }
59 }
60 if len(want) != machineIdentityKeyBytes {
61 t.Fatalf("identity key length = %d, want %d", len(want), machineIdentityKeyBytes)
62 }
63 info, err := os.Stat(filepath.Join(root, machineIdentityKeyFile))
64 if err != nil {
65 t.Fatalf("stat machine identity key: %v", err)
66 }
67 if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 {
68 t.Fatalf("identity key permissions = %o, want 600", info.Mode().Perm())
69 }
70 }
71
72 func TestMachineIdentityKeyCorruptionFailsClosed(t *testing.T) {
73 root := t.TempDir()
74 t.Setenv("REASONIX_HOME", root)
75 t.Setenv("REASONIX_STATE_HOME", "")
76 path := filepath.Join(root, machineIdentityKeyFile)
77 if err := os.WriteFile(path, []byte("corrupt"), 0o600); err != nil {
78 t.Fatal(err)
79 }
80 if _, err := loadMachineIdentityKey(); err == nil {
81 t.Fatal("corrupt identity key was silently accepted or rotated")
82 }
83
84 var out bytes.Buffer
85 if code := runSessionCommand([]string{"list", "--json", "--dir", t.TempDir()}, &out); code != 1 {
86 t.Fatalf("exit code = %d, output = %s", code, out.String())
87 }
88 var response machineErrorResponse
89 if err := json.Unmarshal(out.Bytes(), &response); err != nil {
90 t.Fatalf("decode machine error: %v", err)
91 }
92 if response.Error.Code != "machine_identity_unavailable" || strings.Contains(out.String(), root) {
93 t.Fatalf("machine error leaked identity details: %+v", response)
94 }
95 body, err := os.ReadFile(path)
96 if err != nil {
97 t.Fatal(err)
98 }
99 if string(body) != "corrupt" {
100 t.Fatalf("corrupt identity key was silently replaced: %q", body)
101 }
102 }
103
104 func TestEventsJSONLRejectsCorruptIdentityBeforeRuntimeSetup(t *testing.T) {
105 root := t.TempDir()
106 t.Setenv("REASONIX_HOME", root)
107 t.Setenv("REASONIX_STATE_HOME", "")
108 if err := os.WriteFile(filepath.Join(root, machineIdentityKeyFile), []byte("corrupt"), 0o600); err != nil {
109 t.Fatal(err)
110 }
111 var code int
112 stderr := captureStderr(t, func() {
113 code = runAgent([]string{"--events-jsonl", "do not start a provider run"}, "dev")
114 })
115 if code != 1 || !strings.Contains(stderr, "machine identity is unavailable") {
116 t.Fatalf("run exit=%d stderr=%q", code, stderr)
117 }
118 if strings.Contains(stderr, root) {
119 t.Fatalf("run error leaked identity path: %q", stderr)
120 }
121 }
122
122 lines GO