11import type {
2- CredentialPayload
2+ CredentialPayload ,
3+ EnvelopedVerifiableCredential
34} from "../interfaces" ;
45
5- import {
6- VerifiableCredentialService
6+ import {
7+ VerifiableCredentialService
78} from '../verifiableCredential' ;
89import { privateAPI } from '../utils/httpService' ;
10+ import { decodeJwt } from 'jose' ;
11+
12+ jest . mock ( 'jose' , ( ) => ( {
13+ decodeJwt : jest . fn ( ) ,
14+ } ) ) ;
915
1016jest . mock ( '../utils/httpService' , ( ) => ( {
1117 privateAPI : {
@@ -37,13 +43,15 @@ describe('verifiableCredential', () => {
3743 } ) ;
3844
3945 describe ( 'sign' , ( ) => {
40- const mockEnvelopedVerifiableCredential = {
41- verifiableCredential : {
42- '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
43- type : 'EnvelopedVerifiableCredential' ,
44- id : 'data:application/vc-ld+jwt,eyJhbGciOiJFZERTQSIsImlzcyI6ImRpZDp3ZWIvcmcvMjAxOC9jcmVkZWyMDIyIn1dfQ.8pUt1rZktWKGBGyJ6GH3io6f7fliAg8IWsEqTWCYvKm0fQkIlPnqqTobxgR3qmtMd_jJXc8IHwbVVOBUEvpcCg' ,
45- issuer : 'did:web:uncefact.github.io:project-vckit:test-and-development'
46- }
46+ const mockEnvelopedVC = {
47+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
48+ type : 'EnvelopedVerifiableCredential' ,
49+ id : 'data:application/vc-ld+jwt,eyJhbGciOiJFZERTQSIsImlzcyI6ImRpZDp3ZWIvcmcvMjAxOC9jcmVkZWyMDIyIn1dfQ.8pUt1rZktWKGBGyJ6GH3io6f7fliAg8IWsEqTWCYvKm0fQkIlPnqqTobxgR3qmtMd_jJXc8IHwbVVOBUEvpcCg' ,
50+ issuer : 'did:web:uncefact.github.io:project-vckit:test-and-development'
51+ } ;
52+
53+ const mockSignedCredentialResponse = {
54+ verifiableCredential : mockEnvelopedVC
4755 } ;
4856
4957 it ( 'should call issue API endpoint with credential status' , async ( ) => {
@@ -56,7 +64,7 @@ describe('verifiableCredential', () => {
5664 // Mock credential status issuance first, then VC issuance
5765 ( privateAPI . post as jest . Mock )
5866 . mockResolvedValueOnce ( mockCredentialStatus )
59- . mockResolvedValueOnce ( mockEnvelopedVerifiableCredential ) ;
67+ . mockResolvedValueOnce ( mockSignedCredentialResponse ) ;
6068
6169 const result = await service . sign ( vc ) ;
6270
@@ -87,23 +95,22 @@ describe('verifiableCredential', () => {
8795 { headers : { } } ,
8896 ) ;
8997
90- expect ( result ) . toEqual ( mockEnvelopedVerifiableCredential ) ;
98+ expect ( result ) . toEqual ( mockEnvelopedVC ) ;
9199 } ) ;
92100
93- it ( 'should pass custom headers to API calls' , async ( ) => {
94- const service = new VerifiableCredentialService ( mockAPIUrl ) ;
101+ it ( 'should pass default headers to API calls when configured' , async ( ) => {
102+ const customHeaders = { Authorization : 'Bearer token123' } ;
103+ const service = new VerifiableCredentialService ( mockAPIUrl , customHeaders ) ;
95104 const vc = {
96105 issuer : mockIssuer ,
97106 credentialSubject : mockCredentialSubject
98107 } as CredentialPayload ;
99108
100- const customHeaders = { Authorization : 'Bearer token123' } ;
101-
102109 ( privateAPI . post as jest . Mock )
103110 . mockResolvedValueOnce ( mockCredentialStatus )
104- . mockResolvedValueOnce ( mockEnvelopedVerifiableCredential ) ;
111+ . mockResolvedValueOnce ( mockSignedCredentialResponse ) ;
105112
106- const result = await service . sign ( vc , customHeaders ) ;
113+ const result = await service . sign ( vc ) ;
107114
108115 // Verify headers in credential status call
109116 expect ( privateAPI . post ) . toHaveBeenNthCalledWith (
@@ -121,7 +128,7 @@ describe('verifiableCredential', () => {
121128 { headers : customHeaders } ,
122129 ) ;
123130
124- expect ( result ) . toEqual ( mockEnvelopedVerifiableCredential ) ;
131+ expect ( result ) . toEqual ( mockEnvelopedVC ) ;
125132 } ) ;
126133
127134 it ( 'should fail if credential status issuance fails' , async ( ) => {
@@ -166,7 +173,7 @@ describe('verifiableCredential', () => {
166173
167174 ( privateAPI . post as jest . Mock )
168175 . mockResolvedValueOnce ( mockCredentialStatus )
169- . mockResolvedValueOnce ( mockEnvelopedVerifiableCredential ) ;
176+ . mockResolvedValueOnce ( mockSignedCredentialResponse ) ;
170177
171178 const result = await service . sign ( vc ) ;
172179
@@ -197,7 +204,7 @@ describe('verifiableCredential', () => {
197204 { headers : { } } ,
198205 ) ;
199206
200- expect ( result ) . toEqual ( mockEnvelopedVerifiableCredential ) ;
207+ expect ( result ) . toEqual ( mockEnvelopedVC ) ;
201208 } ) ;
202209
203210 it ( 'should issue VC with added context' , async ( ) => {
@@ -207,11 +214,12 @@ describe('verifiableCredential', () => {
207214 credentialSubject : mockCredentialSubject
208215 } as CredentialPayload ;
209216
217+ const mockContextVC = {
218+ ...mockEnvelopedVC ,
219+ "@context" : [ 'https://www.w3.org/ns/credentials/v2' , 'https://test.uncefact.org/vocabulary/untp/dia/0.6.0/' ]
220+ } ;
210221 const mockIssueResponse = {
211- verifiableCredential : {
212- ...mockEnvelopedVerifiableCredential . verifiableCredential ,
213- "@context" : [ 'https://www.w3.org/ns/credentials/v2' , 'https://test.uncefact.org/vocabulary/untp/dia/0.6.0/' ]
214- }
222+ verifiableCredential : mockContextVC
215223 } ;
216224
217225 ( privateAPI . post as jest . Mock )
@@ -247,7 +255,7 @@ describe('verifiableCredential', () => {
247255 { headers : { } } ,
248256 ) ;
249257
250- expect ( result ) . toEqual ( mockIssueResponse ) ;
258+ expect ( result ) . toEqual ( mockContextVC ) ;
251259 } ) ;
252260
253261 it ( 'should issue VC with added type' , async ( ) => {
@@ -257,11 +265,12 @@ describe('verifiableCredential', () => {
257265 credentialSubject : mockCredentialSubject
258266 } as CredentialPayload ;
259267
268+ const mockTypeVC = {
269+ ...mockEnvelopedVC ,
270+ type : [ 'VerifiableCredential' , 'CustomType' ]
271+ } ;
260272 const mockIssueResponse = {
261- verifiableCredential : {
262- ...mockEnvelopedVerifiableCredential . verifiableCredential ,
263- type : [ 'VerifiableCredential' , 'CustomType' ]
264- }
273+ verifiableCredential : mockTypeVC
265274 } ;
266275
267276 ( privateAPI . post as jest . Mock )
@@ -297,7 +306,7 @@ describe('verifiableCredential', () => {
297306 { headers : { } } ,
298307 ) ;
299308
300- expect ( result ) . toEqual ( mockIssueResponse ) ;
309+ expect ( result ) . toEqual ( mockTypeVC ) ;
301310 } ) ;
302311
303312 it ( 'should use provided credential status instead of issuing new one' , async ( ) => {
@@ -316,7 +325,7 @@ describe('verifiableCredential', () => {
316325 credentialStatus : customCredentialStatus
317326 } as CredentialPayload ;
318327
319- ( privateAPI . post as jest . Mock ) . mockResolvedValueOnce ( mockEnvelopedVerifiableCredential ) ;
328+ ( privateAPI . post as jest . Mock ) . mockResolvedValueOnce ( mockSignedCredentialResponse ) ;
320329
321330 const result = await service . sign ( vc ) ;
322331
@@ -334,7 +343,7 @@ describe('verifiableCredential', () => {
334343 { headers : { } } ,
335344 ) ;
336345
337- expect ( result ) . toEqual ( mockEnvelopedVerifiableCredential ) ;
346+ expect ( result ) . toEqual ( mockEnvelopedVC ) ;
338347 } ) ;
339348
340349 it ( 'should throw error when baseURL is not provided' , ( ) => {
@@ -354,18 +363,196 @@ describe('verifiableCredential', () => {
354363 expect ( privateAPI . post ) . not . toHaveBeenCalled ( ) ;
355364 } ) ;
356365
357- it ( 'should throw error when headers have invalid format' , async ( ) => {
366+ it ( 'should throw error when defaultHeaders have invalid format' , ( ) => {
367+ const invalidHeaders = { Authorization : 123 } as any ;
368+
369+ expect ( ( ) => new VerifiableCredentialService ( mockAPIUrl , invalidHeaders ) ) . not . toThrow ( ) ;
370+ } ) ;
371+
372+ } ) ;
373+
374+ describe ( 'verify' , ( ) => {
375+ const mockEnvelopedCredential : EnvelopedVerifiableCredential = {
376+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
377+ type : 'EnvelopedVerifiableCredential' ,
378+ id : 'data:application/vc-ld+jwt,eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOnVuY2VmYWN0LmdpdGh1Yi5pbyIsInN1YiI6ImRpZDpleGFtcGxlOjEyMyJ9.signature' ,
379+ issuer : 'did:web:uncefact.github.io:project-vckit:test-and-development' ,
380+ credentialSubject : { id : 'did:example:123' }
381+ } ;
382+
383+ it ( 'should call verify API endpoint with credential' , async ( ) => {
358384 const service = new VerifiableCredentialService ( mockAPIUrl ) ;
359- const vc = {
360- issuer : mockIssuer ,
361- credentialSubject : mockCredentialSubject
362- } as CredentialPayload ;
385+ const mockVerifyResult = { verified : true } ;
363386
364- const invalidHeaders = { Authorization : 123 } as any ;
387+ ( privateAPI . post as jest . Mock ) . mockResolvedValueOnce ( mockVerifyResult ) ;
388+
389+ const result = await service . verify ( mockEnvelopedCredential ) ;
365390
366- await expect ( service . sign ( vc , invalidHeaders ) ) . rejects . toThrow ( 'Headers must be a plain object with string values' ) ;
391+ expect ( privateAPI . post ) . toHaveBeenCalledWith (
392+ `${ mockAPIUrl } /credentials/verify` ,
393+ {
394+ credential : mockEnvelopedCredential ,
395+ fetchRemoteContexts : true ,
396+ policies : {
397+ credentialStatus : true ,
398+ } ,
399+ } ,
400+ { headers : { } }
401+ ) ;
402+ expect ( result ) . toEqual ( mockVerifyResult ) ;
403+ } ) ;
404+
405+ it ( 'should pass default headers when verifying' , async ( ) => {
406+ const customHeaders = { Authorization : 'Bearer token123' } ;
407+ const service = new VerifiableCredentialService ( mockAPIUrl , customHeaders ) ;
408+ const mockVerifyResult = { verified : true } ;
409+
410+ ( privateAPI . post as jest . Mock ) . mockResolvedValueOnce ( mockVerifyResult ) ;
411+
412+ const result = await service . verify ( mockEnvelopedCredential ) ;
413+
414+ expect ( privateAPI . post ) . toHaveBeenCalledWith (
415+ `${ mockAPIUrl } /credentials/verify` ,
416+ expect . any ( Object ) ,
417+ { headers : customHeaders }
418+ ) ;
419+ expect ( result ) . toEqual ( mockVerifyResult ) ;
420+ } ) ;
421+
422+ it ( 'should return verification failure result' , async ( ) => {
423+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
424+ const mockVerifyResult = {
425+ verified : false ,
426+ error : { message : 'Invalid signature' }
427+ } ;
428+
429+ ( privateAPI . post as jest . Mock ) . mockResolvedValueOnce ( mockVerifyResult ) ;
430+
431+ const result = await service . verify ( mockEnvelopedCredential ) ;
432+
433+ expect ( result ) . toEqual ( mockVerifyResult ) ;
434+ } ) ;
435+
436+ it ( 'should throw error when credential is not provided' , async ( ) => {
437+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
438+
439+ await expect ( service . verify ( null as any ) ) . rejects . toThrow ( 'Error verifying VC. Credential is required.' ) ;
367440 expect ( privateAPI . post ) . not . toHaveBeenCalled ( ) ;
368441 } ) ;
369442
443+ it ( 'should throw error when API call fails' , async ( ) => {
444+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
445+ const apiError = new Error ( 'Network error' ) ;
446+
447+ ( privateAPI . post as jest . Mock ) . mockRejectedValueOnce ( apiError ) ;
448+
449+ await expect ( service . verify ( mockEnvelopedCredential ) ) . rejects . toThrow ( 'Failed to verify verifiable credential: Network error' ) ;
450+ } ) ;
451+
452+ it ( 'should handle unknown errors' , async ( ) => {
453+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
454+
455+ ( privateAPI . post as jest . Mock ) . mockRejectedValueOnce ( 'Unknown error' ) ;
456+
457+ await expect ( service . verify ( mockEnvelopedCredential ) ) . rejects . toThrow ( 'Failed to verify verifiable credential: Unknown error' ) ;
458+ } ) ;
459+ } ) ;
460+
461+ describe ( 'decode' , ( ) => {
462+ const mockEnvelopedCredential : EnvelopedVerifiableCredential = {
463+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
464+ type : 'EnvelopedVerifiableCredential' ,
465+ id : 'data:application/vc-ld+jwt,eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOnVuY2VmYWN0LmdpdGh1Yi5pbyIsInN1YiI6ImRpZDpleGFtcGxlOjEyMyJ9.signature' ,
466+ issuer : 'did:web:uncefact.github.io:project-vckit:test-and-development' ,
467+ credentialSubject : { id : 'did:example:123' }
468+ } ;
469+
470+ const mockDecodedCredential = {
471+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
472+ type : [ 'VerifiableCredential' ] ,
473+ issuer : 'did:web:uncefact.github.io' ,
474+ credentialSubject : { id : 'did:example:123' , name : 'John Doe' }
475+ } ;
476+
477+ beforeEach ( ( ) => {
478+ ( decodeJwt as jest . Mock ) . mockReset ( ) ;
479+ } ) ;
480+
481+ it ( 'should decode enveloped credential using jose' , async ( ) => {
482+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
483+
484+ ( decodeJwt as jest . Mock ) . mockReturnValueOnce ( mockDecodedCredential ) ;
485+
486+ const result = await service . decode ( mockEnvelopedCredential ) ;
487+
488+ expect ( decodeJwt ) . toHaveBeenCalledWith (
489+ 'eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6d2ViOnVuY2VmYWN0LmdpdGh1Yi5pbyIsInN1YiI6ImRpZDpleGFtcGxlOjEyMyJ9.signature'
490+ ) ;
491+ expect ( result ) . toEqual ( mockDecodedCredential ) ;
492+ } ) ;
493+
494+ it ( 'should throw error when credential is not provided' , async ( ) => {
495+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
496+
497+ await expect ( service . decode ( null as any ) ) . rejects . toThrow ( 'Error decoding VC. Credential is required.' ) ;
498+ expect ( decodeJwt ) . not . toHaveBeenCalled ( ) ;
499+ } ) ;
500+
501+ it ( 'should throw error when credential type is not EnvelopedVerifiableCredential' , async ( ) => {
502+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
503+ const invalidCredential = {
504+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
505+ type : 'VerifiableCredential' ,
506+ issuer : 'did:example:123' ,
507+ credentialSubject : { id : 'did:example:456' }
508+ } as any ;
509+
510+ await expect ( service . decode ( invalidCredential ) ) . rejects . toThrow ( 'Failed to decode verifiable credential: Credential is not an EnvelopedVerifiableCredential' ) ;
511+ } ) ;
512+
513+ it ( 'should throw error when credential id is missing encoded data' , async ( ) => {
514+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
515+ const invalidCredential : EnvelopedVerifiableCredential = {
516+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
517+ type : 'EnvelopedVerifiableCredential' ,
518+ id : 'data:application/vc-ld+jwt' ,
519+ issuer : 'did:example:123' ,
520+ credentialSubject : { id : 'did:example:456' }
521+ } ;
522+
523+ await expect ( service . decode ( invalidCredential ) ) . rejects . toThrow ( 'Failed to decode verifiable credential: Invalid enveloped credential format: missing encoded data' ) ;
524+ } ) ;
525+
526+ it ( 'should throw error when credential id is undefined' , async ( ) => {
527+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
528+ const invalidCredential : EnvelopedVerifiableCredential = {
529+ '@context' : [ 'https://www.w3.org/ns/credentials/v2' ] ,
530+ type : 'EnvelopedVerifiableCredential' ,
531+ issuer : 'did:example:123' ,
532+ credentialSubject : { id : 'did:example:456' }
533+ } ;
534+
535+ await expect ( service . decode ( invalidCredential ) ) . rejects . toThrow ( 'Failed to decode verifiable credential: Invalid enveloped credential format: missing encoded data' ) ;
536+ } ) ;
537+
538+ it ( 'should handle decodeJwt errors' , async ( ) => {
539+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
540+
541+ ( decodeJwt as jest . Mock ) . mockImplementationOnce ( ( ) => {
542+ throw new Error ( 'Invalid JWT format' ) ;
543+ } ) ;
544+
545+ await expect ( service . decode ( mockEnvelopedCredential ) ) . rejects . toThrow ( 'Failed to decode verifiable credential: Invalid JWT format' ) ;
546+ } ) ;
547+
548+ it ( 'should handle unknown errors during decoding' , async ( ) => {
549+ const service = new VerifiableCredentialService ( mockAPIUrl ) ;
550+
551+ ( decodeJwt as jest . Mock ) . mockImplementationOnce ( ( ) => {
552+ throw 'Unknown error' ;
553+ } ) ;
554+
555+ await expect ( service . decode ( mockEnvelopedCredential ) ) . rejects . toThrow ( 'Failed to decode verifiable credential: Unknown error' ) ;
556+ } ) ;
370557 } ) ;
371558} )
0 commit comments