Skip to content

Commit 2f87ea0

Browse files
authored
Merge pull request #423 from docknetwork/DCKA-5098-always-refresh-invalid-credentials
skip cached status for invalid credentials
2 parents f592401 + c7ba3ed commit 2f87ea0

2 files changed

Lines changed: 194 additions & 2 deletions

File tree

packages/core/src/credential-provider.test.ts

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ describe('CredentialProvider', () => {
227227
});
228228

229229
await provider.syncCredentialStatus({forceFetch: true});
230-
230+
231231
// Clear mocks
232232
jest.clearAllMocks();
233233

@@ -255,6 +255,196 @@ describe('CredentialProvider', () => {
255255
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
256256
});
257257

258+
it('should always refetch credentials with Invalid status even when cached', async () => {
259+
// First, create credentials with Invalid status
260+
jest
261+
.spyOn(credentialServiceRPC, 'verifyCredential')
262+
.mockImplementation(async () => {
263+
return {
264+
verified: false,
265+
error: 'Credential validation failed',
266+
};
267+
});
268+
269+
await provider.syncCredentialStatus({forceFetch: true});
270+
271+
// Verify initial calls
272+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
273+
274+
// Check that status is set to Invalid
275+
const initialStatusDoc = await wallet.getDocumentById(
276+
`${customerCredential.id}#status`,
277+
);
278+
expect(initialStatusDoc.status).toBe(CredentialStatus.Invalid);
279+
280+
// Clear mocks to track only subsequent calls
281+
jest.clearAllMocks();
282+
283+
// Now mock successful verification
284+
jest
285+
.spyOn(credentialServiceRPC, 'verifyCredential')
286+
.mockImplementation(async () => {
287+
return {
288+
verified: true,
289+
};
290+
});
291+
292+
// Call syncCredentialStatus again WITHOUT forceFetch
293+
// Invalid status should trigger refetch even without forceFetch
294+
const statusDocs = await provider.syncCredentialStatus({});
295+
296+
// Verify that verifyCredential was called again despite no forceFetch
297+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
298+
299+
// Check that status is now updated to Verified
300+
expect(statusDocs.length).toBe(2);
301+
for (const statusDoc of statusDocs) {
302+
expect(statusDoc.status).toBe(CredentialStatus.Verified);
303+
}
304+
});
305+
306+
it('should always refetch credentials with Pending status even when cached', async () => {
307+
// First, manually create a Pending status document
308+
const pendingStatusDoc = {
309+
type: 'CredentialStatus',
310+
id: `${customerCredential.id}#status`,
311+
createdAt: new Date().toISOString(),
312+
updatedAt: new Date().toISOString(),
313+
status: CredentialStatus.Pending,
314+
error: null,
315+
warning: null,
316+
};
317+
await wallet.updateDocument(pendingStatusDoc);
318+
319+
// Mock successful verification
320+
jest
321+
.spyOn(credentialServiceRPC, 'verifyCredential')
322+
.mockImplementation(async () => {
323+
return {
324+
verified: true,
325+
};
326+
});
327+
328+
// Call syncCredentialStatus without forceFetch
329+
// Pending status should trigger refetch
330+
const statusDocs = await provider.syncCredentialStatus({
331+
credentialIds: [customerCredential.id],
332+
});
333+
334+
// Verify that verifyCredential was called for the Pending credential
335+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(1);
336+
337+
// Check that status is now updated to Verified
338+
expect(statusDocs.length).toBe(1);
339+
expect(statusDocs[0].status).toBe(CredentialStatus.Verified);
340+
});
341+
342+
it('should cache Revoked status and not refetch within 24 hours', async () => {
343+
// First, create credentials with Revoked status
344+
jest
345+
.spyOn(credentialServiceRPC, 'verifyCredential')
346+
.mockImplementation(async () => {
347+
return {
348+
verified: false,
349+
error: 'Revocation check failed',
350+
};
351+
});
352+
353+
await provider.syncCredentialStatus({forceFetch: true});
354+
355+
// Verify initial calls
356+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
357+
358+
// Check that status is set to Revoked
359+
const revokedStatusDoc = await wallet.getDocumentById(
360+
`${customerCredential.id}#status`,
361+
);
362+
expect(revokedStatusDoc.status).toBe(CredentialStatus.Revoked);
363+
364+
// Clear mocks to track only subsequent calls
365+
jest.clearAllMocks();
366+
367+
// Call syncCredentialStatus again WITHOUT forceFetch
368+
// Revoked status should NOT trigger refetch within 24 hours
369+
const statusDocs = await provider.syncCredentialStatus({});
370+
371+
// Verify that verifyCredential was NOT called (cached)
372+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(0);
373+
374+
// Check that status remains Revoked
375+
expect(statusDocs.length).toBe(2);
376+
for (const statusDoc of statusDocs) {
377+
expect(statusDoc.status).toBe(CredentialStatus.Revoked);
378+
}
379+
});
380+
381+
it('should cache Verified status and not refetch within 24 hours', async () => {
382+
// First, create credentials with Verified status
383+
jest
384+
.spyOn(credentialServiceRPC, 'verifyCredential')
385+
.mockImplementation(async () => {
386+
return {
387+
verified: true,
388+
};
389+
});
390+
391+
await provider.syncCredentialStatus({forceFetch: true});
392+
393+
// Verify initial calls
394+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
395+
396+
// Check that status is set to Verified
397+
const verifiedStatusDoc = await wallet.getDocumentById(
398+
`${customerCredential.id}#status`,
399+
);
400+
expect(verifiedStatusDoc.status).toBe(CredentialStatus.Verified);
401+
402+
// Clear mocks to track only subsequent calls
403+
jest.clearAllMocks();
404+
405+
// Call syncCredentialStatus again WITHOUT forceFetch
406+
// Verified status should NOT trigger refetch within 24 hours
407+
const statusDocs = await provider.syncCredentialStatus({});
408+
409+
// Verify that verifyCredential was NOT called (cached)
410+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(0);
411+
412+
// Check that status remains Verified
413+
expect(statusDocs.length).toBe(2);
414+
for (const statusDoc of statusDocs) {
415+
expect(statusDoc.status).toBe(CredentialStatus.Verified);
416+
}
417+
});
418+
419+
it('should refetch credentials without status document', async () => {
420+
// Remove any existing status documents
421+
await wallet.removeDocument(`${customerCredential.id}#status`).catch(() => {});
422+
await wallet.removeDocument(`${biometricsBBSRevocation.id}#status`).catch(() => {});
423+
424+
// Mock successful verification
425+
jest
426+
.spyOn(credentialServiceRPC, 'verifyCredential')
427+
.mockImplementation(async () => {
428+
return {
429+
verified: true,
430+
};
431+
});
432+
433+
// Call syncCredentialStatus
434+
// Missing status docs should trigger fetch
435+
const statusDocs = await provider.syncCredentialStatus({});
436+
437+
// Verify that verifyCredential was called for both credentials
438+
expect(credentialServiceRPC.verifyCredential).toHaveBeenCalledTimes(2);
439+
440+
// Check that status documents were created
441+
expect(statusDocs.length).toBe(2);
442+
for (const statusDoc of statusDocs) {
443+
expect(statusDoc.status).toBe(CredentialStatus.Verified);
444+
expect(statusDoc.type).toBe('CredentialStatus');
445+
}
446+
});
447+
258448
afterEach(() => {
259449
(credentialServiceRPC.verifyCredential as any).mockReset();
260450
});

packages/core/src/credential-provider.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,9 @@ async function syncCredentialStatus({
276276

277277
statusDocs.push(statusDoc);
278278

279-
if (!statusDoc.status || statusDoc.status === CredentialStatus.Pending) {
279+
// Revoked and Expired statuses should be cached
280+
// The user can fore refresh that from the credentials screen
281+
if (!statusDoc.status || statusDoc.status === CredentialStatus.Invalid || statusDoc.status === CredentialStatus.Pending) {
280282
shouldFetch = true;
281283
}
282284

0 commit comments

Comments
 (0)