From 89f0bd96c2517c266d59833a4e59505d9177cf52 Mon Sep 17 00:00:00 2001 From: Shobit Beltangdy Date: Sun, 15 Oct 2023 22:36:46 -0700 Subject: [PATCH 1/5] Add tests for error cases in did-resolver.spec.ts This commit adds new tests in did-resolver.spec.ts to cover some of the previously uncovered error-related branching paths. This commit contributes partly to issue #136. Based on the output of `npm run test:node` before vs. after this commit, the `% Branch` is now increased by 9.66 percentage points, and the `% Stmt` and `% Lines` by 3.29 percentage points. Before: ``` did-resolver.ts | 87.91 | 78.57 | 66.66 | 87.91 ``` After: ``` did-resolver.ts | 91.2 | 88.23 | 66.66 | 91.2 | ``` --- README.md | 2 +- tests/did/did-resolver.spec.ts | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index edac3b807..e844ad3b8 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-98.35%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.14%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-95.73%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-98.35%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-98.43%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.22%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-95.73%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-98.43%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/tests/did/did-resolver.spec.ts b/tests/did/did-resolver.spec.ts index 7301eb19b..542375119 100644 --- a/tests/did/did-resolver.spec.ts +++ b/tests/did/did-resolver.spec.ts @@ -32,4 +32,63 @@ describe('DidResolver', () => { sinon.assert.calledTwice(cacheGetSpy); // should try to fetch from cache both times sinon.assert.calledOnce(ionDidResolveSpy); // should only resolve using ION resolver once (the first time) }); + + it('should throw error when invalid DID is used', async () => { + const did = 'did:ion:invalidDid'; + const didIonResolver = new DidIonResolver(); + const didResolver = new DidResolver([didIonResolver]); + + await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + }); + + it('should throw error when unsupported DID method is used', async () => { + const did = 'did:unsupportedDidMethod:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; + const didIonResolver = new DidIonResolver(); + const didResolver = new DidResolver([didIonResolver]); + + await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + }); + + it('should throw error when resolution fails due to error in didResolutionMetadata', async () => { + const did = 'did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; + const didIonResolver = new DidIonResolver('unusedResolutionEndpoint'); + const didResolver = new DidResolver([didIonResolver]); + + const mockResolution = { + didDocument : 'any' as any, + didResolutionMetadata : { error: 'some error' }, + didDocumentMetadata : 'any' as any + }; + + const ionDidResolveSpy = sinon.stub(didIonResolver, 'resolve').resolves(mockResolution); + const cacheGetSpy = sinon.spy(didResolver['cache'], 'get'); + + await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + + sinon.assert.calledOnce(cacheGetSpy); + sinon.assert.calledOnce(ionDidResolveSpy); + + }); + + it('should throw error when resolution fails due to undefined didDocument', async () => { + const did = 'did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; + const didIonResolver = new DidIonResolver('unusedResolutionEndpoint'); + const didResolver = new DidResolver([didIonResolver]); + + const mockResolution = { + didDocument : undefined, + didResolutionMetadata : 'any' as any, + didDocumentMetadata : 'any' as any + }; + + const ionDidResolveSpy = sinon.stub(didIonResolver, 'resolve').resolves(mockResolution); + const cacheGetSpy = sinon.spy(didResolver['cache'], 'get'); + + await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + + sinon.assert.calledOnce(cacheGetSpy); + sinon.assert.calledOnce(ionDidResolveSpy); + + }); + }); From 36082ce2230a32422d295c7bf79a929efcade892 Mon Sep 17 00:00:00 2001 From: Shobit Beltangdy Date: Sun, 15 Oct 2023 22:36:46 -0700 Subject: [PATCH 2/5] Add tests for error cases in did-resolver.spec.ts This commit adds new tests in did-resolver.spec.ts to cover some of the previously uncovered error-related branching paths. This commit contributes partly to issue #136. Based on the output of `npm run test:node` before vs. after this commit, the `% Branch` is now increased by 9.66 percentage points, and the `% Stmt` and `% Lines` by 3.29 percentage points. Before: ``` did-resolver.ts | 87.91 | 78.57 | 66.66 | 87.91 ``` After: ``` did-resolver.ts | 92.34 | 94.11 | 66.66 | 92.34 | ``` --- src/core/dwn-error.ts | 3 +++ src/did/did-resolver.ts | 5 +++-- src/did/did.ts | 6 ++++-- tests/did/did-resolver.spec.ts | 13 +++++++------ tests/did/did.spec.ts | 4 +++- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index 11c3996ba..d20316e25 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -25,6 +25,9 @@ export enum DwnErrorCode { GrantAuthorizationNotGrantedToAuthor = 'GrantAuthorizationNotGrantedToAuthor', GrantAuthorizationGrantNotYetActive = 'GrantAuthorizationGrantNotYetActive', HdKeyDerivationPathInvalid = 'HdKeyDerivationPathInvalid', + DidMethodNotSupported = 'DidMethodNotSupported', + DidNotValid = 'DidNotValid', + DidResolutionFailed = 'DidResolutionFailed', PermissionsGrantGrantedByMismatch = 'PermissionsGrantGrantedByMismatch', PermissionsGrantScopeContextIdAndProtocolPath = 'PermissionsGrantScopeContextIdAndProtocolPath', PermissionsGrantScopeSchemaProhibitedFields = 'PermissionsGrantScopeSchemaProhibitedFields', diff --git a/src/did/did-resolver.ts b/src/did/did-resolver.ts index 27bd68ea6..944399504 100644 --- a/src/did/did-resolver.ts +++ b/src/did/did-resolver.ts @@ -5,6 +5,7 @@ import { Did } from './did.js'; import { DidIonResolver } from './did-ion-resolver.js'; import { DidKeyResolver } from './did-key-resolver.js'; import { MemoryCache } from '../utils/memory-cache.js'; +import { DwnError, DwnErrorCode } from '../core/dwn-error.js'; /** * A DID resolver that by default supports `did:key` and `did:ion` DIDs. @@ -49,7 +50,7 @@ export class DidResolver { const didResolver = this.didResolvers.get(didMethod); if (!didResolver) { - throw new Error(`${didMethod} DID method not supported`); + throw new DwnError(DwnErrorCode.DidMethodNotSupported, `${didMethod} DID method not supported`); } // use cached result if exists @@ -66,7 +67,7 @@ export class DidResolver { let errMsg = `Failed to resolve DID ${did}.`; errMsg += error ? ` Error: ${error}` : ''; - throw new Error(errMsg); + throw new DwnError(DwnErrorCode.DidResolutionFailed, errMsg); } return resolutionResult; diff --git a/src/did/did.ts b/src/did/did.ts index aeff9cbcd..8277801b7 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -1,3 +1,5 @@ +import { DwnError, DwnErrorCode } from '../core/dwn-error.js'; + /** * DID related operations. */ @@ -16,13 +18,13 @@ export class Did { */ public static validate(did: unknown): void { if (typeof did !== 'string') { - throw new Error(`DID is not string: ${did}`); + throw new DwnError(DwnErrorCode.DidNotValid, `DID is not string: ${did}`); } // eslint-disable-next-line const didRegex= /^did:([a-z0-9]+):((?:(?:[a-zA-Z0-9._-]|(?:%[0-9a-fA-F]{2}))*:)*((?:[a-zA-Z0-9._-]|(?:%[0-9a-fA-F]{2}))+))((;[a-zA-Z0-9_.:%-]+=[a-zA-Z0-9_.:%-]*)*)(\/[^#?]*)?([?][^#]*)?(#.*)?$/; if (!didRegex.test(did)) { - throw new TypeError(`DID is not a valid DID: ${did}`); + throw new DwnError(DwnErrorCode.DidNotValid, `DID is not a valid DID: ${did}`); } } diff --git a/tests/did/did-resolver.spec.ts b/tests/did/did-resolver.spec.ts index 542375119..e9cac319e 100644 --- a/tests/did/did-resolver.spec.ts +++ b/tests/did/did-resolver.spec.ts @@ -4,6 +4,7 @@ import chai, { expect } from 'chai'; import { DidIonResolver } from '../../src/did/did-ion-resolver.js'; import { DidResolver } from '../../src/did/did-resolver.js'; +import { DwnErrorCode } from '../../src/core/dwn-error.js'; // extends chai to test promises chai.use(chaiAsPromised); @@ -34,19 +35,19 @@ describe('DidResolver', () => { }); it('should throw error when invalid DID is used', async () => { - const did = 'did:ion:invalidDid'; + const did = 'blah'; const didIonResolver = new DidIonResolver(); const didResolver = new DidResolver([didIonResolver]); - await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + await expect(didResolver.resolve(did)).to.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidNotValid); }); it('should throw error when unsupported DID method is used', async () => { - const did = 'did:unsupportedDidMethod:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; + const did = 'did:foo:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w'; const didIonResolver = new DidIonResolver(); const didResolver = new DidResolver([didIonResolver]); - await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + await expect(didResolver.resolve(did)).to.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidMethodNotSupported); }); it('should throw error when resolution fails due to error in didResolutionMetadata', async () => { @@ -63,7 +64,7 @@ describe('DidResolver', () => { const ionDidResolveSpy = sinon.stub(didIonResolver, 'resolve').resolves(mockResolution); const cacheGetSpy = sinon.spy(didResolver['cache'], 'get'); - await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + await expect(didResolver.resolve(did)).to.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidResolutionFailed); sinon.assert.calledOnce(cacheGetSpy); sinon.assert.calledOnce(ionDidResolveSpy); @@ -84,7 +85,7 @@ describe('DidResolver', () => { const ionDidResolveSpy = sinon.stub(didIonResolver, 'resolve').resolves(mockResolution); const cacheGetSpy = sinon.spy(didResolver['cache'], 'get'); - await expect(didResolver.resolve(did)).to.be.rejectedWith(Error); + await expect(didResolver.resolve(did)).to.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidResolutionFailed); sinon.assert.calledOnce(cacheGetSpy); sinon.assert.calledOnce(ionDidResolveSpy); diff --git a/tests/did/did.spec.ts b/tests/did/did.spec.ts index 81cb9047f..53a94dc0c 100644 --- a/tests/did/did.spec.ts +++ b/tests/did/did.spec.ts @@ -3,6 +3,8 @@ import chai, { expect } from 'chai'; import { Did } from '../../src/did/did.js'; +import { DwnError } from '../../src/core/dwn-error.js'; + // extends chai to test promises chai.use(chaiAsPromised); @@ -15,6 +17,6 @@ describe('Did.validate', () => { it('should fail validation for valid DIDs', () => { expect(() => Did.validate(null)).to.throw(Error); - expect(() => Did.validate('did:123456789abcdefghijk')).to.throw(Error); + expect(() => Did.validate('did:123456789abcdefghijk')).to.throw(DwnError); }); }); From 404d85ff8564233c9b7ed91595b73d18cc733164 Mon Sep 17 00:00:00 2001 From: Shobit Beltangdy Date: Sun, 15 Oct 2023 22:36:46 -0700 Subject: [PATCH 3/5] Add tests for error cases in did-resolver.spec.ts This commit adds new tests in did-resolver.spec.ts to cover some of the previously uncovered error-related branching paths. This commit contributes partly to issue #136. Based on the output of `npm run test:node` before vs. after this commit, the `% Branch` is now increased by 9.66 percentage points, and the `% Stmt` and `% Lines` by 3.29 percentage points. Before: ``` did-resolver.ts | 87.91 | 78.57 | 66.66 | 87.91 ``` After: ``` did-resolver.ts | 92.34 | 94.11 | 66.66 | 92.34 | ``` --- src/core/dwn-error.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index d20316e25..e6fe16b15 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -15,6 +15,9 @@ export class DwnError extends Error { export enum DwnErrorCode { AuthenticateJwsMissing = 'AuthenticateJwsMissing', AuthorizationUnknownAuthor = 'AuthorizationUnknownAuthor', + DidMethodNotSupported = 'DidMethodNotSupported', + DidNotValid = 'DidNotValid', + DidResolutionFailed = 'DidResolutionFailed', GeneralJwsVerifierInvalidSignature = 'GeneralJwsVerifierInvalidSignature', GrantAuthorizationGrantExpired = 'GrantAuthorizationGrantExpired', GrantAuthorizationGrantMissing = 'GrantAuthorizationGrantMissing', From 3f612eaa76caa3ff831d683f4b13a5f59b1235a4 Mon Sep 17 00:00:00 2001 From: Shobit Beltangdy Date: Mon, 16 Oct 2023 11:18:07 -0700 Subject: [PATCH 4/5] Arrange new Did related error codes alphabetically --- src/core/dwn-error.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index e6fe16b15..d9245d9c0 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -28,9 +28,6 @@ export enum DwnErrorCode { GrantAuthorizationNotGrantedToAuthor = 'GrantAuthorizationNotGrantedToAuthor', GrantAuthorizationGrantNotYetActive = 'GrantAuthorizationGrantNotYetActive', HdKeyDerivationPathInvalid = 'HdKeyDerivationPathInvalid', - DidMethodNotSupported = 'DidMethodNotSupported', - DidNotValid = 'DidNotValid', - DidResolutionFailed = 'DidResolutionFailed', PermissionsGrantGrantedByMismatch = 'PermissionsGrantGrantedByMismatch', PermissionsGrantScopeContextIdAndProtocolPath = 'PermissionsGrantScopeContextIdAndProtocolPath', PermissionsGrantScopeSchemaProhibitedFields = 'PermissionsGrantScopeSchemaProhibitedFields', From 182080cda4d3efbe36fd805449cdf103c8544932 Mon Sep 17 00:00:00 2001 From: Shobit Beltangdy Date: Mon, 16 Oct 2023 11:59:51 -0700 Subject: [PATCH 5/5] Use a different code for DidNotString --- README.md | 2 +- src/core/dwn-error.ts | 1 + src/did/did.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e844ad3b8..34233ae44 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-98.43%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.22%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-95.73%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-98.43%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-98.46%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.29%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-95.73%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-98.46%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index d9245d9c0..cb88f2117 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -16,6 +16,7 @@ export enum DwnErrorCode { AuthenticateJwsMissing = 'AuthenticateJwsMissing', AuthorizationUnknownAuthor = 'AuthorizationUnknownAuthor', DidMethodNotSupported = 'DidMethodNotSupported', + DidNotString = 'DidNotString', DidNotValid = 'DidNotValid', DidResolutionFailed = 'DidResolutionFailed', GeneralJwsVerifierInvalidSignature = 'GeneralJwsVerifierInvalidSignature', diff --git a/src/did/did.ts b/src/did/did.ts index 8277801b7..2bf69138f 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -18,7 +18,7 @@ export class Did { */ public static validate(did: unknown): void { if (typeof did !== 'string') { - throw new DwnError(DwnErrorCode.DidNotValid, `DID is not string: ${did}`); + throw new DwnError(DwnErrorCode.DidNotString, `DID is not string: ${did}`); } // eslint-disable-next-line