33} from 'vitest' ;
44import jsonld from 'jsonld' ;
55import { verifyVPWithDelegation } from '../../src/engine.js' ;
6+ import { DelegationErrorCodes } from '../../src/errors.js' ;
67import {
78 VC_TYPE ,
89 VC_VC ,
@@ -17,6 +18,8 @@ import {
1718
1819const ROOT_CREDENTIAL_ID = 'urn:cred:root' ;
1920const LEAF_CREDENTIAL_ID = 'urn:cred:leaf' ;
21+ const MISSING_PREVIOUS_ID = 'urn:cred:missing-prev' ;
22+ const UNKNOWN_ROOT_REFERENCE = 'urn:cred:missing-root' ;
2023const W3C_CONTEXT = 'https://www.w3.org/2018/credentials/v1' ;
2124const ISSUER_ROOT = 'did:root' ;
2225const SUBJECT_DELEGATE = 'did:delegate' ;
@@ -124,6 +127,94 @@ describe('engine', () => {
124127 expect ( result . decision ) . toBe ( 'allow' ) ;
125128 expect ( result . evaluations ) . toHaveLength ( 1 ) ;
126129 } ) ;
130+
131+ it ( 'denies when a credential references a missing previous credential' , async ( ) => {
132+ const presentation = [
133+ {
134+ '@type' : [ VC_TYPE ] ,
135+ [ VC_VC ] : [
136+ {
137+ '@graph' : [
138+ {
139+ '@id' : LEAF_CREDENTIAL_ID ,
140+ '@type' : [ VC_TYPE_DELEGATION_CREDENTIAL ] ,
141+ [ VC_ISSUER ] : [ { '@id' : SUBJECT_DELEGATE } ] ,
142+ [ VC_SUBJECT ] : [ { '@id' : SUBJECT_HOLDER } ] ,
143+ [ VC_ROOT_CREDENTIAL_ID ] : [ { '@id' : LEAF_CREDENTIAL_ID } ] ,
144+ [ VC_PREVIOUS_CREDENTIAL_ID ] : [ { '@id' : MISSING_PREVIOUS_ID } ] ,
145+ } ,
146+ ] ,
147+ } ,
148+ ] ,
149+ [ SECURITY_PROOF ] : [
150+ {
151+ [ SECURITY_VERIFICATION_METHOD ] : [ { '@id' : `${ SUBJECT_DELEGATE } #key` } ] ,
152+ } ,
153+ ] ,
154+ } ,
155+ ] ;
156+ const contexts = new Map ( [ [ LEAF_CREDENTIAL_ID , [ W3C_CONTEXT ] ] ] ) ;
157+
158+ const result = await verifyVPWithDelegation ( {
159+ expandedPresentation : presentation ,
160+ credentialContexts : contexts ,
161+ } ) ;
162+
163+ expect ( result . decision ) . toBe ( 'deny' ) ;
164+ expect ( result . failures ?. [ 0 ] ?. code ) . toBe ( DelegationErrorCodes . MISSING_CREDENTIAL ) ;
165+ expect ( result . failures ?. [ 0 ] ?. message ) . toContain ( MISSING_PREVIOUS_ID ) ;
166+ } ) ;
167+
168+ it ( 'denies when a credential references a root credential that is not present' , async ( ) => {
169+ const presentation = [
170+ {
171+ '@type' : [ VC_TYPE ] ,
172+ [ VC_VC ] : [
173+ {
174+ '@graph' : [
175+ {
176+ '@id' : ROOT_CREDENTIAL_ID ,
177+ '@type' : [ VC_TYPE_DELEGATION_CREDENTIAL ] ,
178+ [ VC_ISSUER ] : [ { '@id' : ISSUER_ROOT } ] ,
179+ [ VC_SUBJECT ] : [ { '@id' : SUBJECT_DELEGATE } ] ,
180+ [ VC_ROOT_CREDENTIAL_ID ] : [ { '@id' : ROOT_CREDENTIAL_ID } ] ,
181+ } ,
182+ ] ,
183+ } ,
184+ {
185+ '@graph' : [
186+ {
187+ '@id' : LEAF_CREDENTIAL_ID ,
188+ '@type' : [ VC_TYPE_DELEGATION_CREDENTIAL ] ,
189+ [ VC_ISSUER ] : [ { '@id' : SUBJECT_DELEGATE } ] ,
190+ [ VC_SUBJECT ] : [ { '@id' : SUBJECT_HOLDER } ] ,
191+ [ VC_ROOT_CREDENTIAL_ID ] : [ { '@id' : UNKNOWN_ROOT_REFERENCE } ] ,
192+ [ VC_PREVIOUS_CREDENTIAL_ID ] : [ { '@id' : ROOT_CREDENTIAL_ID } ] ,
193+ } ,
194+ ] ,
195+ } ,
196+ ] ,
197+ [ SECURITY_PROOF ] : [
198+ {
199+ [ SECURITY_VERIFICATION_METHOD ] : [ { '@id' : `${ ISSUER_ROOT } #key` } ] ,
200+ } ,
201+ ] ,
202+ } ,
203+ ] ;
204+ const contexts = new Map ( [
205+ [ ROOT_CREDENTIAL_ID , [ W3C_CONTEXT ] ] ,
206+ [ LEAF_CREDENTIAL_ID , [ W3C_CONTEXT ] ] ,
207+ ] ) ;
208+
209+ const result = await verifyVPWithDelegation ( {
210+ expandedPresentation : presentation ,
211+ credentialContexts : contexts ,
212+ } ) ;
213+
214+ expect ( result . decision ) . toBe ( 'deny' ) ;
215+ expect ( result . failures ?. [ 0 ] ?. code ) . toBe ( DelegationErrorCodes . MISSING_CREDENTIAL ) ;
216+ expect ( result . failures ?. [ 0 ] ?. message ) . toContain ( UNKNOWN_ROOT_REFERENCE ) ;
217+ } ) ;
127218 it ( 'fails when credential contexts are missing' , async ( ) => {
128219 const result = await verifyVPWithDelegation ( {
129220 expandedPresentation : buildPresentation ( ) ,
0 commit comments