capdag 1.211.563 → 1.219.594
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/capdag.js +131 -0
- package/capdag.test.js +734 -723
- package/package.json +2 -2
package/capdag.test.js
CHANGED
|
@@ -33,7 +33,10 @@ const {
|
|
|
33
33
|
MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
|
|
34
34
|
MEDIA_DECISION,
|
|
35
35
|
MEDIA_AUDIO_SPEECH,
|
|
36
|
-
CAP_IDENTITY
|
|
36
|
+
CAP_IDENTITY,
|
|
37
|
+
ALIAS_TARGET_CAP, ALIAS_TARGET_MEDIA,
|
|
38
|
+
tokenIsUrn, isAliasToken, normalizeAliasName, classifyAliasTarget,
|
|
39
|
+
StoredAlias, Manifest
|
|
37
40
|
} = require('./capdag.js');
|
|
38
41
|
|
|
39
42
|
// ============================================================================
|
|
@@ -111,8 +114,8 @@ function runTest(name, fn) {
|
|
|
111
114
|
* Uses MEDIA_VOID for in and MEDIA_OBJECT for out, matching the
|
|
112
115
|
* Rust reference test_urn helper: test_urn(tags) => cap:in="media:void";{tags};out="media:enc=utf-8;record"
|
|
113
116
|
*/
|
|
114
|
-
//
|
|
115
|
-
function
|
|
117
|
+
// TEST6204: Urn
|
|
118
|
+
function test6204_Urn(tags) {
|
|
116
119
|
if (!tags || tags === '') {
|
|
117
120
|
return `cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`;
|
|
118
121
|
}
|
|
@@ -136,9 +139,9 @@ function makeGraphCap(inUrn, outUrn, title) {
|
|
|
136
139
|
// cap_urn.rs: TEST001-TEST050, TEST890-TEST891
|
|
137
140
|
// ============================================================================
|
|
138
141
|
|
|
139
|
-
//
|
|
142
|
+
// TEST1: Test that cap URN is created with tags parsed correctly and direction specs accessible
|
|
140
143
|
function test001_capUrnCreation() {
|
|
141
|
-
const cap = CapUrn.fromString(
|
|
144
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
|
|
142
145
|
assert(cap.hasMarkerTag('generate'), 'Should get op tag');
|
|
143
146
|
assertEqual(cap.getTag('target'), 'thumbnail', 'Should get target tag');
|
|
144
147
|
assertEqual(cap.getTag('ext'), 'pdf', 'Should get ext tag');
|
|
@@ -146,7 +149,7 @@ function test001_capUrnCreation() {
|
|
|
146
149
|
assertEqual(cap.getOutSpec(), MEDIA_OBJECT, 'Should get outSpec');
|
|
147
150
|
}
|
|
148
151
|
|
|
149
|
-
//
|
|
152
|
+
// TEST2: Test that missing 'in' or 'out' defaults to media: wildcard
|
|
150
153
|
function test002_directionSpecsRequired() {
|
|
151
154
|
const missingIn = CapUrn.fromString('cap:in=media:;out=media:void;test');
|
|
152
155
|
assertEqual(missingIn.getInSpec(), MEDIA_IDENTITY, 'Missing in should default to media:');
|
|
@@ -157,10 +160,10 @@ function test002_directionSpecsRequired() {
|
|
|
157
160
|
assertEqual(missingOut.getOutSpec(), MEDIA_IDENTITY, 'Missing out should default to media:');
|
|
158
161
|
}
|
|
159
162
|
|
|
160
|
-
//
|
|
163
|
+
// TEST3: Test that direction specs must match exactly, different in/out types don't match, wildcard matches any
|
|
161
164
|
function test003_directionMatching() {
|
|
162
|
-
const cap = CapUrn.fromString(
|
|
163
|
-
const request = CapUrn.fromString(
|
|
165
|
+
const cap = CapUrn.fromString(test6204_Urn('generate'));
|
|
166
|
+
const request = CapUrn.fromString(test6204_Urn('generate'));
|
|
164
167
|
assert(cap.accepts(request), 'Same direction specs should match');
|
|
165
168
|
|
|
166
169
|
// Different direction should not match
|
|
@@ -172,9 +175,7 @@ function test003_directionMatching() {
|
|
|
172
175
|
assert(wildcardCap.accepts(request), 'Wildcard direction should match any');
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
//
|
|
176
|
-
// Key lookup is case-insensitive: uppercase variants of `ext` resolve
|
|
177
|
-
// to the same keyed tag.
|
|
178
|
+
// TEST4: Test that unquoted keys and values are normalized to lowercase
|
|
178
179
|
function test004_unquotedValuesLowercased() {
|
|
179
180
|
const cap = CapUrn.fromString('cap:ext=pdf;generate;in=media:void;out="media:enc=utf-8;record"');
|
|
180
181
|
assert(cap.hasMarkerTag('generate'), 'Unquoted value should be lowercased');
|
|
@@ -182,33 +183,33 @@ function test004_unquotedValuesLowercased() {
|
|
|
182
183
|
assertEqual(cap.getTag('EXT'), 'pdf', 'Key lookup should be case-insensitive');
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
//
|
|
186
|
+
// TEST5: Test that quoted values preserve case while unquoted are lowercased
|
|
186
187
|
function test005_quotedValuesPreserveCase() {
|
|
187
188
|
const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
|
|
188
189
|
assertEqual(cap.getTag('key'), 'HelloWorld', 'Quoted value should preserve case');
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
//
|
|
192
|
+
// TEST6: Test that quoted values can contain special characters (semicolons, equals, spaces)
|
|
192
193
|
function test006_quotedValueSpecialChars() {
|
|
193
194
|
const cap = CapUrn.fromString('cap:in="media:void";key="val;ue=with spaces";out="media:void"');
|
|
194
195
|
assertEqual(cap.getTag('key'), 'val;ue=with spaces', 'Quoted value should preserve special chars');
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
//
|
|
198
|
+
// TEST7: Test that escape sequences in quoted values (\" and \\) are parsed correctly
|
|
198
199
|
function test007_quotedValueEscapeSequences() {
|
|
199
200
|
const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
|
|
200
201
|
const cap = CapUrn.fromString(s);
|
|
201
202
|
assertEqual(cap.getTag('key'), 'val"ue\\test', 'Escaped quote and backslash should be unescaped');
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
//
|
|
205
|
+
// TEST8: Test that mixed quoted and unquoted values in same URN parse correctly
|
|
205
206
|
function test008_mixedQuotedUnquoted() {
|
|
206
207
|
const cap = CapUrn.fromString('cap:a=simple;b="Quoted";in="media:void";out="media:void"');
|
|
207
208
|
assertEqual(cap.getTag('a'), 'simple', 'Unquoted value should be lowercase');
|
|
208
209
|
assertEqual(cap.getTag('b'), 'Quoted', 'Quoted value should preserve case');
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
//
|
|
212
|
+
// TEST9: Test that unterminated quote produces UnterminatedQuote error
|
|
212
213
|
function test009_unterminatedQuoteError() {
|
|
213
214
|
let threw = false;
|
|
214
215
|
try {
|
|
@@ -221,7 +222,7 @@ function test009_unterminatedQuoteError() {
|
|
|
221
222
|
assert(threw, 'Unterminated quote should produce CapUrnError');
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
//
|
|
225
|
+
// TEST10: Test that invalid escape sequences (like \n, \x) produce InvalidEscapeSequence error
|
|
225
226
|
function test010_invalidEscapeSequenceError() {
|
|
226
227
|
let threw = false;
|
|
227
228
|
try {
|
|
@@ -235,7 +236,7 @@ function test010_invalidEscapeSequenceError() {
|
|
|
235
236
|
assert(threw, 'Invalid escape sequence should produce CapUrnError');
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
//
|
|
239
|
+
// TEST11: Test that serialization uses smart quoting (no quotes for simple lowercase, quotes for special chars/uppercase)
|
|
239
240
|
function test011_serializationSmartQuoting() {
|
|
240
241
|
const cap = CapUrn.fromString('cap:a=simple;b="Has Space";in="media:void";out="media:void"');
|
|
241
242
|
const s = cap.toString();
|
|
@@ -244,15 +245,15 @@ function test011_serializationSmartQuoting() {
|
|
|
244
245
|
assert(s.includes('b="Has Space"'), 'Value with space should be quoted');
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
//
|
|
248
|
+
// TEST12: Test that simple cap URN round-trips (parse -> serialize -> parse equals original)
|
|
248
249
|
function test012_roundTripSimple() {
|
|
249
|
-
const original = CapUrn.fromString(
|
|
250
|
+
const original = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
250
251
|
const serialized = original.toString();
|
|
251
252
|
const reparsed = CapUrn.fromString(serialized);
|
|
252
253
|
assert(original.equals(reparsed), 'Simple round-trip should produce equal URN');
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
//
|
|
256
|
+
// TEST13: Test that quoted values round-trip preserving case and spaces
|
|
256
257
|
function test013_roundTripQuoted() {
|
|
257
258
|
const original = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
|
|
258
259
|
const serialized = original.toString();
|
|
@@ -261,7 +262,7 @@ function test013_roundTripQuoted() {
|
|
|
261
262
|
assertEqual(reparsed.getTag('key'), 'HelloWorld', 'Quoted value should survive round-trip');
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
//
|
|
265
|
+
// TEST14: Test that escape sequences round-trip correctly
|
|
265
266
|
function test014_roundTripEscapes() {
|
|
266
267
|
const s = String.raw`cap:in="media:void";key="val\"ue\\test";out="media:void"`;
|
|
267
268
|
const original = CapUrn.fromString(s);
|
|
@@ -271,7 +272,7 @@ function test014_roundTripEscapes() {
|
|
|
271
272
|
assertEqual(reparsed.getTag('key'), 'val"ue\\test', 'Escaped value should survive round-trip');
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
//
|
|
275
|
+
// TEST15: Test that cap: prefix is required and case-insensitive
|
|
275
276
|
function test015_capPrefixRequired() {
|
|
276
277
|
assertThrows(
|
|
277
278
|
() => CapUrn.fromString('in="media:void";out="media:void";generate'),
|
|
@@ -279,28 +280,19 @@ function test015_capPrefixRequired() {
|
|
|
279
280
|
'Should require cap: prefix'
|
|
280
281
|
);
|
|
281
282
|
// Valid cap: prefix should work
|
|
282
|
-
const cap = CapUrn.fromString(
|
|
283
|
+
const cap = CapUrn.fromString(test6204_Urn('generate'));
|
|
283
284
|
assert(cap.hasMarkerTag('generate'), 'Should parse with valid cap: prefix');
|
|
284
285
|
}
|
|
285
286
|
|
|
286
|
-
//
|
|
287
|
+
// TEST16: Test that trailing semicolon is equivalent (same hash, same string, matches)
|
|
287
288
|
function test016_trailingSemicolonEquivalence() {
|
|
288
|
-
const cap1 = CapUrn.fromString(
|
|
289
|
-
const cap2 = CapUrn.fromString(
|
|
289
|
+
const cap1 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
290
|
+
const cap2 = CapUrn.fromString(test6204_Urn('generate;ext=pdf') + ';');
|
|
290
291
|
assert(cap1.equals(cap2), 'With/without trailing semicolon should be equal');
|
|
291
292
|
assertEqual(cap1.toString(), cap2.toString(), 'Canonical forms should match');
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
// TEST939: The canonical form drops `in=media:` and `out=media:`
|
|
295
|
-
// segments. Every spelling of "the same cap with wildcard in/out"
|
|
296
|
-
// collapses to one byte-identical canonical string. This is the
|
|
297
|
-
// contract that makes registry lookups work: the cap-publisher hashes
|
|
298
|
-
// `<canonical-urn>` to compute the cache key, and every language port
|
|
299
|
-
// (Rust, Go, Python, JS, ObjC) must agree on the canonical form for
|
|
300
|
-
// cross-language lookups to land on the same key. A regression that
|
|
301
|
-
// emitted the wildcard segments would silently move the published cap
|
|
302
|
-
// to a different SHA-256 bucket, 404'ing every reader that hashes the
|
|
303
|
-
// canonical form.
|
|
295
|
+
// TEST939: The canonical form drops `in=media:` and `out=media:` segments. Every spelling of "the same cap with wildcard in/out" collapses to one byte-identical canonical string. This is the contract that makes registry lookups work: the cap-publisher hashes `<canonical-urn>` to compute the cache key, and every language port (Rust, Go, Python, JS, ObjC) must agree on the canonical form for cross-language lookups to land on the same key. A regression that emitted the wildcard segments would silently move the published cap to a different SHA-256 bucket, 404'ing every reader that hashes the canonical form.
|
|
304
296
|
function test939_capUrnCanonicalFormDropsWildcardInOut() {
|
|
305
297
|
const canonical = 'cap:decimate-sequence';
|
|
306
298
|
const variants = [
|
|
@@ -328,46 +320,46 @@ function test939_capUrnCanonicalFormDropsWildcardInOut() {
|
|
|
328
320
|
assert(identity.toString() !== 'cap:', 'cap:effect=none must not collapse to the illegal bare top form');
|
|
329
321
|
}
|
|
330
322
|
|
|
331
|
-
//
|
|
323
|
+
// TEST17: Test tag matching: exact match, subset match, wildcard match, value mismatch
|
|
332
324
|
function test017_tagMatching() {
|
|
333
|
-
const cap = CapUrn.fromString(
|
|
325
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
|
|
334
326
|
|
|
335
327
|
// Exact match — both directions accept
|
|
336
|
-
const exact = CapUrn.fromString(
|
|
328
|
+
const exact = CapUrn.fromString(test6204_Urn('generate;ext=pdf;target=thumbnail'));
|
|
337
329
|
assert(cap.accepts(exact), 'Should accept exact match');
|
|
338
330
|
assert(exact.accepts(cap), 'Exact match should accept in reverse too');
|
|
339
331
|
|
|
340
332
|
// Routing direction: request(generate) accepts cap(op,ext,target)
|
|
341
|
-
const subset = CapUrn.fromString(
|
|
333
|
+
const subset = CapUrn.fromString(test6204_Urn('generate'));
|
|
342
334
|
assert(subset.accepts(cap), 'General request should accept more specific instance');
|
|
343
335
|
assert(!cap.accepts(subset), 'Specific pattern should reject subset instance');
|
|
344
336
|
|
|
345
337
|
// Routing direction: request(ext=*) accepts cap(ext=pdf)
|
|
346
|
-
const wildcard = CapUrn.fromString(
|
|
338
|
+
const wildcard = CapUrn.fromString(test6204_Urn('ext=*'));
|
|
347
339
|
assert(wildcard.accepts(cap), 'Wildcard request should accept specific instance');
|
|
348
340
|
|
|
349
341
|
// Conflicting value — neither direction accepts
|
|
350
|
-
const mismatch = CapUrn.fromString(
|
|
342
|
+
const mismatch = CapUrn.fromString(test6204_Urn('extract'));
|
|
351
343
|
assert(!cap.accepts(mismatch), 'Should not accept value mismatch');
|
|
352
344
|
assert(!mismatch.accepts(cap), 'Reverse mismatch should also reject');
|
|
353
345
|
}
|
|
354
346
|
|
|
355
|
-
//
|
|
347
|
+
// TEST18: Test that quoted values with different case do NOT match (case-sensitive)
|
|
356
348
|
function test018_matchingCaseSensitiveValues() {
|
|
357
349
|
const cap = CapUrn.fromString('cap:in="media:void";key="HelloWorld";out="media:void"');
|
|
358
350
|
const request = CapUrn.fromString('cap:in="media:void";key=helloworld;out="media:void"');
|
|
359
351
|
assert(!cap.accepts(request), 'Quoted HelloWorld should not match unquoted helloworld');
|
|
360
352
|
}
|
|
361
353
|
|
|
362
|
-
//
|
|
354
|
+
// TEST19: Missing tag in instance causes rejection — pattern's tags are constraints
|
|
363
355
|
function test019_missingTagHandling() {
|
|
364
|
-
const cap = CapUrn.fromString(
|
|
365
|
-
const request = CapUrn.fromString(
|
|
356
|
+
const cap = CapUrn.fromString(test6204_Urn('generate'));
|
|
357
|
+
const request = CapUrn.fromString(test6204_Urn('ext=pdf'));
|
|
366
358
|
assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
|
|
367
359
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
368
360
|
|
|
369
|
-
const cap2 = CapUrn.fromString(
|
|
370
|
-
const request2 = CapUrn.fromString(
|
|
361
|
+
const cap2 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
362
|
+
const request2 = CapUrn.fromString(test6204_Urn('generate'));
|
|
371
363
|
assert(!cap2.accepts(request2), 'Specific pattern should reject instance missing ext');
|
|
372
364
|
assert(request2.accepts(cap2), 'General request should accept more specific instance');
|
|
373
365
|
}
|
|
@@ -377,27 +369,27 @@ function test019_missingTagHandling() {
|
|
|
377
369
|
// (must-have-any), exact `key=value` tags score 3, missing/`?` score
|
|
378
370
|
// 0, `!` scores 1.
|
|
379
371
|
//
|
|
380
|
-
//
|
|
372
|
+
// test6204_Urn() builds "cap:in=media:void;out=media:record;<tags>" so
|
|
381
373
|
// the directional baseline is:
|
|
382
374
|
// in: media:void -> {void=*} -> 2
|
|
383
375
|
// out: media:record -> {record=*} -> 2
|
|
384
376
|
// Total directional baseline: 4.
|
|
385
377
|
function test020_specificity() {
|
|
386
|
-
//
|
|
378
|
+
// test6204_Urn() prepends in="media:void" (1 marker, score 2) and
|
|
387
379
|
// out="media:record" (1 marker, score 2). Cap-URN spec is
|
|
388
380
|
// 10000*spec_U(out) + 100*spec_U(in) + spec_U(y).
|
|
389
381
|
|
|
390
|
-
const cap1 = CapUrn.fromString(
|
|
382
|
+
const cap1 = CapUrn.fromString(test6204_Urn('type=general'));
|
|
391
383
|
// out=2, in=2, y=4 (type=general exact)
|
|
392
384
|
assertEqual(cap1.specificity(), 10000*2 + 100*2 + 4,
|
|
393
385
|
'out=2, in=2, y=type=general exact=4 -> 20204');
|
|
394
386
|
|
|
395
|
-
const cap2 = CapUrn.fromString(
|
|
387
|
+
const cap2 = CapUrn.fromString(test6204_Urn('generate'));
|
|
396
388
|
// out=2, in=2, y=2 (generate marker = must-have-any)
|
|
397
389
|
assertEqual(cap2.specificity(), 10000*2 + 100*2 + 2,
|
|
398
390
|
'out=2, in=2, y=generate marker=2 -> 20202');
|
|
399
391
|
|
|
400
|
-
const cap3 = CapUrn.fromString(
|
|
392
|
+
const cap3 = CapUrn.fromString(test6204_Urn('op;ext=pdf'));
|
|
401
393
|
// out=2, in=2, y=2+4 (op marker, ext=pdf exact)
|
|
402
394
|
assertEqual(cap3.specificity(), 10000*2 + 100*2 + 6,
|
|
403
395
|
'out=2, in=2, y=op marker(2)+ext=pdf exact(4) -> 20206');
|
|
@@ -409,8 +401,7 @@ function test020_specificity() {
|
|
|
409
401
|
'out=record=2, in=*->0, y=test marker=2 -> 20002');
|
|
410
402
|
}
|
|
411
403
|
|
|
412
|
-
//
|
|
413
|
-
// `op` is no longer a special key — operation names are markers (value-less tags).
|
|
404
|
+
// TEST21: Test builder creates cap URN with correct tags and direction specs
|
|
414
405
|
function test021_builder() {
|
|
415
406
|
const cap = new CapUrnBuilder()
|
|
416
407
|
.inSpec('media:void')
|
|
@@ -424,7 +415,7 @@ function test021_builder() {
|
|
|
424
415
|
assertEqual(cap.getOutSpec(), 'media:object', 'Builder should set outSpec');
|
|
425
416
|
}
|
|
426
417
|
|
|
427
|
-
//
|
|
418
|
+
// TEST22: Test builder requires both in_spec and out_spec
|
|
428
419
|
function test022_builderRequiresDirection() {
|
|
429
420
|
assertThrows(
|
|
430
421
|
() => new CapUrnBuilder().tag('op', 'test').build(),
|
|
@@ -438,7 +429,7 @@ function test022_builderRequiresDirection() {
|
|
|
438
429
|
);
|
|
439
430
|
}
|
|
440
431
|
|
|
441
|
-
//
|
|
432
|
+
// TEST23: Test builder lowercases keys but preserves value case
|
|
442
433
|
function test023_builderPreservesCase() {
|
|
443
434
|
const cap = new CapUrnBuilder()
|
|
444
435
|
.inSpec('media:void')
|
|
@@ -449,18 +440,18 @@ function test023_builderPreservesCase() {
|
|
|
449
440
|
assertEqual(cap.getTag('MyKey'), 'MyValue', 'getTag should be case-insensitive for keys');
|
|
450
441
|
}
|
|
451
442
|
|
|
452
|
-
//
|
|
443
|
+
// TEST24: Directional accepts — pattern's tags are constraints, instance must satisfy
|
|
453
444
|
function test024_compatibility() {
|
|
454
|
-
const cap1 = CapUrn.fromString(
|
|
455
|
-
const cap2 = CapUrn.fromString(
|
|
456
|
-
const cap3 = CapUrn.fromString(
|
|
445
|
+
const cap1 = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
446
|
+
const cap2 = CapUrn.fromString(test6204_Urn('generate;format=*'));
|
|
447
|
+
const cap3 = CapUrn.fromString(test6204_Urn('type=image;extract'));
|
|
457
448
|
|
|
458
449
|
assert(!cap1.accepts(cap2), 'Pattern requiring ext should reject instance missing ext');
|
|
459
450
|
assert(!cap2.accepts(cap1), 'Pattern requiring format should reject instance missing format');
|
|
460
451
|
assert(!cap1.accepts(cap3), 'Different op should not accept');
|
|
461
452
|
assert(!cap3.accepts(cap1), 'Different op should not accept in reverse');
|
|
462
453
|
|
|
463
|
-
const general = CapUrn.fromString(
|
|
454
|
+
const general = CapUrn.fromString(test6204_Urn('generate'));
|
|
464
455
|
assert(general.accepts(cap1), 'General request should accept more specific instance');
|
|
465
456
|
assert(!cap1.accepts(general), 'Specific pattern should reject general instance');
|
|
466
457
|
|
|
@@ -469,22 +460,22 @@ function test024_compatibility() {
|
|
|
469
460
|
assert(broadIn.accepts(cap1), 'Wildcard input should accept specific input');
|
|
470
461
|
}
|
|
471
462
|
|
|
472
|
-
//
|
|
463
|
+
// TEST25: Test find_best_match returns most specific matching cap
|
|
473
464
|
function test025_bestMatch() {
|
|
474
465
|
const caps = [
|
|
475
466
|
CapUrn.fromString('cap:in=*;out=*;op'),
|
|
476
|
-
CapUrn.fromString(
|
|
477
|
-
CapUrn.fromString(
|
|
467
|
+
CapUrn.fromString(test6204_Urn('generate')),
|
|
468
|
+
CapUrn.fromString(test6204_Urn('generate;ext=pdf'))
|
|
478
469
|
];
|
|
479
|
-
const request = CapUrn.fromString(
|
|
470
|
+
const request = CapUrn.fromString(test6204_Urn('generate'));
|
|
480
471
|
const best = CapMatcher.findBestMatch(caps, request);
|
|
481
472
|
assert(best !== null, 'Should find a best match');
|
|
482
473
|
assertEqual(best.getTag('ext'), 'pdf', 'Best match should be the most specific (ext=pdf)');
|
|
483
474
|
}
|
|
484
475
|
|
|
485
|
-
//
|
|
476
|
+
// TEST26: Test merge combines tags from both caps, subset keeps only specified tags
|
|
486
477
|
function test026_mergeAndSubset() {
|
|
487
|
-
const cap1 = CapUrn.fromString(
|
|
478
|
+
const cap1 = CapUrn.fromString(test6204_Urn('generate'));
|
|
488
479
|
const cap2 = CapUrn.fromString('cap:in="media:enc=utf-8";ext=pdf;format=binary;out="media:"');
|
|
489
480
|
|
|
490
481
|
// Merge (other takes precedence)
|
|
@@ -501,9 +492,9 @@ function test026_mergeAndSubset() {
|
|
|
501
492
|
assertEqual(sub.getInSpec(), 'media:enc=utf-8', 'Subset should preserve inSpec');
|
|
502
493
|
}
|
|
503
494
|
|
|
504
|
-
//
|
|
495
|
+
// TEST27: Test with_wildcard_tag sets tag to wildcard, including in/out
|
|
505
496
|
function test027_wildcardTag() {
|
|
506
|
-
const cap = CapUrn.fromString(
|
|
497
|
+
const cap = CapUrn.fromString(test6204_Urn('ext=pdf'));
|
|
507
498
|
const wildcardExt = cap.withWildcardTag('ext');
|
|
508
499
|
assertEqual(wildcardExt.getTag('ext'), '*', 'Should set ext to wildcard');
|
|
509
500
|
|
|
@@ -514,7 +505,7 @@ function test027_wildcardTag() {
|
|
|
514
505
|
assertEqual(wildcardOut.getOutSpec(), 'media:', 'Should set out to canonical top media:');
|
|
515
506
|
}
|
|
516
507
|
|
|
517
|
-
//
|
|
508
|
+
// TEST28: Test empty cap URN is illegal after effect transition
|
|
518
509
|
function test028_emptyCapUrnNotAllowed() {
|
|
519
510
|
assertThrows(
|
|
520
511
|
() => CapUrn.fromString('cap:'),
|
|
@@ -523,7 +514,7 @@ function test028_emptyCapUrnNotAllowed() {
|
|
|
523
514
|
);
|
|
524
515
|
}
|
|
525
516
|
|
|
526
|
-
//
|
|
517
|
+
// TEST29: Test minimal valid cap URN has just in and out, empty tags
|
|
527
518
|
function test029_minimalCapUrn() {
|
|
528
519
|
const minimal = CapUrn.fromString('cap:in="media:void";out="media:void"');
|
|
529
520
|
assertEqual(Object.keys(minimal.tags).length, 0, 'Should have no other tags');
|
|
@@ -531,23 +522,23 @@ function test029_minimalCapUrn() {
|
|
|
531
522
|
assertEqual(minimal.getOutSpec(), 'media:void', 'Should have outSpec');
|
|
532
523
|
}
|
|
533
524
|
|
|
534
|
-
//
|
|
525
|
+
// TEST30: Test extended characters (forward slashes, colons) in tag values
|
|
535
526
|
function test030_extendedCharacterSupport() {
|
|
536
|
-
const cap = CapUrn.fromString(
|
|
527
|
+
const cap = CapUrn.fromString(test6204_Urn('url=https://example_org/api;path=/some/file'));
|
|
537
528
|
assertEqual(cap.getTag('url'), 'https://example_org/api', 'Should support colons and slashes');
|
|
538
529
|
assertEqual(cap.getTag('path'), '/some/file', 'Should support forward slashes');
|
|
539
530
|
}
|
|
540
531
|
|
|
541
|
-
//
|
|
532
|
+
// TEST31: Test wildcard rejected in keys but accepted in values
|
|
542
533
|
function test031_wildcardRestrictions() {
|
|
543
534
|
assertThrows(
|
|
544
|
-
() => CapUrn.fromString(
|
|
535
|
+
() => CapUrn.fromString(test6204_Urn('*=value')),
|
|
545
536
|
ErrorCodes.INVALID_CHARACTER,
|
|
546
537
|
'Should reject wildcard in key'
|
|
547
538
|
);
|
|
548
539
|
|
|
549
540
|
// Wildcard accepted in values
|
|
550
|
-
const cap = CapUrn.fromString(
|
|
541
|
+
const cap = CapUrn.fromString(test6204_Urn('key=*'));
|
|
551
542
|
assertEqual(cap.getTag('key'), '*', 'Should accept wildcard in value');
|
|
552
543
|
|
|
553
544
|
// Wildcard in in/out normalizes to media:
|
|
@@ -556,30 +547,30 @@ function test031_wildcardRestrictions() {
|
|
|
556
547
|
assertEqual(capWild.getOutSpec(), MEDIA_IDENTITY, 'Wildcard outSpec should normalize to media:');
|
|
557
548
|
}
|
|
558
549
|
|
|
559
|
-
//
|
|
550
|
+
// TEST32: Test duplicate keys are rejected with DuplicateKey error
|
|
560
551
|
function test032_duplicateKeyRejection() {
|
|
561
552
|
assertThrows(
|
|
562
|
-
() => CapUrn.fromString(
|
|
553
|
+
() => CapUrn.fromString(test6204_Urn('key=value1;key=value2')),
|
|
563
554
|
ErrorCodes.DUPLICATE_KEY,
|
|
564
555
|
'Should reject duplicate keys'
|
|
565
556
|
);
|
|
566
557
|
}
|
|
567
558
|
|
|
568
|
-
//
|
|
559
|
+
// TEST33: Test pure numeric keys rejected, mixed alphanumeric allowed, numeric values allowed
|
|
569
560
|
function test033_numericKeyRestriction() {
|
|
570
561
|
assertThrows(
|
|
571
|
-
() => CapUrn.fromString(
|
|
562
|
+
() => CapUrn.fromString(test6204_Urn('123=value')),
|
|
572
563
|
ErrorCodes.NUMERIC_KEY,
|
|
573
564
|
'Should reject pure numeric keys'
|
|
574
565
|
);
|
|
575
566
|
// Mixed alphanumeric allowed
|
|
576
|
-
const cap1 = CapUrn.fromString(
|
|
567
|
+
const cap1 = CapUrn.fromString(test6204_Urn('key123=value'));
|
|
577
568
|
assertEqual(cap1.getTag('key123'), 'value', 'Mixed alphanumeric key should be allowed');
|
|
578
|
-
const cap2 = CapUrn.fromString(
|
|
569
|
+
const cap2 = CapUrn.fromString(test6204_Urn('x123key=value'));
|
|
579
570
|
assertEqual(cap2.getTag('x123key'), 'value', 'Mixed alphanumeric key should be allowed');
|
|
580
571
|
}
|
|
581
572
|
|
|
582
|
-
//
|
|
573
|
+
// TEST34: Test empty values are rejected
|
|
583
574
|
function test034_emptyValueError() {
|
|
584
575
|
let threw = false;
|
|
585
576
|
try {
|
|
@@ -592,7 +583,7 @@ function test034_emptyValueError() {
|
|
|
592
583
|
assert(threw, 'Empty value (key=) should be rejected');
|
|
593
584
|
}
|
|
594
585
|
|
|
595
|
-
//
|
|
586
|
+
// TEST35: Test has_tag is case-sensitive for values, case-insensitive for keys, works for in/out
|
|
596
587
|
function test035_hasTagCaseSensitive() {
|
|
597
588
|
const cap = CapUrn.fromString('cap:in="media:void";key="Value";out="media:void"');
|
|
598
589
|
assert(cap.hasTag('key', 'Value'), 'hasTag should match exact value');
|
|
@@ -604,14 +595,14 @@ function test035_hasTagCaseSensitive() {
|
|
|
604
595
|
assert(cap.hasTag('out', 'media:void'), 'hasTag should work for out');
|
|
605
596
|
}
|
|
606
597
|
|
|
607
|
-
//
|
|
598
|
+
// TEST36: Test with_tag preserves value case
|
|
608
599
|
function test036_withTagPreservesValue() {
|
|
609
600
|
const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
|
|
610
601
|
const modified = cap.withTag('key', 'MyValue');
|
|
611
602
|
assertEqual(modified.getTag('key'), 'MyValue', 'withTag should preserve value case');
|
|
612
603
|
}
|
|
613
604
|
|
|
614
|
-
//
|
|
605
|
+
// TEST37: Test with_tag rejects empty value
|
|
615
606
|
function test037_withTagRejectsEmptyValue() {
|
|
616
607
|
const cap = CapUrn.fromString('cap:in="media:void";out="media:void"');
|
|
617
608
|
assertThrows(
|
|
@@ -621,7 +612,7 @@ function test037_withTagRejectsEmptyValue() {
|
|
|
621
612
|
);
|
|
622
613
|
}
|
|
623
614
|
|
|
624
|
-
//
|
|
615
|
+
// TEST38: Test semantic equivalence of unquoted and quoted simple lowercase values
|
|
625
616
|
function test038_semanticEquivalence() {
|
|
626
617
|
const c1 = CapUrn.fromString('cap:in="media:void";key=simple;out="media:void"');
|
|
627
618
|
const c2 = CapUrn.fromString('cap:in="media:void";key="simple";out="media:void"');
|
|
@@ -629,7 +620,7 @@ function test038_semanticEquivalence() {
|
|
|
629
620
|
assertEqual(c1.getTag('key'), c2.getTag('key'), 'Values should be identical');
|
|
630
621
|
}
|
|
631
622
|
|
|
632
|
-
//
|
|
623
|
+
// TEST39: Test get_tag returns direction specs (in/out) with case-insensitive lookup
|
|
633
624
|
function test039_getTagReturnsDirectionSpecs() {
|
|
634
625
|
const cap = CapUrn.fromString(`cap:in="${MEDIA_VOID}";out="${MEDIA_OBJECT}"`);
|
|
635
626
|
assertEqual(cap.getTag('in'), MEDIA_VOID, 'getTag(in) should return inSpec');
|
|
@@ -638,51 +629,51 @@ function test039_getTagReturnsDirectionSpecs() {
|
|
|
638
629
|
assertEqual(cap.getTag('OUT'), MEDIA_OBJECT, 'getTag(OUT) should return outSpec (case-insensitive)');
|
|
639
630
|
}
|
|
640
631
|
|
|
641
|
-
//
|
|
632
|
+
// TEST40: Matching semantics - exact match succeeds
|
|
642
633
|
function test040_matchingSemanticsExactMatch() {
|
|
643
|
-
const cap = CapUrn.fromString(
|
|
644
|
-
const request = CapUrn.fromString(
|
|
634
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
635
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
645
636
|
assert(cap.accepts(request), 'Exact match should accept');
|
|
646
637
|
}
|
|
647
638
|
|
|
648
|
-
//
|
|
639
|
+
// TEST41: Matching semantics - cap missing tag matches (implicit wildcard)
|
|
649
640
|
function test041_matchingSemanticsCapMissingTag() {
|
|
650
|
-
const cap = CapUrn.fromString(
|
|
651
|
-
const request = CapUrn.fromString(
|
|
641
|
+
const cap = CapUrn.fromString(test6204_Urn('generate'));
|
|
642
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
652
643
|
assert(cap.accepts(request), 'General pattern with only op should accept specific instance');
|
|
653
644
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
654
645
|
}
|
|
655
646
|
|
|
656
|
-
//
|
|
647
|
+
// TEST42: Pattern rejects instance missing required tags
|
|
657
648
|
function test042_matchingSemanticsCapHasExtraTag() {
|
|
658
|
-
const cap = CapUrn.fromString(
|
|
659
|
-
const request = CapUrn.fromString(
|
|
649
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf;version=2'));
|
|
650
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
660
651
|
assert(!cap.accepts(request), 'Pattern requiring version should reject instance missing version');
|
|
661
652
|
assert(request.accepts(cap), 'General request should accept refined instance');
|
|
662
653
|
}
|
|
663
654
|
|
|
664
|
-
//
|
|
655
|
+
// TEST43: Matching semantics - request wildcard matches specific cap value
|
|
665
656
|
function test043_matchingSemanticsRequestHasWildcard() {
|
|
666
|
-
const cap = CapUrn.fromString(
|
|
667
|
-
const request = CapUrn.fromString(
|
|
657
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
658
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=*'));
|
|
668
659
|
assert(cap.accepts(request), 'Request wildcard should match specific cap value');
|
|
669
660
|
}
|
|
670
661
|
|
|
671
|
-
//
|
|
662
|
+
// TEST44: Matching semantics - cap wildcard matches specific request value
|
|
672
663
|
function test044_matchingSemanticsCapHasWildcard() {
|
|
673
|
-
const cap = CapUrn.fromString(
|
|
674
|
-
const request = CapUrn.fromString(
|
|
664
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=*'));
|
|
665
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
675
666
|
assert(cap.accepts(request), 'Cap wildcard should match specific request value');
|
|
676
667
|
}
|
|
677
668
|
|
|
678
|
-
//
|
|
669
|
+
// TEST45: Matching semantics - value mismatch does not match
|
|
679
670
|
function test045_matchingSemanticsValueMismatch() {
|
|
680
|
-
const cap = CapUrn.fromString(
|
|
681
|
-
const request = CapUrn.fromString(
|
|
671
|
+
const cap = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
672
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=docx'));
|
|
682
673
|
assert(!cap.accepts(request), 'Value mismatch should not accept');
|
|
683
674
|
}
|
|
684
675
|
|
|
685
|
-
//
|
|
676
|
+
// TEST46: Matching semantics - fallback pattern (cap missing tag = implicit wildcard)
|
|
686
677
|
function test046_matchingSemanticsFallbackPattern() {
|
|
687
678
|
const cap = CapUrn.fromString('cap:in="media:binary";generate-thumbnail;out="media:binary"');
|
|
688
679
|
const request = CapUrn.fromString('cap:ext=wav;in="media:binary";generate-thumbnail;out="media:binary"');
|
|
@@ -690,29 +681,29 @@ function test046_matchingSemanticsFallbackPattern() {
|
|
|
690
681
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
691
682
|
}
|
|
692
683
|
|
|
693
|
-
//
|
|
684
|
+
// TEST47: Matching semantics - thumbnail fallback with void input
|
|
694
685
|
function test047_matchingSemanticsThumbnailVoidInput() {
|
|
695
686
|
const cap = CapUrn.fromString('cap:in="media:void";generate-thumbnail;out="media:ext=png;image;thumbnail"');
|
|
696
687
|
const request = CapUrn.fromString('cap:ext=pdf;in="media:void";generate-thumbnail;out="media:image"');
|
|
697
688
|
assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
|
|
698
689
|
}
|
|
699
690
|
|
|
700
|
-
//
|
|
691
|
+
// TEST48: Matching semantics - wildcard direction matches anything
|
|
701
692
|
function test048_matchingSemanticsWildcardDirection() {
|
|
702
693
|
const cap = CapUrn.fromString('cap:generate');
|
|
703
|
-
const request = CapUrn.fromString(
|
|
694
|
+
const request = CapUrn.fromString(test6204_Urn('generate;ext=pdf'));
|
|
704
695
|
assert(cap.accepts(request), 'Generic declared directions should accept a more specific matching request');
|
|
705
696
|
}
|
|
706
697
|
|
|
707
|
-
//
|
|
698
|
+
// TEST49: Non-overlapping tags — neither direction accepts
|
|
708
699
|
function test049_matchingSemanticsCrossDimension() {
|
|
709
|
-
const cap = CapUrn.fromString(
|
|
710
|
-
const request = CapUrn.fromString(
|
|
700
|
+
const cap = CapUrn.fromString(test6204_Urn('generate'));
|
|
701
|
+
const request = CapUrn.fromString(test6204_Urn('ext=pdf'));
|
|
711
702
|
assert(!cap.accepts(request), 'Pattern requiring op should reject instance missing op');
|
|
712
703
|
assert(!request.accepts(cap), 'Pattern requiring ext should reject instance missing ext');
|
|
713
704
|
}
|
|
714
705
|
|
|
715
|
-
//
|
|
706
|
+
// TEST50: Matching semantics - direction mismatch prevents matching
|
|
716
707
|
function test050_matchingSemanticsDirectionMismatch() {
|
|
717
708
|
const cap = CapUrn.fromString(
|
|
718
709
|
`cap:in="${MEDIA_STRING}";generate;out="${MEDIA_OBJECT}"`
|
|
@@ -773,10 +764,7 @@ function test890_directionSemanticMatching() {
|
|
|
773
764
|
'Generic output cap must NOT satisfy specific output request');
|
|
774
765
|
}
|
|
775
766
|
|
|
776
|
-
// TEST891: Semantic direction specificity — more constraints in
|
|
777
|
-
// either axis means a higher score under the truth-table-driven sum.
|
|
778
|
-
// media: (top, no tags) scores 0; each marker tag scores 2; each
|
|
779
|
-
// exact-value tag (e.g. ext=png) scores 4.
|
|
767
|
+
// TEST891: Semantic direction specificity — more constraints in either axis means a higher score under the truth-table-driven sum. media: (top, no tags) scores 0; each marker tag scores 2; each exact tag scores 3.
|
|
780
768
|
function test891_directionSemanticSpecificity() {
|
|
781
769
|
const genericCap = CapUrn.fromString(
|
|
782
770
|
'cap:in="media:";generate-thumbnail;out="media:ext=png;image;thumbnail"'
|
|
@@ -814,10 +802,10 @@ function test891_directionSemanticSpecificity() {
|
|
|
814
802
|
// validation.rs: TEST053-TEST056
|
|
815
803
|
// ============================================================================
|
|
816
804
|
|
|
817
|
-
//
|
|
805
|
+
// TEST6208: N/A for JS (Rust-only validation infrastructure)
|
|
818
806
|
|
|
819
|
-
//
|
|
820
|
-
function
|
|
807
|
+
// TEST6212: XV5 - Test inline media def redefinition of existing registry spec is detected and rejected
|
|
808
|
+
function test6212_xv5InlineSpecRedefinitionDetected() {
|
|
821
809
|
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
822
810
|
const mediaDefs = [
|
|
823
811
|
{
|
|
@@ -833,8 +821,8 @@ function test054_xv5InlineSpecRedefinitionDetected() {
|
|
|
833
821
|
assert(result.redefines && result.redefines.includes(MEDIA_STRING), 'Should identify MEDIA_STRING as redefined');
|
|
834
822
|
}
|
|
835
823
|
|
|
836
|
-
//
|
|
837
|
-
function
|
|
824
|
+
// TEST6216: XV5 - Test new inline media def (not in registry) is allowed
|
|
825
|
+
function test6216_xv5NewInlineSpecAllowed() {
|
|
838
826
|
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
839
827
|
const mediaDefs = [
|
|
840
828
|
{
|
|
@@ -848,8 +836,8 @@ function test055_xv5NewInlineSpecAllowed() {
|
|
|
848
836
|
assert(result.valid, 'New spec not in registry should pass validation');
|
|
849
837
|
}
|
|
850
838
|
|
|
851
|
-
//
|
|
852
|
-
function
|
|
839
|
+
// TEST6220: XV5 - Test empty media_defs (no inline specs) passes XV5 validation
|
|
840
|
+
function test6220_xv5EmptyMediaDefsAllowed() {
|
|
853
841
|
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
854
842
|
assert(validateNoMediaDefRedefinitionSync({}, registryLookup).valid, 'Empty object should pass');
|
|
855
843
|
assert(validateNoMediaDefRedefinitionSync(null, registryLookup).valid, 'Null should pass');
|
|
@@ -860,7 +848,7 @@ function test056_xv5EmptyMediaDefsAllowed() {
|
|
|
860
848
|
// media_urn.rs: TEST060-TEST078
|
|
861
849
|
// ============================================================================
|
|
862
850
|
|
|
863
|
-
//
|
|
851
|
+
// TEST60: Test wrong prefix fails with InvalidPrefix error showing expected and actual prefix
|
|
864
852
|
function test060_wrongPrefixFails() {
|
|
865
853
|
assertThrowsMediaUrn(
|
|
866
854
|
() => MediaUrn.fromString('cap:string'),
|
|
@@ -873,7 +861,7 @@ function test060_wrongPrefixFails() {
|
|
|
873
861
|
// vocabulary (isBinary() was deleted from MediaUrn; everything is bytes).
|
|
874
862
|
// Encoding is now expressed by the orthogonal `enc=` tag, exercised by TEST067.
|
|
875
863
|
|
|
876
|
-
//
|
|
864
|
+
// TEST62: Test is_record returns true when record marker tag is present indicating key-value structure
|
|
877
865
|
function test062_isRecord() {
|
|
878
866
|
assert(MediaUrn.fromString(MEDIA_OBJECT).isRecord(), 'MEDIA_OBJECT should be record');
|
|
879
867
|
assert(MediaUrn.fromString('media:custom;record').isRecord(), 'custom;record should be record');
|
|
@@ -884,7 +872,7 @@ function test062_isRecord() {
|
|
|
884
872
|
assert(!MediaUrn.fromString(MEDIA_STRING_LIST).isRecord(), 'MEDIA_STRING_LIST should not be record');
|
|
885
873
|
}
|
|
886
874
|
|
|
887
|
-
//
|
|
875
|
+
// TEST63: Test is_scalar returns true when list marker tag is absent (scalar is default)
|
|
888
876
|
function test063_isScalar() {
|
|
889
877
|
assert(MediaUrn.fromString(MEDIA_STRING).isScalar(), 'MEDIA_STRING should be scalar');
|
|
890
878
|
assert(MediaUrn.fromString(MEDIA_INTEGER).isScalar(), 'MEDIA_INTEGER should be scalar');
|
|
@@ -897,7 +885,7 @@ function test063_isScalar() {
|
|
|
897
885
|
assert(!MediaUrn.fromString(MEDIA_OBJECT_LIST).isScalar(), 'MEDIA_OBJECT_LIST should not be scalar');
|
|
898
886
|
}
|
|
899
887
|
|
|
900
|
-
//
|
|
888
|
+
// TEST64: Test is_list returns true when list marker tag is present indicating ordered collection
|
|
901
889
|
function test064_isList() {
|
|
902
890
|
assert(MediaUrn.fromString(MEDIA_STRING_LIST).isList(), 'MEDIA_STRING_LIST should be list');
|
|
903
891
|
assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isList(), 'MEDIA_INTEGER_LIST should be list');
|
|
@@ -906,7 +894,7 @@ function test064_isList() {
|
|
|
906
894
|
assert(!MediaUrn.fromString(MEDIA_OBJECT).isList(), 'MEDIA_OBJECT should not be list');
|
|
907
895
|
}
|
|
908
896
|
|
|
909
|
-
//
|
|
897
|
+
// TEST65: Test is_opaque returns true when record marker is absent (opaque is default)
|
|
910
898
|
function test065_isOpaque() {
|
|
911
899
|
assert(MediaUrn.fromString(MEDIA_STRING).isOpaque(), 'MEDIA_STRING should be opaque');
|
|
912
900
|
assert(MediaUrn.fromString(MEDIA_STRING_LIST).isOpaque(), 'MEDIA_STRING_LIST (list but no record) should be opaque');
|
|
@@ -917,7 +905,7 @@ function test065_isOpaque() {
|
|
|
917
905
|
assert(!MediaUrn.fromString(MEDIA_JSON).isOpaque(), 'MEDIA_JSON should not be opaque');
|
|
918
906
|
}
|
|
919
907
|
|
|
920
|
-
//
|
|
908
|
+
// TEST66: Test is_json returns true only when json marker tag is present for JSON representation
|
|
921
909
|
function test066_isJson() {
|
|
922
910
|
assert(MediaUrn.fromString(MEDIA_JSON).isJson(), 'MEDIA_JSON should be json');
|
|
923
911
|
assert(MediaUrn.fromString('media:custom;fmt=json').isJson(), 'fmt=json should be json');
|
|
@@ -926,9 +914,7 @@ function test066_isJson() {
|
|
|
926
914
|
assert(!MediaUrn.fromString('media:enc=utf-8').isJson(), 'plain text should not be json');
|
|
927
915
|
}
|
|
928
916
|
|
|
929
|
-
//
|
|
930
|
-
// (the old text marker and isText() are gone). A media is "text" iff it
|
|
931
|
-
// declares an encoding.
|
|
917
|
+
// TEST67: Text-representability is now carried by the orthogonal `enc=` tag (the old `textable` marker and is_text() are gone). A media is "text" iff it declares an encoding. enc is orthogonal to format/numeric, so only media that actually carry enc= are text.
|
|
932
918
|
function test067_isText() {
|
|
933
919
|
// Has enc= → text-representable
|
|
934
920
|
assert(MediaUrn.fromString(MEDIA_STRING).getTag('enc') !== undefined, 'MEDIA_STRING should have enc');
|
|
@@ -941,15 +927,15 @@ function test067_isText() {
|
|
|
941
927
|
assert(MediaUrn.fromString(MEDIA_OBJECT).getTag('enc') === undefined, 'MEDIA_OBJECT should not have enc');
|
|
942
928
|
}
|
|
943
929
|
|
|
944
|
-
//
|
|
930
|
+
// TEST68: Test is_void returns true when void flag or type=void tag is present
|
|
945
931
|
function test068_isVoid() {
|
|
946
932
|
assert(MediaUrn.fromString('media:void').isVoid(), 'media:void should be void');
|
|
947
933
|
assert(!MediaUrn.fromString(MEDIA_STRING).isVoid(), 'MEDIA_STRING should not be void');
|
|
948
934
|
}
|
|
949
935
|
|
|
950
|
-
// TEST069-
|
|
936
|
+
// TEST069-TEST6240: N/A for JS (Rust-only binary_media_urn_for_ext/text_media_urn_for_ext)
|
|
951
937
|
|
|
952
|
-
//
|
|
938
|
+
// TEST71: Test to_string roundtrip ensures serialization and deserialization preserve URN structure
|
|
953
939
|
function test071_toStringRoundtrip() {
|
|
954
940
|
const constants = [MEDIA_STRING, MEDIA_INTEGER, MEDIA_OBJECT, MEDIA_IDENTITY, MEDIA_PDF, MEDIA_JSON];
|
|
955
941
|
for (const constant of constants) {
|
|
@@ -959,7 +945,7 @@ function test071_toStringRoundtrip() {
|
|
|
959
945
|
}
|
|
960
946
|
}
|
|
961
947
|
|
|
962
|
-
//
|
|
948
|
+
// TEST72: Test all media URN constants parse successfully as valid media URNs
|
|
963
949
|
function test072_constantsParse() {
|
|
964
950
|
const constants = [
|
|
965
951
|
MEDIA_STRING, MEDIA_INTEGER, MEDIA_NUMBER, MEDIA_BOOLEAN,
|
|
@@ -977,9 +963,9 @@ function test072_constantsParse() {
|
|
|
977
963
|
}
|
|
978
964
|
}
|
|
979
965
|
|
|
980
|
-
//
|
|
966
|
+
// TEST6242: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
|
|
981
967
|
|
|
982
|
-
//
|
|
968
|
+
// TEST74: Test media URN conforms_to using tagged URN semantics with specific and generic requirements
|
|
983
969
|
function test074_mediaUrnMatching() {
|
|
984
970
|
const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
|
|
985
971
|
const pdfPattern = MediaUrn.fromString('media:ext=pdf');
|
|
@@ -993,7 +979,7 @@ function test074_mediaUrnMatching() {
|
|
|
993
979
|
assert(pdfUrn.conformsTo(pdfUrn), 'Same URN should conform to itself');
|
|
994
980
|
}
|
|
995
981
|
|
|
996
|
-
//
|
|
982
|
+
// TEST75: Test accepts with implicit wildcards where handlers with fewer tags can handle more requests
|
|
997
983
|
function test075_accepts() {
|
|
998
984
|
const handler = MediaUrn.fromString(MEDIA_PDF);
|
|
999
985
|
const sameReq = MediaUrn.fromString(MEDIA_PDF);
|
|
@@ -1004,7 +990,7 @@ function test075_accepts() {
|
|
|
1004
990
|
assert(generalHandler.accepts(specificReq), 'General handler should accept specific request');
|
|
1005
991
|
}
|
|
1006
992
|
|
|
1007
|
-
//
|
|
993
|
+
// TEST76: Test specificity increases with more tags for ranking conformance
|
|
1008
994
|
function test076_specificity() {
|
|
1009
995
|
const s1 = MediaUrn.fromString('media:');
|
|
1010
996
|
const s2 = MediaUrn.fromString('media:ext=pdf');
|
|
@@ -1013,7 +999,7 @@ function test076_specificity() {
|
|
|
1013
999
|
assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
|
|
1014
1000
|
}
|
|
1015
1001
|
|
|
1016
|
-
//
|
|
1002
|
+
// TEST77: Test serde roundtrip serializes to JSON string and deserializes back correctly
|
|
1017
1003
|
function test077_serdeRoundtrip() {
|
|
1018
1004
|
const original = MediaUrn.fromString(MEDIA_PDF);
|
|
1019
1005
|
const json = JSON.stringify({ urn: original.toString() });
|
|
@@ -1022,7 +1008,7 @@ function test077_serdeRoundtrip() {
|
|
|
1022
1008
|
assert(original.equals(restored), 'JSON round-trip should preserve MediaUrn');
|
|
1023
1009
|
}
|
|
1024
1010
|
|
|
1025
|
-
//
|
|
1011
|
+
// TEST78: conforms_to behavior between MEDIA_OBJECT and MEDIA_STRING
|
|
1026
1012
|
function test078_debugMatchingBehavior() {
|
|
1027
1013
|
const objUrn = MediaUrn.fromString(MEDIA_OBJECT);
|
|
1028
1014
|
const strUrn = MediaUrn.fromString(MEDIA_STRING);
|
|
@@ -1033,12 +1019,12 @@ function test078_debugMatchingBehavior() {
|
|
|
1033
1019
|
// media_def.rs: TEST088-TEST110
|
|
1034
1020
|
// ============================================================================
|
|
1035
1021
|
|
|
1036
|
-
//
|
|
1037
|
-
//
|
|
1038
|
-
//
|
|
1022
|
+
// TEST6277: N/A for JS (async registry, Rust-only)
|
|
1023
|
+
// TEST6279: N/A for JS
|
|
1024
|
+
// TEST6280: N/A for JS
|
|
1039
1025
|
|
|
1040
|
-
//
|
|
1041
|
-
function
|
|
1026
|
+
// TEST6282: Test resolving a custom media URN from a registry-seeded media def
|
|
1027
|
+
function test6282_resolveCustomMediaDef() {
|
|
1042
1028
|
const mediaDefs = [
|
|
1043
1029
|
{ urn: 'media:custom-json', media_type: 'application/json', title: 'Custom JSON', profile_uri: 'https://example.com/schema/custom' }
|
|
1044
1030
|
];
|
|
@@ -1047,8 +1033,8 @@ function test091_resolveCustomMediaDef() {
|
|
|
1047
1033
|
assertEqual(spec.profile, 'https://example.com/schema/custom', 'Should have custom profile');
|
|
1048
1034
|
}
|
|
1049
1035
|
|
|
1050
|
-
//
|
|
1051
|
-
function
|
|
1036
|
+
// TEST6283: Test resolving a custom record media def carrying a schema from a registry-seeded media def
|
|
1037
|
+
function test6283_resolveCustomWithSchema() {
|
|
1052
1038
|
const mediaDefs = [
|
|
1053
1039
|
{
|
|
1054
1040
|
urn: 'media:rich-xml',
|
|
@@ -1064,8 +1050,8 @@ function test092_resolveCustomWithSchema() {
|
|
|
1064
1050
|
assertEqual(spec.schema.type, 'object', 'Schema should have correct type');
|
|
1065
1051
|
}
|
|
1066
1052
|
|
|
1067
|
-
//
|
|
1068
|
-
function
|
|
1053
|
+
// TEST93: Test resolving unknown media URN fails with UnresolvableMediaUrn error
|
|
1054
|
+
function test93_resolveUnresolvableFailsHard() {
|
|
1069
1055
|
let caught = false;
|
|
1070
1056
|
try {
|
|
1071
1057
|
resolveMediaUrn('media:nonexistent', []);
|
|
@@ -1077,14 +1063,14 @@ function test093_resolveUnresolvableFailsHard() {
|
|
|
1077
1063
|
assert(caught, 'Should fail hard on unresolvable media URN');
|
|
1078
1064
|
}
|
|
1079
1065
|
|
|
1080
|
-
//
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
//
|
|
1084
|
-
//
|
|
1066
|
+
// TEST6286: N/A for JS (no registry concept)
|
|
1067
|
+
// TEST6288: N/A for JS (Rust serde)
|
|
1068
|
+
// TEST6290: N/A for JS (Rust serde)
|
|
1069
|
+
// TEST6292: N/A for JS (Rust validation function)
|
|
1070
|
+
// TEST6294: N/A for JS
|
|
1085
1071
|
|
|
1086
|
-
//
|
|
1087
|
-
function
|
|
1072
|
+
// TEST99: Test ResolvedMediaDef is_binary returns true when enc tag is absent
|
|
1073
|
+
function test99_resolvedIsBinary() {
|
|
1088
1074
|
const spec = new MediaDef('application/octet-stream', null, null, 'Binary', null, MEDIA_IDENTITY);
|
|
1089
1075
|
assert(spec.isBinary(), 'Resolved binary spec should be binary');
|
|
1090
1076
|
}
|
|
@@ -1283,7 +1269,7 @@ function test116_capArgConstructors() {
|
|
|
1283
1269
|
|
|
1284
1270
|
// TEST150: JSON roundtrip
|
|
1285
1271
|
function test150_capManifestJsonSerialization() {
|
|
1286
|
-
const capUrn = CapUrn.fromString(
|
|
1272
|
+
const capUrn = CapUrn.fromString(test6204_Urn('extract;target=metadata'));
|
|
1287
1273
|
const cap = new Cap(capUrn, 'Extract Metadata', 'extract-metadata');
|
|
1288
1274
|
cap.addArg(new CapArg('media:ext=pdf', true, [new ArgSource({ stdin: 'media:ext=pdf' })]));
|
|
1289
1275
|
cap.addArg(new CapArg(
|
|
@@ -1381,7 +1367,7 @@ function test597_capArgWithFullDefinition() {
|
|
|
1381
1367
|
|
|
1382
1368
|
// Add a cap and check it becomes an edge with from/to nodes and carries the
|
|
1383
1369
|
// registry name we passed. This is exactly the shape the renderer depends on.
|
|
1384
|
-
function
|
|
1370
|
+
function test6206_CapFabAddCapPopulatesEdgesAndNodes() {
|
|
1385
1371
|
const graph = new CapFab();
|
|
1386
1372
|
const cap = makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text');
|
|
1387
1373
|
graph.addCap(cap, 'registry');
|
|
@@ -1399,7 +1385,7 @@ function test0052_CapFabAddCapPopulatesEdgesAndNodes() {
|
|
|
1399
1385
|
|
|
1400
1386
|
// getOutgoing takes a concrete source URN and returns edges whose from_spec
|
|
1401
1387
|
// the source conforms to. It must NOT be a plain string lookup.
|
|
1402
|
-
function
|
|
1388
|
+
function test6208_CapFabGetOutgoingConformsToMatching() {
|
|
1403
1389
|
const graph = new CapFab();
|
|
1404
1390
|
graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed text'), 'registry');
|
|
1405
1391
|
|
|
@@ -1420,7 +1406,7 @@ function test0053_CapFabGetOutgoingConformsToMatching() {
|
|
|
1420
1406
|
|
|
1421
1407
|
// Each edge must carry the registry name it was added with. This is how
|
|
1422
1408
|
// the renderer colours/groups edges by provenance in browse mode.
|
|
1423
|
-
function
|
|
1409
|
+
function test6224_CapFabDistinctRegistryNames() {
|
|
1424
1410
|
const graph = new CapFab();
|
|
1425
1411
|
graph.addCap(makeGraphCap('media:ext=pdf', 'media:enc=utf-8', 'PDF to Text'), 'providers');
|
|
1426
1412
|
graph.addCap(makeGraphCap('media:enc=utf-8', 'media:embedding-vector', 'Embed'), 'cartridges');
|
|
@@ -1616,7 +1602,7 @@ function test309_modelAvailabilityAndPathAreDistinct() {
|
|
|
1616
1602
|
assert(avail.toString() !== path.toString(), 'availability and path must be distinct');
|
|
1617
1603
|
}
|
|
1618
1604
|
|
|
1619
|
-
// TEST310: llm_generate_text_urn() produces a valid cap URN with
|
|
1605
|
+
// TEST310: llm_generate_text_urn() produces a valid cap URN with a UTF-8 text input and plain-text terminal output.
|
|
1620
1606
|
function test310_llmGenerateTextUrn() {
|
|
1621
1607
|
const urn = llmGenerateTextUrn();
|
|
1622
1608
|
assert(urn.hasMarkerTag('generate_text'), 'Must have generate_text marker');
|
|
@@ -1629,7 +1615,7 @@ function test310_llmGenerateTextUrn() {
|
|
|
1629
1615
|
}
|
|
1630
1616
|
|
|
1631
1617
|
// Mirror-specific coverage: llm_generate_text_urn input/output specs conform to MEDIA_STRING
|
|
1632
|
-
function
|
|
1618
|
+
function test6228_LlmGenerateTextUrnSpecs() {
|
|
1633
1619
|
const urn = llmGenerateTextUrn();
|
|
1634
1620
|
const inSpec = TaggedUrn.fromString(urn.getInSpec());
|
|
1635
1621
|
const expectedIn = TaggedUrn.fromString(MEDIA_STRING);
|
|
@@ -1660,7 +1646,7 @@ function test312_allUrnBuildersProduceValidUrns() {
|
|
|
1660
1646
|
// These tests cover JS-specific functionality not in the Rust numbering scheme
|
|
1661
1647
|
// but are important for capdag-js correctness.
|
|
1662
1648
|
|
|
1663
|
-
function
|
|
1649
|
+
function test6232_JS_buildExtensionIndex() {
|
|
1664
1650
|
const mediaDefs = [
|
|
1665
1651
|
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1666
1652
|
{ urn: 'media:ext=jpeg;image', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
|
|
@@ -1676,8 +1662,8 @@ function test0059_JS_buildExtensionIndex() {
|
|
|
1676
1662
|
assertEqual(index.get('pdf')[0], 'media:ext=pdf', 'pdf should map correctly');
|
|
1677
1663
|
}
|
|
1678
1664
|
|
|
1679
|
-
//
|
|
1680
|
-
function
|
|
1665
|
+
// TEST6236: J s media urns for extension
|
|
1666
|
+
function test6236_JS_mediaUrnsForExtension() {
|
|
1681
1667
|
const mediaDefs = [
|
|
1682
1668
|
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1683
1669
|
{ urn: 'media:fmt=json;record', media_type: 'application/json', extensions: ['json'] },
|
|
@@ -1706,7 +1692,7 @@ function test0069_JS_mediaUrnsForExtension() {
|
|
|
1706
1692
|
}
|
|
1707
1693
|
|
|
1708
1694
|
// TEST0070: J s get extension mappings
|
|
1709
|
-
function
|
|
1695
|
+
function test6240_JS_getExtensionMappings() {
|
|
1710
1696
|
const mediaDefs = [
|
|
1711
1697
|
{ urn: 'media:ext=pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1712
1698
|
{ urn: 'media:ext=jpeg;image', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
|
|
@@ -1717,7 +1703,7 @@ function test0070_JS_getExtensionMappings() {
|
|
|
1717
1703
|
}
|
|
1718
1704
|
|
|
1719
1705
|
// TEST0073: J s resolve media urn from specs
|
|
1720
|
-
function
|
|
1706
|
+
function test6242_JS_resolveMediaUrnFromSpecs() {
|
|
1721
1707
|
const mediaDefs = [
|
|
1722
1708
|
{ urn: MEDIA_STRING, media_type: 'text/plain', title: 'String', profile_uri: 'https://capdag.com/schema/str' },
|
|
1723
1709
|
{ urn: 'media:custom', media_type: 'application/json', title: 'Custom Output', schema: { type: 'object' } }
|
|
@@ -1729,9 +1715,9 @@ function test0073_JS_resolveMediaUrnFromSpecs() {
|
|
|
1729
1715
|
assert(outputSpec.schema !== null, 'Should have schema');
|
|
1730
1716
|
}
|
|
1731
1717
|
|
|
1732
|
-
//
|
|
1733
|
-
function
|
|
1734
|
-
const urn = CapUrn.fromString(
|
|
1718
|
+
// TEST6246: J s cap j s o n serialization
|
|
1719
|
+
function test6246_JS_capJSONSerialization() {
|
|
1720
|
+
const urn = CapUrn.fromString(test6204_Urn('test'));
|
|
1735
1721
|
const cap = new Cap(urn, 'Test Cap', 'test_command');
|
|
1736
1722
|
cap.arguments = {
|
|
1737
1723
|
required: [{ name: 'input', media_urn: MEDIA_STRING }],
|
|
@@ -1753,8 +1739,8 @@ function test0079_JS_capJSONSerialization() {
|
|
|
1753
1739
|
// backticks, embedded quotes, Unicode) so escaping mismatches between
|
|
1754
1740
|
// JSON.stringify on this side and the Rust serializer on the other side
|
|
1755
1741
|
// surface as failures here.
|
|
1756
|
-
function
|
|
1757
|
-
const urn = CapUrn.fromString(
|
|
1742
|
+
function test6249_JS_capDocumentationRoundTrip() {
|
|
1743
|
+
const urn = CapUrn.fromString(test6204_Urn('documented'));
|
|
1758
1744
|
const cap = new Cap(urn, 'Documented Cap', 'documented');
|
|
1759
1745
|
const body = '# Documented Cap\r\n\nDoes the thing.\n\n```bash\necho "hi"\n```\n\nSee also: \u2605\n';
|
|
1760
1746
|
cap.setDocumentation(body);
|
|
@@ -1775,8 +1761,8 @@ function test0080_JS_capDocumentationRoundTrip() {
|
|
|
1775
1761
|
// toDictionary behaviour. A regression where null is emitted as
|
|
1776
1762
|
// `documentation: null` would break the symmetric round-trip with Rust
|
|
1777
1763
|
// (which has no null sentinel) and pollute generated JSON.
|
|
1778
|
-
function
|
|
1779
|
-
const urn = CapUrn.fromString(
|
|
1764
|
+
function test6253_JS_capDocumentationOmittedWhenNull() {
|
|
1765
|
+
const urn = CapUrn.fromString(test6204_Urn('undocumented'));
|
|
1780
1766
|
const cap = new Cap(urn, 'Undocumented Cap', 'undocumented');
|
|
1781
1767
|
assertEqual(cap.getDocumentation(), null, 'Default documentation must be null');
|
|
1782
1768
|
|
|
@@ -1798,7 +1784,7 @@ function test0081_JS_capDocumentationOmittedWhenNull() {
|
|
|
1798
1784
|
// resolveMediaUrn into the resolved MediaDef. Mirrors TEST924 on the Rust
|
|
1799
1785
|
// side. This is the path every UI consumer uses, so a break here makes the
|
|
1800
1786
|
// new field invisible everywhere downstream.
|
|
1801
|
-
function
|
|
1787
|
+
function test6257_JS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
1802
1788
|
const body = '## Markdown body\n\nWith `code` and a [link](https://example.com).';
|
|
1803
1789
|
const mediaDefs = [
|
|
1804
1790
|
{
|
|
@@ -1828,23 +1814,23 @@ function test0082_JS_mediaDefDocumentationPropagatesThroughResolve() {
|
|
|
1828
1814
|
assertEqual(emptyDoc.documentation, null, 'Empty documentation string must collapse to null');
|
|
1829
1815
|
}
|
|
1830
1816
|
|
|
1831
|
-
//
|
|
1832
|
-
function
|
|
1817
|
+
// TEST6261: J s stdin source kind constants
|
|
1818
|
+
function test6261_JS_stdinSourceKindConstants() {
|
|
1833
1819
|
assert(StdinSourceKind.DATA !== undefined, 'DATA kind should be defined');
|
|
1834
1820
|
assert(StdinSourceKind.FILE_REFERENCE !== undefined, 'FILE_REFERENCE kind should be defined');
|
|
1835
1821
|
assert(StdinSourceKind.DATA !== StdinSourceKind.FILE_REFERENCE, 'Kind values should be distinct');
|
|
1836
1822
|
}
|
|
1837
1823
|
|
|
1838
|
-
//
|
|
1839
|
-
function
|
|
1824
|
+
// TEST6265: J s stdin source null data
|
|
1825
|
+
function test6265_JS_stdinSourceNullData() {
|
|
1840
1826
|
const source = StdinSource.fromData(null);
|
|
1841
1827
|
assert(source !== null, 'Should create source');
|
|
1842
1828
|
assert(source.isData(), 'Should be data source');
|
|
1843
1829
|
assertEqual(source.data, null, 'Data should be null');
|
|
1844
1830
|
}
|
|
1845
1831
|
|
|
1846
|
-
//
|
|
1847
|
-
function
|
|
1832
|
+
// TEST6269: J s media def construction
|
|
1833
|
+
function test6269_JS_mediaDefConstruction() {
|
|
1848
1834
|
const spec1 = new MediaDef('text/plain', 'https://capdag.com/schema/str', null, 'String', null, 'media:string');
|
|
1849
1835
|
assertEqual(spec1.contentType, 'text/plain', 'Should have content type');
|
|
1850
1836
|
assertEqual(spec1.profile, 'https://capdag.com/schema/str', 'Should have profile');
|
|
@@ -2037,7 +2023,7 @@ function test320_cartridgeInfoConstruction() {
|
|
|
2037
2023
|
assert(cartridge.allCaps().length === 1, 'allCaps() should return 1 cap');
|
|
2038
2024
|
}
|
|
2039
2025
|
|
|
2040
|
-
// TEST321: CartridgeInfo.is_signed() returns true when signature is present
|
|
2026
|
+
// TEST321: CartridgeInfo.is_signed() returns true when signature (team_id + signed_at) is present, false when either is empty.
|
|
2041
2027
|
function test321_cartridgeInfoIsSigned() {
|
|
2042
2028
|
const signed = new CartridgeInfo({id: 'test', registryUrl: 'https://test.example/manifest', channel: 'release', teamId: 'TEAM', signedAt: '2026-01-01', cap_groups: []});
|
|
2043
2029
|
assert(signed.isSigned() === true, 'Cartridge with teamId and signedAt should be signed');
|
|
@@ -2049,7 +2035,7 @@ function test321_cartridgeInfoIsSigned() {
|
|
|
2049
2035
|
assert(unsigned2.isSigned() === false, 'Cartridge without signedAt should not be signed');
|
|
2050
2036
|
}
|
|
2051
2037
|
|
|
2052
|
-
// TEST322: CartridgeInfo.build_for_platform() returns the build
|
|
2038
|
+
// TEST322: CartridgeInfo.build_for_platform() returns the build that matches the requested platform string and None otherwise.
|
|
2053
2039
|
function test322_cartridgeInfoBuildForPlatform() {
|
|
2054
2040
|
const withBuilds = new CartridgeInfo({
|
|
2055
2041
|
id: 'test', registryUrl: 'https://test.example/manifest', channel: 'release', version: '1.0.0', cap_groups: [],
|
|
@@ -2120,9 +2106,7 @@ function test323_cartridgeRepoServerValidateRegistry() {
|
|
|
2120
2106
|
assert(threw, 'Should throw when nightly channel is missing');
|
|
2121
2107
|
}
|
|
2122
2108
|
|
|
2123
|
-
// TEST324: CartridgeRepoServer
|
|
2124
|
-
// CartridgeInfo array preserving channel provenance. Release entries
|
|
2125
|
-
// appear first.
|
|
2109
|
+
// TEST324: CartridgeRepoServer transforms a v4.0 entry into a flat CartridgeInfo, preserving cap_groups verbatim.
|
|
2126
2110
|
function test324_cartridgeRepoServerTransformToArray() {
|
|
2127
2111
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
2128
2112
|
const cartridges = server.transformToCartridgeArray();
|
|
@@ -2159,8 +2143,7 @@ function test324_cartridgeRepoServerTransformToArray() {
|
|
|
2159
2143
|
assert(firstNightlyIdx > lastReleaseIdx, 'Release entries must precede nightly entries');
|
|
2160
2144
|
}
|
|
2161
2145
|
|
|
2162
|
-
// TEST325:
|
|
2163
|
-
// flat array (across both channels) in the response envelope.
|
|
2146
|
+
// TEST325: get_cartridges() wraps the transformed array in the response envelope.
|
|
2164
2147
|
function test325_cartridgeRepoServerGetCartridges() {
|
|
2165
2148
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
2166
2149
|
const response = server.getCartridges();
|
|
@@ -2172,9 +2155,7 @@ function test325_cartridgeRepoServerGetCartridges() {
|
|
|
2172
2155
|
'Every cartridge must carry a channel');
|
|
2173
2156
|
}
|
|
2174
2157
|
|
|
2175
|
-
// TEST326:
|
|
2176
|
-
// id). Same id looked up in the wrong channel must miss — channels are
|
|
2177
|
-
// independent namespaces.
|
|
2158
|
+
// TEST326: get_cartridge_by_id requires a channel and returns Some for a known (channel, id), None otherwise. The same id looked up in the wrong channel must miss — channels are independent namespaces.
|
|
2178
2159
|
function test326_cartridgeRepoServerGetCartridgeById() {
|
|
2179
2160
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
2180
2161
|
|
|
@@ -2204,9 +2185,7 @@ function test326_cartridgeRepoServerGetCartridgeById() {
|
|
|
2204
2185
|
assert(threw, 'Should throw for invalid channel');
|
|
2205
2186
|
}
|
|
2206
2187
|
|
|
2207
|
-
// TEST327:
|
|
2208
|
-
// channels by name/description/tags/cap titles. Cap URN strings are
|
|
2209
|
-
// not substring-matched.
|
|
2188
|
+
// TEST327: search_cartridges matches against name/description/tags and cap titles, but never against cap URN strings.
|
|
2210
2189
|
function test327_cartridgeRepoServerSearchCartridges() {
|
|
2211
2190
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
2212
2191
|
|
|
@@ -2321,8 +2300,7 @@ function test331_cartridgeRepoClientGetSuggestions() {
|
|
|
2321
2300
|
assert(nightlySuggestions[0].channel === 'nightly', 'Should report nightly channel');
|
|
2322
2301
|
}
|
|
2323
2302
|
|
|
2324
|
-
// TEST332:
|
|
2325
|
-
// Same id in the wrong channel must miss.
|
|
2303
|
+
// TEST332: get_cartridge requires a (channel, id) pair and returns the cached entry for known pairs, None otherwise. The same id in the wrong channel must miss.
|
|
2326
2304
|
function test332_cartridgeRepoClientGetCartridge() {
|
|
2327
2305
|
const client = new CartridgeRepoClient(3600);
|
|
2328
2306
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
@@ -2366,8 +2344,7 @@ function test332_cartridgeRepoClientGetCartridge() {
|
|
|
2366
2344
|
assert(threw, 'Should throw for invalid channel');
|
|
2367
2345
|
}
|
|
2368
2346
|
|
|
2369
|
-
// TEST333:
|
|
2370
|
-
// of normalized URNs across both channels.
|
|
2347
|
+
// TEST333: get_all_available_caps returns the deduplicated set of normalized URNs across cartridges.
|
|
2371
2348
|
function test333_cartridgeRepoClientGetAllCaps() {
|
|
2372
2349
|
const client = new CartridgeRepoClient(3600);
|
|
2373
2350
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
@@ -2383,8 +2360,7 @@ function test333_cartridgeRepoClientGetAllCaps() {
|
|
|
2383
2360
|
assert(caps.every(c => typeof c === 'string'), 'All caps should be strings');
|
|
2384
2361
|
}
|
|
2385
2362
|
|
|
2386
|
-
// TEST334:
|
|
2387
|
-
// empty / stale, false right after a fresh update.
|
|
2363
|
+
// TEST334: needs_sync returns true on an empty cache, false right after a successful update.
|
|
2388
2364
|
function test334_cartridgeRepoClientNeedsSync() {
|
|
2389
2365
|
const client = new CartridgeRepoClient(1); // 1 second TTL
|
|
2390
2366
|
const server = new CartridgeRepoServer(sampleRegistry);
|
|
@@ -2486,8 +2462,7 @@ function cartridgeWithVersions(id, versions) {
|
|
|
2486
2462
|
});
|
|
2487
2463
|
}
|
|
2488
2464
|
|
|
2489
|
-
// TEST1849: latest version has a host build → Compatible, resolving to the
|
|
2490
|
-
// latest version and that platform's native-format package.
|
|
2465
|
+
// TEST1849: latest version has a host build → Compatible, resolving to the latest version and that platform's native-format package.
|
|
2491
2466
|
function test1849_resolveForHostCompatibleLatest() {
|
|
2492
2467
|
const cartridge = cartridgeWithVersions('c', [
|
|
2493
2468
|
['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg'], ['linux-x86_64', 'deb', 'c-1.2.0.deb']]],
|
|
@@ -2503,9 +2478,7 @@ function test1849_resolveForHostCompatibleLatest() {
|
|
|
2503
2478
|
assertEqual(r.hostPlatform, 'linux-x86_64', 'host platform echoed back');
|
|
2504
2479
|
}
|
|
2505
2480
|
|
|
2506
|
-
// TEST1850: the latest version lacks a host build but an older version has one
|
|
2507
|
-
// → CompatibleOutdated, resolving to the NEWEST older version with a host build
|
|
2508
|
-
// (not the oldest), with a reason naming both the latest and the resolved.
|
|
2481
|
+
// TEST1850: the latest version lacks a host build but an older version has one → CompatibleOutdated, resolving to the older version with a reason naming both the latest and the resolved version.
|
|
2509
2482
|
function test1850_resolveForHostCompatibleOutdated() {
|
|
2510
2483
|
const cartridge = cartridgeWithVersions('c', [
|
|
2511
2484
|
['1.3.0', [['darwin-arm64', 'pkg', 'c-1.3.0.pkg']]],
|
|
@@ -2522,8 +2495,7 @@ function test1850_resolveForHostCompatibleOutdated() {
|
|
|
2522
2495
|
assert(r.reason.includes('1.2.0'), `reason names the resolved: ${r.reason}`);
|
|
2523
2496
|
}
|
|
2524
2497
|
|
|
2525
|
-
// TEST1851: no version ships a host build → Incompatible, no resolved
|
|
2526
|
-
// version/package, reason states the host platform.
|
|
2498
|
+
// TEST1851: no version ships a host build → Incompatible, no resolved version/package, reason states the host platform.
|
|
2527
2499
|
function test1851_resolveForHostIncompatible() {
|
|
2528
2500
|
const cartridge = cartridgeWithVersions('c', [
|
|
2529
2501
|
['1.2.0', [['darwin-arm64', 'pkg', 'c-1.2.0.pkg']]],
|
|
@@ -2537,9 +2509,7 @@ function test1851_resolveForHostIncompatible() {
|
|
|
2537
2509
|
assert(r.reason.includes('windows-x86_64'), `reason names the host platform: ${r.reason}`);
|
|
2538
2510
|
}
|
|
2539
2511
|
|
|
2540
|
-
// TEST1852: a host build whose packages[] is empty AND has no legacy `package`
|
|
2541
|
-
// ships no installer; resolution must SKIP it (not resolve to an un-downloadable
|
|
2542
|
-
// version) and fall through to an older usable version.
|
|
2512
|
+
// TEST1852: a host build whose packages[] is empty AND has no legacy `package` ships no installer; resolution must SKIP it (not resolve to an un-downloadable version) and fall through to an older usable version.
|
|
2543
2513
|
function test1852_resolveForHostSkipsBuildWithNoInstaller() {
|
|
2544
2514
|
const cartridge = cartridgeWithVersions('c', [
|
|
2545
2515
|
['2.0.0', [['linux-x86_64', 'deb', 'c-2.0.0.deb']]],
|
|
@@ -2557,8 +2527,7 @@ function test1852_resolveForHostSkipsBuildWithNoInstaller() {
|
|
|
2557
2527
|
assertEqual(r.resolvedPackage.name, 'c-1.0.0.deb', 'resolves to 1.0.0 deb package');
|
|
2558
2528
|
}
|
|
2559
2529
|
|
|
2560
|
-
// TEST1853:
|
|
2561
|
-
// aarch64/arm64 mapped to arm64 — the exact form the registry uses.
|
|
2530
|
+
// TEST1853: host_platform() returns a normalized {os}-{arch} string with arch aarch64 mapped to arm64 — the exact form the registry uses.
|
|
2562
2531
|
function test1853_hostPlatformNormalizedForm() {
|
|
2563
2532
|
const p = hostPlatform();
|
|
2564
2533
|
const dash = p.indexOf('-');
|
|
@@ -2588,9 +2557,7 @@ function test1873_registryUrlFromBuildEnvNoneForDev() {
|
|
|
2588
2557
|
assert(registryUrlFromBuildEnv(undefined) === null, 'absent env → null (dev build)');
|
|
2589
2558
|
}
|
|
2590
2559
|
|
|
2591
|
-
// TEST1874: an exported-but-empty env (
|
|
2592
|
-
// identity and MUST fail hard, so the build can never silently hash the empty
|
|
2593
|
-
// string into a fake registry slug.
|
|
2560
|
+
// TEST1874: an exported-but-empty env (`Some("")`) is neither a dev build nor a valid identity and MUST fail hard at compile time, so the build can never silently hash the empty string into a fake registry slug. We assert the panic rather than letting a bogus empty primary registry ship.
|
|
2594
2561
|
function test1874_registryUrlFromBuildEnvRejectsEmptyString() {
|
|
2595
2562
|
let threw = false;
|
|
2596
2563
|
try {
|
|
@@ -2748,8 +2715,8 @@ async function test1878_bundledProviderWithoutBakedHashIsRejected() {
|
|
|
2748
2715
|
// media_urn.rs: TEST1294-TEST1302 (MediaUrn predicates)
|
|
2749
2716
|
// ============================================================================
|
|
2750
2717
|
|
|
2751
|
-
//
|
|
2752
|
-
function
|
|
2718
|
+
// TEST546: is_image returns true only when image marker tag is present
|
|
2719
|
+
function test546_isImage() {
|
|
2753
2720
|
assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
|
|
2754
2721
|
assert(MediaUrn.fromString('media:ext=png;image;thumbnail').isImage(), 'media:ext=png;image;thumbnail should be image');
|
|
2755
2722
|
assert(MediaUrn.fromString('media:ext=jpg;image').isImage(), 'media:ext=jpg;image should be image');
|
|
@@ -2760,8 +2727,8 @@ function test1312_isImage() {
|
|
|
2760
2727
|
assert(!MediaUrn.fromString(MEDIA_VIDEO).isImage(), 'MEDIA_VIDEO should not be image');
|
|
2761
2728
|
}
|
|
2762
2729
|
|
|
2763
|
-
//
|
|
2764
|
-
function
|
|
2730
|
+
// TEST547: is_audio returns true only when audio marker tag is present
|
|
2731
|
+
function test547_isAudio() {
|
|
2765
2732
|
assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
|
|
2766
2733
|
assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
|
|
2767
2734
|
assert(MediaUrn.fromString('media:audio;ext=mp3').isAudio(), 'media:audio;ext=mp3 should be audio');
|
|
@@ -2771,8 +2738,8 @@ function test1313_isAudio() {
|
|
|
2771
2738
|
assert(!MediaUrn.fromString(MEDIA_STRING).isAudio(), 'MEDIA_STRING should not be audio');
|
|
2772
2739
|
}
|
|
2773
2740
|
|
|
2774
|
-
//
|
|
2775
|
-
function
|
|
2741
|
+
// TEST548: is_video returns true only when video marker tag is present
|
|
2742
|
+
function test548_isVideo() {
|
|
2776
2743
|
assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
|
|
2777
2744
|
assert(MediaUrn.fromString('media:ext=mp4;video').isVideo(), 'media:ext=mp4;video should be video');
|
|
2778
2745
|
// Non-video types
|
|
@@ -2781,8 +2748,8 @@ function test1314_isVideo() {
|
|
|
2781
2748
|
assert(!MediaUrn.fromString(MEDIA_STRING).isVideo(), 'MEDIA_STRING should not be video');
|
|
2782
2749
|
}
|
|
2783
2750
|
|
|
2784
|
-
//
|
|
2785
|
-
function
|
|
2751
|
+
// TEST549: is_numeric returns true only when numeric marker tag is present
|
|
2752
|
+
function test549_isNumeric() {
|
|
2786
2753
|
assert(MediaUrn.fromString(MEDIA_INTEGER).isNumeric(), 'MEDIA_INTEGER should be numeric');
|
|
2787
2754
|
assert(MediaUrn.fromString(MEDIA_NUMBER).isNumeric(), 'MEDIA_NUMBER should be numeric');
|
|
2788
2755
|
assert(MediaUrn.fromString(MEDIA_INTEGER_LIST).isNumeric(), 'MEDIA_INTEGER_LIST should be numeric');
|
|
@@ -2793,8 +2760,8 @@ function test1315_isNumeric() {
|
|
|
2793
2760
|
assert(!MediaUrn.fromString(MEDIA_IDENTITY).isNumeric(), 'MEDIA_IDENTITY should not be numeric');
|
|
2794
2761
|
}
|
|
2795
2762
|
|
|
2796
|
-
//
|
|
2797
|
-
function
|
|
2763
|
+
// TEST550: is_bool returns true only when bool marker tag is present
|
|
2764
|
+
function test550_isBool() {
|
|
2798
2765
|
assert(MediaUrn.fromString(MEDIA_BOOLEAN).isBool(), 'MEDIA_BOOLEAN should be bool');
|
|
2799
2766
|
assert(MediaUrn.fromString(MEDIA_BOOLEAN_LIST).isBool(), 'MEDIA_BOOLEAN_LIST should be bool');
|
|
2800
2767
|
// MEDIA_DECISION is now a JSON record type (not bool)
|
|
@@ -2805,10 +2772,8 @@ function test1298_isBool() {
|
|
|
2805
2772
|
assert(!MediaUrn.fromString(MEDIA_IDENTITY).isBool(), 'MEDIA_IDENTITY should not be bool');
|
|
2806
2773
|
}
|
|
2807
2774
|
|
|
2808
|
-
//
|
|
2809
|
-
|
|
2810
|
-
// carried by is_sequence on the wire, not by URN tags.
|
|
2811
|
-
function test1299_isFilePath() {
|
|
2775
|
+
// TEST551: is_file_path returns true for the single file-path media URN, false for everything else. There is no "array" variant — cardinality is carried by is_sequence on the wire, not by URN tags.
|
|
2776
|
+
function test551_isFilePath() {
|
|
2812
2777
|
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
|
|
2813
2778
|
assert(!MediaUrn.fromString(MEDIA_STRING).isFilePath(), 'MEDIA_STRING should not be file-path');
|
|
2814
2779
|
assert(!MediaUrn.fromString(MEDIA_IDENTITY).isFilePath(), 'MEDIA_IDENTITY should not be file-path');
|
|
@@ -2816,7 +2781,7 @@ function test1299_isFilePath() {
|
|
|
2816
2781
|
|
|
2817
2782
|
// Mirror-specific coverage: isCollection returns true when collection marker tag is present
|
|
2818
2783
|
// Mirror-specific coverage: N/A for JS (MEDIA_COLLECTION constants removed - no longer exists)
|
|
2819
|
-
function
|
|
2784
|
+
function test6272_isCollection() {
|
|
2820
2785
|
// Skip - collection types removed from capdag
|
|
2821
2786
|
}
|
|
2822
2787
|
|
|
@@ -2826,8 +2791,8 @@ function test0086_isCollection() {
|
|
|
2826
2791
|
|
|
2827
2792
|
// TEST557: N/A for JS (audio_media_urn_for_ext helper not in JS)
|
|
2828
2793
|
|
|
2829
|
-
//
|
|
2830
|
-
function
|
|
2794
|
+
// TEST558: predicates are consistent with constants — every constant triggers exactly the expected predicates
|
|
2795
|
+
function test558_predicateConstantConsistency() {
|
|
2831
2796
|
// MEDIA_INTEGER must be numeric, scalar, NOT enc-bearing/bool/image/list.
|
|
2832
2797
|
// Integers carry no enc= (a number is not a character-encoded string).
|
|
2833
2798
|
const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
|
|
@@ -2865,8 +2830,8 @@ function test1302_predicateConstantConsistency() {
|
|
|
2865
2830
|
// cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
|
|
2866
2831
|
// ============================================================================
|
|
2867
2832
|
|
|
2868
|
-
//
|
|
2869
|
-
function
|
|
2833
|
+
// TEST559: without_tag removes tag, rejects structural keys, case-insensitive for keys
|
|
2834
|
+
function test559_withoutTag() {
|
|
2870
2835
|
const cap = CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"');
|
|
2871
2836
|
const removed = cap.withoutTag('ext');
|
|
2872
2837
|
assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
|
|
@@ -2885,8 +2850,8 @@ function test1303_withoutTag() {
|
|
|
2885
2850
|
assert(same3.equals(cap), 'Removing non-existent tag is no-op');
|
|
2886
2851
|
}
|
|
2887
2852
|
|
|
2888
|
-
//
|
|
2889
|
-
function
|
|
2853
|
+
// TEST560: with_in_spec and with_out_spec change direction specs
|
|
2854
|
+
function test560_withInOutSpec() {
|
|
2890
2855
|
const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
|
|
2891
2856
|
|
|
2892
2857
|
const changedIn = cap.withInSpec('media:');
|
|
@@ -2915,8 +2880,8 @@ function test1304_withInOutSpec() {
|
|
|
2915
2880
|
|
|
2916
2881
|
// TEST562: N/A for JS (canonical_option not in JS CapUrn)
|
|
2917
2882
|
|
|
2918
|
-
//
|
|
2919
|
-
function
|
|
2883
|
+
// TEST563: CapMatcher::find_all_matches returns all matching caps sorted by specificity
|
|
2884
|
+
function test563_findAllMatches() {
|
|
2920
2885
|
const caps = [
|
|
2921
2886
|
CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
|
|
2922
2887
|
CapUrn.fromString('cap:in="media:void";test;ext=pdf;out="media:void"'),
|
|
@@ -2933,8 +2898,8 @@ function test1305_findAllMatches() {
|
|
|
2933
2898
|
assertEqual(matches[0].getTag('ext'), 'pdf', 'Most specific match should have ext=pdf');
|
|
2934
2899
|
}
|
|
2935
2900
|
|
|
2936
|
-
//
|
|
2937
|
-
function
|
|
2901
|
+
// TEST564: CapMatcher::are_compatible detects bidirectional overlap
|
|
2902
|
+
function test564_areCompatible() {
|
|
2938
2903
|
const caps1 = [
|
|
2939
2904
|
CapUrn.fromString('cap:in="media:void";test;out="media:void"'),
|
|
2940
2905
|
];
|
|
@@ -2958,16 +2923,16 @@ function test1306_areCompatible() {
|
|
|
2958
2923
|
|
|
2959
2924
|
// TEST565: N/A for JS (tags_to_string not in JS CapUrn)
|
|
2960
2925
|
|
|
2961
|
-
//
|
|
2962
|
-
function
|
|
2926
|
+
// TEST566: with_tag rejects structural keys
|
|
2927
|
+
function test566_withTagRejectsStructuralKeys() {
|
|
2963
2928
|
const cap = CapUrn.fromString('cap:in="media:void";test;out="media:void"');
|
|
2964
2929
|
assertThrows(() => cap.withTag('in', 'media:'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject in');
|
|
2965
2930
|
assertThrows(() => cap.withTag('out', 'media:'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject out');
|
|
2966
2931
|
assertThrows(() => cap.withTag('effect', 'none'), ErrorCodes.INVALID_TAG_FORMAT, 'withTag must reject effect');
|
|
2967
2932
|
}
|
|
2968
2933
|
|
|
2969
|
-
//
|
|
2970
|
-
function
|
|
2934
|
+
// TEST6544: builder rejects structural keys on tag/marker
|
|
2935
|
+
function test6544_builderRejectsStructuralKeys() {
|
|
2971
2936
|
assertThrows(
|
|
2972
2937
|
() => new CapUrnBuilder().tag('in', 'media:void'),
|
|
2973
2938
|
ErrorCodes.INVALID_TAG_FORMAT,
|
|
@@ -3041,8 +3006,8 @@ function test1297_rule11NonVoidInputWithStdin() {
|
|
|
3041
3006
|
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
3042
3007
|
// ============================================================================
|
|
3043
3008
|
|
|
3044
|
-
//
|
|
3045
|
-
function
|
|
3009
|
+
// TEST6201: cap: (empty) is the illegal bare top form
|
|
3010
|
+
function test6201_emptyCapIsIllegal() {
|
|
3046
3011
|
assertThrows(
|
|
3047
3012
|
() => CapUrn.fromString('cap:'),
|
|
3048
3013
|
ErrorCodes.ILLEGAL_DECLARATION,
|
|
@@ -3050,7 +3015,7 @@ function test639_emptyCapIsIllegal() {
|
|
|
3050
3015
|
);
|
|
3051
3016
|
}
|
|
3052
3017
|
|
|
3053
|
-
// TEST640: cap:in
|
|
3018
|
+
// TEST640: cap:in defaults to the same illegal bare top form
|
|
3054
3019
|
function test640_inOnlyIsIllegal() {
|
|
3055
3020
|
assertThrows(
|
|
3056
3021
|
() => CapUrn.fromString('cap:in'),
|
|
@@ -3059,7 +3024,7 @@ function test640_inOnlyIsIllegal() {
|
|
|
3059
3024
|
);
|
|
3060
3025
|
}
|
|
3061
3026
|
|
|
3062
|
-
// TEST641: cap:out
|
|
3027
|
+
// TEST641: cap:out defaults to the same illegal bare top form
|
|
3063
3028
|
function test641_outOnlyIsIllegal() {
|
|
3064
3029
|
assertThrows(
|
|
3065
3030
|
() => CapUrn.fromString('cap:out'),
|
|
@@ -3068,7 +3033,7 @@ function test641_outOnlyIsIllegal() {
|
|
|
3068
3033
|
);
|
|
3069
3034
|
}
|
|
3070
3035
|
|
|
3071
|
-
// TEST642: cap:in;out
|
|
3036
|
+
// TEST642: cap:in;out becomes the same illegal bare top form
|
|
3072
3037
|
function test642_inOutWithoutValuesAreIllegal() {
|
|
3073
3038
|
assertThrows(
|
|
3074
3039
|
() => CapUrn.fromString('cap:in;out'),
|
|
@@ -3147,8 +3112,8 @@ function test650_wildcardPreserveOtherTags() {
|
|
|
3147
3112
|
assert(cap.hasMarkerTag('test'), 'marker tag should be preserved');
|
|
3148
3113
|
}
|
|
3149
3114
|
|
|
3150
|
-
//
|
|
3151
|
-
function
|
|
3115
|
+
// TEST6620: Generic top-to-top spellings are all rejected.
|
|
3116
|
+
function test6620_wildcardGenericFormsRejected() {
|
|
3152
3117
|
const forms = [
|
|
3153
3118
|
'cap:',
|
|
3154
3119
|
'cap:in;out',
|
|
@@ -3168,8 +3133,8 @@ function test651_wildcardGenericFormsRejected() {
|
|
|
3168
3133
|
}
|
|
3169
3134
|
}
|
|
3170
3135
|
|
|
3171
|
-
//
|
|
3172
|
-
function
|
|
3136
|
+
// TEST6621: CAP_IDENTITY constant names the true identity cap, not bare cap:
|
|
3137
|
+
function test6621_capIdentityConstantWorks() {
|
|
3173
3138
|
const identity = CapUrn.fromString(CAP_IDENTITY);
|
|
3174
3139
|
assertEqual(identity.toString(), 'cap:effect=none', 'CAP_IDENTITY must be explicit effect=none');
|
|
3175
3140
|
assertEqual(identity.kind(), CapKind.IDENTITY, 'CAP_IDENTITY must classify as identity');
|
|
@@ -3185,7 +3150,7 @@ function test652_capIdentityConstantWorks() {
|
|
|
3185
3150
|
);
|
|
3186
3151
|
}
|
|
3187
3152
|
|
|
3188
|
-
// TEST653: invalid effect=none declarations fail at construction
|
|
3153
|
+
// TEST653: invalid effect=none declarations fail at construction
|
|
3189
3154
|
function test653_invalidEffectNoneDeclarationRejected() {
|
|
3190
3155
|
assertThrows(
|
|
3191
3156
|
() => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
|
|
@@ -3194,8 +3159,8 @@ function test653_invalidEffectNoneDeclarationRejected() {
|
|
|
3194
3159
|
);
|
|
3195
3160
|
}
|
|
3196
3161
|
|
|
3197
|
-
//
|
|
3198
|
-
function
|
|
3162
|
+
// TEST125: effect=none preserves runtime media identity
|
|
3163
|
+
function test125_effectNonePreservesRuntimeMedia() {
|
|
3199
3164
|
const decimate = CapUrn.fromString('cap:decimate-sequence;effect=none');
|
|
3200
3165
|
const png = MediaUrn.fromString('media:ext=png;image');
|
|
3201
3166
|
const pdf = MediaUrn.fromString('media:ext=pdf');
|
|
@@ -3203,8 +3168,8 @@ function test654_effectNonePreservesRuntimeMedia() {
|
|
|
3203
3168
|
assertEqual(decimate.inferRuntimeOutputMedia(pdf).toString(), pdf.toString(), 'effect=none should preserve pdf');
|
|
3204
3169
|
}
|
|
3205
3170
|
|
|
3206
|
-
//
|
|
3207
|
-
function
|
|
3171
|
+
// TEST126: default effect=declared uses the declared output
|
|
3172
|
+
function test126_effectDeclaredUsesDeclaredOutput() {
|
|
3208
3173
|
const resize = CapUrn.fromString('cap:in=media:image;out=media:image;resize');
|
|
3209
3174
|
const png = MediaUrn.fromString('media:ext=png;image;width=4000');
|
|
3210
3175
|
assertEqual(
|
|
@@ -3214,8 +3179,8 @@ function test655_effectDeclaredUsesDeclaredOutput() {
|
|
|
3214
3179
|
);
|
|
3215
3180
|
}
|
|
3216
3181
|
|
|
3217
|
-
//
|
|
3218
|
-
function
|
|
3182
|
+
// TEST127: invalid effect=none declarations fail hard
|
|
3183
|
+
function test127_invalidEffectNoneFailsHard() {
|
|
3219
3184
|
assertThrows(
|
|
3220
3185
|
() => CapUrn.fromString('cap:in="media:ext=pdf";effect=none;out="media:enc=utf-8"'),
|
|
3221
3186
|
ErrorCodes.ILLEGAL_DECLARATION,
|
|
@@ -3223,8 +3188,8 @@ function test656_invalidEffectNoneFailsHard() {
|
|
|
3223
3188
|
);
|
|
3224
3189
|
}
|
|
3225
3190
|
|
|
3226
|
-
//
|
|
3227
|
-
function
|
|
3191
|
+
// TEST128: omitted effect means declared; unconstrained effect must be explicit
|
|
3192
|
+
function test128_effectDispatchRequiresExplicitWildcard() {
|
|
3228
3193
|
const noneProvider = CapUrn.fromString('cap:effect=none');
|
|
3229
3194
|
const declaredRequest = CapUrn.fromString('cap:raw');
|
|
3230
3195
|
const anyRequest = CapUrn.fromString('cap:?effect');
|
|
@@ -3238,17 +3203,17 @@ function test657_effectDispatchRequiresExplicitWildcard() {
|
|
|
3238
3203
|
|
|
3239
3204
|
// --- Machine parser tests (mirrors parser.rs tests) ---
|
|
3240
3205
|
|
|
3241
|
-
function
|
|
3206
|
+
function test6275_Machine_emptyInput() {
|
|
3242
3207
|
assertThrowsWithCode(() => parseMachine(''), MachineSyntaxErrorCodes.EMPTY);
|
|
3243
3208
|
}
|
|
3244
3209
|
|
|
3245
3210
|
// TEST0088: Machine whitespace only
|
|
3246
|
-
function
|
|
3211
|
+
function test6277_Machine_whitespaceOnly() {
|
|
3247
3212
|
assertThrowsWithCode(() => parseMachine(' \n \t '), MachineSyntaxErrorCodes.EMPTY);
|
|
3248
3213
|
}
|
|
3249
3214
|
|
|
3250
3215
|
// TEST0089: Machine header only no wirings
|
|
3251
|
-
function
|
|
3216
|
+
function test6279_Machine_headerOnlyNoWirings() {
|
|
3252
3217
|
assertThrowsWithCode(
|
|
3253
3218
|
() => Machine.fromString('[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]'),
|
|
3254
3219
|
MachineSyntaxErrorCodes.NO_EDGES
|
|
@@ -3256,7 +3221,7 @@ function test0089_Machine_headerOnlyNoWirings() {
|
|
|
3256
3221
|
}
|
|
3257
3222
|
|
|
3258
3223
|
// TEST0090: Machine duplicate alias
|
|
3259
|
-
function
|
|
3224
|
+
function test6280_Machine_duplicateAlias() {
|
|
3260
3225
|
assertThrowsWithCode(
|
|
3261
3226
|
() => Machine.fromString(
|
|
3262
3227
|
'[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
@@ -3268,7 +3233,7 @@ function test0090_Machine_duplicateAlias() {
|
|
|
3268
3233
|
}
|
|
3269
3234
|
|
|
3270
3235
|
// TEST0094: Machine simple linear chain
|
|
3271
|
-
function
|
|
3236
|
+
function test6286_Machine_simpleLinearChain() {
|
|
3272
3237
|
const g = Machine.fromString(
|
|
3273
3238
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3274
3239
|
'[doc -> extract -> text]'
|
|
@@ -3284,7 +3249,7 @@ function test0094_Machine_simpleLinearChain() {
|
|
|
3284
3249
|
}
|
|
3285
3250
|
|
|
3286
3251
|
// TEST0095: Machine two step chain
|
|
3287
|
-
function
|
|
3252
|
+
function test6288_Machine_twoStepChain() {
|
|
3288
3253
|
const g = Machine.fromString(
|
|
3289
3254
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3290
3255
|
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]' +
|
|
@@ -3299,7 +3264,7 @@ function test0095_Machine_twoStepChain() {
|
|
|
3299
3264
|
}
|
|
3300
3265
|
|
|
3301
3266
|
// TEST0096: Machine fan out
|
|
3302
|
-
function
|
|
3267
|
+
function test6290_Machine_fanOut() {
|
|
3303
3268
|
const g = Machine.fromString(
|
|
3304
3269
|
'[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]' +
|
|
3305
3270
|
'[outline cap:in="media:ext=pdf";extract-outline;out="media:document-outline;enc=utf-8;record"]' +
|
|
@@ -3317,7 +3282,7 @@ function test0096_Machine_fanOut() {
|
|
|
3317
3282
|
}
|
|
3318
3283
|
|
|
3319
3284
|
// TEST0097: Machine fan in secondary assigned by prior wiring
|
|
3320
|
-
function
|
|
3285
|
+
function test6292_Machine_fanInSecondaryAssignedByPriorWiring() {
|
|
3321
3286
|
const g = Machine.fromString(
|
|
3322
3287
|
'[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"]' +
|
|
3323
3288
|
'[model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"]' +
|
|
@@ -3331,7 +3296,7 @@ function test0097_Machine_fanInSecondaryAssignedByPriorWiring() {
|
|
|
3331
3296
|
}
|
|
3332
3297
|
|
|
3333
3298
|
// TEST0098: Machine fan in secondary unassigned gets wildcard
|
|
3334
|
-
function
|
|
3299
|
+
function test6294_Machine_fanInSecondaryUnassignedGetsWildcard() {
|
|
3335
3300
|
const g = Machine.fromString(
|
|
3336
3301
|
'[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]\n' +
|
|
3337
3302
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -3342,8 +3307,8 @@ function test0098_Machine_fanInSecondaryUnassignedGetsWildcard() {
|
|
|
3342
3307
|
assertEqual(g.edges()[0].sources[1].toString(), 'media:');
|
|
3343
3308
|
}
|
|
3344
3309
|
|
|
3345
|
-
//
|
|
3346
|
-
function
|
|
3310
|
+
// TEST6306: Machine loop edge
|
|
3311
|
+
function test6306_Machine_loopEdge() {
|
|
3347
3312
|
const g = Machine.fromString(
|
|
3348
3313
|
'[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
|
|
3349
3314
|
'[pages -> LOOP p2t -> texts]'
|
|
@@ -3352,16 +3317,16 @@ function test0111_Machine_loopEdge() {
|
|
|
3352
3317
|
assertEqual(g.edges()[0].isLoop, true);
|
|
3353
3318
|
}
|
|
3354
3319
|
|
|
3355
|
-
//
|
|
3356
|
-
function
|
|
3320
|
+
// TEST6308: Machine undefined alias fails
|
|
3321
|
+
function test6308_Machine_undefinedAliasFails() {
|
|
3357
3322
|
assertThrowsWithCode(
|
|
3358
3323
|
() => Machine.fromString('[doc -> nonexistent -> text]'),
|
|
3359
3324
|
MachineSyntaxErrorCodes.UNDEFINED_ALIAS
|
|
3360
3325
|
);
|
|
3361
3326
|
}
|
|
3362
3327
|
|
|
3363
|
-
//
|
|
3364
|
-
function
|
|
3328
|
+
// TEST6310: Machine node alias collision
|
|
3329
|
+
function test6310_Machine_nodeAliasCollision() {
|
|
3365
3330
|
assertThrowsWithCode(
|
|
3366
3331
|
() => Machine.fromString(
|
|
3367
3332
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
@@ -3371,8 +3336,8 @@ function test0113_Machine_nodeAliasCollision() {
|
|
|
3371
3336
|
);
|
|
3372
3337
|
}
|
|
3373
3338
|
|
|
3374
|
-
//
|
|
3375
|
-
function
|
|
3339
|
+
// TEST6312: Machine conflicting media types fail
|
|
3340
|
+
function test6312_Machine_conflictingMediaTypesFail() {
|
|
3376
3341
|
assertThrowsWithCode(
|
|
3377
3342
|
() => Machine.fromString(
|
|
3378
3343
|
'[cap1 cap:in="media:enc=utf-8;ext=txt";a;out="media:ext=pdf"]' +
|
|
@@ -3384,8 +3349,8 @@ function test0114_Machine_conflictingMediaTypesFail() {
|
|
|
3384
3349
|
);
|
|
3385
3350
|
}
|
|
3386
3351
|
|
|
3387
|
-
//
|
|
3388
|
-
function
|
|
3352
|
+
// TEST6315: Machine multiline format
|
|
3353
|
+
function test6315_Machine_multilineFormat() {
|
|
3389
3354
|
const g = Machine.fromString(
|
|
3390
3355
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
|
|
3391
3356
|
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]\n' +
|
|
@@ -3395,8 +3360,8 @@ function test0117_Machine_multilineFormat() {
|
|
|
3395
3360
|
assertEqual(g.edgeCount(), 2);
|
|
3396
3361
|
}
|
|
3397
3362
|
|
|
3398
|
-
//
|
|
3399
|
-
function
|
|
3363
|
+
// TEST6318: Machine different aliases same graph
|
|
3364
|
+
function test6318_Machine_differentAliasesSameGraph() {
|
|
3400
3365
|
const g1 = Machine.fromString(
|
|
3401
3366
|
'[ex cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3402
3367
|
'[a -> ex -> b]'
|
|
@@ -3408,16 +3373,16 @@ function test0118_Machine_differentAliasesSameGraph() {
|
|
|
3408
3373
|
assert(g1.isEquivalent(g2), 'Different aliases should produce equivalent graphs');
|
|
3409
3374
|
}
|
|
3410
3375
|
|
|
3411
|
-
//
|
|
3412
|
-
function
|
|
3376
|
+
// TEST6321: Machine malformed input fails
|
|
3377
|
+
function test6321_Machine_malformedInputFails() {
|
|
3413
3378
|
assertThrowsWithCode(
|
|
3414
3379
|
() => parseMachine('not valid machine notation'),
|
|
3415
3380
|
MachineSyntaxErrorCodes.PARSE_ERROR
|
|
3416
3381
|
);
|
|
3417
3382
|
}
|
|
3418
3383
|
|
|
3419
|
-
//
|
|
3420
|
-
function
|
|
3384
|
+
// TEST6323: Machine unterminated bracket fails
|
|
3385
|
+
function test6323_Machine_unterminatedBracketFails() {
|
|
3421
3386
|
assertThrowsWithCode(
|
|
3422
3387
|
() => parseMachine('[extract cap:in=media:ext=pdf'),
|
|
3423
3388
|
MachineSyntaxErrorCodes.PARSE_ERROR
|
|
@@ -3426,7 +3391,7 @@ function test0120_Machine_unterminatedBracketFails() {
|
|
|
3426
3391
|
|
|
3427
3392
|
// --- Machine parser line-based mode tests ---
|
|
3428
3393
|
|
|
3429
|
-
function
|
|
3394
|
+
function test6327_Machine_lineBasedSimpleChain() {
|
|
3430
3395
|
const g = Machine.fromString(
|
|
3431
3396
|
'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
|
|
3432
3397
|
'doc -> extract -> text'
|
|
@@ -3439,8 +3404,8 @@ function test0121_Machine_lineBasedSimpleChain() {
|
|
|
3439
3404
|
'Target should be media:enc=utf-8;ext=txt');
|
|
3440
3405
|
}
|
|
3441
3406
|
|
|
3442
|
-
//
|
|
3443
|
-
function
|
|
3407
|
+
// TEST6331: Machine line based two step chain
|
|
3408
|
+
function test6331_Machine_lineBasedTwoStepChain() {
|
|
3444
3409
|
const g = Machine.fromString(
|
|
3445
3410
|
'extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"\n' +
|
|
3446
3411
|
'embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"\n' +
|
|
@@ -3450,8 +3415,8 @@ function test0122_Machine_lineBasedTwoStepChain() {
|
|
|
3450
3415
|
assertEqual(g.edgeCount(), 2);
|
|
3451
3416
|
}
|
|
3452
3417
|
|
|
3453
|
-
//
|
|
3454
|
-
function
|
|
3418
|
+
// TEST6334: Machine line based loop
|
|
3419
|
+
function test6334_Machine_lineBasedLoop() {
|
|
3455
3420
|
const g = Machine.fromString(
|
|
3456
3421
|
'p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"\n' +
|
|
3457
3422
|
'pages -> LOOP p2t -> texts'
|
|
@@ -3460,8 +3425,8 @@ function test0123_Machine_lineBasedLoop() {
|
|
|
3460
3425
|
assertEqual(g.edges()[0].isLoop, true);
|
|
3461
3426
|
}
|
|
3462
3427
|
|
|
3463
|
-
//
|
|
3464
|
-
function
|
|
3428
|
+
// TEST6337: Machine line based fan in
|
|
3429
|
+
function test6337_Machine_lineBasedFanIn() {
|
|
3465
3430
|
const g = Machine.fromString(
|
|
3466
3431
|
'thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"\n' +
|
|
3467
3432
|
'model_dl cap:in="media:enc=utf-8;model-spec";download;out="media:enc=utf-8;model-spec"\n' +
|
|
@@ -3474,8 +3439,8 @@ function test0124_Machine_lineBasedFanIn() {
|
|
|
3474
3439
|
assertEqual(g.edges()[2].sources.length, 2);
|
|
3475
3440
|
}
|
|
3476
3441
|
|
|
3477
|
-
//
|
|
3478
|
-
function
|
|
3442
|
+
// TEST6341: Machine mixed bracketed and line based
|
|
3443
|
+
function test6341_Machine_mixedBracketedAndLineBased() {
|
|
3479
3444
|
const g = Machine.fromString(
|
|
3480
3445
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n' +
|
|
3481
3446
|
'doc -> extract -> text'
|
|
@@ -3483,8 +3448,8 @@ function test0125_Machine_mixedBracketedAndLineBased() {
|
|
|
3483
3448
|
assertEqual(g.edgeCount(), 1);
|
|
3484
3449
|
}
|
|
3485
3450
|
|
|
3486
|
-
//
|
|
3487
|
-
function
|
|
3451
|
+
// TEST6345: Machine line based equivalent to bracketed
|
|
3452
|
+
function test6345_Machine_lineBasedEquivalentToBracketed() {
|
|
3488
3453
|
const g1 = Machine.fromString(
|
|
3489
3454
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
3490
3455
|
'[doc -> extract -> text]'
|
|
@@ -3496,8 +3461,8 @@ function test0126_Machine_lineBasedEquivalentToBracketed() {
|
|
|
3496
3461
|
assert(g1.isEquivalent(g2), 'Line-based and bracketed must produce equivalent graphs');
|
|
3497
3462
|
}
|
|
3498
3463
|
|
|
3499
|
-
//
|
|
3500
|
-
function
|
|
3464
|
+
// TEST6349: Machine line based format serialization
|
|
3465
|
+
function test6349_Machine_lineBasedFormatSerialization() {
|
|
3501
3466
|
const g = new Machine([
|
|
3502
3467
|
new MachineEdge(
|
|
3503
3468
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
@@ -3518,8 +3483,8 @@ function test0127_Machine_lineBasedFormatSerialization() {
|
|
|
3518
3483
|
assert(g.isEquivalent(reparsed), 'Line-based round-trip must produce equivalent graph');
|
|
3519
3484
|
}
|
|
3520
3485
|
|
|
3521
|
-
//
|
|
3522
|
-
function
|
|
3486
|
+
// TEST6353: Machine line based and bracketed parse same graph
|
|
3487
|
+
function test6353_Machine_lineBasedAndBracketedParseSameGraph() {
|
|
3523
3488
|
const g = new Machine([
|
|
3524
3489
|
new MachineEdge(
|
|
3525
3490
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
@@ -3545,7 +3510,7 @@ function test0128_Machine_lineBasedAndBracketedParseSameGraph() {
|
|
|
3545
3510
|
|
|
3546
3511
|
// --- Machine graph tests (mirrors graph.rs tests) ---
|
|
3547
3512
|
|
|
3548
|
-
function
|
|
3513
|
+
function test6357_Machine_edgeEquivalenceSameUrns() {
|
|
3549
3514
|
const e1 = new MachineEdge(
|
|
3550
3515
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3551
3516
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3561,8 +3526,8 @@ function test0129_Machine_edgeEquivalenceSameUrns() {
|
|
|
3561
3526
|
assert(e1.isEquivalent(e2), 'Same URNs should be equivalent');
|
|
3562
3527
|
}
|
|
3563
3528
|
|
|
3564
|
-
//
|
|
3565
|
-
function
|
|
3529
|
+
// TEST6361: Machine edge equivalence different cap urns
|
|
3530
|
+
function test6361_Machine_edgeEquivalenceDifferentCapUrns() {
|
|
3566
3531
|
const e1 = new MachineEdge(
|
|
3567
3532
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3568
3533
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3578,8 +3543,8 @@ function test0130_Machine_edgeEquivalenceDifferentCapUrns() {
|
|
|
3578
3543
|
assert(!e1.isEquivalent(e2), 'Different cap URNs should not be equivalent');
|
|
3579
3544
|
}
|
|
3580
3545
|
|
|
3581
|
-
//
|
|
3582
|
-
function
|
|
3546
|
+
// TEST6365: Machine edge equivalence different targets
|
|
3547
|
+
function test6365_Machine_edgeEquivalenceDifferentTargets() {
|
|
3583
3548
|
const e1 = new MachineEdge(
|
|
3584
3549
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3585
3550
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3595,8 +3560,8 @@ function test0131_Machine_edgeEquivalenceDifferentTargets() {
|
|
|
3595
3560
|
assert(!e1.isEquivalent(e2), 'Different targets should not be equivalent');
|
|
3596
3561
|
}
|
|
3597
3562
|
|
|
3598
|
-
//
|
|
3599
|
-
function
|
|
3563
|
+
// TEST6369: Machine edge equivalence different loop flag
|
|
3564
|
+
function test6369_Machine_edgeEquivalenceDifferentLoopFlag() {
|
|
3600
3565
|
const e1 = new MachineEdge(
|
|
3601
3566
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3602
3567
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3612,8 +3577,8 @@ function test0132_Machine_edgeEquivalenceDifferentLoopFlag() {
|
|
|
3612
3577
|
assert(!e1.isEquivalent(e2), 'Different loop flags should not be equivalent');
|
|
3613
3578
|
}
|
|
3614
3579
|
|
|
3615
|
-
//
|
|
3616
|
-
function
|
|
3580
|
+
// TEST6372: Machine edge equivalence source order independent
|
|
3581
|
+
function test6372_Machine_edgeEquivalenceSourceOrderIndependent() {
|
|
3617
3582
|
const e1 = new MachineEdge(
|
|
3618
3583
|
[MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
|
|
3619
3584
|
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
@@ -3629,8 +3594,8 @@ function test0133_Machine_edgeEquivalenceSourceOrderIndependent() {
|
|
|
3629
3594
|
assert(e1.isEquivalent(e2), 'Source order should not matter for equivalence');
|
|
3630
3595
|
}
|
|
3631
3596
|
|
|
3632
|
-
//
|
|
3633
|
-
function
|
|
3597
|
+
// TEST6375: Machine edge equivalence different source count
|
|
3598
|
+
function test6375_Machine_edgeEquivalenceDifferentSourceCount() {
|
|
3634
3599
|
const e1 = new MachineEdge(
|
|
3635
3600
|
[MediaUrn.fromString('media:enc=utf-8;ext=txt')],
|
|
3636
3601
|
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
@@ -3646,8 +3611,8 @@ function test0134_Machine_edgeEquivalenceDifferentSourceCount() {
|
|
|
3646
3611
|
assert(!e1.isEquivalent(e2), 'Different source counts should not be equivalent');
|
|
3647
3612
|
}
|
|
3648
3613
|
|
|
3649
|
-
//
|
|
3650
|
-
function
|
|
3614
|
+
// TEST6377: Machine graph equivalence same edges
|
|
3615
|
+
function test6377_Machine_graphEquivalenceSameEdges() {
|
|
3651
3616
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3652
3617
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3653
3618
|
);
|
|
@@ -3662,8 +3627,8 @@ function test0135_Machine_graphEquivalenceSameEdges() {
|
|
|
3662
3627
|
assert(g1.isEquivalent(g2), 'Same edges should be equivalent');
|
|
3663
3628
|
}
|
|
3664
3629
|
|
|
3665
|
-
//
|
|
3666
|
-
function
|
|
3630
|
+
// TEST6380: Machine graph equivalence reordered edges
|
|
3631
|
+
function test6380_Machine_graphEquivalenceReorderedEdges() {
|
|
3667
3632
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3668
3633
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3669
3634
|
);
|
|
@@ -3678,8 +3643,8 @@ function test0136_Machine_graphEquivalenceReorderedEdges() {
|
|
|
3678
3643
|
assert(g1.isEquivalent(g2), 'Reordered edges should still be equivalent');
|
|
3679
3644
|
}
|
|
3680
3645
|
|
|
3681
|
-
//
|
|
3682
|
-
function
|
|
3646
|
+
// TEST6383: Machine graph not equivalent different edge count
|
|
3647
|
+
function test6383_Machine_graphNotEquivalentDifferentEdgeCount() {
|
|
3683
3648
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3684
3649
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3685
3650
|
);
|
|
@@ -3693,8 +3658,8 @@ function test0137_Machine_graphNotEquivalentDifferentEdgeCount() {
|
|
|
3693
3658
|
assert(!g1.isEquivalent(g2), 'Different edge counts should not be equivalent');
|
|
3694
3659
|
}
|
|
3695
3660
|
|
|
3696
|
-
//
|
|
3697
|
-
function
|
|
3661
|
+
// TEST6386: Machine graph not equivalent different cap
|
|
3662
|
+
function test6386_Machine_graphNotEquivalentDifferentCap() {
|
|
3698
3663
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3699
3664
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3700
3665
|
);
|
|
@@ -3707,22 +3672,22 @@ function test0138_Machine_graphNotEquivalentDifferentCap() {
|
|
|
3707
3672
|
assert(!g1.isEquivalent(g2), 'Different caps should not be equivalent');
|
|
3708
3673
|
}
|
|
3709
3674
|
|
|
3710
|
-
//
|
|
3711
|
-
function
|
|
3675
|
+
// TEST6389: Machine graph empty
|
|
3676
|
+
function test6389_Machine_graphEmpty() {
|
|
3712
3677
|
const g = Machine.empty();
|
|
3713
3678
|
assert(g.isEmpty(), 'Empty graph should be empty');
|
|
3714
3679
|
assertEqual(g.edgeCount(), 0);
|
|
3715
3680
|
}
|
|
3716
3681
|
|
|
3717
|
-
//
|
|
3718
|
-
function
|
|
3682
|
+
// TEST6392: Machine graph empty equivalence
|
|
3683
|
+
function test6392_Machine_graphEmptyEquivalence() {
|
|
3719
3684
|
const g1 = Machine.empty();
|
|
3720
3685
|
const g2 = Machine.empty();
|
|
3721
3686
|
assert(g1.isEquivalent(g2), 'Two empty graphs should be equivalent');
|
|
3722
3687
|
}
|
|
3723
3688
|
|
|
3724
|
-
//
|
|
3725
|
-
function
|
|
3689
|
+
// TEST6395: Machine root sources linear chain
|
|
3690
|
+
function test6395_Machine_rootSourcesLinearChain() {
|
|
3726
3691
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3727
3692
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3728
3693
|
);
|
|
@@ -3736,8 +3701,8 @@ function test0141_Machine_rootSourcesLinearChain() {
|
|
|
3736
3701
|
'Root source should be media:ext=pdf');
|
|
3737
3702
|
}
|
|
3738
3703
|
|
|
3739
|
-
//
|
|
3740
|
-
function
|
|
3704
|
+
// TEST6397: Machine leaf targets linear chain
|
|
3705
|
+
function test6397_Machine_leafTargetsLinearChain() {
|
|
3741
3706
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3742
3707
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3743
3708
|
);
|
|
@@ -3751,8 +3716,8 @@ function test0142_Machine_leafTargetsLinearChain() {
|
|
|
3751
3716
|
'Leaf target should be media:embedding-vector;enc=utf-8;record');
|
|
3752
3717
|
}
|
|
3753
3718
|
|
|
3754
|
-
//
|
|
3755
|
-
function
|
|
3719
|
+
// TEST6398: Machine root sources fan in
|
|
3720
|
+
function test6398_Machine_rootSourcesFanIn() {
|
|
3756
3721
|
const e = new MachineEdge(
|
|
3757
3722
|
[MediaUrn.fromString('media:enc=utf-8;ext=txt'), MediaUrn.fromString('media:enc=utf-8;model-spec')],
|
|
3758
3723
|
CapUrn.fromString('cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"'),
|
|
@@ -3764,8 +3729,8 @@ function test0143_Machine_rootSourcesFanIn() {
|
|
|
3764
3729
|
assertEqual(roots.length, 2);
|
|
3765
3730
|
}
|
|
3766
3731
|
|
|
3767
|
-
//
|
|
3768
|
-
function
|
|
3732
|
+
// TEST6400: Machine display edge
|
|
3733
|
+
function test6400_Machine_displayEdge() {
|
|
3769
3734
|
const e = new MachineEdge(
|
|
3770
3735
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3771
3736
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3776,8 +3741,8 @@ function test0144_Machine_displayEdge() {
|
|
|
3776
3741
|
assert(display.includes('media:ext=pdf'), 'Display should contain media:ext=pdf');
|
|
3777
3742
|
}
|
|
3778
3743
|
|
|
3779
|
-
//
|
|
3780
|
-
function
|
|
3744
|
+
// TEST6402: Machine display graph
|
|
3745
|
+
function test6402_Machine_displayGraph() {
|
|
3781
3746
|
const e = new MachineEdge(
|
|
3782
3747
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3783
3748
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3790,7 +3755,7 @@ function test0145_Machine_displayGraph() {
|
|
|
3790
3755
|
|
|
3791
3756
|
// --- Machine serializer tests (mirrors serializer.rs tests) ---
|
|
3792
3757
|
|
|
3793
|
-
function
|
|
3758
|
+
function test6404_Machine_serializeSingleEdge() {
|
|
3794
3759
|
const g = new Machine([new MachineEdge(
|
|
3795
3760
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3796
3761
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3805,8 +3770,8 @@ function test0146_Machine_serializeSingleEdge() {
|
|
|
3805
3770
|
assert(notation.includes('-> n1]'), 'Should use n1 for target: ' + notation);
|
|
3806
3771
|
}
|
|
3807
3772
|
|
|
3808
|
-
//
|
|
3809
|
-
function
|
|
3773
|
+
// TEST6406: Machine serialize two edge chain
|
|
3774
|
+
function test6406_Machine_serializeTwoEdgeChain() {
|
|
3810
3775
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3811
3776
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3812
3777
|
);
|
|
@@ -3819,13 +3784,13 @@ function test0147_Machine_serializeTwoEdgeChain() {
|
|
|
3819
3784
|
assertEqual(bracketCount, 4, 'Should have 4 brackets (2 headers + 2 wirings)');
|
|
3820
3785
|
}
|
|
3821
3786
|
|
|
3822
|
-
//
|
|
3823
|
-
function
|
|
3787
|
+
// TEST6408: Machine serialize empty graph
|
|
3788
|
+
function test6408_Machine_serializeEmptyGraph() {
|
|
3824
3789
|
assertEqual(Machine.empty().toMachineNotation(), '');
|
|
3825
3790
|
}
|
|
3826
3791
|
|
|
3827
|
-
//
|
|
3828
|
-
function
|
|
3792
|
+
// TEST6410: Machine roundtrip single edge
|
|
3793
|
+
function test6410_Machine_roundtripSingleEdge() {
|
|
3829
3794
|
const original = new Machine([new MachineEdge(
|
|
3830
3795
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3831
3796
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3838,8 +3803,8 @@ function test0149_Machine_roundtripSingleEdge() {
|
|
|
3838
3803
|
'Single edge round-trip failed: ' + notation);
|
|
3839
3804
|
}
|
|
3840
3805
|
|
|
3841
|
-
//
|
|
3842
|
-
function
|
|
3806
|
+
// TEST6413: Machine roundtrip two edge chain
|
|
3807
|
+
function test6413_Machine_roundtripTwoEdgeChain() {
|
|
3843
3808
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3844
3809
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3845
3810
|
);
|
|
@@ -3853,8 +3818,8 @@ function test0151_Machine_roundtripTwoEdgeChain() {
|
|
|
3853
3818
|
'Two-edge chain round-trip failed: ' + notation);
|
|
3854
3819
|
}
|
|
3855
3820
|
|
|
3856
|
-
//
|
|
3857
|
-
function
|
|
3821
|
+
// TEST6415: Machine roundtrip fan out
|
|
3822
|
+
function test6415_Machine_roundtripFanOut() {
|
|
3858
3823
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3859
3824
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3860
3825
|
);
|
|
@@ -3869,8 +3834,8 @@ function test0152_Machine_roundtripFanOut() {
|
|
|
3869
3834
|
'Fan-out round-trip failed: ' + notation);
|
|
3870
3835
|
}
|
|
3871
3836
|
|
|
3872
|
-
//
|
|
3873
|
-
function
|
|
3837
|
+
// TEST6417: Machine roundtrip loop edge
|
|
3838
|
+
function test6417_Machine_roundtripLoopEdge() {
|
|
3874
3839
|
const original = new Machine([new MachineEdge(
|
|
3875
3840
|
[MediaUrn.fromString('media:disbound-page;enc=utf-8')],
|
|
3876
3841
|
CapUrn.fromString('cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3883,8 +3848,8 @@ function test0153_Machine_roundtripLoopEdge() {
|
|
|
3883
3848
|
assertEqual(reparsed.edges()[0].isLoop, true, 'isLoop must be preserved');
|
|
3884
3849
|
}
|
|
3885
3850
|
|
|
3886
|
-
//
|
|
3887
|
-
function
|
|
3851
|
+
// TEST6419: Machine serialization is deterministic
|
|
3852
|
+
function test6419_Machine_serializationIsDeterministic() {
|
|
3888
3853
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3889
3854
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3890
3855
|
);
|
|
@@ -3897,8 +3862,8 @@ function test0154_Machine_serializationIsDeterministic() {
|
|
|
3897
3862
|
assertEqual(n1, n2, 'Serialization must be deterministic');
|
|
3898
3863
|
}
|
|
3899
3864
|
|
|
3900
|
-
//
|
|
3901
|
-
function
|
|
3865
|
+
// TEST6421: Machine reordered edges produce same notation
|
|
3866
|
+
function test6421_Machine_reorderedEdgesProduceSameNotation() {
|
|
3902
3867
|
const mkEdge = (src, cap, tgt) => new MachineEdge(
|
|
3903
3868
|
[MediaUrn.fromString(src)], CapUrn.fromString(cap), MediaUrn.fromString(tgt), false
|
|
3904
3869
|
);
|
|
@@ -3914,8 +3879,8 @@ function test0155_Machine_reorderedEdgesProduceSameNotation() {
|
|
|
3914
3879
|
'Same graph with reordered edges must produce identical notation');
|
|
3915
3880
|
}
|
|
3916
3881
|
|
|
3917
|
-
//
|
|
3918
|
-
function
|
|
3882
|
+
// TEST6429: Machine multiline serialize format
|
|
3883
|
+
function test6429_Machine_multilineSerializeFormat() {
|
|
3919
3884
|
const g = new Machine([new MachineEdge(
|
|
3920
3885
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3921
3886
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3931,7 +3896,7 @@ function test0160_Machine_multilineSerializeFormat() {
|
|
|
3931
3896
|
|
|
3932
3897
|
// Aliases are pure-index `edge_<N>` regardless of the cap's tags; there is
|
|
3933
3898
|
// no privileged `op` tag to derive a friendlier name from.
|
|
3934
|
-
function
|
|
3899
|
+
function test6432_Machine_aliasFromOpTag() {
|
|
3935
3900
|
const g = new Machine([new MachineEdge(
|
|
3936
3901
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3937
3902
|
CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3942,8 +3907,8 @@ function test0161_Machine_aliasFromOpTag() {
|
|
|
3942
3907
|
assert(notation.includes('[edge_0 '), 'Expected edge_0 alias, got: ' + notation);
|
|
3943
3908
|
}
|
|
3944
3909
|
|
|
3945
|
-
//
|
|
3946
|
-
function
|
|
3910
|
+
// TEST6434: Machine alias fallback without op tag
|
|
3911
|
+
function test6434_Machine_aliasFallbackWithoutOpTag() {
|
|
3947
3912
|
const g = new Machine([new MachineEdge(
|
|
3948
3913
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
3949
3914
|
CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"'),
|
|
@@ -3955,7 +3920,7 @@ function test0162_Machine_aliasFallbackWithoutOpTag() {
|
|
|
3955
3920
|
}
|
|
3956
3921
|
|
|
3957
3922
|
// Pure-index aliases inherently disambiguate edges that share a marker tag.
|
|
3958
|
-
function
|
|
3923
|
+
function test6436_Machine_duplicateOpTagsDisambiguated() {
|
|
3959
3924
|
const g = new Machine([
|
|
3960
3925
|
new MachineEdge(
|
|
3961
3926
|
[MediaUrn.fromString('media:ext=pdf')],
|
|
@@ -3977,7 +3942,7 @@ function test0163_Machine_duplicateOpTagsDisambiguated() {
|
|
|
3977
3942
|
|
|
3978
3943
|
// --- Machine builder tests ---
|
|
3979
3944
|
|
|
3980
|
-
function
|
|
3945
|
+
function test6437_Machine_builderSingleEdge() {
|
|
3981
3946
|
const builder = new MachineBuilder();
|
|
3982
3947
|
builder.addEdge(
|
|
3983
3948
|
['media:ext=pdf'],
|
|
@@ -3989,8 +3954,8 @@ function test0164_Machine_builderSingleEdge() {
|
|
|
3989
3954
|
assertEqual(g.edges()[0].isLoop, false);
|
|
3990
3955
|
}
|
|
3991
3956
|
|
|
3992
|
-
//
|
|
3993
|
-
function
|
|
3957
|
+
// TEST6438: Machine builder with loop
|
|
3958
|
+
function test6438_Machine_builderWithLoop() {
|
|
3994
3959
|
const builder = new MachineBuilder();
|
|
3995
3960
|
builder.addEdge(
|
|
3996
3961
|
['media:disbound-page;enc=utf-8'],
|
|
@@ -4002,8 +3967,8 @@ function test0165_Machine_builderWithLoop() {
|
|
|
4002
3967
|
assertEqual(g.edges()[0].isLoop, true);
|
|
4003
3968
|
}
|
|
4004
3969
|
|
|
4005
|
-
//
|
|
4006
|
-
function
|
|
3970
|
+
// TEST6439: Machine builder chaining
|
|
3971
|
+
function test6439_Machine_builderChaining() {
|
|
4007
3972
|
const g = new MachineBuilder()
|
|
4008
3973
|
.addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
|
|
4009
3974
|
.addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
|
|
@@ -4011,8 +3976,8 @@ function test0166_Machine_builderChaining() {
|
|
|
4011
3976
|
assertEqual(g.edgeCount(), 2);
|
|
4012
3977
|
}
|
|
4013
3978
|
|
|
4014
|
-
//
|
|
4015
|
-
function
|
|
3979
|
+
// TEST6440: Machine builder equivalent to parsed
|
|
3980
|
+
function test6440_Machine_builderEquivalentToParsed() {
|
|
4016
3981
|
const parsed = Machine.fromString(
|
|
4017
3982
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
4018
3983
|
'[doc -> extract -> text]'
|
|
@@ -4024,8 +3989,8 @@ function test0167_Machine_builderEquivalentToParsed() {
|
|
|
4024
3989
|
'Builder-constructed graph should be equivalent to parsed graph');
|
|
4025
3990
|
}
|
|
4026
3991
|
|
|
4027
|
-
//
|
|
4028
|
-
function
|
|
3992
|
+
// TEST6442: Machine builder round trip
|
|
3993
|
+
function test6442_Machine_builderRoundTrip() {
|
|
4029
3994
|
const built = new MachineBuilder()
|
|
4030
3995
|
.addEdge(['media:ext=pdf'], 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"', 'media:enc=utf-8;ext=txt')
|
|
4031
3996
|
.addEdge(['media:enc=utf-8;ext=txt'], 'cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"', 'media:embedding-vector;enc=utf-8;record')
|
|
@@ -4037,7 +4002,7 @@ function test0168_Machine_builderRoundTrip() {
|
|
|
4037
4002
|
|
|
4038
4003
|
// --- CapUrn.isEquivalent/isComparable tests ---
|
|
4039
4004
|
|
|
4040
|
-
function
|
|
4005
|
+
function test6444_Machine_capUrnIsEquivalent() {
|
|
4041
4006
|
const a = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4042
4007
|
const b = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4043
4008
|
assert(a.isEquivalent(b), 'Same cap URNs should be equivalent');
|
|
@@ -4045,24 +4010,24 @@ function test0169_Machine_capUrnIsEquivalent() {
|
|
|
4045
4010
|
assert(!a.isEquivalent(c), 'Different cap URNs should not be equivalent');
|
|
4046
4011
|
}
|
|
4047
4012
|
|
|
4048
|
-
//
|
|
4049
|
-
function
|
|
4013
|
+
// TEST6446: Machine cap urn is comparable
|
|
4014
|
+
function test6446_Machine_capUrnIsComparable() {
|
|
4050
4015
|
const general = CapUrn.fromString('cap:in="media:ext=pdf";out="media:enc=utf-8;ext=txt"');
|
|
4051
4016
|
const specific = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4052
4017
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
4053
4018
|
assert(specific.isComparable(general), 'isComparable should be symmetric');
|
|
4054
4019
|
}
|
|
4055
4020
|
|
|
4056
|
-
//
|
|
4057
|
-
function
|
|
4021
|
+
// TEST6448: Machine cap urn in media urn
|
|
4022
|
+
function test6448_Machine_capUrnInMediaUrn() {
|
|
4058
4023
|
const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4059
4024
|
const inUrn = cap.inMediaUrn();
|
|
4060
4025
|
assert(inUrn instanceof MediaUrn, 'inMediaUrn should return MediaUrn');
|
|
4061
4026
|
assert(inUrn.isEquivalent(MediaUrn.fromString('media:ext=pdf')), 'inMediaUrn should be media:ext=pdf');
|
|
4062
4027
|
}
|
|
4063
4028
|
|
|
4064
|
-
//
|
|
4065
|
-
function
|
|
4029
|
+
// TEST6449: Machine cap urn out media urn
|
|
4030
|
+
function test6449_Machine_capUrnOutMediaUrn() {
|
|
4066
4031
|
const cap = CapUrn.fromString('cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
4067
4032
|
const outUrn = cap.outMediaUrn();
|
|
4068
4033
|
assert(outUrn instanceof MediaUrn, 'outMediaUrn should return MediaUrn');
|
|
@@ -4071,7 +4036,7 @@ function test0172_Machine_capUrnOutMediaUrn() {
|
|
|
4071
4036
|
|
|
4072
4037
|
// --- MediaUrn.isEquivalent/isComparable tests ---
|
|
4073
4038
|
|
|
4074
|
-
function
|
|
4039
|
+
function test6450_Machine_mediaUrnIsEquivalent() {
|
|
4075
4040
|
const a = MediaUrn.fromString('media:ext=pdf');
|
|
4076
4041
|
const b = MediaUrn.fromString('media:ext=pdf');
|
|
4077
4042
|
assert(a.isEquivalent(b), 'Same media URNs should be equivalent');
|
|
@@ -4079,8 +4044,8 @@ function test0173_Machine_mediaUrnIsEquivalent() {
|
|
|
4079
4044
|
assert(!a.isEquivalent(c), 'Different media URNs should not be equivalent');
|
|
4080
4045
|
}
|
|
4081
4046
|
|
|
4082
|
-
//
|
|
4083
|
-
function
|
|
4047
|
+
// TEST6451: Machine media urn is comparable
|
|
4048
|
+
function test6451_Machine_mediaUrnIsComparable() {
|
|
4084
4049
|
const general = MediaUrn.fromString('media:enc=utf-8');
|
|
4085
4050
|
const specific = MediaUrn.fromString('media:enc=utf-8;ext=txt');
|
|
4086
4051
|
assert(general.isComparable(specific), 'General should be comparable to specific');
|
|
@@ -4093,7 +4058,7 @@ function test0174_Machine_mediaUrnIsComparable() {
|
|
|
4093
4058
|
// Phase 0A: Position tracking tests
|
|
4094
4059
|
// ============================================================================
|
|
4095
4060
|
|
|
4096
|
-
function
|
|
4061
|
+
function test6452_Machine_parseMachineWithAST_headerLocation() {
|
|
4097
4062
|
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"][doc -> extract -> text]';
|
|
4098
4063
|
const result = parseMachineWithAST(input);
|
|
4099
4064
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
@@ -4109,8 +4074,8 @@ function test0175_Machine_parseMachineWithAST_headerLocation() {
|
|
|
4109
4074
|
assertEqual(stmt.alias, 'extract', 'Alias should be extract');
|
|
4110
4075
|
}
|
|
4111
4076
|
|
|
4112
|
-
//
|
|
4113
|
-
function
|
|
4077
|
+
// TEST6453: Machine parse machine with a s t wiring location
|
|
4078
|
+
function test6453_Machine_parseMachineWithAST_wiringLocation() {
|
|
4114
4079
|
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
|
|
4115
4080
|
const result = parseMachineWithAST(input);
|
|
4116
4081
|
assert(result.statements.length === 2, 'Should have 2 statements');
|
|
@@ -4124,8 +4089,8 @@ function test0176_Machine_parseMachineWithAST_wiringLocation() {
|
|
|
4124
4089
|
assertEqual(wiring.target, 'text', 'Target should be text');
|
|
4125
4090
|
}
|
|
4126
4091
|
|
|
4127
|
-
//
|
|
4128
|
-
function
|
|
4092
|
+
// TEST6454: Machine parse machine with a s t multiline positions
|
|
4093
|
+
function test6454_Machine_parseMachineWithAST_multilinePositions() {
|
|
4129
4094
|
const input = '[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]\n[doc -> extract -> text]';
|
|
4130
4095
|
const result = parseMachineWithAST(input);
|
|
4131
4096
|
const headerLoc = result.statements[0].location;
|
|
@@ -4134,8 +4099,8 @@ function test0177_Machine_parseMachineWithAST_multilinePositions() {
|
|
|
4134
4099
|
assertEqual(wiringLoc.start.line, 2, 'Wiring should be on line 2');
|
|
4135
4100
|
}
|
|
4136
4101
|
|
|
4137
|
-
//
|
|
4138
|
-
function
|
|
4102
|
+
// TEST6455: Machine parse machine with a s t fan in source locations
|
|
4103
|
+
function test6455_Machine_parseMachineWithAST_fanInSourceLocations() {
|
|
4139
4104
|
const input = [
|
|
4140
4105
|
'[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]',
|
|
4141
4106
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -4146,8 +4111,8 @@ function test0178_Machine_parseMachineWithAST_fanInSourceLocations() {
|
|
|
4146
4111
|
assert(wiring.sourceLocations.length === 2, 'Should have 2 source locations');
|
|
4147
4112
|
}
|
|
4148
4113
|
|
|
4149
|
-
//
|
|
4150
|
-
function
|
|
4114
|
+
// TEST6456: Machine parse machine with a s t alias map
|
|
4115
|
+
function test6456_Machine_parseMachineWithAST_aliasMap() {
|
|
4151
4116
|
const input = [
|
|
4152
4117
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
|
|
4153
4118
|
'[embed cap:in="media:enc=utf-8;ext=txt";embed;out="media:embedding-vector;enc=utf-8;record"]',
|
|
@@ -4165,8 +4130,8 @@ function test0179_Machine_parseMachineWithAST_aliasMap() {
|
|
|
4165
4130
|
assert(extractEntry.capUrnLocation !== undefined, 'Alias entry should have capUrnLocation');
|
|
4166
4131
|
}
|
|
4167
4132
|
|
|
4168
|
-
//
|
|
4169
|
-
function
|
|
4133
|
+
// TEST6457: Machine parse machine with a s t node media
|
|
4134
|
+
function test6457_Machine_parseMachineWithAST_nodeMedia() {
|
|
4170
4135
|
const input = [
|
|
4171
4136
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]',
|
|
4172
4137
|
'[doc -> extract -> text]',
|
|
@@ -4178,8 +4143,8 @@ function test0180_Machine_parseMachineWithAST_nodeMedia() {
|
|
|
4178
4143
|
assertEqual(result.nodeMedia.get('text').toString(), 'media:enc=utf-8;ext=txt', 'text should be media:enc=utf-8;ext=txt');
|
|
4179
4144
|
}
|
|
4180
4145
|
|
|
4181
|
-
//
|
|
4182
|
-
function
|
|
4146
|
+
// TEST6458: Machine error location parse error
|
|
4147
|
+
function test6458_Machine_errorLocation_parseError() {
|
|
4183
4148
|
try {
|
|
4184
4149
|
parseMachine('[this is not valid');
|
|
4185
4150
|
throw new Error('Expected MachineSyntaxError');
|
|
@@ -4189,8 +4154,8 @@ function test0181_Machine_errorLocation_parseError() {
|
|
|
4189
4154
|
}
|
|
4190
4155
|
}
|
|
4191
4156
|
|
|
4192
|
-
//
|
|
4193
|
-
function
|
|
4157
|
+
// TEST6459: Machine error location duplicate alias
|
|
4158
|
+
function test6459_Machine_errorLocation_duplicateAlias() {
|
|
4194
4159
|
try {
|
|
4195
4160
|
parseMachine(
|
|
4196
4161
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
@@ -4204,8 +4169,8 @@ function test0182_Machine_errorLocation_duplicateAlias() {
|
|
|
4204
4169
|
}
|
|
4205
4170
|
}
|
|
4206
4171
|
|
|
4207
|
-
//
|
|
4208
|
-
function
|
|
4172
|
+
// TEST6460: Machine error location undefined alias
|
|
4173
|
+
function test6460_Machine_errorLocation_undefinedAlias() {
|
|
4209
4174
|
try {
|
|
4210
4175
|
parseMachine('[doc -> nonexistent -> text]');
|
|
4211
4176
|
throw new Error('Expected MachineSyntaxError');
|
|
@@ -4219,7 +4184,7 @@ function test0183_Machine_errorLocation_undefinedAlias() {
|
|
|
4219
4184
|
// Phase 0C: Machine.toMermaid() tests
|
|
4220
4185
|
// ============================================================================
|
|
4221
4186
|
|
|
4222
|
-
function
|
|
4187
|
+
function test6462_Machine_toMermaid_linearChain() {
|
|
4223
4188
|
const machine = Machine.fromString(
|
|
4224
4189
|
'[extract cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
4225
4190
|
'[doc -> extract -> text]'
|
|
@@ -4237,8 +4202,8 @@ function test0184_Machine_toMermaid_linearChain() {
|
|
|
4237
4202
|
assert(mermaid.includes('(['), 'Should have stadium shape nodes');
|
|
4238
4203
|
}
|
|
4239
4204
|
|
|
4240
|
-
//
|
|
4241
|
-
function
|
|
4205
|
+
// TEST6463: Machine to mermaid loop edge
|
|
4206
|
+
function test6463_Machine_toMermaid_loopEdge() {
|
|
4242
4207
|
const machine = Machine.fromString(
|
|
4243
4208
|
'[p2t cap:in="media:disbound-page;enc=utf-8";page-to-text;out="media:enc=utf-8;ext=txt"]' +
|
|
4244
4209
|
'[pages -> LOOP p2t -> texts]'
|
|
@@ -4249,15 +4214,15 @@ function test0185_Machine_toMermaid_loopEdge() {
|
|
|
4249
4214
|
assert(mermaid.includes('.->'), 'Should use dotted arrow for LOOP');
|
|
4250
4215
|
}
|
|
4251
4216
|
|
|
4252
|
-
//
|
|
4253
|
-
function
|
|
4217
|
+
// TEST6464: Machine to mermaid empty graph
|
|
4218
|
+
function test6464_Machine_toMermaid_emptyGraph() {
|
|
4254
4219
|
const machine = Machine.empty();
|
|
4255
4220
|
const mermaid = machine.toMermaid();
|
|
4256
4221
|
assert(mermaid.includes('empty graph'), 'Should indicate empty graph');
|
|
4257
4222
|
}
|
|
4258
4223
|
|
|
4259
|
-
//
|
|
4260
|
-
function
|
|
4224
|
+
// TEST6465: Machine to mermaid fan in
|
|
4225
|
+
function test6465_Machine_toMermaid_fanIn() {
|
|
4261
4226
|
const machine = Machine.fromString(
|
|
4262
4227
|
'[describe cap:in="media:ext=png;image";describe-image;out="media:enc=utf-8;image-description"]' +
|
|
4263
4228
|
'[(thumbnail, model_spec) -> describe -> description]'
|
|
@@ -4268,8 +4233,8 @@ function test0187_Machine_toMermaid_fanIn() {
|
|
|
4268
4233
|
assertEqual(arrowCount, 2, 'Fan-in should produce 2 arrows');
|
|
4269
4234
|
}
|
|
4270
4235
|
|
|
4271
|
-
//
|
|
4272
|
-
function
|
|
4236
|
+
// TEST6466: Machine to mermaid fan out
|
|
4237
|
+
function test6466_Machine_toMermaid_fanOut() {
|
|
4273
4238
|
const input = [
|
|
4274
4239
|
'[meta cap:in="media:ext=pdf";extract-metadata;out="media:enc=utf-8;file-metadata;record"]',
|
|
4275
4240
|
'[thumb cap:in="media:ext=pdf";generate-thumbnail;out="media:ext=png;image;thumbnail"]',
|
|
@@ -4289,7 +4254,7 @@ function test0188_Machine_toMermaid_fanOut() {
|
|
|
4289
4254
|
// Phase 0B: FabricRegistryClient tests
|
|
4290
4255
|
// ============================================================================
|
|
4291
4256
|
|
|
4292
|
-
function
|
|
4257
|
+
function test6467_Machine_capRegistryEntry_construction() {
|
|
4293
4258
|
const entry = new FabricRegistryEntry({
|
|
4294
4259
|
urn: 'cap:in="media:ext=pdf";extract;out="media:enc=utf-8;ext=txt"',
|
|
4295
4260
|
title: 'PDF Extractor',
|
|
@@ -4312,8 +4277,8 @@ function test0189_Machine_capRegistryEntry_construction() {
|
|
|
4312
4277
|
assertEqual(entry.urnTags.op, 'extract', 'op tag should match');
|
|
4313
4278
|
}
|
|
4314
4279
|
|
|
4315
|
-
//
|
|
4316
|
-
function
|
|
4280
|
+
// TEST6468: Machine media registry entry construction
|
|
4281
|
+
function test6468_Machine_mediaRegistryEntry_construction() {
|
|
4317
4282
|
const entry = new MediaRegistryEntry({
|
|
4318
4283
|
urn: 'media:ext=pdf',
|
|
4319
4284
|
title: 'PDF Document',
|
|
@@ -4326,16 +4291,16 @@ function test0190_Machine_mediaRegistryEntry_construction() {
|
|
|
4326
4291
|
assertEqual(entry.description, 'Portable Document Format', 'Description should match');
|
|
4327
4292
|
}
|
|
4328
4293
|
|
|
4329
|
-
//
|
|
4330
|
-
function
|
|
4294
|
+
// TEST6469: Machine cap registry client construction
|
|
4295
|
+
function test6469_Machine_capRegistryClient_construction() {
|
|
4331
4296
|
const client = new FabricRegistryClient('https://example.com', 600);
|
|
4332
4297
|
assert(client !== null, 'Client should be constructed');
|
|
4333
4298
|
// Invalidate should not throw
|
|
4334
4299
|
client.invalidate();
|
|
4335
4300
|
}
|
|
4336
4301
|
|
|
4337
|
-
//
|
|
4338
|
-
function
|
|
4302
|
+
// TEST6470: Machine cap registry entry defaults
|
|
4303
|
+
function test6470_Machine_capRegistryEntry_defaults() {
|
|
4339
4304
|
// Verify that missing fields default gracefully
|
|
4340
4305
|
const entry = new FabricRegistryEntry({ urn: 'cap:in=media:;test;out=media:' });
|
|
4341
4306
|
assertEqual(entry.urn, 'cap:in=media:;test;out=media:', 'URN should match');
|
|
@@ -4400,16 +4365,16 @@ if (typeof global.createCap === 'undefined') {
|
|
|
4400
4365
|
global.createCap = require('./capdag.js').createCap;
|
|
4401
4366
|
}
|
|
4402
4367
|
|
|
4403
|
-
//
|
|
4404
|
-
function
|
|
4368
|
+
// TEST6471: Renderer cardinality label all four cases
|
|
4369
|
+
function test6471_Renderer_cardinalityLabel_allFourCases() {
|
|
4405
4370
|
assertEqual(rendererCardinalityLabel(false, false), '1\u21921', 'scalar-to-scalar');
|
|
4406
4371
|
assertEqual(rendererCardinalityLabel(true, false), 'n\u21921', 'sequence-to-scalar');
|
|
4407
4372
|
assertEqual(rendererCardinalityLabel(false, true), '1\u2192n', 'scalar-to-sequence');
|
|
4408
4373
|
assertEqual(rendererCardinalityLabel(true, true), 'n\u2192n', 'sequence-to-sequence');
|
|
4409
4374
|
}
|
|
4410
4375
|
|
|
4411
|
-
//
|
|
4412
|
-
function
|
|
4376
|
+
// TEST6472: Renderer cardinality label uses unicode arrow
|
|
4377
|
+
function test6472_Renderer_cardinalityLabel_usesUnicodeArrow() {
|
|
4413
4378
|
// The label must use the real rightwards arrow (U+2192), not ASCII "->".
|
|
4414
4379
|
// Downstream styling and tests depend on this glyph.
|
|
4415
4380
|
const label = rendererCardinalityLabel(false, true);
|
|
@@ -4417,8 +4382,8 @@ function test0194_Renderer_cardinalityLabel_usesUnicodeArrow() {
|
|
|
4417
4382
|
assert(!label.includes('->'), 'label must not contain the ASCII replacement "->"');
|
|
4418
4383
|
}
|
|
4419
4384
|
|
|
4420
|
-
//
|
|
4421
|
-
function
|
|
4385
|
+
// TEST6473: Renderer cardinality from cap finds stdin arg not first arg
|
|
4386
|
+
function test6473_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
|
|
4422
4387
|
// The main input arg is the one whose sources include a stdin source.
|
|
4423
4388
|
// A naive implementation that reads args[0] would see `cli-only` (not a
|
|
4424
4389
|
// sequence) and report 1→1 even though the stdin arg is a sequence.
|
|
@@ -4442,8 +4407,8 @@ function test0195_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg() {
|
|
|
4442
4407
|
'must pick the arg that has a stdin source, not args[0]');
|
|
4443
4408
|
}
|
|
4444
4409
|
|
|
4445
|
-
//
|
|
4446
|
-
function
|
|
4410
|
+
// TEST6474: Renderer cardinality from cap scalar defaults when fields missing
|
|
4411
|
+
function test6474_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing() {
|
|
4447
4412
|
// No args and no output: both sides collapse to 1 (scalar default).
|
|
4448
4413
|
// If a bug makes the function return "n" for missing data, this fails.
|
|
4449
4414
|
const cap = { urn: 'cap:in="media:";noop;out="media:"' };
|
|
@@ -4451,8 +4416,8 @@ function test0196_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing()
|
|
|
4451
4416
|
'missing args/output must default to scalar on both sides');
|
|
4452
4417
|
}
|
|
4453
4418
|
|
|
4454
|
-
//
|
|
4455
|
-
function
|
|
4419
|
+
// TEST6475: Renderer cardinality from cap output only sequence
|
|
4420
|
+
function test6475_Renderer_cardinalityFromCap_outputOnlySequence() {
|
|
4456
4421
|
// One scalar stdin arg, output is a sequence: expects 1→n.
|
|
4457
4422
|
const cap = {
|
|
4458
4423
|
urn: 'cap:in="media:enc=utf-8";generate;out="media:enc=utf-8;list"',
|
|
@@ -4463,8 +4428,8 @@ function test0197_Renderer_cardinalityFromCap_outputOnlySequence() {
|
|
|
4463
4428
|
'scalar stdin with sequence output must yield 1→n');
|
|
4464
4429
|
}
|
|
4465
4430
|
|
|
4466
|
-
//
|
|
4467
|
-
function
|
|
4431
|
+
// TEST6476: Renderer cardinality from cap rejects string is sequence
|
|
4432
|
+
function test6476_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
|
|
4468
4433
|
// The function must use strict `=== true` to avoid treating truthy strings
|
|
4469
4434
|
// as booleans. "true" is a string, not a boolean — it must NOT be treated
|
|
4470
4435
|
// as a sequence, because downstream renderers expect boolean semantics.
|
|
@@ -4477,8 +4442,8 @@ function test0198_Renderer_cardinalityFromCap_rejectsStringIsSequence() {
|
|
|
4477
4442
|
'string "true" must not be treated as boolean true');
|
|
4478
4443
|
}
|
|
4479
4444
|
|
|
4480
|
-
//
|
|
4481
|
-
function
|
|
4445
|
+
// TEST6478: Renderer cardinality from cap throws on non object
|
|
4446
|
+
function test6478_Renderer_cardinalityFromCap_throwsOnNonObject() {
|
|
4482
4447
|
// Fail-hard on invalid input; no fallback to a default cardinality.
|
|
4483
4448
|
let threw = false;
|
|
4484
4449
|
try {
|
|
@@ -4497,8 +4462,8 @@ function test0199_Renderer_cardinalityFromCap_throwsOnNonObject() {
|
|
|
4497
4462
|
assert(threw, 'cardinalityFromCap(string) must throw');
|
|
4498
4463
|
}
|
|
4499
4464
|
|
|
4500
|
-
//
|
|
4501
|
-
function
|
|
4465
|
+
// TEST6479: Renderer canonical media urn normalizes tag order
|
|
4466
|
+
function test6479_Renderer_canonicalMediaUrn_normalizesTagOrder() {
|
|
4502
4467
|
// Two media URNs with identical tags in different input orders must
|
|
4503
4468
|
// produce the same canonical string. If canonicalization is bypassed,
|
|
4504
4469
|
// the two strings remain different and this test exposes it.
|
|
@@ -4507,14 +4472,14 @@ function test0200_Renderer_canonicalMediaUrn_normalizesTagOrder() {
|
|
|
4507
4472
|
assertEqual(a, b, 'tag-order differences must not survive canonicalization');
|
|
4508
4473
|
}
|
|
4509
4474
|
|
|
4510
|
-
//
|
|
4511
|
-
function
|
|
4475
|
+
// TEST6480: Renderer canonical media urn preserves value tags
|
|
4476
|
+
function test6480_Renderer_canonicalMediaUrn_preservesValueTags() {
|
|
4512
4477
|
const c = rendererCanonicalMediaUrn('media:model;quant=q4');
|
|
4513
4478
|
assert(c.includes('quant=q4'), 'value tag must be preserved');
|
|
4514
4479
|
}
|
|
4515
4480
|
|
|
4516
|
-
//
|
|
4517
|
-
function
|
|
4481
|
+
// TEST6481: Renderer canonical media urn rejects cap urn
|
|
4482
|
+
function test6481_Renderer_canonicalMediaUrn_rejectsCapUrn() {
|
|
4518
4483
|
// MediaUrn.fromString enforces the media: prefix. Feeding a cap URN to
|
|
4519
4484
|
// canonicalMediaUrn must fail hard.
|
|
4520
4485
|
let threw = false;
|
|
@@ -4526,8 +4491,8 @@ function test0202_Renderer_canonicalMediaUrn_rejectsCapUrn() {
|
|
|
4526
4491
|
assert(threw, 'canonicalMediaUrn must reject non-media URNs');
|
|
4527
4492
|
}
|
|
4528
4493
|
|
|
4529
|
-
//
|
|
4530
|
-
function
|
|
4494
|
+
// TEST6482: Renderer media node label rejects urn derived labels
|
|
4495
|
+
function test6482_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
|
|
4531
4496
|
let threw = false;
|
|
4532
4497
|
let message = '';
|
|
4533
4498
|
try {
|
|
@@ -4541,8 +4506,8 @@ function test0203_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels() {
|
|
|
4541
4506
|
'error must explain that explicit titles are required');
|
|
4542
4507
|
}
|
|
4543
4508
|
|
|
4544
|
-
//
|
|
4545
|
-
function
|
|
4509
|
+
// TEST6483: Renderer build browse graph data rejects missing media titles
|
|
4510
|
+
function test6483_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles() {
|
|
4546
4511
|
let threw = false;
|
|
4547
4512
|
let message = '';
|
|
4548
4513
|
try {
|
|
@@ -4599,8 +4564,8 @@ function makeCollectStep(mediaDef) {
|
|
|
4599
4564
|
};
|
|
4600
4565
|
}
|
|
4601
4566
|
|
|
4602
|
-
//
|
|
4603
|
-
function
|
|
4567
|
+
// TEST6484: Renderer validate strand step rejects unknown variant
|
|
4568
|
+
function test6484_Renderer_validateStrandStep_rejectsUnknownVariant() {
|
|
4604
4569
|
// A step with an unknown variant must fail hard at validation; no
|
|
4605
4570
|
// silent coercion.
|
|
4606
4571
|
let threw = false;
|
|
@@ -4617,8 +4582,8 @@ function test0205_Renderer_validateStrandStep_rejectsUnknownVariant() {
|
|
|
4617
4582
|
assert(threw, 'unknown variant must throw');
|
|
4618
4583
|
}
|
|
4619
4584
|
|
|
4620
|
-
//
|
|
4621
|
-
function
|
|
4585
|
+
// TEST6486: Renderer validate strand step requires boolean is sequence
|
|
4586
|
+
function test6486_Renderer_validateStrandStep_requiresBooleanIsSequence() {
|
|
4622
4587
|
// A Cap variant must have boolean is_sequence fields; number or string
|
|
4623
4588
|
// must reject.
|
|
4624
4589
|
let threw = false;
|
|
@@ -4640,8 +4605,8 @@ function test0206_Renderer_validateStrandStep_requiresBooleanIsSequence() {
|
|
|
4640
4605
|
assert(threw, 'non-boolean is_sequence must throw');
|
|
4641
4606
|
}
|
|
4642
4607
|
|
|
4643
|
-
//
|
|
4644
|
-
function
|
|
4608
|
+
// TEST6487: Renderer classify strand cap steps cap flags
|
|
4609
|
+
function test6487_Renderer_classifyStrandCapSteps_capFlags() {
|
|
4645
4610
|
// Strand: ForEach → cap1 → cap2 → cap3 → Collect. cap1 should have
|
|
4646
4611
|
// prevForEach=true; cap3 should have nextCollect=true; cap2 should
|
|
4647
4612
|
// have neither.
|
|
@@ -4662,8 +4627,8 @@ function test0207_Renderer_classifyStrandCapSteps_capFlags() {
|
|
|
4662
4627
|
assert(capFlags.get(3).nextCollect, 'cap3 has nextCollect');
|
|
4663
4628
|
}
|
|
4664
4629
|
|
|
4665
|
-
//
|
|
4666
|
-
function
|
|
4630
|
+
// TEST6488: Renderer classify strand cap steps nested forks
|
|
4631
|
+
function test6488_Renderer_classifyStrandCapSteps_nestedForks() {
|
|
4667
4632
|
// Nested strand: ForEach → cap1 → ForEach → cap2 → Collect → cap3 → Collect.
|
|
4668
4633
|
// cap1 has prevForEach (outer), cap2 has prevForEach (inner) and
|
|
4669
4634
|
// nextCollect (inner), cap3 has nextCollect (outer).
|
|
@@ -4693,8 +4658,8 @@ function withMediaDisplayNames(payload, mediaDisplayNames) {
|
|
|
4693
4658
|
});
|
|
4694
4659
|
}
|
|
4695
4660
|
|
|
4696
|
-
//
|
|
4697
|
-
function
|
|
4661
|
+
// TEST6489: Renderer build strand graph data single cap plain
|
|
4662
|
+
function test6489_Renderer_buildStrandGraphData_singleCapPlain() {
|
|
4698
4663
|
// Minimal strand with one plain 1→1 cap. Plan builder produces:
|
|
4699
4664
|
// input_slot → step_0 (cap) → output
|
|
4700
4665
|
// (two edges, three nodes). No cardinality marker in the cap label
|
|
@@ -4721,8 +4686,8 @@ function test0209_Renderer_buildStrandGraphData_singleCapPlain() {
|
|
|
4721
4686
|
assert(outEdge !== undefined, 'output edge from step_0 to output exists');
|
|
4722
4687
|
}
|
|
4723
4688
|
|
|
4724
|
-
//
|
|
4725
|
-
function
|
|
4689
|
+
// TEST6491: Renderer build strand graph data sequence shows cardinality
|
|
4690
|
+
function test6491_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
4726
4691
|
// A cap with input_is_sequence=true MUST emit "(n→1)" on its edge
|
|
4727
4692
|
// label.
|
|
4728
4693
|
const payload = withMediaDisplayNames({
|
|
@@ -4742,8 +4707,8 @@ function test0210_Renderer_buildStrandGraphData_sequenceShowsCardinality() {
|
|
|
4742
4707
|
`cap edge label must include (n\u21921) marker; got: ${capEdge.label}`);
|
|
4743
4708
|
}
|
|
4744
4709
|
|
|
4745
|
-
//
|
|
4746
|
-
function
|
|
4710
|
+
// TEST6492: Renderer build strand graph data foreach collect span
|
|
4711
|
+
function test6492_Renderer_buildStrandGraphData_foreachCollectSpan() {
|
|
4747
4712
|
// Strand: [ForEach, Cap, Collect]. Plan builder produces:
|
|
4748
4713
|
// input_slot (source) →direct→ step_1 (cap) — cap emits its own
|
|
4749
4714
|
// direct edge from prev
|
|
@@ -4794,8 +4759,8 @@ function test0211_Renderer_buildStrandGraphData_foreachCollectSpan() {
|
|
|
4794
4759
|
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4795
4760
|
}
|
|
4796
4761
|
|
|
4797
|
-
//
|
|
4798
|
-
function
|
|
4762
|
+
// TEST6493: Renderer build strand graph data standalone collect
|
|
4763
|
+
function test6493_Renderer_buildStrandGraphData_standaloneCollect() {
|
|
4799
4764
|
// Strand with a standalone Collect (no enclosing ForEach). Plan
|
|
4800
4765
|
// builder creates a Collect node consuming prev directly — plain
|
|
4801
4766
|
// direct edge, no iteration/collection semantics.
|
|
@@ -4822,8 +4787,8 @@ function test0212_Renderer_buildStrandGraphData_standaloneCollect() {
|
|
|
4822
4787
|
assertEqual(collectNode.label, 'collect', 'Collect node labeled "collect"');
|
|
4823
4788
|
}
|
|
4824
4789
|
|
|
4825
|
-
//
|
|
4826
|
-
function
|
|
4790
|
+
// TEST6494: Renderer build strand graph data unclosed for each body
|
|
4791
|
+
function test6494_Renderer_buildStrandGraphData_unclosedForEachBody() {
|
|
4827
4792
|
// Strand: [Cap_a, ForEach, Cap_b] with no closing Collect. The plan
|
|
4828
4793
|
// builder's "unclosed ForEach" branch creates a ForEach node
|
|
4829
4794
|
// connecting Cap_a to Cap_b via iteration, with prev becoming the
|
|
@@ -4860,8 +4825,8 @@ function test0213_Renderer_buildStrandGraphData_unclosedForEachBody() {
|
|
|
4860
4825
|
'output edge step_2 → output');
|
|
4861
4826
|
}
|
|
4862
4827
|
|
|
4863
|
-
//
|
|
4864
|
-
function
|
|
4828
|
+
// TEST6495: Renderer build strand graph data nested for each throws
|
|
4829
|
+
function test6495_Renderer_buildStrandGraphData_nestedForEachThrows() {
|
|
4865
4830
|
// Nested ForEach without an intervening body cap in the outer
|
|
4866
4831
|
// ForEach is an illegal nesting per plan_builder. The renderer
|
|
4867
4832
|
// must throw the same error to surface the issue rather than
|
|
@@ -4889,8 +4854,8 @@ function test0214_Renderer_buildStrandGraphData_nestedForEachThrows() {
|
|
|
4889
4854
|
assert(threw, 'nested ForEach without outer body cap must throw');
|
|
4890
4855
|
}
|
|
4891
4856
|
|
|
4892
|
-
//
|
|
4893
|
-
function
|
|
4857
|
+
// TEST6496: Renderer collapse strand single cap body keeps cap own label
|
|
4858
|
+
function test6496_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
4894
4859
|
// User spec: ForEach/Collect are NOT rendered as nodes, and
|
|
4895
4860
|
// the collapse does NOT relabel cap edges. Each cap edge
|
|
4896
4861
|
// carries whatever label the strand builder emitted — the
|
|
@@ -4942,8 +4907,8 @@ function test0215_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel() {
|
|
|
4942
4907
|
'collect bridge is unlabeled');
|
|
4943
4908
|
}
|
|
4944
4909
|
|
|
4945
|
-
//
|
|
4946
|
-
function
|
|
4910
|
+
// TEST6497: Renderer collapse strand unclosed for each body collapses
|
|
4911
|
+
function test6497_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
4947
4912
|
// [Cap_a(1→1), ForEach, Cap_b(1→1)] with no Collect,
|
|
4948
4913
|
// source=media:a, target=media:c. Cap_b's to_spec is media:c
|
|
4949
4914
|
// which is equivalent to target_media_urn, so the output node is
|
|
@@ -4999,8 +4964,8 @@ function test0216_Renderer_collapseStrand_unclosedForEachBodyCollapses() {
|
|
|
4999
4964
|
'merged step_2 takes on the strand-target role');
|
|
5000
4965
|
}
|
|
5001
4966
|
|
|
5002
|
-
//
|
|
5003
|
-
function
|
|
4967
|
+
// TEST6498: Renderer collapse strand standalone collect collapses
|
|
4968
|
+
function test6498_Renderer_collapseStrand_standaloneCollectCollapses() {
|
|
5004
4969
|
// [Cap, Collect] with no enclosing ForEach, source=media:a,
|
|
5005
4970
|
// target=media:b;list (NOT equivalent to cap's to_spec media:b,
|
|
5006
4971
|
// so the output node is retained after collapse).
|
|
@@ -5043,8 +5008,8 @@ function test0217_Renderer_collapseStrand_standaloneCollectCollapses() {
|
|
|
5043
5008
|
'the synthesized bridging edge for a standalone Collect is an unlabeled connector (cap labels carry all cardinality info)');
|
|
5044
5009
|
}
|
|
5045
5010
|
|
|
5046
|
-
//
|
|
5047
|
-
function
|
|
5011
|
+
// TEST6499: Renderer collapse strand sequence producing cap before foreach
|
|
5012
|
+
function test6499_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
5048
5013
|
// Regression test mirroring the user's real strand:
|
|
5049
5014
|
// [Cap_disbind (output_is_sequence=true), ForEach, Cap_make_decision],
|
|
5050
5015
|
// source = media:ext=pdf, target = media:decision (equivalent to
|
|
@@ -5108,8 +5073,8 @@ function test0218_Renderer_collapseStrand_sequenceProducingCapBeforeForeach() {
|
|
|
5108
5073
|
'output node merged into step_2 because they represent the same URN');
|
|
5109
5074
|
}
|
|
5110
5075
|
|
|
5111
|
-
//
|
|
5112
|
-
function
|
|
5076
|
+
// TEST6500: Renderer collapse strand plain cap merges trailing output
|
|
5077
|
+
function test6500_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
|
|
5113
5078
|
// A strand with a single plain 1→1 cap whose to_spec equals
|
|
5114
5079
|
// target_media_urn. The plan-builder topology produces:
|
|
5115
5080
|
// input_slot → step_0 (cap) → output
|
|
@@ -5145,8 +5110,8 @@ function test0219_Renderer_collapseStrand_plainCapMergesTrailingOutput() {
|
|
|
5145
5110
|
assertEqual(collapsed.edges[0].label, 'x', 'cap title preserved as edge label');
|
|
5146
5111
|
}
|
|
5147
5112
|
|
|
5148
|
-
//
|
|
5149
|
-
function
|
|
5113
|
+
// TEST6501: Renderer collapse strand plain cap distinct target no merge
|
|
5114
|
+
function test6501_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
|
|
5150
5115
|
// A strand with a single plain cap whose to_spec is NOT
|
|
5151
5116
|
// equivalent to target_media_urn. The output node must be retained
|
|
5152
5117
|
// and the trailing connector edge preserved.
|
|
@@ -5172,8 +5137,8 @@ function test0220_Renderer_collapseStrand_plainCapDistinctTargetNoMerge() {
|
|
|
5172
5137
|
'step_0 retained');
|
|
5173
5138
|
}
|
|
5174
5139
|
|
|
5175
|
-
//
|
|
5176
|
-
function
|
|
5140
|
+
// TEST6502: Renderer validate strand payload missing source media urn
|
|
5141
|
+
function test6502_Renderer_validateStrandPayload_missingSourceMediaUrn() {
|
|
5177
5142
|
let threw = false;
|
|
5178
5143
|
try {
|
|
5179
5144
|
rendererValidateStrandPayload({ target_media_urn: 'media:b', steps: [] });
|
|
@@ -5186,7 +5151,7 @@ function test0221_Renderer_validateStrandPayload_missingSourceMediaUrn() {
|
|
|
5186
5151
|
|
|
5187
5152
|
// ---------------- run builder ----------------
|
|
5188
5153
|
|
|
5189
|
-
function
|
|
5154
|
+
function test6503_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
|
|
5190
5155
|
let threw = false;
|
|
5191
5156
|
try {
|
|
5192
5157
|
rendererValidateBodyOutcome({ body_index: -1, success: true, cap_urns: [] }, 'test');
|
|
@@ -5196,8 +5161,8 @@ function test0222_Renderer_validateBodyOutcome_rejectsNegativeIndex() {
|
|
|
5196
5161
|
assert(threw, 'negative body_index must throw');
|
|
5197
5162
|
}
|
|
5198
5163
|
|
|
5199
|
-
//
|
|
5200
|
-
function
|
|
5164
|
+
// TEST6504: Renderer build run graph data pages successes and failures
|
|
5165
|
+
function test6504_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
5201
5166
|
// 6 successes, 4 failures. visible=3+2, total=10. Body has 2
|
|
5202
5167
|
// caps (a, b). Each body replica is a chain of:
|
|
5203
5168
|
// entry node + body_step_0 (cap a) + body_step_1 (cap b)
|
|
@@ -5267,8 +5232,8 @@ function test0223_Renderer_buildRunGraphData_pagesSuccessesAndFailures() {
|
|
|
5267
5232
|
assertEqual(failureShowMore.data.hiddenCount, 2, 'failure hidden count = 2');
|
|
5268
5233
|
}
|
|
5269
5234
|
|
|
5270
|
-
//
|
|
5271
|
-
function
|
|
5235
|
+
// TEST6505: Renderer build run graph data failure without failed cap renders full trace
|
|
5236
|
+
function test6505_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace() {
|
|
5272
5237
|
// A failure without failed_cap (infrastructure failure) must
|
|
5273
5238
|
// still render the full body trace — the builder must not
|
|
5274
5239
|
// crash or produce zero replicas.
|
|
@@ -5306,8 +5271,8 @@ function test0224_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullT
|
|
|
5306
5271
|
assertEqual(failureNodes, 2, 'entry + body cap = 2 failure replica nodes');
|
|
5307
5272
|
}
|
|
5308
5273
|
|
|
5309
|
-
//
|
|
5310
|
-
function
|
|
5274
|
+
// TEST6506: Renderer build run graph data uses cap urn is equivalent for failed cap
|
|
5275
|
+
function test6506_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap() {
|
|
5311
5276
|
// The renderer matches failed_cap against step cap URNs via
|
|
5312
5277
|
// CapUrn.isEquivalent, NOT string equality. Feed a payload where
|
|
5313
5278
|
// failed_cap and the step's cap_urn differ only in tag order — they
|
|
@@ -5366,8 +5331,8 @@ function test0225_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap(
|
|
|
5366
5331
|
assertEqual(failureNodes, 3, 'trace truncates at cap y via isEquivalent, yielding entry + 2 cap nodes');
|
|
5367
5332
|
}
|
|
5368
5333
|
|
|
5369
|
-
//
|
|
5370
|
-
function
|
|
5334
|
+
// TEST6507: Renderer build run graph data backbone has no foreach node
|
|
5335
|
+
function test6507_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
5371
5336
|
// Regression test for the run-mode rendering fix: the backbone
|
|
5372
5337
|
// delivered to cytoscape must NOT contain any strand-foreach or
|
|
5373
5338
|
// strand-collect nodes. Run mode inherits the same cosmetic
|
|
@@ -5421,8 +5386,8 @@ function test0226_Renderer_buildRunGraphData_backboneHasNoForeachNode() {
|
|
|
5421
5386
|
assertEqual(built.showMoreNodes.length, 0, 'no show-more nodes when no hidden outcomes');
|
|
5422
5387
|
}
|
|
5423
5388
|
|
|
5424
|
-
//
|
|
5425
|
-
function
|
|
5389
|
+
// TEST6508: Renderer build run graph data all failed drops target placeholder
|
|
5390
|
+
function test6508_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
5426
5391
|
// When every body fails, the strand target node was never
|
|
5427
5392
|
// reached by any execution. The render drops BOTH the backbone
|
|
5428
5393
|
// foreach-entry edge AND the orphaned target node so the user
|
|
@@ -5487,8 +5452,8 @@ function test0227_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder() {
|
|
|
5487
5452
|
assertEqual(hasInputSlot, true, 'input_slot survives as the shared source');
|
|
5488
5453
|
}
|
|
5489
5454
|
|
|
5490
|
-
//
|
|
5491
|
-
function
|
|
5455
|
+
// TEST6509: Renderer build run graph data unclosed foreach success no merge
|
|
5456
|
+
function test6509_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
5492
5457
|
// Strand without a Collect: [Disbind, ForEach, make_decision].
|
|
5493
5458
|
// Under the machfab realize_strand semantics there's no Collect,
|
|
5494
5459
|
// so each body produces its OWN terminal output. Successful
|
|
@@ -5549,8 +5514,8 @@ function test0228_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge() {
|
|
|
5549
5514
|
assertEqual(forkEdges.length, 1, 'fork edge input_slot → body-0-entry exists');
|
|
5550
5515
|
}
|
|
5551
5516
|
|
|
5552
|
-
//
|
|
5553
|
-
function
|
|
5517
|
+
// TEST6510: Renderer build run graph data closed foreach success merges at collect target
|
|
5518
|
+
function test6510_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget() {
|
|
5554
5519
|
// With a Collect closing the body, successful replicas DO merge
|
|
5555
5520
|
// into the post-collect target so the flow converges.
|
|
5556
5521
|
// Strand: [Disbind, ForEach, Cap_a, Cap_b, Collect] with a
|
|
@@ -5605,7 +5570,7 @@ function test0229_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollect
|
|
|
5605
5570
|
|
|
5606
5571
|
// ---------------- editor-graph builder ----------------
|
|
5607
5572
|
|
|
5608
|
-
function
|
|
5573
|
+
function test6511_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
|
|
5609
5574
|
let threw = false;
|
|
5610
5575
|
try {
|
|
5611
5576
|
rendererValidateEditorGraphPayload({
|
|
@@ -5619,8 +5584,8 @@ function test0230_Renderer_validateEditorGraphPayload_rejectsUnknownKind() {
|
|
|
5619
5584
|
assert(threw, 'unknown element kind must throw');
|
|
5620
5585
|
}
|
|
5621
5586
|
|
|
5622
|
-
//
|
|
5623
|
-
function
|
|
5587
|
+
// TEST6512: Renderer build editor graph data collapses caps into labeled edges
|
|
5588
|
+
function test6512_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges() {
|
|
5624
5589
|
// The notation analyzer emits a bipartite chain per cap
|
|
5625
5590
|
// application: data_node → arg_edge → cap_node → arg_edge →
|
|
5626
5591
|
// data_node. The machine builder collapses each cap into a
|
|
@@ -5658,8 +5623,8 @@ function test0231_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges()
|
|
|
5658
5623
|
'edge carries the cap node tokenId so editor cross-highlight points to the cap in source text');
|
|
5659
5624
|
}
|
|
5660
5625
|
|
|
5661
|
-
//
|
|
5662
|
-
function
|
|
5626
|
+
// TEST6513: Renderer build editor graph data loop marked edge gets loop class
|
|
5627
|
+
function test6513_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
|
|
5663
5628
|
// A cap marked `is_loop: true` must produce a `machine-loop`
|
|
5664
5629
|
// edge so the stylesheet's dashed amber rule applies.
|
|
5665
5630
|
const data = {
|
|
@@ -5677,8 +5642,8 @@ function test0232_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass() {
|
|
|
5677
5642
|
'loop-marked cap emits machine-loop class on the collapsed edge');
|
|
5678
5643
|
}
|
|
5679
5644
|
|
|
5680
|
-
//
|
|
5681
|
-
function
|
|
5645
|
+
// TEST6514: Renderer build editor graph data cardinality from data slot sequence flags
|
|
5646
|
+
function test6514_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags() {
|
|
5682
5647
|
// Cardinality markers come from the source and target data
|
|
5683
5648
|
// slots' `is_sequence` flags. A cap whose output data slot has
|
|
5684
5649
|
// `is_sequence=true` shows "(1→n)" on its collapsed edge.
|
|
@@ -5697,8 +5662,8 @@ function test0233_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceF
|
|
|
5697
5662
|
'cardinality marker "(1→n)" derived from output data slot is_sequence=true');
|
|
5698
5663
|
}
|
|
5699
5664
|
|
|
5700
|
-
//
|
|
5701
|
-
function
|
|
5665
|
+
// TEST6515: Renderer build editor graph data cap without complete args is dropped
|
|
5666
|
+
function test6515_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped() {
|
|
5702
5667
|
// A cap with no incoming or no outgoing argument edges (e.g.
|
|
5703
5668
|
// the user is mid-typing) contributes nothing to the render.
|
|
5704
5669
|
// The data slots are still emitted.
|
|
@@ -5715,8 +5680,8 @@ function test0234_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped(
|
|
|
5715
5680
|
'incomplete cap (no outgoing argument) drops out of the render');
|
|
5716
5681
|
}
|
|
5717
5682
|
|
|
5718
|
-
//
|
|
5719
|
-
function
|
|
5683
|
+
// TEST6516: Renderer build editor graph data rejects edge with missing source
|
|
5684
|
+
function test6516_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
|
|
5720
5685
|
let threw = false;
|
|
5721
5686
|
try {
|
|
5722
5687
|
rendererBuildEditorGraphData({
|
|
@@ -5732,7 +5697,7 @@ function test0235_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource() {
|
|
|
5732
5697
|
|
|
5733
5698
|
// ---------------- resolved-machine builder ----------------
|
|
5734
5699
|
|
|
5735
|
-
function
|
|
5700
|
+
function test6517_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain() {
|
|
5736
5701
|
// A single-strand machine: media:ext=pdf → extract → media:ext=txt
|
|
5737
5702
|
// → embed → media:embedding. Two edges, three nodes, no
|
|
5738
5703
|
// loops, no fan-in. Tests the basic shape — nodes and
|
|
@@ -5790,8 +5755,8 @@ function test0236_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain
|
|
|
5790
5755
|
'output anchor node carries strand-target class');
|
|
5791
5756
|
}
|
|
5792
5757
|
|
|
5793
|
-
//
|
|
5794
|
-
function
|
|
5758
|
+
// TEST6518: Renderer build resolved machine graph data loop edge gets loop class
|
|
5759
|
+
function test6518_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass() {
|
|
5795
5760
|
// An is_loop edge corresponds to a strand step inside a
|
|
5796
5761
|
// ForEach body. The renderer must mark it with the
|
|
5797
5762
|
// `machine-loop` class so the dashed amber rule applies.
|
|
@@ -5825,8 +5790,8 @@ function test0237_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass()
|
|
|
5825
5790
|
'is_loop=true must produce a machine-loop class on the cap edge');
|
|
5826
5791
|
}
|
|
5827
5792
|
|
|
5828
|
-
//
|
|
5829
|
-
function
|
|
5793
|
+
// TEST6519: Renderer build resolved machine graph data fan in produces edge per assignment
|
|
5794
|
+
function test6519_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment() {
|
|
5830
5795
|
// A cap with two input args (a fan-in) gets one rendered
|
|
5831
5796
|
// edge per (source_node, target_node) pair so cytoscape can
|
|
5832
5797
|
// draw both incoming wires. Both edges share the cap title
|
|
@@ -5866,8 +5831,8 @@ function test0238_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAss
|
|
|
5866
5831
|
assertEqual(built.edges[1].data.target, 'n2', 'second edge targets n2');
|
|
5867
5832
|
}
|
|
5868
5833
|
|
|
5869
|
-
//
|
|
5870
|
-
function
|
|
5834
|
+
// TEST6520: Renderer build resolved machine graph data multi strand keeps strands disjoint
|
|
5835
|
+
function test6520_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint() {
|
|
5871
5836
|
// Two strands inside one machine. Each strand has its own
|
|
5872
5837
|
// nodes and edges. Node ids are globally unique across
|
|
5873
5838
|
// strands (Rust assigns them via a single counter), so no
|
|
@@ -5929,8 +5894,8 @@ function test0239_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrands
|
|
|
5929
5894
|
assertEqual(idToStrand['n3'], 1, 'n3 belongs to strand 1');
|
|
5930
5895
|
}
|
|
5931
5896
|
|
|
5932
|
-
//
|
|
5933
|
-
function
|
|
5897
|
+
// TEST6521: Renderer build resolved machine graph data duplicate node id across strands fails hard
|
|
5898
|
+
function test6521_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard() {
|
|
5934
5899
|
// Node ids must be globally unique across strands. The
|
|
5935
5900
|
// Rust serializer guarantees this via a single global
|
|
5936
5901
|
// counter. If the host ever feeds a payload that violates
|
|
@@ -5965,8 +5930,8 @@ function test0240_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossSt
|
|
|
5965
5930
|
'error must name the colliding node id');
|
|
5966
5931
|
}
|
|
5967
5932
|
|
|
5968
|
-
//
|
|
5969
|
-
function
|
|
5933
|
+
// TEST6522: Renderer validate resolved machine payload rejects missing fields
|
|
5934
|
+
function test6522_Renderer_validateResolvedMachinePayload_rejectsMissingFields() {
|
|
5970
5935
|
// The validator must reject any payload missing a required
|
|
5971
5936
|
// field on a strand, edge, node, or assignment binding.
|
|
5972
5937
|
// We exercise the most-likely-to-be-missed field on each
|
|
@@ -6007,7 +5972,7 @@ function test0241_Renderer_validateResolvedMachinePayload_rejectsMissingFields()
|
|
|
6007
5972
|
// surface, not a per-port detail.
|
|
6008
5973
|
// ============================================================================
|
|
6009
5974
|
|
|
6010
|
-
// TEST1800: Identity classifier — only explicit effect=none qualifies.
|
|
5975
|
+
// TEST1800: Identity classifier — and only explicit effect=none qualifies.
|
|
6011
5976
|
function test1800_kindIdentityOnlyForBareCap() {
|
|
6012
5977
|
const identity = CapUrn.fromString('cap:effect=none');
|
|
6013
5978
|
assertEqual(identity.kind(), CapKind.IDENTITY, 'cap:effect=none should be Identity');
|
|
@@ -6062,8 +6027,7 @@ function test1803_kindEffectWhenBothSidesVoid() {
|
|
|
6062
6027
|
'in=void;out=void with empty y is still an Effect');
|
|
6063
6028
|
}
|
|
6064
6029
|
|
|
6065
|
-
// TEST1804: Transform classifier — at least one side non-void, and
|
|
6066
|
-
// the cap is not the bare identity.
|
|
6030
|
+
// TEST1804: Transform classifier — at least one side non-void, and the cap is not the bare identity. The default kind for ordinary data-processing caps.
|
|
6067
6031
|
function test1804_kindTransformForNormalDataProcessors() {
|
|
6068
6032
|
const extract = CapUrn.fromString('cap:extract;in="media:ext=pdf";out="media:enc=utf-8;record"');
|
|
6069
6033
|
assertEqual(extract.kind(), CapKind.TRANSFORM, 'extract is a Transform');
|
|
@@ -6073,12 +6037,7 @@ function test1804_kindTransformForNormalDataProcessors() {
|
|
|
6073
6037
|
'fully generic in/out with a tag is a Transform, not Identity');
|
|
6074
6038
|
}
|
|
6075
6039
|
|
|
6076
|
-
// TEST1810: media:void is atomic — refinements are parse errors.
|
|
6077
|
-
//
|
|
6078
|
-
// Mirrored across every language port (Rust, Go, Python, Swift/ObjC,
|
|
6079
|
-
// JS) under the SAME number. Any divergence is a wire-level
|
|
6080
|
-
// inconsistency — the unit type's atomicity is part of the protocol's
|
|
6081
|
-
// deepest layer, not a per-port detail.
|
|
6040
|
+
// TEST1810: media:void is atomic — refinements are parse errors. Mirrored across every language port (Rust, Go, Python, Swift/ObjC, JS) under the SAME number. Any divergence is a wire-level inconsistency — the unit type's atomicity is part of the protocol's deepest layer, not a per-port detail. The bare `media:void` parses successfully; any combination with another tag (marker or key=value) MUST fail with VoidNotAtomic. This forecloses a fake taxonomy of unit values; reasons or labels for *why* void is used belong on the cap URN's non-directional tags or in cap args.
|
|
6082
6041
|
function test1810_mediaVoidIsAtomic() {
|
|
6083
6042
|
// Bare void: must parse successfully.
|
|
6084
6043
|
const bare = MediaUrn.fromString('media:void');
|
|
@@ -6111,9 +6070,7 @@ function test1810_mediaVoidIsAtomic() {
|
|
|
6111
6070
|
}
|
|
6112
6071
|
}
|
|
6113
6072
|
|
|
6114
|
-
// TEST1805: Kind is invariant under canonicalization. The same
|
|
6115
|
-
// morphism written in many surface forms must classify the same way
|
|
6116
|
-
// once parsed.
|
|
6073
|
+
// TEST1805: Kind is invariant under canonicalization. The same morphism written in many surface forms must classify the same way once parsed. This pins the rule that kind is a property of the cap as a structured object, not of any particular spelling.
|
|
6117
6074
|
function test1805_kindInvariantUnderCanonicalSpellings() {
|
|
6118
6075
|
const cases = [
|
|
6119
6076
|
{ a: 'cap:effect=none', b: 'cap:in=media:;out=media:;effect=none', expected: CapKind.IDENTITY },
|
|
@@ -6192,8 +6149,7 @@ function test1823_specificityExactValueIsFour() {
|
|
|
6192
6149
|
'target=metadata (exact value) must score 4');
|
|
6193
6150
|
}
|
|
6194
6151
|
|
|
6195
|
-
// TEST1824: All six forms compose additively on a single cap.
|
|
6196
|
-
// y combining 0+1+2+3+4+5 must sum to 15.
|
|
6152
|
+
// TEST1824: All six forms compose additively on a single cap. This pins the truth-table sum across the y axis as a whole.
|
|
6197
6153
|
function test1824_specificityCombinedYAxis() {
|
|
6198
6154
|
const cap = CapUrn.fromString('cap:!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft');
|
|
6199
6155
|
assertEqual(cap.specificity(), 15,
|
|
@@ -6214,10 +6170,7 @@ function test1830_canonicalizeNoConstraint() {
|
|
|
6214
6170
|
}
|
|
6215
6171
|
}
|
|
6216
6172
|
|
|
6217
|
-
// TEST1831: ?x=v and x?=v both canonicalize to x?=v. The third
|
|
6218
|
-
// hypothetical form `x=?v` is NOT recognized as a qualifier — a
|
|
6219
|
-
// value starting with `?` is just an exact value beginning with
|
|
6220
|
-
// a `?` character.
|
|
6173
|
+
// TEST1831: ?x=v and x?=v both canonicalize to x?=v. The third hypothetical form `x=?v` is NOT recognized as a qualifier — a value starting with `?` is just an exact value beginning with a `?` character.
|
|
6221
6174
|
function test1831_canonicalizeAbsentOrNotValue() {
|
|
6222
6175
|
const canonical = 'cap:x?=foo';
|
|
6223
6176
|
for (const input of ['cap:?x=foo', 'cap:x?=foo']) {
|
|
@@ -6243,10 +6196,7 @@ function test1832_canonicalizeMustHaveAny() {
|
|
|
6243
6196
|
}
|
|
6244
6197
|
}
|
|
6245
6198
|
|
|
6246
|
-
// TEST1833: !x=v and x!=v both canonicalize to x!=v. The third
|
|
6247
|
-
// hypothetical form `x=!v` is NOT recognized as a qualifier — a
|
|
6248
|
-
// value starting with `!` is just an exact value beginning with
|
|
6249
|
-
// a `!` character.
|
|
6199
|
+
// TEST1833: !x=v and x!=v both canonicalize to x!=v. The third hypothetical form `x=!v` is NOT recognized as a qualifier — a value starting with `!` is just an exact value beginning with a `!` character.
|
|
6250
6200
|
function test1833_canonicalizePresentNotValue() {
|
|
6251
6201
|
const canonical = 'cap:x!=foo';
|
|
6252
6202
|
for (const input of ['cap:!x=foo', 'cap:x!=foo']) {
|
|
@@ -6262,7 +6212,7 @@ function test1833_canonicalizePresentNotValue() {
|
|
|
6262
6212
|
assertEqual(exact.getTag('x'), '!foo');
|
|
6263
6213
|
}
|
|
6264
6214
|
|
|
6265
|
-
// TEST1834: x=v stays as x=v.
|
|
6215
|
+
// TEST1834: x=v stays as x=v (the lone exact-value form).
|
|
6266
6216
|
function test1834_canonicalizeExactValue() {
|
|
6267
6217
|
const cap = CapUrn.fromString('cap:x=foo');
|
|
6268
6218
|
assertEqual(cap.toString(), 'cap:x=foo');
|
|
@@ -6306,8 +6256,8 @@ function test1842_truthTableFullCrossProduct() {
|
|
|
6306
6256
|
}
|
|
6307
6257
|
}
|
|
6308
6258
|
|
|
6309
|
-
//
|
|
6310
|
-
function
|
|
6259
|
+
// TEST6734: Invalid qualifier combinations must be rejected.
|
|
6260
|
+
function test6734_rejectInvalidCombinations() {
|
|
6311
6261
|
const invalid = [
|
|
6312
6262
|
'cap:?x?=v', 'cap:!x!=v', 'cap:?!x', 'cap:!?x',
|
|
6313
6263
|
'cap:?x=*', 'cap:!x=*',
|
|
@@ -6321,8 +6271,8 @@ function test1843_rejectInvalidCombinations() {
|
|
|
6321
6271
|
}
|
|
6322
6272
|
}
|
|
6323
6273
|
|
|
6324
|
-
//
|
|
6325
|
-
function
|
|
6274
|
+
// TEST6735: out-axis difference dominates combined in+y differences.
|
|
6275
|
+
function test6735_axisWeightingOutDominates() {
|
|
6326
6276
|
const bigOut = CapUrn.fromString('cap:in=media:;out="media:enc=utf-8;record"');
|
|
6327
6277
|
const bigInAndY = CapUrn.fromString(
|
|
6328
6278
|
'cap:in="media:ext=pdf";out=media:record;!constrained;?target;extract;stage!=alpha;target2=metadata;ver?=draft'
|
|
@@ -6331,7 +6281,7 @@ function test1844_axisWeightingOutDominates() {
|
|
|
6331
6281
|
'out-axis difference must dominate combined in+y differences');
|
|
6332
6282
|
}
|
|
6333
6283
|
|
|
6334
|
-
// TEST1845: With equal out, in-axis dominates over y-axis.
|
|
6284
|
+
// TEST1845: With equal out-axis, in-axis dominates over y-axis.
|
|
6335
6285
|
function test1845_axisWeightingInDominatesY() {
|
|
6336
6286
|
const bigIn = CapUrn.fromString('cap:in="media:ext=pdf";out=media:record');
|
|
6337
6287
|
const bigY = CapUrn.fromString(
|
|
@@ -6341,8 +6291,8 @@ function test1845_axisWeightingInDominatesY() {
|
|
|
6341
6291
|
'in-axis difference must dominate y-axis');
|
|
6342
6292
|
}
|
|
6343
6293
|
|
|
6344
|
-
//
|
|
6345
|
-
function
|
|
6294
|
+
// TEST6736: Decoded layout — 10000*out + 100*in + y.
|
|
6295
|
+
function test6736_axisWeightingDecodedLayout() {
|
|
6346
6296
|
const cap = CapUrn.fromString('cap:in="media:a;b";out="media:a;b;c;d";extract');
|
|
6347
6297
|
// out=4 markers (8), in=2 markers (4), y=1 marker (2)
|
|
6348
6298
|
// 10000*8 + 100*4 + 2 = 80402
|
|
@@ -6353,8 +6303,8 @@ function test1846_axisWeightingDecodedLayout() {
|
|
|
6353
6303
|
// Cap.version round-trip tests: TEST1847-TEST1848
|
|
6354
6304
|
// ============================================================================
|
|
6355
6305
|
|
|
6356
|
-
//
|
|
6357
|
-
function
|
|
6306
|
+
// TEST6737: Cap with version=0 round-trips with no `version` key on wire
|
|
6307
|
+
function test6737_capVersionZeroOmittedOnWire() {
|
|
6358
6308
|
const urn = CapUrn.fromString('cap:in="media:void";test-op;out="media:enc=utf-8;record"');
|
|
6359
6309
|
const cap = new Cap(urn, 'Test Cap', 'test-op');
|
|
6360
6310
|
// version defaults to 0
|
|
@@ -6377,6 +6327,62 @@ function test1848_capVersionNonZeroOnWire() {
|
|
|
6377
6327
|
assertEqual(restored.version, 42, 'Restored version must equal 42');
|
|
6378
6328
|
}
|
|
6379
6329
|
|
|
6330
|
+
// ===========================================================================
|
|
6331
|
+
// Fabric alias tests (shared numbers 1880-1882, 1887)
|
|
6332
|
+
//
|
|
6333
|
+
// Shared test numbers test the same behavior, with the same method, across
|
|
6334
|
+
// every capdag implementation. This lightweight JS mirror provides the alias
|
|
6335
|
+
// primitives + Manifest (de)serialization; the registry-resolution tests
|
|
6336
|
+
// (1888-1892) and notation-parser tests (1883-1886) belong to the mirrors
|
|
6337
|
+
// that implement the full registry/resolver pipeline.
|
|
6338
|
+
// ===========================================================================
|
|
6339
|
+
|
|
6340
|
+
// TEST1880: alias name normalization lowercases and accepts the allowed character class; rejects colon, whitespace, and out-of-class chars with the right error. A broken validator would let a URN-shaped or whitespace name through, or mangle a valid name.
|
|
6341
|
+
function test1880_aliasNameNormalizationRules() {
|
|
6342
|
+
assertEqual(normalizeAliasName('JSONDoc'), 'jsondoc', 'lowercases');
|
|
6343
|
+
assertEqual(normalizeAliasName('pdf2text'), 'pdf2text', 'plain name');
|
|
6344
|
+
assertEqual(normalizeAliasName('my.alias-1_x'), 'my.alias-1_x', 'allowed punctuation');
|
|
6345
|
+
for (const bad of ['', 'pdf:text', 'my alias', 'a/b']) {
|
|
6346
|
+
let threw = false;
|
|
6347
|
+
try { normalizeAliasName(bad); } catch (_) { threw = true; }
|
|
6348
|
+
assert(threw, `normalizeAliasName must reject ${JSON.stringify(bad)}`);
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
|
|
6352
|
+
// TEST1881: URN-vs-alias detection keys purely on the presence of ':'. The whole design rests on this discriminator being exact.
|
|
6353
|
+
function test1881_tokenUrnVsAliasDetection() {
|
|
6354
|
+
assert(tokenIsUrn('cap:in="media:ext=pdf";extract;out="media:enc=utf-8"'), 'cap URN is a URN');
|
|
6355
|
+
assert(tokenIsUrn('media:fmt=json;record'), 'media URN is a URN');
|
|
6356
|
+
assert(!tokenIsUrn('pdf2text'), 'bare name is not a URN');
|
|
6357
|
+
assert(isAliasToken('pdf2text'), 'bare name is an alias token');
|
|
6358
|
+
assert(!isAliasToken('media:enc=utf-8'), 'media URN is not an alias token');
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
// TEST1882: alias target classification distinguishes cap from media by prefix and rejects a non-URN target. The typed-boundary enforcement in the registry depends on this.
|
|
6362
|
+
function test1882_classifyAliasTargetByPrefix() {
|
|
6363
|
+
assertEqual(classifyAliasTarget('media:fmt=json;record'), ALIAS_TARGET_MEDIA, 'media target');
|
|
6364
|
+
assertEqual(
|
|
6365
|
+
classifyAliasTarget('cap:effect=patch;in="media:image";name;out="media:ext=png;image"'),
|
|
6366
|
+
ALIAS_TARGET_CAP, 'cap target');
|
|
6367
|
+
assertEqual(classifyAliasTarget('not-a-urn'), null, 'non-URN target is null');
|
|
6368
|
+
}
|
|
6369
|
+
|
|
6370
|
+
// TEST1887: the Manifest type round-trips an `aliases` map.
|
|
6371
|
+
function test1887_manifestSerdeRoundTripsAliases() {
|
|
6372
|
+
const body = '{"version":1,"previous":0,"caps":{},"media":{},"aliases":{"pdf2text":3,"jsondoc":1}}';
|
|
6373
|
+
const m = Manifest.fromJSON(JSON.parse(body));
|
|
6374
|
+
assertEqual(m.aliases['pdf2text'], 3, 'pdf2text defver');
|
|
6375
|
+
assertEqual(m.aliases['jsondoc'], 1, 'jsondoc defver');
|
|
6376
|
+
const back = m.toJSON();
|
|
6377
|
+
assertEqual(back.aliases['pdf2text'], 3, 'round-trip pdf2text');
|
|
6378
|
+
assertEqual(back.aliases['jsondoc'], 1, 'round-trip jsondoc');
|
|
6379
|
+
|
|
6380
|
+
// A StoredAlias round-trips its wire shape.
|
|
6381
|
+
const a = StoredAlias.fromJSON({ name: 'pdf2text', target: 'cap:effect=none', version: 3 });
|
|
6382
|
+
assertEqual(a.target, 'cap:effect=none', 'alias target');
|
|
6383
|
+
assertEqual(JSON.stringify(a.toJSON()), '{"name":"pdf2text","target":"cap:effect=none","version":3}', 'alias wire shape');
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6380
6386
|
// ============================================================================
|
|
6381
6387
|
// Test runner
|
|
6382
6388
|
// ============================================================================
|
|
@@ -6443,9 +6449,9 @@ async function runTests() {
|
|
|
6443
6449
|
// validation.rs: TEST053-TEST056
|
|
6444
6450
|
console.log('\n--- validation.rs ---');
|
|
6445
6451
|
console.log(' SKIP TEST053: N/A for JS (Rust-only validation infrastructure)');
|
|
6446
|
-
runTest('TEST054: xv5_inline_spec_redefinition_detected',
|
|
6447
|
-
runTest('TEST055: xv5_new_inline_spec_allowed',
|
|
6448
|
-
runTest('TEST056: xv5_empty_media_defs_allowed',
|
|
6452
|
+
runTest('TEST054: xv5_inline_spec_redefinition_detected', test6212_xv5InlineSpecRedefinitionDetected);
|
|
6453
|
+
runTest('TEST055: xv5_new_inline_spec_allowed', test6216_xv5NewInlineSpecAllowed);
|
|
6454
|
+
runTest('TEST056: xv5_empty_media_defs_allowed', test6220_xv5EmptyMediaDefsAllowed);
|
|
6449
6455
|
|
|
6450
6456
|
// media_urn.rs: TEST060-TEST078
|
|
6451
6457
|
console.log('\n--- media_urn.rs ---');
|
|
@@ -6471,12 +6477,12 @@ async function runTests() {
|
|
|
6471
6477
|
// media_def.rs: TEST088-TEST110
|
|
6472
6478
|
console.log('\n--- media_def.rs ---');
|
|
6473
6479
|
console.log(' SKIP TEST088-090: N/A for JS (async registry, Rust-only)');
|
|
6474
|
-
runTest('
|
|
6475
|
-
runTest('
|
|
6476
|
-
runTest('TEST093: resolve_unresolvable_fails_hard',
|
|
6480
|
+
runTest('TEST6282: resolve_custom_media_def', test6282_resolveCustomMediaDef);
|
|
6481
|
+
runTest('TEST6283: resolve_custom_with_schema', test6283_resolveCustomWithSchema);
|
|
6482
|
+
runTest('TEST093: resolve_unresolvable_fails_hard', test93_resolveUnresolvableFailsHard);
|
|
6477
6483
|
console.log(' SKIP TEST094: N/A for JS (no registry concept)');
|
|
6478
6484
|
console.log(' SKIP TEST095-098: N/A for JS (Rust serde/validation)');
|
|
6479
|
-
runTest('TEST099: resolved_is_binary',
|
|
6485
|
+
runTest('TEST099: resolved_is_binary', test99_resolvedIsBinary);
|
|
6480
6486
|
runTest('TEST100: resolved_is_record', test100_resolvedIsRecord);
|
|
6481
6487
|
runTest('TEST101: resolved_is_scalar', test101_resolvedIsScalar);
|
|
6482
6488
|
runTest('TEST102: resolved_is_list', test102_resolvedIsList);
|
|
@@ -6497,9 +6503,9 @@ async function runTests() {
|
|
|
6497
6503
|
// /api/capabilities). These tests guard the minimal API the renderer relies
|
|
6498
6504
|
// on: new CapFab(), addCap(cap, registryName), getEdges(), getOutgoing().
|
|
6499
6505
|
console.log('\n--- cap_fab (browse-mode API used by cap-fab-renderer) ---');
|
|
6500
|
-
runTest('cap_fab: add_cap_populates_edges_and_nodes',
|
|
6501
|
-
runTest('cap_fab: get_outgoing_conforms_to_matching',
|
|
6502
|
-
runTest('cap_fab: distinct_registry_names_recorded_per_edge',
|
|
6506
|
+
runTest('cap_fab: add_cap_populates_edges_and_nodes', test6206_CapFabAddCapPopulatesEdgesAndNodes);
|
|
6507
|
+
runTest('cap_fab: get_outgoing_conforms_to_matching', test6208_CapFabGetOutgoingConformsToMatching);
|
|
6508
|
+
runTest('cap_fab: distinct_registry_names_recorded_per_edge', test6224_CapFabDistinctRegistryNames);
|
|
6503
6509
|
|
|
6504
6510
|
// caller.rs: TEST156-TEST159
|
|
6505
6511
|
console.log('\n--- caller.rs (StdinSource) ---');
|
|
@@ -6528,22 +6534,22 @@ async function runTests() {
|
|
|
6528
6534
|
runTest('TEST308: model_path_urn', test308_modelPathUrn);
|
|
6529
6535
|
runTest('TEST309: model_availability_and_path_are_distinct', test309_modelAvailabilityAndPathAreDistinct);
|
|
6530
6536
|
runTest('TEST310: llm_generate_text_urn', test310_llmGenerateTextUrn);
|
|
6531
|
-
runTest('llm_generate_text_urn_specs',
|
|
6537
|
+
runTest('llm_generate_text_urn_specs', test6228_LlmGenerateTextUrnSpecs);
|
|
6532
6538
|
runTest('TEST312: all_urn_builders_produce_valid_urns', test312_allUrnBuildersProduceValidUrns);
|
|
6533
6539
|
|
|
6534
6540
|
// JS-specific tests (no Rust number)
|
|
6535
6541
|
console.log('\n--- JS-specific ---');
|
|
6536
|
-
runTest('JS: build_extension_index',
|
|
6537
|
-
runTest('JS: media_urns_for_extension',
|
|
6538
|
-
runTest('JS: get_extension_mappings',
|
|
6539
|
-
runTest('JS: resolve_media_urn_from_specs',
|
|
6540
|
-
runTest('JS: cap_json_serialization',
|
|
6541
|
-
runTest('JS: cap_documentation_round_trip',
|
|
6542
|
-
runTest('JS: cap_documentation_omitted_when_null',
|
|
6543
|
-
runTest('JS: media_def_documentation_propagates_through_resolve',
|
|
6544
|
-
runTest('JS: stdin_source_kind_constants',
|
|
6545
|
-
runTest('JS: stdin_source_null_data',
|
|
6546
|
-
runTest('JS: media_def_construction',
|
|
6542
|
+
runTest('JS: build_extension_index', test6232_JS_buildExtensionIndex);
|
|
6543
|
+
runTest('JS: media_urns_for_extension', test6236_JS_mediaUrnsForExtension);
|
|
6544
|
+
runTest('JS: get_extension_mappings', test6240_JS_getExtensionMappings);
|
|
6545
|
+
runTest('JS: resolve_media_urn_from_specs', test6242_JS_resolveMediaUrnFromSpecs);
|
|
6546
|
+
runTest('JS: cap_json_serialization', test6246_JS_capJSONSerialization);
|
|
6547
|
+
runTest('JS: cap_documentation_round_trip', test6249_JS_capDocumentationRoundTrip);
|
|
6548
|
+
runTest('JS: cap_documentation_omitted_when_null', test6253_JS_capDocumentationOmittedWhenNull);
|
|
6549
|
+
runTest('JS: media_def_documentation_propagates_through_resolve', test6257_JS_mediaDefDocumentationPropagatesThroughResolve);
|
|
6550
|
+
runTest('JS: stdin_source_kind_constants', test6261_JS_stdinSourceKindConstants);
|
|
6551
|
+
runTest('JS: stdin_source_null_data', test6265_JS_stdinSourceNullData);
|
|
6552
|
+
runTest('JS: media_def_construction', test6269_JS_mediaDefConstruction);
|
|
6547
6553
|
|
|
6548
6554
|
// cartridge_repo: CartridgeRepoServer and CartridgeRepoClient tests
|
|
6549
6555
|
console.log('\n--- cartridge_repo ---');
|
|
@@ -6587,22 +6593,22 @@ async function runTests() {
|
|
|
6587
6593
|
|
|
6588
6594
|
// media_urn.rs: TEST1312-TEST1315, TEST1298-TEST1302 (MediaUrn predicates)
|
|
6589
6595
|
console.log('\n--- media_urn.rs (predicates) ---');
|
|
6590
|
-
runTest('TEST1312: is_image',
|
|
6591
|
-
runTest('TEST1313: is_audio',
|
|
6592
|
-
runTest('TEST1314: is_video',
|
|
6593
|
-
runTest('TEST1315: is_numeric',
|
|
6594
|
-
runTest('TEST1298: is_bool',
|
|
6595
|
-
runTest('TEST1299: is_file_path',
|
|
6596
|
-
runTest('TEST1302: predicate_constant_consistency',
|
|
6596
|
+
runTest('TEST1312: is_image', test546_isImage);
|
|
6597
|
+
runTest('TEST1313: is_audio', test547_isAudio);
|
|
6598
|
+
runTest('TEST1314: is_video', test548_isVideo);
|
|
6599
|
+
runTest('TEST1315: is_numeric', test549_isNumeric);
|
|
6600
|
+
runTest('TEST1298: is_bool', test550_isBool);
|
|
6601
|
+
runTest('TEST1299: is_file_path', test551_isFilePath);
|
|
6602
|
+
runTest('TEST1302: predicate_constant_consistency', test558_predicateConstantConsistency);
|
|
6597
6603
|
|
|
6598
6604
|
// cap_urn.rs: TEST1303-TEST1307 (CapUrn tier tests)
|
|
6599
6605
|
console.log('\n--- cap_urn.rs (tier tests) ---');
|
|
6600
|
-
runTest('TEST1303: without_tag',
|
|
6601
|
-
runTest('TEST1304: with_in_out_spec',
|
|
6602
|
-
runTest('TEST1305: find_all_matches',
|
|
6603
|
-
runTest('TEST1306: are_compatible',
|
|
6604
|
-
runTest('TEST1307: with_tag_rejects_structural_keys',
|
|
6605
|
-
runTest('TEST1308: builder_rejects_structural_keys',
|
|
6606
|
+
runTest('TEST1303: without_tag', test559_withoutTag);
|
|
6607
|
+
runTest('TEST1304: with_in_out_spec', test560_withInOutSpec);
|
|
6608
|
+
runTest('TEST1305: find_all_matches', test563_findAllMatches);
|
|
6609
|
+
runTest('TEST1306: are_compatible', test564_areCompatible);
|
|
6610
|
+
runTest('TEST1307: with_tag_rejects_structural_keys', test566_withTagRejectsStructuralKeys);
|
|
6611
|
+
runTest('TEST1308: builder_rejects_structural_keys', test6544_builderRejectsStructuralKeys);
|
|
6606
6612
|
runTest('TEST1294: rule11_void_input_with_stdin_rejected', test1294_rule11VoidInputWithStdinRejected);
|
|
6607
6613
|
runTest('TEST1295: rule11_non_void_input_without_stdin_rejected', test1295_rule11NonVoidInputWithoutStdinRejected);
|
|
6608
6614
|
runTest('TEST1296: rule11_void_input_cli_flag_only', test1296_rule11VoidInputCliFlagOnly);
|
|
@@ -6610,7 +6616,7 @@ async function runTests() {
|
|
|
6610
6616
|
|
|
6611
6617
|
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
6612
6618
|
console.log('\n--- cap_urn.rs (wildcard tests) ---');
|
|
6613
|
-
runTest('TEST639: empty_cap_is_illegal',
|
|
6619
|
+
runTest('TEST639: empty_cap_is_illegal', test6201_emptyCapIsIllegal);
|
|
6614
6620
|
runTest('TEST640: in_only_is_illegal', test640_inOnlyIsIllegal);
|
|
6615
6621
|
runTest('TEST641: out_only_is_illegal', test641_outOnlyIsIllegal);
|
|
6616
6622
|
runTest('TEST642: in_out_without_values_are_illegal', test642_inOutWithoutValuesAreIllegal);
|
|
@@ -6622,184 +6628,184 @@ async function runTests() {
|
|
|
6622
6628
|
runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
|
|
6623
6629
|
runTest('TEST649: specificity_scoring', test649_specificityScoring);
|
|
6624
6630
|
runTest('TEST650: wildcard_preserve_other_tags', test650_wildcardPreserveOtherTags);
|
|
6625
|
-
runTest('TEST651: wildcard_generic_forms_rejected',
|
|
6626
|
-
runTest('TEST652: cap_identity_constant_works',
|
|
6631
|
+
runTest('TEST651: wildcard_generic_forms_rejected', test6620_wildcardGenericFormsRejected);
|
|
6632
|
+
runTest('TEST652: cap_identity_constant_works', test6621_capIdentityConstantWorks);
|
|
6627
6633
|
runTest('TEST653: invalid_effect_none_declaration_rejected', test653_invalidEffectNoneDeclarationRejected);
|
|
6628
|
-
runTest('TEST654: effect_none_preserves_runtime_media',
|
|
6629
|
-
runTest('TEST655: effect_declared_uses_declared_output',
|
|
6630
|
-
runTest('TEST656: invalid_effect_none_fails_hard',
|
|
6631
|
-
runTest('TEST657: effect_dispatch_requires_explicit_wildcard',
|
|
6634
|
+
runTest('TEST654: effect_none_preserves_runtime_media', test125_effectNonePreservesRuntimeMedia);
|
|
6635
|
+
runTest('TEST655: effect_declared_uses_declared_output', test126_effectDeclaredUsesDeclaredOutput);
|
|
6636
|
+
runTest('TEST656: invalid_effect_none_fails_hard', test127_invalidEffectNoneFailsHard);
|
|
6637
|
+
runTest('TEST657: effect_dispatch_requires_explicit_wildcard', test128_effectDispatchRequiresExplicitWildcard);
|
|
6632
6638
|
|
|
6633
6639
|
// machine module: parser tests (mirrors parser.rs)
|
|
6634
6640
|
console.log('\n--- machine/parser.rs ---');
|
|
6635
|
-
runTest('MACHINE:empty_input',
|
|
6636
|
-
runTest('MACHINE:whitespace_only',
|
|
6637
|
-
runTest('MACHINE:header_only_no_wirings',
|
|
6638
|
-
runTest('MACHINE:duplicate_alias',
|
|
6639
|
-
runTest('MACHINE:simple_linear_chain',
|
|
6640
|
-
runTest('MACHINE:two_step_chain',
|
|
6641
|
-
runTest('MACHINE:fan_out',
|
|
6642
|
-
runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring',
|
|
6643
|
-
runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard',
|
|
6644
|
-
runTest('MACHINE:loop_edge',
|
|
6645
|
-
runTest('MACHINE:undefined_alias_fails',
|
|
6646
|
-
runTest('MACHINE:node_alias_collision',
|
|
6647
|
-
runTest('MACHINE:conflicting_media_types_fail',
|
|
6648
|
-
runTest('MACHINE:multiline_format',
|
|
6649
|
-
runTest('MACHINE:different_aliases_same_graph',
|
|
6650
|
-
runTest('MACHINE:malformed_input_fails',
|
|
6651
|
-
runTest('MACHINE:unterminated_bracket_fails',
|
|
6641
|
+
runTest('MACHINE:empty_input', test6275_Machine_emptyInput);
|
|
6642
|
+
runTest('MACHINE:whitespace_only', test6277_Machine_whitespaceOnly);
|
|
6643
|
+
runTest('MACHINE:header_only_no_wirings', test6279_Machine_headerOnlyNoWirings);
|
|
6644
|
+
runTest('MACHINE:duplicate_alias', test6280_Machine_duplicateAlias);
|
|
6645
|
+
runTest('MACHINE:simple_linear_chain', test6286_Machine_simpleLinearChain);
|
|
6646
|
+
runTest('MACHINE:two_step_chain', test6288_Machine_twoStepChain);
|
|
6647
|
+
runTest('MACHINE:fan_out', test6290_Machine_fanOut);
|
|
6648
|
+
runTest('MACHINE:fan_in_secondary_assigned_by_prior_wiring', test6292_Machine_fanInSecondaryAssignedByPriorWiring);
|
|
6649
|
+
runTest('MACHINE:fan_in_secondary_unassigned_gets_wildcard', test6294_Machine_fanInSecondaryUnassignedGetsWildcard);
|
|
6650
|
+
runTest('MACHINE:loop_edge', test6306_Machine_loopEdge);
|
|
6651
|
+
runTest('MACHINE:undefined_alias_fails', test6308_Machine_undefinedAliasFails);
|
|
6652
|
+
runTest('MACHINE:node_alias_collision', test6310_Machine_nodeAliasCollision);
|
|
6653
|
+
runTest('MACHINE:conflicting_media_types_fail', test6312_Machine_conflictingMediaTypesFail);
|
|
6654
|
+
runTest('MACHINE:multiline_format', test6315_Machine_multilineFormat);
|
|
6655
|
+
runTest('MACHINE:different_aliases_same_graph', test6318_Machine_differentAliasesSameGraph);
|
|
6656
|
+
runTest('MACHINE:malformed_input_fails', test6321_Machine_malformedInputFails);
|
|
6657
|
+
runTest('MACHINE:unterminated_bracket_fails', test6323_Machine_unterminatedBracketFails);
|
|
6652
6658
|
|
|
6653
6659
|
// machine module: line-based mode tests
|
|
6654
6660
|
console.log('\n--- machine/parser.rs (line-based) ---');
|
|
6655
|
-
runTest('MACHINE:line_based_simple_chain',
|
|
6656
|
-
runTest('MACHINE:line_based_two_step_chain',
|
|
6657
|
-
runTest('MACHINE:line_based_loop',
|
|
6658
|
-
runTest('MACHINE:line_based_fan_in',
|
|
6659
|
-
runTest('MACHINE:mixed_bracketed_and_line_based',
|
|
6660
|
-
runTest('MACHINE:line_based_equivalent_to_bracketed',
|
|
6661
|
-
runTest('MACHINE:line_based_format_serialization',
|
|
6662
|
-
runTest('MACHINE:line_based_and_bracketed_parse_same_graph',
|
|
6661
|
+
runTest('MACHINE:line_based_simple_chain', test6327_Machine_lineBasedSimpleChain);
|
|
6662
|
+
runTest('MACHINE:line_based_two_step_chain', test6331_Machine_lineBasedTwoStepChain);
|
|
6663
|
+
runTest('MACHINE:line_based_loop', test6334_Machine_lineBasedLoop);
|
|
6664
|
+
runTest('MACHINE:line_based_fan_in', test6337_Machine_lineBasedFanIn);
|
|
6665
|
+
runTest('MACHINE:mixed_bracketed_and_line_based', test6341_Machine_mixedBracketedAndLineBased);
|
|
6666
|
+
runTest('MACHINE:line_based_equivalent_to_bracketed', test6345_Machine_lineBasedEquivalentToBracketed);
|
|
6667
|
+
runTest('MACHINE:line_based_format_serialization', test6349_Machine_lineBasedFormatSerialization);
|
|
6668
|
+
runTest('MACHINE:line_based_and_bracketed_parse_same_graph', test6353_Machine_lineBasedAndBracketedParseSameGraph);
|
|
6663
6669
|
|
|
6664
6670
|
// machine module: graph tests (mirrors graph.rs)
|
|
6665
6671
|
console.log('\n--- machine/graph.rs ---');
|
|
6666
|
-
runTest('MACHINE:edge_equivalence_same_urns',
|
|
6667
|
-
runTest('MACHINE:edge_equivalence_different_cap_urns',
|
|
6668
|
-
runTest('MACHINE:edge_equivalence_different_targets',
|
|
6669
|
-
runTest('MACHINE:edge_equivalence_different_loop_flag',
|
|
6670
|
-
runTest('MACHINE:edge_equivalence_source_order_independent',
|
|
6671
|
-
runTest('MACHINE:edge_equivalence_different_source_count',
|
|
6672
|
-
runTest('MACHINE:graph_equivalence_same_edges',
|
|
6673
|
-
runTest('MACHINE:graph_equivalence_reordered_edges',
|
|
6674
|
-
runTest('MACHINE:graph_not_equivalent_different_edge_count',
|
|
6675
|
-
runTest('MACHINE:graph_not_equivalent_different_cap',
|
|
6676
|
-
runTest('MACHINE:graph_empty',
|
|
6677
|
-
runTest('MACHINE:graph_empty_equivalence',
|
|
6678
|
-
runTest('MACHINE:root_sources_linear_chain',
|
|
6679
|
-
runTest('MACHINE:leaf_targets_linear_chain',
|
|
6680
|
-
runTest('MACHINE:root_sources_fan_in',
|
|
6681
|
-
runTest('MACHINE:display_edge',
|
|
6682
|
-
runTest('MACHINE:display_graph',
|
|
6672
|
+
runTest('MACHINE:edge_equivalence_same_urns', test6357_Machine_edgeEquivalenceSameUrns);
|
|
6673
|
+
runTest('MACHINE:edge_equivalence_different_cap_urns', test6361_Machine_edgeEquivalenceDifferentCapUrns);
|
|
6674
|
+
runTest('MACHINE:edge_equivalence_different_targets', test6365_Machine_edgeEquivalenceDifferentTargets);
|
|
6675
|
+
runTest('MACHINE:edge_equivalence_different_loop_flag', test6369_Machine_edgeEquivalenceDifferentLoopFlag);
|
|
6676
|
+
runTest('MACHINE:edge_equivalence_source_order_independent', test6372_Machine_edgeEquivalenceSourceOrderIndependent);
|
|
6677
|
+
runTest('MACHINE:edge_equivalence_different_source_count', test6375_Machine_edgeEquivalenceDifferentSourceCount);
|
|
6678
|
+
runTest('MACHINE:graph_equivalence_same_edges', test6377_Machine_graphEquivalenceSameEdges);
|
|
6679
|
+
runTest('MACHINE:graph_equivalence_reordered_edges', test6380_Machine_graphEquivalenceReorderedEdges);
|
|
6680
|
+
runTest('MACHINE:graph_not_equivalent_different_edge_count', test6383_Machine_graphNotEquivalentDifferentEdgeCount);
|
|
6681
|
+
runTest('MACHINE:graph_not_equivalent_different_cap', test6386_Machine_graphNotEquivalentDifferentCap);
|
|
6682
|
+
runTest('MACHINE:graph_empty', test6389_Machine_graphEmpty);
|
|
6683
|
+
runTest('MACHINE:graph_empty_equivalence', test6392_Machine_graphEmptyEquivalence);
|
|
6684
|
+
runTest('MACHINE:root_sources_linear_chain', test6395_Machine_rootSourcesLinearChain);
|
|
6685
|
+
runTest('MACHINE:leaf_targets_linear_chain', test6397_Machine_leafTargetsLinearChain);
|
|
6686
|
+
runTest('MACHINE:root_sources_fan_in', test6398_Machine_rootSourcesFanIn);
|
|
6687
|
+
runTest('MACHINE:display_edge', test6400_Machine_displayEdge);
|
|
6688
|
+
runTest('MACHINE:display_graph', test6402_Machine_displayGraph);
|
|
6683
6689
|
|
|
6684
6690
|
// machine module: serializer tests (mirrors serializer.rs)
|
|
6685
6691
|
console.log('\n--- machine/serializer.rs ---');
|
|
6686
|
-
runTest('MACHINE:serialize_single_edge',
|
|
6687
|
-
runTest('MACHINE:serialize_two_edge_chain',
|
|
6688
|
-
runTest('MACHINE:serialize_empty_graph',
|
|
6689
|
-
runTest('MACHINE:roundtrip_single_edge',
|
|
6690
|
-
runTest('MACHINE:roundtrip_two_edge_chain',
|
|
6691
|
-
runTest('MACHINE:roundtrip_fan_out',
|
|
6692
|
-
runTest('MACHINE:roundtrip_loop_edge',
|
|
6693
|
-
runTest('MACHINE:serialization_is_deterministic',
|
|
6694
|
-
runTest('MACHINE:reordered_edges_produce_same_notation',
|
|
6695
|
-
runTest('MACHINE:multiline_serialize_format',
|
|
6696
|
-
runTest('MACHINE:alias_from_op_tag',
|
|
6697
|
-
runTest('MACHINE:alias_fallback_without_op_tag',
|
|
6698
|
-
runTest('MACHINE:duplicate_op_tags_disambiguated',
|
|
6692
|
+
runTest('MACHINE:serialize_single_edge', test6404_Machine_serializeSingleEdge);
|
|
6693
|
+
runTest('MACHINE:serialize_two_edge_chain', test6406_Machine_serializeTwoEdgeChain);
|
|
6694
|
+
runTest('MACHINE:serialize_empty_graph', test6408_Machine_serializeEmptyGraph);
|
|
6695
|
+
runTest('MACHINE:roundtrip_single_edge', test6410_Machine_roundtripSingleEdge);
|
|
6696
|
+
runTest('MACHINE:roundtrip_two_edge_chain', test6413_Machine_roundtripTwoEdgeChain);
|
|
6697
|
+
runTest('MACHINE:roundtrip_fan_out', test6415_Machine_roundtripFanOut);
|
|
6698
|
+
runTest('MACHINE:roundtrip_loop_edge', test6417_Machine_roundtripLoopEdge);
|
|
6699
|
+
runTest('MACHINE:serialization_is_deterministic', test6419_Machine_serializationIsDeterministic);
|
|
6700
|
+
runTest('MACHINE:reordered_edges_produce_same_notation', test6421_Machine_reorderedEdgesProduceSameNotation);
|
|
6701
|
+
runTest('MACHINE:multiline_serialize_format', test6429_Machine_multilineSerializeFormat);
|
|
6702
|
+
runTest('MACHINE:alias_from_op_tag', test6432_Machine_aliasFromOpTag);
|
|
6703
|
+
runTest('MACHINE:alias_fallback_without_op_tag', test6434_Machine_aliasFallbackWithoutOpTag);
|
|
6704
|
+
runTest('MACHINE:duplicate_op_tags_disambiguated', test6436_Machine_duplicateOpTagsDisambiguated);
|
|
6699
6705
|
|
|
6700
6706
|
// machine module: builder tests
|
|
6701
6707
|
console.log('\n--- machine/builder ---');
|
|
6702
|
-
runTest('MACHINE:builder_single_edge',
|
|
6703
|
-
runTest('MACHINE:builder_with_loop',
|
|
6704
|
-
runTest('MACHINE:builder_chaining',
|
|
6705
|
-
runTest('MACHINE:builder_equivalent_to_parsed',
|
|
6706
|
-
runTest('MACHINE:builder_round_trip',
|
|
6708
|
+
runTest('MACHINE:builder_single_edge', test6437_Machine_builderSingleEdge);
|
|
6709
|
+
runTest('MACHINE:builder_with_loop', test6438_Machine_builderWithLoop);
|
|
6710
|
+
runTest('MACHINE:builder_chaining', test6439_Machine_builderChaining);
|
|
6711
|
+
runTest('MACHINE:builder_equivalent_to_parsed', test6440_Machine_builderEquivalentToParsed);
|
|
6712
|
+
runTest('MACHINE:builder_round_trip', test6442_Machine_builderRoundTrip);
|
|
6707
6713
|
|
|
6708
6714
|
// machine module: CapUrn.isEquivalent/isComparable
|
|
6709
6715
|
console.log('\n--- machine/urn_predicates ---');
|
|
6710
|
-
runTest('MACHINE:cap_urn_is_equivalent',
|
|
6711
|
-
runTest('MACHINE:cap_urn_is_comparable',
|
|
6712
|
-
runTest('MACHINE:cap_urn_in_media_urn',
|
|
6713
|
-
runTest('MACHINE:cap_urn_out_media_urn',
|
|
6714
|
-
runTest('MACHINE:media_urn_is_equivalent',
|
|
6715
|
-
runTest('MACHINE:media_urn_is_comparable',
|
|
6716
|
+
runTest('MACHINE:cap_urn_is_equivalent', test6444_Machine_capUrnIsEquivalent);
|
|
6717
|
+
runTest('MACHINE:cap_urn_is_comparable', test6446_Machine_capUrnIsComparable);
|
|
6718
|
+
runTest('MACHINE:cap_urn_in_media_urn', test6448_Machine_capUrnInMediaUrn);
|
|
6719
|
+
runTest('MACHINE:cap_urn_out_media_urn', test6449_Machine_capUrnOutMediaUrn);
|
|
6720
|
+
runTest('MACHINE:media_urn_is_equivalent', test6450_Machine_mediaUrnIsEquivalent);
|
|
6721
|
+
runTest('MACHINE:media_urn_is_comparable', test6451_Machine_mediaUrnIsComparable);
|
|
6716
6722
|
|
|
6717
6723
|
// Phase 0A: Position tracking
|
|
6718
6724
|
console.log('\n--- machine/position_tracking ---');
|
|
6719
|
-
runTest('MACHINE:parseMachineWithAST_headerLocation',
|
|
6720
|
-
runTest('MACHINE:parseMachineWithAST_wiringLocation',
|
|
6721
|
-
runTest('MACHINE:parseMachineWithAST_multilinePositions',
|
|
6722
|
-
runTest('MACHINE:parseMachineWithAST_fanInSourceLocations',
|
|
6723
|
-
runTest('MACHINE:parseMachineWithAST_aliasMap',
|
|
6724
|
-
runTest('MACHINE:parseMachineWithAST_nodeMedia',
|
|
6725
|
-
runTest('MACHINE:errorLocation_parseError',
|
|
6726
|
-
runTest('MACHINE:errorLocation_duplicateAlias',
|
|
6727
|
-
runTest('MACHINE:errorLocation_undefinedAlias',
|
|
6725
|
+
runTest('MACHINE:parseMachineWithAST_headerLocation', test6452_Machine_parseMachineWithAST_headerLocation);
|
|
6726
|
+
runTest('MACHINE:parseMachineWithAST_wiringLocation', test6453_Machine_parseMachineWithAST_wiringLocation);
|
|
6727
|
+
runTest('MACHINE:parseMachineWithAST_multilinePositions', test6454_Machine_parseMachineWithAST_multilinePositions);
|
|
6728
|
+
runTest('MACHINE:parseMachineWithAST_fanInSourceLocations', test6455_Machine_parseMachineWithAST_fanInSourceLocations);
|
|
6729
|
+
runTest('MACHINE:parseMachineWithAST_aliasMap', test6456_Machine_parseMachineWithAST_aliasMap);
|
|
6730
|
+
runTest('MACHINE:parseMachineWithAST_nodeMedia', test6457_Machine_parseMachineWithAST_nodeMedia);
|
|
6731
|
+
runTest('MACHINE:errorLocation_parseError', test6458_Machine_errorLocation_parseError);
|
|
6732
|
+
runTest('MACHINE:errorLocation_duplicateAlias', test6459_Machine_errorLocation_duplicateAlias);
|
|
6733
|
+
runTest('MACHINE:errorLocation_undefinedAlias', test6460_Machine_errorLocation_undefinedAlias);
|
|
6728
6734
|
|
|
6729
6735
|
// Phase 0C: Machine.toMermaid()
|
|
6730
6736
|
console.log('\n--- machine/mermaid ---');
|
|
6731
|
-
runTest('MACHINE:toMermaid_linearChain',
|
|
6732
|
-
runTest('MACHINE:toMermaid_loopEdge',
|
|
6733
|
-
runTest('MACHINE:toMermaid_emptyGraph',
|
|
6734
|
-
runTest('MACHINE:toMermaid_fanIn',
|
|
6735
|
-
runTest('MACHINE:toMermaid_fanOut',
|
|
6737
|
+
runTest('MACHINE:toMermaid_linearChain', test6462_Machine_toMermaid_linearChain);
|
|
6738
|
+
runTest('MACHINE:toMermaid_loopEdge', test6463_Machine_toMermaid_loopEdge);
|
|
6739
|
+
runTest('MACHINE:toMermaid_emptyGraph', test6464_Machine_toMermaid_emptyGraph);
|
|
6740
|
+
runTest('MACHINE:toMermaid_fanIn', test6465_Machine_toMermaid_fanIn);
|
|
6741
|
+
runTest('MACHINE:toMermaid_fanOut', test6466_Machine_toMermaid_fanOut);
|
|
6736
6742
|
|
|
6737
6743
|
// Phase 0B: FabricRegistryClient
|
|
6738
6744
|
console.log('\n--- registry/client ---');
|
|
6739
|
-
runTest('REGISTRY: capRegistryEntry_construction',
|
|
6740
|
-
runTest('REGISTRY: mediaRegistryEntry_construction',
|
|
6741
|
-
runTest('REGISTRY: capRegistryClient_construction',
|
|
6742
|
-
runTest('REGISTRY: capRegistryEntry_defaults',
|
|
6745
|
+
runTest('REGISTRY: capRegistryEntry_construction', test6467_Machine_capRegistryEntry_construction);
|
|
6746
|
+
runTest('REGISTRY: mediaRegistryEntry_construction', test6468_Machine_mediaRegistryEntry_construction);
|
|
6747
|
+
runTest('REGISTRY: capRegistryClient_construction', test6469_Machine_capRegistryClient_construction);
|
|
6748
|
+
runTest('REGISTRY: capRegistryEntry_defaults', test6470_Machine_capRegistryEntry_defaults);
|
|
6743
6749
|
|
|
6744
6750
|
// cap-fab-renderer pure helpers (no DOM dependency)
|
|
6745
6751
|
console.log('\n--- cap-fab-renderer helpers ---');
|
|
6746
|
-
runTest('RENDERER: cardinalityLabel_allFourCases',
|
|
6747
|
-
runTest('RENDERER: cardinalityLabel_usesUnicodeArrow',
|
|
6748
|
-
runTest('RENDERER: cardinalityFromCap_findsStdinArg',
|
|
6749
|
-
runTest('RENDERER: cardinalityFromCap_scalarDefaults',
|
|
6750
|
-
runTest('RENDERER: cardinalityFromCap_outputOnlySequence',
|
|
6751
|
-
runTest('RENDERER: cardinalityFromCap_rejectsStringBool',
|
|
6752
|
-
runTest('RENDERER: cardinalityFromCap_throwsOnNonObject',
|
|
6753
|
-
runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder',
|
|
6754
|
-
runTest('RENDERER: canonicalMediaUrn_preservesValueTags',
|
|
6755
|
-
runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn',
|
|
6756
|
-
runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived',
|
|
6757
|
-
runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles',
|
|
6752
|
+
runTest('RENDERER: cardinalityLabel_allFourCases', test6471_Renderer_cardinalityLabel_allFourCases);
|
|
6753
|
+
runTest('RENDERER: cardinalityLabel_usesUnicodeArrow', test6472_Renderer_cardinalityLabel_usesUnicodeArrow);
|
|
6754
|
+
runTest('RENDERER: cardinalityFromCap_findsStdinArg', test6473_Renderer_cardinalityFromCap_findsStdinArgNotFirstArg);
|
|
6755
|
+
runTest('RENDERER: cardinalityFromCap_scalarDefaults', test6474_Renderer_cardinalityFromCap_scalarDefaultsWhenFieldsMissing);
|
|
6756
|
+
runTest('RENDERER: cardinalityFromCap_outputOnlySequence', test6475_Renderer_cardinalityFromCap_outputOnlySequence);
|
|
6757
|
+
runTest('RENDERER: cardinalityFromCap_rejectsStringBool', test6476_Renderer_cardinalityFromCap_rejectsStringIsSequence);
|
|
6758
|
+
runTest('RENDERER: cardinalityFromCap_throwsOnNonObject', test6478_Renderer_cardinalityFromCap_throwsOnNonObject);
|
|
6759
|
+
runTest('RENDERER: canonicalMediaUrn_normalizesTagOrder', test6479_Renderer_canonicalMediaUrn_normalizesTagOrder);
|
|
6760
|
+
runTest('RENDERER: canonicalMediaUrn_preservesValueTags', test6480_Renderer_canonicalMediaUrn_preservesValueTags);
|
|
6761
|
+
runTest('RENDERER: canonicalMediaUrn_rejectsCapUrn', test6481_Renderer_canonicalMediaUrn_rejectsCapUrn);
|
|
6762
|
+
runTest('RENDERER: mediaNodeLabel_rejectsUrnDerived', test6482_Renderer_mediaNodeLabel_rejectsUrnDerivedLabels);
|
|
6763
|
+
runTest('RENDERER: buildBrowse_rejectsMissingMediaTitles', test6483_Renderer_buildBrowseGraphData_rejectsMissingMediaTitles);
|
|
6758
6764
|
|
|
6759
6765
|
console.log('\n--- cap-fab-renderer strand builder ---');
|
|
6760
|
-
runTest('RENDERER: validateStrandStep_unknownVariant',
|
|
6761
|
-
runTest('RENDERER: validateStrandStep_booleanIsSequence',
|
|
6762
|
-
runTest('RENDERER: classifyStrandCapSteps_simple',
|
|
6763
|
-
runTest('RENDERER: classifyStrandCapSteps_nested',
|
|
6764
|
-
runTest('RENDERER: buildStrand_singleCapPlain',
|
|
6765
|
-
runTest('RENDERER: buildStrand_sequenceShowsCardinality',
|
|
6766
|
-
runTest('RENDERER: buildStrand_foreachCollectSpan',
|
|
6767
|
-
runTest('RENDERER: buildStrand_standaloneCollect',
|
|
6768
|
-
runTest('RENDERER: buildStrand_unclosedForEachBody',
|
|
6769
|
-
runTest('RENDERER: buildStrand_nestedForEachThrows',
|
|
6770
|
-
runTest('RENDERER: collapseStrand_singleCapBody',
|
|
6771
|
-
runTest('RENDERER: collapseStrand_unclosedForEachBody',
|
|
6772
|
-
runTest('RENDERER: collapseStrand_standaloneCollect',
|
|
6773
|
-
runTest('RENDERER: collapseStrand_seqCapBeforeForeach',
|
|
6774
|
-
runTest('RENDERER: collapseStrand_plainCapMergesOutput',
|
|
6775
|
-
runTest('RENDERER: collapseStrand_plainCapDistinctTarget',
|
|
6776
|
-
runTest('RENDERER: validateStrand_missingSourceMediaUrn',
|
|
6766
|
+
runTest('RENDERER: validateStrandStep_unknownVariant', test6484_Renderer_validateStrandStep_rejectsUnknownVariant);
|
|
6767
|
+
runTest('RENDERER: validateStrandStep_booleanIsSequence', test6486_Renderer_validateStrandStep_requiresBooleanIsSequence);
|
|
6768
|
+
runTest('RENDERER: classifyStrandCapSteps_simple', test6487_Renderer_classifyStrandCapSteps_capFlags);
|
|
6769
|
+
runTest('RENDERER: classifyStrandCapSteps_nested', test6488_Renderer_classifyStrandCapSteps_nestedForks);
|
|
6770
|
+
runTest('RENDERER: buildStrand_singleCapPlain', test6489_Renderer_buildStrandGraphData_singleCapPlain);
|
|
6771
|
+
runTest('RENDERER: buildStrand_sequenceShowsCardinality', test6491_Renderer_buildStrandGraphData_sequenceShowsCardinality);
|
|
6772
|
+
runTest('RENDERER: buildStrand_foreachCollectSpan', test6492_Renderer_buildStrandGraphData_foreachCollectSpan);
|
|
6773
|
+
runTest('RENDERER: buildStrand_standaloneCollect', test6493_Renderer_buildStrandGraphData_standaloneCollect);
|
|
6774
|
+
runTest('RENDERER: buildStrand_unclosedForEachBody', test6494_Renderer_buildStrandGraphData_unclosedForEachBody);
|
|
6775
|
+
runTest('RENDERER: buildStrand_nestedForEachThrows', test6495_Renderer_buildStrandGraphData_nestedForEachThrows);
|
|
6776
|
+
runTest('RENDERER: collapseStrand_singleCapBody', test6496_Renderer_collapseStrand_singleCapBodyKeepsCapOwnLabel);
|
|
6777
|
+
runTest('RENDERER: collapseStrand_unclosedForEachBody', test6497_Renderer_collapseStrand_unclosedForEachBodyCollapses);
|
|
6778
|
+
runTest('RENDERER: collapseStrand_standaloneCollect', test6498_Renderer_collapseStrand_standaloneCollectCollapses);
|
|
6779
|
+
runTest('RENDERER: collapseStrand_seqCapBeforeForeach', test6499_Renderer_collapseStrand_sequenceProducingCapBeforeForeach);
|
|
6780
|
+
runTest('RENDERER: collapseStrand_plainCapMergesOutput', test6500_Renderer_collapseStrand_plainCapMergesTrailingOutput);
|
|
6781
|
+
runTest('RENDERER: collapseStrand_plainCapDistinctTarget', test6501_Renderer_collapseStrand_plainCapDistinctTargetNoMerge);
|
|
6782
|
+
runTest('RENDERER: validateStrand_missingSourceMediaUrn', test6502_Renderer_validateStrandPayload_missingSourceMediaUrn);
|
|
6777
6783
|
|
|
6778
6784
|
console.log('\n--- cap-fab-renderer run builder ---');
|
|
6779
|
-
runTest('RENDERER: validateBodyOutcome_negativeIndex',
|
|
6780
|
-
runTest('RENDERER: buildRun_pagesSuccessesAndFailures',
|
|
6781
|
-
runTest('RENDERER: buildRun_failureWithoutFailedCap',
|
|
6782
|
-
runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap',
|
|
6783
|
-
runTest('RENDERER: buildRun_backboneHasNoForeachNode',
|
|
6784
|
-
runTest('RENDERER: buildRun_allFailedDropsPlaceholder',
|
|
6785
|
-
runTest('RENDERER: buildRun_unclosedForeachNoMerge',
|
|
6786
|
-
runTest('RENDERER: buildRun_closedForeachMerges',
|
|
6785
|
+
runTest('RENDERER: validateBodyOutcome_negativeIndex', test6503_Renderer_validateBodyOutcome_rejectsNegativeIndex);
|
|
6786
|
+
runTest('RENDERER: buildRun_pagesSuccessesAndFailures', test6504_Renderer_buildRunGraphData_pagesSuccessesAndFailures);
|
|
6787
|
+
runTest('RENDERER: buildRun_failureWithoutFailedCap', test6505_Renderer_buildRunGraphData_failureWithoutFailedCapRendersFullTrace);
|
|
6788
|
+
runTest('RENDERER: buildRun_usesIsEquivalentForFailedCap', test6506_Renderer_buildRunGraphData_usesCapUrnIsEquivalentForFailedCap);
|
|
6789
|
+
runTest('RENDERER: buildRun_backboneHasNoForeachNode', test6507_Renderer_buildRunGraphData_backboneHasNoForeachNode);
|
|
6790
|
+
runTest('RENDERER: buildRun_allFailedDropsPlaceholder', test6508_Renderer_buildRunGraphData_allFailedDropsTargetPlaceholder);
|
|
6791
|
+
runTest('RENDERER: buildRun_unclosedForeachNoMerge', test6509_Renderer_buildRunGraphData_unclosedForeachSuccessNoMerge);
|
|
6792
|
+
runTest('RENDERER: buildRun_closedForeachMerges', test6510_Renderer_buildRunGraphData_closedForeachSuccessMergesAtCollectTarget);
|
|
6787
6793
|
|
|
6788
6794
|
console.log('\n--- cap-fab-renderer editor-graph builder ---');
|
|
6789
|
-
runTest('RENDERER: validateEditorGraph_unknownKind',
|
|
6790
|
-
runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges',
|
|
6791
|
-
runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass',
|
|
6792
|
-
runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq',
|
|
6793
|
-
runTest('RENDERER: buildEditorGraph_incompleteCapDropped',
|
|
6794
|
-
runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc',
|
|
6795
|
+
runTest('RENDERER: validateEditorGraph_unknownKind', test6511_Renderer_validateEditorGraphPayload_rejectsUnknownKind);
|
|
6796
|
+
runTest('RENDERER: buildEditorGraph_collapsesCapsIntoEdges', test6512_Renderer_buildEditorGraphData_collapsesCapsIntoLabeledEdges);
|
|
6797
|
+
runTest('RENDERER: buildEditorGraph_loopEdgeGetsClass', test6513_Renderer_buildEditorGraphData_loopMarkedEdgeGetsLoopClass);
|
|
6798
|
+
runTest('RENDERER: buildEditorGraph_cardinalityFromIsSeq', test6514_Renderer_buildEditorGraphData_cardinalityFromDataSlotSequenceFlags);
|
|
6799
|
+
runTest('RENDERER: buildEditorGraph_incompleteCapDropped', test6515_Renderer_buildEditorGraphData_capWithoutCompleteArgsIsDropped);
|
|
6800
|
+
runTest('RENDERER: buildEditorGraph_rejectsEdgeMissingSrc', test6516_Renderer_buildEditorGraphData_rejectsEdgeWithMissingSource);
|
|
6795
6801
|
|
|
6796
6802
|
console.log('\n--- cap-fab-renderer resolved-machine builder ---');
|
|
6797
|
-
runTest('RENDERER: buildResolvedMachine_singleStrandLinear',
|
|
6798
|
-
runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass',
|
|
6799
|
-
runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc',
|
|
6800
|
-
runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint',
|
|
6801
|
-
runTest('RENDERER: buildResolvedMachine_dupNodeIdFails',
|
|
6802
|
-
runTest('RENDERER: validateResolvedMachine_rejectsMissingFields',
|
|
6803
|
+
runTest('RENDERER: buildResolvedMachine_singleStrandLinear', test6517_Renderer_buildResolvedMachineGraphData_singleStrandLinearChain);
|
|
6804
|
+
runTest('RENDERER: buildResolvedMachine_loopGetsLoopClass', test6518_Renderer_buildResolvedMachineGraphData_loopEdgeGetsLoopClass);
|
|
6805
|
+
runTest('RENDERER: buildResolvedMachine_fanInOneEdgePerSrc', test6519_Renderer_buildResolvedMachineGraphData_fanInProducesEdgePerAssignment);
|
|
6806
|
+
runTest('RENDERER: buildResolvedMachine_multiStrandDisjoint', test6520_Renderer_buildResolvedMachineGraphData_multiStrandKeepsStrandsDisjoint);
|
|
6807
|
+
runTest('RENDERER: buildResolvedMachine_dupNodeIdFails', test6521_Renderer_buildResolvedMachineGraphData_duplicateNodeIdAcrossStrandsFailsHard);
|
|
6808
|
+
runTest('RENDERER: validateResolvedMachine_rejectsMissingFields', test6522_Renderer_validateResolvedMachinePayload_rejectsMissingFields);
|
|
6803
6809
|
|
|
6804
6810
|
console.log('\n--- CapKind classifier (test1800–test1805) ---');
|
|
6805
6811
|
runTest('TEST1800: kind_identity_requires_effect_none', test1800_kindIdentityOnlyForBareCap);
|
|
@@ -6829,15 +6835,20 @@ async function runTests() {
|
|
|
6829
6835
|
|
|
6830
6836
|
console.log('\n--- Truth-table cross-product + axis weighting (test1842–test1846) ---');
|
|
6831
6837
|
runTest('TEST1842: truth_table_full_cross_product', test1842_truthTableFullCrossProduct);
|
|
6832
|
-
runTest('TEST1843: reject_invalid_combinations',
|
|
6833
|
-
runTest('TEST1844: axis_weighting_out_dominates',
|
|
6838
|
+
runTest('TEST1843: reject_invalid_combinations', test6734_rejectInvalidCombinations);
|
|
6839
|
+
runTest('TEST1844: axis_weighting_out_dominates', test6735_axisWeightingOutDominates);
|
|
6834
6840
|
runTest('TEST1845: axis_weighting_in_dominates_y', test1845_axisWeightingInDominatesY);
|
|
6835
|
-
runTest('TEST1846: axis_weighting_decoded_layout',
|
|
6841
|
+
runTest('TEST1846: axis_weighting_decoded_layout', test6736_axisWeightingDecodedLayout);
|
|
6836
6842
|
|
|
6837
6843
|
// Cap.version round-trip tests
|
|
6838
|
-
runTest('TEST1847: cap_version_zero_omitted_on_wire',
|
|
6844
|
+
runTest('TEST1847: cap_version_zero_omitted_on_wire', test6737_capVersionZeroOmittedOnWire);
|
|
6839
6845
|
runTest('TEST1848: cap_version_nonzero_on_wire', test1848_capVersionNonZeroOnWire);
|
|
6840
6846
|
|
|
6847
|
+
runTest('TEST1880: alias_name_normalization_rules', test1880_aliasNameNormalizationRules);
|
|
6848
|
+
runTest('TEST1881: token_urn_vs_alias_detection', test1881_tokenUrnVsAliasDetection);
|
|
6849
|
+
runTest('TEST1882: classify_alias_target_by_prefix', test1882_classifyAliasTargetByPrefix);
|
|
6850
|
+
runTest('TEST1887: manifest_serde_round_trips_aliases', test1887_manifestSerdeRoundTripsAliases);
|
|
6851
|
+
|
|
6841
6852
|
// Summary
|
|
6842
6853
|
console.log(`\n${passCount + failCount} tests: ${passCount} passed, ${failCount} failed`);
|
|
6843
6854
|
if (failCount > 0) {
|