tagged-urn 0.34.83 → 0.35.86
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/README.md +4 -4
- package/package.json +1 -1
- package/tagged-urn.test.js +67 -67
package/README.md
CHANGED
|
@@ -26,8 +26,8 @@ npm install tagged-urn
|
|
|
26
26
|
const { TaggedUrn, TaggedUrnBuilder, UrnMatcher } = require('tagged-urn');
|
|
27
27
|
|
|
28
28
|
// Create from string
|
|
29
|
-
const urn = TaggedUrn.fromString('cap:
|
|
30
|
-
console.log(urn.toString()); // "cap:ext=pdf;
|
|
29
|
+
const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
30
|
+
console.log(urn.toString()); // "cap:ext=pdf;generate"
|
|
31
31
|
|
|
32
32
|
// Use builder pattern
|
|
33
33
|
const built = new TaggedUrnBuilder()
|
|
@@ -43,10 +43,10 @@ console.log(urn.conformsTo(request)); // true
|
|
|
43
43
|
const urns = [
|
|
44
44
|
TaggedUrn.fromString('cap:op=*'),
|
|
45
45
|
TaggedUrn.fromString('cap:op=generate'),
|
|
46
|
-
TaggedUrn.fromString('cap:
|
|
46
|
+
TaggedUrn.fromString('cap:generate;ext=pdf')
|
|
47
47
|
];
|
|
48
48
|
const best = UrnMatcher.findBestMatch(urns, request);
|
|
49
|
-
console.log(best.toString()); // "cap:ext=pdf;
|
|
49
|
+
console.log(best.toString()); // "cap:ext=pdf;generate" (most specific)
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## API Reference
|
package/package.json
CHANGED
package/tagged-urn.test.js
CHANGED
|
@@ -61,7 +61,7 @@ function assertThrowsAny(fn, message) {
|
|
|
61
61
|
|
|
62
62
|
// TEST501: Verify basic URN creation from string with multiple tags
|
|
63
63
|
function test501_tagged_urn_creation() {
|
|
64
|
-
const urn = TaggedUrn.fromString('cap:
|
|
64
|
+
const urn = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
|
|
65
65
|
assertEqual(urn.getPrefix(), 'cap', 'Should get prefix');
|
|
66
66
|
assertEqual(urn.getTag('op'), 'generate', 'Should get op tag');
|
|
67
67
|
assertEqual(urn.getTag('target'), 'thumbnail', 'Should get target tag');
|
|
@@ -70,7 +70,7 @@ function test501_tagged_urn_creation() {
|
|
|
70
70
|
|
|
71
71
|
// TEST502: Verify custom prefixes work and tags are sorted alphabetically
|
|
72
72
|
function test502_custom_prefix() {
|
|
73
|
-
const urn = TaggedUrn.fromString('myapp:
|
|
73
|
+
const urn = TaggedUrn.fromString('myapp:generate;ext=pdf');
|
|
74
74
|
assertEqual(urn.getPrefix(), 'myapp', 'Should get custom prefix');
|
|
75
75
|
assertEqual(urn.getTag('op'), 'generate', 'Should get op tag');
|
|
76
76
|
assertEqual(urn.toString(), 'myapp:ext=pdf;op=generate', 'Should sort tags alphabetically');
|
|
@@ -125,7 +125,7 @@ function test506_unquoted_values_lowercased() {
|
|
|
125
125
|
assertEqual(urn.getTag('Op'), 'generate', 'Should lookup with mixed case key');
|
|
126
126
|
|
|
127
127
|
// Normalized URN equals explicitly lowercase URN
|
|
128
|
-
const urn2 = TaggedUrn.fromString('cap:
|
|
128
|
+
const urn2 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
|
|
129
129
|
assertEqual(urn.toString(), urn2.toString(), 'Should produce same canonical form');
|
|
130
130
|
assert(urn.equals(urn2), 'Should be equal after normalization');
|
|
131
131
|
}
|
|
@@ -266,7 +266,7 @@ function test516_round_trip_escapes() {
|
|
|
266
266
|
// TEST517: Verify missing prefix causes error
|
|
267
267
|
function test517_prefix_required() {
|
|
268
268
|
assertThrows(
|
|
269
|
-
() => TaggedUrn.fromString('
|
|
269
|
+
() => TaggedUrn.fromString('generate;ext=pdf'),
|
|
270
270
|
ErrorCodes.MISSING_PREFIX,
|
|
271
271
|
'Should require prefix'
|
|
272
272
|
);
|
|
@@ -277,7 +277,7 @@ function test517_prefix_required() {
|
|
|
277
277
|
'Should reject empty prefix'
|
|
278
278
|
);
|
|
279
279
|
|
|
280
|
-
const urn = TaggedUrn.fromString('cap:
|
|
280
|
+
const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
281
281
|
assertEqual(urn.getTag('op'), 'generate', 'Should parse with valid prefix');
|
|
282
282
|
|
|
283
283
|
// Case-insensitive prefix
|
|
@@ -287,8 +287,8 @@ function test517_prefix_required() {
|
|
|
287
287
|
|
|
288
288
|
// TEST518: Verify trailing semicolon is optional and doesn't affect equality
|
|
289
289
|
function test518_trailing_semicolon_equivalence() {
|
|
290
|
-
const urn1 = TaggedUrn.fromString('cap:
|
|
291
|
-
const urn2 = TaggedUrn.fromString('cap:
|
|
290
|
+
const urn1 = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
291
|
+
const urn2 = TaggedUrn.fromString('cap:generate;ext=pdf;');
|
|
292
292
|
|
|
293
293
|
assert(urn1.equals(urn2), 'Should be equal with/without trailing semicolon');
|
|
294
294
|
assertEqual(urn1.toString(), urn2.toString(), 'Should have same canonical form');
|
|
@@ -302,8 +302,8 @@ function test518_trailing_semicolon_equivalence() {
|
|
|
302
302
|
|
|
303
303
|
// TEST519: Verify canonical form: alphabetically sorted tags, no trailing semicolon
|
|
304
304
|
function test519_canonical_string_format() {
|
|
305
|
-
const urn = TaggedUrn.fromString('cap:
|
|
306
|
-
assertEqual(urn.toString(), 'cap:ext=pdf;
|
|
305
|
+
const urn = TaggedUrn.fromString('cap:generate;target=thumbnail;ext=pdf');
|
|
306
|
+
assertEqual(urn.toString(), 'cap:ext=pdf;generate;target=thumbnail', 'Should be alphabetically sorted');
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
// ============================================================================
|
|
@@ -312,9 +312,9 @@ function test519_canonical_string_format() {
|
|
|
312
312
|
|
|
313
313
|
// TEST520: Verify hasTag and getTag methods work correctly
|
|
314
314
|
function test520_tag_matching() {
|
|
315
|
-
const cap = TaggedUrn.fromString('cap:
|
|
315
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
|
|
316
316
|
|
|
317
|
-
const request1 = TaggedUrn.fromString('cap:
|
|
317
|
+
const request1 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
|
|
318
318
|
assert(cap.conformsTo(request1), 'Should match exact request');
|
|
319
319
|
|
|
320
320
|
const request2 = TaggedUrn.fromString('cap:op=generate');
|
|
@@ -330,7 +330,7 @@ function test520_tag_matching() {
|
|
|
330
330
|
// TEST521: Verify value matching is case-sensitive
|
|
331
331
|
function test521_matching_case_sensitive_values() {
|
|
332
332
|
const cap1 = TaggedUrn.fromString('cap:OP=Generate;EXT=PDF;Target=Thumbnail');
|
|
333
|
-
const cap2 = TaggedUrn.fromString('cap:
|
|
333
|
+
const cap2 = TaggedUrn.fromString('cap:generate;ext=pdf;target=thumbnail');
|
|
334
334
|
|
|
335
335
|
// Unquoted values are lowercased, so these should be equal
|
|
336
336
|
assert(cap1.equals(cap2), 'URNs with unquoted case differences should be equal');
|
|
@@ -352,7 +352,7 @@ function test522_missing_tag_handling() {
|
|
|
352
352
|
assert(!instance.conformsTo(pattern1), 'Should NOT match when instance missing pattern-required tag');
|
|
353
353
|
|
|
354
354
|
// Pattern missing tag = no constraint: MATCH
|
|
355
|
-
const instance2 = TaggedUrn.fromString('cap:
|
|
355
|
+
const instance2 = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
356
356
|
const pattern2 = TaggedUrn.fromString('cap:op=generate');
|
|
357
357
|
assert(instance2.conformsTo(pattern2), 'Should match subset pattern');
|
|
358
358
|
|
|
@@ -419,17 +419,17 @@ function test525_builder_preserves_case() {
|
|
|
419
419
|
function test526_compatibility() {
|
|
420
420
|
// General pattern accepts specific instance
|
|
421
421
|
const general = TaggedUrn.fromString('cap:op=generate');
|
|
422
|
-
const specific = TaggedUrn.fromString('cap:
|
|
422
|
+
const specific = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
423
423
|
assert(general.accepts(specific), 'General pattern accepts specific instance');
|
|
424
424
|
assert(!specific.accepts(general), 'Specific does not accept general');
|
|
425
425
|
|
|
426
426
|
// Wildcard pattern accepts any value
|
|
427
|
-
const wildcard = TaggedUrn.fromString('cap:
|
|
428
|
-
const withFormat = TaggedUrn.fromString('cap:
|
|
427
|
+
const wildcard = TaggedUrn.fromString('cap:generate;format=*');
|
|
428
|
+
const withFormat = TaggedUrn.fromString('cap:generate;format=json');
|
|
429
429
|
assert(wildcard.accepts(withFormat), 'Wildcard accepts specific value');
|
|
430
430
|
|
|
431
431
|
// Different op values: neither accepts the other
|
|
432
|
-
const cap1 = TaggedUrn.fromString('cap:
|
|
432
|
+
const cap1 = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
433
433
|
const cap3 = TaggedUrn.fromString('cap:image;op=extract');
|
|
434
434
|
assert(!cap1.accepts(cap3), 'Different op should not accept');
|
|
435
435
|
assert(!cap3.accepts(cap1), 'Different op should not accept (reverse)');
|
|
@@ -440,7 +440,7 @@ function test527_best_match() {
|
|
|
440
440
|
const caps = [
|
|
441
441
|
TaggedUrn.fromString('cap:op=*'),
|
|
442
442
|
TaggedUrn.fromString('cap:op=generate'),
|
|
443
|
-
TaggedUrn.fromString('cap:
|
|
443
|
+
TaggedUrn.fromString('cap:generate;ext=pdf')
|
|
444
444
|
];
|
|
445
445
|
|
|
446
446
|
const request = TaggedUrn.fromString('cap:op=generate');
|
|
@@ -462,7 +462,7 @@ function test528_merge_and_subset() {
|
|
|
462
462
|
const cap1 = TaggedUrn.fromString('cap:op=generate');
|
|
463
463
|
const cap2 = TaggedUrn.fromString('cap:ext=pdf;output=binary');
|
|
464
464
|
const merged = cap1.merge(cap2);
|
|
465
|
-
assertEqual(merged.toString(), 'cap:ext=pdf;
|
|
465
|
+
assertEqual(merged.toString(), 'cap:ext=pdf;generate;output=binary', 'Should merge correctly');
|
|
466
466
|
|
|
467
467
|
// Subset
|
|
468
468
|
const subset = merged.subset(['type', 'ext']);
|
|
@@ -502,7 +502,7 @@ function test531_empty_tagged_urn() {
|
|
|
502
502
|
assertEqual(Object.keys(empty.tags).length, 0, 'Should have no tags');
|
|
503
503
|
assertEqual(empty.toString(), 'cap:', 'Should have correct string representation');
|
|
504
504
|
|
|
505
|
-
const specific = TaggedUrn.fromString('cap:
|
|
505
|
+
const specific = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
506
506
|
|
|
507
507
|
// Empty instance vs specific pattern: NO MATCH
|
|
508
508
|
assert(!empty.conformsTo(specific), 'Empty instance should NOT match pattern with requirements');
|
|
@@ -642,67 +642,67 @@ function test542_semantic_equivalence() {
|
|
|
642
642
|
|
|
643
643
|
// TEST543: Instance and pattern have same tag/value - matches
|
|
644
644
|
function test543_matching_semantics_exact_match() {
|
|
645
|
-
const cap = TaggedUrn.fromString('cap:
|
|
646
|
-
const request = TaggedUrn.fromString('cap:
|
|
645
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
646
|
+
const request = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
647
647
|
assert(cap.conformsTo(request), 'Exact match should succeed');
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
// TEST544: Pattern requires tag but instance doesn't have it - no match
|
|
651
651
|
function test544_matching_semantics_instance_missing_tag() {
|
|
652
652
|
const instance = TaggedUrn.fromString('cap:op=generate');
|
|
653
|
-
const pattern = TaggedUrn.fromString('cap:
|
|
653
|
+
const pattern = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
654
654
|
assert(!instance.conformsTo(pattern), 'Instance missing tag should NOT match');
|
|
655
655
|
|
|
656
|
-
const patternOptional = TaggedUrn.fromString('cap:
|
|
656
|
+
const patternOptional = TaggedUrn.fromString('cap:generate;ext=?');
|
|
657
657
|
assert(instance.conformsTo(patternOptional), 'Pattern with ext=? should match instance without ext');
|
|
658
658
|
}
|
|
659
659
|
|
|
660
660
|
// TEST545: Instance has extra tag not in pattern - still matches
|
|
661
661
|
function test545_matching_semantics_extra_tag() {
|
|
662
|
-
const cap = TaggedUrn.fromString('cap:
|
|
663
|
-
const request = TaggedUrn.fromString('cap:
|
|
662
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=pdf;version=2');
|
|
663
|
+
const request = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
664
664
|
assert(cap.conformsTo(request), 'Cap with extra tag should match');
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
// TEST546: Pattern has wildcard - matches any value
|
|
668
668
|
function test546_matching_semantics_request_wildcard() {
|
|
669
|
-
const cap = TaggedUrn.fromString('cap:
|
|
670
|
-
const request = TaggedUrn.fromString('cap:
|
|
669
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
670
|
+
const request = TaggedUrn.fromString('cap:generate;ext=*');
|
|
671
671
|
assert(cap.conformsTo(request), 'Request wildcard should match');
|
|
672
672
|
}
|
|
673
673
|
|
|
674
674
|
// TEST547: Instance has wildcard - matches any pattern constraint
|
|
675
675
|
function test547_matching_semantics_cap_wildcard() {
|
|
676
|
-
const cap = TaggedUrn.fromString('cap:
|
|
677
|
-
const request = TaggedUrn.fromString('cap:
|
|
676
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=*');
|
|
677
|
+
const request = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
678
678
|
assert(cap.conformsTo(request), 'Cap wildcard should match');
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
// TEST548: Instance and pattern have same key but different values - no match
|
|
682
682
|
function test548_matching_semantics_value_mismatch() {
|
|
683
|
-
const cap = TaggedUrn.fromString('cap:
|
|
684
|
-
const request = TaggedUrn.fromString('cap:
|
|
683
|
+
const cap = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
684
|
+
const request = TaggedUrn.fromString('cap:generate;ext=docx');
|
|
685
685
|
assert(!cap.conformsTo(request), 'Value mismatch should not match');
|
|
686
686
|
}
|
|
687
687
|
|
|
688
688
|
// TEST549: Pattern has constraint instance doesn't have - no match
|
|
689
689
|
function test549_matching_semantics_pattern_extra_tag() {
|
|
690
|
-
const instance = TaggedUrn.fromString('cap:
|
|
691
|
-
const pattern = TaggedUrn.fromString('cap:
|
|
690
|
+
const instance = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary"');
|
|
691
|
+
const pattern = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary";ext=wav');
|
|
692
692
|
assert(!instance.conformsTo(pattern), 'Instance missing ext should NOT match pattern requiring ext=wav');
|
|
693
693
|
|
|
694
|
-
const patternNoExt = TaggedUrn.fromString('cap:
|
|
694
|
+
const patternNoExt = TaggedUrn.fromString('cap:generate-thumbnail;out="media:binary"');
|
|
695
695
|
assert(instance.conformsTo(patternNoExt), 'Should match pattern without ext constraint');
|
|
696
696
|
}
|
|
697
697
|
|
|
698
698
|
// TEST550: Empty pattern matches any instance
|
|
699
699
|
function test550_matching_semantics_empty_pattern() {
|
|
700
|
-
const instance = TaggedUrn.fromString('cap:
|
|
700
|
+
const instance = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
701
701
|
const emptyPattern = TaggedUrn.fromString('cap:');
|
|
702
702
|
assert(instance.conformsTo(emptyPattern), 'Any instance should match empty pattern');
|
|
703
703
|
|
|
704
704
|
const emptyInstance = TaggedUrn.fromString('cap:');
|
|
705
|
-
const pattern = TaggedUrn.fromString('cap:
|
|
705
|
+
const pattern = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
706
706
|
assert(!emptyInstance.conformsTo(pattern), 'Empty instance should NOT match pattern with requirements');
|
|
707
707
|
}
|
|
708
708
|
|
|
@@ -712,7 +712,7 @@ function test551_matching_semantics_cross_dimension() {
|
|
|
712
712
|
const pattern = TaggedUrn.fromString('cap:ext=pdf');
|
|
713
713
|
assert(!instance.conformsTo(pattern), 'Instance without ext should NOT match pattern requiring ext');
|
|
714
714
|
|
|
715
|
-
const instance2 = TaggedUrn.fromString('cap:
|
|
715
|
+
const instance2 = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
716
716
|
const pattern2 = TaggedUrn.fromString('cap:ext=pdf');
|
|
717
717
|
assert(instance2.conformsTo(pattern2), 'Instance with ext=pdf should match');
|
|
718
718
|
}
|
|
@@ -763,20 +763,20 @@ function test554_valueless_tag_parsing_multiple() {
|
|
|
763
763
|
|
|
764
764
|
// TEST555: Mix of valueless and valued tags works
|
|
765
765
|
function test555_valueless_tag_mixed_with_valued() {
|
|
766
|
-
const urn = TaggedUrn.fromString('cap:
|
|
766
|
+
const urn = TaggedUrn.fromString('cap:generate;optimize;ext=pdf;secure');
|
|
767
767
|
assertEqual(urn.getTag('op'), 'generate', 'Should parse valued tag');
|
|
768
768
|
assertEqual(urn.getTag('optimize'), '*', 'Should parse value-less tag');
|
|
769
769
|
assertEqual(urn.getTag('ext'), 'pdf', 'Should parse valued tag');
|
|
770
770
|
assertEqual(urn.getTag('secure'), '*', 'Should parse value-less tag');
|
|
771
|
-
assertEqual(urn.toString(), 'cap:ext=pdf;
|
|
771
|
+
assertEqual(urn.toString(), 'cap:ext=pdf;generate;optimize;secure', 'Should serialize alphabetically');
|
|
772
772
|
}
|
|
773
773
|
|
|
774
774
|
// TEST556: Valueless tag at end (no trailing semicolon) works
|
|
775
775
|
function test556_valueless_tag_at_end() {
|
|
776
|
-
const urn = TaggedUrn.fromString('cap:
|
|
776
|
+
const urn = TaggedUrn.fromString('cap:generate;optimize');
|
|
777
777
|
assertEqual(urn.getTag('op'), 'generate', 'Should parse valued tag');
|
|
778
778
|
assertEqual(urn.getTag('optimize'), '*', 'Should parse value-less tag');
|
|
779
|
-
assertEqual(urn.toString(), 'cap:
|
|
779
|
+
assertEqual(urn.toString(), 'cap:generate;optimize', 'Should serialize correctly');
|
|
780
780
|
}
|
|
781
781
|
|
|
782
782
|
// TEST557: Valueless tag is equivalent to explicit wildcard
|
|
@@ -790,10 +790,10 @@ function test557_valueless_tag_equivalence_to_wildcard() {
|
|
|
790
790
|
|
|
791
791
|
// TEST558: Valueless tag (wildcard) matches any value
|
|
792
792
|
function test558_valueless_tag_matching() {
|
|
793
|
-
const urn = TaggedUrn.fromString('cap:
|
|
794
|
-
const requestPdf = TaggedUrn.fromString('cap:
|
|
795
|
-
const requestDocx = TaggedUrn.fromString('cap:
|
|
796
|
-
const requestAny = TaggedUrn.fromString('cap:
|
|
793
|
+
const urn = TaggedUrn.fromString('cap:generate;ext');
|
|
794
|
+
const requestPdf = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
795
|
+
const requestDocx = TaggedUrn.fromString('cap:generate;ext=docx');
|
|
796
|
+
const requestAny = TaggedUrn.fromString('cap:generate;ext=anything');
|
|
797
797
|
|
|
798
798
|
assert(urn.conformsTo(requestPdf), 'Should match pdf');
|
|
799
799
|
assert(urn.conformsTo(requestDocx), 'Should match docx');
|
|
@@ -802,24 +802,24 @@ function test558_valueless_tag_matching() {
|
|
|
802
802
|
|
|
803
803
|
// TEST559: Pattern with valueless tag requires instance to have tag (any value)
|
|
804
804
|
function test559_valueless_tag_in_pattern() {
|
|
805
|
-
const pattern = TaggedUrn.fromString('cap:
|
|
806
|
-
const instancePdf = TaggedUrn.fromString('cap:
|
|
807
|
-
const instanceDocx = TaggedUrn.fromString('cap:
|
|
805
|
+
const pattern = TaggedUrn.fromString('cap:generate;ext');
|
|
806
|
+
const instancePdf = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
807
|
+
const instanceDocx = TaggedUrn.fromString('cap:generate;ext=docx');
|
|
808
808
|
const instanceMissing = TaggedUrn.fromString('cap:op=generate');
|
|
809
809
|
|
|
810
810
|
assert(instancePdf.conformsTo(pattern), 'Should match pdf instance');
|
|
811
811
|
assert(instanceDocx.conformsTo(pattern), 'Should match docx instance');
|
|
812
812
|
assert(!instanceMissing.conformsTo(pattern), 'Should NOT match instance without ext');
|
|
813
813
|
|
|
814
|
-
const patternOptional = TaggedUrn.fromString('cap:
|
|
814
|
+
const patternOptional = TaggedUrn.fromString('cap:generate;ext=?');
|
|
815
815
|
assert(instanceMissing.conformsTo(patternOptional), 'Instance should match pattern with ext=?');
|
|
816
816
|
}
|
|
817
817
|
|
|
818
818
|
// TEST560: Valueless tag contributes 2 points to specificity
|
|
819
819
|
function test560_valueless_tag_specificity() {
|
|
820
820
|
const urn1 = TaggedUrn.fromString('cap:op=generate');
|
|
821
|
-
const urn2 = TaggedUrn.fromString('cap:
|
|
822
|
-
const urn3 = TaggedUrn.fromString('cap:
|
|
821
|
+
const urn2 = TaggedUrn.fromString('cap:generate;optimize');
|
|
822
|
+
const urn3 = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
823
823
|
|
|
824
824
|
assertEqual(urn1.specificity(), 3, '1 exact = 3');
|
|
825
825
|
assertEqual(urn2.specificity(), 5, '1 exact + 1 * = 3 + 2 = 5');
|
|
@@ -828,7 +828,7 @@ function test560_valueless_tag_specificity() {
|
|
|
828
828
|
|
|
829
829
|
// TEST561: Valueless tags round-trip correctly (serialize as just key)
|
|
830
830
|
function test561_valueless_tag_roundtrip() {
|
|
831
|
-
const original = 'cap:ext=pdf;
|
|
831
|
+
const original = 'cap:ext=pdf;generate;optimize;secure';
|
|
832
832
|
const urn = TaggedUrn.fromString(original);
|
|
833
833
|
const serialized = urn.toString();
|
|
834
834
|
const reparsed = TaggedUrn.fromString(serialized);
|
|
@@ -861,9 +861,9 @@ function test563_empty_value_still_error() {
|
|
|
861
861
|
|
|
862
862
|
// TEST564: Valueless tags (wildcard) accept any specific value
|
|
863
863
|
function test564_valueless_tag_compatibility() {
|
|
864
|
-
const wildcard = TaggedUrn.fromString('cap:
|
|
865
|
-
const pdf = TaggedUrn.fromString('cap:
|
|
866
|
-
const docx = TaggedUrn.fromString('cap:
|
|
864
|
+
const wildcard = TaggedUrn.fromString('cap:generate;ext');
|
|
865
|
+
const pdf = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
866
|
+
const docx = TaggedUrn.fromString('cap:generate;ext=docx');
|
|
867
867
|
|
|
868
868
|
// Wildcard ext accepts both pdf and docx instances
|
|
869
869
|
assert(wildcard.accepts(pdf), 'Wildcard ext should accept pdf');
|
|
@@ -881,7 +881,7 @@ function test565_valueless_numeric_key_still_rejected() {
|
|
|
881
881
|
'Should reject numeric key'
|
|
882
882
|
);
|
|
883
883
|
assertThrows(
|
|
884
|
-
() => TaggedUrn.fromString('cap:
|
|
884
|
+
() => TaggedUrn.fromString('cap:generate;456'),
|
|
885
885
|
ErrorCodes.NUMERIC_KEY,
|
|
886
886
|
'Should reject numeric key'
|
|
887
887
|
);
|
|
@@ -1149,7 +1149,7 @@ function test577_specificity_with_special_values() {
|
|
|
1149
1149
|
|
|
1150
1150
|
// JS-only: Test op tag is used instead of deprecated action tag
|
|
1151
1151
|
function testJsOnly_op_tag_rename() {
|
|
1152
|
-
const cap = TaggedUrn.fromString('cap:
|
|
1152
|
+
const cap = TaggedUrn.fromString('cap:generate;format=json');
|
|
1153
1153
|
assertEqual(cap.getTag('op'), 'generate', 'Should have op tag');
|
|
1154
1154
|
assertEqual(cap.getTag('action'), undefined, 'Should not have action tag');
|
|
1155
1155
|
|
|
@@ -1166,7 +1166,7 @@ function testJsOnly_op_tag_rename() {
|
|
|
1166
1166
|
|
|
1167
1167
|
// Test conformsToStr convenience method
|
|
1168
1168
|
function testConformsToStr() {
|
|
1169
|
-
const urn = TaggedUrn.fromString('cap:
|
|
1169
|
+
const urn = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
1170
1170
|
assert(urn.conformsToStr('cap:op=generate'), 'Should match subset pattern string');
|
|
1171
1171
|
assert(!urn.conformsToStr('cap:op=extract'), 'Should not match conflicting pattern string');
|
|
1172
1172
|
}
|
|
@@ -1174,15 +1174,15 @@ function testConformsToStr() {
|
|
|
1174
1174
|
// Test acceptsStr convenience method
|
|
1175
1175
|
function testAcceptsStr() {
|
|
1176
1176
|
const pattern = TaggedUrn.fromString('cap:op=generate');
|
|
1177
|
-
assert(pattern.acceptsStr('cap:
|
|
1177
|
+
assert(pattern.acceptsStr('cap:generate;ext=pdf'), 'Should accept more specific instance string');
|
|
1178
1178
|
assert(!pattern.acceptsStr('cap:op=extract'), 'Should not accept conflicting instance string');
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
1181
|
// Test canonical static method
|
|
1182
1182
|
function testCanonical() {
|
|
1183
1183
|
assertEqual(
|
|
1184
|
-
TaggedUrn.canonical('cap:
|
|
1185
|
-
'cap:ext=pdf;
|
|
1184
|
+
TaggedUrn.canonical('cap:generate;ext=pdf;target=thumbnail'),
|
|
1185
|
+
'cap:ext=pdf;generate;target=thumbnail',
|
|
1186
1186
|
'Should return canonical form'
|
|
1187
1187
|
);
|
|
1188
1188
|
}
|
|
@@ -1190,7 +1190,7 @@ function testCanonical() {
|
|
|
1190
1190
|
// Test canonicalOption static method
|
|
1191
1191
|
function testCanonicalOption() {
|
|
1192
1192
|
assertEqual(
|
|
1193
|
-
TaggedUrn.canonicalOption('cap:
|
|
1193
|
+
TaggedUrn.canonicalOption('cap:generate;ext=pdf'),
|
|
1194
1194
|
'cap:ext=pdf;op=generate',
|
|
1195
1195
|
'Should return canonical form for valid input'
|
|
1196
1196
|
);
|
|
@@ -1345,7 +1345,7 @@ module.exports = { runTests };
|
|
|
1345
1345
|
|
|
1346
1346
|
// TEST578: Equivalent URNs with identical tag sets
|
|
1347
1347
|
(() => {
|
|
1348
|
-
const a = TaggedUrn.fromString('cap:
|
|
1348
|
+
const a = TaggedUrn.fromString('cap:generate;ext=pdf');
|
|
1349
1349
|
const b = TaggedUrn.fromString('cap:ext=pdf;op=generate');
|
|
1350
1350
|
assert(a.isEquivalent(b), 'TEST578: Equivalent should be true');
|
|
1351
1351
|
assert(b.isEquivalent(a), 'TEST578: Equivalent should be symmetric');
|
|
@@ -1377,8 +1377,8 @@ module.exports = { runTests };
|
|
|
1377
1377
|
|
|
1378
1378
|
// TEST582: Equivalent implies comparable
|
|
1379
1379
|
(() => {
|
|
1380
|
-
const a = TaggedUrn.fromString('cap:
|
|
1381
|
-
const b = TaggedUrn.fromString('cap:
|
|
1380
|
+
const a = TaggedUrn.fromString('cap:test;ext=pdf');
|
|
1381
|
+
const b = TaggedUrn.fromString('cap:test;ext=pdf');
|
|
1382
1382
|
assert(a.isEquivalent(b), 'TEST582: Should be equivalent');
|
|
1383
1383
|
assert(a.isComparable(b), 'TEST582: Equivalent implies comparable');
|
|
1384
1384
|
})();
|
|
@@ -1422,7 +1422,7 @@ module.exports = { runTests };
|
|
|
1422
1422
|
.soloTag('ext')
|
|
1423
1423
|
.soloTag('quality')
|
|
1424
1424
|
.build();
|
|
1425
|
-
assertEqual(urn.toString(), 'cap:ext;
|
|
1425
|
+
assertEqual(urn.toString(), 'cap:ext;convert;quality', 'TEST593: toString with wildcards');
|
|
1426
1426
|
assertEqual(urn.specificity(), 7, 'TEST593: specificity');
|
|
1427
1427
|
})();
|
|
1428
1428
|
|