-
Notifications
You must be signed in to change notification settings - Fork 457
Expand file tree
/
Copy pathsys_req_110_111_witness_test.go
More file actions
502 lines (471 loc) · 21 KB
/
Copy pathsys_req_110_111_witness_test.go
File metadata and controls
502 lines (471 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
package jsonparser
import (
"errors"
"testing"
)
// =============================================================================
// SYS-REQ-110 / SYS-REQ-111 witness tests.
// =============================================================================
//
// This file closes the mcdc_coverage and obligation_evidence_complete gaps
// for the two SYS-REQs traced up to STK-REQ-005:
//
// * SYS-REQ-110 — Set on array-index `[N]` where N >= len(array) shall
// append at end (PR #286). The FRETish implication is
// `!set_targets_array_index_beyond_length | set_appends_value_at_array_end`.
//
// * SYS-REQ-111 — Empty-string key component shall return KeyPathNotFound
// (Get family / Delete) or a defined error (Set) and never panic. The 7
// guarded dereference sites (parser.go:428, 634, 740, 762, 773, 791, 814)
// all surface a typed not-found outcome.
//
// Each MCDC witness line below is placed directly above the test line that
// drives the exact truth-table row, and each test body asserts the observable
// contract so the comment alone is never the only evidence.
// =============================================================================
// SYS-REQ-110 — Set append-at-end beyond array length.
// =============================================================================
// Verifies: SYS-REQ-110 [boundary]
// SYS-REQ-110:boundary:nominal
// MCDC SYS-REQ-110: set_appends_value_at_array_end=F, set_targets_array_index_beyond_length=F => TRUE [no-action: in-range Set resolves via internalGet and overwrites the addressed element in place; the createInsertComponent append branch is never entered because the full path exists.]
// Expected: TRUE (requirement satisfied — antecedent false, implication vacuously true)
func TestMCDC_SYS_REQ_110_Row1_InRangeNoAppend(t *testing.T) {
// Witness row 1: target index [0] is within the array bounds of [1,2]
// (set_targets_array_index_beyond_length=F). Set overwrites the element
// in place — no append code path executes (set_appends_value_at_array_end=F).
got, err := Set([]byte(`[1,2]`), []byte(`9`), "[0]")
if err != nil {
t.Fatalf("Set in-range returned error: %v", err)
}
want := `[9,2]`
if string(got) != want {
t.Fatalf("Set in-range result = %s, want %s (in-place replace, no append)", string(got), want)
}
// Re-parse the mutated document to confirm only element 0 changed and no
// trailing append occurred.
v, _, _, err := Get(got, "[1]")
if err != nil {
t.Fatalf("re-parse Get [1] err: %v", err)
}
if string(v) != "2" {
t.Fatalf("element [1] = %s, want 2 (untouched)", string(v))
}
}
// Verifies: SYS-REQ-110 [boundary]
// MCDC SYS-REQ-110: set_appends_value_at_array_end=F, set_targets_array_index_beyond_length=T => FALSE
// Expected: FALSE (invariant-violation row; drives the positive path to prove unreachable)
func TestMCDC_SYS_REQ_110_Row2_InvariantViolation(t *testing.T) {
// Row 2 is the invariant-violation row: it would require Set on a
// beyond-length index to NOT append (set_appends_value_at_array_end=F
// while set_targets_array_index_beyond_length=T). In a correct build
// (post PR #286) this state cannot occur — Set always appends. Drive the
// positive path to prove the FALSE row unreachable.
got, err := Set([]byte(`{"a":[{"x":1}]}`), []byte(`9`), "a", "[5]")
if err != nil {
t.Fatalf("Set beyond-length returned error: %v", err)
}
// The implementation MUST append at end (index becomes len(array)); the
// pre-existing element {"x":1} must survive untouched.
want := `{"a":[{"x":1},9]}`
if string(got) != want {
t.Fatalf("Set beyond-length result = %s, want %s (value appended at end, existing element preserved)", string(got), want)
}
}
// Verifies: SYS-REQ-110 [nested_mutation]
// SYS-REQ-110:nested_mutation:nominal
// MCDC SYS-REQ-110: set_appends_value_at_array_end=T, set_targets_array_index_beyond_length=T => TRUE
// Expected: TRUE (positive witness — beyond-length index appends value at array end)
func TestMCDC_SYS_REQ_110_Row3_BeyondLengthAppendsAtEnd(t *testing.T) {
// Witness row 3: target index [5] is beyond the array length 1 inside a
// nested object (set_targets_array_index_beyond_length=T). Set appends
// the value at the end of the addressed array (set_appends_value_at_array_end=T).
got, err := Set([]byte(`{"a":[{"x":1}]}`), []byte(`9`), "a", "[5]")
if err != nil {
t.Fatalf("Set beyond-length returned error: %v", err)
}
want := `{"a":[{"x":1},9]}`
if string(got) != want {
t.Fatalf("Set beyond-length result = %s, want %s", string(got), want)
}
// Re-parse to assert the appended value sits at index len(array)-of-original
// = 1 (the new tail), and that the pre-existing element survives at [0].
v0, _, _, err := Get(got, "a", "[0]")
if err != nil {
t.Fatalf("re-parse Get a.[0] err: %v", err)
}
if string(v0) != `{"x":1}` {
t.Fatalf("existing element a.[0] = %s, want {\"x\":1} (must survive append)", string(v0))
}
v1, _, _, err := Get(got, "a", "[1]")
if err != nil {
t.Fatalf("re-parse Get a.[1] err: %v", err)
}
if string(v1) != `9` {
t.Fatalf("appended element a.[1] = %s, want 9 (appended at end)", string(v1))
}
}
// =============================================================================
// SYS-REQ-111 — Empty-string key component → KeyPathNotFoundError, no panic.
// =============================================================================
// Verifies: SYS-REQ-111 [missing_path]
// SYS-REQ-111:missing_path:nominal
// MCDC SYS-REQ-111: completes_without_panic_on_empty_key_component=F, path_component_is_empty_string=F, returns_not_found_for_empty_key_component=F => TRUE [no-action: Get returns the resolved value (1, Number, offset >= 0, err == nil) and NOT KeyPathNotFoundError; the empty-key not-found action is observed zero times on a non-empty key path.]
// Expected: TRUE (antecedent false — no empty key in path)
func TestMCDC_SYS_REQ_111_Row1_NonEmptyKeyNoAction(t *testing.T) {
// Witness row 1: the path component "a" is non-empty
// (path_component_is_empty_string=F), so the empty-key handling action
// (returns_not_found_for_empty_key_component) is never triggered. We
// assert the observable absence of the not-found action: the call
// returns the resolved value, NOT KeyPathNotFoundError and NOT a nil
// value. This is the caller-level proof that zero not-found-events
// fired on the non-empty path.
val, dt, off, err := Get([]byte(`{"a":1}`), "a")
if errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("Get non-empty key returned KeyPathNotFoundError — the empty-key not-found action fired when it must not have (row 1 no-action violation)")
}
if err != nil {
t.Fatalf("Get non-empty key err: %v", err)
}
if dt != Number {
t.Fatalf("Get non-empty key type = %v, want Number", dt)
}
if string(val) != "1" {
t.Fatalf("Get non-empty key value = %s, want 1", string(val))
}
if off < 0 {
t.Fatalf("Get non-empty key offset = %d, want >= 0", off)
}
}
// Verifies: SYS-REQ-111 [missing_path]
// MCDC SYS-REQ-111: completes_without_panic_on_empty_key_component=F, path_component_is_empty_string=T, returns_not_found_for_empty_key_component=F => FALSE
// Expected: FALSE (invariant-violation row; drives the positive path)
func TestMCDC_SYS_REQ_111_Row2_InvariantViolation(t *testing.T) {
// Row 2 invariant violation: empty key but neither not-found nor no-panic
// held. Drive the positive path (Row 5): empty key MUST surface
// KeyPathNotFoundError and MUST complete without panic. Exercises the
// guarded searchKeys dereference site at parser.go:428.
var (
val []byte
err error
)
runNoPanic(t, "Get empty key on object root", func() {
val, _, _, err = Get([]byte(`{"a":1}`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("Get empty-key err = %v, want KeyPathNotFoundError", err)
}
if val != nil {
t.Fatalf("Get empty-key value = %s, want nil", string(val))
}
}
// Verifies: SYS-REQ-111 [missing_path]
// MCDC SYS-REQ-111: completes_without_panic_on_empty_key_component=F, path_component_is_empty_string=T, returns_not_found_for_empty_key_component=T => FALSE
// Expected: FALSE (invariant-violation row; drives the positive path)
func TestMCDC_SYS_REQ_111_Row3_InvariantViolation(t *testing.T) {
// Row 3 invariant violation: empty key returned not-found but panicked.
// Drive the positive path (Row 5) via a typed accessor (GetString) which
// also routes through the guarded searchKeys site at parser.go:428 and
// the EachKey p[level][0] guard at parser.go:634.
var err error
runNoPanic(t, "GetString empty key", func() {
_, err = GetString([]byte(`{"a":"x"}`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("GetString empty-key err = %v, want KeyPathNotFoundError", err)
}
}
// Verifies: SYS-REQ-111 [missing_path]
// MCDC SYS-REQ-111: completes_without_panic_on_empty_key_component=T, path_component_is_empty_string=T, returns_not_found_for_empty_key_component=F => FALSE
// Expected: FALSE (invariant-violation row; drives the positive path)
func TestMCDC_SYS_REQ_111_Row4_InvariantViolation(t *testing.T) {
// Row 4 invariant violation: empty key completed without panic but did
// NOT return not-found (i.e. resolved to a real callback). Drive the
// positive path via EachKey: an empty path component must never resolve
// to a callback slot. Exercises the guarded p[level][0] dereference at
// parser.go:634.
emptyCalled := false
runNoPanic(t, "EachKey empty path mixed with valid path", func() {
EachKey([]byte(`{"a":1,"b":2}`), func(idx int, val []byte, dt ValueType, err error) {
// paths[0] is the empty path; if its callback fires the
// invariant was violated.
if idx == 0 {
emptyCalled = true
}
}, []string{""}, []string{"a"})
})
if emptyCalled {
t.Fatalf("EachKey emitted a callback for the empty path component (row 4 violation actually occurred)")
}
}
// Verifies: SYS-REQ-111 [nil_safety]
// SYS-REQ-111:nil_safety:nominal
// SYS-REQ-016:nil_safety:nominal
// Carrier: this test drives the same parser.go:Get/searchKeys surface that
// SYS-REQ-016 traces to (verified_by_extra: deep_spec_test.go,
// parser_test.go). Well-formed JSON + empty-string key component exercises
// the searchKeys empty-key guard (parser.go:428) and must complete without
// panic, surfacing the typed KeyPathNotFoundError — exactly SYS-REQ-016's
// nil_safety positive contract on the missing-path lookup surface.
// MCDC SYS-REQ-111: completes_without_panic_on_empty_key_component=T, path_component_is_empty_string=T, returns_not_found_for_empty_key_component=T => TRUE
// Expected: TRUE (positive witness — empty key returns not-found AND completes without panic)
func TestMCDC_SYS_REQ_111_Row5_EmptyKeyReturnsNotFoundNoPanic(t *testing.T) {
// Witness row 5: an empty-string key component flows through Get; the
// guarded dereference sites (parser.go:428 searchKeys, plus the EachKey
// / createInsertComponent / calcAllocateSpace guards) surface a typed
// KeyPathNotFoundError without panicking. Each subtest targets a
// different guarded site so the witness is honest about coverage.
// Site: parser.go:428 — searchKeys `[` dereference on object root.
t.Run("searchKeys_object_root_guard_428", func(t *testing.T) {
var err error
runNoPanic(t, "Get", func() {
_, _, _, err = Get([]byte(`{"a":1}`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("err = %v, want KeyPathNotFoundError", err)
}
})
// Site: parser.go:428 — searchKeys `[` dereference on array root.
t.Run("searchKeys_array_root_guard_428", func(t *testing.T) {
var err error
runNoPanic(t, "Get", func() {
_, _, _, err = Get([]byte(`[1,2,3]`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("err = %v, want KeyPathNotFoundError", err)
}
})
// Site: parser.go:428 — searchKeys empty component after a valid key.
t.Run("searchKeys_after_valid_key_guard_428", func(t *testing.T) {
var err error
runNoPanic(t, "Get", func() {
_, _, _, err = Get([]byte(`{"a":[1]}`), "a", "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("err = %v, want KeyPathNotFoundError", err)
}
})
// Site: parser.go:634 — EachKey p[level][0] dereference on array root.
t.Run("EachKey_array_root_guard_634", func(t *testing.T) {
called := false
runNoPanic(t, "EachKey", func() {
EachKey([]byte(`[1,2,3]`), func(idx int, val []byte, dt ValueType, err error) {
called = true
}, []string{""})
})
if called {
t.Fatalf("EachKey must not invoke callback for an empty path")
}
})
// Sites: parser.go:740 + parser.go:791 — createInsertComponent and
// calcAllocateSpace keys[0][0] dereference (root-level empty key).
t.Run("createInsertComponent_calcAllocateSpace_root_guard_740_791", func(t *testing.T) {
var (
val []byte
err error
)
runNoPanic(t, "Set", func() {
val, err = Set([]byte(`{}`), []byte(`"v"`), "")
})
if err != nil {
t.Fatalf("Set empty key root err: %v", err)
}
if val == nil {
t.Fatalf("Set empty key root returned nil document")
}
})
// Sites: parser.go:762, parser.go:773, parser.go:814 — inner-loop
// keys[i][0] dereferences in createInsertComponent / calcAllocateSpace
// when an empty key appears at depth > 0.
t.Run("createInsertComponent_inner_loop_guard_762_773_814", func(t *testing.T) {
var (
val []byte
err error
)
runNoPanic(t, "Set", func() {
val, err = Set([]byte(`{}`), []byte(`"v"`), "", "a")
})
if err != nil {
t.Fatalf("Set empty key inner err: %v", err)
}
if val == nil {
t.Fatalf("Set empty key inner returned nil document")
}
})
}
// =============================================================================
// Obligation evidence — SYS-REQ-111 nil_safety:negative (required evidence
// per the nil_safety catalog entry: tests with nil inputs are the most
// direct evidence for the nil-safety hazard class).
// =============================================================================
// Verifies: SYS-REQ-111 [nil_safety]
// SYS-REQ-111:nil_safety:negative
// SYS-REQ-016:nil_safety:negative
// Carrier: nil data + empty key drives Get(nil,"") into searchKeys on a
// nil slice (the same parser.go:Get surface SYS-REQ-016 traces to). The
// negative contract — typed KeyPathNotFoundError rather than nil-slice
// panic — is the same nil_safety obligation on SYS-REQ-016's surface.
func TestObligation_SYS_REQ_111_NilSafety_Negative(t *testing.T) {
// nil data + empty key: the parser must surface a typed error
// (KeyPathNotFoundError) rather than panicking on a nil-slice dereference
// inside searchKeys. This is the negative-polarity witness required by
// the nil_safety obligation class.
var err error
runNoPanic(t, "Get nil data empty key", func() {
_, _, _, err = Get(nil, "")
})
if err == nil {
t.Fatal("expected error on nil input with empty key, got nil")
}
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("nil+empty-key err = %v, want KeyPathNotFoundError", err)
}
// Set on nil data with empty key — defined error, no panic.
var (
setVal []byte
setErr error
)
runNoPanic(t, "Set nil data empty key", func() {
setVal, setErr = Set(nil, []byte(`42`), "")
})
if setErr == nil {
t.Fatal("expected error on nil Set input with empty key, got nil")
}
if setVal != nil {
t.Fatalf("nil Set empty key val = %s, want nil", string(setVal))
}
}
// =============================================================================
// Obligation evidence — SYS-REQ-111 no_path_provided:nominal.
// =============================================================================
// Verifies: SYS-REQ-111 [no_path_provided]
// SYS-REQ-111:no_path_provided:nominal
// SYS-REQ-016:no_path_provided:nominal
// Carrier: Set with zero variadic keys / Set with an empty-string key on
// a non-object root drives the same parser.go:Get/searchKeys surface
// SYS-REQ-016 traces to; the no-effective-path case must surface the
// defined KeyPathNotFoundError triplet rather than panic — SYS-REQ-016's
// no_path_provided positive contract.
func TestObligation_SYS_REQ_111_NoPathProvided_Nominal(t *testing.T) {
// An empty-string key component is structurally a no-effective-path
// case (it cannot address an object key or array index). The positive
// contract is the same as the missing-path contract: Set returns a
// defined KeyPathNotFoundError rather than panicking on the empty keys
// slice dereference. Compare with Set() invoked with zero variadic keys,
// which is the literal no-path-provided case and is also defined.
t.Run("Set_zero_keys", func(t *testing.T) {
val, err := Set([]byte(`{"a":1}`), []byte(`42`))
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("Set zero-keys err = %v, want KeyPathNotFoundError", err)
}
if val != nil {
t.Fatalf("Set zero-keys val = %s, want nil", string(val))
}
})
t.Run("Set_empty_key_returns_defined_error", func(t *testing.T) {
// Empty-string key on a non-object root must return KeyPathNotFoundError
// (the defined no-path outcome), never panic.
var err error
runNoPanic(t, "Set on array root with empty key", func() {
_, err = Set([]byte(`[1,2]`), []byte(`9`), "")
})
if err == nil {
t.Fatal("expected defined error for Set with empty key on array root")
}
})
}
// =============================================================================
// Obligation evidence — STK-REQ-005 triples for the obligation classes
// SYS-REQ-110/111 carry (boundary, missing_path, no_path_provided,
// nil_safety, nested_mutation). These mirror the SYS-REQ-level triples above
// onto STK-REQ-005 (the parent stakeholder story) so the parent obligation
// check sees carriers as well.
// =============================================================================
// STK-REQ-005:boundary:nominal
func TestObligation_STK_REQ_005_Boundary_Nominal_ForSetBeyondLengthContract(t *testing.T) {
// Boundary positive witness for STK-REQ-005: Set with an in-range array
// index must replace the addressed element, and Set with an out-of-range
// index must append at end (no silent corruption of an unaddressed slot).
// The in-range boundary case (index == len-1) is the most common off-by-
// one trap and is asserted explicitly here.
got, err := Set([]byte(`[1,2]`), []byte(`9`), "[1]")
if err != nil {
t.Fatalf("Set [1] err: %v", err)
}
if string(got) != `[1,9]` {
t.Fatalf("Set [1] = %s, want [1,9] (boundary in-range replace)", string(got))
}
}
// STK-REQ-005:nested_mutation:nominal
func TestObligation_STK_REQ_005_NestedMutation_Nominal_ForBeyondLengthAppend(t *testing.T) {
// Nested-mutation positive witness for STK-REQ-005: Set on a beyond-
// length array index nested inside an object must emit array scaffolding
// at the correct offset (appended at end), not overwrite a sibling or
// build malformed JSON.
got, err := Set([]byte(`{"top":[{"x":1}]}`), []byte(`{"y":2}`), "top", "[9]")
if err != nil {
t.Fatalf("Set nested beyond-length err: %v", err)
}
want := `{"top":[{"x":1},{"y":2}]}`
if string(got) != want {
t.Fatalf("Set nested beyond-length = %s, want %s", string(got), want)
}
}
// STK-REQ-005:missing_path:nominal
func TestObligation_STK_REQ_005_MissingPath_Nominal_ForEmptyKeyContract(t *testing.T) {
// Missing-path positive witness for STK-REQ-005: an empty-string key
// component resolves to no path; Get must surface the typed not-found
// triplet (nil value, NotExist type, -1 offset, KeyPathNotFoundError).
var (
val []byte
dt ValueType
off int
err error
)
runNoPanic(t, "Get empty key", func() {
val, dt, off, err = Get([]byte(`{"a":1}`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("err = %v, want KeyPathNotFoundError", err)
}
if val != nil || dt != NotExist || off != -1 {
t.Fatalf("not-found triplet mismatch: val=%v dt=%v off=%d", val, dt, off)
}
}
// STK-REQ-005:nil_safety:nominal
func TestObligation_STK_REQ_005_NilSafety_Nominal_ForEmptyKeyContract(t *testing.T) {
// Nil-safety positive witness for STK-REQ-005: a well-formed JSON payload
// paired with an empty-string key component must produce the defined
// not-found outcome (the empty-key positive contract), never panic.
var err error
runNoPanic(t, "Get empty key", func() {
_, _, _, err = Get([]byte(`{"a":1}`), "")
})
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("err = %v, want KeyPathNotFoundError", err)
}
}
// STK-REQ-005:nil_safety:negative
func TestObligation_STK_REQ_005_NilSafety_Negative_ForNilPlusEmptyKey(t *testing.T) {
// Nil-safety negative witness for STK-REQ-005: nil data combined with an
// empty-string key must surface a typed error rather than panic.
var err error
runNoPanic(t, "Get nil empty key", func() {
_, _, _, err = Get(nil, "")
})
if err == nil {
t.Fatal("expected error on nil input with empty key")
}
}
// STK-REQ-005:no_path_provided:nominal
func TestObligation_STK_REQ_005_NoPathProvided_Nominal_ForSetZeroKeys(t *testing.T) {
// No-path-provided positive witness for STK-REQ-005: Set invoked with
// zero variadic keys must return the defined KeyPathNotFoundError
// contract rather than panic on the empty keys slice.
val, err := Set([]byte(`{"a":1}`), []byte(`42`))
if !errors.Is(err, KeyPathNotFoundError) {
t.Fatalf("Set zero-keys err = %v, want KeyPathNotFoundError", err)
}
if val != nil {
t.Fatalf("Set zero-keys val = %s, want nil", string(val))
}
}