返回 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 i := 0; i < readers; i++ {
39 wg.Add(1)
40 go func() {
41 defer wg.Done()
42 key, err := loadMachineIdentityKey()
43 results <- result{key: key, err: err}
44 }()
45 }
46 wg.Wait()
47 close(results)
48
49 var want []byte
50 for result := range results {
51 if result.err != nil {
52 t.Fatalf("load machine identity key: %v", result.err)
53 }
54 if want == nil {
55 want = result.key
56 continue
57 }
58 if !bytes.Equal(result.key, want) {
59 t.Fatalf("concurrent readers observed different identity keys")
60 }
61 }
62 if len(want) != machineIdentityKeyBytes {
63 t.Fatalf("identity key length = %d, want %d", len(want), machineIdentityKeyBytes)
64 }
65 info, err := os.Stat(filepath.Join(root, machineIdentityKeyFile))
66 if err != nil {
67 t.Fatalf("stat machine identity key: %v", err)
68 }
69 if runtime.GOOS != "windows" && info.Mode().Perm() != 0o600 {
70 t.Fatalf("identity key permissions = %o, want 600", info.Mode().Perm())
71 }
72 }
73
74 func TestMachineIdentityKeyCorruptionFailsClosed(t *testing.T) {
75 root := t.TempDir()
76 t.Setenv("REASONIX_HOME", root)
77 t.Setenv("REASONIX_STATE_HOME", "")
78 path := filepath.Join(root, machineIdentityKeyFile)
79 if err := os.WriteFile(path, []byte("corrupt"), 0o600); err != nil {
80 t.Fatal(err)
81 }
82 if _, err := loadMachineIdentityKey(); err == nil {
83 t.Fatal("corrupt identity key was silently accepted or rotated")
84 }
85
86 var out bytes.Buffer
87 if code := runSessionCommand([]string{"list", "--json", "--dir", t.TempDir()}, &out); code != 1 {
88 t.Fatalf("exit code = %d, output = %s", code, out.String())
89 }
90 var response machineErrorResponse
91 if err := json.Unmarshal(out.Bytes(), &response); err != nil {
92 t.Fatalf("decode machine error: %v", err)
93 }
94 if response.Error.Code != "machine_identity_unavailable" || strings.Contains(out.String(), root) {
95 t.Fatalf("machine error leaked identity details: %+v", response)
96 }
97 body, err := os.ReadFile(path)
98 if err != nil {
99 t.Fatal(err)
100 }
101 if string(body) != "corrupt" {
102 t.Fatalf("corrupt identity key was silently replaced: %q", body)
103 }
104 }
105
106 func TestEventsJSONLRejectsCorruptIdentityBeforeRuntimeSetup(t *testing.T) {
107 root := t.TempDir()
108 t.Setenv("REASONIX_HOME", root)
109 t.Setenv("REASONIX_STATE_HOME", "")
110 if err := os.WriteFile(filepath.Join(root, machineIdentityKeyFile), []byte("corrupt"), 0o600); err != nil {
111 t.Fatal(err)
112 }
113 var code int
114 stderr := captureStderr(t, func() {
115 code = runAgent([]string{"--events-jsonl", "do not start a provider run"}, "dev")
116 })
117 if code != 1 || !strings.Contains(stderr, "machine identity is unavailable") {
118 t.Fatalf("run exit=%d stderr=%q", code, stderr)
119 }
120 if strings.Contains(stderr, root) {
121 t.Fatalf("run error leaked identity path: %q", stderr)
122 }
123 }
124
124 lines GO