capns 0.58.11575 → 0.60.11938
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 +42 -91
- package/capns.test.js +77 -63
- package/package.json +1 -1
package/capns.js
CHANGED
|
@@ -834,46 +834,11 @@ function getProfileURL(profileName) {
|
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
// =============================================================================
|
|
837
|
-
//
|
|
837
|
+
// MEDIA URN TAG UTILITIES
|
|
838
838
|
// =============================================================================
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
* Maps media URN to canonical format: <media-type>; profile=<url>
|
|
843
|
-
*
|
|
844
|
-
* NOTE: These use hardcoded URLs for static initialization.
|
|
845
|
-
* Use getSchemaBaseURL() and getProfileURL() for dynamic resolution.
|
|
846
|
-
*/
|
|
847
|
-
const BUILTIN_SPECS = {
|
|
848
|
-
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str',
|
|
849
|
-
[MEDIA_INTEGER]: 'text/plain; profile=https://capns.org/schema/int',
|
|
850
|
-
[MEDIA_NUMBER]: 'text/plain; profile=https://capns.org/schema/num',
|
|
851
|
-
[MEDIA_BOOLEAN]: 'text/plain; profile=https://capns.org/schema/bool',
|
|
852
|
-
[MEDIA_OBJECT]: 'application/json; profile=https://capns.org/schema/obj',
|
|
853
|
-
[MEDIA_STRING_ARRAY]: 'application/json; profile=https://capns.org/schema/str-array',
|
|
854
|
-
[MEDIA_INTEGER_ARRAY]: 'application/json; profile=https://capns.org/schema/int-array',
|
|
855
|
-
[MEDIA_NUMBER_ARRAY]: 'application/json; profile=https://capns.org/schema/num-array',
|
|
856
|
-
[MEDIA_BOOLEAN_ARRAY]: 'application/json; profile=https://capns.org/schema/bool-array',
|
|
857
|
-
[MEDIA_OBJECT_ARRAY]: 'application/json; profile=https://capns.org/schema/obj-array',
|
|
858
|
-
[MEDIA_BINARY]: 'application/octet-stream',
|
|
859
|
-
[MEDIA_VOID]: 'application/x-void; profile=https://capns.org/schema/void',
|
|
860
|
-
// Semantic content types
|
|
861
|
-
[MEDIA_PNG]: 'image/png; profile=https://capns.org/schema/image',
|
|
862
|
-
[MEDIA_AUDIO]: 'audio/wav; profile=https://capns.org/schema/audio',
|
|
863
|
-
[MEDIA_VIDEO]: 'video/mp4; profile=https://capns.org/schema/video',
|
|
864
|
-
// Document types (PRIMARY naming)
|
|
865
|
-
[MEDIA_PDF]: 'application/pdf',
|
|
866
|
-
[MEDIA_EPUB]: 'application/epub+zip',
|
|
867
|
-
// Text format types (PRIMARY naming)
|
|
868
|
-
[MEDIA_MD]: 'text/markdown',
|
|
869
|
-
[MEDIA_TXT]: 'text/plain',
|
|
870
|
-
[MEDIA_RST]: 'text/x-rst',
|
|
871
|
-
[MEDIA_LOG]: 'text/plain',
|
|
872
|
-
[MEDIA_HTML]: 'text/html',
|
|
873
|
-
[MEDIA_XML]: 'application/xml',
|
|
874
|
-
[MEDIA_JSON]: 'application/json',
|
|
875
|
-
[MEDIA_YAML]: 'application/x-yaml'
|
|
876
|
-
};
|
|
839
|
+
// NOTE: The MEDIA_X constants above are convenience values for referencing
|
|
840
|
+
// common media URNs in code. Resolution must go through mediaSpecs tables -
|
|
841
|
+
// there is NO built-in resolution.
|
|
877
842
|
|
|
878
843
|
/**
|
|
879
844
|
* Check if a media URN has a marker tag (e.g., bytes, json, textable).
|
|
@@ -1137,18 +1102,16 @@ class MediaSpec {
|
|
|
1137
1102
|
/**
|
|
1138
1103
|
* Resolve a media URN to a MediaSpec
|
|
1139
1104
|
*
|
|
1140
|
-
* Resolution
|
|
1141
|
-
*
|
|
1142
|
-
* 2. If not found AND mediaUrn is a known built-in: use built-in definition
|
|
1143
|
-
* 3. If not found and not a built-in: FAIL HARD
|
|
1105
|
+
* Resolution: Look up mediaUrn in mediaSpecs table, FAIL HARD if not found.
|
|
1106
|
+
* There is no built-in resolution - all media URNs must be in mediaSpecs.
|
|
1144
1107
|
*
|
|
1145
|
-
* @param {string} mediaUrn - The media URN (e.g., "media:
|
|
1146
|
-
* @param {Object} mediaSpecs - The mediaSpecs lookup table
|
|
1108
|
+
* @param {string} mediaUrn - The media URN (e.g., "media:textable;form=scalar")
|
|
1109
|
+
* @param {Object} mediaSpecs - The mediaSpecs lookup table (required)
|
|
1147
1110
|
* @returns {MediaSpec} The resolved MediaSpec
|
|
1148
1111
|
* @throws {MediaSpecError} If media URN cannot be resolved
|
|
1149
1112
|
*/
|
|
1150
1113
|
function resolveMediaUrn(mediaUrn, mediaSpecs = {}) {
|
|
1151
|
-
//
|
|
1114
|
+
// Look up in mediaSpecs table
|
|
1152
1115
|
if (mediaSpecs && mediaSpecs[mediaUrn]) {
|
|
1153
1116
|
const def = mediaSpecs[mediaUrn];
|
|
1154
1117
|
|
|
@@ -1178,38 +1141,24 @@ function resolveMediaUrn(mediaUrn, mediaSpecs = {}) {
|
|
|
1178
1141
|
}
|
|
1179
1142
|
}
|
|
1180
1143
|
|
|
1181
|
-
//
|
|
1182
|
-
if (BUILTIN_SPECS[mediaUrn]) {
|
|
1183
|
-
const spec = MediaSpec.parse(BUILTIN_SPECS[mediaUrn]);
|
|
1184
|
-
spec.mediaUrn = mediaUrn; // Attach source URN for tag-based checks
|
|
1185
|
-
return spec;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// FAIL HARD - no fallbacks, no guessing
|
|
1144
|
+
// FAIL HARD - media URN must be in mediaSpecs table
|
|
1189
1145
|
throw new MediaSpecError(
|
|
1190
1146
|
MediaSpecErrorCodes.UNRESOLVABLE_MEDIA_URN,
|
|
1191
|
-
`Cannot resolve media URN: '${mediaUrn}'. Not found in mediaSpecs table
|
|
1147
|
+
`Cannot resolve media URN: '${mediaUrn}'. Not found in mediaSpecs table.`
|
|
1192
1148
|
);
|
|
1193
1149
|
}
|
|
1194
1150
|
|
|
1195
1151
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
* @param {string} mediaUrn - The media URN to check
|
|
1198
|
-
* @returns {boolean} True if built-in
|
|
1199
|
-
*/
|
|
1200
|
-
function isBuiltinMediaUrn(mediaUrn) {
|
|
1201
|
-
return BUILTIN_SPECS.hasOwnProperty(mediaUrn);
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* XV5: Validate that inline media_specs don't redefine built-in/registry specs.
|
|
1152
|
+
* XV5: Validate that inline media_specs don't redefine existing registry specs.
|
|
1206
1153
|
*
|
|
1207
|
-
*
|
|
1208
|
-
*
|
|
1154
|
+
* Validation requires a registryLookup function to check if media URNs exist.
|
|
1155
|
+
* If no registryLookup is provided, validation passes (graceful degradation).
|
|
1209
1156
|
*
|
|
1210
1157
|
* @param {Object} mediaSpecs - The inline media_specs object from a capability
|
|
1211
1158
|
* @param {Object} [options] - Validation options
|
|
1212
|
-
* @param {Function} [options.registryLookup] -
|
|
1159
|
+
* @param {Function} [options.registryLookup] - Function to check if media URN exists in registry
|
|
1160
|
+
* Returns true if exists, false otherwise
|
|
1161
|
+
* Should handle errors gracefully (return false)
|
|
1213
1162
|
* @returns {Promise<{valid: boolean, error?: string, redefines?: string[]}>}
|
|
1214
1163
|
*/
|
|
1215
1164
|
async function validateNoMediaSpecRedefinition(mediaSpecs, options = {}) {
|
|
@@ -1218,26 +1167,23 @@ async function validateNoMediaSpecRedefinition(mediaSpecs, options = {}) {
|
|
|
1218
1167
|
}
|
|
1219
1168
|
|
|
1220
1169
|
const { registryLookup } = options;
|
|
1170
|
+
|
|
1171
|
+
// If no registry lookup provided, degrade gracefully and allow
|
|
1172
|
+
if (!registryLookup || typeof registryLookup !== 'function') {
|
|
1173
|
+
return { valid: true };
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1221
1176
|
const redefines = [];
|
|
1222
1177
|
|
|
1223
1178
|
for (const mediaUrn of Object.keys(mediaSpecs)) {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// If a registry lookup function is provided (server-side), check against it
|
|
1231
|
-
if (registryLookup && typeof registryLookup === 'function') {
|
|
1232
|
-
try {
|
|
1233
|
-
const existsInRegistry = await registryLookup(mediaUrn);
|
|
1234
|
-
if (existsInRegistry) {
|
|
1235
|
-
redefines.push(mediaUrn);
|
|
1236
|
-
}
|
|
1237
|
-
} catch (err) {
|
|
1238
|
-
// Network/registry unavailable - log warning and allow (graceful degradation)
|
|
1239
|
-
console.warn(`[WARN] XV5: Could not verify inline spec '${mediaUrn}' against registry: ${err.message}. Allowing operation in offline mode.`);
|
|
1179
|
+
try {
|
|
1180
|
+
const existsInRegistry = await registryLookup(mediaUrn);
|
|
1181
|
+
if (existsInRegistry) {
|
|
1182
|
+
redefines.push(mediaUrn);
|
|
1240
1183
|
}
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
// Registry lookup failed - log warning and allow (graceful degradation)
|
|
1186
|
+
console.warn(`[WARN] XV5: Could not verify inline spec '${mediaUrn}' against registry: ${err.message}. Allowing operation in offline mode.`);
|
|
1241
1187
|
}
|
|
1242
1188
|
}
|
|
1243
1189
|
|
|
@@ -1253,21 +1199,28 @@ async function validateNoMediaSpecRedefinition(mediaSpecs, options = {}) {
|
|
|
1253
1199
|
}
|
|
1254
1200
|
|
|
1255
1201
|
/**
|
|
1256
|
-
* XV5: Synchronous version
|
|
1257
|
-
*
|
|
1202
|
+
* XV5: Synchronous version that checks against a provided lookup function.
|
|
1203
|
+
* If no registryLookup is provided, validation passes (graceful degradation).
|
|
1258
1204
|
*
|
|
1259
1205
|
* @param {Object} mediaSpecs - The inline media_specs object from a capability
|
|
1206
|
+
* @param {Function} [registryLookup] - Synchronous function to check if media URN exists
|
|
1207
|
+
* Returns true if exists, false otherwise
|
|
1260
1208
|
* @returns {{valid: boolean, error?: string, redefines?: string[]}}
|
|
1261
1209
|
*/
|
|
1262
|
-
function validateNoMediaSpecRedefinitionSync(mediaSpecs) {
|
|
1210
|
+
function validateNoMediaSpecRedefinitionSync(mediaSpecs, registryLookup = null) {
|
|
1263
1211
|
if (!mediaSpecs || typeof mediaSpecs !== 'object' || Object.keys(mediaSpecs).length === 0) {
|
|
1264
1212
|
return { valid: true };
|
|
1265
1213
|
}
|
|
1266
1214
|
|
|
1215
|
+
// If no registry lookup provided, degrade gracefully and allow
|
|
1216
|
+
if (!registryLookup || typeof registryLookup !== 'function') {
|
|
1217
|
+
return { valid: true };
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1267
1220
|
const redefines = [];
|
|
1268
1221
|
|
|
1269
1222
|
for (const mediaUrn of Object.keys(mediaSpecs)) {
|
|
1270
|
-
if (
|
|
1223
|
+
if (registryLookup(mediaUrn)) {
|
|
1271
1224
|
redefines.push(mediaUrn);
|
|
1272
1225
|
}
|
|
1273
1226
|
}
|
|
@@ -1275,7 +1228,7 @@ function validateNoMediaSpecRedefinitionSync(mediaSpecs) {
|
|
|
1275
1228
|
if (redefines.length > 0) {
|
|
1276
1229
|
return {
|
|
1277
1230
|
valid: false,
|
|
1278
|
-
error: `XV5: Inline media specs redefine existing
|
|
1231
|
+
error: `XV5: Inline media specs redefine existing registry specs: ${redefines.join(', ')}`,
|
|
1279
1232
|
redefines
|
|
1280
1233
|
};
|
|
1281
1234
|
}
|
|
@@ -3508,10 +3461,8 @@ module.exports = {
|
|
|
3508
3461
|
isJSONCapUrn,
|
|
3509
3462
|
isStructuredCapUrn,
|
|
3510
3463
|
resolveMediaUrn,
|
|
3511
|
-
isBuiltinMediaUrn,
|
|
3512
3464
|
validateNoMediaSpecRedefinition,
|
|
3513
3465
|
validateNoMediaSpecRedefinitionSync,
|
|
3514
|
-
BUILTIN_SPECS,
|
|
3515
3466
|
getSchemaBaseURL,
|
|
3516
3467
|
getProfileURL,
|
|
3517
3468
|
MEDIA_STRING,
|
package/capns.test.js
CHANGED
|
@@ -12,15 +12,6 @@ const {
|
|
|
12
12
|
MediaSpecError,
|
|
13
13
|
MediaSpecErrorCodes,
|
|
14
14
|
resolveMediaUrn,
|
|
15
|
-
isBuiltinMediaUrn,
|
|
16
|
-
BUILTIN_SPECS,
|
|
17
|
-
MEDIA_STRING,
|
|
18
|
-
MEDIA_INTEGER,
|
|
19
|
-
MEDIA_NUMBER,
|
|
20
|
-
MEDIA_BOOLEAN,
|
|
21
|
-
MEDIA_OBJECT,
|
|
22
|
-
MEDIA_BINARY,
|
|
23
|
-
MEDIA_VOID,
|
|
24
15
|
// CapMatrix and CapCube
|
|
25
16
|
CapMatrixError,
|
|
26
17
|
CapMatrix,
|
|
@@ -38,6 +29,23 @@ const {
|
|
|
38
29
|
validateNoMediaSpecRedefinitionSync
|
|
39
30
|
} = require('./capns.js');
|
|
40
31
|
|
|
32
|
+
// Media URN constants (previously exported from capns.js as built-ins)
|
|
33
|
+
const MEDIA_STRING = 'media:string';
|
|
34
|
+
const MEDIA_INTEGER = 'media:integer';
|
|
35
|
+
const MEDIA_NUMBER = 'media:number';
|
|
36
|
+
const MEDIA_BOOLEAN = 'media:boolean';
|
|
37
|
+
const MEDIA_OBJECT = 'media:object';
|
|
38
|
+
const MEDIA_BINARY = 'media:binary';
|
|
39
|
+
const MEDIA_VOID = 'media:void';
|
|
40
|
+
|
|
41
|
+
// Media spec definitions for tests (no longer built into capns.js)
|
|
42
|
+
const TEST_MEDIA_SPECS = {
|
|
43
|
+
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str',
|
|
44
|
+
[MEDIA_INTEGER]: 'text/plain; profile=https://capns.org/schema/int',
|
|
45
|
+
[MEDIA_OBJECT]: 'application/json; profile=https://capns.org/schema/obj',
|
|
46
|
+
[MEDIA_BINARY]: 'application/octet-stream'
|
|
47
|
+
};
|
|
48
|
+
|
|
41
49
|
// Test assertion utility
|
|
42
50
|
function assert(condition, message) {
|
|
43
51
|
if (!condition) {
|
|
@@ -565,45 +573,36 @@ function testMediaSpecLegacyFormatRejection() {
|
|
|
565
573
|
console.log(' ✓ Legacy format rejection');
|
|
566
574
|
}
|
|
567
575
|
|
|
568
|
-
// TEST059: Test built-in media URNs are recognized by isBuiltinMediaUrn
|
|
569
|
-
function testBuiltinSpecIds() {
|
|
570
|
-
console.log('Testing built-in spec IDs...');
|
|
571
|
-
|
|
572
|
-
// Verify built-in spec IDs exist
|
|
573
|
-
assert(isBuiltinMediaUrn(MEDIA_STRING), 'MEDIA_STRING should be built-in');
|
|
574
|
-
assert(isBuiltinMediaUrn(MEDIA_INTEGER), 'MEDIA_INTEGER should be built-in');
|
|
575
|
-
assert(isBuiltinMediaUrn(MEDIA_NUMBER), 'MEDIA_NUMBER should be built-in');
|
|
576
|
-
assert(isBuiltinMediaUrn(MEDIA_BOOLEAN), 'MEDIA_BOOLEAN should be built-in');
|
|
577
|
-
assert(isBuiltinMediaUrn(MEDIA_OBJECT), 'MEDIA_OBJECT should be built-in');
|
|
578
|
-
assert(isBuiltinMediaUrn(MEDIA_BINARY), 'MEDIA_BINARY should be built-in');
|
|
579
576
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
console.log(' ✓ Built-in spec IDs');
|
|
584
|
-
}
|
|
577
|
+
// TEST060: Test resolveMediaUrn requires mediaSpecs table for resolution
|
|
578
|
+
function testMediaUrnResolutionRequiresMediaSpecs() {
|
|
579
|
+
console.log('Testing media URN resolution requires mediaSpecs...');
|
|
585
580
|
|
|
586
|
-
//
|
|
587
|
-
|
|
588
|
-
|
|
581
|
+
// Media specs table with definitions
|
|
582
|
+
const mediaSpecs = {
|
|
583
|
+
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str',
|
|
584
|
+
[MEDIA_INTEGER]: 'text/plain; profile=https://capns.org/schema/int',
|
|
585
|
+
[MEDIA_OBJECT]: 'application/json; profile=https://capns.org/schema/obj',
|
|
586
|
+
[MEDIA_BINARY]: 'application/octet-stream'
|
|
587
|
+
};
|
|
589
588
|
|
|
590
|
-
// Should resolve
|
|
591
|
-
const strSpec = resolveMediaUrn(MEDIA_STRING);
|
|
589
|
+
// Should resolve spec IDs from mediaSpecs table
|
|
590
|
+
const strSpec = resolveMediaUrn(MEDIA_STRING, mediaSpecs);
|
|
592
591
|
assertEqual(strSpec.contentType, 'text/plain', 'Should resolve str spec');
|
|
593
592
|
assertEqual(strSpec.profile, 'https://capns.org/schema/str', 'Should have correct profile');
|
|
594
593
|
|
|
595
|
-
const intSpec = resolveMediaUrn(MEDIA_INTEGER);
|
|
594
|
+
const intSpec = resolveMediaUrn(MEDIA_INTEGER, mediaSpecs);
|
|
596
595
|
assertEqual(intSpec.contentType, 'text/plain', 'Should resolve int spec');
|
|
597
596
|
assertEqual(intSpec.profile, 'https://capns.org/schema/int', 'Should have correct profile');
|
|
598
597
|
|
|
599
|
-
const objSpec = resolveMediaUrn(MEDIA_OBJECT);
|
|
598
|
+
const objSpec = resolveMediaUrn(MEDIA_OBJECT, mediaSpecs);
|
|
600
599
|
assertEqual(objSpec.contentType, 'application/json', 'Should resolve obj spec');
|
|
601
600
|
|
|
602
|
-
const binarySpec = resolveMediaUrn(MEDIA_BINARY);
|
|
601
|
+
const binarySpec = resolveMediaUrn(MEDIA_BINARY, mediaSpecs);
|
|
603
602
|
assertEqual(binarySpec.contentType, 'application/octet-stream', 'Should resolve binary spec');
|
|
604
|
-
|
|
603
|
+
// Note: isBinary() checks for 'bytes' tag in media URN, not content type
|
|
605
604
|
|
|
606
|
-
console.log(' ✓
|
|
605
|
+
console.log(' ✓ Media URN resolution requires mediaSpecs');
|
|
607
606
|
}
|
|
608
607
|
|
|
609
608
|
// TEST061: Test resolveMediaUrn resolves custom media URNs from mediaSpecs table
|
|
@@ -611,13 +610,15 @@ function testMediaUrnResolutionWithMediaSpecs() {
|
|
|
611
610
|
console.log('Testing media URN resolution with custom mediaSpecs...');
|
|
612
611
|
|
|
613
612
|
// Custom mediaSpecs table (using media URN format as keys)
|
|
613
|
+
// Includes MEDIA_STRING so resolution works
|
|
614
614
|
const mediaSpecs = {
|
|
615
615
|
'media:custom-json': 'application/json; profile=https://example.com/schema/custom',
|
|
616
616
|
'media:rich-xml': {
|
|
617
617
|
media_type: 'application/xml',
|
|
618
618
|
profile_uri: 'https://example.com/schema/rich',
|
|
619
619
|
schema: { type: 'object' }
|
|
620
|
-
}
|
|
620
|
+
},
|
|
621
|
+
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str'
|
|
621
622
|
};
|
|
622
623
|
|
|
623
624
|
// Should resolve custom string form
|
|
@@ -631,9 +632,9 @@ function testMediaUrnResolutionWithMediaSpecs() {
|
|
|
631
632
|
assertEqual(richSpec.profile, 'https://example.com/schema/rich', 'Should have rich profile');
|
|
632
633
|
assert(richSpec.schema !== null, 'Should have schema from object form');
|
|
633
634
|
|
|
634
|
-
// Should
|
|
635
|
+
// Should resolve MEDIA_STRING from mediaSpecs table
|
|
635
636
|
const strSpec = resolveMediaUrn(MEDIA_STRING, mediaSpecs);
|
|
636
|
-
assertEqual(strSpec.contentType, 'text/plain', 'Should
|
|
637
|
+
assertEqual(strSpec.contentType, 'text/plain', 'Should resolve string spec from mediaSpecs');
|
|
637
638
|
|
|
638
639
|
console.log(' ✓ Media URN resolution with custom mediaSpecs');
|
|
639
640
|
}
|
|
@@ -704,15 +705,19 @@ function testMetadataForStringDef() {
|
|
|
704
705
|
console.log(' ✓ String form has no metadata');
|
|
705
706
|
}
|
|
706
707
|
|
|
707
|
-
// TEST065: Test
|
|
708
|
-
function
|
|
709
|
-
console.log('Testing metadata for
|
|
708
|
+
// TEST065: Test string form definitions have no metadata
|
|
709
|
+
function testMetadataForSimpleStringDef() {
|
|
710
|
+
console.log('Testing metadata for simple string definition...');
|
|
710
711
|
|
|
711
|
-
//
|
|
712
|
-
const
|
|
713
|
-
|
|
712
|
+
// String form definitions should have no metadata
|
|
713
|
+
const mediaSpecs = {
|
|
714
|
+
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str'
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const resolved = resolveMediaUrn(MEDIA_STRING, mediaSpecs);
|
|
718
|
+
assert(resolved.metadata === null, 'String form definition should have no metadata');
|
|
714
719
|
|
|
715
|
-
console.log(' ✓
|
|
720
|
+
console.log(' ✓ String form definition has no metadata');
|
|
716
721
|
}
|
|
717
722
|
|
|
718
723
|
// TEST066: Test metadata and validation coexist in media spec definition
|
|
@@ -751,7 +756,7 @@ function testMetadataWithValidation() {
|
|
|
751
756
|
console.log(' ✓ Metadata coexists with validation');
|
|
752
757
|
}
|
|
753
758
|
|
|
754
|
-
// TEST108: Test Cap with mediaSpecs resolves custom
|
|
759
|
+
// TEST108: Test Cap with mediaSpecs resolves custom media URNs
|
|
755
760
|
function testCapWithMediaSpecs() {
|
|
756
761
|
console.log('Testing Cap with mediaSpecs...');
|
|
757
762
|
|
|
@@ -762,8 +767,9 @@ function testCapWithMediaSpecs() {
|
|
|
762
767
|
|
|
763
768
|
const cap = new Cap(urn, 'Test Cap', 'test_command');
|
|
764
769
|
|
|
765
|
-
// Set custom mediaSpecs
|
|
770
|
+
// Set custom mediaSpecs - must include MEDIA_STRING for resolution to work
|
|
766
771
|
cap.mediaSpecs = {
|
|
772
|
+
[MEDIA_STRING]: 'text/plain; profile=https://capns.org/schema/str',
|
|
767
773
|
'media:custom': {
|
|
768
774
|
media_type: 'application/json',
|
|
769
775
|
profile_uri: 'https://example.com/schema/output',
|
|
@@ -774,9 +780,9 @@ function testCapWithMediaSpecs() {
|
|
|
774
780
|
}
|
|
775
781
|
};
|
|
776
782
|
|
|
777
|
-
// Should resolve
|
|
783
|
+
// Should resolve MEDIA_STRING from mediaSpecs via cap.resolveMediaUrn
|
|
778
784
|
const strSpec = cap.resolveMediaUrn(MEDIA_STRING);
|
|
779
|
-
assertEqual(strSpec.contentType, 'text/plain', 'Should resolve
|
|
785
|
+
assertEqual(strSpec.contentType, 'text/plain', 'Should resolve string spec through cap');
|
|
780
786
|
|
|
781
787
|
// Should resolve custom spec via cap.resolveMediaUrn
|
|
782
788
|
const outputSpec = cap.resolveMediaUrn('media:custom');
|
|
@@ -1729,11 +1735,14 @@ function testStdinSourceFileReferencePassedToExecuteCap() {
|
|
|
1729
1735
|
// TEST054-056: Validate that inline media_specs don't redefine registry specs
|
|
1730
1736
|
// ============================================================================
|
|
1731
1737
|
|
|
1732
|
-
// TEST054: Test XV5 validation detects inline media spec redefinition of
|
|
1738
|
+
// TEST054: Test XV5 validation detects inline media spec redefinition of registry spec
|
|
1733
1739
|
function testXV5InlineSpecRedefinitionDetected() {
|
|
1734
1740
|
console.log('Testing XV5: Inline spec redefinition detected...');
|
|
1735
1741
|
|
|
1736
|
-
//
|
|
1742
|
+
// Mock registry lookup that reports MEDIA_STRING as existing in registry
|
|
1743
|
+
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
1744
|
+
|
|
1745
|
+
// Try to redefine MEDIA_STRING which is in the registry
|
|
1737
1746
|
const mediaSpecs = {
|
|
1738
1747
|
[MEDIA_STRING]: {
|
|
1739
1748
|
media_type: 'text/plain',
|
|
@@ -1742,20 +1751,23 @@ function testXV5InlineSpecRedefinitionDetected() {
|
|
|
1742
1751
|
}
|
|
1743
1752
|
};
|
|
1744
1753
|
|
|
1745
|
-
const result = validateNoMediaSpecRedefinitionSync(mediaSpecs);
|
|
1754
|
+
const result = validateNoMediaSpecRedefinitionSync(mediaSpecs, registryLookup);
|
|
1746
1755
|
|
|
1747
|
-
assert(!result.valid, 'Should fail validation when redefining
|
|
1756
|
+
assert(!result.valid, 'Should fail validation when redefining registry spec');
|
|
1748
1757
|
assert(result.error && result.error.includes('XV5'), 'Error should mention XV5');
|
|
1749
1758
|
assert(result.redefines && result.redefines.includes(MEDIA_STRING), 'Should identify MEDIA_STRING as redefined');
|
|
1750
1759
|
|
|
1751
1760
|
console.log(' ✓ Inline spec redefinition detected');
|
|
1752
1761
|
}
|
|
1753
1762
|
|
|
1754
|
-
// TEST055: Test XV5 validation allows new inline media spec not in
|
|
1763
|
+
// TEST055: Test XV5 validation allows new inline media spec not in registry
|
|
1755
1764
|
function testXV5NewInlineSpecAllowed() {
|
|
1756
1765
|
console.log('Testing XV5: New inline spec allowed...');
|
|
1757
1766
|
|
|
1758
|
-
//
|
|
1767
|
+
// Mock registry lookup that reports MEDIA_STRING as existing, but not custom specs
|
|
1768
|
+
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
1769
|
+
|
|
1770
|
+
// Define a completely new media spec that doesn't exist in registry
|
|
1759
1771
|
const mediaSpecs = {
|
|
1760
1772
|
'media:my-unique-custom-type-xyz123': {
|
|
1761
1773
|
media_type: 'application/json',
|
|
@@ -1764,9 +1776,9 @@ function testXV5NewInlineSpecAllowed() {
|
|
|
1764
1776
|
}
|
|
1765
1777
|
};
|
|
1766
1778
|
|
|
1767
|
-
const result = validateNoMediaSpecRedefinitionSync(mediaSpecs);
|
|
1779
|
+
const result = validateNoMediaSpecRedefinitionSync(mediaSpecs, registryLookup);
|
|
1768
1780
|
|
|
1769
|
-
assert(result.valid, 'Should pass validation for new spec not in
|
|
1781
|
+
assert(result.valid, 'Should pass validation for new spec not in registry');
|
|
1770
1782
|
assert(!result.error, 'Should not have error message');
|
|
1771
1783
|
|
|
1772
1784
|
console.log(' ✓ New inline spec allowed');
|
|
@@ -1776,14 +1788,17 @@ function testXV5NewInlineSpecAllowed() {
|
|
|
1776
1788
|
function testXV5EmptyMediaSpecsAllowed() {
|
|
1777
1789
|
console.log('Testing XV5: Empty media_specs allowed...');
|
|
1778
1790
|
|
|
1791
|
+
// Mock registry lookup function
|
|
1792
|
+
const registryLookup = (mediaUrn) => mediaUrn === MEDIA_STRING;
|
|
1793
|
+
|
|
1779
1794
|
// Empty or null media_specs should pass
|
|
1780
|
-
let result = validateNoMediaSpecRedefinitionSync({});
|
|
1795
|
+
let result = validateNoMediaSpecRedefinitionSync({}, registryLookup);
|
|
1781
1796
|
assert(result.valid, 'Empty object should pass validation');
|
|
1782
1797
|
|
|
1783
|
-
result = validateNoMediaSpecRedefinitionSync(null);
|
|
1798
|
+
result = validateNoMediaSpecRedefinitionSync(null, registryLookup);
|
|
1784
1799
|
assert(result.valid, 'Null should pass validation');
|
|
1785
1800
|
|
|
1786
|
-
result = validateNoMediaSpecRedefinitionSync(undefined);
|
|
1801
|
+
result = validateNoMediaSpecRedefinitionSync(undefined, registryLookup);
|
|
1787
1802
|
assert(result.valid, 'Undefined should pass validation');
|
|
1788
1803
|
|
|
1789
1804
|
console.log(' ✓ Empty media_specs allowed');
|
|
@@ -1816,13 +1831,12 @@ async function runTests() {
|
|
|
1816
1831
|
// New format tests
|
|
1817
1832
|
testMediaSpecCanonicalFormat();
|
|
1818
1833
|
testMediaSpecLegacyFormatRejection();
|
|
1819
|
-
|
|
1820
|
-
testSpecIdResolution();
|
|
1834
|
+
testMediaUrnResolutionRequiresMediaSpecs();
|
|
1821
1835
|
testMediaUrnResolutionWithMediaSpecs();
|
|
1822
1836
|
testMediaUrnResolutionFailHard();
|
|
1823
1837
|
testMetadataPropagation();
|
|
1824
1838
|
testMetadataForStringDef();
|
|
1825
|
-
|
|
1839
|
+
testMetadataForSimpleStringDef();
|
|
1826
1840
|
testMetadataWithValidation();
|
|
1827
1841
|
testCapWithMediaSpecs();
|
|
1828
1842
|
testCapJSONSerialization();
|
package/package.json
CHANGED