返回 DeepSeek-Reasonix
session_errors_test.go
根目录 / desktop / session_errors_test.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "runtime"
9 "strings"
10 "syscall"
11 "testing"
12
13 "reasonix/internal/agent"
14 )
15
16 // Platform errnos for the blocked-file classes. Windows values follow the
17 // isRenameCrossDeviceOrBusy convention of naming the raw code inline.
18 func blockedFileErrnos(t *testing.T) (inUse, accessDenied, diskFull syscall.Errno) {
19 t.Helper()
20 if runtime.GOOS == "windows" {
21 return 32, 5, 112 // ERROR_SHARING_VIOLATION, ERROR_ACCESS_DENIED, ERROR_DISK_FULL
22 }
23 return syscall.EBUSY, syscall.EACCES, syscall.ENOSPC
24 }
25
26 func TestFriendlySessionFileErrorMapsBlockedFileErrors(t *testing.T) {
27 inUse, accessDenied, diskFull := blockedFileErrnos(t)
28 secret := "/very/secret/session.jsonl"
29
30 cases := []struct {
31 name string
32 err error
33 want error
34 }{
35 {
36 name: "rename sharing violation",
37 err: &os.LinkError{Op: "rename", Old: secret, New: secret + ".trash", Err: inUse},
38 want: errSessionFileLocked,
39 },
40 {
41 name: "remove access denied",
42 err: &os.PathError{Op: "remove", Path: secret, Err: accessDenied},
43 want: errSessionFileAccessDenied,
44 },
45 {
46 name: "write disk full",
47 err: &os.PathError{Op: "write", Path: secret, Err: diskFull},
48 want: errSessionDiskFull,
49 },
50 }
51 for _, tc := range cases {
52 t.Run(tc.name, func(t *testing.T) {
53 got := friendlySessionFileError(tc.err)
54 if got != tc.want {
55 t.Fatalf("friendlySessionFileError() = %v, want %v", got, tc.want)
56 }
57 if strings.Contains(got.Error(), secret) {
58 t.Fatalf("sanitized error leaks the path: %v", got)
59 }
60 })
61 }
62 }
63
64 func TestFriendlySessionFileErrorPassesThroughOtherErrors(t *testing.T) {
65 if err := friendlySessionFileError(nil); err != nil {
66 t.Fatalf("nil should stay nil, got %v", err)
67 }
68 plain := errors.New("plain failure")
69 if got := friendlySessionFileError(plain); got != plain {
70 t.Fatalf("plain error rewritten to %v", got)
71 }
72 if got := friendlySessionFileError(errSessionBusyElsewhere); got != errSessionBusyElsewhere {
73 t.Fatalf("sanitized busy error rewritten to %v", got)
74 }
75 notExist := &os.PathError{Op: "lstat", Path: "gone.jsonl", Err: syscall.ENOENT}
76 if got := friendlySessionFileError(notExist); got != error(notExist) {
77 t.Fatalf("not-exist error rewritten to %v", got)
78 }
79 }
80
81 func TestFriendlySessionLoadErrorPreservesBudgetAndSanitizesDecoderDetails(t *testing.T) {
82 path := filepath.Join(t.TempDir(), "private-session.jsonl")
83 limitErr := &agent.SessionReplayLimitError{
84 Path: path, Resource: "encoded_bytes", Value: 200, Limit: 100,
85 }
86 if got := friendlySessionLoadError(limitErr); got != limitErr {
87 t.Fatalf("budget error = %v, want original path-free error", got)
88 }
89 if strings.Contains(limitErr.Error(), path) {
90 t.Fatalf("budget error leaked path: %q", limitErr)
91 }
92
93 decodeErr := fmt.Errorf("decode %s: malformed event", path)
94 got := friendlySessionLoadError(decodeErr)
95 if !errors.Is(got, errSessionHistoryUnreadable) {
96 t.Fatalf("decoder error = %v, want errSessionHistoryUnreadable", got)
97 }
98 if strings.Contains(got.Error(), path) {
99 t.Fatalf("sanitized decoder error leaked path: %q", got)
100 }
101 }
102
102 lines GO