@learncard/learn-card-plugin 1.2.24 → 1.2.25
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/README.md +12 -7
- package/dist/learn-card-plugin.cjs.development.cjs +36 -36
- package/dist/learn-card-plugin.cjs.development.cjs.map +1 -1
- package/dist/learn-card-plugin.cjs.production.min.cjs.map +1 -1
- package/dist/learn-card-plugin.esm.js +36 -36
- package/dist/learn-card-plugin.esm.js.map +1 -1
- package/package.json +59 -57
- package/src/index.ts +20 -0
- package/src/test/verificationPrettifier.test.ts +412 -0
- package/src/test/verify.test.ts +118 -0
- package/src/types.ts +27 -0
- package/src/verificationPrettifier.ts +160 -0
- package/src/verify.ts +194 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { VerificationStatusEnum } from '@learncard/types';
|
|
2
|
+
|
|
3
|
+
import { prettifyVerificationItem, prettifyVerificationItems } from '../verificationPrettifier';
|
|
4
|
+
|
|
5
|
+
describe('verificationPrettifier', () => {
|
|
6
|
+
describe('SD-JWT-VC success checks', () => {
|
|
7
|
+
it('prettifies parse', () => {
|
|
8
|
+
expect(
|
|
9
|
+
prettifyVerificationItem({
|
|
10
|
+
check: 'parse',
|
|
11
|
+
message: 'parse',
|
|
12
|
+
status: VerificationStatusEnum.Success,
|
|
13
|
+
})
|
|
14
|
+
).toEqual({
|
|
15
|
+
check: 'Format',
|
|
16
|
+
message: 'Valid',
|
|
17
|
+
status: VerificationStatusEnum.Success,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('prettifies disclosure_hash_integrity', () => {
|
|
22
|
+
expect(
|
|
23
|
+
prettifyVerificationItem({
|
|
24
|
+
check: 'disclosure_hash_integrity',
|
|
25
|
+
message: 'disclosure_hash_integrity',
|
|
26
|
+
status: VerificationStatusEnum.Success,
|
|
27
|
+
})
|
|
28
|
+
).toEqual({
|
|
29
|
+
check: 'Selective Disclosure',
|
|
30
|
+
message: 'Claims verified',
|
|
31
|
+
status: VerificationStatusEnum.Success,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('prettifies issuer_resolved and issuer_signature', () => {
|
|
36
|
+
const resolved = prettifyVerificationItem({
|
|
37
|
+
check: 'issuer_resolved',
|
|
38
|
+
message: 'issuer_resolved',
|
|
39
|
+
status: VerificationStatusEnum.Success,
|
|
40
|
+
});
|
|
41
|
+
const signature = prettifyVerificationItem({
|
|
42
|
+
check: 'issuer_signature',
|
|
43
|
+
message: 'issuer_signature',
|
|
44
|
+
status: VerificationStatusEnum.Success,
|
|
45
|
+
});
|
|
46
|
+
expect(resolved.check).toBe('Issuer');
|
|
47
|
+
expect(resolved.message).toBe('Identified');
|
|
48
|
+
expect(signature.check).toBe('Signature');
|
|
49
|
+
expect(signature.message).toBe('Valid');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('prettifies expiration success', () => {
|
|
53
|
+
expect(
|
|
54
|
+
prettifyVerificationItem({
|
|
55
|
+
check: 'expiration',
|
|
56
|
+
message: 'expiration',
|
|
57
|
+
status: VerificationStatusEnum.Success,
|
|
58
|
+
})
|
|
59
|
+
).toEqual({
|
|
60
|
+
check: 'Expiration',
|
|
61
|
+
message: 'Does Not Expire',
|
|
62
|
+
status: VerificationStatusEnum.Success,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('SD-JWT-VC failure cases', () => {
|
|
68
|
+
it('extracts error code from message and labels it', () => {
|
|
69
|
+
expect(
|
|
70
|
+
prettifyVerificationItem({
|
|
71
|
+
check: 'signature_invalid',
|
|
72
|
+
message: 'signature_invalid: signature verification failed',
|
|
73
|
+
status: VerificationStatusEnum.Failed,
|
|
74
|
+
})
|
|
75
|
+
).toEqual({
|
|
76
|
+
check: 'Signature',
|
|
77
|
+
message: 'Invalid',
|
|
78
|
+
status: VerificationStatusEnum.Failed,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('handles expired error', () => {
|
|
83
|
+
expect(
|
|
84
|
+
prettifyVerificationItem({
|
|
85
|
+
check: 'expired',
|
|
86
|
+
message: 'expired: Credential expired at 2024-01-01T00:00:00.000Z',
|
|
87
|
+
status: VerificationStatusEnum.Failed,
|
|
88
|
+
})
|
|
89
|
+
).toEqual({
|
|
90
|
+
check: 'Expiration',
|
|
91
|
+
message: 'Expired',
|
|
92
|
+
status: VerificationStatusEnum.Failed,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('handles disclosure tampering', () => {
|
|
97
|
+
const result = prettifyVerificationItem({
|
|
98
|
+
check: 'disclosure_hash_mismatch',
|
|
99
|
+
message: 'disclosure_hash_mismatch: hash does not match',
|
|
100
|
+
status: VerificationStatusEnum.Failed,
|
|
101
|
+
});
|
|
102
|
+
expect(result.check).toBe('Selective Disclosure');
|
|
103
|
+
expect(result.message).toBe('Tampered');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('idempotency', () => {
|
|
108
|
+
it('leaves already-prettified items alone (capitalized check)', () => {
|
|
109
|
+
const item = {
|
|
110
|
+
check: 'Expiration',
|
|
111
|
+
message: 'Does Not Expire',
|
|
112
|
+
status: VerificationStatusEnum.Success,
|
|
113
|
+
};
|
|
114
|
+
expect(prettifyVerificationItem(item)).toEqual(item);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('leaves already-prettified items alone (space in check)', () => {
|
|
118
|
+
const item = {
|
|
119
|
+
check: 'Credential Type',
|
|
120
|
+
message: 'Verified',
|
|
121
|
+
status: VerificationStatusEnum.Success,
|
|
122
|
+
};
|
|
123
|
+
expect(prettifyVerificationItem(item)).toEqual(item);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('handles double-prettify (no double-application)', () => {
|
|
127
|
+
const raw = {
|
|
128
|
+
check: 'parse',
|
|
129
|
+
message: 'parse',
|
|
130
|
+
status: VerificationStatusEnum.Success,
|
|
131
|
+
};
|
|
132
|
+
const once = prettifyVerificationItem(raw);
|
|
133
|
+
const twice = prettifyVerificationItem(once);
|
|
134
|
+
expect(twice).toEqual(once);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('W3C VC checks', () => {
|
|
139
|
+
it('prettifies proof', () => {
|
|
140
|
+
const result = prettifyVerificationItem({
|
|
141
|
+
check: 'proof',
|
|
142
|
+
message: 'proof',
|
|
143
|
+
status: VerificationStatusEnum.Success,
|
|
144
|
+
});
|
|
145
|
+
expect(result.check).toBe('Proof');
|
|
146
|
+
expect(result.message).toBe('Valid');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('prettifies credentialStatus and credentialSchema', () => {
|
|
150
|
+
const status = prettifyVerificationItem({
|
|
151
|
+
check: 'credentialStatus',
|
|
152
|
+
message: 'credentialStatus',
|
|
153
|
+
status: VerificationStatusEnum.Success,
|
|
154
|
+
});
|
|
155
|
+
expect(status.check).toBe('Status');
|
|
156
|
+
expect(status.message).toBe('Active');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('preserves humanized messages from upstream verifiers (regression)', () => {
|
|
161
|
+
it('preserves W3C VC expiration date message', () => {
|
|
162
|
+
const item = {
|
|
163
|
+
check: 'expiration',
|
|
164
|
+
message: 'Expires 28 FEB 2023',
|
|
165
|
+
status: VerificationStatusEnum.Success,
|
|
166
|
+
};
|
|
167
|
+
expect(prettifyVerificationItem(item)).toEqual({
|
|
168
|
+
check: 'Expiration',
|
|
169
|
+
message: 'Expires 28 FEB 2023',
|
|
170
|
+
status: VerificationStatusEnum.Success,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('preserves W3C VC "Does Not Expire" message verbatim', () => {
|
|
175
|
+
const item = {
|
|
176
|
+
check: 'expiration',
|
|
177
|
+
message: 'Does Not Expire',
|
|
178
|
+
status: VerificationStatusEnum.Success,
|
|
179
|
+
};
|
|
180
|
+
expect(prettifyVerificationItem(item)).toEqual({
|
|
181
|
+
check: 'Expiration',
|
|
182
|
+
message: 'Does Not Expire',
|
|
183
|
+
status: VerificationStatusEnum.Success,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('preserves humanized proof message', () => {
|
|
188
|
+
const item = {
|
|
189
|
+
check: 'proof',
|
|
190
|
+
message: 'Valid',
|
|
191
|
+
status: VerificationStatusEnum.Success,
|
|
192
|
+
};
|
|
193
|
+
expect(prettifyVerificationItem(item)).toEqual({
|
|
194
|
+
check: 'Proof',
|
|
195
|
+
message: 'Valid',
|
|
196
|
+
status: VerificationStatusEnum.Success,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('still replaces raw lowercase message (SD-JWT-VC shape)', () => {
|
|
201
|
+
expect(
|
|
202
|
+
prettifyVerificationItem({
|
|
203
|
+
check: 'expiration',
|
|
204
|
+
message: 'expiration',
|
|
205
|
+
status: VerificationStatusEnum.Success,
|
|
206
|
+
})
|
|
207
|
+
).toEqual({
|
|
208
|
+
check: 'Expiration',
|
|
209
|
+
message: 'Does Not Expire',
|
|
210
|
+
status: VerificationStatusEnum.Success,
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('still replaces undefined message (SD-JWT-VC shape)', () => {
|
|
215
|
+
expect(
|
|
216
|
+
prettifyVerificationItem({
|
|
217
|
+
check: 'expiration',
|
|
218
|
+
status: VerificationStatusEnum.Success,
|
|
219
|
+
})
|
|
220
|
+
).toEqual({
|
|
221
|
+
check: 'Expiration',
|
|
222
|
+
message: 'Does Not Expire',
|
|
223
|
+
status: VerificationStatusEnum.Success,
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('failed items do not leak into SUCCESS_LABELS (regression)', () => {
|
|
229
|
+
it('failed expiration check does not become "Does Not Expire"', () => {
|
|
230
|
+
const item = {
|
|
231
|
+
check: 'expiration',
|
|
232
|
+
status: VerificationStatusEnum.Failed,
|
|
233
|
+
details: 'Expired 28 FEB 2023',
|
|
234
|
+
};
|
|
235
|
+
const result = prettifyVerificationItem(item);
|
|
236
|
+
expect(result.check).not.toBe('Expiration');
|
|
237
|
+
expect(result.message).not.toBe('Does Not Expire');
|
|
238
|
+
expect(result.status).toBe(VerificationStatusEnum.Failed);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('failed proof check does not become "Valid"', () => {
|
|
242
|
+
const item = {
|
|
243
|
+
check: 'proof',
|
|
244
|
+
status: VerificationStatusEnum.Failed,
|
|
245
|
+
details: 'signature mismatch',
|
|
246
|
+
};
|
|
247
|
+
const result = prettifyVerificationItem(item);
|
|
248
|
+
expect(result.message).not.toBe('Valid');
|
|
249
|
+
expect(result.status).toBe(VerificationStatusEnum.Failed);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('failed item with known ERROR_LABEL still gets prettified', () => {
|
|
253
|
+
expect(
|
|
254
|
+
prettifyVerificationItem({
|
|
255
|
+
check: 'expired',
|
|
256
|
+
message: 'expired',
|
|
257
|
+
status: VerificationStatusEnum.Failed,
|
|
258
|
+
})
|
|
259
|
+
).toEqual({
|
|
260
|
+
check: 'Expiration',
|
|
261
|
+
message: 'Expired',
|
|
262
|
+
status: VerificationStatusEnum.Failed,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('fallback behavior', () => {
|
|
268
|
+
it('leaves unknown checks alone', () => {
|
|
269
|
+
const item = {
|
|
270
|
+
check: 'unknown_check',
|
|
271
|
+
message: 'some message',
|
|
272
|
+
status: VerificationStatusEnum.Success,
|
|
273
|
+
};
|
|
274
|
+
expect(prettifyVerificationItem(item)).toEqual(item);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('handles empty check', () => {
|
|
278
|
+
const item = {
|
|
279
|
+
check: '',
|
|
280
|
+
message: 'just a message',
|
|
281
|
+
status: VerificationStatusEnum.Success,
|
|
282
|
+
};
|
|
283
|
+
expect(prettifyVerificationItem(item)).toEqual(item);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('real W3C verifier (learn-card/verify.ts) failure shape', () => {
|
|
288
|
+
it('prettifies a failure whose code lives in a spaced check + details (not message)', () => {
|
|
289
|
+
const result = prettifyVerificationItem({
|
|
290
|
+
check: 'signature_invalid: bad signature',
|
|
291
|
+
details: 'signature_invalid: bad signature',
|
|
292
|
+
status: VerificationStatusEnum.Failed,
|
|
293
|
+
});
|
|
294
|
+
expect(result.check).toBe('Signature');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('preserves humanized details on a failure rather than synthesizing a generic message', () => {
|
|
298
|
+
const result = prettifyVerificationItem({
|
|
299
|
+
check: 'expired',
|
|
300
|
+
details: 'Expired 28 FEB 2023',
|
|
301
|
+
status: VerificationStatusEnum.Failed,
|
|
302
|
+
});
|
|
303
|
+
expect(result.check).toBe('Expiration');
|
|
304
|
+
expect(result.details).toBe('Expired 28 FEB 2023');
|
|
305
|
+
expect(result.message).toBeUndefined();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('does not map a failed status row (check "status") to a generic label', () => {
|
|
309
|
+
const result = prettifyVerificationItem({
|
|
310
|
+
check: 'status',
|
|
311
|
+
details: 'Status: Revoked',
|
|
312
|
+
status: VerificationStatusEnum.Failed,
|
|
313
|
+
});
|
|
314
|
+
expect(result.details).toBe('Status: Revoked');
|
|
315
|
+
expect(result.message).not.toBe('Revoked or suspended');
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('W3C verifier status success normalization (NB2)', () => {
|
|
320
|
+
it('prettifies a lowercase "status" success check', () => {
|
|
321
|
+
const result = prettifyVerificationItem({
|
|
322
|
+
check: 'status',
|
|
323
|
+
message: 'status',
|
|
324
|
+
status: VerificationStatusEnum.Success,
|
|
325
|
+
});
|
|
326
|
+
expect(result.check).toBe('Status');
|
|
327
|
+
expect(result.message).toBe('Active');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('preserves a humanized status message like "Not Revoked"', () => {
|
|
331
|
+
const result = prettifyVerificationItem({
|
|
332
|
+
check: 'status',
|
|
333
|
+
message: 'Not Revoked',
|
|
334
|
+
status: VerificationStatusEnum.Success,
|
|
335
|
+
});
|
|
336
|
+
expect(result.check).toBe('Status');
|
|
337
|
+
expect(result.message).toBe('Not Revoked');
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe('double-prettify idempotency on real shapes', () => {
|
|
342
|
+
it('is idempotent on a failed signature shape', () => {
|
|
343
|
+
const raw = {
|
|
344
|
+
check: 'signature_invalid',
|
|
345
|
+
message: 'signature_invalid: bad signature',
|
|
346
|
+
status: VerificationStatusEnum.Failed,
|
|
347
|
+
};
|
|
348
|
+
const once = prettifyVerificationItem(raw);
|
|
349
|
+
const twice = prettifyVerificationItem(once);
|
|
350
|
+
expect(twice).toEqual(once);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('is idempotent on a status success shape', () => {
|
|
354
|
+
const raw = {
|
|
355
|
+
check: 'status',
|
|
356
|
+
message: 'status',
|
|
357
|
+
status: VerificationStatusEnum.Success,
|
|
358
|
+
};
|
|
359
|
+
const once = prettifyVerificationItem(raw);
|
|
360
|
+
const twice = prettifyVerificationItem(once);
|
|
361
|
+
expect(twice).toEqual(once);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('is idempotent on a failure with humanized details', () => {
|
|
365
|
+
const raw = {
|
|
366
|
+
check: 'expired',
|
|
367
|
+
details: 'Expired 28 FEB 2023',
|
|
368
|
+
status: VerificationStatusEnum.Failed,
|
|
369
|
+
};
|
|
370
|
+
const once = prettifyVerificationItem(raw);
|
|
371
|
+
const twice = prettifyVerificationItem(once);
|
|
372
|
+
expect(twice).toEqual(once);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
describe('batch', () => {
|
|
377
|
+
it('prettifies a full SD-JWT-VC verification result', () => {
|
|
378
|
+
const raw = [
|
|
379
|
+
{ check: 'parse', message: 'parse', status: VerificationStatusEnum.Success },
|
|
380
|
+
{
|
|
381
|
+
check: 'disclosure_hash_integrity',
|
|
382
|
+
message: 'disclosure_hash_integrity',
|
|
383
|
+
status: VerificationStatusEnum.Success,
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
check: 'issuer_resolved',
|
|
387
|
+
message: 'issuer_resolved',
|
|
388
|
+
status: VerificationStatusEnum.Success,
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
check: 'issuer_signature',
|
|
392
|
+
message: 'issuer_signature',
|
|
393
|
+
status: VerificationStatusEnum.Success,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
check: 'expiration',
|
|
397
|
+
message: 'expiration',
|
|
398
|
+
status: VerificationStatusEnum.Success,
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
const result = prettifyVerificationItems(raw);
|
|
402
|
+
expect(result.map(r => r.check)).toEqual([
|
|
403
|
+
'Format',
|
|
404
|
+
'Selective Disclosure',
|
|
405
|
+
'Issuer',
|
|
406
|
+
'Signature',
|
|
407
|
+
'Expiration',
|
|
408
|
+
]);
|
|
409
|
+
expect(result.every(r => r.message && r.message !== r.check)).toBe(true);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { VC, VerificationCheck, VerificationStatusEnum } from '@learncard/types';
|
|
2
|
+
|
|
3
|
+
import { verifyCredential } from '../verify';
|
|
4
|
+
|
|
5
|
+
const credential = {} as VC;
|
|
6
|
+
|
|
7
|
+
const prettify = async (verificationCheck: VerificationCheck) => {
|
|
8
|
+
const learnCard = {
|
|
9
|
+
invoke: {
|
|
10
|
+
verifyCredential: jest.fn().mockResolvedValue(verificationCheck),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return verifyCredential(learnCard as any)({} as any, credential, {}, true);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('verifyCredential prettify', () => {
|
|
18
|
+
it('formats a successful multi-purpose status check as active', async () => {
|
|
19
|
+
await expect(
|
|
20
|
+
prettify({
|
|
21
|
+
checks: ['proof', 'status'],
|
|
22
|
+
warnings: [],
|
|
23
|
+
errors: [],
|
|
24
|
+
status: [
|
|
25
|
+
{
|
|
26
|
+
entryType: 'BitstringStatusListEntry',
|
|
27
|
+
statusPurpose: 'revocation',
|
|
28
|
+
isSet: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
entryType: 'BitstringStatusListEntry',
|
|
32
|
+
statusPurpose: 'suspension',
|
|
33
|
+
isSet: false,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
).resolves.toEqual(
|
|
38
|
+
expect.arrayContaining([
|
|
39
|
+
{
|
|
40
|
+
status: VerificationStatusEnum.Success,
|
|
41
|
+
check: 'status',
|
|
42
|
+
message: 'Active',
|
|
43
|
+
},
|
|
44
|
+
])
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('formats a successful single-purpose status check with the purpose', async () => {
|
|
49
|
+
await expect(
|
|
50
|
+
prettify({
|
|
51
|
+
checks: ['credentialStatus'],
|
|
52
|
+
warnings: [],
|
|
53
|
+
errors: [],
|
|
54
|
+
status: [
|
|
55
|
+
{
|
|
56
|
+
entryType: 'BitstringStatusListEntry',
|
|
57
|
+
statusPurpose: 'revocation',
|
|
58
|
+
isSet: false,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
})
|
|
62
|
+
).resolves.toEqual([
|
|
63
|
+
{
|
|
64
|
+
status: VerificationStatusEnum.Success,
|
|
65
|
+
check: 'status',
|
|
66
|
+
message: 'Not Revoked',
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('formats set status entries as user-facing failures', async () => {
|
|
72
|
+
await expect(
|
|
73
|
+
prettify({
|
|
74
|
+
checks: ['proof'],
|
|
75
|
+
warnings: [],
|
|
76
|
+
errors: ['Credential is revoked.'],
|
|
77
|
+
status: [
|
|
78
|
+
{
|
|
79
|
+
entryType: 'BitstringStatusListEntry',
|
|
80
|
+
statusPurpose: 'revocation',
|
|
81
|
+
isSet: true,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
})
|
|
85
|
+
).resolves.toEqual(
|
|
86
|
+
expect.arrayContaining([
|
|
87
|
+
{
|
|
88
|
+
status: VerificationStatusEnum.Failed,
|
|
89
|
+
check: 'status',
|
|
90
|
+
details: 'Status: Revoked',
|
|
91
|
+
},
|
|
92
|
+
])
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('does not treat unrelated errors as status errors just because status data exists', async () => {
|
|
97
|
+
await expect(
|
|
98
|
+
prettify({
|
|
99
|
+
checks: [],
|
|
100
|
+
warnings: [],
|
|
101
|
+
errors: ['Boost Credential could not be verified.'],
|
|
102
|
+
status: [
|
|
103
|
+
{
|
|
104
|
+
entryType: 'BitstringStatusListEntry',
|
|
105
|
+
statusPurpose: 'revocation',
|
|
106
|
+
isSet: true,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
})
|
|
110
|
+
).resolves.toEqual([
|
|
111
|
+
{
|
|
112
|
+
status: VerificationStatusEnum.Failed,
|
|
113
|
+
check: 'Boost Credential could not be verified.',
|
|
114
|
+
details: 'Boost Credential could not be verified.',
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VC, VerificationCheck, VerificationItem } from '@learncard/types';
|
|
2
|
+
import { Plugin } from '@learncard/core';
|
|
3
|
+
import { ProofOptions } from '@learncard/didkit-plugin';
|
|
4
|
+
|
|
5
|
+
export type VerifyCredential = {
|
|
6
|
+
(vc: VC, options?: Partial<ProofOptions>): Promise<VerificationCheck>;
|
|
7
|
+
(vc: VC, options: Partial<ProofOptions>, prettify: true): Promise<VerificationItem[]>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/** @group LearnCard Plugin */
|
|
11
|
+
export type LearnCardPluginDependentMethods = {
|
|
12
|
+
verifyCredential: (vc: VC, options?: Partial<ProofOptions>) => Promise<VerificationCheck>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** @group LearnCard Plugin */
|
|
16
|
+
export type LearnCardPluginMethods = {
|
|
17
|
+
verifyCredential: VerifyCredential;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** @group LearnCard Plugin */
|
|
21
|
+
export type LearnCardPlugin = Plugin<
|
|
22
|
+
'LearnCard',
|
|
23
|
+
any,
|
|
24
|
+
LearnCardPluginMethods,
|
|
25
|
+
any,
|
|
26
|
+
LearnCardPluginDependentMethods
|
|
27
|
+
>;
|