test(goals): verify write rollback
- commit
- dd0b211768fb992ec8746a3d533ce238163049ab
- author
- brice · · 2026-08-14 06:40 UTC
- parent
- d395d30
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +27 -5
M packages/agent/cli_session_test.go +27 -5
| @@ -114,7 +114,9 @@ func TestToolResultPersistenceUsesBoundSession(t *testing.T) { | |||
| 114 | 114 | func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |
| 115 | 115 | const privateGoal = "private goal text" | |
| 116 | 116 | const privateMission = "private-mission-id" | |
| 117 | + | const privateReason = "private active reason" | |
| 117 | 118 | const privateSessionContent = "private session content" | |
| 119 | + | var writeFailureGoal core.SessionGoal | |
| 118 | 120 | ||
| 119 | 121 | newSession := func(t *testing.T) *core.Session { | |
| 120 | 122 | t.Helper() | |
| @@ -130,10 +132,11 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||
| 130 | 132 | } | |
| 131 | 133 | ||
| 132 | 134 | tests := []struct { | |
| 133 | - | name string | |
| 134 | - | setup func(*testing.T) *core.Session | |
| 135 | - | update tools.GoalUpdate | |
| 136 | - | message string | |
| 135 | + | name string | |
| 136 | + | setup func(*testing.T) *core.Session | |
| 137 | + | update tools.GoalUpdate | |
| 138 | + | message string | |
| 139 | + | assertUnchanged func(*testing.T, *core.Session) | |
| 137 | 140 | }{ | |
| 138 | 141 | { | |
| 139 | 142 | name: "active goal", | |
| @@ -175,9 +178,10 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||
| 175 | 178 | name: "write failure", | |
| 176 | 179 | setup: func(t *testing.T) *core.Session { | |
| 177 | 180 | sess := newSession(t) | |
| 178 | - | if err := sess.UpdateGoal(&core.SessionGoal{Objective: privateGoal, Status: core.GoalActive}); err != nil { | |
| 181 | + | if err := sess.UpdateGoal(&core.SessionGoal{Objective: privateGoal, Status: core.GoalActive, Reason: privateReason}); err != nil { | |
| 179 | 182 | t.Fatal(err) | |
| 180 | 183 | } | |
| 184 | + | writeFailureGoal = *sess.Meta.Goal | |
| 181 | 185 | if err := sess.Close(); err != nil { | |
| 182 | 186 | t.Fatal(err) | |
| 183 | 187 | } | |
| @@ -185,6 +189,21 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||
| 185 | 189 | }, | |
| 186 | 190 | update: tools.GoalUpdate{Status: core.GoalDone}, | |
| 187 | 191 | message: "goal state could not be saved", | |
| 192 | + | assertUnchanged: func(t *testing.T, sess *core.Session) { | |
| 193 | + | t.Helper() | |
| 194 | + | assertGoal := func(source string, goal *core.SessionGoal) { | |
| 195 | + | t.Helper() | |
| 196 | + | if goal == nil || goal.Status != writeFailureGoal.Status || goal.Objective != writeFailureGoal.Objective || goal.Reason != writeFailureGoal.Reason { | |
| 197 | + | t.Fatalf("%s goal after failed update = %#v, want status=%q objective=%q reason=%q", source, goal, writeFailureGoal.Status, writeFailureGoal.Objective, writeFailureGoal.Reason) | |
| 198 | + | } | |
| 199 | + | } | |
| 200 | + | assertGoal("in-memory", sess.Meta.Goal) | |
| 201 | + | snapshot, err := core.ReadSessionSnapshot(sess.Path) | |
| 202 | + | if err != nil { | |
| 203 | + | t.Fatal(err) | |
| 204 | + | } | |
| 205 | + | assertGoal("persisted", snapshot.Meta.Goal) | |
| 206 | + | }, | |
| 188 | 207 | }, | |
| 189 | 208 | } | |
| 190 | 209 | for _, test := range tests { | |
| @@ -201,6 +220,9 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||
| 201 | 220 | if strings.Contains(safe.Message, privateGoal) || strings.Contains(safe.Message, privateMission) || strings.Contains(safe.Message, privateSessionContent) || (sess != nil && strings.Contains(safe.Message, sess.Path)) { | |
| 202 | 221 | t.Fatalf("safe message leaked private state: %q", safe.Message) | |
| 203 | 222 | } | |
| 223 | + | if test.assertUnchanged != nil { | |
| 224 | + | test.assertUnchanged(t, sess) | |
| 225 | + | } | |
| 204 | 226 | }) | |
| 205 | 227 | } | |
| 206 | 228 | } | |
| @@ -114,7 +114,9 @@ func TestToolResultPersistenceUsesBoundSession(t *testing.T) { | |||||
| 114 | func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | 114 | func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | ||
| 115 | const privateGoal = "private goal text" | 115 | const privateGoal = "private goal text" | ||
| 116 | const privateMission = "private-mission-id" | 116 | const privateMission = "private-mission-id" | ||
| 117 | + | const privateReason = "private active reason" | |||
| 117 | const privateSessionContent = "private session content" | 118 | const privateSessionContent = "private session content" | ||
| 119 | + | var writeFailureGoal core.SessionGoal | |||
| 118 | 120 | ||||
| 119 | newSession := func(t *testing.T) *core.Session { | 121 | newSession := func(t *testing.T) *core.Session { | ||
| 120 | t.Helper() | 122 | t.Helper() | ||
| @@ -130,10 +132,11 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||||
| 130 | } | 132 | } | ||
| 131 | 133 | ||||
| 132 | tests := []struct { | 134 | tests := []struct { | ||
| 133 | - | name string | 135 | + | name string |
| 134 | - | setup func(*testing.T) *core.Session | 136 | + | setup func(*testing.T) *core.Session |
| 135 | - | update tools.GoalUpdate | 137 | + | update tools.GoalUpdate |
| 136 | - | message string | 138 | + | message string |
| 139 | + | assertUnchanged func(*testing.T, *core.Session) | |||
| 137 | }{ | 140 | }{ | ||
| 138 | { | 141 | { | ||
| 139 | name: "active goal", | 142 | name: "active goal", | ||
| @@ -175,9 +178,10 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||||
| 175 | name: "write failure", | 178 | name: "write failure", | ||
| 176 | setup: func(t *testing.T) *core.Session { | 179 | setup: func(t *testing.T) *core.Session { | ||
| 177 | sess := newSession(t) | 180 | sess := newSession(t) | ||
| 178 | - | if err := sess.UpdateGoal(&core.SessionGoal{Objective: privateGoal, Status: core.GoalActive}); err != nil { | 181 | + | if err := sess.UpdateGoal(&core.SessionGoal{Objective: privateGoal, Status: core.GoalActive, Reason: privateReason}); err != nil { |
| 179 | t.Fatal(err) | 182 | t.Fatal(err) | ||
| 180 | } | 183 | } | ||
| 184 | + | writeFailureGoal = *sess.Meta.Goal | |||
| 181 | if err := sess.Close(); err != nil { | 185 | if err := sess.Close(); err != nil { | ||
| 182 | t.Fatal(err) | 186 | t.Fatal(err) | ||
| 183 | } | 187 | } | ||
| @@ -185,6 +189,21 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||||
| 185 | }, | 189 | }, | ||
| 186 | update: tools.GoalUpdate{Status: core.GoalDone}, | 190 | update: tools.GoalUpdate{Status: core.GoalDone}, | ||
| 187 | message: "goal state could not be saved", | 191 | message: "goal state could not be saved", | ||
| 192 | + | assertUnchanged: func(t *testing.T, sess *core.Session) { | |||
| 193 | + | t.Helper() | |||
| 194 | + | assertGoal := func(source string, goal *core.SessionGoal) { | |||
| 195 | + | t.Helper() | |||
| 196 | + | if goal == nil || goal.Status != writeFailureGoal.Status || goal.Objective != writeFailureGoal.Objective || goal.Reason != writeFailureGoal.Reason { | |||
| 197 | + | t.Fatalf("%s goal after failed update = %#v, want status=%q objective=%q reason=%q", source, goal, writeFailureGoal.Status, writeFailureGoal.Objective, writeFailureGoal.Reason) | |||
| 198 | + | } | |||
| 199 | + | } | |||
| 200 | + | assertGoal("in-memory", sess.Meta.Goal) | |||
| 201 | + | snapshot, err := core.ReadSessionSnapshot(sess.Path) | |||
| 202 | + | if err != nil { | |||
| 203 | + | t.Fatal(err) | |||
| 204 | + | } | |||
| 205 | + | assertGoal("persisted", snapshot.Meta.Goal) | |||
| 206 | + | }, | |||
| 188 | }, | 207 | }, | ||
| 189 | } | 208 | } | ||
| 190 | for _, test := range tests { | 209 | for _, test := range tests { | ||
| @@ -201,6 +220,9 @@ func TestPersistToolResultStateUsesSafeGoalPersistenceCategories(t *testing.T) { | |||||
| 201 | if strings.Contains(safe.Message, privateGoal) || strings.Contains(safe.Message, privateMission) || strings.Contains(safe.Message, privateSessionContent) || (sess != nil && strings.Contains(safe.Message, sess.Path)) { | 220 | if strings.Contains(safe.Message, privateGoal) || strings.Contains(safe.Message, privateMission) || strings.Contains(safe.Message, privateSessionContent) || (sess != nil && strings.Contains(safe.Message, sess.Path)) { | ||
| 202 | t.Fatalf("safe message leaked private state: %q", safe.Message) | 221 | t.Fatalf("safe message leaked private state: %q", safe.Message) | ||
| 203 | } | 222 | } | ||
| 223 | + | if test.assertUnchanged != nil { | |||
| 224 | + | test.assertUnchanged(t, sess) | |||
| 225 | + | } | |||
| 204 | }) | 226 | }) | ||
| 205 | } | 227 | } | ||
| 206 | } | 228 | } | ||