@liquidmetal-ai/drizzle 0.4.1 → 0.4.2

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.
@@ -0,0 +1,769 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseMRN, parsePartialMRN, serializeMRN, serializePartialMRN } from './mrn';
3
+ import type { MRNObject } from '@liquidmetal-ai/raindrop-framework';
4
+
5
+ describe('MRN (Metal Resource Name)', () => {
6
+ describe('parseMRN', () => {
7
+ describe('valid MRN examples', () => {
8
+ it('should parse annotation:my-app:versionId:::', () => {
9
+ const result = parseMRN('annotation:my-app:versionId:::');
10
+ expect(result).toEqual({
11
+ type: 'annotation',
12
+ applicationName: 'my-app',
13
+ versionId: 'versionId',
14
+ });
15
+ });
16
+
17
+ it('should parse annotation:my-app:versionId:module::', () => {
18
+ const result = parseMRN('annotation:my-app:versionId:module::');
19
+ expect(result).toEqual({
20
+ type: 'annotation',
21
+ applicationName: 'my-app',
22
+ versionId: 'versionId',
23
+ module: 'module',
24
+ });
25
+ });
26
+
27
+ it('should parse annotation:my-app:versionId:module:someKey', () => {
28
+ const result = parseMRN('annotation:my-app:versionId:module:someKey:');
29
+ expect(result).toEqual({
30
+ type: 'annotation',
31
+ applicationName: 'my-app',
32
+ versionId: 'versionId',
33
+ module: 'module',
34
+ key: 'someKey',
35
+ });
36
+ });
37
+
38
+ it('should parse annotation:my-app:versionId:module:someItem^someKey:', () => {
39
+ const result = parseMRN('annotation:my-app:versionId:module:someItem^someKey:');
40
+ expect(result).toEqual({
41
+ type: 'annotation',
42
+ applicationName: 'my-app',
43
+ versionId: 'versionId',
44
+ module: 'module',
45
+ item: 'someItem',
46
+ key: 'someKey',
47
+ });
48
+ });
49
+
50
+ it('should parse annotation:my-app:versionId:module:someItem^someKey:someRevision', () => {
51
+ const result = parseMRN('annotation:my-app:versionId:module:someItem^someKey:someRevision');
52
+ expect(result).toEqual({
53
+ type: 'annotation',
54
+ applicationName: 'my-app',
55
+ versionId: 'versionId',
56
+ module: 'module',
57
+ item: 'someItem',
58
+ key: 'someKey',
59
+ revision: 'someRevision',
60
+ });
61
+ });
62
+
63
+ it('should parse annotation:my-app:versionId:module:aDifferentKey:someRevision', () => {
64
+ const result = parseMRN('annotation:my-app:versionId:module:aDifferentKey:someRevision');
65
+ expect(result).toEqual({
66
+ type: 'annotation',
67
+ applicationName: 'my-app',
68
+ versionId: 'versionId',
69
+ module: 'module',
70
+ key: 'aDifferentKey',
71
+ revision: 'someRevision',
72
+ });
73
+ });
74
+ });
75
+
76
+ describe('basic valid cases', () => {
77
+ it('should parse minimal annotation MRN', () => {
78
+ const result = parseMRN('annotation:app:v1:::');
79
+ expect(result).toEqual({
80
+ type: 'annotation',
81
+ applicationName: 'app',
82
+ versionId: 'v1',
83
+ });
84
+ });
85
+
86
+ it('should parse minimal label MRN', () => {
87
+ const result = parseMRN('label:app:v1:::');
88
+ expect(result).toEqual({
89
+ type: 'label',
90
+ applicationName: 'app',
91
+ versionId: 'v1',
92
+ });
93
+ });
94
+
95
+ it('should parse MRN with module only', () => {
96
+ const result = parseMRN('annotation:app:v1:auth::');
97
+ expect(result).toEqual({
98
+ type: 'annotation',
99
+ applicationName: 'app',
100
+ versionId: 'v1',
101
+ module: 'auth',
102
+ });
103
+ });
104
+
105
+ it('should parse MRN with module and key', () => {
106
+ const result = parseMRN('annotation:app:v1:auth:secretKey:');
107
+ expect(result).toEqual({
108
+ type: 'annotation',
109
+ applicationName: 'app',
110
+ versionId: 'v1',
111
+ module: 'auth',
112
+ key: 'secretKey',
113
+ });
114
+ });
115
+
116
+ it('should parse MRN with module, key, and revision', () => {
117
+ const result = parseMRN('annotation:app:v1:auth:secretKey:rev123');
118
+ expect(result).toEqual({
119
+ type: 'annotation',
120
+ applicationName: 'app',
121
+ versionId: 'v1',
122
+ module: 'auth',
123
+ key: 'secretKey',
124
+ revision: 'rev123',
125
+ });
126
+ });
127
+
128
+ it('should parse MRN with item and key using ^ separator', () => {
129
+ const result = parseMRN('annotation:app:v1:auth:user^email:');
130
+ expect(result).toEqual({
131
+ type: 'annotation',
132
+ applicationName: 'app',
133
+ versionId: 'v1',
134
+ module: 'auth',
135
+ item: 'user',
136
+ key: 'email',
137
+ });
138
+ });
139
+
140
+ it('should parse MRN with item, key, and revision', () => {
141
+ const result = parseMRN('annotation:app:v1:auth:user^email:rev456');
142
+ expect(result).toEqual({
143
+ type: 'annotation',
144
+ applicationName: 'app',
145
+ versionId: 'v1',
146
+ module: 'auth',
147
+ item: 'user',
148
+ key: 'email',
149
+ revision: 'rev456',
150
+ });
151
+ });
152
+
153
+ it('should parse MRN with empty key after ^', () => {
154
+ const result = parseMRN('annotation:app:v1:auth:user^:');
155
+ expect(result).toEqual({
156
+ type: 'annotation',
157
+ applicationName: 'app',
158
+ versionId: 'v1',
159
+ module: 'auth',
160
+ item: 'user',
161
+ key: '',
162
+ });
163
+ });
164
+
165
+ it('should parse label MRN with module and key', () => {
166
+ const result = parseMRN('label:myapp:v2:config:dbUrl:');
167
+ expect(result).toEqual({
168
+ type: 'label',
169
+ applicationName: 'myapp',
170
+ versionId: 'v2',
171
+ module: 'config',
172
+ key: 'dbUrl',
173
+ });
174
+ });
175
+
176
+ it('should parse label MRN with item and key', () => {
177
+ const result = parseMRN('label:myapp:v2:config:database^url:');
178
+ expect(result).toEqual({
179
+ type: 'label',
180
+ applicationName: 'myapp',
181
+ versionId: 'v2',
182
+ module: 'config',
183
+ item: 'database',
184
+ key: 'url',
185
+ });
186
+ });
187
+
188
+ it('should parse MRN with revision but no module', () => {
189
+ const result = parseMRN('annotation:app:v1:::rev1');
190
+ expect(result).toEqual({
191
+ type: 'annotation',
192
+ applicationName: 'app',
193
+ versionId: 'v1',
194
+ revision: 'rev1',
195
+ });
196
+ });
197
+
198
+ it('should parse MRN with special characters in names', () => {
199
+ const result = parseMRN('annotation:my-app_test:v1.2.3:auth-module:api_key:');
200
+ expect(result).toEqual({
201
+ type: 'annotation',
202
+ applicationName: 'my-app_test',
203
+ versionId: 'v1.2.3',
204
+ module: 'auth-module',
205
+ key: 'api_key',
206
+ });
207
+ });
208
+
209
+ it('should parse MRN with numbers in components', () => {
210
+ const result = parseMRN('label:app123:v2:module456:item789^key000:');
211
+ expect(result).toEqual({
212
+ type: 'label',
213
+ applicationName: 'app123',
214
+ versionId: 'v2',
215
+ module: 'module456',
216
+ item: 'item789',
217
+ key: 'key000',
218
+ });
219
+ });
220
+
221
+ it('should parse MRN with long revision identifier', () => {
222
+ const result = parseMRN('annotation:app:v1:auth:secretKey:abc123def456ghi789');
223
+ expect(result).toEqual({
224
+ type: 'annotation',
225
+ applicationName: 'app',
226
+ versionId: 'v1',
227
+ module: 'auth',
228
+ key: 'secretKey',
229
+ revision: 'abc123def456ghi789',
230
+ });
231
+ });
232
+ });
233
+
234
+ describe('error cases', () => {
235
+ it('should throw error for too few parts', () => {
236
+ expect(() => parseMRN('annotation:app')).toThrow('Invalid MRN format');
237
+ expect(() => parseMRN('annotation:app:v1')).toThrow('Invalid MRN format');
238
+ });
239
+
240
+ it('should throw error for too many parts', () => {
241
+ expect(() => parseMRN('annotation:app:v1:module:key:revision:extra')).toThrow('Invalid MRN format');
242
+ });
243
+
244
+ it('should throw error for invalid type', () => {
245
+ expect(() => parseMRN('invalid:app:v1:::')).toThrow('Invalid MRN type: invalid');
246
+ });
247
+
248
+ it('should throw error for missing type', () => {
249
+ expect(() => parseMRN(':app:v1:::')).toThrow('MRN must contain type, applicationName, and versionId');
250
+ });
251
+
252
+ it('should throw error for missing applicationName', () => {
253
+ expect(() => parseMRN('annotation::v1:::')).toThrow('MRN must contain type, applicationName, and versionId');
254
+ });
255
+
256
+ it('should throw error for missing versionId', () => {
257
+ expect(() => parseMRN('annotation:app::::')).toThrow('MRN must contain type, applicationName, and versionId');
258
+ });
259
+
260
+ it('should throw error for empty string', () => {
261
+ expect(() => parseMRN('')).toThrow('Invalid MRN format');
262
+ });
263
+ it('should throw error for MRN with only colons', () => {
264
+ expect(() => parseMRN(':::::')).toThrow('MRN must contain type, applicationName, and versionId');
265
+ });
266
+
267
+ it('should throw error for unsupported type', () => {
268
+ expect(() => parseMRN('config:app:v1:::')).toThrow('Invalid MRN type: config');
269
+ });
270
+ });
271
+ });
272
+
273
+ describe('serializeMRN', () => {
274
+ it('should serialize minimal MRN object', () => {
275
+ const mrnObject: MRNObject = {
276
+ type: 'annotation',
277
+ applicationName: 'app',
278
+ versionId: 'v1',
279
+ };
280
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:::');
281
+ });
282
+
283
+ it('should serialize MRN object with module', () => {
284
+ const mrnObject: MRNObject = {
285
+ type: 'label',
286
+ applicationName: 'app',
287
+ versionId: 'v1',
288
+ module: 'auth',
289
+ };
290
+ expect(serializeMRN(mrnObject)).toBe('label:app:v1:auth::');
291
+ });
292
+
293
+ it('should serialize MRN object with module and key', () => {
294
+ const mrnObject: MRNObject = {
295
+ type: 'annotation',
296
+ applicationName: 'app',
297
+ versionId: 'v1',
298
+ module: 'auth',
299
+ key: 'secretKey',
300
+ };
301
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:secretKey:');
302
+ });
303
+
304
+ it('should serialize MRN object with module, key, and revision', () => {
305
+ const mrnObject: MRNObject = {
306
+ type: 'annotation',
307
+ applicationName: 'app',
308
+ versionId: 'v1',
309
+ module: 'auth',
310
+ key: 'secretKey',
311
+ revision: 'rev123',
312
+ };
313
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:secretKey:rev123');
314
+ });
315
+
316
+ it('should serialize MRN object with item and key using ^ separator', () => {
317
+ const mrnObject: MRNObject = {
318
+ type: 'annotation',
319
+ applicationName: 'app',
320
+ versionId: 'v1',
321
+ module: 'auth',
322
+ item: 'user',
323
+ key: 'email',
324
+ };
325
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:user^email:');
326
+ });
327
+
328
+ it('should serialize MRN object with item, key, and revision', () => {
329
+ const mrnObject: MRNObject = {
330
+ type: 'annotation',
331
+ applicationName: 'app',
332
+ versionId: 'v1',
333
+ module: 'auth',
334
+ item: 'user',
335
+ key: 'email',
336
+ revision: 'rev456',
337
+ };
338
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:user^email:rev456');
339
+ });
340
+
341
+ it('should serialize MRN object with item but no key', () => {
342
+ const mrnObject: MRNObject = {
343
+ type: 'annotation',
344
+ applicationName: 'app',
345
+ versionId: 'v1',
346
+ module: 'auth',
347
+ item: 'user',
348
+ };
349
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:user^:');
350
+ });
351
+
352
+ it('should serialize MRN object with empty key', () => {
353
+ const mrnObject: MRNObject = {
354
+ type: 'annotation',
355
+ applicationName: 'app',
356
+ versionId: 'v1',
357
+ module: 'auth',
358
+ item: 'user',
359
+ key: '',
360
+ };
361
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:auth:user^:');
362
+ });
363
+
364
+ it('should serialize label MRN object', () => {
365
+ const mrnObject: MRNObject = {
366
+ type: 'label',
367
+ applicationName: 'myapp',
368
+ versionId: 'v2',
369
+ module: 'config',
370
+ key: 'dbUrl',
371
+ };
372
+ expect(serializeMRN(mrnObject)).toBe('label:myapp:v2:config:dbUrl:');
373
+ });
374
+
375
+ it('should serialize MRN object with revision but no module', () => {
376
+ const mrnObject: MRNObject = {
377
+ type: 'annotation',
378
+ applicationName: 'app',
379
+ versionId: 'v1',
380
+ revision: 'rev1',
381
+ };
382
+ expect(serializeMRN(mrnObject)).toBe('annotation:app:v1:::rev1');
383
+ });
384
+
385
+ it('should serialize MRN object with special characters', () => {
386
+ const mrnObject: MRNObject = {
387
+ type: 'annotation',
388
+ applicationName: 'my-app_test',
389
+ versionId: 'v1.2.3',
390
+ module: 'auth-module',
391
+ key: 'api_key',
392
+ };
393
+ expect(serializeMRN(mrnObject)).toBe('annotation:my-app_test:v1.2.3:auth-module:api_key:');
394
+ });
395
+
396
+ it('should serialize MRN object with numbers', () => {
397
+ const mrnObject: MRNObject = {
398
+ type: 'label',
399
+ applicationName: 'app123',
400
+ versionId: 'v2',
401
+ module: 'module456',
402
+ item: 'item789',
403
+ key: 'key000',
404
+ };
405
+ expect(serializeMRN(mrnObject)).toBe('label:app123:v2:module456:item789^key000:');
406
+ });
407
+ });
408
+
409
+ describe('round-trip serialization', () => {
410
+ const testCases = [
411
+ 'annotation:my-app:versionId:::',
412
+ 'annotation:my-app:versionId:::revision',
413
+ 'annotation:my-app:versionId:module::',
414
+ 'annotation:my-app:versionId:module::revision',
415
+ 'annotation:my-app:versionId:module:someKey:',
416
+ 'annotation:my-app:versionId:module:someItem^someKey:',
417
+ 'annotation:my-app:versionId:module:someItem^someKey:revision',
418
+ 'annotation:my-app:versionId:module:aDifferentKey:revision',
419
+ 'label:myapp:v2:config:dbUrl:',
420
+ 'label:myapp:v2:config:database^url:',
421
+ 'annotation:app:v1:::rev1',
422
+ 'annotation:my-app_test:v1.2.3:auth-module:api_key:',
423
+ 'label:app123:v2:module456:item789^key000:',
424
+ 'annotation:app:v1:auth:secretKey:abc123def456ghi789',
425
+ 'annotation:app:v1:auth:user^:',
426
+ ];
427
+
428
+ testCases.forEach((mrn) => {
429
+ it(`should round-trip serialize/parse for: ${mrn}`, () => {
430
+ const parsed = parseMRN(mrn);
431
+ const serialized = serializeMRN(parsed);
432
+ expect(serialized).toBe(mrn);
433
+
434
+ // Double check by parsing the serialized result
435
+ const reparsed = parseMRN(serialized);
436
+ expect(reparsed).toEqual(parsed);
437
+ });
438
+ });
439
+ });
440
+
441
+ describe('parsePartialMRN', () => {
442
+ describe('partial MRN parsing for prefixes', () => {
443
+ it('should parse type only', () => {
444
+ const result = parsePartialMRN('annotation');
445
+ expect(result).toEqual({
446
+ type: 'annotation',
447
+ });
448
+ });
449
+
450
+ it('should parse type and applicationName', () => {
451
+ const result = parsePartialMRN('annotation:test-app');
452
+ expect(result).toEqual({
453
+ type: 'annotation',
454
+ applicationName: 'test-app',
455
+ });
456
+ });
457
+
458
+ it('should parse type, applicationName, and versionId', () => {
459
+ const result = parsePartialMRN('annotation:test-app:v1.0.0');
460
+ expect(result).toEqual({
461
+ type: 'annotation',
462
+ applicationName: 'test-app',
463
+ versionId: 'v1.0.0',
464
+ });
465
+ });
466
+
467
+ it('should parse type, applicationName, versionId, and module', () => {
468
+ const result = parsePartialMRN('annotation:test-app:v1.0.0:auth');
469
+ expect(result).toEqual({
470
+ type: 'annotation',
471
+ applicationName: 'test-app',
472
+ versionId: 'v1.0.0',
473
+ module: 'auth',
474
+ });
475
+ });
476
+
477
+ it('should parse partial MRN with key only (no item)', () => {
478
+ const result = parsePartialMRN('annotation:test-app:v1.0.0:auth:secretKey');
479
+ expect(result).toEqual({
480
+ type: 'annotation',
481
+ applicationName: 'test-app',
482
+ versionId: 'v1.0.0',
483
+ module: 'auth',
484
+ key: 'secretKey',
485
+ item: undefined,
486
+ });
487
+ });
488
+
489
+ it('should parse partial MRN with item and key using ^ separator', () => {
490
+ const result = parsePartialMRN('annotation:test-app:v1.0.0:auth:user^email');
491
+ expect(result).toEqual({
492
+ type: 'annotation',
493
+ applicationName: 'test-app',
494
+ versionId: 'v1.0.0',
495
+ module: 'auth',
496
+ item: 'user',
497
+ key: 'email',
498
+ });
499
+ });
500
+
501
+ it('should parse partial MRN with item but empty key', () => {
502
+ const result = parsePartialMRN('annotation:test-app:v1.0.0:auth:user^');
503
+ expect(result).toEqual({
504
+ type: 'annotation',
505
+ applicationName: 'test-app',
506
+ versionId: 'v1.0.0',
507
+ module: 'auth',
508
+ item: 'user',
509
+ key: '',
510
+ });
511
+ });
512
+
513
+ it('should parse full partial MRN with all components including revision', () => {
514
+ const result = parsePartialMRN('annotation:test-app:v1.0.0:auth:user^email:rev123');
515
+ expect(result).toEqual({
516
+ type: 'annotation',
517
+ applicationName: 'test-app',
518
+ versionId: 'v1.0.0',
519
+ module: 'auth',
520
+ item: 'user',
521
+ key: 'email',
522
+ revision: 'rev123',
523
+ });
524
+ });
525
+
526
+ it('should handle label type', () => {
527
+ const result = parsePartialMRN('label:my-app:v2.0.0:config');
528
+ expect(result).toEqual({
529
+ type: 'label',
530
+ applicationName: 'my-app',
531
+ versionId: 'v2.0.0',
532
+ module: 'config',
533
+ });
534
+ });
535
+
536
+ it('should handle empty string', () => {
537
+ const result = parsePartialMRN('');
538
+ expect(result).toEqual({});
539
+ });
540
+
541
+ it('should handle single colon', () => {
542
+ const result = parsePartialMRN(':');
543
+ expect(result).toEqual({});
544
+ });
545
+
546
+ it('should handle multiple colons with empty values', () => {
547
+ const result = parsePartialMRN('annotation::');
548
+ expect(result).toEqual({
549
+ type: 'annotation',
550
+ });
551
+ });
552
+
553
+ it('should handle partial MRN with special characters', () => {
554
+ const result = parsePartialMRN('annotation:my-app_test:v1.2.3:auth-module');
555
+ expect(result).toEqual({
556
+ type: 'annotation',
557
+ applicationName: 'my-app_test',
558
+ versionId: 'v1.2.3',
559
+ module: 'auth-module',
560
+ });
561
+ });
562
+
563
+ it('should handle partial MRN with numbers', () => {
564
+ const result = parsePartialMRN('label:app123:v2:module456');
565
+ expect(result).toEqual({
566
+ type: 'label',
567
+ applicationName: 'app123',
568
+ versionId: 'v2',
569
+ module: 'module456',
570
+ });
571
+ });
572
+ });
573
+
574
+ describe('edge cases and flexibility', () => {
575
+ it('should handle invalid type gracefully', () => {
576
+ const result = parsePartialMRN('invalid:test-app');
577
+ expect(result).toEqual({
578
+ type: 'invalid' as 'annotation' | 'label',
579
+ applicationName: 'test-app',
580
+ });
581
+ });
582
+
583
+ it('should handle partial MRN ending with colon', () => {
584
+ const result = parsePartialMRN('annotation:test-app:');
585
+ expect(result).toEqual({
586
+ type: 'annotation',
587
+ applicationName: 'test-app',
588
+ });
589
+ });
590
+
591
+ it('should handle partial MRN with only separators', () => {
592
+ const result = parsePartialMRN(':::');
593
+ expect(result).toEqual({});
594
+ });
595
+ });
596
+
597
+ describe('use cases for list operations', () => {
598
+ it('should support prefix matching for all annotations of an app', () => {
599
+ const result = parsePartialMRN('annotation:my-app');
600
+ expect(result).toEqual({
601
+ type: 'annotation',
602
+ applicationName: 'my-app',
603
+ });
604
+ });
605
+
606
+ it('should support prefix matching for all annotations of an app version', () => {
607
+ const result = parsePartialMRN('annotation:my-app:v1.0.0');
608
+ expect(result).toEqual({
609
+ type: 'annotation',
610
+ applicationName: 'my-app',
611
+ versionId: 'v1.0.0',
612
+ });
613
+ });
614
+
615
+ it('should support prefix matching for all annotations in a module', () => {
616
+ const result = parsePartialMRN('annotation:my-app:v1.0.0:auth');
617
+ expect(result).toEqual({
618
+ type: 'annotation',
619
+ applicationName: 'my-app',
620
+ versionId: 'v1.0.0',
621
+ module: 'auth',
622
+ });
623
+ });
624
+
625
+ it('should support prefix matching for item-specific annotations', () => {
626
+ const result = parsePartialMRN('annotation:my-app:v1.0.0:auth:user^');
627
+ expect(result).toEqual({
628
+ type: 'annotation',
629
+ applicationName: 'my-app',
630
+ versionId: 'v1.0.0',
631
+ module: 'auth',
632
+ item: 'user',
633
+ key: '',
634
+ });
635
+ });
636
+ });
637
+ });
638
+
639
+ describe('serializePartialMRN', () => {
640
+ describe('partial serialization', () => {
641
+ it('should serialize type only', () => {
642
+ const result = serializePartialMRN({ type: 'annotation' });
643
+ expect(result).toBe('annotation');
644
+ });
645
+
646
+ it('should serialize type and applicationName', () => {
647
+ const result = serializePartialMRN({
648
+ type: 'annotation',
649
+ applicationName: 'test-app',
650
+ });
651
+ expect(result).toBe('annotation:test-app');
652
+ });
653
+
654
+ it('should serialize type, applicationName, and versionId', () => {
655
+ const result = serializePartialMRN({
656
+ type: 'annotation',
657
+ applicationName: 'test-app',
658
+ versionId: 'v1.0.0',
659
+ });
660
+ expect(result).toBe('annotation:test-app:v1.0.0');
661
+ });
662
+
663
+ it('should serialize with module', () => {
664
+ const result = serializePartialMRN({
665
+ type: 'annotation',
666
+ applicationName: 'test-app',
667
+ versionId: 'v1.0.0',
668
+ module: 'auth',
669
+ });
670
+ expect(result).toBe('annotation:test-app:v1.0.0:auth');
671
+ });
672
+
673
+ it('should serialize with key only (no item)', () => {
674
+ const result = serializePartialMRN({
675
+ type: 'annotation',
676
+ applicationName: 'test-app',
677
+ versionId: 'v1.0.0',
678
+ module: 'auth',
679
+ key: 'secretKey',
680
+ });
681
+ expect(result).toBe('annotation:test-app:v1.0.0:auth:secretKey');
682
+ });
683
+
684
+ it('should serialize with item and key using ^ separator', () => {
685
+ const result = serializePartialMRN({
686
+ type: 'annotation',
687
+ applicationName: 'test-app',
688
+ versionId: 'v1.0.0',
689
+ module: 'auth',
690
+ item: 'user',
691
+ key: 'email',
692
+ });
693
+ expect(result).toBe('annotation:test-app:v1.0.0:auth:user^email');
694
+ });
695
+
696
+ it('should serialize with item but no key', () => {
697
+ const result = serializePartialMRN({
698
+ type: 'annotation',
699
+ applicationName: 'test-app',
700
+ versionId: 'v1.0.0',
701
+ module: 'auth',
702
+ item: 'user',
703
+ });
704
+ expect(result).toBe('annotation:test-app:v1.0.0:auth:user^');
705
+ });
706
+
707
+ it('should serialize with revision', () => {
708
+ const result = serializePartialMRN({
709
+ type: 'annotation',
710
+ applicationName: 'test-app',
711
+ versionId: 'v1.0.0',
712
+ module: 'auth',
713
+ key: 'secretKey',
714
+ revision: 'rev123',
715
+ });
716
+ expect(result).toBe('annotation:test-app:v1.0.0:auth:secretKey:rev123');
717
+ });
718
+
719
+ it('should handle empty partial MRN', () => {
720
+ const result = serializePartialMRN({});
721
+ expect(result).toBe('');
722
+ });
723
+
724
+ it('should handle partial MRN with gaps', () => {
725
+ const result = serializePartialMRN({
726
+ type: 'label',
727
+ versionId: 'v1.0.0',
728
+ key: 'test-key',
729
+ });
730
+ expect(result).toBe('label:v1.0.0:test-key');
731
+ });
732
+
733
+ it('should handle label type', () => {
734
+ const result = serializePartialMRN({
735
+ type: 'label',
736
+ applicationName: 'my-app',
737
+ versionId: 'v2.0.0',
738
+ module: 'config',
739
+ });
740
+ expect(result).toBe('label:my-app:v2.0.0:config');
741
+ });
742
+ });
743
+
744
+ describe('round-trip with parsePartialMRN', () => {
745
+ const testCases = [
746
+ 'annotation',
747
+ 'annotation:test-app',
748
+ 'annotation:test-app:v1.0.0',
749
+ 'annotation:test-app:v1.0.0:auth',
750
+ 'annotation:test-app:v1.0.0:auth:secretKey',
751
+ 'annotation:test-app:v1.0.0:auth:user^email',
752
+ 'annotation:test-app:v1.0.0:auth:user^',
753
+ 'label:my-app:v2.0.0:config',
754
+ ];
755
+
756
+ testCases.forEach((partialMrn) => {
757
+ it(`should round-trip serialize/parse for: ${partialMrn}`, () => {
758
+ const parsed = parsePartialMRN(partialMrn);
759
+ const serialized = serializePartialMRN(parsed);
760
+ expect(serialized).toBe(partialMrn);
761
+
762
+ // Double check by parsing the serialized result
763
+ const reparsed = parsePartialMRN(serialized);
764
+ expect(reparsed).toEqual(parsed);
765
+ });
766
+ });
767
+ });
768
+ });
769
+ });