返回 DeepSeek-Reasonix
historical_import_failure_test.go
根目录 / desktop / historical_import_failure_test.go
1 package main
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11
12 "reasonix/desktop/internal/workspacestate"
13 "reasonix/internal/agent"
14 "reasonix/internal/provider"
15 "reasonix/internal/session"
16 )
17
18 func TestHistoricalPreparationFailureCarriesItsCause(t *testing.T) {
19 isolateDesktopUserDirs(t)
20 base, err := filepath.EvalSymlinks(t.TempDir())
21 if err != nil {
22 t.Fatal(err)
23 }
24 root := filepath.Join(base, "project")
25 if err := os.MkdirAll(root, 0o700); err != nil {
26 t.Fatal(err)
27 }
28 if err := addProject(root, ""); err != nil {
29 t.Fatal(err)
30 }
31 dir := desktopSessionDir(root)
32 path := filepath.Join(dir, "20260801-101010-deepseek.jsonl")
33 legacy := agent.NewSession("system")
34 legacy.Add(provider.Message{ID: "q", Role: provider.RoleUser, Content: "question"})
35 if err := legacy.Save(path); err != nil {
36 t.Fatal(err)
37 }
38 elsewhere := filepath.Join(base, "elsewhere")
39 if err := agent.SaveBranchMetaPreserveUpdated(path, agent.BranchMeta{Scope: "project", WorkspaceRoot: elsewhere}); err != nil {
40 t.Fatal(err)
41 }
42 app := newHistoricalLifecycleApp(t)
43 installSessionCatalogForTest(t, app, dir, "project", root)
44 page, err := app.ListProjectTopics(ProjectTopicPageRequest{Scope: "project", WorkspaceRoot: root, Limit: 50})
45 if err != nil || len(page.Items) != 1 || page.Items[0].Source == nil {
46 t.Fatalf("historical source missing: %+v %v", page, err)
47 }
48 view, err := app.PrepareSession(SessionSelector{Source: page.Items[0].Source})
49 if err != nil {
50 t.Fatal(err)
51 }
52 deadline := time.Now().Add(20 * time.Second)
53 for !terminalPreparationStatus(view.Status) && time.Now().Before(deadline) {
54 time.Sleep(20 * time.Millisecond)
55 if view, err = app.GetSessionPreparation(view.OperationID); err != nil {
56 t.Fatal(err)
57 }
58 }
59 if view.Status != "failed" || view.ErrorCode != "workspace_conflict" {
60 t.Fatalf("preparation must name the workspace conflict, got %+v", view)
61 }
62 if !strings.Contains(view.ErrorDetail, errSessionWorkspaceConflict.Error()) {
63 t.Fatalf("preparation must carry the failure detail, got %q", view.ErrorDetail)
64 }
65 status := app.GetHistoricalImportStatus()
66 var listed *HistoricalSessionView
67 for i := range status.Items {
68 if status.Items[i].ID == view.SourceKey {
69 listed = &status.Items[i]
70 }
71 }
72 if listed == nil || listed.ErrorCode != "workspace_conflict" || listed.ErrorDetail != view.ErrorDetail {
73 t.Fatalf("recovery list must show the same cause, got %+v", listed)
74 }
75 }
76
77 func terminalPreparationStatus(status string) bool {
78 return status == "ready" || status == "failed" || status == "blocked" || status == "cancelled"
79 }
80
81 func TestHistoricalImportFailureCodeReadsIdentity(t *testing.T) {
82 for _, tc := range []struct {
83 err error
84 want string
85 }{
86 {fmt.Errorf("wrap: %w", errSessionWorkspaceConflict), "workspace_conflict"},
87 {fmt.Errorf("replay: %w", agent.ErrSessionReplayLimitExceeded), "history_too_large"},
88 {errors.Join(errors.New("ledger"), agent.ErrSessionHistoryDamaged), "history_damaged"},
89 {fmt.Errorf("open: %w", session.ErrDamagedStore), "history_damaged"},
90 {fmt.Errorf("open: %w", session.ErrUnsupportedVersion), "unsupported_version"},
91 {fmt.Errorf("resolve: %w", workspacestate.ErrMutationConflict), "state_conflict"},
92 {newSessionOperationError("target_changed", "changed"), "target_changed"},
93 {fmt.Errorf("lease: %w", session.ErrWriterOwned), "source_busy"},
94 {errors.New("disk full"), "import_failed"},
95 } {
96 if got := historicalImportFailureCode(tc.err); got != tc.want {
97 t.Errorf("%v: code %q, want %q", tc.err, got, tc.want)
98 }
99 }
100 long := strings.Repeat("历", historicalFailureDetailLimit)
101 detail := historicalImportFailureDetail(errors.New(long))
102 if len(detail) > historicalFailureDetailLimit+len("…") || !strings.HasSuffix(detail, "…") || !strings.HasPrefix(long, strings.TrimSuffix(detail, "…")) {
103 t.Fatalf("detail not bounded on a rune boundary: %d bytes", len(detail))
104 }
105 }
106
106 lines GO