capns 0.83.18915 → 0.85.19364
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/capns.js +60 -17
- package/capns.test.js +457 -66
- package/package.json +2 -2
package/capns.js
CHANGED
|
@@ -349,8 +349,10 @@ class CapUrn {
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
// Direction specs: TaggedUrn semantic matching via MediaUrn
|
|
352
|
-
// Check in_urn: cap's input spec (pattern) accepts request's input (instance)
|
|
353
|
-
|
|
352
|
+
// Check in_urn: cap's input spec (pattern) accepts request's input (instance).
|
|
353
|
+
// "media:" on the PATTERN side (this.inSpec) means "I accept any input" — skip check.
|
|
354
|
+
// "*" is also treated as wildcard. "media:" on the instance side still participates.
|
|
355
|
+
if (this.inSpec !== '*' && this.inSpec !== 'media:' && request.inSpec !== '*') {
|
|
354
356
|
const capIn = TaggedUrn.fromString(this.inSpec);
|
|
355
357
|
const requestIn = TaggedUrn.fromString(request.inSpec);
|
|
356
358
|
if (!capIn.accepts(requestIn)) {
|
|
@@ -358,8 +360,10 @@ class CapUrn {
|
|
|
358
360
|
}
|
|
359
361
|
}
|
|
360
362
|
|
|
361
|
-
// Check out_urn: cap's output (instance) conforms to request's output (pattern)
|
|
362
|
-
|
|
363
|
+
// Check out_urn: cap's output (instance) conforms to request's output (pattern).
|
|
364
|
+
// "media:" on the PATTERN side (this.outSpec) means "I accept any output" — skip check.
|
|
365
|
+
// "*" is also treated as wildcard. "media:" on the instance side still participates.
|
|
366
|
+
if (this.outSpec !== '*' && this.outSpec !== 'media:' && request.outSpec !== '*') {
|
|
363
367
|
const capOut = TaggedUrn.fromString(this.outSpec);
|
|
364
368
|
const requestOut = TaggedUrn.fromString(request.outSpec);
|
|
365
369
|
if (!capOut.conformsTo(requestOut)) {
|
|
@@ -728,18 +732,18 @@ const MEDIA_INTEGER_ARRAY = 'media:integer;textable;numeric;form=list';
|
|
|
728
732
|
const MEDIA_NUMBER_ARRAY = 'media:textable;numeric;form=list';
|
|
729
733
|
const MEDIA_BOOLEAN_ARRAY = 'media:bool;textable;form=list';
|
|
730
734
|
const MEDIA_OBJECT_ARRAY = 'media:form=list;textable';
|
|
731
|
-
const MEDIA_BINARY = 'media:
|
|
735
|
+
const MEDIA_BINARY = 'media:';
|
|
732
736
|
const MEDIA_VOID = 'media:void';
|
|
733
737
|
// Semantic content types
|
|
734
|
-
const MEDIA_PNG = 'media:image;png
|
|
735
|
-
const MEDIA_AUDIO = 'media:wav;audio
|
|
736
|
-
const MEDIA_VIDEO = 'media:video
|
|
738
|
+
const MEDIA_PNG = 'media:image;png';
|
|
739
|
+
const MEDIA_AUDIO = 'media:wav;audio';
|
|
740
|
+
const MEDIA_VIDEO = 'media:video';
|
|
737
741
|
// Semantic AI input types
|
|
738
|
-
const MEDIA_AUDIO_SPEECH = 'media:audio;wav;
|
|
739
|
-
const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;
|
|
742
|
+
const MEDIA_AUDIO_SPEECH = 'media:audio;wav;speech';
|
|
743
|
+
const MEDIA_IMAGE_THUMBNAIL = 'media:image;png;thumbnail';
|
|
740
744
|
// Document types (PRIMARY naming - type IS the format)
|
|
741
|
-
const MEDIA_PDF = 'media:pdf
|
|
742
|
-
const MEDIA_EPUB = 'media:epub
|
|
745
|
+
const MEDIA_PDF = 'media:pdf';
|
|
746
|
+
const MEDIA_EPUB = 'media:epub';
|
|
743
747
|
// Text format types (PRIMARY naming - type IS the format)
|
|
744
748
|
const MEDIA_MD = 'media:md;textable';
|
|
745
749
|
const MEDIA_TXT = 'media:txt;textable';
|
|
@@ -767,6 +771,12 @@ const MEDIA_PATH_OUTPUT = 'media:model-path;textable;form=map';
|
|
|
767
771
|
// Semantic output types - inference
|
|
768
772
|
const MEDIA_EMBEDDING_VECTOR = 'media:embedding-vector;textable;form=map';
|
|
769
773
|
const MEDIA_LLM_INFERENCE_OUTPUT = 'media:generated-text;textable;form=map';
|
|
774
|
+
// File path types
|
|
775
|
+
const MEDIA_FILE_PATH = 'media:file-path;textable;form=scalar';
|
|
776
|
+
const MEDIA_FILE_PATH_ARRAY = 'media:file-path;textable;form=list';
|
|
777
|
+
// Collection types
|
|
778
|
+
const MEDIA_COLLECTION = 'media:collection;textable;form=map';
|
|
779
|
+
const MEDIA_COLLECTION_LIST = 'media:collection;textable;form=list';
|
|
770
780
|
|
|
771
781
|
// =============================================================================
|
|
772
782
|
// STANDARD CAP URN CONSTANTS
|
|
@@ -815,7 +825,7 @@ class MediaUrn {
|
|
|
815
825
|
|
|
816
826
|
/**
|
|
817
827
|
* Parse a media URN string. Validates the prefix is 'media'.
|
|
818
|
-
* @param {string} str - The media URN string (e.g., "media:
|
|
828
|
+
* @param {string} str - The media URN string (e.g., "media:pdf")
|
|
819
829
|
* @returns {MediaUrn}
|
|
820
830
|
* @throws {MediaUrnError} If prefix is not 'media'
|
|
821
831
|
*/
|
|
@@ -824,8 +834,8 @@ class MediaUrn {
|
|
|
824
834
|
return new MediaUrn(urn);
|
|
825
835
|
}
|
|
826
836
|
|
|
827
|
-
/** @returns {boolean} True if the "
|
|
828
|
-
isBinary() { return this._urn.getTag('
|
|
837
|
+
/** @returns {boolean} True if the "textable" marker tag is NOT present (binary = not textable) */
|
|
838
|
+
isBinary() { return this._urn.getTag('textable') === undefined; }
|
|
829
839
|
|
|
830
840
|
/** @returns {boolean} True if form=map tag is present */
|
|
831
841
|
isMap() { return this._urn.hasTag('form', 'map'); }
|
|
@@ -848,6 +858,33 @@ class MediaUrn {
|
|
|
848
858
|
/** @returns {boolean} True if the "void" marker tag is present */
|
|
849
859
|
isVoid() { return this._urn.getTag('void') !== undefined; }
|
|
850
860
|
|
|
861
|
+
/** @returns {boolean} True if the "image" marker tag is present */
|
|
862
|
+
isImage() { return this._urn.getTag('image') !== undefined; }
|
|
863
|
+
|
|
864
|
+
/** @returns {boolean} True if the "audio" marker tag is present */
|
|
865
|
+
isAudio() { return this._urn.getTag('audio') !== undefined; }
|
|
866
|
+
|
|
867
|
+
/** @returns {boolean} True if the "video" marker tag is present */
|
|
868
|
+
isVideo() { return this._urn.getTag('video') !== undefined; }
|
|
869
|
+
|
|
870
|
+
/** @returns {boolean} True if the "numeric" marker tag is present */
|
|
871
|
+
isNumeric() { return this._urn.getTag('numeric') !== undefined; }
|
|
872
|
+
|
|
873
|
+
/** @returns {boolean} True if the "bool" marker tag is present */
|
|
874
|
+
isBool() { return this._urn.getTag('bool') !== undefined; }
|
|
875
|
+
|
|
876
|
+
/** @returns {boolean} True if "file-path" marker tag is present AND NOT form=list */
|
|
877
|
+
isFilePath() { return this._urn.getTag('file-path') !== undefined && !this.isList(); }
|
|
878
|
+
|
|
879
|
+
/** @returns {boolean} True if "file-path" marker tag is present AND form=list */
|
|
880
|
+
isFilePathArray() { return this._urn.getTag('file-path') !== undefined && this.isList(); }
|
|
881
|
+
|
|
882
|
+
/** @returns {boolean} True if "file-path" marker tag is present (single or array) */
|
|
883
|
+
isAnyFilePath() { return this.isFilePath() || this.isFilePathArray(); }
|
|
884
|
+
|
|
885
|
+
/** @returns {boolean} True if the "collection" marker tag is present */
|
|
886
|
+
isCollection() { return this._urn.getTag('collection') !== undefined; }
|
|
887
|
+
|
|
851
888
|
/**
|
|
852
889
|
* Check if this media URN conforms to another (pattern).
|
|
853
890
|
* @param {MediaUrn} pattern
|
|
@@ -1036,7 +1073,7 @@ class MediaSpec {
|
|
|
1036
1073
|
return this._parsedMediaUrn;
|
|
1037
1074
|
}
|
|
1038
1075
|
|
|
1039
|
-
/** @returns {boolean} True if binary (
|
|
1076
|
+
/** @returns {boolean} True if binary (textable marker tag absent) */
|
|
1040
1077
|
isBinary() {
|
|
1041
1078
|
const mu = this.parsedMediaUrn();
|
|
1042
1079
|
return mu ? mu.isBinary() : false;
|
|
@@ -1243,7 +1280,7 @@ function buildExtensionIndex(mediaSpecs) {
|
|
|
1243
1280
|
*
|
|
1244
1281
|
* @example
|
|
1245
1282
|
* const urns = mediaUrnsForExtension('pdf', mediaSpecs);
|
|
1246
|
-
* // May return ['media:pdf
|
|
1283
|
+
* // May return ['media:pdf']
|
|
1247
1284
|
*/
|
|
1248
1285
|
function mediaUrnsForExtension(extension, mediaSpecs) {
|
|
1249
1286
|
const index = buildExtensionIndex(mediaSpecs);
|
|
@@ -4123,6 +4160,12 @@ module.exports = {
|
|
|
4123
4160
|
// Semantic output types - inference
|
|
4124
4161
|
MEDIA_EMBEDDING_VECTOR,
|
|
4125
4162
|
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
4163
|
+
// File path types
|
|
4164
|
+
MEDIA_FILE_PATH,
|
|
4165
|
+
MEDIA_FILE_PATH_ARRAY,
|
|
4166
|
+
// Collection types
|
|
4167
|
+
MEDIA_COLLECTION,
|
|
4168
|
+
MEDIA_COLLECTION_LIST,
|
|
4126
4169
|
// Unified argument type
|
|
4127
4170
|
CapArgumentValue,
|
|
4128
4171
|
// Standard cap URN builders
|
package/capns.test.js
CHANGED
|
@@ -21,7 +21,11 @@ const {
|
|
|
21
21
|
MEDIA_PDF, MEDIA_EPUB, MEDIA_MD, MEDIA_TXT, MEDIA_RST, MEDIA_LOG,
|
|
22
22
|
MEDIA_HTML, MEDIA_XML, MEDIA_JSON, MEDIA_YAML, MEDIA_JSON_SCHEMA,
|
|
23
23
|
MEDIA_MODEL_SPEC, MEDIA_AVAILABILITY_OUTPUT, MEDIA_PATH_OUTPUT,
|
|
24
|
-
MEDIA_LLM_INFERENCE_OUTPUT
|
|
24
|
+
MEDIA_LLM_INFERENCE_OUTPUT,
|
|
25
|
+
MEDIA_FILE_PATH, MEDIA_FILE_PATH_ARRAY,
|
|
26
|
+
MEDIA_COLLECTION, MEDIA_COLLECTION_LIST,
|
|
27
|
+
MEDIA_DECISION, MEDIA_DECISION_ARRAY,
|
|
28
|
+
MEDIA_AUDIO_SPEECH, MEDIA_IMAGE_THUMBNAIL
|
|
25
29
|
} = require('./capns.js');
|
|
26
30
|
|
|
27
31
|
// ============================================================================
|
|
@@ -437,12 +441,12 @@ function test025_bestMatch() {
|
|
|
437
441
|
// TEST026: merge combines tags, subset keeps only specified
|
|
438
442
|
function test026_mergeAndSubset() {
|
|
439
443
|
const cap1 = CapUrn.fromString(testUrn('op=generate'));
|
|
440
|
-
const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:
|
|
444
|
+
const cap2 = CapUrn.fromString('cap:in="media:textable;form=scalar";ext=pdf;format=binary;out="media:"');
|
|
441
445
|
|
|
442
446
|
// Merge (other takes precedence)
|
|
443
447
|
const merged = cap1.merge(cap2);
|
|
444
448
|
assertEqual(merged.getInSpec(), 'media:textable;form=scalar', 'Merge should take inSpec from other');
|
|
445
|
-
assertEqual(merged.getOutSpec(), 'media:
|
|
449
|
+
assertEqual(merged.getOutSpec(), 'media:', 'Merge should take outSpec from other');
|
|
446
450
|
assertEqual(merged.getTag('op'), 'generate', 'Merge should keep original tags');
|
|
447
451
|
assertEqual(merged.getTag('ext'), 'pdf', 'Merge should add other tags');
|
|
448
452
|
|
|
@@ -643,8 +647,8 @@ function test046_matchingSemanticsFallbackPattern() {
|
|
|
643
647
|
|
|
644
648
|
// TEST047: Thumbnail with void input matches specific ext request
|
|
645
649
|
function test047_matchingSemanticsThumbnailVoidInput() {
|
|
646
|
-
const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;
|
|
647
|
-
const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image
|
|
650
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=generate_thumbnail;out="media:image;png;thumbnail"');
|
|
651
|
+
const request = CapUrn.fromString('cap:ext=pdf;in="media:void";op=generate_thumbnail;out="media:image"');
|
|
648
652
|
assert(cap.accepts(request), 'Void input cap should accept request; cap output conforms to less-specific request output');
|
|
649
653
|
}
|
|
650
654
|
|
|
@@ -662,7 +666,7 @@ function test049_matchingSemanticsCrossDimension() {
|
|
|
662
666
|
assert(cap.accepts(request), 'Independent tags should not block matching');
|
|
663
667
|
}
|
|
664
668
|
|
|
665
|
-
// TEST050: media:string vs media:
|
|
669
|
+
// TEST050: media:string vs media: (wildcard) -> no match
|
|
666
670
|
function test050_matchingSemanticsDirectionMismatch() {
|
|
667
671
|
const cap = CapUrn.fromString(
|
|
668
672
|
`cap:in="${MEDIA_STRING}";op=generate;out="${MEDIA_OBJECT}"`
|
|
@@ -673,79 +677,79 @@ function test050_matchingSemanticsDirectionMismatch() {
|
|
|
673
677
|
assert(!cap.accepts(request), 'Incompatible direction types should not match');
|
|
674
678
|
}
|
|
675
679
|
|
|
676
|
-
// TEST051: Generic media:
|
|
677
|
-
// specific pdf cap rejects generic
|
|
680
|
+
// TEST051: Generic media: (wildcard) provider accepts media:pdf request;
|
|
681
|
+
// specific pdf cap rejects generic wildcard request;
|
|
678
682
|
// output direction: more specific output satisfies less specific request
|
|
679
683
|
function test051_directionSemanticMatching() {
|
|
680
|
-
// Generic
|
|
684
|
+
// Generic wildcard cap accepts specific pdf request
|
|
681
685
|
const genericCap = CapUrn.fromString(
|
|
682
|
-
'cap:in="media:
|
|
686
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
683
687
|
);
|
|
684
688
|
const pdfRequest = CapUrn.fromString(
|
|
685
|
-
'cap:in="media:pdf
|
|
689
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
686
690
|
);
|
|
687
|
-
assert(genericCap.accepts(pdfRequest), 'Generic
|
|
691
|
+
assert(genericCap.accepts(pdfRequest), 'Generic wildcard cap must accept pdf request');
|
|
688
692
|
|
|
689
|
-
// Also accepts epub
|
|
693
|
+
// Also accepts epub
|
|
690
694
|
const epubRequest = CapUrn.fromString(
|
|
691
|
-
'cap:in="media:epub
|
|
695
|
+
'cap:in="media:epub";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
692
696
|
);
|
|
693
|
-
assert(genericCap.accepts(epubRequest), 'Generic
|
|
697
|
+
assert(genericCap.accepts(epubRequest), 'Generic wildcard cap must accept epub request');
|
|
694
698
|
|
|
695
699
|
// Reverse: specific pdf cap does NOT accept generic bytes request
|
|
696
700
|
const pdfCap = CapUrn.fromString(
|
|
697
|
-
'cap:in="media:pdf
|
|
701
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
698
702
|
);
|
|
699
703
|
const genericRequest = CapUrn.fromString(
|
|
700
|
-
'cap:in="media:
|
|
704
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
701
705
|
);
|
|
702
|
-
assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic
|
|
706
|
+
assert(!pdfCap.accepts(genericRequest), 'Specific pdf cap must NOT accept generic wildcard request');
|
|
703
707
|
|
|
704
708
|
// PDF cap does NOT accept epub request
|
|
705
709
|
assert(!pdfCap.accepts(epubRequest), 'PDF cap must NOT accept epub request');
|
|
706
710
|
|
|
707
711
|
// Output direction: cap producing more specific output satisfies less specific request
|
|
708
712
|
const specificOutCap = CapUrn.fromString(
|
|
709
|
-
'cap:in="media:
|
|
713
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
710
714
|
);
|
|
711
715
|
const genericOutRequest = CapUrn.fromString(
|
|
712
|
-
'cap:in="media:
|
|
716
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image"'
|
|
713
717
|
);
|
|
714
718
|
assert(specificOutCap.accepts(genericOutRequest),
|
|
715
|
-
'Cap producing image;png;
|
|
719
|
+
'Cap producing image;png;thumbnail must satisfy request for image');
|
|
716
720
|
|
|
717
721
|
// Reverse output: generic output cap does NOT satisfy specific output request
|
|
718
722
|
const genericOutCap = CapUrn.fromString(
|
|
719
|
-
'cap:in="media:
|
|
723
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image"'
|
|
720
724
|
);
|
|
721
725
|
const specificOutRequest = CapUrn.fromString(
|
|
722
|
-
'cap:in="media:
|
|
726
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
723
727
|
);
|
|
724
728
|
assert(!genericOutCap.accepts(specificOutRequest),
|
|
725
729
|
'Generic output cap must NOT satisfy specific output request');
|
|
726
730
|
}
|
|
727
731
|
|
|
728
|
-
// TEST052: Specificity: media:
|
|
729
|
-
// pdf;
|
|
732
|
+
// TEST052: Specificity: media:(0 tags) + image;png;thumbnail(3 tags) + op(1) = 4;
|
|
733
|
+
// pdf(1) + image;png;thumbnail(3) + op(1) = 5; CapMatcher prefers higher
|
|
730
734
|
function test052_directionSemanticSpecificity() {
|
|
731
735
|
const genericCap = CapUrn.fromString(
|
|
732
|
-
'cap:in="media:
|
|
736
|
+
'cap:in="media:";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
733
737
|
);
|
|
734
738
|
const specificCap = CapUrn.fromString(
|
|
735
|
-
'cap:in="media:pdf
|
|
739
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
736
740
|
);
|
|
737
741
|
|
|
738
|
-
assertEqual(genericCap.specificity(),
|
|
739
|
-
assertEqual(specificCap.specificity(),
|
|
740
|
-
assert(specificCap.specificity() > genericCap.specificity(), 'pdf
|
|
742
|
+
assertEqual(genericCap.specificity(), 4, 'media:(0) + image;png;thumbnail(3) + op(1) = 4');
|
|
743
|
+
assertEqual(specificCap.specificity(), 5, 'pdf(1) + image;png;thumbnail(3) + op(1) = 5');
|
|
744
|
+
assert(specificCap.specificity() > genericCap.specificity(), 'pdf should be more specific');
|
|
741
745
|
|
|
742
746
|
// CapMatcher should prefer more specific
|
|
743
747
|
const pdfRequest = CapUrn.fromString(
|
|
744
|
-
'cap:in="media:pdf
|
|
748
|
+
'cap:in="media:pdf";op=generate_thumbnail;out="media:image;png;thumbnail"'
|
|
745
749
|
);
|
|
746
750
|
const best = CapMatcher.findBestMatch([genericCap, specificCap], pdfRequest);
|
|
747
751
|
assert(best !== null, 'Should find a match');
|
|
748
|
-
assertEqual(best.getInSpec(), 'media:pdf
|
|
752
|
+
assertEqual(best.getInSpec(), 'media:pdf', 'Should prefer more specific pdf cap');
|
|
749
753
|
}
|
|
750
754
|
|
|
751
755
|
// ============================================================================
|
|
@@ -807,13 +811,20 @@ function test060_wrongPrefixFails() {
|
|
|
807
811
|
);
|
|
808
812
|
}
|
|
809
813
|
|
|
810
|
-
// TEST061: isBinary true
|
|
814
|
+
// TEST061: isBinary true when textable tag is absent (binary = not textable)
|
|
811
815
|
function test061_isBinary() {
|
|
812
|
-
|
|
816
|
+
// Binary types: no textable tag
|
|
817
|
+
assert(MediaUrn.fromString(MEDIA_BINARY).isBinary(), 'MEDIA_BINARY (media:) should be binary');
|
|
813
818
|
assert(MediaUrn.fromString(MEDIA_PNG).isBinary(), 'MEDIA_PNG should be binary');
|
|
814
819
|
assert(MediaUrn.fromString(MEDIA_PDF).isBinary(), 'MEDIA_PDF should be binary');
|
|
815
|
-
assert(MediaUrn.fromString(
|
|
820
|
+
assert(MediaUrn.fromString('media:video').isBinary(), 'media:video should be binary');
|
|
821
|
+
assert(MediaUrn.fromString('media:epub').isBinary(), 'media:epub should be binary');
|
|
822
|
+
// Textable types: is_binary is false
|
|
816
823
|
assert(!MediaUrn.fromString('media:textable').isBinary(), 'media:textable should not be binary');
|
|
824
|
+
assert(!MediaUrn.fromString('media:textable;form=map').isBinary(), 'textable map should not be binary');
|
|
825
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isBinary(), 'MEDIA_STRING should not be binary');
|
|
826
|
+
assert(!MediaUrn.fromString(MEDIA_JSON).isBinary(), 'MEDIA_JSON should not be binary');
|
|
827
|
+
assert(!MediaUrn.fromString(MEDIA_MD).isBinary(), 'MEDIA_MD should not be binary');
|
|
817
828
|
}
|
|
818
829
|
|
|
819
830
|
// TEST062: isMap true for MEDIA_OBJECT (form=map); false for MEDIA_STRING (form=scalar), MEDIA_STRING_ARRAY (form=list)
|
|
@@ -907,7 +918,7 @@ function test072_constantsParse() {
|
|
|
907
918
|
|
|
908
919
|
// TEST073: N/A for JS (Rust has binary_media_urn_for_ext/text_media_urn_for_ext)
|
|
909
920
|
|
|
910
|
-
// TEST074: MEDIA_PDF (media:pdf
|
|
921
|
+
// TEST074: MEDIA_PDF (media:pdf) conformsTo media:pdf; MEDIA_MD conformsTo media:md; same URNs conform
|
|
911
922
|
function test074_mediaUrnMatching() {
|
|
912
923
|
const pdfUrn = MediaUrn.fromString(MEDIA_PDF);
|
|
913
924
|
const pdfPattern = MediaUrn.fromString('media:pdf');
|
|
@@ -934,11 +945,11 @@ function test075_accepts() {
|
|
|
934
945
|
|
|
935
946
|
// TEST076: More tags = higher specificity
|
|
936
947
|
function test076_specificity() {
|
|
937
|
-
const s1 = MediaUrn.fromString('media:
|
|
938
|
-
const s2 = MediaUrn.fromString('media:pdf
|
|
939
|
-
const s3 = MediaUrn.fromString('media:image;png;
|
|
940
|
-
assert(s2.specificity() > s1.specificity(), 'pdf
|
|
941
|
-
assert(s3.specificity() > s2.specificity(), 'image;png;
|
|
948
|
+
const s1 = MediaUrn.fromString('media:');
|
|
949
|
+
const s2 = MediaUrn.fromString('media:pdf');
|
|
950
|
+
const s3 = MediaUrn.fromString('media:image;png;thumbnail');
|
|
951
|
+
assert(s2.specificity() > s1.specificity(), 'pdf should be more specific than wildcard');
|
|
952
|
+
assert(s3.specificity() > s2.specificity(), 'image;png;thumbnail should be more specific than pdf');
|
|
942
953
|
}
|
|
943
954
|
|
|
944
955
|
// TEST077: N/A for JS (Rust serde) - but we test JSON.stringify round-trip
|
|
@@ -1011,7 +1022,7 @@ function test093_resolveUnresolvableFailsHard() {
|
|
|
1011
1022
|
// TEST097: N/A for JS (Rust validation function)
|
|
1012
1023
|
// TEST098: N/A for JS
|
|
1013
1024
|
|
|
1014
|
-
// TEST099: MediaSpec with media:
|
|
1025
|
+
// TEST099: MediaSpec with media: (no textable tag) -> isBinary() true
|
|
1015
1026
|
function test099_resolvedIsBinary() {
|
|
1016
1027
|
const spec = new MediaSpec('application/octet-stream', null, null, 'Binary', null, MEDIA_BINARY);
|
|
1017
1028
|
assert(spec.isBinary(), 'Resolved binary spec should be binary');
|
|
@@ -1093,13 +1104,13 @@ function test106_metadataWithValidation() {
|
|
|
1093
1104
|
function test107_extensionsPropagation() {
|
|
1094
1105
|
const mediaSpecs = [
|
|
1095
1106
|
{
|
|
1096
|
-
urn: 'media:pdf
|
|
1107
|
+
urn: 'media:pdf',
|
|
1097
1108
|
media_type: 'application/pdf',
|
|
1098
1109
|
title: 'PDF Document',
|
|
1099
1110
|
extensions: ['pdf']
|
|
1100
1111
|
}
|
|
1101
1112
|
];
|
|
1102
|
-
const resolved = resolveMediaUrn('media:pdf
|
|
1113
|
+
const resolved = resolveMediaUrn('media:pdf', mediaSpecs);
|
|
1103
1114
|
assert(Array.isArray(resolved.extensions), 'Extensions should be an array');
|
|
1104
1115
|
assertEqual(resolved.extensions.length, 1, 'Should have one extension');
|
|
1105
1116
|
assertEqual(resolved.extensions[0], 'pdf', 'Should have pdf extension');
|
|
@@ -1108,7 +1119,7 @@ function test107_extensionsPropagation() {
|
|
|
1108
1119
|
// TEST108: N/A for JS (Rust serde) - but we test MediaSpec with extensions
|
|
1109
1120
|
function test108_extensionsSerialization() {
|
|
1110
1121
|
// Test that MediaSpec can hold extensions correctly
|
|
1111
|
-
const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf
|
|
1122
|
+
const spec = new MediaSpec('application/pdf', null, null, 'PDF', null, 'media:pdf', null, null, ['pdf']);
|
|
1112
1123
|
assert(Array.isArray(spec.extensions), 'Extensions should be array');
|
|
1113
1124
|
assertEqual(spec.extensions[0], 'pdf', 'Should have pdf extension');
|
|
1114
1125
|
}
|
|
@@ -1136,13 +1147,13 @@ function test109_extensionsWithMetadataAndValidation() {
|
|
|
1136
1147
|
function test110_multipleExtensions() {
|
|
1137
1148
|
const mediaSpecs = [
|
|
1138
1149
|
{
|
|
1139
|
-
urn: 'media:image;jpeg
|
|
1150
|
+
urn: 'media:image;jpeg',
|
|
1140
1151
|
media_type: 'image/jpeg',
|
|
1141
1152
|
title: 'JPEG Image',
|
|
1142
1153
|
extensions: ['jpg', 'jpeg']
|
|
1143
1154
|
}
|
|
1144
1155
|
];
|
|
1145
|
-
const resolved = resolveMediaUrn('media:image;jpeg
|
|
1156
|
+
const resolved = resolveMediaUrn('media:image;jpeg', mediaSpecs);
|
|
1146
1157
|
assertEqual(resolved.extensions.length, 2, 'Should have two extensions');
|
|
1147
1158
|
assertEqual(resolved.extensions[0], 'jpg', 'First extension should be jpg');
|
|
1148
1159
|
assertEqual(resolved.extensions[1], 'jpeg', 'Second extension should be jpeg');
|
|
@@ -1520,7 +1531,7 @@ function test157_stdinSourceFromFileReference() {
|
|
|
1520
1531
|
const trackedFileId = 'tracked-file-123';
|
|
1521
1532
|
const originalPath = '/path/to/original.pdf';
|
|
1522
1533
|
const securityBookmark = new Uint8Array([0x62, 0x6f, 0x6f, 0x6b]);
|
|
1523
|
-
const mediaUrn = 'media:pdf
|
|
1534
|
+
const mediaUrn = 'media:pdf';
|
|
1524
1535
|
|
|
1525
1536
|
const source = StdinSource.fromFileReference(trackedFileId, originalPath, securityBookmark, mediaUrn);
|
|
1526
1537
|
assert(source !== null, 'Should create source');
|
|
@@ -1576,7 +1587,7 @@ function test276_capArgumentValueAsStrValid() {
|
|
|
1576
1587
|
|
|
1577
1588
|
// TEST277: CapArgumentValue.valueAsStr fails for non-UTF-8 binary data
|
|
1578
1589
|
function test277_capArgumentValueAsStrInvalidUtf8() {
|
|
1579
|
-
const arg = new CapArgumentValue('media:pdf
|
|
1590
|
+
const arg = new CapArgumentValue('media:pdf', new Uint8Array([0xFF, 0xFE, 0x80]));
|
|
1580
1591
|
let threw = false;
|
|
1581
1592
|
try {
|
|
1582
1593
|
arg.valueAsStr();
|
|
@@ -1607,7 +1618,7 @@ function test283_capArgumentValueLargeBinary() {
|
|
|
1607
1618
|
for (let i = 0; i < 10000; i++) {
|
|
1608
1619
|
data[i] = i % 256;
|
|
1609
1620
|
}
|
|
1610
|
-
const arg = new CapArgumentValue('media:pdf
|
|
1621
|
+
const arg = new CapArgumentValue('media:pdf', data);
|
|
1611
1622
|
assertEqual(arg.value.length, 10000, 'large binary must preserve all bytes');
|
|
1612
1623
|
assertEqual(arg.value[0], 0, 'first byte check');
|
|
1613
1624
|
assertEqual(arg.value[255], 255, 'byte 255 check');
|
|
@@ -1625,7 +1636,7 @@ function test304_mediaAvailabilityOutputConstant() {
|
|
|
1625
1636
|
const urn = TaggedUrn.fromString(MEDIA_AVAILABILITY_OUTPUT);
|
|
1626
1637
|
assert(urn.getTag('textable') !== undefined, 'model-availability must be textable');
|
|
1627
1638
|
assertEqual(urn.getTag('form'), 'map', 'model-availability must be form=map');
|
|
1628
|
-
assert(urn.getTag('
|
|
1639
|
+
assert(urn.getTag('textable') !== undefined, 'model-availability must not be binary (has textable)');
|
|
1629
1640
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1630
1641
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1631
1642
|
}
|
|
@@ -1635,7 +1646,7 @@ function test305_mediaPathOutputConstant() {
|
|
|
1635
1646
|
const urn = TaggedUrn.fromString(MEDIA_PATH_OUTPUT);
|
|
1636
1647
|
assert(urn.getTag('textable') !== undefined, 'model-path must be textable');
|
|
1637
1648
|
assertEqual(urn.getTag('form'), 'map', 'model-path must be form=map');
|
|
1638
|
-
assert(urn.getTag('
|
|
1649
|
+
assert(urn.getTag('textable') !== undefined, 'model-path must not be binary (has textable)');
|
|
1639
1650
|
const reparsed = TaggedUrn.fromString(urn.toString());
|
|
1640
1651
|
assert(urn.conformsTo(reparsed), 'roundtrip must match original');
|
|
1641
1652
|
}
|
|
@@ -1727,8 +1738,8 @@ function test312_allUrnBuildersProduceValidUrns() {
|
|
|
1727
1738
|
|
|
1728
1739
|
function testJS_buildExtensionIndex() {
|
|
1729
1740
|
const mediaSpecs = [
|
|
1730
|
-
{ urn: 'media:pdf
|
|
1731
|
-
{ urn: 'media:image;jpeg
|
|
1741
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1742
|
+
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] },
|
|
1732
1743
|
{ urn: 'media:json;textable', media_type: 'application/json', extensions: ['json'] }
|
|
1733
1744
|
];
|
|
1734
1745
|
const index = buildExtensionIndex(mediaSpecs);
|
|
@@ -1738,12 +1749,12 @@ function testJS_buildExtensionIndex() {
|
|
|
1738
1749
|
assert(index.has('jpg'), 'Should have jpg');
|
|
1739
1750
|
assert(index.has('jpeg'), 'Should have jpeg');
|
|
1740
1751
|
assert(index.has('json'), 'Should have json');
|
|
1741
|
-
assertEqual(index.get('pdf')[0], 'media:pdf
|
|
1752
|
+
assertEqual(index.get('pdf')[0], 'media:pdf', 'pdf should map correctly');
|
|
1742
1753
|
}
|
|
1743
1754
|
|
|
1744
1755
|
function testJS_mediaUrnsForExtension() {
|
|
1745
1756
|
const mediaSpecs = [
|
|
1746
|
-
{ urn: 'media:pdf
|
|
1757
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1747
1758
|
{ urn: 'media:json;textable;form=map', media_type: 'application/json', extensions: ['json'] },
|
|
1748
1759
|
{ urn: 'media:json;textable;form=list', media_type: 'application/json', extensions: ['json'] }
|
|
1749
1760
|
];
|
|
@@ -1771,8 +1782,8 @@ function testJS_mediaUrnsForExtension() {
|
|
|
1771
1782
|
|
|
1772
1783
|
function testJS_getExtensionMappings() {
|
|
1773
1784
|
const mediaSpecs = [
|
|
1774
|
-
{ urn: 'media:pdf
|
|
1775
|
-
{ urn: 'media:image;jpeg
|
|
1785
|
+
{ urn: 'media:pdf', media_type: 'application/pdf', extensions: ['pdf'] },
|
|
1786
|
+
{ urn: 'media:image;jpeg', media_type: 'image/jpeg', extensions: ['jpg', 'jpeg'] }
|
|
1776
1787
|
];
|
|
1777
1788
|
const mappings = getExtensionMappings(mediaSpecs);
|
|
1778
1789
|
assert(Array.isArray(mappings), 'Should return an array');
|
|
@@ -1881,7 +1892,7 @@ function testJS_binaryArgPassedToExecuteCap() {
|
|
|
1881
1892
|
const cube = new CapBlock();
|
|
1882
1893
|
cube.addRegistry('test', registry);
|
|
1883
1894
|
|
|
1884
|
-
const binaryArg = new CapArgumentValue('media:pdf
|
|
1895
|
+
const binaryArg = new CapArgumentValue('media:pdf', new Uint8Array([0x89, 0x50, 0x4E, 0x47]));
|
|
1885
1896
|
const { compositeHost } = cube.can('cap:in="media:void";op=test;out="media:string"');
|
|
1886
1897
|
|
|
1887
1898
|
return compositeHost.executeCap(
|
|
@@ -1889,7 +1900,7 @@ function testJS_binaryArgPassedToExecuteCap() {
|
|
|
1889
1900
|
[binaryArg]
|
|
1890
1901
|
).then(() => {
|
|
1891
1902
|
assert(receivedArgs !== null, 'Should receive arguments');
|
|
1892
|
-
assertEqual(receivedArgs[0].mediaUrn, 'media:pdf
|
|
1903
|
+
assertEqual(receivedArgs[0].mediaUrn, 'media:pdf', 'Correct mediaUrn');
|
|
1893
1904
|
assertEqual(receivedArgs[0].value[0], 0x89, 'First byte check');
|
|
1894
1905
|
assertEqual(receivedArgs[0].value.length, 4, 'Correct data length');
|
|
1895
1906
|
});
|
|
@@ -1926,12 +1937,12 @@ const sampleRegistry = {
|
|
|
1926
1937
|
tags: ['pdf', 'extractor'],
|
|
1927
1938
|
caps: [
|
|
1928
1939
|
{
|
|
1929
|
-
urn: 'cap:in="media:pdf
|
|
1940
|
+
urn: 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"',
|
|
1930
1941
|
title: 'Disbind PDF',
|
|
1931
1942
|
description: 'Extract pages'
|
|
1932
1943
|
},
|
|
1933
1944
|
{
|
|
1934
|
-
urn: 'cap:in="media:pdf
|
|
1945
|
+
urn: 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"',
|
|
1935
1946
|
title: 'Extract Metadata',
|
|
1936
1947
|
description: 'Get PDF metadata'
|
|
1937
1948
|
}
|
|
@@ -2139,13 +2150,13 @@ function test328_pluginRepoServerGetByCategory() {
|
|
|
2139
2150
|
function test329_pluginRepoServerGetByCap() {
|
|
2140
2151
|
const server = new PluginRepoServer(sampleRegistry);
|
|
2141
2152
|
|
|
2142
|
-
const disbindCap = 'cap:in="media:pdf
|
|
2153
|
+
const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2143
2154
|
const plugins = server.getPluginsByCap(disbindCap);
|
|
2144
2155
|
|
|
2145
2156
|
assert(plugins.length === 1, 'Should find 1 plugin with this cap');
|
|
2146
2157
|
assert(plugins[0].id === 'pdfcartridge', 'Should be pdfcartridge');
|
|
2147
2158
|
|
|
2148
|
-
const metadataCap = 'cap:in="media:pdf
|
|
2159
|
+
const metadataCap = 'cap:in="media:pdf";op=extract_metadata;out="media:file-metadata;textable;form=map"';
|
|
2149
2160
|
const metadataPlugins = server.getPluginsByCap(metadataCap);
|
|
2150
2161
|
assert(metadataPlugins.length === 1, 'Should find metadata cap');
|
|
2151
2162
|
}
|
|
@@ -2172,7 +2183,7 @@ function test331_pluginRepoClientGetSuggestions() {
|
|
|
2172
2183
|
|
|
2173
2184
|
client.updateCache('https://example.com/api/plugins', plugins);
|
|
2174
2185
|
|
|
2175
|
-
const disbindCap = 'cap:in="media:pdf
|
|
2186
|
+
const disbindCap = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2176
2187
|
const suggestions = client.getSuggestionsForCap(disbindCap);
|
|
2177
2188
|
|
|
2178
2189
|
assert(suggestions.length === 1, 'Should find 1 suggestion');
|
|
@@ -2250,7 +2261,7 @@ function test335_pluginRepoServerClientIntegration() {
|
|
|
2250
2261
|
assert(plugin.hasBinary(), 'Plugin should have binary');
|
|
2251
2262
|
|
|
2252
2263
|
// Client can get suggestions
|
|
2253
|
-
const capUrn = 'cap:in="media:pdf
|
|
2264
|
+
const capUrn = 'cap:in="media:pdf";op=disbind;out="media:disbound-page;textable;form=list"';
|
|
2254
2265
|
const suggestions = client.getSuggestionsForCap(capUrn);
|
|
2255
2266
|
assert(suggestions.length === 1, 'Should get suggestions');
|
|
2256
2267
|
assert(suggestions[0].pluginId === 'pdfcartridge', 'Should suggest correct plugin');
|
|
@@ -2261,6 +2272,344 @@ function test335_pluginRepoServerClientIntegration() {
|
|
|
2261
2272
|
assert(searchResults[0].id === plugin.id, 'Search and client should agree');
|
|
2262
2273
|
}
|
|
2263
2274
|
|
|
2275
|
+
// ============================================================================
|
|
2276
|
+
// media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
|
|
2277
|
+
// ============================================================================
|
|
2278
|
+
|
|
2279
|
+
// TEST546: isImage returns true only when image marker tag is present
|
|
2280
|
+
function test546_isImage() {
|
|
2281
|
+
assert(MediaUrn.fromString(MEDIA_PNG).isImage(), 'MEDIA_PNG should be image');
|
|
2282
|
+
assert(MediaUrn.fromString(MEDIA_IMAGE_THUMBNAIL).isImage(), 'MEDIA_IMAGE_THUMBNAIL should be image');
|
|
2283
|
+
assert(MediaUrn.fromString('media:image;jpg').isImage(), 'media:image;jpg should be image');
|
|
2284
|
+
// Non-image types
|
|
2285
|
+
assert(!MediaUrn.fromString(MEDIA_PDF).isImage(), 'MEDIA_PDF should not be image');
|
|
2286
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isImage(), 'MEDIA_STRING should not be image');
|
|
2287
|
+
assert(!MediaUrn.fromString(MEDIA_AUDIO).isImage(), 'MEDIA_AUDIO should not be image');
|
|
2288
|
+
assert(!MediaUrn.fromString(MEDIA_VIDEO).isImage(), 'MEDIA_VIDEO should not be image');
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// TEST547: isAudio returns true only when audio marker tag is present
|
|
2292
|
+
function test547_isAudio() {
|
|
2293
|
+
assert(MediaUrn.fromString(MEDIA_AUDIO).isAudio(), 'MEDIA_AUDIO should be audio');
|
|
2294
|
+
assert(MediaUrn.fromString(MEDIA_AUDIO_SPEECH).isAudio(), 'MEDIA_AUDIO_SPEECH should be audio');
|
|
2295
|
+
assert(MediaUrn.fromString('media:audio;mp3').isAudio(), 'media:audio;mp3 should be audio');
|
|
2296
|
+
// Non-audio types
|
|
2297
|
+
assert(!MediaUrn.fromString(MEDIA_VIDEO).isAudio(), 'MEDIA_VIDEO should not be audio');
|
|
2298
|
+
assert(!MediaUrn.fromString(MEDIA_PNG).isAudio(), 'MEDIA_PNG should not be audio');
|
|
2299
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isAudio(), 'MEDIA_STRING should not be audio');
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
// TEST548: isVideo returns true only when video marker tag is present
|
|
2303
|
+
function test548_isVideo() {
|
|
2304
|
+
assert(MediaUrn.fromString(MEDIA_VIDEO).isVideo(), 'MEDIA_VIDEO should be video');
|
|
2305
|
+
assert(MediaUrn.fromString('media:video;mp4').isVideo(), 'media:video;mp4 should be video');
|
|
2306
|
+
// Non-video types
|
|
2307
|
+
assert(!MediaUrn.fromString(MEDIA_AUDIO).isVideo(), 'MEDIA_AUDIO should not be video');
|
|
2308
|
+
assert(!MediaUrn.fromString(MEDIA_PNG).isVideo(), 'MEDIA_PNG should not be video');
|
|
2309
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isVideo(), 'MEDIA_STRING should not be video');
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
// TEST549: isNumeric returns true only when numeric marker tag is present
|
|
2313
|
+
function test549_isNumeric() {
|
|
2314
|
+
assert(MediaUrn.fromString(MEDIA_INTEGER).isNumeric(), 'MEDIA_INTEGER should be numeric');
|
|
2315
|
+
assert(MediaUrn.fromString(MEDIA_NUMBER).isNumeric(), 'MEDIA_NUMBER should be numeric');
|
|
2316
|
+
assert(MediaUrn.fromString(MEDIA_INTEGER_ARRAY).isNumeric(), 'MEDIA_INTEGER_ARRAY should be numeric');
|
|
2317
|
+
assert(MediaUrn.fromString(MEDIA_NUMBER_ARRAY).isNumeric(), 'MEDIA_NUMBER_ARRAY should be numeric');
|
|
2318
|
+
// Non-numeric types
|
|
2319
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isNumeric(), 'MEDIA_STRING should not be numeric');
|
|
2320
|
+
assert(!MediaUrn.fromString(MEDIA_BOOLEAN).isNumeric(), 'MEDIA_BOOLEAN should not be numeric');
|
|
2321
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isNumeric(), 'MEDIA_BINARY should not be numeric');
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
// TEST550: isBool returns true only when bool marker tag is present
|
|
2325
|
+
function test550_isBool() {
|
|
2326
|
+
assert(MediaUrn.fromString(MEDIA_BOOLEAN).isBool(), 'MEDIA_BOOLEAN should be bool');
|
|
2327
|
+
assert(MediaUrn.fromString(MEDIA_BOOLEAN_ARRAY).isBool(), 'MEDIA_BOOLEAN_ARRAY should be bool');
|
|
2328
|
+
assert(MediaUrn.fromString(MEDIA_DECISION).isBool(), 'MEDIA_DECISION should be bool');
|
|
2329
|
+
assert(MediaUrn.fromString(MEDIA_DECISION_ARRAY).isBool(), 'MEDIA_DECISION_ARRAY should be bool');
|
|
2330
|
+
// Non-bool types
|
|
2331
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isBool(), 'MEDIA_STRING should not be bool');
|
|
2332
|
+
assert(!MediaUrn.fromString(MEDIA_INTEGER).isBool(), 'MEDIA_INTEGER should not be bool');
|
|
2333
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isBool(), 'MEDIA_BINARY should not be bool');
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// TEST551: isFilePath returns true for scalar file-path, false for array
|
|
2337
|
+
function test551_isFilePath() {
|
|
2338
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isFilePath(), 'MEDIA_FILE_PATH should be file-path');
|
|
2339
|
+
// Array file-path is NOT isFilePath (it's isFilePathArray)
|
|
2340
|
+
assert(!MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePath(), 'MEDIA_FILE_PATH_ARRAY should not be isFilePath');
|
|
2341
|
+
// Non-file-path types
|
|
2342
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isFilePath(), 'MEDIA_STRING should not be file-path');
|
|
2343
|
+
assert(!MediaUrn.fromString(MEDIA_BINARY).isFilePath(), 'MEDIA_BINARY should not be file-path');
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// TEST552: isFilePathArray returns true for list file-path, false for scalar
|
|
2347
|
+
function test552_isFilePathArray() {
|
|
2348
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isFilePathArray(), 'MEDIA_FILE_PATH_ARRAY should be file-path-array');
|
|
2349
|
+
// Scalar file-path is NOT isFilePathArray
|
|
2350
|
+
assert(!MediaUrn.fromString(MEDIA_FILE_PATH).isFilePathArray(), 'MEDIA_FILE_PATH should not be isFilePathArray');
|
|
2351
|
+
// Non-file-path types
|
|
2352
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isFilePathArray(), 'MEDIA_STRING_ARRAY should not be file-path-array');
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// TEST553: isAnyFilePath returns true for both scalar and array file-path
|
|
2356
|
+
function test553_isAnyFilePath() {
|
|
2357
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH).isAnyFilePath(), 'MEDIA_FILE_PATH should be any-file-path');
|
|
2358
|
+
assert(MediaUrn.fromString(MEDIA_FILE_PATH_ARRAY).isAnyFilePath(), 'MEDIA_FILE_PATH_ARRAY should be any-file-path');
|
|
2359
|
+
// Non-file-path types
|
|
2360
|
+
assert(!MediaUrn.fromString(MEDIA_STRING).isAnyFilePath(), 'MEDIA_STRING should not be any-file-path');
|
|
2361
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isAnyFilePath(), 'MEDIA_STRING_ARRAY should not be any-file-path');
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// TEST554: isCollection returns true when collection marker tag is present
|
|
2365
|
+
function test554_isCollection() {
|
|
2366
|
+
assert(MediaUrn.fromString(MEDIA_COLLECTION).isCollection(), 'MEDIA_COLLECTION should be collection');
|
|
2367
|
+
assert(MediaUrn.fromString(MEDIA_COLLECTION_LIST).isCollection(), 'MEDIA_COLLECTION_LIST should be collection');
|
|
2368
|
+
// Non-collection types
|
|
2369
|
+
assert(!MediaUrn.fromString(MEDIA_OBJECT).isCollection(), 'MEDIA_OBJECT should not be collection');
|
|
2370
|
+
assert(!MediaUrn.fromString(MEDIA_STRING_ARRAY).isCollection(), 'MEDIA_STRING_ARRAY should not be collection');
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// TEST555: N/A for JS (with_tag/without_tag on MediaUrn - JS MediaUrn does not have these methods)
|
|
2374
|
+
|
|
2375
|
+
// TEST556: N/A for JS (image_media_urn_for_ext helper not in JS)
|
|
2376
|
+
|
|
2377
|
+
// TEST557: N/A for JS (audio_media_urn_for_ext helper not in JS)
|
|
2378
|
+
|
|
2379
|
+
// TEST558: predicates are consistent with constants - every constant triggers exactly the expected predicates
|
|
2380
|
+
function test558_predicateConstantConsistency() {
|
|
2381
|
+
// MEDIA_INTEGER must be numeric, text, scalar, NOT binary/bool/image/audio/video
|
|
2382
|
+
const intUrn = MediaUrn.fromString(MEDIA_INTEGER);
|
|
2383
|
+
assert(intUrn.isNumeric(), 'MEDIA_INTEGER must be numeric');
|
|
2384
|
+
assert(intUrn.isText(), 'MEDIA_INTEGER must be text');
|
|
2385
|
+
assert(intUrn.isScalar(), 'MEDIA_INTEGER must be scalar');
|
|
2386
|
+
assert(!intUrn.isBinary(), 'MEDIA_INTEGER must not be binary');
|
|
2387
|
+
assert(!intUrn.isBool(), 'MEDIA_INTEGER must not be bool');
|
|
2388
|
+
assert(!intUrn.isImage(), 'MEDIA_INTEGER must not be image');
|
|
2389
|
+
assert(!intUrn.isList(), 'MEDIA_INTEGER must not be list');
|
|
2390
|
+
|
|
2391
|
+
// MEDIA_BOOLEAN must be bool, text, scalar, NOT numeric
|
|
2392
|
+
const boolUrn = MediaUrn.fromString(MEDIA_BOOLEAN);
|
|
2393
|
+
assert(boolUrn.isBool(), 'MEDIA_BOOLEAN must be bool');
|
|
2394
|
+
assert(boolUrn.isText(), 'MEDIA_BOOLEAN must be text');
|
|
2395
|
+
assert(boolUrn.isScalar(), 'MEDIA_BOOLEAN must be scalar');
|
|
2396
|
+
assert(!boolUrn.isNumeric(), 'MEDIA_BOOLEAN must not be numeric');
|
|
2397
|
+
|
|
2398
|
+
// MEDIA_JSON must be json, text, map, structured, NOT binary
|
|
2399
|
+
const jsonUrn = MediaUrn.fromString(MEDIA_JSON);
|
|
2400
|
+
assert(jsonUrn.isJson(), 'MEDIA_JSON must be json');
|
|
2401
|
+
assert(jsonUrn.isText(), 'MEDIA_JSON must be text');
|
|
2402
|
+
assert(jsonUrn.isMap(), 'MEDIA_JSON must be map');
|
|
2403
|
+
assert(jsonUrn.isStructured(), 'MEDIA_JSON must be structured');
|
|
2404
|
+
assert(!jsonUrn.isBinary(), 'MEDIA_JSON must not be binary');
|
|
2405
|
+
assert(!jsonUrn.isList(), 'MEDIA_JSON must not be list');
|
|
2406
|
+
|
|
2407
|
+
// MEDIA_VOID is void, NOT text/numeric — but IS binary (no textable tag)
|
|
2408
|
+
const voidUrn = MediaUrn.fromString(MEDIA_VOID);
|
|
2409
|
+
assert(voidUrn.isVoid(), 'MEDIA_VOID must be void');
|
|
2410
|
+
assert(!voidUrn.isText(), 'MEDIA_VOID must not be text');
|
|
2411
|
+
assert(voidUrn.isBinary(), 'MEDIA_VOID must be binary (no textable tag)');
|
|
2412
|
+
assert(!voidUrn.isNumeric(), 'MEDIA_VOID must not be numeric');
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// ============================================================================
|
|
2416
|
+
// cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
|
|
2417
|
+
// ============================================================================
|
|
2418
|
+
|
|
2419
|
+
// TEST559: withoutTag removes tag, ignores in/out, case-insensitive for keys
|
|
2420
|
+
function test559_withoutTag() {
|
|
2421
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"');
|
|
2422
|
+
const removed = cap.withoutTag('ext');
|
|
2423
|
+
assertEqual(removed.getTag('ext'), undefined, 'withoutTag should remove ext');
|
|
2424
|
+
assertEqual(removed.getTag('op'), 'test', 'withoutTag should preserve op');
|
|
2425
|
+
|
|
2426
|
+
// Case-insensitive removal
|
|
2427
|
+
const removed2 = cap.withoutTag('EXT');
|
|
2428
|
+
assertEqual(removed2.getTag('ext'), undefined, 'withoutTag should be case-insensitive');
|
|
2429
|
+
|
|
2430
|
+
// Removing in/out is silently ignored
|
|
2431
|
+
const same = cap.withoutTag('in');
|
|
2432
|
+
assertEqual(same.getInSpec(), 'media:void', 'withoutTag must not remove in');
|
|
2433
|
+
const same2 = cap.withoutTag('out');
|
|
2434
|
+
assertEqual(same2.getOutSpec(), 'media:void', 'withoutTag must not remove out');
|
|
2435
|
+
|
|
2436
|
+
// Removing non-existent tag is no-op
|
|
2437
|
+
const same3 = cap.withoutTag('nonexistent');
|
|
2438
|
+
assert(same3.equals(cap), 'Removing non-existent tag is no-op');
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// TEST560: withInSpec and withOutSpec change direction specs
|
|
2442
|
+
function test560_withInOutSpec() {
|
|
2443
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2444
|
+
|
|
2445
|
+
const changedIn = cap.withInSpec('media:');
|
|
2446
|
+
assertEqual(changedIn.getInSpec(), 'media:', 'withInSpec should change inSpec');
|
|
2447
|
+
assertEqual(changedIn.getOutSpec(), 'media:void', 'withInSpec should preserve outSpec');
|
|
2448
|
+
assertEqual(changedIn.getTag('op'), 'test', 'withInSpec should preserve tags');
|
|
2449
|
+
|
|
2450
|
+
const changedOut = cap.withOutSpec('media:string');
|
|
2451
|
+
assertEqual(changedOut.getInSpec(), 'media:void', 'withOutSpec should preserve inSpec');
|
|
2452
|
+
assertEqual(changedOut.getOutSpec(), 'media:string', 'withOutSpec should change outSpec');
|
|
2453
|
+
|
|
2454
|
+
// Chain both
|
|
2455
|
+
const changedBoth = cap.withInSpec('media:pdf').withOutSpec('media:txt;textable');
|
|
2456
|
+
assertEqual(changedBoth.getInSpec(), 'media:pdf', 'Chain should set inSpec');
|
|
2457
|
+
assertEqual(changedBoth.getOutSpec(), 'media:txt;textable', 'Chain should set outSpec');
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// TEST561: N/A for JS (in_media_urn/out_media_urn not in JS CapUrn)
|
|
2461
|
+
|
|
2462
|
+
// TEST562: N/A for JS (canonical_option not in JS CapUrn)
|
|
2463
|
+
|
|
2464
|
+
// TEST563: CapMatcher.findAllMatches returns all matching caps sorted by specificity
|
|
2465
|
+
function test563_findAllMatches() {
|
|
2466
|
+
const caps = [
|
|
2467
|
+
CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
|
|
2468
|
+
CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
|
|
2469
|
+
CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
|
|
2470
|
+
];
|
|
2471
|
+
|
|
2472
|
+
const request = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2473
|
+
const matches = CapMatcher.findAllMatches(caps, request);
|
|
2474
|
+
|
|
2475
|
+
// Should find 2 matches (op=test and op=test;ext=pdf), not op=different
|
|
2476
|
+
assertEqual(matches.length, 2, 'Should find 2 matches');
|
|
2477
|
+
// Sorted by specificity descending: ext=pdf first (more specific)
|
|
2478
|
+
assert(matches[0].specificity() >= matches[1].specificity(), 'First match should be more specific');
|
|
2479
|
+
assertEqual(matches[0].getTag('ext'), 'pdf', 'Most specific match should have ext=pdf');
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// TEST564: CapMatcher.areCompatible detects bidirectional overlap
|
|
2483
|
+
function test564_areCompatible() {
|
|
2484
|
+
const caps1 = [
|
|
2485
|
+
CapUrn.fromString('cap:in="media:void";op=test;out="media:void"'),
|
|
2486
|
+
];
|
|
2487
|
+
const caps2 = [
|
|
2488
|
+
CapUrn.fromString('cap:in="media:void";op=test;ext=pdf;out="media:void"'),
|
|
2489
|
+
];
|
|
2490
|
+
const caps3 = [
|
|
2491
|
+
CapUrn.fromString('cap:in="media:void";op=different;out="media:void"'),
|
|
2492
|
+
];
|
|
2493
|
+
|
|
2494
|
+
// caps1 (op=test) accepts caps2 (op=test;ext=pdf) -> compatible
|
|
2495
|
+
assert(CapMatcher.areCompatible(caps1, caps2), 'caps1 and caps2 should be compatible');
|
|
2496
|
+
|
|
2497
|
+
// caps1 (op=test) vs caps3 (op=different) -> not compatible
|
|
2498
|
+
assert(!CapMatcher.areCompatible(caps1, caps3), 'caps1 and caps3 should not be compatible');
|
|
2499
|
+
|
|
2500
|
+
// Empty sets are not compatible
|
|
2501
|
+
assert(!CapMatcher.areCompatible([], caps1), 'Empty vs non-empty should not be compatible');
|
|
2502
|
+
assert(!CapMatcher.areCompatible(caps1, []), 'Non-empty vs empty should not be compatible');
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// TEST565: N/A for JS (tags_to_string not in JS CapUrn)
|
|
2506
|
+
|
|
2507
|
+
// TEST566: withTag silently ignores in/out keys
|
|
2508
|
+
function test566_withTagIgnoresInOut() {
|
|
2509
|
+
const cap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2510
|
+
// Attempting to set in/out via withTag is silently ignored
|
|
2511
|
+
const same = cap.withTag('in', 'media:');
|
|
2512
|
+
assertEqual(same.getInSpec(), 'media:void', 'withTag must not change in_spec');
|
|
2513
|
+
|
|
2514
|
+
const same2 = cap.withTag('out', 'media:');
|
|
2515
|
+
assertEqual(same2.getOutSpec(), 'media:void', 'withTag must not change out_spec');
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// TEST567: N/A for JS (conforms_to_str/accepts_str not in JS CapUrn)
|
|
2519
|
+
|
|
2520
|
+
// ============================================================================
|
|
2521
|
+
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
2522
|
+
// ============================================================================
|
|
2523
|
+
|
|
2524
|
+
// Note: Rust allows missing in/out to default to "media:" wildcard.
|
|
2525
|
+
// JS currently requires in/out (throws MISSING_IN_SPEC/MISSING_OUT_SPEC).
|
|
2526
|
+
// The following tests cover the wildcard behavior that IS applicable to JS.
|
|
2527
|
+
|
|
2528
|
+
// TEST639-642: N/A for JS (JS requires in/out, does not default to media: wildcard)
|
|
2529
|
+
|
|
2530
|
+
// TEST643: cap:in=*;out=* treated as wildcards
|
|
2531
|
+
function test643_explicitAsteriskIsWildcard() {
|
|
2532
|
+
const cap = CapUrn.fromString('cap:in=*;out=*');
|
|
2533
|
+
assertEqual(cap.getInSpec(), '*', 'in=* should be stored as wildcard');
|
|
2534
|
+
assertEqual(cap.getOutSpec(), '*', 'out=* should be stored as wildcard');
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// TEST644: cap:in=media:;out=* has specific in, wildcard out
|
|
2538
|
+
function test644_specificInWildcardOut() {
|
|
2539
|
+
const cap = CapUrn.fromString('cap:in=media:;out=*');
|
|
2540
|
+
assertEqual(cap.getInSpec(), 'media:', 'Should have specific in');
|
|
2541
|
+
assertEqual(cap.getOutSpec(), '*', 'Should have wildcard out');
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// TEST645: cap:in=*;out=media:text has wildcard in, specific out
|
|
2545
|
+
function test645_wildcardInSpecificOut() {
|
|
2546
|
+
const cap = CapUrn.fromString('cap:in=*;out=media:text');
|
|
2547
|
+
assertEqual(cap.getInSpec(), '*', 'Should have wildcard in');
|
|
2548
|
+
assertEqual(cap.getOutSpec(), 'media:text', 'Should have specific out');
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// TEST646: N/A for JS (JS allows in=foo since it just checks for media: or *)
|
|
2552
|
+
// TEST647: N/A for JS (JS allows out=bar since it just checks for media: or *)
|
|
2553
|
+
|
|
2554
|
+
// TEST648: Wildcard in/out match specific caps
|
|
2555
|
+
function test648_wildcardAcceptsSpecific() {
|
|
2556
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2557
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2558
|
+
|
|
2559
|
+
assert(wildcard.accepts(specific), 'Wildcard should accept specific');
|
|
2560
|
+
assert(specific.conformsTo(wildcard), 'Specific should conform to wildcard');
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// TEST649: Specificity - wildcard has 0, specific has tag count
|
|
2564
|
+
function test649_specificityScoring() {
|
|
2565
|
+
const wildcard = CapUrn.fromString('cap:in=*;out=*');
|
|
2566
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2567
|
+
|
|
2568
|
+
assertEqual(wildcard.specificity(), 0, 'Wildcard cap should have 0 specificity');
|
|
2569
|
+
assert(specific.specificity() > 0, 'Specific cap should have non-zero specificity');
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// TEST650: N/A for JS (JS requires in/out, cap:in;out;op=test would fail parsing)
|
|
2573
|
+
|
|
2574
|
+
// TEST651: All identity forms with explicit wildcards produce the same CapUrn
|
|
2575
|
+
function test651_identityFormsEquivalent() {
|
|
2576
|
+
const forms = [
|
|
2577
|
+
'cap:in=*;out=*',
|
|
2578
|
+
'cap:in="media:";out="media:"',
|
|
2579
|
+
];
|
|
2580
|
+
|
|
2581
|
+
const first = CapUrn.fromString(forms[0]);
|
|
2582
|
+
// All forms should produce equivalent caps (wildcard behavior)
|
|
2583
|
+
for (let i = 1; i < forms.length; i++) {
|
|
2584
|
+
const cap = CapUrn.fromString(forms[i]);
|
|
2585
|
+
// Both should accept specific caps
|
|
2586
|
+
const specific = CapUrn.fromString('cap:in="media:";out="media:text"');
|
|
2587
|
+
assert(first.accepts(specific), `Form 0 should accept specific`);
|
|
2588
|
+
assert(cap.accepts(specific), `Form ${i} should accept specific`);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
// TEST652: N/A for JS (CAP_IDENTITY constant not in JS)
|
|
2593
|
+
|
|
2594
|
+
// TEST653: Identity (no extra tags) does not steal routes from specific handlers
|
|
2595
|
+
function test653_identityRoutingIsolation() {
|
|
2596
|
+
const identity = CapUrn.fromString('cap:in=*;out=*');
|
|
2597
|
+
const specificRequest = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2598
|
+
|
|
2599
|
+
// Identity has specificity 0 (no tags, wildcard directions)
|
|
2600
|
+
assertEqual(identity.specificity(), 0, 'Identity specificity should be 0');
|
|
2601
|
+
|
|
2602
|
+
// Specific request has higher specificity
|
|
2603
|
+
assert(specificRequest.specificity() > identity.specificity(),
|
|
2604
|
+
'Specific request should have higher specificity than identity');
|
|
2605
|
+
|
|
2606
|
+
// CapMatcher should prefer specific over identity
|
|
2607
|
+
const specificCap = CapUrn.fromString('cap:in="media:void";op=test;out="media:void"');
|
|
2608
|
+
const best = CapMatcher.findBestMatch([identity, specificCap], specificRequest);
|
|
2609
|
+
assert(best !== null, 'Should find a match');
|
|
2610
|
+
assertEqual(best.getTag('op'), 'test', 'CapMatcher should prefer specific cap over identity');
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2264
2613
|
// ============================================================================
|
|
2265
2614
|
// Test runner
|
|
2266
2615
|
// ============================================================================
|
|
@@ -2455,6 +2804,48 @@ async function runTests() {
|
|
|
2455
2804
|
runTest('TEST334: plugin_repo_client_needs_sync', test334_pluginRepoClientNeedsSync);
|
|
2456
2805
|
runTest('TEST335: plugin_repo_server_client_integration', test335_pluginRepoServerClientIntegration);
|
|
2457
2806
|
|
|
2807
|
+
// media_urn.rs: TEST546-TEST558 (MediaUrn predicates)
|
|
2808
|
+
console.log('\n--- media_urn.rs (predicates) ---');
|
|
2809
|
+
runTest('TEST546: is_image', test546_isImage);
|
|
2810
|
+
runTest('TEST547: is_audio', test547_isAudio);
|
|
2811
|
+
runTest('TEST548: is_video', test548_isVideo);
|
|
2812
|
+
runTest('TEST549: is_numeric', test549_isNumeric);
|
|
2813
|
+
runTest('TEST550: is_bool', test550_isBool);
|
|
2814
|
+
runTest('TEST551: is_file_path', test551_isFilePath);
|
|
2815
|
+
runTest('TEST552: is_file_path_array', test552_isFilePathArray);
|
|
2816
|
+
runTest('TEST553: is_any_file_path', test553_isAnyFilePath);
|
|
2817
|
+
runTest('TEST554: is_collection', test554_isCollection);
|
|
2818
|
+
console.log(' SKIP TEST555: N/A for JS (with_tag/without_tag on MediaUrn)');
|
|
2819
|
+
console.log(' SKIP TEST556: N/A for JS (image_media_urn_for_ext helper)');
|
|
2820
|
+
console.log(' SKIP TEST557: N/A for JS (audio_media_urn_for_ext helper)');
|
|
2821
|
+
runTest('TEST558: predicate_constant_consistency', test558_predicateConstantConsistency);
|
|
2822
|
+
|
|
2823
|
+
// cap_urn.rs: TEST559-TEST567 (CapUrn tier tests)
|
|
2824
|
+
console.log('\n--- cap_urn.rs (tier tests) ---');
|
|
2825
|
+
runTest('TEST559: without_tag', test559_withoutTag);
|
|
2826
|
+
runTest('TEST560: with_in_out_spec', test560_withInOutSpec);
|
|
2827
|
+
console.log(' SKIP TEST561: N/A for JS (in_media_urn/out_media_urn)');
|
|
2828
|
+
console.log(' SKIP TEST562: N/A for JS (canonical_option)');
|
|
2829
|
+
runTest('TEST563: find_all_matches', test563_findAllMatches);
|
|
2830
|
+
runTest('TEST564: are_compatible', test564_areCompatible);
|
|
2831
|
+
console.log(' SKIP TEST565: N/A for JS (tags_to_string)');
|
|
2832
|
+
runTest('TEST566: with_tag_ignores_in_out', test566_withTagIgnoresInOut);
|
|
2833
|
+
console.log(' SKIP TEST567: N/A for JS (conforms_to_str/accepts_str)');
|
|
2834
|
+
|
|
2835
|
+
// cap_urn.rs: TEST639-TEST653 (Cap URN wildcard tests)
|
|
2836
|
+
console.log('\n--- cap_urn.rs (wildcard tests) ---');
|
|
2837
|
+
console.log(' SKIP TEST639-642: N/A for JS (implicit wildcard defaults)');
|
|
2838
|
+
runTest('TEST643: explicit_asterisk_is_wildcard', test643_explicitAsteriskIsWildcard);
|
|
2839
|
+
runTest('TEST644: specific_in_wildcard_out', test644_specificInWildcardOut);
|
|
2840
|
+
runTest('TEST645: wildcard_in_specific_out', test645_wildcardInSpecificOut);
|
|
2841
|
+
console.log(' SKIP TEST646-647: N/A for JS (invalid spec validation differs)');
|
|
2842
|
+
runTest('TEST648: wildcard_accepts_specific', test648_wildcardAcceptsSpecific);
|
|
2843
|
+
runTest('TEST649: specificity_scoring', test649_specificityScoring);
|
|
2844
|
+
console.log(' SKIP TEST650: N/A for JS (requires in/out)');
|
|
2845
|
+
runTest('TEST651: identity_forms_equivalent', test651_identityFormsEquivalent);
|
|
2846
|
+
console.log(' SKIP TEST652: N/A for JS (CAP_IDENTITY constant)');
|
|
2847
|
+
runTest('TEST653: identity_routing_isolation', test653_identityRoutingIsolation);
|
|
2848
|
+
|
|
2458
2849
|
// Summary
|
|
2459
2850
|
console.log(`\n${passCount + failCount} tests: ${passCount} passed, ${failCount} failed`);
|
|
2460
2851
|
if (failCount > 0) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Bahram Joharshamshiri",
|
|
3
3
|
"dependencies": {
|
|
4
|
-
"tagged-urn": "^0.
|
|
4
|
+
"tagged-urn": "^0.28.4918"
|
|
5
5
|
},
|
|
6
6
|
"description": "JavaScript implementation of Cap URN (Capability Uniform Resource Names) with strict validation and matching",
|
|
7
7
|
"engines": {
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "node capns.test.js"
|
|
34
34
|
},
|
|
35
|
-
"version": "0.
|
|
35
|
+
"version": "0.85.19364"
|
|
36
36
|
}
|