返回 DeepSeek-Reasonix
store_test.go
根目录 / desktop / internal / sessionui / store_test.go
1 package sessionui
2
3 import (
4 "bytes"
5 "context"
6 "database/sql"
7 "encoding/json"
8 "errors"
9 "os"
10 "path/filepath"
11 "sync"
12 "testing"
13 )
14
15 func TestComposerCASAcrossStoresDoesNotCreateRecoveryCopies(t *testing.T) {
16 path := filepath.Join(t.TempDir(), "session-ui.sqlite")
17 a, b := New(path), New(path)
18 t.Cleanup(func() { _ = a.Close(); _ = b.Close() })
19 r, err := a.Save(t.Context(), "composer", "local:one", "0", json.RawMessage(`{"text":"seed","future":{"keep":true}}`))
20 if err != nil {
21 t.Fatal(err)
22 }
23 if _, err = b.Get(t.Context(), "composer", "local:one"); err != nil {
24 t.Fatal(err)
25 }
26 var wg sync.WaitGroup
27 failures := make(chan error, 2)
28 for _, s := range []*Store{a, b} {
29 wg.Add(1)
30 go func(s *Store) {
31 defer wg.Done()
32 _, err := s.Save(context.Background(), "composer", "local:one", r.Revision, json.RawMessage(`{"text":"edited"}`))
33 failures <- err
34 }(s)
35 }
36 wg.Wait()
37 close(failures)
38 conflicts := 0
39 for err := range failures {
40 if errors.Is(err, ErrConflict) {
41 conflicts++
42 } else if err != nil {
43 t.Fatal(err)
44 }
45 }
46 if conflicts != 1 {
47 t.Fatalf("conflicts=%d", conflicts)
48 }
49 var count int
50 if err := a.db.QueryRow(`SELECT COUNT(*) FROM conflicts`).Scan(&count); err != nil || count != 0 {
51 t.Fatalf("conflict copy count=%d err=%v", count, err)
52 }
53 _ = a.Close()
54 if _, err := a.Get(t.Context(), "composer", "local:one"); err == nil {
55 t.Fatal("closed store reopened after shutdown")
56 }
57 a = New(path)
58 defer a.Close()
59 r, err = a.Get(t.Context(), "composer", "local:one")
60 if err != nil || r.Revision != "2" {
61 t.Fatalf("restart=%+v %v", r, err)
62 }
63 }
64
65 func TestInputDatabasePathTreatsPunctuationAsFilename(t *testing.T) {
66 path := filepath.Join(t.TempDir(), "会话 #百分号%.sqlite")
67 store := New(path)
68 if _, err := store.Save(t.Context(), "composer", "local:one", "0", json.RawMessage(`{"text":"keep"}`)); err != nil {
69 t.Fatal(err)
70 }
71 if err := store.Close(); err != nil {
72 t.Fatal(err)
73 }
74 if _, err := os.Stat(path); err != nil {
75 t.Fatal(err)
76 }
77 reopened := New(path)
78 defer reopened.Close()
79 record, err := reopened.Get(t.Context(), "composer", "local:one")
80 if err != nil || string(record.Payload) != `{"text":"keep"}` {
81 t.Fatalf("wrong path reopened: %+v %v", record, err)
82 }
83 }
84
85 func TestFutureVersionIsBytePreserved(t *testing.T) {
86 path := filepath.Join(t.TempDir(), "future.sqlite")
87 db, err := sql.Open("sqlite", path)
88 if err != nil {
89 t.Fatal(err)
90 }
91 if _, err = db.Exec(`PRAGMA user_version=99`); err != nil {
92 t.Fatal(err)
93 }
94 _ = db.Close()
95 before, err := os.ReadFile(path)
96 if err != nil {
97 t.Fatal(err)
98 }
99 s := New(path)
100 defer s.Close()
101 if _, err = s.Get(t.Context(), "composer", "one"); !errors.Is(err, ErrFutureVersion) {
102 t.Fatal(err)
103 }
104 after, err := os.ReadFile(path)
105 if err != nil || !bytes.Equal(before, after) {
106 t.Fatal("future database changed", err)
107 }
108 }
109
109 lines GO