Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for error cases in did-resolver.spec.ts #561

Merged
merged 5 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉
# Decentralized Web Node (DWN) SDK <!-- omit in toc -->

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.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)
Expand Down
4 changes: 4 additions & 0 deletions src/core/dwn-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class DwnError extends Error {
export enum DwnErrorCode {
AuthenticateJwsMissing = 'AuthenticateJwsMissing',
AuthorizationUnknownAuthor = 'AuthorizationUnknownAuthor',
DidMethodNotSupported = 'DidMethodNotSupported',
DidNotString = 'DidNotString',
DidNotValid = 'DidNotValid',
DidResolutionFailed = 'DidResolutionFailed',
GeneralJwsVerifierInvalidSignature = 'GeneralJwsVerifierInvalidSignature',
GrantAuthorizationGrantExpired = 'GrantAuthorizationGrantExpired',
GrantAuthorizationGrantMissing = 'GrantAuthorizationGrantMissing',
Expand Down
5 changes: 3 additions & 2 deletions src/did/did-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions src/did/did.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';

/**
* DID related operations.
*/
Expand All @@ -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.DidNotString, `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}`);
}
}

Expand Down
60 changes: 60 additions & 0 deletions tests/did/did-resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -32,4 +33,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 = 'blah';
const didIonResolver = new DidIonResolver();
const didResolver = new DidResolver([didIonResolver]);

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:foo:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w';
const didIonResolver = new DidIonResolver();
const didResolver = new DidResolver([didIonResolver]);

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 () => {
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.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidResolutionFailed);

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.eventually.be.rejected.and.has.property('code', DwnErrorCode.DidResolutionFailed);

sinon.assert.calledOnce(cacheGetSpy);
sinon.assert.calledOnce(ionDidResolveSpy);

});

});
4 changes: 3 additions & 1 deletion tests/did/did.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
});
});