| 1 | package workspacestate |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | func TestReadProjectionDoesNotCopyOrDiscardJournals(t *testing.T) { |
| 12 | state := newState() |
| 13 | state.Generation = 7 |
| 14 | state.Workspaces["w"] = Workspace{ID: "w", Title: "original", SessionIDs: []string{"s"}} |
| 15 | state.SessionStates["s"] = SessionState{Lifecycle: "active"} |
| 16 | state.PendingOperations["op"] = Operation{ID: "op", Lifecycle: "active", Kind: "import", Phase: "committed", Request: []byte(`{"large":"journal"}`)} |
| 17 | body, err := json.Marshal(state) |
| 18 | if err != nil { |
| 19 | t.Fatal(err) |
| 20 | } |
| 21 | path := filepath.Join(t.TempDir(), "state.json") |
| 22 | if err := os.WriteFile(path, body, 0600); err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | store := NewStore(path) |
| 26 | projection, err := store.LoadProjection(t.Context()) |
| 27 | if err != nil || projection.Generation != 7 || len(projection.PendingOperations) != 0 { |
| 28 | t.Fatalf("invalid display projection: %v", err) |
| 29 | } |
| 30 | projection.Workspaces["w"].SessionIDs[0] = "changed" |
| 31 | full, err := store.Load(t.Context()) |
| 32 | if err != nil || full.Workspaces["w"].SessionIDs[0] != "s" || len(full.PendingOperations) != 1 { |
| 33 | t.Fatalf("projection changed authoritative state: %v", err) |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | func TestReadCacheObservesReplacementWithoutGenerationOrTimestampChange(t *testing.T) { |
| 38 | path := filepath.Join(t.TempDir(), "state.json") |
| 39 | body := []byte(`{"version":3,"generation":7,"workspaceIds":["global"],"workspaces":{"global":{"id":"global","title":"first","sessionIds":[]}},"sessionStates":{}}`) |
| 40 | if err := os.WriteFile(path, body, 0600); err != nil { |
| 41 | t.Fatal(err) |
| 42 | } |
| 43 | store := NewStore(path) |
| 44 | if _, err := store.Load(t.Context()); err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | stat, err := os.Stat(path) |
| 48 | if err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | body = bytes.Replace(body, []byte("first"), []byte("other"), 1) |
| 52 | if err := os.WriteFile(path, body, 0600); err != nil { |
| 53 | t.Fatal(err) |
| 54 | } |
| 55 | if err := os.Chtimes(path, stat.ModTime(), stat.ModTime()); err != nil { |
| 56 | t.Fatal(err) |
| 57 | } |
| 58 | state, err := store.Load(t.Context()) |
| 59 | if err != nil || state.Workspaces["global"].Title != "other" { |
| 60 | t.Fatalf("stale cache: %v", err) |
| 61 | } |
| 62 | if err := os.WriteFile(path, []byte("{"), 0600); err != nil { |
| 63 | t.Fatal(err) |
| 64 | } |
| 65 | if _, err := store.Load(t.Context()); err == nil { |
| 66 | t.Fatal("cached state hid corruption") |
| 67 | } |
| 68 | if err := os.Remove(path); err != nil { |
| 69 | t.Fatal(err) |
| 70 | } |
| 71 | state, err = store.Load(t.Context()) |
| 72 | if err != nil || len(state.Workspaces) != 0 { |
| 73 | t.Fatal("cached state survived deletion") |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | func TestReadCacheIsolatesOperationAndUnknownFields(t *testing.T) { |
| 78 | state := newState() |
| 79 | state.PendingOperations["op"] = Operation{ID: "op", Request: []byte(`{"value":1}`), Result: []byte(`{"value":2}`), |
| 80 | SessionIDs: []string{"s"}, Dependencies: []string{"dep"}, Mapping: &SourceMapping{RetainedArtifacts: []string{"artifact"}}, |
| 81 | Presentation: &Presentation{Title: "title"}} |
| 82 | copy := cloneSnapshot(state) |
| 83 | operation := copy.PendingOperations["op"] |
| 84 | operation.Request[0], operation.Result[0] = '!', '!' |
| 85 | operation.SessionIDs[0], operation.Dependencies[0] = "changed", "changed" |
| 86 | operation.Mapping.RetainedArtifacts[0], operation.Presentation.Title = "changed", "changed" |
| 87 | original := state.PendingOperations["op"] |
| 88 | if original.Request[0] != '{' || original.Result[0] != '{' || original.SessionIDs[0] != "s" || original.Dependencies[0] != "dep" || original.Mapping.RetainedArtifacts[0] != "artifact" || original.Presentation.Title != "title" { |
| 89 | t.Fatal("snapshot aliases cached operation storage") |
| 90 | } |
| 91 | } |
| 92 |