generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix bug in getTechPreviewDwnEndpoints() that sometimes returned only …
…1 endpoint Signed-off-by: Frank Hinek <[email protected]>
- Loading branch information
1 parent
5c08c5d
commit b259d24
Showing
6 changed files
with
318 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import type { DwnServiceEndpoint } from '../src/types.js'; | ||
|
||
import { expect } from 'chai'; | ||
|
||
import { DidIonApi } from '../src/did-ion.js'; | ||
|
||
describe('Tech Preview', function () { | ||
describe('generateDwnConfiguration()', () => { | ||
it('returns keys and services with two DWN URLs', async () => { | ||
const ionCreateOptions = await DidIonApi.generateDwnConfiguration([ | ||
'https://dwn.tbddev.test/dwn0', | ||
'https://dwn.tbddev.test/dwn1' | ||
]); | ||
|
||
expect(ionCreateOptions).to.have.property('keys'); | ||
expect(ionCreateOptions.keys).to.have.lengthOf(2); | ||
let encryptionKey = ionCreateOptions.keys!.find(key => key.id === 'enc'); | ||
expect(encryptionKey).to.exist; | ||
let authorizationKey = ionCreateOptions.keys!.find(key => key.id === 'authz'); | ||
expect(authorizationKey).to.exist; | ||
|
||
expect(ionCreateOptions).to.have.property('services'); | ||
expect(ionCreateOptions.services).to.have.lengthOf(1); | ||
|
||
const [ service ] = ionCreateOptions.services!; | ||
expect(service.id).to.equal('dwn'); | ||
expect(service).to.have.property('serviceEndpoint'); | ||
|
||
const serviceEndpoint = service.serviceEndpoint as DwnServiceEndpoint; | ||
expect(serviceEndpoint).to.have.property('nodes'); | ||
expect(serviceEndpoint.nodes).to.have.lengthOf(2); | ||
expect(serviceEndpoint).to.have.property('messageAuthorizationKeys'); | ||
expect(serviceEndpoint!.messageAuthorizationKeys![0]).to.equal(`#${authorizationKey!.id}`); | ||
expect(serviceEndpoint).to.have.property('recordEncryptionKeys'); | ||
expect(serviceEndpoint!.recordEncryptionKeys![0]).to.equal(`#${encryptionKey!.id}`); | ||
}); | ||
|
||
it('returns keys and services with one DWN URLs', async () => { | ||
const ionCreateOptions = await DidIonApi.generateDwnConfiguration([ | ||
'https://dwn.tbddev.test/dwn0' | ||
]); | ||
|
||
const [ service ] = ionCreateOptions.services!; | ||
expect(service.id).to.equal('dwn'); | ||
expect(service).to.have.property('serviceEndpoint'); | ||
|
||
const serviceEndpoint = service.serviceEndpoint as DwnServiceEndpoint; | ||
expect(serviceEndpoint).to.have.property('nodes'); | ||
expect(serviceEndpoint.nodes).to.have.lengthOf(1); | ||
expect(serviceEndpoint).to.have.property('messageAuthorizationKeys'); | ||
expect(serviceEndpoint).to.have.property('recordEncryptionKeys'); | ||
}); | ||
|
||
it('returns keys and services with 0 DWN URLs', async () => { | ||
const ionCreateOptions = await DidIonApi.generateDwnConfiguration([]); | ||
|
||
const [ service ] = ionCreateOptions.services!; | ||
expect(service.id).to.equal('dwn'); | ||
expect(service).to.have.property('serviceEndpoint'); | ||
|
||
const serviceEndpoint = service.serviceEndpoint as DwnServiceEndpoint; | ||
expect(serviceEndpoint).to.have.property('nodes'); | ||
expect(serviceEndpoint.nodes).to.have.lengthOf(0); | ||
expect(serviceEndpoint).to.have.property('messageAuthorizationKeys'); | ||
expect(serviceEndpoint).to.have.property('recordEncryptionKeys'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/// <reference types="chai" /> | ||
|
||
declare namespace Chai { | ||
|
||
// For BDD API | ||
interface Assertion extends LanguageChains, NumericComparison, TypeComparison { | ||
url: Assertion; | ||
} | ||
|
||
// For Assert API | ||
interface AssertStatic { | ||
isUrl: (actual: any) => Assertion; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import sinon from 'sinon'; | ||
import chai, { expect } from 'chai'; | ||
|
||
import { chaiUrl } from './test-utils/chai-plugins.js'; | ||
import { Web5 } from '../src/web5.js'; | ||
|
||
chai.use(chaiUrl); | ||
|
||
describe('Tech Preview', () => { | ||
|
||
describe('web5.getTechPreviewDwnEndpoints()', () => { | ||
|
||
let fetchStub: sinon.SinonStub; | ||
|
||
let mockDwnEndpoints: Array<string>; | ||
|
||
let tbdWellKnownOkResponse = { | ||
status : 200, | ||
ok : true, | ||
json : async () => Promise.resolve({ | ||
id : 'did:web:dwn.tbddev.org', | ||
service : [ | ||
{ | ||
id : '#dwn', | ||
serviceEndpoint : { | ||
nodes: mockDwnEndpoints | ||
}, | ||
type: 'DecentralizedWebNode' | ||
} | ||
] | ||
}) | ||
}; | ||
|
||
let tbdWellKnownBadResponse = { | ||
status : 400, | ||
ok : false | ||
}; | ||
|
||
let dwnServerHealthOkResponse = { | ||
status : 200, | ||
ok : true, | ||
json : async () => Promise.resolve({ok: true}) | ||
}; | ||
|
||
let dwnServerHealthBadResponse = { | ||
status : 400, | ||
ok : false | ||
}; | ||
|
||
beforeEach(() => { | ||
mockDwnEndpoints = [ | ||
'https://dwn.tbddev.test/dwn0', | ||
'https://dwn.tbddev.test/dwn1', | ||
'https://dwn.tbddev.test/dwn2', | ||
'https://dwn.tbddev.test/dwn3', | ||
'https://dwn.tbddev.test/dwn4', | ||
'https://dwn.tbddev.test/dwn5', | ||
'https://dwn.tbddev.test/dwn6' | ||
]; | ||
|
||
fetchStub = sinon.stub(globalThis as any, 'fetch'); | ||
|
||
fetchStub.callsFake((url) => { | ||
if (url === 'https://dwn.tbddev.org/.well-known/did.json') { | ||
return Promise.resolve(tbdWellKnownOkResponse); | ||
} else if (url.endsWith('/health')) { | ||
return Promise.resolve(dwnServerHealthOkResponse); | ||
} | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
fetchStub.restore(); | ||
}); | ||
|
||
it('returns an array', async () => { | ||
const dwnEndpoints = await Web5.getTechPreviewDwnEndpoints(); | ||
|
||
expect(dwnEndpoints).to.be.an('array'); | ||
}); | ||
|
||
it('returns valid DWN endpoint URLs', async () => { | ||
const dwnEndpoints = await Web5.getTechPreviewDwnEndpoints(); | ||
|
||
// There must be at one URL to check or else this test always passes. | ||
expect(dwnEndpoints).to.have.length.greaterThan(0); | ||
|
||
dwnEndpoints.forEach(endpoint => { | ||
expect(endpoint).to.be.a.url; | ||
expect(mockDwnEndpoints).to.include(endpoint); | ||
}); | ||
}); | ||
|
||
it('returns 2 DWN endpoints if at least 2 are healthy', async function() { | ||
const promises = Array(50).fill(0).map(() => Web5.getTechPreviewDwnEndpoints()); | ||
|
||
const results = await Promise.all(promises); | ||
|
||
results.forEach(result => { | ||
expect(result).to.be.an('array').that.has.lengthOf(2); | ||
}); | ||
}); | ||
|
||
it('returns 1 DWN endpoints if only 1 is healthy', async function() { | ||
mockDwnEndpoints = [ | ||
'https://dwn.tbddev.test/dwn0' | ||
]; | ||
|
||
const promises = Array(50).fill(0).map(() => Web5.getTechPreviewDwnEndpoints()); | ||
|
||
const results = await Promise.all(promises); | ||
|
||
results.forEach(result => { | ||
expect(result).to.be.an('array').that.has.lengthOf(1); | ||
}); | ||
}); | ||
|
||
it('returns 0 DWN endpoints if none are healthy', async function() { | ||
// Stub fetch to simulate dwn.tbddev.org responding but all of the hosted DWN Server reporting not healthy. | ||
fetchStub.restore(); | ||
fetchStub = sinon.stub(globalThis as any, 'fetch'); | ||
fetchStub.callsFake((url) => { | ||
if (url === 'https://dwn.tbddev.org/.well-known/did.json') { | ||
return Promise.resolve(tbdWellKnownOkResponse); | ||
} else if (url.endsWith('/health')) { | ||
return Promise.resolve(dwnServerHealthBadResponse); | ||
} | ||
}); | ||
|
||
const dwnEndpoints = await Web5.getTechPreviewDwnEndpoints(); | ||
|
||
expect(dwnEndpoints).to.be.an('array').that.has.lengthOf(0); | ||
}); | ||
|
||
it('returns 0 DWN endpoints if dwn.tbddev.org is not responding', async function() { | ||
// Stub fetch to simulate dwn.tbddev.org responding but all of the hosted DWN Server reporting not healthy. | ||
fetchStub.restore(); | ||
fetchStub = sinon.stub(globalThis as any, 'fetch'); | ||
fetchStub.callsFake((url) => { | ||
if (url === 'https://dwn.tbddev.org/.well-known/did.json') { | ||
return Promise.resolve(tbdWellKnownBadResponse); | ||
} | ||
}); | ||
|
||
const dwnEndpoints = await Web5.getTechPreviewDwnEndpoints(); | ||
|
||
expect(dwnEndpoints).to.be.an('array').that.has.lengthOf(0); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* Chai plugin for validating URLs. | ||
* | ||
* This function adds two types of URL validation methods to Chai: | ||
* 1. For the BDD "expect" API: `expect(string).to.be.a.url;` | ||
* 2. For the Assert API: `assert.isUrl(string);` | ||
* | ||
* @param {Chai.ChaiStatic} chai - The Chai library object. | ||
* @param {Chai.ChaiUtils} utils - The Chai Utilities object. | ||
* | ||
* @example | ||
* // BDD API example: | ||
* import chai, { expect } from 'chai'; | ||
* import chaiUrl from './chai-plugins.js'; | ||
* chai.use(chaiUrl); | ||
* | ||
* describe('My Test Suite', () => { | ||
* it('should validate the URL', () => { | ||
* const url = 'https://example.org'; | ||
* expect(url).to.be.a.url; | ||
* }); | ||
* }); | ||
* | ||
* @example | ||
* // Assert API example: | ||
* import chai, { assert } from 'chai'; | ||
* import chaiUrl from './chai-plugins.js'; | ||
* chai.use(chaiUrl); | ||
* | ||
* describe('My Test Suite', () => { | ||
* it('should validate the URL', () => { | ||
* const url = 'https://example.org'; | ||
* assert.isUrl(url); | ||
* }); | ||
* }); | ||
*/ | ||
export const chaiUrl: Chai.ChaiPlugin = function(chai: Chai.ChaiStatic, utils: Chai.ChaiUtils) { | ||
const assert = chai.assert; | ||
function isValidUrl() { | ||
const obj = utils.flag(this, 'object') as string; | ||
let isUrl = true; | ||
try { | ||
new URL(obj); | ||
} catch (err) { | ||
isUrl = false; | ||
} | ||
this.assert( | ||
isUrl, | ||
'expected #{this} to be a valid URL', | ||
'expected #{this} not to be a valid URL' | ||
); | ||
} | ||
|
||
// Add the property to the BDD "expect" API. | ||
utils.addProperty(chai.Assertion.prototype, 'url', isValidUrl); | ||
|
||
// Add the method to the Assert API. | ||
assert.isUrl = (actual) => (new chai.Assertion(actual)).to.be.a.url; | ||
}; |