oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1package core_test
2
3import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "github.com/carlosframework/docs/internal/core"
11)
12
13// fixture loads fixtures/vectors/fragment/<name>.update.bin, a real
14// Yjs update building a Y.XmlFragment on root "prosemirror"
15// (fixtures/generate-fragment.mjs's own "trap-containment vectors"
16// section), plus its expected plain text.
17//
18// Not fixtures/data (a bare Y.Text on root "content"): every test in
19// this file reads content back through (*Handle).Text, which now
20// reads fragment_text off root "prosemirror" — see
21// core/src/abi.rs's text_string doc comment. fixtures/data's updates
22// still apply cleanly (Apply/apply_slice decode and merge regardless
23// of which root they target) but read back as "" through Text now,
24// which is exactly the silent-empty failure this whole fix round
25// exists to stop reintroducing.
26func fixture(t *testing.T, name string) ([]byte, string) {
27 t.Helper()
28 dir := filepath.Join("..", "..", "fixtures", "vectors", "fragment")
29 update, err := os.ReadFile(filepath.Join(dir, name+".update.bin"))
30 if err != nil {
31 t.Fatalf("reading fixture: %v", err)
32 }
33 raw, err := os.ReadFile(filepath.Join(dir, name+".expected.json"))
34 if err != nil {
35 t.Fatalf("reading expectation: %v", err)
36 }
37 var want struct {
38 Text string `json:"text"`
39 }
40 if err := json.Unmarshal(raw, &want); err != nil {
41 t.Fatalf("parsing expectation: %v", err)
42 }
43 return update, want.Text
44}
45
46func TestAppliesFixturesUnderWazero(t *testing.T) {
47 ctx := context.Background()
48 rt, err := core.New(ctx)
49 if err != nil {
50 t.Fatalf("core.New: %v", err)
51 }
52 t.Cleanup(func() { rt.Close(ctx) })
53
54 for _, name := range []string{"hello", "unicode", "formatted", "concurrent"} {
55 t.Run(name, func(t *testing.T) {
56 update, want := fixture(t, name)
57 doc, err := rt.NewDoc(ctx, "go-host-test-guid", 9001)
58 if err != nil {
59 t.Fatalf("NewDoc: %v", err)
60 }
61 defer doc.Close(ctx)
62
63 if err := doc.Apply(ctx, update); err != nil {
64 t.Fatalf("Apply: %v", err)
65 }
66 got, err := doc.Text(ctx)
67 if err != nil {
68 t.Fatalf("Text: %v", err)
69 }
70 if got != want {
71 t.Errorf("got %q, want %q", got, want)
72 }
73 })
74 }
75}
76
77func TestGarbageUpdateReturnsErrorNotTrap(t *testing.T) {
78 ctx := context.Background()
79 rt, err := core.New(ctx)
80 if err != nil {
81 t.Fatalf("core.New: %v", err)
82 }
83 t.Cleanup(func() { rt.Close(ctx) })
84
85 doc, err := rt.NewDoc(ctx, "go-host-test-guid", 9001)
86 if err != nil {
87 t.Fatalf("NewDoc: %v", err)
88 }
89 defer doc.Close(ctx)
90
91 if err := doc.Apply(ctx, []byte{0xff, 0xff, 0xff, 0xff}); err == nil {
92 t.Fatal("expected an error for garbage input, got nil")
93 }
94
95 // The instance must still be usable: an error is not a trap.
96 update, want := fixture(t, "hello")
97 if err := doc.Apply(ctx, update); err != nil {
98 t.Fatalf("instance unusable after a rejected update: %v", err)
99 }
100 got, _ := doc.Text(ctx)
101 if got != want {
102 t.Errorf("after recovery got %q, want %q", got, want)
103 }
104}
105
106// TestTrappedInstanceIsDiscardedAndRebuilds lives in core_internal_test.go
107// (package core), not here. Fix-round-1 finding 1: the trap this test uses
108// fires inside Update::decode_v1 before any RefCell is borrowed, so a host
109// that simply kept reusing the trapped instance would pass every
110// public-API-observable check (Text still succeeds, content still
111// correct) — the one assertion that can actually fail against a host that
112// skips the discard is a direct check that the module instance itself
113// changed, which needs unexported access this external test package
114// doesn't have. This file's tests only cover the other two cases: a clean
115// success (TestAppliesFixturesUnderWazero) and a clean rejection
116// (TestGarbageUpdateReturnsErrorNotTrap), both of which the public API is
117// enough to verify.
118
119func TestHandleHTMLRendersFragment(t *testing.T) {
120 ctx := context.Background()
121 rt, err := core.New(ctx)
122 if err != nil {
123 t.Fatalf("core.New: %v", err)
124 }
125 defer rt.Close(ctx)
126
127 h, err := rt.NewDoc(ctx, "html-test", 1)
128 if err != nil {
129 t.Fatalf("NewDoc: %v", err)
130 }
131 defer h.Close(ctx)
132
133 update, err := os.ReadFile("../../fixtures/vectors/fragment/simple.bin")
134 if err != nil {
135 t.Fatalf("read fixture: %v", err)
136 }
137 if err := h.Apply(ctx, update); err != nil {
138 t.Fatalf("Apply: %v", err)
139 }
140
141 got, err := h.HTML(ctx)
142 if err != nil {
143 t.Fatalf("HTML: %v", err)
144 }
145 want := "<p>First paragraph.</p><p>Second paragraph.</p>"
146 if got != want {
147 t.Errorf("HTML() = %q, want %q", got, want)
148 }
149}
150
151// TestNewDocSucceedsAgainstTheCurrentModule is the end-to-end counterpart
152// to TestCheckABIVersion (core_internal_test.go): that test proves the
153// version-comparison decision itself is correct in isolation; this one
154// proves instantiate still calls it and still accepts the module this
155// package embeds. Fix round 1 on Task 2: the test previously here
156// (TestABIVersionIsPinnedAtTwo) only called core.New, which never
157// instantiates a module or reaches the ABI check at all (only NewDoc/
158// rebuild do, via Runtime.instantiate) — it passed even with the check
159// deleted outright.
160func TestNewDocSucceedsAgainstTheCurrentModule(t *testing.T) {
161 ctx := context.Background()
162 rt, err := core.New(ctx)
163 if err != nil {
164 t.Fatalf("core.New: %v", err)
165 }
166 defer rt.Close(ctx)
167
168 h, err := rt.NewDoc(ctx, "abi-wiring-test", 1)
169 if err != nil {
170 t.Fatalf("NewDoc: %v", err)
171 }
172 defer h.Close(ctx)
173}
174
175// TestSnapshotRoundTripsThroughAFreshHandle proves Snapshot's bytes are a
176// real Yjs update: applying them into a brand-new, otherwise-empty handle
177// reproduces the same text the snapshot was taken from (the snapshots
178// design, D1).
179func TestSnapshotRoundTripsThroughAFreshHandle(t *testing.T) {
180 ctx := context.Background()
181 rt, err := core.New(ctx)
182 if err != nil {
183 t.Fatalf("core.New: %v", err)
184 }
185 defer rt.Close(ctx)
186
187 h, err := rt.NewDoc(ctx, "snapshot-source", 1)
188 if err != nil {
189 t.Fatalf("NewDoc: %v", err)
190 }
191 defer h.Close(ctx)
192
193 update, want := fixture(t, "hello")
194 if err := h.Apply(ctx, update); err != nil {
195 t.Fatalf("Apply: %v", err)
196 }
197
198 snap, err := h.Snapshot(ctx)
199 if err != nil {
200 t.Fatalf("Snapshot: %v", err)
201 }
202 if len(snap) == 0 {
203 t.Fatal("Snapshot returned no bytes")
204 }
205
206 fresh, err := rt.NewDoc(ctx, "snapshot-target", 2)
207 if err != nil {
208 t.Fatalf("NewDoc fresh: %v", err)
209 }
210 defer fresh.Close(ctx)
211
212 if err := fresh.Apply(ctx, snap); err != nil {
213 t.Fatalf("Apply snapshot: %v", err)
214 }
215 got, err := fresh.Text(ctx)
216 if err != nil {
217 t.Fatalf("Text: %v", err)
218 }
219 if got != want {
220 t.Errorf("snapshot rebuilt %q, want %q", got, want)
221 }
222}
223
224// TestVerifySnapshotAcceptsItsOwnAndRejectsGarbage exercises all three
225// outcomes VerifySnapshot returns without an error: a document's own
226// snapshot verifies true; a validly-decodable snapshot of a genuinely
227// different document verifies false (return code 1 — the verifier's
228// primary purpose, distinct from "not even a decodable update"); and
229// bytes that are not a decodable update at all (ERR_DECODE) also come
230// back false with no error — a "no" answer, not a failure of the call.
231// The middle case and the garbage case are different failures of the
232// candidate and must not be conflated: only the garbage case may ever
233// legitimately share its outcome with a rejected-but-valid document.
234func TestVerifySnapshotAcceptsItsOwnAndRejectsGarbage(t *testing.T) {
235 ctx := context.Background()
236 rt, err := core.New(ctx)
237 if err != nil {
238 t.Fatalf("core.New: %v", err)
239 }
240 defer rt.Close(ctx)
241
242 h, err := rt.NewDoc(ctx, "verify-test", 1)
243 if err != nil {
244 t.Fatalf("NewDoc: %v", err)
245 }
246 defer h.Close(ctx)
247
248 update, _ := fixture(t, "hello")
249 if err := h.Apply(ctx, update); err != nil {
250 t.Fatalf("Apply: %v", err)
251 }
252
253 snap, err := h.Snapshot(ctx)
254 if err != nil {
255 t.Fatalf("Snapshot: %v", err)
256 }
257
258 ok, err := h.VerifySnapshot(ctx, snap)
259 if err != nil {
260 t.Fatalf("VerifySnapshot: %v", err)
261 }
262 if !ok {
263 t.Error("a document refused its own snapshot")
264 }
265
266 // A genuinely different document's snapshot: a real, validly-decodable
267 // update, but not a snapshot of h. This is not "simple" (also a
268 // fixtures/vectors/fragment .bin, used elsewhere in this file via
269 // os.ReadFile) reused as a second, unrelated document's content — the
270 // point is that decode succeeds and the structural comparison still
271 // says no.
272 other, err := rt.NewDoc(ctx, "verify-test-other", 2)
273 if err != nil {
274 t.Fatalf("NewDoc other: %v", err)
275 }
276 defer other.Close(ctx)
277
278 otherUpdate, err := os.ReadFile("../../fixtures/vectors/fragment/simple.bin")
279 if err != nil {
280 t.Fatalf("read fixture: %v", err)
281 }
282 if err := other.Apply(ctx, otherUpdate); err != nil {
283 t.Fatalf("Apply other: %v", err)
284 }
285 otherSnap, err := other.Snapshot(ctx)
286 if err != nil {
287 t.Fatalf("Snapshot other: %v", err)
288 }
289
290 if ok, err := h.VerifySnapshot(ctx, otherSnap); err != nil || ok {
291 t.Errorf("a different document's snapshot verified against h: ok=%v err=%v", ok, err)
292 }
293
294 if ok, err := h.VerifySnapshot(ctx, []byte{0xff, 0x00}); err != nil || ok {
295 t.Errorf("garbage verified: ok=%v err=%v", ok, err)
296 }
297}
298