返回 DeepSeek-Reasonix
maintenance_test.go
根目录 / internal / session / maintenance_test.go
1 package session
2
3 import (
4 "errors"
5 "os"
6 "path/filepath"
7 "testing"
8
9 "reasonix/internal/filelock"
10 )
11
12 func TestPurgeOwnershipAndFailureAtomicity(t *testing.T) {
13 root := t.TempDir()
14 p := NewFilesystemPersistence(root)
15 dir := filepath.Join(root, "victim")
16 if err := os.Mkdir(dir, 0700); err != nil {
17 t.Fatal(err)
18 }
19 body := filepath.Join(dir, "body")
20 if err := os.WriteFile(body, []byte("history"), 0600); err != nil {
21 t.Fatal(err)
22 }
23 release, err := acquireSessionWriter(dir)
24 if err != nil {
25 t.Fatal(err)
26 }
27 called := false
28 if err := p.PurgeWithTombstone(t.Context(), "victim", func() error { called = true; return nil }); !errors.Is(err, filelock.ErrHeld) || called {
29 t.Fatalf("external owner bypassed: %v %v", err, called)
30 }
31 release()
32 failed := errors.New("registry unavailable")
33 if err := p.PurgeWithTombstone(t.Context(), "victim", func() error { return failed }); !errors.Is(err, failed) {
34 t.Fatal(err)
35 }
36 if bytes, err := os.ReadFile(body); err != nil || string(bytes) != "history" {
37 t.Fatalf("prepare failure changed history: %s %v", bytes, err)
38 }
39 if err := p.PurgeWithTombstone(t.Context(), "victim", func() error {
40 if release, err := acquireSessionWriter(dir); err == nil {
41 release()
42 return errors.New("writer entered purge")
43 }
44 return nil
45 }); err != nil {
46 t.Fatal(err)
47 }
48 if _, err := os.Stat(dir); !os.IsNotExist(err) {
49 t.Fatalf("purge left body: %v", err)
50 }
51 if err := p.PurgeWithTombstone(t.Context(), "victim", func() error { return nil }); err != nil {
52 t.Fatalf("replay: %v", err)
53 }
54 }
55
56 func TestPurgeRejectsSymlinkAndStagingCollision(t *testing.T) {
57 for _, scenario := range []string{"symlink", "collision", "unowned-staging"} {
58 t.Run(scenario, func(t *testing.T) {
59 root := t.TempDir()
60 p := NewFilesystemPersistence(root)
61 outside := t.TempDir()
62 if err := os.WriteFile(filepath.Join(outside, "keep"), []byte("keep"), 0600); err != nil {
63 t.Fatal(err)
64 }
65 dir := filepath.Join(root, "victim")
66 if scenario == "symlink" {
67 if err := os.Symlink(outside, dir); err != nil {
68 t.Fatal(err)
69 }
70 } else {
71 if err := os.Mkdir(dir, 0700); err != nil {
72 t.Fatal(err)
73 }
74 if scenario == "unowned-staging" {
75 if err := os.Remove(dir); err != nil {
76 t.Fatal(err)
77 }
78 }
79 if err := os.MkdirAll(filepath.Join(root, ".purging", "victim"), 0700); err != nil {
80 t.Fatal(err)
81 }
82 }
83 called := false
84 if err := p.PurgeWithTombstone(t.Context(), "victim", func() error { called = true; return nil }); err == nil || called {
85 t.Fatalf("unsafe purge accepted: %v", err)
86 }
87 if _, err := os.Stat(filepath.Join(outside, "keep")); err != nil {
88 t.Fatal("outside data removed")
89 }
90 })
91 }
92 }
93
93 lines GO