Add comprehensive installation and setup documentation

- Add GETTING_STARTED.md with quick start guide and development modes
- Add INSTALL.sh automated installation script
- Add INSTALLATION_CHECKLIST.md, INSTALLATION_SUCCESS.md, and INSTALLATION_SUMMARY.md
- Add QUICK_REFERENCE.md for common commands
- Add SETUP_GUIDE.md with detailed setup instructions
- Update README.md with improved project overview
- Add did-wallet app dependencies and node_modules
This commit is contained in:
Dorian
2026-01-27 17:18:21 +00:00
parent a81f655133
commit 0d073fa89e
22658 changed files with 4494151 additions and 6 deletions
@@ -0,0 +1,22 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { authenticate } from '../../src/core/auth.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { expect } from 'chai';
import { DidDht, UniversalResolver } from '@web5/dids';
describe('Auth', () => {
describe('authenticate()', () => {
it('should throw if given JWS is `undefined`', () => __awaiter(void 0, void 0, void 0, function* () {
const jws = undefined;
yield expect(authenticate(jws, new UniversalResolver({ didResolvers: [DidDht] }))).to.be.rejectedWith(DwnErrorCode.AuthenticateJwsMissing);
}));
});
});
//# sourceMappingURL=auth.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"auth.spec.js","sourceRoot":"","sources":["../../../../tests/core/auth.spec.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEvD,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACpB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,0CAA0C,EAAE,GAAS,EAAE;YACxD,MAAM,GAAG,GAAG,SAAS,CAAC;YACtB,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,IAAI,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QAC7I,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,16 @@
import { expect } from 'chai';
import { messageReplyFromError } from '../../src/core/message-reply.js';
describe('Message Reply', () => {
it('handles non-Errors being thrown', () => {
let response;
try {
throw 'Some error message';
}
catch (e) {
response = messageReplyFromError(e, 500);
}
expect(response.status.code).to.eq(500);
expect(response.status.detail).to.eq('Error');
});
});
//# sourceMappingURL=message-reply.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message-reply.spec.js","sourceRoot":"","sources":["../../../../tests/core/message-reply.spec.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,IAAI,QAA6B,CAAC;QAClC,IAAI;YACF,MAAM,oBAAoB,CAAC;SAC5B;QAAC,OAAO,CAAU,EAAE;YACnB,QAAQ,GAAG,qBAAqB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC1C;QACD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,82 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { expect } from 'chai';
import { Message } from '../../src/core/message.js';
import { RecordsRead } from '../../src/index.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
describe('Message', () => {
describe('getSigner()', () => {
it('should return `undefined` if message is not signed', () => __awaiter(void 0, void 0, void 0, function* () {
const recordsRead = yield RecordsRead.create({
filter: {
recordId: yield TestDataGenerator.randomCborSha256Cid()
}
});
const author = Message.getSigner(recordsRead.message);
expect(author).to.be.undefined;
}));
});
describe('toJSON()', () => {
it('should return the message passed in to the constructor', () => __awaiter(void 0, void 0, void 0, function* () {
// create a message without `authorization`
const { message } = yield RecordsRead.create({
filter: {
recordId: yield TestDataGenerator.randomCborSha256Cid()
}
});
// NOTE: parse() calls constructor internally
const recordsRead = yield RecordsRead.parse(message);
expect(recordsRead.toJSON()).to.equal(message);
}));
});
describe('compareMessageTimestamp', () => {
it('should return 0 if age is same', () => __awaiter(void 0, void 0, void 0, function* () {
const dateModified = Time.getCurrentTimestamp();
const a = (yield TestDataGenerator.generateRecordsWrite({ messageTimestamp: dateModified })).message;
const b = JSON.parse(JSON.stringify(a)); // create a deep copy of `a`
const compareResult = yield Message.compareMessageTimestamp(a, b);
expect(compareResult).to.equal(0);
}));
});
describe('getNewestMessage', () => {
it('should return the newest message', () => __awaiter(void 0, void 0, void 0, function* () {
const a = (yield TestDataGenerator.generateRecordsWrite()).message;
yield Time.minimalSleep();
const b = (yield TestDataGenerator.generateRecordsWrite()).message;
yield Time.minimalSleep();
const c = (yield TestDataGenerator.generateRecordsWrite()).message; // c is the newest since its created last
const newestMessage = yield Message.getNewestMessage([b, c, a]);
expect(newestMessage.recordId).to.equal(c.recordId);
}));
});
describe('getOldestMessage', () => {
it('should return the newest message', () => __awaiter(void 0, void 0, void 0, function* () {
const a = (yield TestDataGenerator.generateRecordsWrite()).message;
yield Time.minimalSleep();
const b = (yield TestDataGenerator.generateRecordsWrite()).message;
yield Time.minimalSleep();
const c = (yield TestDataGenerator.generateRecordsWrite()).message; // c is the newest since its created last
const newestMessage = yield Message.getOldestMessage([b, c, a]);
expect(newestMessage.recordId).to.equal(a.recordId);
}));
});
describe('getCid()', () => {
it('encodedData does not have an effect on getCid()', () => __awaiter(void 0, void 0, void 0, function* () {
const { message } = yield TestDataGenerator.generateRecordsWrite();
const cid1 = yield Message.getCid(message);
const messageWithData = message;
messageWithData.encodedData = TestDataGenerator.randomString(25);
const cid2 = yield Message.getCid(messageWithData);
expect(cid1).to.equal(cid2);
}));
});
});
//# sourceMappingURL=message.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message.spec.js","sourceRoot":"","sources":["../../../../tests/core/message.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { expect } from 'chai';
import { ProtocolAuthorization } from '../../src/core/protocol-authorization.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
describe('ProtocolAuthorization', () => {
describe('authorizeWrite()', () => {
it('should throw if message references non-existent parent', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({
author: alice,
parentContextId: 'nonExistentParent',
});
// stub the message store
const messageStoreStub = stubInterface();
messageStoreStub.query.resolves({ messages: [] }); // simulate parent not in message store
yield expect(ProtocolAuthorization.authorizeWrite(alice.did, recordsWrite, messageStoreStub)).to.be.rejectedWith(DwnErrorCode.ProtocolAuthorizationParentNotFoundConstructingRecordChain);
}));
});
describe('getActionsSeekingARuleMatch()', () => {
it('should return empty array if unknown message method type is given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const deliberatelyCraftedInvalidMessage = {
message: {
descriptor: {
method: 'invalid-method'
},
}
};
const messageStoreStub = stubInterface();
expect(ProtocolAuthorization['getActionsSeekingARuleMatch'](alice.did, deliberatelyCraftedInvalidMessage, messageStoreStub)).to.be.empty;
}));
});
});
//# sourceMappingURL=protocol-authorization.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"protocol-authorization.spec.js","sourceRoot":"","sources":["../../../../tests/core/protocol-authorization.spec.ts"],"names":[],"mappings":";;;;;;;;;AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,wDAAwD,EAAE,GAAS,EAAE;YACtE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,qBAAqB,EAAE,CAAC;YAE9D,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,iBAAiB,CAAC,oBAAoB,CAAC;gBACpE,MAAM,EAAY,KAAK;gBACvB,eAAe,EAAG,mBAAmB;aACtC,CAAC,CAAC;YAEH,yBAAyB;YACzB,MAAM,gBAAgB,GAAG,aAAa,EAAgB,CAAC;YACvD,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,uCAAuC;YAE1F,MAAM,MAAM,CAAC,qBAAqB,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,YAAY,CAC9G,YAAY,CAAC,0DAA0D,CACxE,CAAC;QACJ,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,+BAA+B,EAAE,GAAG,EAAE;QAC7C,EAAE,CAAC,mEAAmE,EAAE,GAAS,EAAE;YACjF,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,qBAAqB,EAAE,CAAC;YAE9D,MAAM,iCAAiC,GAAG;gBACxC,OAAO,EAAE;oBACP,UAAU,EAAE;wBACV,MAAM,EAAE,gBAAgB;qBACzB;iBACF;aACK,CAAC;YAET,MAAM,gBAAgB,GAAG,aAAa,EAAgB,CAAC;YACvD,MAAM,CAAC,qBAAqB,CAAC,6BAA6B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,iCAAiC,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC;QAC3I,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,159 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { Dwn } from '../src/dwn.js';
import { Message } from '../src/core/message.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from './utils/test-data-generator.js';
import { TestEventStream } from './test-event-stream.js';
import { TestStores } from './test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testDwnClass() {
describe('DWN', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all stubs/spies/mocks/fakes from previous test
yield messageStore.clear(); // clean up before each test rather than after so that a test does not depend on other tests to do the clean up
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
describe('processMessage()', () => {
it('should process RecordsWrite message signed by a `did:key` DID', () => __awaiter(this, void 0, void 0, function* () {
// generate a `did:key` DID
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const reply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(reply.status.code).to.equal(202);
}));
it('should process RecordsQuery message', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { author, message } = yield TestDataGenerator.generateRecordsQuery({ author: alice });
const tenant = author.did;
const reply = yield dwn.processMessage(tenant, message);
expect(reply.status.code).to.equal(200);
expect(reply.entries).to.be.empty;
}));
it('should process an EventsGet message', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsGet({ author: alice });
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(200);
expect(reply.entries).to.be.empty;
expect(reply.data).to.not.exist;
}));
it('#191 - regression - should run JSON schema validation', () => __awaiter(this, void 0, void 0, function* () {
const invalidMessage = {
descriptor: {
interface: 'Records',
method: 'Read',
messageTimestamp: '2023-07-25T10:20:30.123456Z'
},
authorization: {}
};
const validateJsonSchemaSpy = sinon.spy(Message, 'validateJsonSchema');
const alice = yield TestDataGenerator.generateDidKeyPersona();
const reply = yield dwn.processMessage(alice.did, invalidMessage);
sinon.assert.calledOnce(validateJsonSchemaSpy);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(`must have required property 'filter'`);
}));
it('should throw 400 if given no interface or method found in message', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const reply1 = yield dwn.processMessage(alice.did, undefined); // missing message entirely, thus missing both `interface` and `method`
expect(reply1.status.code).to.equal(400);
expect(reply1.status.detail).to.contain('Both interface and method must be present');
const reply2 = yield dwn.processMessage(alice.did, { descriptor: { method: 'anyValue' } }); // missing `interface`
expect(reply2.status.code).to.equal(400);
expect(reply2.status.detail).to.contain('Both interface and method must be present');
const reply3 = yield dwn.processMessage(alice.did, { descriptor: { interface: 'anyValue' } }); // missing `method`
expect(reply3.status.code).to.equal(400);
expect(reply3.status.detail).to.contain('Both interface and method must be present');
}));
it('should throw 401 if message is targeted at a non active tenant', () => __awaiter(this, void 0, void 0, function* () {
// tenant gate that blocks everyone
const blockAllTenantGate = {
isActiveTenant() {
return __awaiter(this, void 0, void 0, function* () {
return { isActiveTenant: false };
});
}
};
const messageStoreStub = stubInterface();
const dataStoreStub = stubInterface();
const eventLogStub = stubInterface();
const eventStreamStub = stubInterface();
const dwnWithConfig = yield Dwn.create({
tenantGate: blockAllTenantGate,
messageStore: messageStoreStub,
dataStore: dataStoreStub,
eventLog: eventLogStub,
eventStream: eventStreamStub
});
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { author, message } = yield TestDataGenerator.generateRecordsQuery({ author: alice });
const tenant = author.did;
const reply = yield dwnWithConfig.processMessage(tenant, message);
expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.contain('not an active tenant');
}));
it('should throw 401 with custom message from tenant gate if provided', () => __awaiter(this, void 0, void 0, function* () {
// tenant gate that blocks everyone with a custom message
const customMessage = 'a custom not-an-active-tenant message';
const blockAllTenantGate = {
isActiveTenant() {
return __awaiter(this, void 0, void 0, function* () {
return { isActiveTenant: false, detail: customMessage };
});
}
};
const messageStoreStub = stubInterface();
const dataStoreStub = stubInterface();
const eventLogStub = stubInterface();
const eventStreamStub = stubInterface();
const dwnWithConfig = yield Dwn.create({
tenantGate: blockAllTenantGate,
messageStore: messageStoreStub,
dataStore: dataStoreStub,
eventLog: eventLogStub,
eventStream: eventStreamStub
});
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { author, message } = yield TestDataGenerator.generateRecordsQuery({ author: alice });
const tenant = author.did;
const reply = yield dwnWithConfig.processMessage(tenant, message);
expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.equal(customMessage);
}));
});
});
}
//# sourceMappingURL=dwn.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,81 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { EventEmitterStream } from '../../src/event-log/event-emitter-stream.js';
import { TestStores } from '../test-stores.js';
import { Message, TestDataGenerator, Time } from '../../src/index.js';
import sinon from 'sinon';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
chai.use(chaiAsPromised);
describe('EventEmitterStream', () => {
let messageStore;
before(() => {
({ messageStore } = TestStores.get());
});
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
messageStore.clear();
}));
after(() => __awaiter(void 0, void 0, void 0, function* () {
// Clean up after each test by closing and clearing the event stream
yield messageStore.close();
sinon.restore();
}));
it('should remove listeners when `close` method is used', () => __awaiter(void 0, void 0, void 0, function* () {
const eventStream = new EventEmitterStream();
const emitter = eventStream['eventEmitter'];
// count the `events` listeners, which represents all listeners
expect(emitter.listenerCount('did:alice_events')).to.equal(0);
const sub = yield eventStream.subscribe('did:alice', 'id', () => { });
expect(emitter.listenerCount('did:alice_events')).to.equal(1);
// close the subscription, which should remove the listener
yield sub.close();
expect(emitter.listenerCount('did:alice_events')).to.equal(0);
}));
it('logs message when the emitter experiences an error', () => __awaiter(void 0, void 0, void 0, function* () {
const testHandler = {
errorHandler: (_) => { },
};
const eventErrorSpy = sinon.spy(testHandler, 'errorHandler');
const eventStream = new EventEmitterStream({ errorHandler: testHandler.errorHandler });
const emitter = eventStream['eventEmitter'];
emitter.emit('error', new Error('random error'));
expect(eventErrorSpy.callCount).to.equal(1);
}));
it('does not emit messages if event stream is closed', () => __awaiter(void 0, void 0, void 0, function* () {
const testHandler = {
errorHandler: (_) => { },
};
const eventErrorSpy = sinon.spy(testHandler, 'errorHandler');
const eventStream = new EventEmitterStream({ errorHandler: testHandler.errorHandler });
const messageCids = [];
const handler = (_tenant, event, _indexes) => __awaiter(void 0, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
const subscription = yield eventStream.subscribe('did:alice', 'sub-1', handler);
// close eventStream
yield eventStream.close();
const message1 = yield TestDataGenerator.generateRecordsWrite({});
eventStream.emit('did:alice', { message: message1.message }, {});
const message2 = yield TestDataGenerator.generateRecordsWrite({});
eventStream.emit('did:alice', { message: message2.message }, {});
expect(eventErrorSpy.callCount).to.equal(2);
yield subscription.close();
yield Time.minimalSleep();
expect(messageCids).to.have.length(0);
}));
it('sets max listeners to 0 which represents infinity', () => __awaiter(void 0, void 0, void 0, function* () {
const eventStreamOne = new EventEmitterStream();
const emitterOne = eventStreamOne['eventEmitter'];
expect(emitterOne.getMaxListeners()).to.equal(0);
}));
});
//# sourceMappingURL=event-emitter-stream.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"event-emitter-stream.spec.js","sourceRoot":"","sources":["../../../../tests/event-log/event-emitter-stream.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { ArrayUtility } from '../../src/utils/array.js';
import { EventLogLevel } from '../../src/event-log/event-log-level.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
chai.use(chaiAsPromised);
let eventLog;
describe('EventLogLevel Tests', () => {
before(() => __awaiter(void 0, void 0, void 0, function* () {
eventLog = new EventLogLevel({ location: 'TEST-EVENTLOG' });
yield eventLog.open();
}));
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
yield eventLog.clear();
}));
after(() => __awaiter(void 0, void 0, void 0, function* () {
yield eventLog.close();
}));
describe('deleteEventsByCid', () => {
it('deletes all index related data', () => __awaiter(void 0, void 0, void 0, function* () {
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
const indexLevelDeleteSpy = sinon.spy(eventLog.index, 'delete');
yield eventLog.deleteEventsByCid(author.did, [messageCid]);
expect(indexLevelDeleteSpy.callCount).to.equal(1);
const keysAfterDelete = yield ArrayUtility.fromAsyncGenerator(eventLog.index.db.keys());
expect(keysAfterDelete.length).to.equal(0);
}));
});
});
//# sourceMappingURL=event-log-level.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"event-log-level.spec.js","sourceRoot":"","sources":["../../../../tests/event-log/event-log-level.spec.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAEzB,IAAI,QAAuB,CAAC;AAE5B,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,MAAM,CAAC,GAAS,EAAE;QAChB,QAAQ,GAAG,IAAI,aAAa,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;QAC5D,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC,CAAA,CAAC,CAAC;IAEH,UAAU,CAAC,GAAS,EAAE;QACpB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAA,CAAC,CAAC;IAEH,KAAK,CAAC,GAAS,EAAE;QACf,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAA,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,gCAAgC,EAAE,GAAS,EAAE;YAC9C,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,iBAAiB,CAAC,oBAAoB,EAAE,CAAC;YACzF,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;YAErD,MAAM,mBAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAEhE,MAAM,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAE,UAAU,CAAE,CAAC,CAAC;YAC7D,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAElD,MAAM,eAAe,GAAG,MAAM,YAAY,CAAC,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YACxF,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,236 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Message } from '../../src/core/message.js';
import { normalizeSchemaUrl } from '../../src/utils/url.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestStores } from '../test-stores.js';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
chai.use(chaiAsPromised);
export function testEventLog() {
describe('EventLog Tests', () => {
let eventLog;
before(() => __awaiter(this, void 0, void 0, function* () {
const stores = TestStores.get();
eventLog = stores.eventLog;
yield eventLog.open();
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield eventLog.close();
}));
it('separates events by tenant', () => __awaiter(this, void 0, void 0, function* () {
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const message1Index = yield recordsWrite.constructIndexes(true);
const messageCid = yield Message.getCid(message);
yield eventLog.append(author.did, messageCid, message1Index);
const { author: author2, message: message2, recordsWrite: recordsWrite2 } = yield TestDataGenerator.generateRecordsWrite();
const message2Index = yield recordsWrite2.constructIndexes(true);
const messageCid2 = yield Message.getCid(message2);
yield eventLog.append(author2.did, messageCid2, message2Index);
let { events } = yield eventLog.getEvents(author.did);
expect(events.length).to.equal(1);
expect(events[0]).to.equal(messageCid);
({ events } = yield eventLog.getEvents(author2.did));
expect(events.length).to.equal(1);
expect(events[0]).to.equal(messageCid2);
}));
it('returns events in the order that they were appended', () => __awaiter(this, void 0, void 0, function* () {
const expectedMessages = [];
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const messageIndex = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, messageIndex);
expectedMessages.push(messageCid);
for (let i = 0; i < 9; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
expectedMessages.push(messageCid);
}
const { events } = yield eventLog.getEvents(author.did);
expect(events.length).to.equal(expectedMessages.length);
for (let i = 0; i < 10; i += 1) {
expect(events[i]).to.equal(expectedMessages[i]);
}
}));
describe('getEventsAfter', () => {
it('gets all events for a tenant if a cursor is not provided', () => __awaiter(this, void 0, void 0, function* () {
const expectedMessages = [];
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const messageIndex = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, messageIndex);
expectedMessages.push(messageCid);
for (let i = 0; i < 9; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
expectedMessages.push(messageCid);
}
const { events } = yield eventLog.getEvents(author.did);
expect(events.length).to.equal(10);
for (let i = 0; i < events.length; i += 1) {
expect(events[i]).to.equal(expectedMessages[i]);
}
}));
it('gets all events that occurred after the cursor provided', () => __awaiter(this, void 0, void 0, function* () {
const author = yield TestDataGenerator.generateDidKeyPersona();
// create an initial record to and, issue a getEvents and grab the cursor
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
const { events: cursorEvents, cursor } = yield eventLog.getEvents(author.did);
expect(cursorEvents.length).to.equal(1);
expect(cursor).to.not.be.undefined;
expect(cursorEvents[0]).to.equal(messageCid);
// add more messages
const expectedMessages = [];
for (let i = 0; i < 5; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
expectedMessages.push(messageCid);
}
const { events } = yield eventLog.getEvents(author.did, cursor);
expect(events.length).to.equal(5);
for (let i = 0; i < events.length; i += 1) {
expect(events[i]).to.equal(expectedMessages[i], `${i}`);
}
}));
});
describe('deleteEventsByCid', () => {
it('finds and deletes events that whose values match the cids provided', () => __awaiter(this, void 0, void 0, function* () {
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
const deleteMessages = [];
for (let i = 0; i < 9; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
if (i % 2 === 0) {
deleteMessages.push(messageCid);
}
}
yield eventLog.deleteEventsByCid(author.did, deleteMessages);
const { events: remainingEvents } = yield eventLog.getEvents(author.did);
expect(remainingEvents.length).to.equal(10 - deleteMessages.length);
expect(remainingEvents).to.not.include.members(deleteMessages);
}));
it('skips if cid is invalid', () => __awaiter(this, void 0, void 0, function* () {
const cids = [];
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
cids.push(messageCid);
for (let i = 0; i < 3; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author });
const messageCid = yield Message.getCid(message);
const index = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, index);
cids.push(messageCid);
}
// does not error and deletes all messages
yield eventLog.deleteEventsByCid(author.did, [...cids, 'someInvalidCid']);
const { events: remainingEvents } = yield eventLog.getEvents(author.did);
expect(remainingEvents.length).to.equal(0);
}));
});
describe('query', () => {
it('returns filtered events in the order that they were appended', () => __awaiter(this, void 0, void 0, function* () {
const expectedMessages = [];
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ schema: 'schema1' });
const messageCid = yield Message.getCid(message);
const indexes = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, indexes);
expectedMessages.push(messageCid);
for (let i = 0; i < 5; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const messageCid = yield Message.getCid(message);
const indexes = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, indexes);
expectedMessages.push(messageCid);
}
// insert a record that will not show up in the filtered query.
// not inserted into expected events.
const { message: message2, recordsWrite: recordsWrite2 } = yield TestDataGenerator.generateRecordsWrite({ author });
const message2Cid = yield Message.getCid(message2);
const message2Indexes = yield recordsWrite2.constructIndexes(true);
yield eventLog.append(author.did, message2Cid, message2Indexes);
for (let i = 0; i < 5; i += 1) {
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const messageCid = yield Message.getCid(message);
const indexes = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, indexes);
expectedMessages.push(messageCid);
}
const { events } = yield eventLog.queryEvents(author.did, [{ schema: normalizeSchemaUrl('schema1') }]);
expect(events.length).to.equal(expectedMessages.length);
for (let i = 0; i < expectedMessages.length; i += 1) {
expect(events[i]).to.equal(expectedMessages[i]);
}
}));
it('returns filtered events after cursor', () => __awaiter(this, void 0, void 0, function* () {
const author = yield TestDataGenerator.generateDidKeyPersona();
// message 1 schema1
const { message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const messageCid = yield Message.getCid(message);
const indexes = yield recordsWrite.constructIndexes(true);
yield eventLog.append(author.did, messageCid, indexes);
// message 2 schema1
const { message: message2, recordsWrite: recordsWrite2 } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const message2Cid = yield Message.getCid(message2);
const message2Indexes = yield recordsWrite2.constructIndexes(true);
yield eventLog.append(author.did, message2Cid, message2Indexes);
// message 3 schema1
const { message: message3, recordsWrite: recordsWrite3 } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const message3Cid = yield Message.getCid(message3);
const message3Indexes = yield recordsWrite3.constructIndexes(true);
yield eventLog.append(author.did, message3Cid, message3Indexes);
// insert a record that will not show up in the filtered query.
// not inserted into expected events because it's not a part of the schema.
const { message: nonSchemaMessage1, recordsWrite: nonSchemaMessage1Write } = yield TestDataGenerator.generateRecordsWrite({ author });
const nonSchemaMessage1Cid = yield Message.getCid(nonSchemaMessage1);
const nonSchemaMessage1Indexes = yield nonSchemaMessage1Write.constructIndexes(true);
yield eventLog.append(author.did, nonSchemaMessage1Cid, nonSchemaMessage1Indexes);
// make initial query
let { events, cursor } = yield eventLog.queryEvents(author.did, [{ schema: normalizeSchemaUrl('schema1') }]);
expect(events.length).to.equal(3);
expect(events[0]).to.equal(yield Message.getCid(message));
expect(events[1]).to.equal(yield Message.getCid(message2));
expect(events[2]).to.equal(yield Message.getCid(message3));
// add an additional message to schema 1
const { message: message4, recordsWrite: recordsWrite4 } = yield TestDataGenerator.generateRecordsWrite({ author, schema: 'schema1' });
const message4Cid = yield Message.getCid(message4);
const message4Indexes = yield recordsWrite4.constructIndexes(true);
yield eventLog.append(author.did, message4Cid, message4Indexes);
// insert another non schema record
const { message: nonSchemaMessage2, recordsWrite: nonSchemaMessage2Write } = yield TestDataGenerator.generateRecordsWrite({ author });
const nonSchemaMessage2Cid = yield Message.getCid(nonSchemaMessage2);
const nonSchemaMessage2Indexes = yield nonSchemaMessage2Write.constructIndexes(true);
yield eventLog.append(author.did, nonSchemaMessage2Cid, nonSchemaMessage2Indexes);
({ events } = yield eventLog.queryEvents(author.did, [{ schema: normalizeSchemaUrl('schema1') }], cursor));
expect(events.length).to.equal(1);
expect(events[0]).to.equal(yield Message.getCid(message4));
}));
});
});
}
//# sourceMappingURL=event-log.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,96 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { TestEventStream } from '../test-event-stream.js';
import { Message, TestDataGenerator, Time } from '../../src/index.js';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
chai.use(chaiAsPromised);
describe('EventStream', () => {
// saving the original `console.error` function to re-assign after tests complete
const originalConsoleErrorFunction = console.error;
let eventStream;
before(() => __awaiter(void 0, void 0, void 0, function* () {
eventStream = TestEventStream.get();
yield eventStream.open();
// do not print the console error statements from the emitter error
console.error = (_) => { };
}));
after(() => __awaiter(void 0, void 0, void 0, function* () {
console.error = originalConsoleErrorFunction;
// Clean up after each test by closing and clearing the event stream
yield eventStream.close();
}));
it('emits all messages to each subscriptions', () => __awaiter(void 0, void 0, void 0, function* () {
const messageCids1 = [];
const handler1 = (_tenant, event, _indexes) => __awaiter(void 0, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids1.push(messageCid);
});
const messageCids2 = [];
const handler2 = (_tenant, event, _indexes) => __awaiter(void 0, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids2.push(messageCid);
});
const subscription1 = yield eventStream.subscribe('did:alice', 'sub-1', handler1);
const subscription2 = yield eventStream.subscribe('did:alice', 'sub-2', handler2);
const message1 = yield TestDataGenerator.generateRecordsWrite({});
const message1Cid = yield Message.getCid(message1.message);
eventStream.emit('did:alice', { message: message1.message }, {});
const message2 = yield TestDataGenerator.generateRecordsWrite({});
const message2Cid = yield Message.getCid(message2.message);
eventStream.emit('did:alice', { message: message2.message }, {});
const message3 = yield TestDataGenerator.generateRecordsWrite({});
const message3Cid = yield Message.getCid(message3.message);
eventStream.emit('did:alice', { message: message3.message }, {});
yield subscription1.close();
yield subscription2.close();
yield Time.minimalSleep();
expect(messageCids1).to.have.members([message1Cid, message2Cid, message3Cid]);
expect(messageCids2).to.have.members([message1Cid, message2Cid, message3Cid]);
}));
it('does not emit messages if subscription is closed', () => __awaiter(void 0, void 0, void 0, function* () {
const messageCids = [];
const handler = (_tenant, event, _indexes) => __awaiter(void 0, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
const subscription = yield eventStream.subscribe('did:alice', 'sub-1', handler);
const message1 = yield TestDataGenerator.generateRecordsWrite({});
const message1Cid = yield Message.getCid(message1.message);
eventStream.emit('did:alice', { message: message1.message }, {});
yield subscription.close();
const message2 = yield TestDataGenerator.generateRecordsWrite({});
eventStream.emit('did:alice', { message: message2.message }, {});
yield Time.minimalSleep();
expect(messageCids).to.have.members([message1Cid]);
}));
it('does not emit messages if event stream is closed', () => __awaiter(void 0, void 0, void 0, function* () {
const messageCids = [];
const handler = (_tenant, event, _indexes) => __awaiter(void 0, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
const subscription = yield eventStream.subscribe('did:alice', 'sub-1', handler);
// close eventStream
yield eventStream.close();
const message1 = yield TestDataGenerator.generateRecordsWrite({});
eventStream.emit('did:alice', { message: message1.message }, {});
const message2 = yield TestDataGenerator.generateRecordsWrite({});
eventStream.emit('did:alice', { message: message2.message }, {});
yield subscription.close();
yield Time.minimalSleep();
expect(messageCids).to.have.length(0);
}));
});
//# sourceMappingURL=event-stream.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"event-stream.spec.js","sourceRoot":"","sources":["../../../../tests/event-log/event-stream.spec.ts"],"names":[],"mappings":"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"}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,580 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import minimalProtocolDefinition from '../vectors/protocol-definitions/minimal.json' assert { type: 'json' };
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { Jws } from '../../src/utils/jws.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { Time } from '../../src/utils/time.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { DwnInterfaceName, DwnMethodName, Encoder, PermissionsProtocol, ProtocolsConfigure } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testOwnerDelegatedGrant() {
describe('owner delegated grant', () => __awaiter(this, void 0, void 0, function* () {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
describe('RecordsWrite.parse()', () => __awaiter(this, void 0, void 0, function* () {
it('should throw if a message invokes an owner-delegated grant (ID) but the owner-delegated grant is not given', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const appX = yield TestDataGenerator.generatePersona();
// Alice grants App X to write as her for the chat protocol
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: 'chat'
};
const grantToAppX = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow App X to write as me in chat protocol',
grantedTo: appX.did,
scope,
signer: Jws.createSigner(alice)
});
// Bob creates a RecordsWrite message
const recordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
dataFormat: 'application/octet-stream',
data: TestDataGenerator.randomBytes(10),
});
// App X signs over Bob's RecordsWrite as DWN owner but does not include the delegated grant (we remove it below)
yield recordsWrite.signAsOwnerDelegate(Jws.createSigner(appX), grantToAppX.dataEncodedMessage);
delete recordsWrite.message.authorization.ownerDelegatedGrant; // intentionally remove `ownerDelegatedGrant`
const parsePromise = RecordsWrite.parse(recordsWrite.message);
yield expect(parsePromise).to.be.rejectedWith(DwnErrorCode.RecordsOwnerDelegatedGrantAndIdExistenceMismatch);
}));
it('should throw if a message includes an owner-delegated grant but does not reference it in owner signature', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const appX = yield TestDataGenerator.generatePersona();
// Alice grants App X to write as her for the chat protocol
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: 'chat'
};
const grantToAppX = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow App X to write as me in chat protocol',
grantedTo: appX.did,
scope,
signer: Jws.createSigner(alice)
});
// Bob creates a RecordsWrite message
const recordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
dataFormat: 'application/octet-stream',
data: TestDataGenerator.randomBytes(10),
});
// App X attempts to sign over Bob's RecordsWrite as the DWN owner by including an owner-delegated grant
// but does not reference the grant ID in owner signature (we remove it below)
yield recordsWrite.signAsOwnerDelegate(Jws.createSigner(appX), grantToAppX.dataEncodedMessage);
const ownerSignaturePayloadCopy = Object.assign({}, recordsWrite.ownerSignaturePayload);
delete ownerSignaturePayloadCopy.delegatedGrantId; // intentionally remove `delegatedGrantId` in ownerSignature
recordsWrite.message.authorization.ownerSignature.payload = Encoder.stringToBase64Url(JSON.stringify(ownerSignaturePayloadCopy));
const parsePromise = RecordsWrite.parse(recordsWrite.message);
yield expect(parsePromise).to.be.rejectedWith(DwnErrorCode.RecordsOwnerDelegatedGrantAndIdExistenceMismatch);
}));
}));
it('should only allow correct entity invoking an owner-delegated grant to write', () => __awaiter(this, void 0, void 0, function* () {
var _a;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a delegated grant for app X to write in the protocol
// 3. A third party (Bob) authors a RecordsWrite
// 4. Sanity test that Bob's RecordsWrite cannot be written to Alice's DWN by itself
// 5. Verify that App Y cannot write Bob's message in Alice's DWN by invoking the delegated grant for App X.
// 6. Verify that App X can successfully write Bob's message in Alice's DWN by invoking an owner-delegated grant
// 7. Sanity verify the RecordsWrite written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
const appY = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a delegated grant for app X to write in the protocol
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
// 3. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 4. Sanity test that Bob's RecordsWrite cannot be written to Alice's DWN by itself
const unauthorizedRecordsWriteReply = yield dwn.processMessage(alice.did, bobRecordsWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(unauthorizedRecordsWriteReply.status.code).to.equal(401);
// 5. Verify that App Y cannot write Bob's message in Alice's DWN by invoking the delegated grant for App X.
const appYAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appYAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appY), appXGrant.dataEncodedMessage);
const appYWriteReply = yield dwn.processMessage(alice.did, appYAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appYWriteReply.status.code).to.equal(400);
expect(appYWriteReply.status.detail).to.contain(DwnErrorCode.RecordsOwnerDelegatedGrantGrantedToAndOwnerSignatureMismatch);
// 6. Verify that App X can successfully write Bob's message in Alice's DWN by invoking an owner-delegated grant
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrant.dataEncodedMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(202);
// 7. Sanity verify the RecordsWrite written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_a = recordsQueryReply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1);
const fetchedEntry = recordsQueryReply.entries[0];
expect(fetchedEntry.encodedData).to.equal(Encoder.bytesToBase64Url(bobRecordsWriteBytes));
const fetchedRecordsWrite = yield RecordsWrite.parse(fetchedEntry);
expect(fetchedRecordsWrite.author).to.equal(bob.did);
}));
it('should not allow entity using a non-delegated grant as an owner-delegated grant to invoke write', () => __awaiter(this, void 0, void 0, function* () {
var _b;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a non-delegated grant for app X to write in the protocol
// 3. A third party (Bob) authors a RecordsWrite
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an non-delegated grant
// 5. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a non-delegated grant for app X to write in the protocol
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
// delegated : true, // intentionally commented out to show that this is not a delegated grant
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
// 3. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an non-delegated grant
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrant.dataEncodedMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(400);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.RecordsOwnerDelegatedGrantNotADelegatedGrant);
// 5. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_b = recordsQueryReply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(0);
}));
it('should fail if owner-delegated grant invoked for write has a mismatching interface method or protocol scope', () => __awaiter(this, void 0, void 0, function* () {
var _c;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a delegated grant for app X to read in the protocol
// 3. Alice creates a delegated grant for app X to write in another random protocol
// 4. A third party (Bob) authors a RecordsWrite
// 5. Verify that App X cannot write Bob's message in Alice's DWN by invoking a delegated grant for RecordsRead
// 6. Verify that App X cannot write Bob's message in Alice's DWN by invoking a delegated grant for writing in another random protocol
// 7. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a delegated grant for app X to read in the protocol
const readScope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Read,
protocol
};
const appXGrantToRead = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: readScope,
signer: Jws.createSigner(alice)
});
// 3. Alice creates a delegated grant for app X to write in another random protocol
const randomProtocolWriteScope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: `random-protocol`
};
const appXGrantToWriteInRandomProtocol = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: randomProtocolWriteScope,
signer: Jws.createSigner(alice)
});
// 4. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 5. Verify that App X cannot write Bob's message in Alice's DWN by invoking a delegated grant for RecordsRead
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrantToRead.dataEncodedMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(401);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationMethodMismatch);
// 6. Verify that App X cannot write Bob's message in Alice's DWN by invoking a delegated grant for writing in another random protocol
const appXAugmentedWrite2 = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite2.signAsOwnerDelegate(Jws.createSigner(appX), appXGrantToWriteInRandomProtocol.dataEncodedMessage);
const appXWriteReply2 = yield dwn.processMessage(alice.did, appXAugmentedWrite2.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply2.status.code).to.equal(401);
expect(appXWriteReply2.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeProtocolMismatch);
// 7. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_c = recordsQueryReply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(0);
}));
it('should fail RecordsWrite if presented with an owner-delegated grant with invalid grantor signature', () => __awaiter(this, void 0, void 0, function* () {
var _d;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a delegated grant for App X to write as Alice, but with invalid signature
// 3. A third party (Bob) authors a RecordsWrite
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an owner-delegated grant with invalid signature
// 5. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a delegated grant for App X to write as Alice, but with invalid signature
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
const appXGrantMessage = appXGrant.dataEncodedMessage;
appXGrantMessage.authorization.signature.signatures[0].signature = yield TestDataGenerator.randomSignatureString();
// 3. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an owner-delegated grant with invalid signature
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrantMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(401);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.GeneralJwsVerifierInvalidSignature);
// 5. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_d = recordsQueryReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(0);
}));
it('should fail RecordsWrite if grant ID in owner signature payload and CID of owner-delegated grant are mismatching', () => __awaiter(this, void 0, void 0, function* () {
var _e;
// scenario:
// 1. Alice installs a protocol
// 2. Creates two delegated grant for App X to write as Alice
// 3. A third party (Bob) authors a RecordsWrite
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an owner-delegated grant with the wrong ID
// 5. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Creates two delegated grant for App X to write as Alice
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
yield Time.minimalSleep();
const appXGrant2 = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
// 3. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an owner-delegated grant with the wrong ID
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrant.dataEncodedMessage);
appXAugmentedWrite.message.authorization.ownerDelegatedGrant = appXGrant2.dataEncodedMessage; // intentionally have a mismatching grant
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(400);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.RecordsOwnerDelegatedGrantCidMismatch);
// 5. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_e = recordsQueryReply.entries) === null || _e === void 0 ? void 0 : _e.length).to.equal(0);
}));
it('should fail RecordsWrite if owner-delegated grant is revoked', () => __awaiter(this, void 0, void 0, function* () {
var _f;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a delegated grant for App X to write as Alice
// 3. Alice revokes the grant
// 4. A third party (Bob) authors a RecordsWrite
// 5. Verify that App X cannot write Bob's message in Alice's DWN by invoking a revoked owner-delegated grant
// 6. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a delegated grant for App X to write as Alice
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
const grantDataStream = DataStream.fromBytes(appXGrant.permissionGrantBytes);
const permissionGrantWriteReply = yield dwn.processMessage(alice.did, appXGrant.recordsWrite.message, { dataStream: grantDataStream });
expect(permissionGrantWriteReply.status.code).to.equal(202);
// 3. Alice revokes the grant
const permissionRevoke = yield PermissionsProtocol.createRevocation({
signer: Jws.createSigner(alice),
grantId: appXGrant.recordsWrite.message.recordId
});
const revocationDataStream = DataStream.fromBytes(permissionRevoke.permissionRevocationBytes);
const permissionRevokeReply = yield dwn.processMessage(alice.did, permissionRevoke.recordsWrite.message, { dataStream: revocationDataStream });
expect(permissionRevokeReply.status.code).to.equal(202);
// 4. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 5. Verify that App X cannot write Bob's message in Alice's DWN by invoking a revoked owner-delegated grant
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrant.dataEncodedMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(401);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantRevoked);
// 6. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_f = recordsQueryReply.entries) === null || _f === void 0 ? void 0 : _f.length).to.equal(0);
}));
it('should fail RecordsWrite if owner-delegated grant is expired', () => __awaiter(this, void 0, void 0, function* () {
var _g;
// scenario:
// 1. Alice installs a protocol
// 2. Alice creates a delegated grant for App X to write as Alice, but make it expired
// 3. A third party (Bob) authors a RecordsWrite
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an expired owner-delegated grant
// 5. Sanity verify the RecordsWrite is not written by App X
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const appX = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol
const protocolDefinition = minimalProtocolDefinition;
const protocol = minimalProtocolDefinition.protocol;
const protocolsConfig = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition: protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a delegated grant for App X to write as Alice, but make it expired
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol
};
const appXGrant = yield PermissionsProtocol.createGrant({
delegated: true,
dateExpires: Time.getCurrentTimestamp(),
grantedTo: appX.did,
scope: scope,
signer: Jws.createSigner(alice)
});
// 3. A third party (Bob) authors a RecordsWrite
const bobRecordsWriteBytes = new TextEncoder().encode('message from Bob');
const bobRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol,
protocolPath: 'foo',
dataFormat: 'any-format',
data: bobRecordsWriteBytes
});
// 4. Verify that App X cannot write Bob's message in Alice's DWN by invoking an expired owner-delegated grant
const appXAugmentedWrite = yield RecordsWrite.parse(bobRecordsWrite.message);
yield appXAugmentedWrite.signAsOwnerDelegate(Jws.createSigner(appX), appXGrant.dataEncodedMessage);
const appXWriteReply = yield dwn.processMessage(alice.did, appXAugmentedWrite.message, { dataStream: DataStream.fromBytes(bobRecordsWriteBytes) });
expect(appXWriteReply.status.code).to.equal(401);
expect(appXWriteReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantExpired);
// 5. Sanity verify the RecordsWrite is not written by App X
const recordsQuery = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_g = recordsQueryReply.entries) === null || _g === void 0 ? void 0 : _g.length).to.equal(0);
}));
}));
}
//# sourceMappingURL=owner-delegated-grant.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,200 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import chaiAsPromised from 'chai-as-promised';
import minimalProtocolDefinition from '../vectors/protocol-definitions/minimal.json' assert { type: 'json' };
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { ArrayUtility } from '../../src/utils/array.js';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { Jws } from '../../src/utils/jws.js';
import { RecordsRead } from '../../src/interfaces/records-read.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testOwnerSignature() {
describe('owner signature', () => __awaiter(this, void 0, void 0, function* () {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should use `ownerSignature` for authorization when it is given - flat-space', () => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
// scenario: Alice fetch a message authored by Bob from Bob's DWN and retains (writes) it in her DWN
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Bob writes a message to his DWN
const { message, dataStream, dataBytes } = yield TestDataGenerator.generateRecordsWrite({ author: bob, published: true });
const writeReply = yield dwn.processMessage(bob.did, message, { dataStream });
expect(writeReply.status.code).to.equal(202);
// Alice fetches the message from Bob's DWN
const recordsRead = yield RecordsRead.create({
filter: { recordId: message.recordId },
signer: Jws.createSigner(alice)
});
const readReply = yield dwn.processMessage(bob.did, recordsRead.message);
expect(readReply.status.code).to.equal(200);
expect(readReply.record).to.exist;
expect((_a = readReply.record) === null || _a === void 0 ? void 0 : _a.descriptor).to.exist;
// Alice augments Bob's message as an external owner
const _c = readReply.record, { data } = _c, messageFetched = __rest(_c, ["data"]); // remove data from message
const ownerSignedMessage = yield RecordsWrite.parse(messageFetched);
yield ownerSignedMessage.signAsOwner(Jws.createSigner(alice));
// Test that Alice can successfully retain/write Bob's message to her DWN
const aliceDataStream = readReply.record.data;
const aliceWriteReply = yield dwn.processMessage(alice.did, ownerSignedMessage.message, { dataStream: aliceDataStream });
expect(aliceWriteReply.status.code).to.equal(202);
// Test that Bob's message can be read from Alice's DWN
const readReply2 = yield dwn.processMessage(alice.did, recordsRead.message);
expect(readReply2.status.code).to.equal(200);
expect(readReply2.record).to.exist;
expect((_b = readReply2.record) === null || _b === void 0 ? void 0 : _b.descriptor).to.exist;
const dataFetched = yield DataStream.toBytes(readReply2.record.data);
expect(ArrayUtility.byteArraysEqual(dataFetched, dataBytes)).to.be.true;
}));
it('should use `ownerSignature` for authorization when it is given - protocol-space', () => __awaiter(this, void 0, void 0, function* () {
var _d;
// scenario: Alice and Bob both have the same protocol which does NOT allow external entities to write,
// but Alice can store a message authored by Bob as a owner in her own DWN
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = minimalProtocolDefinition;
// Alice installs the protocol
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Sanity test that Bob cannot write to a protocol record to Alice's DWN
const bobRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
const recordsWriteReply = yield dwn.processMessage(alice.did, bobRecordsWrite.message, { dataStream: bobRecordsWrite.dataStream });
expect(recordsWriteReply.status.code).to.equal(401);
// Skipping Alice fetching the message from Bob's DWN (as this is tested already in the flat-space test)
// Alice augments Bob's message as an external owner
const ownerSignedMessage = yield RecordsWrite.parse(bobRecordsWrite.message);
yield ownerSignedMessage.signAsOwner(Jws.createSigner(alice));
// Test that Alice can successfully retain/write Bob's message to her DWN
const aliceDataStream = DataStream.fromBytes(bobRecordsWrite.dataBytes);
const aliceWriteReply = yield dwn.processMessage(alice.did, ownerSignedMessage.message, { dataStream: aliceDataStream });
expect(aliceWriteReply.status.code).to.equal(202);
// Test that Bob's message can be read from Alice's DWN
const recordsRead = yield RecordsRead.create({
filter: { recordId: bobRecordsWrite.message.recordId },
signer: Jws.createSigner(alice)
});
const readReply = yield dwn.processMessage(alice.did, recordsRead.message);
expect(readReply.status.code).to.equal(200);
expect(readReply.record).to.exist;
expect((_d = readReply.record) === null || _d === void 0 ? void 0 : _d.descriptor).to.exist;
const dataFetched = yield DataStream.toBytes(readReply.record.data);
expect(ArrayUtility.byteArraysEqual(dataFetched, bobRecordsWrite.dataBytes)).to.be.true;
}));
it('should throw if `ownerSignature` in `authorization` is mismatching with the tenant - flat-space', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Carol attempts to store a message with Alice being the owner, and should fail
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// Bob creates a message, we skip writing to bob's DWN because that's orthogonal to this test
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: bob, published: true });
// Alice augments Bob's message as an external owner, we also skipping writing to Alice's DWN because that's also orthogonal to this test
yield recordsWrite.signAsOwner(Jws.createSigner(alice));
// Test that Carol is not able to store the message Alice created
const carolWriteReply = yield dwn.processMessage(carol.did, recordsWrite.message, { dataStream });
expect(carolWriteReply.status.code).to.equal(401);
expect(carolWriteReply.status.detail).to.contain('RecordsWriteOwnerAndTenantMismatch');
}));
it('should throw if `ownerSignature` in `authorization` is mismatching with the tenant - protocol-space', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice, Bob, and Carol all have the same protocol which does NOT allow external entities to write,
// scenario: Carol attempts to store a message with Alice being the owner, and should fail
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = minimalProtocolDefinition;
// Bob creates a message, we skip writing to Bob's DWN because that's orthogonal to this test
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
// Alice augments Bob's message as an external owner, we also skipping writing to Alice's DWN because that's also orthogonal to this test
yield recordsWrite.signAsOwner(Jws.createSigner(alice));
// Carol installs the protocol
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: carol,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(carol.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Test that Carol is not able to store the message Alice created
const carolWriteReply = yield dwn.processMessage(carol.did, recordsWrite.message, { dataStream });
expect(carolWriteReply.status.code).to.equal(401);
expect(carolWriteReply.status.detail).to.contain('RecordsWriteOwnerAndTenantMismatch');
}));
it('should throw if `ownerSignature` fails verification', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Malicious Bob attempts to retain an externally authored message in Alice's DWN by providing an invalid `ownerSignature`
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Bob creates a message, we skip writing to bob's DWN because that's orthogonal to this test
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: bob, published: true });
// Bob pretends to be Alice by adding an invalid `ownerSignature`
// We do this by creating a valid signature first then swap out with an invalid one
yield recordsWrite.signAsOwner(Jws.createSigner(alice));
const bobSignature = recordsWrite.message.authorization.signature.signatures[0];
recordsWrite.message.authorization.ownerSignature.signatures[0].signature = bobSignature.signature; // invalid `ownerSignature`
// Test that Bob is not able to store the message in Alice's DWN using an invalid `ownerSignature`
const aliceWriteReply = yield dwn.processMessage(alice.did, recordsWrite.message, { dataStream });
expect(aliceWriteReply.status.detail).to.contain(DwnErrorCode.GeneralJwsVerifierInvalidSignature);
}));
}));
}
//# sourceMappingURL=owner-signature.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,320 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { Jws } from '../../src/utils/jws.js';
import { PermissionsProtocol } from '../../src/protocols/permissions.js';
import { RecordsRead } from '../../src/interfaces/records-read.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, RecordsQuery, RecordsWrite, Time } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testPermissions() {
describe('permissions', () => __awaiter(this, void 0, void 0, function* () {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should support permission management through use of Request, Grants, and Revocations', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// 1. Verify anyone (Bob) can send a permission request to Alice
// 2. Alice queries her DWN for new permission requests
// 3. Verify a non-owner cannot create a grant for Bob in Alice's DWN
// 4. Alice creates a permission grant for Bob in her DWN
// 5. Verify that Bob can query the permission grant from Alice's DWN (even though Alice can also send it directly to Bob)
// 6. Verify that any third-party can fetch revocation of the grant and find it is still active (not revoked)
// 7. Verify that non-owner cannot revoke the grant
// 8. Alice revokes the permission grant for Bob
// 9. Verify that any third-party can fetch the revocation status of the permission grant
var _a, _b, _c, _d, _e;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Verify anyone (Bob) can send a permission request to Alice
const permissionScope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: `any-protocol`
};
const requestToAlice = yield PermissionsProtocol.createRequest({
signer: Jws.createSigner(bob),
description: `Requesting to write to Alice's DWN`,
delegated: false,
scope: permissionScope
});
const requestWriteReply = yield dwn.processMessage(alice.did, requestToAlice.recordsWrite.message, { dataStream: DataStream.fromBytes(requestToAlice.permissionRequestBytes) });
expect(requestWriteReply.status.code).to.equal(202);
// 2. Alice queries her DWN for new permission requests
const requestQuery = yield RecordsQuery.create({
signer: Jws.createSigner(alice),
filter: {
protocolPath: PermissionsProtocol.requestPath,
protocol: PermissionsProtocol.uri,
dateUpdated: { from: Time.createOffsetTimestamp({ seconds: -1 * 60 * 60 * 24 }) } // last 24 hours
}
});
const requestQueryReply = yield dwn.processMessage(alice.did, requestQuery.message);
const requestFromBob = (_a = requestQueryReply.entries) === null || _a === void 0 ? void 0 : _a[0];
expect(requestQueryReply.status.code).to.equal(200);
expect((_b = requestQueryReply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(1);
expect(requestFromBob.recordId).to.equal(requestToAlice.recordsWrite.message.recordId);
// 3. Verify a non-owner cannot create a grant for Bob in Alice's DWN
const decodedRequest = PermissionsProtocol.parseRequest(requestFromBob.encodedData);
const unauthorizedGrantWrite = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(bob),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: decodedRequest.scope
});
const unauthorizedGrantWriteReply = yield dwn.processMessage(alice.did, unauthorizedGrantWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(unauthorizedGrantWrite.permissionGrantBytes) });
expect(unauthorizedGrantWriteReply.status.code).to.equal(401);
expect(unauthorizedGrantWriteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 4. Alice creates a permission grant for Bob in her DWN
const grantWrite = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: decodedRequest.scope
});
const grantWriteReply = yield dwn.processMessage(alice.did, grantWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(grantWrite.permissionGrantBytes) });
expect(grantWriteReply.status.code).to.equal(202);
// 5. Verify that Bob can query the permission grant from Alice's DWN (even though Alice can also send it directly to Bob)
const grantQuery = yield RecordsQuery.create({
signer: Jws.createSigner(bob),
filter: {
protocolPath: PermissionsProtocol.grantPath,
protocol: PermissionsProtocol.uri,
dateUpdated: { from: Time.createOffsetTimestamp({ seconds: -1 * 60 * 60 * 24 }) } // last 24 hours
}
});
const grantQueryReply = yield dwn.processMessage(alice.did, grantQuery.message);
const grantFromBob = (_c = grantQueryReply.entries) === null || _c === void 0 ? void 0 : _c[0];
expect(grantQueryReply.status.code).to.equal(200);
expect((_d = grantQueryReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(1);
expect(grantFromBob.recordId).to.equal(grantWrite.recordsWrite.message.recordId);
// 6. Verify that any third-party can fetch revocation of the grant and find it is still active (not revoked)
const revocationRead = yield RecordsRead.create({
signer: Jws.createSigner(bob),
filter: {
contextId: grantWrite.recordsWrite.message.contextId,
protocolPath: PermissionsProtocol.revocationPath
}
});
const revocationReadReply = yield dwn.processMessage(alice.did, revocationRead.message);
expect(revocationReadReply.status.code).to.equal(404);
// 7. Verify that non-owner cannot revoke the grant
const unauthorizedRevokeWrite = yield PermissionsProtocol.createRevocation({
signer: Jws.createSigner(bob),
grantId: grantWrite.recordsWrite.message.recordId,
dateRevoked: Time.getCurrentTimestamp()
});
const unauthorizedRevokeWriteReply = yield dwn.processMessage(alice.did, unauthorizedRevokeWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(unauthorizedRevokeWrite.permissionRevocationBytes) });
expect(unauthorizedRevokeWriteReply.status.code).to.equal(401);
expect(unauthorizedGrantWriteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 8. Alice revokes the permission grant for Bob
const revokeWrite = yield PermissionsProtocol.createRevocation({
signer: Jws.createSigner(alice),
grantId: grantWrite.recordsWrite.message.recordId,
dateRevoked: Time.getCurrentTimestamp()
});
const revokeWriteReply = yield dwn.processMessage(alice.did, revokeWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(revokeWrite.permissionRevocationBytes) });
expect(revokeWriteReply.status.code).to.equal(202);
// 9. Verify that any third-party can fetch the revocation status of the permission grant
const revocationReadReply2 = yield dwn.processMessage(alice.did, revocationRead.message);
expect(revocationReadReply2.status.code).to.equal(200);
expect((_e = revocationReadReply2.record) === null || _e === void 0 ? void 0 : _e.recordId).to.equal(revokeWrite.recordsWrite.message.recordId);
}));
describe('schema validation', () => {
it('should reject with 400 if a permission message fails schema validation', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Verify that a permission request is rejected with the data payload is invalid
// 2. Verify that a permission grant is rejected with the data payload is invalid
// 3. Write a valid permission grant
// 4. Verify that a permission revocation is rejected with the data payload is invalid
// 5. Verify that an unexpected/unknown permission record is rejected
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Verify that a permission request is rejected with the data payload is invalid
const invalidPermissionRequestData = {
description: 'missing required properties such as `scope`'
};
const requestBytes = Encoder.objectToBytes(invalidPermissionRequestData);
const requestRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.requestPath,
dataFormat: 'application/json',
data: requestBytes,
});
const requestWriteReply = yield dwn.processMessage(alice.did, requestRecordsWrite.message, { dataStream: DataStream.fromBytes(requestBytes) });
expect(requestWriteReply.status.code).to.equal(400);
expect(requestWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorFailure);
// 2. Verify that a permission grant is rejected with the data payload is invalid
const invalidPermissionGrantData = {
description: 'missing required properties such as `scope`'
};
const grantBytes = Encoder.objectToBytes(invalidPermissionGrantData);
const grantRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(alice),
recipient: bob.did,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.grantPath,
dataFormat: 'application/json',
data: grantBytes,
});
const invalidGrantWriteReply = yield dwn.processMessage(alice.did, grantRecordsWrite.message, { dataStream: DataStream.fromBytes(grantBytes) });
expect(invalidGrantWriteReply.status.code).to.equal(400);
expect(invalidGrantWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorFailure);
// 3. Write a valid permission grant
const grantWrite = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: { interface: DwnInterfaceName.Records, method: DwnMethodName.Write }
});
const grantWriteReply = yield dwn.processMessage(alice.did, grantWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(grantWrite.permissionGrantBytes) });
expect(grantWriteReply.status.code).to.equal(202);
// 4. Verify that a permission revocation is rejected with the data payload is invalid
const invalidPermissionRevocationData = {
unknownProperty: 'unknown property',
};
const revocationBytes = Encoder.objectToBytes(invalidPermissionRevocationData);
const revocationRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(alice),
parentContextId: grantWrite.recordsWrite.message.recordId,
protocol: PermissionsProtocol.uri,
protocolPath: PermissionsProtocol.revocationPath,
dataFormat: 'application/json',
data: revocationBytes,
});
const revokeWriteReply = yield dwn.processMessage(alice.did, revocationRecordsWrite.message, { dataStream: DataStream.fromBytes(revocationBytes) });
expect(revokeWriteReply.status.code).to.equal(400);
expect(revokeWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorAdditionalPropertyNotAllowed);
// 5. Verify that an unexpected/unknown permission record is rejected
const unknownPermissionRecordData = {
unknownProperty: 'unknown property',
};
const unknownRecordBytes = Encoder.objectToBytes(unknownPermissionRecordData);
const unknownRecordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(alice),
protocol: PermissionsProtocol.uri,
protocolPath: 'unknown-path',
dataFormat: 'application/json',
data: revocationBytes,
});
expect(() => PermissionsProtocol.validateSchema(unknownRecordsWrite.message, unknownRecordBytes))
.to.throw(DwnErrorCode.PermissionsProtocolValidateSchemaUnexpectedRecord);
}));
it('ensures that `schema` and protocol related fields `protocol`, `contextId` or `protocolPath` are not both present', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// `schema` and `protocol` may not both be present in grant `scope`
const schemaAndProtocolGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
schema: 'some-schema',
protocol: 'some-protocol'
}
});
const schemaAndProtocolGrantReply = yield dwn.processMessage(alice.did, schemaAndProtocolGrant.recordsWrite.message, { dataStream: DataStream.fromBytes(schemaAndProtocolGrant.permissionGrantBytes) });
expect(schemaAndProtocolGrantReply.status.code).to.eq(400);
expect(schemaAndProtocolGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties);
// `schema` and `contextId` may not both be present in grant `scope`
const schemaAndContextIdGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
schema: 'some-schema',
contextId: 'some-context-id'
}
});
const schemaAndContextIdGrantReply = yield dwn.processMessage(alice.did, schemaAndContextIdGrant.recordsWrite.message, { dataStream: DataStream.fromBytes(schemaAndContextIdGrant.permissionGrantBytes) });
expect(schemaAndContextIdGrantReply.status.code).to.eq(400);
expect(schemaAndContextIdGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties);
// `schema` and `protocolPath` may not both be present in grant `scope`
const schemaAndProtocolPathGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
schema: 'some-schema',
protocolPath: 'some-protocol-path'
}
});
const schemaAndProtocolPathGrantReply = yield dwn.processMessage(alice.did, schemaAndProtocolPathGrant.recordsWrite.message, { dataStream: DataStream.fromBytes(schemaAndProtocolPathGrant.permissionGrantBytes) });
expect(schemaAndProtocolPathGrantReply.status.code).to.eq(400);
expect(schemaAndProtocolPathGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties);
}));
it('ensures that `contextId` and `protocolPath` are not both present in grant scope', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const grant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write',
grantedTo: bob.did,
scope: {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: 'some-protocol',
contextId: 'some-context-id',
protocolPath: 'some-protocol-path'
}
});
const schemaAndProtocolGrantReply = yield dwn.processMessage(alice.did, grant.recordsWrite.message, { dataStream: DataStream.fromBytes(grant.permissionGrantBytes) });
expect(schemaAndProtocolGrantReply.status.code).to.eq(400);
expect(schemaAndProtocolGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeContextIdProhibitedProperties);
}));
});
}));
}
//# sourceMappingURL=permissions.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,245 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { Jws } from '../../src/utils/jws.js';
import { ProtocolAction } from '../../src/types/protocols-types.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { DwnErrorCode, ProtocolsConfigure } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testProtocolCreateAction() {
describe('Protocol `create` action', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should process "create" rule correctly', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// verify requester cannot create without a matching "create" rule
// verify role authorized create rule
// verify authorized author of ancestor create rule
// verify authorized recipient of ancestor create rule
// verify anyone can create rule
// verify create rule does not grant subsequent write (update)
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const daniel = yield TestDataGenerator.generateDidKeyPersona();
// Alice installs a protocol with "can create" rules
const protocolDefinition = {
protocol: 'foo-bar-baz',
published: true,
types: {
admin: {},
foo: {},
bar: {},
baz: {}
},
structure: {
admin: {
$role: true
},
foo: {
$actions: [
{
role: 'admin',
can: [ProtocolAction.Create]
},
],
bar: {
$actions: [
{
who: 'author',
of: 'foo',
can: [ProtocolAction.Create]
},
{
who: 'recipient',
of: 'foo',
can: [ProtocolAction.Create]
}
],
baz: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Create]
}
],
}
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Verify Bob cannot create a record without a matching create rule
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobUnauthorizedWrite = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobUnauthorizedCreateReply = yield dwn.processMessage(alice.did, bobUnauthorizedWrite.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobUnauthorizedCreateReply.status.code).to.equal(401);
expect(bobUnauthorizedCreateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Alice gives Bob the "admin" role to be able to write `foo` records.
const adminBobRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'admin'
});
const adminBobRecordsWriteReply = yield dwn.processMessage(alice.did, adminBobRecordsWrite.message, { dataStream: adminBobRecordsWrite.dataStream });
expect(adminBobRecordsWriteReply.status.code).to.equal(202);
// Verify that Bob can create `foo` by invoking the admin role.
const bobRoleAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
recipient: carol.did,
protocolRole: 'admin',
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobRoleAuthorizedCreateReply = yield dwn.processMessage(alice.did, bobRoleAuthorizedFoo.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobRoleAuthorizedCreateReply.status.code).to.equal(202);
// Verify that Bob cannot update `foo`
const bobUnauthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobRoleAuthorizedFoo.message,
dataFormat: `any-new-format`,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedFooUpdate.message);
expect(bobUnauthorizedFooUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedFooUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Verify that Bob can create `bar` as the author of the ancestor `foo`
const bobBarBytes = TestDataGenerator.randomBytes(100);
const bobAuthorAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: bobRoleAuthorizedFoo.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: bobBarBytes
});
const bobBarCreateReply = yield dwn.processMessage(alice.did, bobAuthorAuthorizedBar.message, { dataStream: DataStream.fromBytes(bobBarBytes) });
expect(bobBarCreateReply.status.code).to.equal(202);
// Verify that Bob cannot update `bar`
const bobUnauthorizedBarUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobAuthorAuthorizedBar.message,
dataFormat: `any-new-format`,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedBarUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedBarUpdate.message);
expect(bobUnauthorizedBarUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedBarUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Verify that Carol can create `bar` as the recipient of the ancestor `foo`
const carolBarBytes = TestDataGenerator.randomBytes(100);
const carolRecipientAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: bobRoleAuthorizedFoo.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: carolBarBytes
});
const carolBarCreateReply = yield dwn.processMessage(alice.did, carolRecipientAuthorizedBar.message, { dataStream: DataStream.fromBytes(carolBarBytes) });
expect(carolBarCreateReply.status.code).to.equal(202);
// Verify that Carol cannot update `bar`
const carolUnauthorizedBarUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolRecipientAuthorizedBar.message,
dataFormat: `any-new-format`,
signer: Jws.createSigner(carol)
});
const carolUnauthorizedBarUpdateReply = yield dwn.processMessage(alice.did, carolUnauthorizedBarUpdate.message);
expect(carolUnauthorizedBarUpdateReply.status.code).to.equal(401);
expect(carolUnauthorizedBarUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Verify that Daniel cannot create `bar` as no create rule applies to him
const danielBarBytes = TestDataGenerator.randomBytes(100);
const danielUnauthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(daniel),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: bobRoleAuthorizedFoo.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: danielBarBytes
});
const danielUnauthorizedBarCreateReply = yield dwn.processMessage(alice.did, danielUnauthorizedBar.message, { dataStream: DataStream.fromBytes(danielBarBytes) });
expect(danielUnauthorizedBarCreateReply.status.code).to.equal(401);
expect(danielUnauthorizedBarCreateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Verify anyone can create `baz`
const danielBazBytes = TestDataGenerator.randomBytes(100);
const danielAnyoneAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(daniel),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar/baz',
parentContextId: carolRecipientAuthorizedBar.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: danielBazBytes
});
const danielBazCreateReply = yield dwn.processMessage(alice.did, danielAnyoneAuthorizedBar.message, { dataStream: DataStream.fromBytes(danielBazBytes) });
expect(danielBazCreateReply.status.code).to.equal(202);
// Verify that Daniel cannot update `baz`
const danielUnauthorizedBazUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobAuthorAuthorizedBar.message,
dataFormat: `any-new-format`,
signer: Jws.createSigner(daniel)
});
const danielUnauthorizedBazUpdateReply = yield dwn.processMessage(alice.did, danielUnauthorizedBazUpdate.message);
expect(danielUnauthorizedBazUpdateReply.status.code).to.equal(401);
expect(danielUnauthorizedBazUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
});
}
//# sourceMappingURL=protocol-create-action.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,489 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { Jws } from '../../src/utils/jws.js';
import { ProtocolAction } from '../../src/types/protocols-types.js';
import { ProtocolAuthorization } from '../../src/core/protocol-authorization.js';
import { RecordsRead } from '../../src/interfaces/records-read.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Message, ProtocolsConfigure, RecordsDelete, Time } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testProtocolDeleteAction() {
describe('Protocol `delete` action', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should only allow author of a role-authorized create to delete', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with a "author of a role-authorized create can delete" rule.
// 2. Alice gives Bob and Carol the "user" role to be able to write `foo` records.
// 3. Bob creates a `foo` by invoking the user role.
// 4. Verify that Bob can delete his `foo`
// 4a. Sanity verify that the delete took effect by reading the record back
// 5. Carol creates a `foo` by invoking the user role.
// 6. Verify that Bob cannot delete Carol's `foo`
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with a "author of a role-authorized create can delete" rule.
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
user: {},
foo: {}
},
structure: {
user: {
$role: true
},
foo: {
$actions: [
{
role: 'user',
can: [ProtocolAction.Create, ProtocolAction.Delete]
}
]
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice gives Bob and Carol the "user" role to be able to write `foo` records.
const userBobRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'user'
});
const userBobRecordsWriteReply = yield dwn.processMessage(alice.did, userBobRecordsWrite.message, { dataStream: userBobRecordsWrite.dataStream });
expect(userBobRecordsWriteReply.status.code).to.equal(202);
const userCarolRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'user'
});
const userCarolRecordsWriteReply = yield dwn.processMessage(alice.did, userCarolRecordsWrite.message, { dataStream: userCarolRecordsWrite.dataStream });
expect(userCarolRecordsWriteReply.status.code).to.equal(202);
// 3. Bob creates a `foo` by invoking the user role.
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobRoleAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocolRole: 'user',
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobRoleAuthorizedCreateReply = yield dwn.processMessage(alice.did, bobRoleAuthorizedFoo.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobRoleAuthorizedCreateReply.status.code).to.equal(202);
// 4. Verify that Bob can delete his `foo`
const bobAuthorizedFooDelete = yield RecordsDelete.create({
protocolRole: 'user',
recordId: bobRoleAuthorizedFoo.message.recordId,
signer: Jws.createSigner(bob)
});
const bobAuthorizedFooDeleteReply = yield dwn.processMessage(alice.did, bobAuthorizedFooDelete.message);
expect(bobAuthorizedFooDeleteReply.status.code).to.equal(202);
// 4a. Sanity verify that the delete took effect by reading the record back
const recordsRead = yield RecordsRead.create({
filter: {
recordId: bobRoleAuthorizedFoo.message.recordId
},
signer: Jws.createSigner(alice)
});
const recordsReadReply = yield dwn.processMessage(alice.did, recordsRead.message);
expect(recordsReadReply.status.code).to.equal(404);
// 5. Carol creates a `foo` by invoking the user role.
const carolFooBytes = TestDataGenerator.randomBytes(100);
const carolRoleAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocolRole: 'user',
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: carolFooBytes
});
const carolRoleAuthorizedCreateReply = yield dwn.processMessage(alice.did, carolRoleAuthorizedFoo.message, { dataStream: DataStream.fromBytes(carolFooBytes) });
expect(carolRoleAuthorizedCreateReply.status.code).to.equal(202);
// 6. Verify that Bob cannot delete Carol's `foo`
const bobUnauthorizedFooDelete = yield RecordsDelete.create({
protocolRole: 'user',
recordId: carolRoleAuthorizedFoo.message.recordId,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedFooDeleteReply = yield dwn.processMessage(alice.did, bobUnauthorizedFooDelete.message);
expect(bobUnauthorizedFooDeleteReply.status.code).to.equal(401);
expect(bobUnauthorizedFooDeleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should only allow author of an author/recipient-authorized create to delete', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with "author of an author/recipient-authorized create can delete" rules.
// 2. Alice creates a `foo` with Bob being the recipient, so that Bob can create `bar`.
// 3. Alice creates a `foo` with Carol being the recipient, so that Carol can create `bar`.
// 4. Bob creates a recipient-authorized `bar`.
// 5. Bob creates a author-authorized `baz` after his `bar`.
// 6. Carol creates a recipient-authorized `bar`.
// 7. Carol creates a author-authorized `baz` after her `bar`.
// 8. Verify that Bob can delete his `baz`
// 8a. Sanity verify that the delete took effect by reading the record back.
// 9. Verify that Bob can delete his `bar`.
// 10. Verify that Bob cannot delete Carol's `bar`.
// 11. Verify that Bob cannot delete Carol's `baz`
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with "author of an author/recipient-authorized create can delete" rules.
const protocolDefinition = {
protocol: 'foo-bar-baz',
published: true,
types: {
foo: {},
bar: {},
baz: {}
},
structure: {
foo: {
bar: {
$actions: [
{
who: 'recipient',
of: 'foo',
can: [ProtocolAction.Create, ProtocolAction.Delete]
}
],
baz: {
$actions: [
{
who: 'author',
of: 'foo/bar',
can: [ProtocolAction.Create, ProtocolAction.Delete]
}
]
}
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a `foo` with Bob being the recipient, so that Bob can create `bar`.
const fooForBob = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
const fooForBobReply = yield dwn.processMessage(alice.did, fooForBob.message, { dataStream: fooForBob.dataStream });
expect(fooForBobReply.status.code).to.equal(202);
// 3. Alice creates a `foo` with Carol being the recipient, so that Carol can create `bar`.
const fooForCarol = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
const fooForCarolReply = yield dwn.processMessage(alice.did, fooForCarol.message, { dataStream: fooForCarol.dataStream });
expect(fooForCarolReply.status.code).to.equal(202);
// 4. Bob creates a recipient-authorized `bar`.
const bobBarBytes = TestDataGenerator.randomBytes(100);
const bobRecipientAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: fooForBob.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: bobBarBytes
});
const bobRecipientAuthorizedBarReply = yield dwn.processMessage(alice.did, bobRecipientAuthorizedBar.message, { dataStream: DataStream.fromBytes(bobBarBytes) });
expect(bobRecipientAuthorizedBarReply.status.code).to.equal(202);
// 5. Bob creates a author-authorized `baz` after his `bar`.
const bobBazBytes = TestDataGenerator.randomBytes(100);
const bobAuthorAuthorizedBaz = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar/baz',
parentContextId: bobRecipientAuthorizedBar.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: bobBazBytes
});
const bobAuthorAuthorizedBazReply = yield dwn.processMessage(alice.did, bobAuthorAuthorizedBaz.message, { dataStream: DataStream.fromBytes(bobBazBytes) });
expect(bobAuthorAuthorizedBazReply.status.code).to.equal(202);
// 6. Carol creates a recipient-authorized `bar`.
const carolBarBytes = TestDataGenerator.randomBytes(100);
const carolRecipientAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: fooForCarol.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: carolBarBytes
});
const carolRecipientAuthorizedBarReply = yield dwn.processMessage(alice.did, carolRecipientAuthorizedBar.message, { dataStream: DataStream.fromBytes(carolBarBytes) });
expect(carolRecipientAuthorizedBarReply.status.code).to.equal(202);
// 7. Carol creates a author-authorized `baz` after her `bar`.
const carolBazBytes = TestDataGenerator.randomBytes(100);
const carolAuthorAuthorizedBaz = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar/baz',
parentContextId: carolRecipientAuthorizedBar.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: carolBazBytes
});
const carolAuthorAuthorizedBazReply = yield dwn.processMessage(alice.did, carolAuthorAuthorizedBaz.message, { dataStream: DataStream.fromBytes(carolBazBytes) });
expect(carolAuthorAuthorizedBazReply.status.code).to.equal(202);
// 8. Verify that Bob can delete his `baz`
const bobAuthorizedBazDelete = yield RecordsDelete.create({
recordId: bobAuthorAuthorizedBaz.message.recordId,
signer: Jws.createSigner(bob)
});
const bobAuthorizedBazDeleteReply = yield dwn.processMessage(alice.did, bobAuthorizedBazDelete.message);
expect(bobAuthorizedBazDeleteReply.status.code).to.equal(202);
// 8a. Sanity verify that the delete took effect by reading the record back.
const bobBarRead = yield RecordsRead.create({
filter: {
recordId: bobAuthorAuthorizedBaz.message.recordId
},
signer: Jws.createSigner(alice)
});
const bobBarReadReply = yield dwn.processMessage(alice.did, bobBarRead.message);
expect(bobBarReadReply.status.code).to.equal(404);
// 9. Verify that Bob can delete his `bar`.
const bobAuthorizedBarDelete = yield RecordsDelete.create({
recordId: bobRecipientAuthorizedBar.message.recordId,
signer: Jws.createSigner(bob)
});
const bobAuthorizedBarDeleteReply = yield dwn.processMessage(alice.did, bobAuthorizedBarDelete.message);
expect(bobAuthorizedBarDeleteReply.status.code).to.equal(202);
// 10. Verify that Bob cannot delete Carol's `bar`.
const bobUnauthorizedBarDelete = yield RecordsDelete.create({
recordId: carolRecipientAuthorizedBar.message.recordId,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedBarDeleteReply = yield dwn.processMessage(alice.did, bobUnauthorizedBarDelete.message);
expect(bobUnauthorizedBarDeleteReply.status.code).to.equal(401);
expect(bobUnauthorizedBarDeleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 11. Verify that Bob cannot delete Carol's `baz`
const bobUnauthorizedBazDelete = yield RecordsDelete.create({
recordId: carolRecipientAuthorizedBar.message.recordId,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedBazDeleteReply = yield dwn.processMessage(alice.did, bobUnauthorizedBazDelete.message);
expect(bobUnauthorizedBazDeleteReply.status.code).to.equal(401);
expect(bobUnauthorizedBazDeleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should only allow author of an anyone-authorized create to delete', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with "author of an anyone-authorized create can delete" rules.
// 2. Bob creates an anyone-authorized `foo`.
// 3. Carol creates a anyone-authorized `foo`.
// 4. Verify that Bob can delete his `foo`.
// 4a. Sanity verify that the delete took effect by reading the record back.
// 5. Verify that Bob cannot delete Carol's `foo`.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with "author of an anyone-authorized create can delete" rule.
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Create, ProtocolAction.Delete]
}
]
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob creates an anyone-authorized `foo`.
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobAnyoneAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobAnyoneAuthorizedFooReply = yield dwn.processMessage(alice.did, bobAnyoneAuthorizedFoo.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobAnyoneAuthorizedFooReply.status.code).to.equal(202);
// 3. Carol creates a anyone-authorized `foo`.
const carolFooBytes = TestDataGenerator.randomBytes(100);
const carolAnyoneAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: carolFooBytes
});
const carolAnyoneAuthorizedFooReply = yield dwn.processMessage(alice.did, carolAnyoneAuthorizedFoo.message, { dataStream: DataStream.fromBytes(carolFooBytes) });
expect(carolAnyoneAuthorizedFooReply.status.code).to.equal(202);
// 4. Verify that Bob can delete his `foo`.
const bobAuthorizedFooDelete = yield RecordsDelete.create({
recordId: bobAnyoneAuthorizedFoo.message.recordId,
signer: Jws.createSigner(bob)
});
const bobAuthorizedFooDeleteReply = yield dwn.processMessage(alice.did, bobAuthorizedFooDelete.message);
expect(bobAuthorizedFooDeleteReply.status.code).to.equal(202);
// 4a. Sanity verify that the delete took effect by reading the record back.
const bobBarRead = yield RecordsRead.create({
filter: {
recordId: bobAnyoneAuthorizedFoo.message.recordId
},
signer: Jws.createSigner(alice)
});
const bobFooReadReply = yield dwn.processMessage(alice.did, bobBarRead.message);
expect(bobFooReadReply.status.code).to.equal(404);
// 5. Verify that Bob cannot delete Carol's `foo`.
const bobUnauthorizedBarDelete = yield RecordsDelete.create({
recordId: carolAnyoneAuthorizedFoo.message.recordId,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedFooDeleteReply = yield dwn.processMessage(alice.did, bobUnauthorizedBarDelete.message);
expect(bobUnauthorizedFooDeleteReply.status.code).to.equal(401);
expect(bobUnauthorizedFooDeleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should not allow creation of a protocol definition with action rule containing `delete` without `create`', () => __awaiter(this, void 0, void 0, function* () {
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Delete] // intentionally missing `create` action
}
]
}
}
};
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolsConfigureCreatePromise = ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
yield expect(protocolsConfigureCreatePromise).to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionDeleteWithoutCreate);
}));
it('should reject ProtocolsConfigure with action rule containing `delete` action without `create` during processing', () => __awaiter(this, void 0, void 0, function* () {
const protocolDefinition = {
protocol: 'http://foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Delete] // intentionally missing `create` action
}
]
}
}
};
// manually craft the invalid ProtocolsConfigure message because our library will not let you create an invalid definition
const descriptor = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition: protocolDefinition
};
const alice = yield TestDataGenerator.generateDidKeyPersona();
const authorization = yield Message.createAuthorization({
descriptor,
signer: Jws.createSigner(alice)
});
const protocolsConfigureMessage = { descriptor, authorization };
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfigureMessage);
expect(protocolsConfigureReply.status.code).to.equal(400);
expect(protocolsConfigureReply.status.detail).to.contain(DwnErrorCode.ProtocolsConfigureInvalidActionDeleteWithoutCreate);
}));
describe('ProtocolAuthorization.getActionsSeekingARuleMatch()', () => {
it('should return empty array when given a RecordsDelete on a non-existent record', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bobUnauthorizedFooDelete = yield RecordsDelete.create({
recordId: 'non-existent-record-id',
signer: Jws.createSigner(alice)
});
const bobUnauthorizedFooDeleteReply = yield dwn.processMessage(alice.did, bobUnauthorizedFooDelete.message);
expect(bobUnauthorizedFooDeleteReply.status.code).to.equal(404);
const actionsSeekingARuleMatch = yield ProtocolAuthorization['getActionsSeekingARuleMatch'](alice.did, bobUnauthorizedFooDelete, messageStore);
expect(actionsSeekingARuleMatch).to.be.empty;
}));
});
});
}
//# sourceMappingURL=protocol-delete-action.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,572 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { DataStream } from '../../src/utils/data-stream.js';
import { Dwn } from '../../src/dwn.js';
import { Jws } from '../../src/utils/jws.js';
import { ProtocolAction } from '../../src/types/protocols-types.js';
import { RecordsRead } from '../../src/interfaces/records-read.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Message, ProtocolsConfigure, Time } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testProtocolUpdateAction() {
describe('Protocol `update` action', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should only allow author of a role-authorized create to update', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with a "author of a role-authorized create can update" rule.
// 2. Alice gives Bob and Carol the "user" role to be able to write `foo` records.
// 3. Bob creates a `foo` by invoking the user role.
// 4. Verify that Bob can update his `foo`
// 4a. Sanity verify that the update took effect by reading the record back
// 5. Carol creates a `foo` by invoking the user role.
// 6. Verify that Carol can update her `foo`
// 7. Verify that Bob cannot update Carol's `foo`
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with a "author of a role-authorized create can update" rule.
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
user: {},
foo: {}
},
structure: {
user: {
$role: true
},
foo: {
$actions: [
{
role: 'user',
can: [ProtocolAction.Create, ProtocolAction.Update]
}
]
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice gives Bob and Carol the "user" role to be able to write `foo` records.
const userBobRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'user'
});
const userBobRecordsWriteReply = yield dwn.processMessage(alice.did, userBobRecordsWrite.message, { dataStream: userBobRecordsWrite.dataStream });
expect(userBobRecordsWriteReply.status.code).to.equal(202);
const userCarolRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'user'
});
const userCarolRecordsWriteReply = yield dwn.processMessage(alice.did, userCarolRecordsWrite.message, { dataStream: userCarolRecordsWrite.dataStream });
expect(userCarolRecordsWriteReply.status.code).to.equal(202);
// 3. Bob creates a `foo` by invoking the user role.
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobRoleAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocolRole: 'user',
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobRoleAuthorizedCreateReply = yield dwn.processMessage(alice.did, bobRoleAuthorizedFoo.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobRoleAuthorizedCreateReply.status.code).to.equal(202);
// 4. Verify that Bob can update his `foo`
const bobFooNewBytes = TestDataGenerator.randomBytes(100);
const bobAuthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobRoleAuthorizedFoo.message,
data: bobFooNewBytes,
signer: Jws.createSigner(bob)
});
const bobAuthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobAuthorizedFooUpdate.message, { dataStream: DataStream.fromBytes(bobFooNewBytes) });
expect(bobAuthorizedFooUpdateReply.status.code).to.equal(202);
// 4a. Sanity verify that the update took effect by reading the record back
const recordsRead = yield RecordsRead.create({
filter: {
recordId: bobRoleAuthorizedFoo.message.recordId
},
signer: Jws.createSigner(alice)
});
const recordsReadReply = yield dwn.processMessage(alice.did, recordsRead.message);
expect(recordsReadReply.status.code).to.equal(200);
expect((_a = recordsReadReply.record) === null || _a === void 0 ? void 0 : _a.data).to.exist;
const dataFromReply = yield DataStream.toBytes(recordsReadReply.record.data);
expect(dataFromReply).to.eql(bobFooNewBytes);
// 5. Carol creates a `foo` by invoking the user role.
const carolFooBytes = TestDataGenerator.randomBytes(100);
const carolRoleAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocolRole: 'user',
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: carolFooBytes
});
const carolRoleAuthorizedCreateReply = yield dwn.processMessage(alice.did, carolRoleAuthorizedFoo.message, { dataStream: DataStream.fromBytes(carolFooBytes) });
expect(carolRoleAuthorizedCreateReply.status.code).to.equal(202);
// 6. Verify that carol can update her `foo`
const carolFooNewBytes = TestDataGenerator.randomBytes(100);
const carolAuthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolRoleAuthorizedFoo.message,
data: carolFooNewBytes,
signer: Jws.createSigner(carol)
});
const carolAuthorizedFooUpdateReply = yield dwn.processMessage(alice.did, carolAuthorizedFooUpdate.message, { dataStream: DataStream.fromBytes(carolFooNewBytes) });
expect(carolAuthorizedFooUpdateReply.status.code).to.equal(202);
// 7. Verify that Bob cannot update Carol's `foo`
const bobUnauthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolRoleAuthorizedFoo.message,
data: bobFooNewBytes,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedFooUpdate.message, { dataStream: DataStream.fromBytes(bobFooNewBytes) });
expect(bobUnauthorizedFooUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedFooUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should only allow author of an author/recipient-authorized create to update', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with "author of an author/recipient-authorized create can update" rules.
// 2. Alice creates a `foo` with Bob being the recipient, so that Bob can create `bar`.
// 3. Alice creates a `foo` with Carol being the recipient, so that Carol can create `bar`.
// 4. Bob creates a recipient-authorized `bar`.
// 5. Carol creates a recipient-authorized `bar`.
// 6. Verify that Bob can update his `bar`.
// 6a. Sanity verify that the update took effect by reading the record back.
// 7. Verify that Bob cannot update Carol's `bar`.
// 8. Bob creates a author-authorized `baz` after his `bar`.
// 9. Carol creates a author-authorized `baz` after her `bar`.
// 10. Verify that Bob can update his `baz`
// 10a. Sanity verify that the update took effect by reading the record back.
// 11. Verify that Bob cannot update Carol's `baz`
var _b, _c;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with "author of an author/recipient-authorized create can update" rules.
const protocolDefinition = {
protocol: 'foo-bar-baz',
published: true,
types: {
foo: {},
bar: {},
baz: {}
},
structure: {
foo: {
bar: {
$actions: [
{
who: 'recipient',
of: 'foo',
can: [ProtocolAction.Create, ProtocolAction.Update]
}
],
baz: {
$actions: [
{
who: 'author',
of: 'foo/bar',
can: [ProtocolAction.Create, ProtocolAction.Update]
}
]
}
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a `foo` with Bob being the recipient, so that Bob can create `bar`.
const fooForBob = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
const fooForBobReply = yield dwn.processMessage(alice.did, fooForBob.message, { dataStream: fooForBob.dataStream });
expect(fooForBobReply.status.code).to.equal(202);
// 3. Alice creates a `foo` with Carol being the recipient, so that Carol can create `bar`.
const fooForCarol = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'foo'
});
const fooForCarolReply = yield dwn.processMessage(alice.did, fooForCarol.message, { dataStream: fooForCarol.dataStream });
expect(fooForCarolReply.status.code).to.equal(202);
// 4. Bob creates a recipient-authorized `bar`.
const bobBarBytes = TestDataGenerator.randomBytes(100);
const bobRecipientAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: fooForBob.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: bobBarBytes
});
const bobRecipientAuthorizedBarReply = yield dwn.processMessage(alice.did, bobRecipientAuthorizedBar.message, { dataStream: DataStream.fromBytes(bobBarBytes) });
expect(bobRecipientAuthorizedBarReply.status.code).to.equal(202);
// 5. Carol creates a recipient-authorized `bar`.
const carolBarBytes = TestDataGenerator.randomBytes(100);
const carolRecipientAuthorizedBar = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar',
parentContextId: fooForCarol.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: carolBarBytes
});
const carolRecipientAuthorizedBarReply = yield dwn.processMessage(alice.did, carolRecipientAuthorizedBar.message, { dataStream: DataStream.fromBytes(carolBarBytes) });
expect(carolRecipientAuthorizedBarReply.status.code).to.equal(202);
// 6. Verify that Bob can update his `bar`.
const bobBarNewBytes = TestDataGenerator.randomBytes(100);
const bobAuthorizedBarUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobRecipientAuthorizedBar.message,
data: bobBarNewBytes,
signer: Jws.createSigner(bob)
});
const bobAuthorizedBarUpdateReply = yield dwn.processMessage(alice.did, bobAuthorizedBarUpdate.message, { dataStream: DataStream.fromBytes(bobBarNewBytes) });
expect(bobAuthorizedBarUpdateReply.status.code).to.equal(202);
// 6a. Sanity verify that the update took effect by reading the record back.
const bobBarRead = yield RecordsRead.create({
filter: {
recordId: bobRecipientAuthorizedBar.message.recordId
},
signer: Jws.createSigner(alice)
});
const bobBarReadReply = yield dwn.processMessage(alice.did, bobBarRead.message);
expect(bobBarReadReply.status.code).to.equal(200);
expect((_b = bobBarReadReply.record) === null || _b === void 0 ? void 0 : _b.data).to.exist;
const dataFromBobBarRead = yield DataStream.toBytes(bobBarReadReply.record.data);
expect(dataFromBobBarRead).to.eql(bobBarNewBytes);
// 7. Verify that Bob cannot update Carol's `bar`.
const bobUnauthorizedBarUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolRecipientAuthorizedBar.message,
data: bobBarNewBytes,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedBarUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedBarUpdate.message, { dataStream: DataStream.fromBytes(bobBarNewBytes) });
expect(bobUnauthorizedBarUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedBarUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 8. Bob creates a author-authorized `baz` after his `bar`.
const bobBazBytes = TestDataGenerator.randomBytes(100);
const bobAuthorAuthorizedBaz = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar/baz',
parentContextId: bobRecipientAuthorizedBar.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: bobBazBytes
});
const bobAuthorAuthorizedBazReply = yield dwn.processMessage(alice.did, bobAuthorAuthorizedBaz.message, { dataStream: DataStream.fromBytes(bobBazBytes) });
expect(bobAuthorAuthorizedBazReply.status.code).to.equal(202);
// 9. Carol creates a author-authorized `baz` after her `bar`.
const carolBazBytes = TestDataGenerator.randomBytes(100);
const carolAuthorAuthorizedBaz = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo/bar/baz',
parentContextId: carolRecipientAuthorizedBar.message.contextId,
schema: 'any-schema',
dataFormat: 'any-format',
data: carolBazBytes
});
const carolAuthorAuthorizedBazReply = yield dwn.processMessage(alice.did, carolAuthorAuthorizedBaz.message, { dataStream: DataStream.fromBytes(carolBazBytes) });
expect(carolAuthorAuthorizedBazReply.status.code).to.equal(202);
// 10. Verify that Bob can update his `baz`
const bobBazNewBytes = TestDataGenerator.randomBytes(100);
const bobAuthorizedBazUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobAuthorAuthorizedBaz.message,
data: bobBazNewBytes,
signer: Jws.createSigner(bob)
});
const bobAuthorizedBazUpdateReply = yield dwn.processMessage(alice.did, bobAuthorizedBazUpdate.message, { dataStream: DataStream.fromBytes(bobBazNewBytes) });
expect(bobAuthorizedBazUpdateReply.status.code).to.equal(202);
// 10a. Sanity verify that the update took effect by reading the record back.
const bobBazRead = yield RecordsRead.create({
filter: {
recordId: bobAuthorAuthorizedBaz.message.recordId
},
signer: Jws.createSigner(alice)
});
const bobBazReadReply = yield dwn.processMessage(alice.did, bobBazRead.message);
expect(bobBazReadReply.status.code).to.equal(200);
expect((_c = bobBazReadReply.record) === null || _c === void 0 ? void 0 : _c.data).to.exist;
const dataFromBobBazRead = yield DataStream.toBytes(bobBazReadReply.record.data);
expect(dataFromBobBazRead).to.eql(bobBazNewBytes);
// 11. Verify that Bob cannot update Carol's `baz`
const bobUnauthorizedBazUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolRecipientAuthorizedBar.message,
data: bobBazNewBytes,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedBazUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedBazUpdate.message, { dataStream: DataStream.fromBytes(bobBazNewBytes) });
expect(bobUnauthorizedBazUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedBazUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should only allow author of an anyone-authorized create to update', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with "author of an anyone-authorized create can update" rules.
// 2. Bob creates an anyone-authorized `foo`.
// 3. Carol creates a anyone-authorized `foo`.
// 4. Verify that Bob can update his `foo`.
// 4a. Sanity verify that the update took effect by reading the record back.
// 5. Verify that Bob cannot update Carol's `foo`.
var _d;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with "author of an anyone-authorized create can update" rule.
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Create, ProtocolAction.Update]
}
]
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob creates an anyone-authorized `foo`.
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobAnyoneAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
const bobAnyoneAuthorizedFooReply = yield dwn.processMessage(alice.did, bobAnyoneAuthorizedFoo.message, { dataStream: DataStream.fromBytes(bobFooBytes) });
expect(bobAnyoneAuthorizedFooReply.status.code).to.equal(202);
// 3. Carol creates a anyone-authorized `foo`.
const carolFooBytes = TestDataGenerator.randomBytes(100);
const carolAnyoneAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(carol),
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: carolFooBytes
});
const carolAnyoneAuthorizedFooReply = yield dwn.processMessage(alice.did, carolAnyoneAuthorizedFoo.message, { dataStream: DataStream.fromBytes(carolFooBytes) });
expect(carolAnyoneAuthorizedFooReply.status.code).to.equal(202);
// 4. Verify that Bob can update his `foo`.
const bobFooNewBytes = TestDataGenerator.randomBytes(100);
const bobAuthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobAnyoneAuthorizedFoo.message,
data: bobFooNewBytes,
signer: Jws.createSigner(bob)
});
const bobAuthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobAuthorizedFooUpdate.message, { dataStream: DataStream.fromBytes(bobFooNewBytes) });
expect(bobAuthorizedFooUpdateReply.status.code).to.equal(202);
// 4a. Sanity verify that the update took effect by reading the record back.
const bobBarRead = yield RecordsRead.create({
filter: {
recordId: bobAnyoneAuthorizedFoo.message.recordId
},
signer: Jws.createSigner(alice)
});
const bobFooReadReply = yield dwn.processMessage(alice.did, bobBarRead.message);
expect(bobFooReadReply.status.code).to.equal(200);
expect((_d = bobFooReadReply.record) === null || _d === void 0 ? void 0 : _d.data).to.exist;
const dataFromBobFooRead = yield DataStream.toBytes(bobFooReadReply.record.data);
expect(dataFromBobFooRead).to.eql(bobFooNewBytes);
// 5. Verify that Bob cannot update Carol's `foo`.
const bobUnauthorizedBarUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: carolAnyoneAuthorizedFoo.message,
data: bobFooNewBytes,
signer: Jws.createSigner(bob)
});
const bobUnauthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobUnauthorizedBarUpdate.message, { dataStream: DataStream.fromBytes(bobFooNewBytes) });
expect(bobUnauthorizedFooUpdateReply.status.code).to.equal(401);
expect(bobUnauthorizedFooUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should fail an update to an non-existent record', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol with "author of an anyone-authorized create can update" rules.
// 2. Bob constructs an anyone-authorized `foo` but never sent it to the DWN.
// 3. Verify that Bob cannot update a `foo` that does not exist in the DWN.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol with "author of an anyone-authorized create can update" rule.
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Create, ProtocolAction.Update]
}
]
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob constructs an anyone-authorized `foo` but never sent it to the DWN.
const bobFooBytes = TestDataGenerator.randomBytes(100);
const bobAnyoneAuthorizedFoo = yield RecordsWrite.create({
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'foo',
schema: 'any-schema',
dataFormat: 'any-format',
data: bobFooBytes
});
// 3. Verify that Bob cannot update a `foo` that does not exist in the DWN.
const bobFooNewBytes = TestDataGenerator.randomBytes(100);
const bobAuthorizedFooUpdate = yield RecordsWrite.createFrom({
recordsWriteMessage: bobAnyoneAuthorizedFoo.message,
data: bobFooNewBytes,
signer: Jws.createSigner(bob)
});
const bobAuthorizedFooUpdateReply = yield dwn.processMessage(alice.did, bobAuthorizedFooUpdate.message, { dataStream: DataStream.fromBytes(bobFooNewBytes) });
expect(bobAuthorizedFooUpdateReply.status.code).to.equal(401);
expect(bobAuthorizedFooUpdateReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
it('should not allow creation of a protocol definition with action rule containing `update` without `create`', () => __awaiter(this, void 0, void 0, function* () {
const protocolDefinition = {
protocol: 'foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Update] // intentionally missing `create` action
}
]
}
}
};
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolsConfigureCreatePromise = ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
yield expect(protocolsConfigureCreatePromise).to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionUpdateWithoutCreate);
}));
it('should reject ProtocolsConfigure with action rule containing `update` action without `create` during processing', () => __awaiter(this, void 0, void 0, function* () {
const protocolDefinition = {
protocol: 'http://foo',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Update] // intentionally missing `create` action
}
]
}
}
};
// manually craft the invalid ProtocolsConfigure message because our library will not let you create an invalid definition
const descriptor = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition: protocolDefinition
};
const alice = yield TestDataGenerator.generateDidKeyPersona();
const authorization = yield Message.createAuthorization({
descriptor,
signer: Jws.createSigner(alice)
});
const protocolsConfigureMessage = { descriptor, authorization };
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfigureMessage);
expect(protocolsConfigureReply.status.code).to.equal(400);
expect(protocolsConfigureReply.status.detail).to.contain(DwnErrorCode.ProtocolsConfigureInvalidActionUpdateWithoutCreate);
}));
});
}
//# sourceMappingURL=protocol-update-action.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,619 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import messageProtocolDefinition from '../vectors/protocol-definitions/message.json' assert { type: 'json' };
import nestedProtocolDefinition from '../vectors/protocol-definitions/nested.json' assert { type: 'json' };
import { DwnInterfaceName } from '../../src/enums/dwn-interface-method.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DataStream, Dwn, DwnConstant, DwnErrorCode, Jws, ProtocolsConfigure, RecordsDelete, RecordsQuery, RecordsWrite, SortDirection } from '../../src/index.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testRecordsPrune() {
describe('records pruning', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should purge all descendants when given RecordsDelete with `prune` set to `true`', () => __awaiter(this, void 0, void 0, function* () {
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// install a protocol with foo <- bar <- baz structure
const nestedProtocol = nestedProtocolDefinition;
const protocolsConfig = yield ProtocolsConfigure.create({
definition: nestedProtocol,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// writes 2 foos, 2 bars under foo1, and 2 bazes under bar1
// write 2 foos
const fooData = TestDataGenerator.randomBytes(100);
const fooOptions = {
signer: Jws.createSigner(alice),
protocol: nestedProtocol.protocol,
protocolPath: 'foo',
schema: nestedProtocol.types.foo.schema,
dataFormat: nestedProtocol.types.foo.dataFormats[0],
data: fooData
};
const foo1 = yield RecordsWrite.create(fooOptions);
const foo1WriteResponse = yield dwn.processMessage(alice.did, foo1.message, { dataStream: DataStream.fromBytes(fooData) });
expect(foo1WriteResponse.status.code).equals(202);
const foo2 = yield RecordsWrite.create(fooOptions);
const foo2WriteResponse = yield dwn.processMessage(alice.did, foo2.message, { dataStream: DataStream.fromBytes(fooData) });
expect(foo2WriteResponse.status.code).equals(202);
// write 2 bars under foo1 with data large enough to be required to be stored in the data store so we can test purge in data store
const barData = TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded + 1);
const barOptions = {
signer: Jws.createSigner(alice),
protocol: nestedProtocol.protocol,
protocolPath: 'foo/bar',
schema: nestedProtocol.types.bar.schema,
dataFormat: nestedProtocol.types.bar.dataFormats[0],
parentContextId: foo1.message.contextId,
data: barData
};
const bar1 = yield RecordsWrite.create(Object.assign({}, barOptions));
const bar1WriteResponse = yield dwn.processMessage(alice.did, bar1.message, { dataStream: DataStream.fromBytes(barData) });
expect(bar1WriteResponse.status.code).equals(202);
const bar2 = yield RecordsWrite.create(Object.assign({}, barOptions));
const bar2WriteResponse = yield dwn.processMessage(alice.did, bar2.message, { dataStream: DataStream.fromBytes(barData) });
expect(bar2WriteResponse.status.code).equals(202);
// write 2 bazes under bar1, each has more than 1 message associated with the record so we can test multi-message purge
const bazData = TestDataGenerator.randomBytes(100);
const bazOptions = {
signer: Jws.createSigner(alice),
protocol: nestedProtocol.protocol,
protocolPath: 'foo/bar/baz',
schema: nestedProtocol.types.baz.schema,
dataFormat: nestedProtocol.types.baz.dataFormats[0],
parentContextId: bar1.message.contextId,
data: bazData
};
const baz1 = yield RecordsWrite.create(Object.assign({}, bazOptions));
const baz1WriteResponse = yield dwn.processMessage(alice.did, baz1.message, { dataStream: DataStream.fromBytes(bazData) });
expect(baz1WriteResponse.status.code).equals(202);
const baz2 = yield RecordsWrite.create(Object.assign({}, bazOptions));
const baz2WriteResponse = yield dwn.processMessage(alice.did, baz2.message, { dataStream: DataStream.fromBytes(bazData) });
expect(baz2WriteResponse.status.code).equals(202);
// make latest state of baz1 a `RecordsWrite`
const newBaz1Data = TestDataGenerator.randomBytes(100);
const baz1Update = yield RecordsWrite.createFrom({
signer: Jws.createSigner(alice),
recordsWriteMessage: baz1.message,
data: newBaz1Data
});
const baz1UpdateResponse = yield dwn.processMessage(alice.did, baz1Update.message, { dataStream: DataStream.fromBytes(newBaz1Data) });
expect(baz1UpdateResponse.status.code).equals(202);
// make latest state of baz2 a `RecordsDelete`
const baz2Delete = yield RecordsDelete.create({
signer: Jws.createSigner(alice),
recordId: baz2.message.recordId
});
const baz2DeleteResponse = yield dwn.processMessage(alice.did, baz2Delete.message);
expect(baz2DeleteResponse.status.code).equals(202);
// sanity test messages are inserted in message store
const queryFilter = [{
interface: DwnInterfaceName.Records,
protocol: nestedProtocol.protocol
}];
const queryResult = yield messageStore.query(alice.did, queryFilter);
expect(queryResult.messages.length).to.equal(8); // 2 foos, 2 bars, 2 bazes x 2 messages each
// sanity test events are inserted in event log
const { events } = yield eventLog.queryEvents(alice.did, queryFilter);
expect(events.length).to.equal(8);
// sanity test data is inserted in data store
const bar1DataGetResult = yield dataStore.get(alice.did, bar1.message.recordId, bar1.message.descriptor.dataCid);
const bar2DataGetResult = yield dataStore.get(alice.did, bar2.message.recordId, bar2.message.descriptor.dataCid);
expect(bar1DataGetResult).to.not.be.undefined;
expect(bar2DataGetResult).to.not.be.undefined;
// Delete foo1 with prune enabled
const foo1Delete = yield RecordsDelete.create({
recordId: foo1.message.recordId,
prune: true,
signer: Jws.createSigner(alice)
});
const deleteReply = yield dwn.processMessage(alice.did, foo1Delete.message);
expect(deleteReply.status.code).to.equal(202);
// verify all bar and baz message are permanently deleted
const queryResult2 = yield messageStore.query(alice.did, queryFilter, { messageTimestamp: SortDirection.Ascending });
expect(queryResult2.messages.length).to.equal(3); // foo2 RecordsWrite, foo1 RecordsWrite and RecordsDelete
expect(queryResult2.messages[0]).to.deep.include(foo1.message);
expect(queryResult2.messages[1]).to.deep.include(foo2.message);
expect(queryResult2.messages[2]).to.deep.include(foo1Delete.message);
// verify all bar and baz events are permanently deleted
const { events: events2 } = yield eventLog.queryEvents(alice.did, queryFilter);
expect(events2.length).to.equal(3);
const foo1RecordsWriteCid = yield Message.getCid(foo1.message);
const foo2RecordsWriteCid = yield Message.getCid(foo2.message);
const foo2RecordsDeleteCid = yield Message.getCid(foo1Delete.message);
expect(events2).to.contain.members([foo1RecordsWriteCid, foo2RecordsWriteCid, foo2RecordsDeleteCid]);
// verify all bar data are permanently deleted
const bar1DataGetResult2 = yield dataStore.get(alice.did, bar1.message.recordId, bar1.message.descriptor.dataCid);
const bar2DataGetResult2 = yield dataStore.get(alice.did, bar2.message.recordId, bar2.message.descriptor.dataCid);
expect(bar1DataGetResult2).to.be.undefined;
expect(bar2DataGetResult2).to.be.undefined;
// sanity test an external query will no longer return the deleted records
const queryData = yield RecordsQuery.create({
signer: Jws.createSigner(alice),
filter: { protocol: nestedProtocol.protocol }
});
const reply2 = yield dwn.processMessage(alice.did, queryData.message);
expect(reply2.status.code).to.equal(200);
expect((_a = reply2.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1); // only foo2 is left
expect(reply2.entries[0]).to.deep.include(foo2.message);
}));
describe('prune and co-prune protocol action', () => {
it('should only allow a non-owner author to prune if `prune` is allowed and set to `true` in RecordsDelete', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol allowing others to add and prune records.
// 2. Bob writes a record + a descendant in Alice's DWN.
// 3. Verify Bob cannot prune the records if `prune` is not set to `true` in RecordsDelete.
// 4. Verify Bob can prune the records by setting `prune` to `true` in RecordsDelete.
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol allowing others to add and prune records.
const protocolDefinition = {
protocol: 'http://post-protocol.xyz',
published: true,
types: {
post: {},
attachment: {}
},
structure: {
post: {
$actions: [
{
who: 'anyone',
can: [
'create',
'prune',
'read'
]
}
],
attachment: {
$actions: [
{
who: 'anyone',
can: ['read']
},
{
who: 'author',
of: 'post',
can: ['create']
}
]
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob writes a record + a descendant in Alice's DWN.
const postData = TestDataGenerator.randomBytes(100);
const postOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post',
dataFormat: 'application/json',
data: postData
};
const post = yield RecordsWrite.create(postOptions);
const postWriteResponse = yield dwn.processMessage(alice.did, post.message, { dataStream: DataStream.fromBytes(postData) });
expect(postWriteResponse.status.code).equals(202);
const attachmentData = TestDataGenerator.randomBytes(100);
const attachmentOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post/attachment',
parentContextId: post.message.contextId,
dataFormat: 'application/octet-stream',
data: attachmentData
};
const attachment = yield RecordsWrite.create(attachmentOptions);
const attachmentWriteResponse = yield dwn.processMessage(alice.did, attachment.message, { dataStream: DataStream.fromBytes(attachmentData) });
expect(attachmentWriteResponse.status.code).equals(202);
// 3. Verify Bob cannot prune the records if `prune` is not set to `true` in RecordsDelete.
const unauthorizedPostPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
// prune : true, // intentionally not setting `prune` to true
signer: Jws.createSigner(bob)
});
const unauthorizedPostPruneReply = yield dwn.processMessage(alice.did, unauthorizedPostPrune.message);
expect(unauthorizedPostPruneReply.status.code).to.equal(401);
expect(unauthorizedPostPruneReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 4. Verify Bob can prune the records by setting `prune` to `true` in RecordsDelete.
const postPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
prune: true,
signer: Jws.createSigner(bob)
});
const pruneReply = yield dwn.processMessage(alice.did, postPrune.message);
expect(pruneReply.status.code).to.equal(202);
// sanity test `RecordsQuery` no longer returns the deleted record
const recordsQuery = yield RecordsQuery.create({
signer: Jws.createSigner(bob),
filter: { protocol: protocolDefinition.protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_a = recordsQueryReply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(0);
}));
it('should not allow a non-owner author to prune if `prune` is not an authorized action', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol allowing others to add records but not prune.
// 2. Bob writes a record + a descendant in Alice's DWN.
// 3. Verify Bob cannot prune the records.
var _b;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol allowing others to add records but not prune.
const protocolDefinition = messageProtocolDefinition;
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob writes a record + a descendant in Alice's DWN.
const messageData = TestDataGenerator.randomBytes(100);
const messageOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'message',
schema: protocolDefinition.types.message.schema,
dataFormat: protocolDefinition.types.message.dataFormats[0],
data: messageData
};
const message = yield RecordsWrite.create(messageOptions);
const messageWriteResponse = yield dwn.processMessage(alice.did, message.message, { dataStream: DataStream.fromBytes(messageData) });
expect(messageWriteResponse.status.code).equals(202);
const attachmentData = TestDataGenerator.randomBytes(100);
const attachmentOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'message/attachment',
parentContextId: message.message.contextId,
dataFormat: 'application/octet-stream',
data: attachmentData
};
const attachment = yield RecordsWrite.create(attachmentOptions);
const attachmentWriteResponse = yield dwn.processMessage(alice.did, attachment.message, { dataStream: DataStream.fromBytes(attachmentData) });
expect(attachmentWriteResponse.status.code).equals(202);
// 3. Verify Bob cannot prune the records.
const messagePrune = yield RecordsDelete.create({
recordId: message.message.recordId,
prune: true,
signer: Jws.createSigner(bob)
});
const deleteReply = yield dwn.processMessage(alice.did, messagePrune.message);
expect(deleteReply.status.code).to.equal(401);
expect(deleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// sanity test `RecordsQuery` still returns the records
const recordsQuery = yield RecordsQuery.create({
signer: Jws.createSigner(alice),
filter: { protocol: protocolDefinition.protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_b = recordsQueryReply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(2);
}));
it('should allow a non-author to prune if `co-prune` is allowed and `prune` is set to `true` in RecordsDelete', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol allowing others to add and prune records.
// 2. Bob writes a record + a descendant in Alice's DWN.
// 3. Verify Carol cannot prune the records if `prune` is not set to `true` in RecordsDelete.
// 4. Verify Carol can prune the records by setting `prune` to `true` in RecordsDelete.
var _c;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol allowing others to add and prune records.
const protocolDefinition = {
protocol: 'http://post-protocol.xyz',
published: true,
types: {
post: {},
attachment: {}
},
structure: {
post: {
$actions: [
{
who: 'anyone',
can: [
'create',
'co-prune',
'read'
]
}
],
attachment: {
$actions: [
{
who: 'anyone',
can: [
'create',
'read'
]
}
]
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob writes a record + a descendant in Alice's DWN.
const postData = TestDataGenerator.randomBytes(100);
const postOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post',
dataFormat: 'application/json',
data: postData
};
const post = yield RecordsWrite.create(postOptions);
const postWriteResponse = yield dwn.processMessage(alice.did, post.message, { dataStream: DataStream.fromBytes(postData) });
expect(postWriteResponse.status.code).equals(202);
const attachmentData = TestDataGenerator.randomBytes(100);
const attachmentOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post/attachment',
parentContextId: post.message.contextId,
dataFormat: 'application/octet-stream',
data: attachmentData
};
const attachment = yield RecordsWrite.create(attachmentOptions);
const attachmentWriteResponse = yield dwn.processMessage(alice.did, attachment.message, { dataStream: DataStream.fromBytes(attachmentData) });
expect(attachmentWriteResponse.status.code).equals(202);
// 3. Verify Carol cannot prune the records if `prune` is not set to `true` in RecordsDelete.
const unauthorizedPostPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
// prune : true, // intentionally not setting `prune` to true
signer: Jws.createSigner(carol)
});
const unauthorizedPostPruneReply = yield dwn.processMessage(alice.did, unauthorizedPostPrune.message);
expect(unauthorizedPostPruneReply.status.code).to.equal(401);
expect(unauthorizedPostPruneReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 4. Verify Carol can prune the records by setting `prune` to `true` in RecordsDelete.
const postPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
prune: true,
signer: Jws.createSigner(carol)
});
const deleteReply = yield dwn.processMessage(alice.did, postPrune.message);
expect(deleteReply.status.code).to.equal(202);
// sanity test `RecordsQuery` no longer returns the deleted record
const recordsQuery = yield RecordsQuery.create({
signer: Jws.createSigner(bob),
filter: { protocol: protocolDefinition.protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_c = recordsQueryReply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(0);
}));
it('should not allow a non-author to prune if `prune` is allowed but `co-prune` is not allowed', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol allowing others to add records AND only author to prune.
// 2. Bob writes a record + a descendant in Alice's DWN.
// 3. Verify Carol cannot prune the records.
var _d;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol allowing others to add records AND only author to prune.
const protocolDefinition = {
protocol: 'http://post-protocol.xyz',
published: true,
types: {
post: {},
attachment: {}
},
structure: {
post: {
$actions: [
{
who: 'anyone',
can: [
'create',
'prune',
'read'
]
}
],
attachment: {
$actions: [
{
who: 'anyone',
can: ['read']
},
{
who: 'author',
of: 'post',
can: ['create']
}
]
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob writes a record + a descendant in Alice's DWN.
const postData = TestDataGenerator.randomBytes(100);
const postOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post',
dataFormat: 'application/json',
data: postData
};
const post = yield RecordsWrite.create(postOptions);
const postWriteResponse = yield dwn.processMessage(alice.did, post.message, { dataStream: DataStream.fromBytes(postData) });
expect(postWriteResponse.status.code).equals(202);
const attachmentData = TestDataGenerator.randomBytes(100);
const attachmentOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post/attachment',
parentContextId: post.message.contextId,
dataFormat: 'application/octet-stream',
data: attachmentData
};
const attachment = yield RecordsWrite.create(attachmentOptions);
const attachmentWriteResponse = yield dwn.processMessage(alice.did, attachment.message, { dataStream: DataStream.fromBytes(attachmentData) });
expect(attachmentWriteResponse.status.code).equals(202);
// 3. Verify Carol cannot prune the records.
const postPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
prune: true,
signer: Jws.createSigner(carol)
});
const deleteReply = yield dwn.processMessage(alice.did, postPrune.message);
expect(deleteReply.status.code).to.equal(401);
expect(deleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// sanity test `RecordsQuery` still returns the records
const recordsQuery = yield RecordsQuery.create({
signer: Jws.createSigner(bob),
filter: { protocol: protocolDefinition.protocol }
});
const recordsQueryReply = yield dwn.processMessage(alice.did, recordsQuery.message);
expect(recordsQueryReply.status.code).to.equal(200);
expect((_d = recordsQueryReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(2);
}));
it('should throw if only `delete` is allowed but received a RecordsDelete with `prune` set to `true`', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice installs a protocol allowing others to add and delete (not prune) records.
// 2. Bob writes a record + a descendant in Alice's DWN.
// 3. Verify Bob cannot prune the records.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice installs a protocol allowing others to add and delete (not prune) records.
const protocolDefinition = {
protocol: 'http://post-protocol.xyz',
published: true,
types: {
post: {},
attachment: {}
},
structure: {
post: {
$actions: [
{
who: 'anyone',
can: [
'create',
'delete',
'read'
]
}
],
attachment: {
$actions: [
{
who: 'anyone',
can: [
'create',
'read'
]
}
]
}
}
}
};
const protocolsConfig = yield ProtocolsConfigure.create({
definition: protocolDefinition,
signer: Jws.createSigner(alice)
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Bob writes a record + a descendant in Alice's DWN.
const postData = TestDataGenerator.randomBytes(100);
const postOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post',
dataFormat: 'application/json',
data: postData
};
const post = yield RecordsWrite.create(postOptions);
const postWriteResponse = yield dwn.processMessage(alice.did, post.message, { dataStream: DataStream.fromBytes(postData) });
expect(postWriteResponse.status.code).equals(202);
const attachmentData = TestDataGenerator.randomBytes(100);
const attachmentOptions = {
signer: Jws.createSigner(bob),
protocol: protocolDefinition.protocol,
protocolPath: 'post/attachment',
parentContextId: post.message.contextId,
dataFormat: 'application/octet-stream',
data: attachmentData
};
const attachment = yield RecordsWrite.create(attachmentOptions);
const attachmentWriteResponse = yield dwn.processMessage(alice.did, attachment.message, { dataStream: DataStream.fromBytes(attachmentData) });
expect(attachmentWriteResponse.status.code).equals(202);
// 3. Verify Bob cannot prune the records.
const unauthorizedPostPrune = yield RecordsDelete.create({
recordId: post.message.recordId,
prune: true,
signer: Jws.createSigner(bob)
});
const unauthorizedPostPruneReply = yield dwn.processMessage(alice.did, unauthorizedPostPrune.message);
expect(unauthorizedPostPruneReply.status.code).to.equal(401);
expect(unauthorizedPostPruneReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
}));
});
});
}
//# sourceMappingURL=records-prune.spec.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,116 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Dwn } from '../../src/index.js';
import { EventsGetHandler } from '../../src/handlers/events-get.js';
import { expect } from 'chai';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Message } from '../../src/core/message.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
export function testEventsGetHandler() {
describe('EventsGetHandler.handle()', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('returns a 401 if tenant is not author', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsGet({ author: alice });
const eventsGetHandler = new EventsGetHandler(didResolver, eventLog);
const reply = yield eventsGetHandler.handle({ tenant: bob.did, message });
expect(reply.status.code).to.equal(401);
expect(reply.entries).to.not.exist;
}));
it('returns a 400 if message is invalid', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsGet({ author: alice });
message['descriptor']['troll'] = 'hehe';
const eventsGetHandler = new EventsGetHandler(didResolver, eventLog);
const reply = yield eventsGetHandler.handle({ tenant: alice.did, message });
expect(reply.status.code).to.equal(400);
expect(reply.entries).to.not.exist;
}));
it('returns all events for a tenant if cursor is not provided', () => __awaiter(this, void 0, void 0, function* () {
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const expectedCids = [];
for (let i = 0; i < 5; i += 1) {
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const reply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(reply.status.code).to.equal(202);
const messageCid = yield Message.getCid(message);
expectedCids.push(messageCid);
}
const { message } = yield TestDataGenerator.generateEventsGet({ author: alice });
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(200);
expect(reply.data).to.not.exist;
expect((_a = reply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(expectedCids.length);
for (let i = 0; i < reply.entries.length; i += 1) {
expect(reply.entries[i]).to.equal(expectedCids[i]);
}
}));
it('returns all events after cursor if provided', () => __awaiter(this, void 0, void 0, function* () {
var _b;
const alice = yield TestDataGenerator.generateDidKeyPersona();
for (let i = 0; i < 5; i += 1) {
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const reply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(reply.status.code).to.equal(202);
}
const { message } = yield TestDataGenerator.generateEventsGet({ author: alice });
let reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(200);
expect((_b = reply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(5);
expect(reply.cursor).to.not.be.undefined;
const expectedCids = [];
for (let i = 0; i < 3; i += 1) {
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const reply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(reply.status.code).to.equal(202);
const messageCid = yield Message.getCid(message);
expectedCids.push(messageCid);
}
const { message: m } = yield TestDataGenerator.generateEventsGet({ author: alice, cursor: reply.cursor });
reply = yield dwn.processMessage(alice.did, m);
expect(reply.status.code).to.equal(200);
expect(reply.data).to.not.exist;
expect(reply.entries.length).to.equal(expectedCids.length);
for (let i = 0; i < reply.entries.length; i += 1) {
expect(reply.entries[i]).to.equal(expectedCids[i]);
}
}));
});
}
//# sourceMappingURL=events-get.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,95 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Dwn } from '../../src/index.js';
import { EventsQueryHandler } from '../../src/handlers/events-query.js';
import { expect } from 'chai';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
export function testEventsQueryHandler() {
describe('EventsQueryHandler.handle()', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('returns a 401 if tenant is not author', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }]
});
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
const reply = yield eventsQueryHandler.handle({ tenant: bob.did, message });
expect(reply.status.code).to.equal(401);
expect(reply.entries).to.not.exist;
}));
it('returns a 400 if message is invalid', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }]
});
message['descriptor']['troll'] = 'hehe';
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
const reply = yield eventsQueryHandler.handle({ tenant: alice.did, message });
expect(reply.status.code).to.equal(400);
expect(reply.entries).to.not.exist;
}));
it('returns 400 if no filters are provided', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }],
}); // create with filter to prevent failure on .create()
message.descriptor.filters = []; // remove filters
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
const reply = yield eventsQueryHandler.handle({ tenant: alice.did, message });
expect(reply.status.code).to.equal(400);
expect(reply.entries).to.not.exist;
}));
it('returns 400 if an empty filter without properties is provided', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }],
}); // create with filter to prevent failure on .create()
message.descriptor.filters = [{}]; // empty out filter properties
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
const reply = yield eventsQueryHandler.handle({ tenant: alice.did, message });
expect(reply.status.code).to.equal(400);
expect(reply.entries).to.not.exist;
}));
});
}
//# sourceMappingURL=events-query.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"events-query.spec.js","sourceRoot":"","sources":["../../../../tests/handlers/events-query.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Dwn } from '../../src/dwn.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { EventsSubscribe } from '../../src/interfaces/events-subscribe.js';
import { Jws } from '../../src/utils/jws.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { EventsSubscribeHandler } from '../../src/handlers/events-subscribe.js';
chai.use(chaiAsPromised);
export function testEventsSubscribeHandler() {
describe('EventsSubscribe.handle()', () => {
describe('EventStream disabled', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
dwn = yield Dwn.create({
didResolver,
messageStore,
dataStore,
eventLog,
});
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should respond with a 501 if subscriptions are not supported', () => __awaiter(this, void 0, void 0, function* () {
yield dwn.close(); // close the original dwn instance
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog }); // leave out eventStream
const alice = yield TestDataGenerator.generateDidKeyPersona();
// attempt to subscribe
const { message } = yield EventsSubscribe.create({ signer: Jws.createSigner(alice) });
const subscriptionMessageReply = yield dwn.processMessage(alice.did, message, { subscriptionHandler: (_) => { } });
expect(subscriptionMessageReply.status.code).to.equal(501, subscriptionMessageReply.status.detail);
expect(subscriptionMessageReply.status.detail).to.include(DwnErrorCode.EventsSubscribeEventStreamUnimplemented);
}));
});
describe('EventStream enabled', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({
didResolver,
messageStore,
dataStore,
eventLog,
eventStream,
});
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('returns a 400 if message is invalid', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateEventsSubscribe({ author: alice });
// add an invalid property to the descriptor
message['descriptor']['invalid'] = 'invalid';
const eventsSubscribeHandler = new EventsSubscribeHandler(didResolver, eventStream);
const reply = yield eventsSubscribeHandler.handle({ tenant: alice.did, message, subscriptionHandler: (_) => { } });
expect(reply.status.code).to.equal(400);
}));
it('should allow tenant to subscribe their own event stream', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// set up a promise to read later that captures the emitted messageCid
let handler;
const messageSubscriptionPromise = new Promise((resolve) => {
handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
resolve(messageCid);
});
});
// testing EventsSubscribe
const eventsSubscribe = yield EventsSubscribe.create({
signer: Jws.createSigner(alice),
});
const subscriptionReply = yield dwn.processMessage(alice.did, eventsSubscribe.message, { subscriptionHandler: handler });
expect(subscriptionReply.status.code).to.equal(200, subscriptionReply.status.detail);
expect(subscriptionReply.subscription).to.not.be.undefined;
const messageWrite = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const writeReply = yield dwn.processMessage(alice.did, messageWrite.message, { dataStream: messageWrite.dataStream });
expect(writeReply.status.code).to.equal(202);
const messageCid = yield Message.getCid(messageWrite.message);
// control: ensure that the event exists
const { events } = yield eventLog.getEvents(alice.did);
expect(events.length).to.equal(1);
expect(events[0]).to.equal(messageCid);
// await the event
yield expect(messageSubscriptionPromise).to.eventually.equal(messageCid);
}));
it('should not allow non-tenant to subscribe to an event stream they are not authorized for', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// test anonymous request
const anonymousSubscription = yield TestDataGenerator.generateEventsSubscribe();
delete anonymousSubscription.message.authorization;
const anonymousReply = yield dwn.processMessage(alice.did, anonymousSubscription.message);
expect(anonymousReply.status.code).to.equal(400);
expect(anonymousReply.status.detail).to.include(`EventsSubscribe: must have required property 'authorization'`);
expect(anonymousReply.subscription).to.be.undefined;
// testing EventsSubscribe
const eventsSubscribe = yield EventsSubscribe.create({
signer: Jws.createSigner(bob),
});
const subscriptionReply = yield dwn.processMessage(alice.did, eventsSubscribe.message);
expect(subscriptionReply.status.code).to.equal(401);
expect(subscriptionReply.subscription).to.be.undefined;
}));
});
});
}
//# sourceMappingURL=events-subscribe.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,210 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { expect } from 'chai';
import { Message } from '../../src/core/message.js';
import { MessagesGetHandler } from '../../src/handlers/messages-get.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { Dwn, DwnConstant } from '../../src/index.js';
import sinon from 'sinon';
export function testMessagesGetHandler() {
describe('MessagesGetHandler.handle()', () => {
let dwn;
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('returns a 401 if tenant is not author', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [yield Message.getCid(recordsWrite.message)]
});
const reply = yield dwn.processMessage(bob.did, message);
expect(reply.status.code).to.equal(401);
}));
it('returns a 400 if message is invalid', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [yield Message.getCid(recordsWrite.message)]
});
message['descriptor']['troll'] = 'hehe';
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(400);
}));
it('returns a 400 if message contains an invalid message cid', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [yield Message.getCid(recordsWrite.message)]
});
message.descriptor.messageCids = ['hehetroll'];
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.include('is not a valid CID');
expect(reply.entries).to.be.undefined;
}));
it('returns all requested messages', () => __awaiter(this, void 0, void 0, function* () {
const did = yield TestDataGenerator.generateDidKeyPersona();
const alice = yield TestDataGenerator.generatePersona(did);
const messageCids = [];
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({
author: alice
});
let messageCid = yield Message.getCid(recordsWrite.message);
messageCids.push(messageCid);
let reply = yield dwn.processMessage(alice.did, recordsWrite.toJSON(), { dataStream });
expect(reply.status.code).to.equal(202);
const { recordsDelete } = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: recordsWrite.message.recordId
});
messageCid = yield Message.getCid(recordsDelete.message);
messageCids.push(messageCid);
reply = yield dwn.processMessage(alice.did, recordsDelete.toJSON());
expect(reply.status.code).to.equal(202);
const { protocolsConfigure } = yield TestDataGenerator.generateProtocolsConfigure({
author: alice
});
messageCid = yield Message.getCid(protocolsConfigure.message);
messageCids.push(messageCid);
reply = yield dwn.processMessage(alice.did, protocolsConfigure.toJSON());
expect(reply.status.code).to.equal(202);
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids
});
const messagesGetReply = yield dwn.processMessage(alice.did, message);
expect(messagesGetReply.status.code).to.equal(200);
expect(messagesGetReply.entries.length).to.equal(messageCids.length);
for (const messageReply of messagesGetReply.entries) {
expect(messageReply.messageCid).to.not.be.undefined;
expect(messageReply.message).to.not.be.undefined;
expect(messageCids).to.include(messageReply.messageCid);
const cid = yield Message.getCid(messageReply.message);
expect(messageReply.messageCid).to.equal(cid);
}
}));
it('returns message as undefined in reply entry when a messageCid is not found', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const recordsWriteMessageCid = yield Message.getCid(recordsWrite.message);
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [recordsWriteMessageCid]
});
// 0 messages expected because the RecordsWrite created above was never stored
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(200);
expect(reply.entries.length).to.equal(1);
for (const messageReply of reply.entries) {
expect(messageReply.messageCid).to.equal(recordsWriteMessageCid);
expect(messageReply.message).to.be.undefined;
}
}));
it('returns an error message for a specific cid if getting that message from the MessageStore fails', () => __awaiter(this, void 0, void 0, function* () {
// stub the messageStore.get call to throw an error
const messageStore = stubInterface();
messageStore.get.rejects('internal db error');
const dataStore = stubInterface();
const messagesGetHandler = new MessagesGetHandler(didResolver, messageStore, dataStore);
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const recordsWriteMessageCid = yield Message.getCid(recordsWrite.message);
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [recordsWriteMessageCid]
});
const reply = yield messagesGetHandler.handle({ tenant: alice.did, message });
expect(messageStore.get.called).to.be.true;
expect(reply.status.code).to.equal(200);
expect(reply.entries.length).to.equal(1);
expect(reply.entries[0].error).to.exist;
expect(reply.entries[0].error).to.include(`Failed to get message ${recordsWriteMessageCid}`);
expect(reply.entries[0].message).to.be.undefined;
}));
it('includes encodedData in reply entry if the data is available and dataSize < threshold', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data: TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded),
});
const reply = yield dwn.processMessage(alice.did, recordsWrite.toJSON(), { dataStream });
expect(reply.status.code).to.equal(202);
const recordsWriteMessageCid = yield Message.getCid(recordsWrite.message);
const { message } = yield TestDataGenerator.generateMessagesGet({
author: alice,
messageCids: [recordsWriteMessageCid]
});
const messagesGetReply = yield dwn.processMessage(alice.did, message);
expect(messagesGetReply.status.code).to.equal(200);
expect(messagesGetReply.entries.length).to.equal(1);
for (const messageReply of messagesGetReply.entries) {
expect(messageReply.messageCid).to.exist;
expect(messageReply.messageCid).to.equal(recordsWriteMessageCid);
expect(messageReply.message).to.exist.and.not.be.undefined;
expect(messageReply.encodedData).to.exist.and.not.be.undefined;
}
}));
it('does not return messages that belong to other tenants', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const { recordsWrite, dataStream } = yield TestDataGenerator.generateRecordsWrite({
author: alice
});
const reply = yield dwn.processMessage(alice.did, recordsWrite.toJSON(), { dataStream });
expect(reply.status.code).to.equal(202);
const recordsWriteMessageCid = yield Message.getCid(recordsWrite.message);
const { message } = yield TestDataGenerator.generateMessagesGet({
author: bob,
messageCids: [yield Message.getCid(recordsWrite.message)]
});
// 0 messages expected because the RecordsWrite created above is not bob's
const messagesGetReply = yield dwn.processMessage(bob.did, message);
expect(messagesGetReply.status.code).to.equal(200);
expect(messagesGetReply.entries.length).to.equal(1);
for (const messageReply of messagesGetReply.entries) {
expect(messageReply.messageCid).to.equal(recordsWriteMessageCid);
expect(messageReply.message).to.be.undefined;
}
}));
});
}
//# sourceMappingURL=messages-get.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,380 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import minimalProtocolDefinition from '../vectors/protocol-definitions/minimal.json' assert { type: 'json' };
import { GeneralJwsBuilder } from '../../src/jose/jws/general/builder.js';
import { lexicographicalCompare } from '../../src/utils/string.js';
import { Message } from '../../src/core/message.js';
import { ProtocolAction } from '../../src/types/protocols-types.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import { Time } from '../../src/utils/time.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { Dwn, DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, Jws } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testProtocolsConfigureHandler() {
describe('ProtocolsConfigureHandler.handle()', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
describe('functional tests', () => {
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should allow a protocol definition with schema or dataFormat omitted', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = minimalProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition,
});
const reply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(reply.status.code).to.equal(202);
}));
it('should return 400 if more than 1 signature is provided in `authorization`', () => __awaiter(this, void 0, void 0, function* () {
const { author, message, protocolsConfigure } = yield TestDataGenerator.generateProtocolsConfigure();
const tenant = author.did;
// intentionally create more than one signature, which is not allowed
const extraRandomPersona = yield TestDataGenerator.generatePersona();
const signer1 = Jws.createSigner(author);
const signer2 = Jws.createSigner(extraRandomPersona);
const signaturePayloadBytes = Encoder.objectToBytes(protocolsConfigure.signaturePayload);
const jwsBuilder = yield GeneralJwsBuilder.create(signaturePayloadBytes, [signer1, signer2]);
message.authorization = { signature: jwsBuilder.getJws() };
TestStubGenerator.stubDidResolver(didResolver, [author]);
const reply = yield dwn.processMessage(tenant, message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain('expected no more than 1 signature');
}));
it('should return 401 if auth fails', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
// use a bad signature to fail authentication
const badSignature = yield TestDataGenerator.randomSignatureString();
message.authorization.signature.signatures[0].signature = badSignature;
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.contain(DwnErrorCode.GeneralJwsVerifierInvalidSignature);
}));
it('should be able to overwrite existing protocol if timestamp is newer', () => __awaiter(this, void 0, void 0, function* () {
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = minimalProtocolDefinition;
const oldProtocolsConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition,
});
yield Time.minimalSleep();
const middleProtocolsConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition,
});
// first ProtocolsConfigure
const reply1 = yield dwn.processMessage(alice.did, middleProtocolsConfigure.message);
expect(reply1.status.code).to.equal(202);
// older messages will not overwrite the existing
const reply2 = yield dwn.processMessage(alice.did, oldProtocolsConfigure.message);
expect(reply2.status.code).to.equal(409);
// newer message can overwrite the existing message
const newProtocolsConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition,
});
const reply3 = yield dwn.processMessage(alice.did, newProtocolsConfigure.message);
expect(reply3.status.code).to.equal(202);
// only the newest protocol should remain
const queryMessageData = yield TestDataGenerator.generateProtocolsQuery({
author: alice,
filter: { protocol: protocolDefinition.protocol }
});
const queryReply = yield dwn.processMessage(alice.did, queryMessageData.message);
expect(queryReply.status.code).to.equal(200);
expect((_a = queryReply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1);
}));
it('should only be able to overwrite existing protocol if new protocol is lexicographically larger and timestamps are identical', () => __awaiter(this, void 0, void 0, function* () {
var _b;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// Alter each protocol slightly to create lexicographic difference between them
const protocolDefinition1 = Object.assign(Object.assign({}, minimalProtocolDefinition), { types: Object.assign(Object.assign({}, minimalProtocolDefinition.types), { foo1: { dataFormats: ['bar1'] } }) });
const protocolDefinition2 = Object.assign(Object.assign({}, minimalProtocolDefinition), { types: Object.assign(Object.assign({}, minimalProtocolDefinition.types), { foo2: { dataFormats: ['bar2'] } }) });
const protocolDefinition3 = Object.assign(Object.assign({}, minimalProtocolDefinition), { types: Object.assign(Object.assign({}, minimalProtocolDefinition.types), { foo3: { dataFormats: ['bar3'] } }) });
// Create three `ProtocolsConfigure` with identical timestamp
const messageData1 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: protocolDefinition1
});
const messageData2 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: protocolDefinition2,
messageTimestamp: messageData1.message.descriptor.messageTimestamp
});
const messageData3 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: protocolDefinition3,
messageTimestamp: messageData1.message.descriptor.messageTimestamp
});
const messageDataWithCid = [];
for (const messageData of [messageData1, messageData2, messageData3]) {
const cid = yield Message.getCid(messageData.message);
messageDataWithCid.push(Object.assign({ cid }, messageData));
}
// sort the message in lexicographic order
const [lowestProtocolsConfigure, middleProtocolsConfigure, highestProtocolsConfigure] = messageDataWithCid.sort((messageDataA, messageDataB) => { return lexicographicalCompare(messageDataA.cid, messageDataB.cid); });
// write the protocol with the middle lexicographic value
const reply1 = yield dwn.processMessage(alice.did, middleProtocolsConfigure.message);
expect(reply1.status.code).to.equal(202);
// test that the protocol with the smallest lexicographic value cannot be written
const reply2 = yield dwn.processMessage(alice.did, lowestProtocolsConfigure.message);
expect(reply2.status.code).to.equal(409);
// test that the protocol with the largest lexicographic value can be written
const reply3 = yield dwn.processMessage(alice.did, highestProtocolsConfigure.message);
expect(reply3.status.code).to.equal(202);
// test that lower lexicographic protocol message is removed from DB and only the newer protocol message remains
const queryMessageData = yield TestDataGenerator.generateProtocolsQuery({
author: alice,
filter: { protocol: protocolDefinition1.protocol }
});
const queryReply = yield dwn.processMessage(alice.did, queryMessageData.message);
expect(queryReply.status.code).to.equal(200);
expect((_b = queryReply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(1);
}));
it('should return 400 if protocol is not normalized', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// query for non-normalized protocol
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: minimalProtocolDefinition
});
// overwrite protocol because #create auto-normalizes protocol
protocolsConfig.message.descriptor.definition.protocol = 'example.com/';
// Re-create auth because we altered the descriptor after signing
protocolsConfig.message.authorization = yield Message.createAuthorization({
descriptor: protocolsConfig.message.descriptor,
signer: Jws.createSigner(alice)
});
// Send records write message
const reply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(DwnErrorCode.UrlProtocolNotNormalized);
}));
it('should return 400 if schema is not normalized', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = dexProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition,
});
// overwrite schema because #create auto-normalizes schema
protocolsConfig.message.descriptor.definition.types.ask.schema = 'ask';
// Re-create auth because we altered the descriptor after signing
protocolsConfig.message.authorization = yield Message.createAuthorization({
descriptor: protocolsConfig.message.descriptor,
signer: Jws.createSigner(alice)
});
// Send records write message
const reply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(DwnErrorCode.UrlSchemaNotNormalized);
}));
it('rejects non-tenant non-granted ProtocolsConfigures with 401', () => __awaiter(this, void 0, void 0, function* () {
// Bob tries to ProtocolsConfigure to Alice's DWN without a permission grant
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = dexProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: bob,
protocolDefinition,
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(401);
expect(protocolsConfigureReply.status.detail).to.contain(DwnErrorCode.AuthorizationAuthorNotOwner);
}));
it('should reject ProtocolsConfigure with action rule containing duplicated actor (`who` or `who` + `of` combination) within a rule set', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = {
protocol: 'http://foo-bar',
published: true,
types: {
foo: {},
},
structure: {
foo: {
$actions: [
{
who: 'anyone',
can: [ProtocolAction.Create]
},
// duplicated `who` value
{
who: 'anyone',
can: [ProtocolAction.Update]
}
]
}
}
};
// manually craft the invalid ProtocolsConfigure message because our library will not let you create an invalid definition
const descriptor = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition: protocolDefinition
};
const authorization = yield Message.createAuthorization({
descriptor,
signer: Jws.createSigner(alice)
});
const protocolsConfigureMessage = { descriptor, authorization };
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfigureMessage);
expect(protocolsConfigureReply.status.code).to.equal(400);
expect(protocolsConfigureReply.status.detail).to.contain(DwnErrorCode.ProtocolsConfigureDuplicateActorInRuleSet);
// similar test as above but with `of` property
const protocolDefinition2 = {
protocol: 'http://foo-bar',
published: true,
types: {
foo: {},
bar: {},
},
structure: {
foo: {
bar: {
$actions: [
{
who: 'recipient',
of: 'foo',
can: [ProtocolAction.Create]
},
// duplicated `who` value
{
who: 'recipient',
of: 'foo',
can: [ProtocolAction.Update]
}
]
}
}
}
};
const descriptor2 = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition: protocolDefinition2
};
const authorization2 = yield Message.createAuthorization({
descriptor: descriptor2,
signer: Jws.createSigner(alice)
});
const protocolsConfigureMessage2 = { descriptor: descriptor2, authorization: authorization2 };
const protocolsConfigure2Reply = yield dwn.processMessage(alice.did, protocolsConfigureMessage2);
expect(protocolsConfigure2Reply.status.code).to.equal(400);
expect(protocolsConfigure2Reply.status.detail).to.contain(DwnErrorCode.ProtocolsConfigureDuplicateActorInRuleSet);
}));
it('should reject ProtocolsConfigure with action rule containing duplicated role within a rule set', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = {
protocol: 'http://foo',
published: true,
types: {
user: {},
foo: {},
},
structure: {
user: {
$role: true
},
foo: {
$actions: [
{
role: 'user',
can: [ProtocolAction.Create]
},
// duplicated `role` value
{
role: 'user',
can: [ProtocolAction.Update]
}
]
}
}
};
// manually craft the invalid ProtocolsConfigure message because our library will not let you create an invalid definition
const descriptor = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition: protocolDefinition
};
const authorization = yield Message.createAuthorization({
descriptor,
signer: Jws.createSigner(alice)
});
const protocolsConfigureMessage = { descriptor, authorization };
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfigureMessage);
expect(protocolsConfigureReply.status.code).to.equal(400);
expect(protocolsConfigureReply.status.detail).to.contain(DwnErrorCode.ProtocolsConfigureDuplicateRoleInRuleSet);
}));
describe('event log', () => {
it('should add event for ProtocolsConfigure', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(202);
const { events } = yield eventLog.getEvents(alice.did);
expect(events.length).to.equal(1);
const messageCid = yield Message.getCid(message);
expect(events[0]).to.equal(messageCid);
}));
it('should delete older ProtocolsConfigure events when one is overwritten', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const oldestWrite = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, protocolDefinition: minimalProtocolDefinition });
yield Time.minimalSleep();
const newestWrite = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, protocolDefinition: minimalProtocolDefinition });
let reply = yield dwn.processMessage(alice.did, oldestWrite.message);
expect(reply.status.code).to.equal(202);
reply = yield dwn.processMessage(alice.did, newestWrite.message);
expect(reply.status.code).to.equal(202);
const { events } = yield eventLog.getEvents(alice.did);
expect(events.length).to.equal(1);
const newestMessageCid = yield Message.getCid(newestWrite.message);
expect(events[0]).to.equal(newestMessageCid);
}));
});
});
});
}
//# sourceMappingURL=protocols-configure.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,354 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import { GeneralJwsBuilder } from '../../src/jose/jws/general/builder.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import { Time } from '../../src/utils/time.js';
import { DataStream, Dwn, DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, Jws, PermissionsProtocol, ProtocolsQuery, RecordsWrite } from '../../src/index.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testProtocolsQueryHandler() {
describe('ProtocolsQueryHandler.handle()', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
describe('functional tests', () => {
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should return protocols matching the query', () => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const alice = yield TestDataGenerator.generatePersona();
// setting up a stub method resolver
TestStubGenerator.stubDidResolver(didResolver, [alice]);
// insert three messages into DB, two with matching protocol
const protocol1 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
const protocol2 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
const protocol3 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
yield dwn.processMessage(alice.did, protocol1.message);
yield dwn.processMessage(alice.did, protocol2.message);
yield dwn.processMessage(alice.did, protocol3.message);
// testing singular conditional query
const queryMessageData = yield TestDataGenerator.generateProtocolsQuery({
author: alice,
filter: { protocol: protocol1.message.descriptor.definition.protocol }
});
const reply = yield dwn.processMessage(alice.did, queryMessageData.message);
expect(reply.status.code).to.equal(200);
expect((_a = reply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1); // only 1 entry should match the query on protocol
// testing fetch-all query without filter
const queryMessageData2 = yield TestDataGenerator.generateProtocolsQuery({
author: alice
});
const reply2 = yield dwn.processMessage(alice.did, queryMessageData2.message);
expect(reply2.status.code).to.equal(200);
expect((_b = reply2.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(3); // expecting all 3 entries written above match the query
}));
it('should return published protocols matching the query if query is unauthenticated or unauthorized', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// 1. Alice has 3 protocols installed: 1 private + 2 published
// 2. Unauthenticated ProtocolsQuery should return published ProtocolsConfigure
// 3. Authenticated ProtocolsQuery by Bob but unauthorized to private ProtocolsConfigures should return published ProtocolsConfigure
var _c, _d, _e, _f;
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
TestStubGenerator.stubDidResolver(didResolver, [alice, bob]);
// insert three messages into DB, two with matching protocol
const protocol1 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, published: false });
const protocol2 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, published: true });
const protocol3 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, published: true });
yield dwn.processMessage(alice.did, protocol1.message);
yield dwn.processMessage(alice.did, protocol2.message);
yield dwn.processMessage(alice.did, protocol3.message);
// testing unauthenticated conditional query
const conditionalQuery = yield ProtocolsQuery.create({
filter: { protocol: protocol2.message.descriptor.definition.protocol }
});
const conditionalQueryReply = yield dwn.processMessage(alice.did, conditionalQuery.message);
expect(conditionalQueryReply.status.code).to.equal(200);
expect((_c = conditionalQueryReply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(1); // only 1 entry should match the query on protocol
const protocolConfigured = conditionalQueryReply.entries[0];
expect(protocolConfigured).to.deep.equal(protocol2.message);
// testing authenticated but unauthorized conditional query, it should return only matching published ProtocolsConfigures
const signedConditionalQuery = yield ProtocolsQuery.create({
filter: { protocol: protocol2.message.descriptor.definition.protocol },
signer: Jws.createSigner(bob)
});
const signedConditionalQueryReply = yield dwn.processMessage(alice.did, signedConditionalQuery.message);
expect(signedConditionalQueryReply.status.code).to.equal(200);
expect((_d = signedConditionalQueryReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(1); // only 1 entry should match the query on protocol
const protocolConfigured2 = conditionalQueryReply.entries[0];
expect(protocolConfigured2).to.deep.equal(protocol2.message);
// testing unauthenticated fetch-all query without filter
const fetchAllQuery = yield ProtocolsQuery.create({});
const fetchAllQueryReply = yield dwn.processMessage(alice.did, fetchAllQuery.message);
expect(fetchAllQueryReply.status.code).to.equal(200);
expect((_e = fetchAllQueryReply.entries) === null || _e === void 0 ? void 0 : _e.length).to.equal(2);
expect(fetchAllQueryReply.entries).to.deep.include(protocol2.message);
expect(fetchAllQueryReply.entries).to.deep.include(protocol3.message);
// testing authenticated but unauthorized fetch-all query without filter, it should return all matching published ProtocolsConfigures
const signedFetchAllQuery = yield ProtocolsQuery.create({
signer: Jws.createSigner(bob)
});
const signedFetchAllQueryReply = yield dwn.processMessage(alice.did, signedFetchAllQuery.message);
expect(signedFetchAllQueryReply.status.code).to.equal(200);
expect((_f = signedFetchAllQueryReply.entries) === null || _f === void 0 ? void 0 : _f.length).to.equal(2);
expect(signedFetchAllQueryReply.entries).to.deep.include(protocol2.message);
expect(signedFetchAllQueryReply.entries).to.deep.include(protocol3.message);
}));
it('should return 400 if protocol is not normalized', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// query for non-normalized protocol
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: alice,
filter: { protocol: 'example.com/' },
});
// overwrite protocol because #create auto-normalizes protocol
protocolsQuery.message.descriptor.filter.protocol = 'example.com/';
// Re-create auth because we altered the descriptor after signing
protocolsQuery.message.authorization = yield Message.createAuthorization({
descriptor: protocolsQuery.message.descriptor,
signer: Jws.createSigner(alice)
});
// Send records write message
const reply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(DwnErrorCode.UrlProtocolNotNormalized);
}));
it('should fail with 400 if signature payload is referencing a different message (`descriptorCid`)', () => __awaiter(this, void 0, void 0, function* () {
const { author, message, protocolsQuery } = yield TestDataGenerator.generateProtocolsQuery();
const tenant = author.did;
// replace signature with incorrect `descriptorCid`, even though signature is still valid
const incorrectDescriptorCid = yield TestDataGenerator.randomCborSha256Cid();
const signaturePayload = Object.assign({}, protocolsQuery.signaturePayload);
signaturePayload.descriptorCid = incorrectDescriptorCid;
const signaturePayloadBytes = Encoder.objectToBytes(signaturePayload);
const signer = Jws.createSigner(author);
const jwsBuilder = yield GeneralJwsBuilder.create(signaturePayloadBytes, [signer]);
message.authorization = { signature: jwsBuilder.getJws() };
const reply = yield dwn.processMessage(tenant, message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(`${incorrectDescriptorCid} does not match expected CID`);
}));
it('should return 401 if auth fails', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message } = yield TestDataGenerator.generateProtocolsQuery({ author: alice });
// use a bad signature to fail authentication
const badSignature = yield TestDataGenerator.randomSignatureString();
message.authorization.signature.signatures[0].signature = badSignature;
const reply = yield dwn.processMessage(alice.did, message);
expect(reply.status.code).to.equal(401);
expect(reply.status.detail).to.contain(DwnErrorCode.GeneralJwsVerifierInvalidSignature);
}));
describe('Grant authorization', () => {
it('allows an external party to ProtocolsQuery only if they have a valid grant', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// 1. Alice grants Bob the access to ProtocolsQuery on her DWN
// 2. Verify Bob can perform a ProtocolsQuery
// 3. Verify that Mallory cannot to use Bob's permission grant to gain access to Alice's DWN
// 4. Alice revokes Bob's grant
// 5. Verify Bob cannot perform ProtocolsQuery with the revoked grant
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const mallory = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice grants Bob the access to ProtocolsQuery on her DWN
const permissionGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
grantedTo: bob.did,
dateExpires: Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }),
scope: { interface: DwnInterfaceName.Protocols, method: DwnMethodName.Query }
});
const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes);
const grantRecordsWriteReply = yield dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream });
expect(grantRecordsWriteReply.status.code).to.equal(202);
// 2. Verify Bob can perform a ProtocolsQuery
const permissionGrantId = permissionGrant.recordsWrite.message.recordId;
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
permissionGrantId,
});
const protocolsQueryReply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(200);
// 3. Verify that Mallory cannot to use Bob's permission grant to gain access to Alice's DWN
const malloryProtocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: mallory,
permissionGrantId,
});
const malloryProtocolsQueryReply = yield dwn.processMessage(alice.did, malloryProtocolsQuery.message);
expect(malloryProtocolsQueryReply.status.code).to.equal(401);
expect(malloryProtocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationNotGrantedToAuthor);
// 4. Alice revokes Bob's grant
const revokeWrite = yield PermissionsProtocol.createRevocation({
signer: Jws.createSigner(alice),
grantId: permissionGrantId,
dateRevoked: Time.getCurrentTimestamp()
});
const revokeWriteReply = yield dwn.processMessage(alice.did, revokeWrite.recordsWrite.message, { dataStream: DataStream.fromBytes(revokeWrite.permissionRevocationBytes) });
expect(revokeWriteReply.status.code).to.equal(202);
// 5. Verify Bob cannot perform ProtocolsQuery with the revoked grant
const unauthorizedProtocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
permissionGrantId,
});
const unauthorizedProtocolsQueryReply = yield dwn.processMessage(alice.did, unauthorizedProtocolsQuery.message);
expect(unauthorizedProtocolsQueryReply.status.code).to.equal(401);
expect(unauthorizedProtocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantRevoked);
}));
it('rejects with 401 when an external party attempts to ProtocolsQuery if they present an expired grant', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice grants Bob access to ProtocolsQuery, but when Bob invokes the grant it has expired
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Alice gives Bob a permission grant with scope ProtocolsQuery and an expiry time
const permissionGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
grantedTo: bob.did,
dateGranted: Time.getCurrentTimestamp(),
dateExpires: Time.getCurrentTimestamp(),
scope: { interface: DwnInterfaceName.Protocols, method: DwnMethodName.Query }
});
const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes);
const permissionGrantWriteReply = yield dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream });
expect(permissionGrantWriteReply.status.code).to.equal(202);
// Bob does ProtocolsQuery after the grant has expired
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
permissionGrantId: permissionGrant.recordsWrite.message.recordId,
});
const protocolsQueryReply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantExpired);
}));
it('rejects with 401 when an external party attempts to ProtocolsQuery if the grant is not yet active', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice grants Bob access to ProtocolsQuery, but Bob's message has a timestamp just before the grant is active
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Set up timestamps
const protocolsQueryTimestamp = Time.getCurrentTimestamp();
yield Time.minimalSleep(); // to ensure granted created will be after the query timestamp
// Alice gives Bob a permission grant with scope ProtocolsQuery
const permissionGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
grantedTo: bob.did,
dateGranted: Time.getCurrentTimestamp(),
dateExpires: Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }),
scope: { interface: DwnInterfaceName.Protocols, method: DwnMethodName.Query }
});
const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes);
const permissionGrantWriteReply = yield dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream });
expect(permissionGrantWriteReply.status.code).to.equal(202);
// Bob does ProtocolsQuery but his message has timestamp before the grant is active
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
messageTimestamp: protocolsQueryTimestamp,
permissionGrantId: permissionGrant.recordsWrite.message.recordId,
});
const protocolsQueryReply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantNotYetActive);
}));
it('rejects with 401 when an external party attempts to ProtocolsQuery using a grant that has a different scope', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice grants Bob access to RecordsRead, then Bob tries to invoke the grant with ProtocolsQuery
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Alice gives Bob a permission grant with scope RecordsRead
const permissionGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
grantedTo: bob.did,
dateExpires: Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }),
scope: { interface: DwnInterfaceName.Records, method: DwnMethodName.Read }
});
const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes);
const grantRecordsWriteReply = yield dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream });
expect(grantRecordsWriteReply.status.code).to.equal(202);
// Bob tries to ProtocolsQuery
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
permissionGrantId: permissionGrant.recordsWrite.message.recordId,
});
const protocolsQueryReply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationInterfaceMismatch);
}));
it('rejects with 401 if the permission grant cannot be found', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Bob uses a permissionGrantId to ProtocolsQuery, but no permission grant can be found.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
// Bob tries to ProtocolsQuery
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: bob,
permissionGrantId: yield TestDataGenerator.randomCborSha256Cid(),
});
const protocolsQueryReply = yield dwn.processMessage(alice.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationGrantMissing);
}));
it('rejects with 401 if the permission grant has not been grantedFor the tenant', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice gives Carol a permission grant with scope ProtocolsQuery
// 2. Bob (for unknown reason, thus this is a super edge case) stores the grant in his DWN
// 3. Verify that Carol cannot use permission grant to gain access to Bob's DWN
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// 1. Alice gives Carol a permission grant with scope ProtocolsQuery
const permissionGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
grantedTo: carol.did,
dateExpires: Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }),
scope: { interface: DwnInterfaceName.Protocols, method: DwnMethodName.Query }
});
const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes);
// 2. Bob (for unknown reason, thus this is a super edge case) stores the grant in his DWN
const bobWrappedGrant = yield RecordsWrite.parse(permissionGrant.recordsWrite.message);
yield bobWrappedGrant.signAsOwner(Jws.createSigner(bob));
const grantRecordsWriteReply = yield dwn.processMessage(bob.did, bobWrappedGrant.message, { dataStream });
expect(grantRecordsWriteReply.status.code).to.equal(202);
// 3. Verify that Carol cannot use permission grant to gain access to Bob's DWN
const permissionGrantId = permissionGrant.recordsWrite.message.recordId;
const protocolsQuery = yield TestDataGenerator.generateProtocolsQuery({
author: carol,
permissionGrantId,
});
const protocolsQueryReply = yield dwn.processMessage(bob.did, protocolsQuery.message);
expect(protocolsQueryReply.status.code).to.equal(401);
expect(protocolsQueryReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationNotGrantedForTenant);
}));
});
});
});
}
//# sourceMappingURL=protocols-query.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,653 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import anyoneCollaborateProtocolDefinition from '../vectors/protocol-definitions/anyone-collaborate.json' assert { type: 'json' };
import authorCanProtocolDefinition from '../vectors/protocol-definitions/author-can.json' assert { type: 'json' };
import friendRoleProtocolDefinition from '../vectors/protocol-definitions/friend-role.json' assert { type: 'json' };
import recipientCanProtocolDefinition from '../vectors/protocol-definitions/recipient-can.json' assert { type: 'json' };
import threadRoleProtocolDefinition from '../vectors/protocol-definitions/thread-role.json' assert { type: 'json' };
import { ArrayUtility } from '../../src/utils/array.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { DwnMethodName } from '../../src/enums/dwn-interface-method.js';
import { Message } from '../../src/core/message.js';
import { normalizeSchemaUrl } from '../../src/utils/url.js';
import { RecordsDeleteHandler } from '../../src/handlers/records-delete.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import { Time } from '../../src/utils/time.js';
import { DataStream, Dwn, Encoder, Jws, RecordsDelete, RecordsRead, RecordsWrite } from '../../src/index.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testRecordsDeleteHandler() {
describe('RecordsDeleteHandler.handle()', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
describe('functional tests', () => {
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should handle RecordsDelete successfully and return 404 if deleting a deleted record', () => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// insert data
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const writeReply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(writeReply.status.code).to.equal(202);
// ensure data is inserted
const queryData = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { recordId: message.recordId }
});
const reply = yield dwn.processMessage(alice.did, queryData.message);
expect(reply.status.code).to.equal(200);
expect((_a = reply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(1);
// testing delete
const recordsDelete = yield RecordsDelete.create({
recordId: message.recordId,
signer: Jws.createSigner(alice)
});
const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(202);
// ensure a query will no longer find the deleted record
const reply2 = yield dwn.processMessage(alice.did, queryData.message);
expect(reply2.status.code).to.equal(200);
expect((_b = reply2.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(0);
// testing deleting a deleted record
const recordsDelete2 = yield RecordsDelete.create({
recordId: message.recordId,
signer: Jws.createSigner(alice)
});
const recordsDelete2Reply = yield dwn.processMessage(alice.did, recordsDelete2.message);
expect(recordsDelete2Reply.status.code).to.equal(404);
}));
it('should not affect other records or tenants with the same data', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const data = Encoder.stringToBytes('test');
// alice writes a records with data
const aliceWriteData = yield TestDataGenerator.generateRecordsWrite({ author: alice, data });
const aliceWriteReply = yield dwn.processMessage(alice.did, aliceWriteData.message, { dataStream: aliceWriteData.dataStream });
expect(aliceWriteReply.status.code).to.equal(202);
// alice writes another record with the same data
const aliceWrite2Data = yield TestDataGenerator.generateRecordsWrite({ author: alice, data });
const aliceWrite2Reply = yield dwn.processMessage(alice.did, aliceWrite2Data.message, { dataStream: aliceWrite2Data.dataStream });
expect(aliceWrite2Reply.status.code).to.equal(202);
// bob writes a records with same data
const bobWriteData = yield TestDataGenerator.generateRecordsWrite({ author: bob, data });
const bobWriteReply = yield dwn.processMessage(bob.did, bobWriteData.message, { dataStream: bobWriteData.dataStream });
expect(bobWriteReply.status.code).to.equal(202);
// bob writes another record with the same data
const bobWrite2Data = yield TestDataGenerator.generateRecordsWrite({ author: bob, data });
const bobWrite2Reply = yield dwn.processMessage(bob.did, bobWrite2Data.message, { dataStream: bobWrite2Data.dataStream });
expect(bobWrite2Reply.status.code).to.equal(202);
// alice deletes one of the two records
const aliceDeleteWriteData = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: aliceWriteData.message.recordId
});
const aliceDeleteWriteReply = yield dwn.processMessage(alice.did, aliceDeleteWriteData.message);
expect(aliceDeleteWriteReply.status.code).to.equal(202);
// verify the other record with the same data is unaffected
const aliceRead1 = yield RecordsRead.create({
filter: {
recordId: aliceWrite2Data.message.recordId,
},
signer: Jws.createSigner(alice)
});
const aliceRead1Reply = yield dwn.processMessage(alice.did, aliceRead1.message);
expect(aliceRead1Reply.status.code).to.equal(200);
const aliceDataFetched = yield DataStream.toBytes(aliceRead1Reply.record.data);
expect(ArrayUtility.byteArraysEqual(aliceDataFetched, data)).to.be.true;
// alice deletes the other record
const aliceDeleteWrite2Data = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: aliceWrite2Data.message.recordId
});
const aliceDeleteWrite2Reply = yield dwn.processMessage(alice.did, aliceDeleteWrite2Data.message);
expect(aliceDeleteWrite2Reply.status.code).to.equal(202);
// verify that alice can no longer fetch the 2nd record
const aliceRead2Reply = yield dwn.processMessage(alice.did, aliceRead1.message);
expect(aliceRead2Reply.status.code).to.equal(404);
// verify that bob can still fetch record with the same data
const bobRead1 = yield RecordsRead.create({
filter: {
recordId: bobWriteData.message.recordId,
},
signer: Jws.createSigner(bob)
});
const bobRead1Reply = yield dwn.processMessage(bob.did, bobRead1.message);
expect(bobRead1Reply.status.code).to.equal(200);
const bobDataFetched = yield DataStream.toBytes(bobRead1Reply.record.data);
expect(ArrayUtility.byteArraysEqual(bobDataFetched, data)).to.be.true;
}));
it('should return 404 if deleting a non-existent record', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// testing deleting a non-existent record
const recordsDelete = yield RecordsDelete.create({
recordId: 'nonExistentRecordId',
signer: Jws.createSigner(alice)
});
const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(404);
}));
it('should be disallowed if there is a newer RecordsWrite already in the DWN ', () => __awaiter(this, void 0, void 0, function* () {
var _c;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// initial write
const initialWriteData = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const initialWriteReply = yield dwn.processMessage(alice.did, initialWriteData.message, { dataStream: initialWriteData.dataStream });
expect(initialWriteReply.status.code).to.equal(202);
// generate subsequent write and delete with the delete having an earlier timestamp
// NOTE: creating RecordsDelete first ensures it has an earlier `messageTimestamp` time
const recordsDelete = yield RecordsDelete.create({
recordId: initialWriteData.message.recordId,
signer: Jws.createSigner(alice)
});
yield Time.minimalSleep();
const subsequentWriteData = yield TestDataGenerator.generateFromRecordsWrite({
existingWrite: initialWriteData.recordsWrite,
author: alice
});
// subsequent write
const subsequentWriteReply = yield dwn.processMessage(alice.did, subsequentWriteData.message, { dataStream: subsequentWriteData.dataStream });
expect(subsequentWriteReply.status.code).to.equal(202);
// test that a delete with an earlier `messageTimestamp` results in a 409
const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(409);
// ensure data still exists
const queryData = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { recordId: initialWriteData.message.recordId }
});
const expectedEncodedData = Encoder.bytesToBase64Url(subsequentWriteData.dataBytes);
const reply = yield dwn.processMessage(alice.did, queryData.message);
expect(reply.status.code).to.equal(200);
expect((_c = reply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(1);
expect(reply.entries[0].encodedData).to.equal(expectedEncodedData);
}));
it('should be able to delete then rewrite the same data', () => __awaiter(this, void 0, void 0, function* () {
var _d, _e, _f;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const data = Encoder.stringToBytes('test');
const encodedData = Encoder.bytesToBase64Url(data);
// alice writes a record
const aliceWriteData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data
});
const aliceWriteReply = yield dwn.processMessage(alice.did, aliceWriteData.message, { dataStream: aliceWriteData.dataStream });
expect(aliceWriteReply.status.code).to.equal(202);
const aliceQueryWriteAfterAliceWriteData = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { recordId: aliceWriteData.message.recordId }
});
const aliceQueryWriteAfterAliceWriteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceWriteData.message);
expect(aliceQueryWriteAfterAliceWriteReply.status.code).to.equal(200);
expect((_d = aliceQueryWriteAfterAliceWriteReply.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(1);
expect(aliceQueryWriteAfterAliceWriteReply.entries[0].encodedData).to.equal(encodedData);
// alice deleting the record
const aliceDeleteWriteData = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: aliceWriteData.message.recordId
});
const aliceDeleteWriteReply = yield dwn.processMessage(alice.did, aliceDeleteWriteData.message);
expect(aliceDeleteWriteReply.status.code).to.equal(202);
const aliceQueryWriteAfterAliceDeleteData = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { recordId: aliceWriteData.message.recordId }
});
const aliceQueryWriteAfterAliceDeleteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceDeleteData.message);
expect(aliceQueryWriteAfterAliceDeleteReply.status.code).to.equal(200);
expect((_e = aliceQueryWriteAfterAliceDeleteReply.entries) === null || _e === void 0 ? void 0 : _e.length).to.equal(0);
// alice writes a new record with the same data
const aliceRewriteData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data
});
const aliceRewriteReply = yield dwn.processMessage(alice.did, aliceRewriteData.message, { dataStream: aliceRewriteData.dataStream });
expect(aliceRewriteReply.status.code).to.equal(202);
const aliceQueryWriteAfterAliceRewriteData = yield TestDataGenerator.generateRecordsQuery({
author: alice,
filter: { recordId: aliceRewriteData.message.recordId }
});
const aliceQueryWriteAfterAliceRewriteReply = yield dwn.processMessage(alice.did, aliceQueryWriteAfterAliceRewriteData.message);
expect(aliceQueryWriteAfterAliceRewriteReply.status.code).to.equal(200);
expect((_f = aliceQueryWriteAfterAliceRewriteReply.entries) === null || _f === void 0 ? void 0 : _f.length).to.equal(1);
expect(aliceQueryWriteAfterAliceRewriteReply.entries[0].encodedData).to.equal(encodedData);
}));
describe('protocol based deletes', () => {
it('should allow delete with allow-anyone rule', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a record in her DWN. Bob (anyone) is able to delete the record.
const protocolDefinition = anyoneCollaborateProtocolDefinition;
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
// setting up a stub DID resolver
TestStubGenerator.stubDidResolver(didResolver, [alice, bob]);
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a record
const recordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'doc',
});
const recordsWriteReply = yield dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
expect(recordsWriteReply.status.code).to.eq(202);
// Bob (anyone) is able to delete the record
const recordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: recordsWrite.message.recordId,
});
const recordsDeleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(recordsDeleteReply.status.code).to.eq(202);
}));
describe('recipient rules', () => {
it('should allow delete with ancestor recipient rule', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a 'post' with Bob as recipient and a 'post/tag'. Bob is able to delete
// the 'chat/tag' because he was recipient of the 'chat'. Carol is not able to delete.
const protocolDefinition = recipientCanProtocolDefinition;
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const carol = yield TestDataGenerator.generatePersona();
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
// setting up a stub DID resolver
TestStubGenerator.stubDidResolver(didResolver, [alice, bob, carol]);
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a chat
const chatRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'post',
});
const chatRecordsWriteReply = yield dwn.processMessage(alice.did, chatRecordsWrite.message, { dataStream: chatRecordsWrite.dataStream });
expect(chatRecordsWriteReply.status.code).to.eq(202);
// Alice writes a 'chat/tag'
const tagRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'post/tag',
parentContextId: chatRecordsWrite.message.contextId,
});
const tagRecordsWriteReply = yield dwn.processMessage(alice.did, tagRecordsWrite.message, { dataStream: tagRecordsWrite.dataStream });
expect(tagRecordsWriteReply.status.code).to.eq(202);
// Carol is unable to delete the 'chat/tag'
const recordsDeleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: carol,
recordId: tagRecordsWrite.message.recordId,
});
const recordsDeleteCarolReply = yield dwn.processMessage(alice.did, recordsDeleteCarol.message);
expect(recordsDeleteCarolReply.status.code).to.eq(401);
expect(recordsDeleteCarolReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Bob is able to delete the 'chat/tag'
const recordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: tagRecordsWrite.message.recordId,
});
const recordsDeleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(recordsDeleteReply.status.code).to.eq(202);
}));
it('should allow delete with direct recipient rule', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a 'post' with Bob as recipient. Bob is able to delete
// the 'post' because he was recipient of it. Carol is not able to delete.
const protocolDefinition = recipientCanProtocolDefinition;
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const carol = yield TestDataGenerator.generatePersona();
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
// setting up a stub DID resolver
TestStubGenerator.stubDidResolver(didResolver, [alice, bob, carol]);
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice creates a 'post' with Bob as recipient
const recordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'post',
});
const recordsWriteReply = yield dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
expect(recordsWriteReply.status.code).to.eq(202);
// Carol is unable to delete the 'post'
const carolRecordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: carol,
recordId: recordsWrite.message.recordId,
});
const carolRecordsDeleteReply = yield dwn.processMessage(alice.did, carolRecordsDelete.message);
expect(carolRecordsDeleteReply.status.code).to.eq(401);
expect(carolRecordsDeleteReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Bob is able to delete the post
const bobRecordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: recordsWrite.message.recordId,
});
const bobRecordsDeleteReply = yield dwn.processMessage(alice.did, bobRecordsDelete.message);
expect(bobRecordsDeleteReply.status.code).to.eq(202);
}));
});
describe('author action rules', () => {
it('allow author to delete with ancestor author rule', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Bob writes a 'post' and Alice writes a 'post/comment' to her DWN. Bob deletes the comment
// because author of post can delete. Carol is unable to delete the comment.
const protocolDefinition = authorCanProtocolDefinition;
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const carol = yield TestDataGenerator.generatePersona();
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
// setting up a stub DID resolver
TestStubGenerator.stubDidResolver(didResolver, [alice, bob, carol]);
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Bob writes a post
const postRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolPath: 'post',
});
const postRecordsWriteReply = yield dwn.processMessage(alice.did, postRecordsWrite.message, { dataStream: postRecordsWrite.dataStream });
expect(postRecordsWriteReply.status.code).to.eq(202);
// Alice writes a 'post/comment'
const commentRecordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'post/comment',
parentContextId: postRecordsWrite.message.contextId,
});
const commentRecordsWriteReply = yield dwn.processMessage(alice.did, commentRecordsWrite.message, { dataStream: commentRecordsWrite.dataStream });
expect(commentRecordsWriteReply.status.code).to.eq(202);
// Carol is unable to delete Alice's 'post/comment'
const recordsDeleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: carol,
recordId: commentRecordsWrite.message.recordId,
});
const recordsDeleteCarolReply = yield dwn.processMessage(alice.did, recordsDeleteCarol.message);
expect(recordsDeleteCarolReply.status.code).to.eq(401);
expect(recordsDeleteCarolReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Bob is able to delete the Alice's 'post/comment'
const recordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: commentRecordsWrite.message.recordId,
});
const recordsDeleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(recordsDeleteReply.status.code).to.eq(202);
}));
});
describe('role based deletes', () => {
it('should allow co-delete by invoking a context role', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice adds Bob as a 'thread/admin' role. She writes a 'thread/chat'.
// Bob invokes his admin role to delete the 'thread/chat'. Carol is unable to delete
// the 'thread/chat'.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = threadRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice creates a thread
const threadRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread'
});
const threadRecordReply = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRecordReply.status.code).to.equal(202);
// Alice adds Bob as a 'thread/admin' in that thread
const participantRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/admin',
parentContextId: threadRecord.message.contextId,
});
const participantRecordReply = yield dwn.processMessage(alice.did, participantRecord.message, { dataStream: participantRecord.dataStream });
expect(participantRecordReply.status.code).to.equal(202);
// Alice writes a chat message in that thread
const chatRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: alice.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
parentContextId: threadRecord.message.contextId,
});
const chatRecordReply = yield dwn.processMessage(alice.did, chatRecord.message, { dataStream: chatRecord.dataStream });
expect(chatRecordReply.status.code).to.equal(202);
// Verifies that Carol cannot delete without appropriate role
const chatDeleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: carol,
recordId: chatRecord.message.recordId,
});
const chatDeleteReplyCarol = yield dwn.processMessage(alice.did, chatDeleteCarol.message);
expect(chatDeleteReplyCarol.status.code).to.eq(401);
expect(chatDeleteReplyCarol.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Bob invokes the role to delete the chat message
const chatDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: chatRecord.message.recordId,
protocolRole: 'thread/admin',
});
const chatDeleteReply = yield dwn.processMessage(alice.did, chatDelete.message);
expect(chatDeleteReply.status.code).to.equal(202);
}));
it('should allow co-delete invoking a root-level role', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice adds Bob as a root-level 'admin' role. She writes a 'chat'.
// Bob invokes his admin role to delete the 'chat'.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = friendRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice adds Bob as a 'thread/admin' in that thread
const participantRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'admin',
});
const participantRecordReply = yield dwn.processMessage(alice.did, participantRecord.message, { dataStream: participantRecord.dataStream });
expect(participantRecordReply.status.code).to.equal(202);
// Alice writes a chat message in that thread
const chatRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: alice.did,
protocol: protocolDefinition.protocol,
protocolPath: 'chat',
});
const chatRecordReply = yield dwn.processMessage(alice.did, chatRecord.message, { dataStream: chatRecord.dataStream });
expect(chatRecordReply.status.code).to.equal(202);
// Carol is unable to delete the chat message
const chatDeleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: carol,
recordId: chatRecord.message.recordId,
});
const chatDeleteCarolReply = yield dwn.processMessage(alice.did, chatDeleteCarol.message);
expect(chatDeleteCarolReply.status.code).to.equal(401);
expect(chatDeleteCarolReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// Bob invokes the role to delete the chat message
const chatDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: chatRecord.message.recordId,
protocolRole: 'admin',
});
const chatDeleteReply = yield dwn.processMessage(alice.did, chatDelete.message);
expect(chatDeleteReply.status.code).to.equal(202);
}));
});
});
it('should return 401 if message is not authorized', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a record and Bob is unable to delete it.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const recordsWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const recordsWriteReply = yield dwn.processMessage(alice.did, recordsWrite.message, { dataStream: recordsWrite.dataStream });
expect(recordsWriteReply.status.code).to.equal(202);
const recordsDelete = yield TestDataGenerator.generateRecordsDelete({
author: bob,
recordId: recordsWrite.message.recordId,
});
const recordsDeleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(recordsDeleteReply.status.code).to.equal(401);
expect(recordsDeleteReply.status.detail).to.contain(DwnErrorCode.RecordsDeleteAuthorizationFailed);
}));
it('should index additional properties from the RecordsWrite being deleted', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// initial write
const initialWriteData = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'testSchema' });
const initialWriteReply = yield dwn.processMessage(alice.did, initialWriteData.message, { dataStream: initialWriteData.dataStream });
expect(initialWriteReply.status.code).to.equal(202);
// generate subsequent write and delete with the delete having an earlier timestamp
// NOTE: creating RecordsDelete first ensures it has an earlier `messageTimestamp` time
const recordsDelete = yield RecordsDelete.create({
recordId: initialWriteData.message.recordId,
signer: Jws.createSigner(alice)
});
const deleteMessageCid = yield Message.getCid(recordsDelete.message);
const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(202);
// message store
const { messages } = yield messageStore.query(alice.did, [{ schema: normalizeSchemaUrl('testSchema'), method: DwnMethodName.Delete }]);
expect(messages.length).to.equal(1);
expect(yield Message.getCid(messages[0])).to.equal(deleteMessageCid);
// event log
const { events } = yield eventLog.queryEvents(alice.did, [{ schema: normalizeSchemaUrl('testSchema'), method: DwnMethodName.Delete }]);
expect(events.length).to.equal(1);
expect(events[0]).to.equal(deleteMessageCid);
}));
describe('event log', () => {
it('should include RecordsDelete event and keep initial RecordsWrite event', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const { message, dataStream } = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const writeReply = yield dwn.processMessage(alice.did, message, { dataStream });
expect(writeReply.status.code).to.equal(202);
const recordsDelete = yield RecordsDelete.create({
recordId: message.recordId,
signer: Jws.createSigner(alice)
});
const deleteReply = yield dwn.processMessage(alice.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(202);
const { events } = yield eventLog.getEvents(alice.did);
expect(events.length).to.equal(2);
const writeMessageCid = yield Message.getCid(message);
const deleteMessageCid = yield Message.getCid(recordsDelete.message);
const expectedMessageCids = new Set([writeMessageCid, deleteMessageCid]);
for (const messageCid of events) {
expectedMessageCids.delete(messageCid);
}
expect(expectedMessageCids.size).to.equal(0);
}));
it('should only keep first write and delete when subsequent writes happen', () => __awaiter(this, void 0, void 0, function* () {
const { message, author, dataStream, recordsWrite } = yield TestDataGenerator.generateRecordsWrite();
TestStubGenerator.stubDidResolver(didResolver, [author]);
const reply = yield dwn.processMessage(author.did, message, { dataStream });
expect(reply.status.code).to.equal(202);
const newWrite = yield RecordsWrite.createFrom({
recordsWriteMessage: recordsWrite.message,
published: true,
signer: Jws.createSigner(author)
});
const newWriteReply = yield dwn.processMessage(author.did, newWrite.message);
expect(newWriteReply.status.code).to.equal(202);
const recordsDelete = yield RecordsDelete.create({
recordId: message.recordId,
signer: Jws.createSigner(author)
});
const deleteReply = yield dwn.processMessage(author.did, recordsDelete.message);
expect(deleteReply.status.code).to.equal(202);
const { events } = yield eventLog.getEvents(author.did);
expect(events.length).to.equal(2);
const deletedMessageCid = yield Message.getCid(newWrite.message);
for (const messageCid of events) {
if (messageCid === deletedMessageCid) {
expect.fail(`${messageCid} should not exist`);
}
}
}));
});
});
it('should return 401 if signature check fails', () => __awaiter(this, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsDelete();
const tenant = author.did;
// setting up a stub did resolver & message store
// intentionally not supplying the public key so a different public key is generated to simulate invalid signature
const mismatchingPersona = yield TestDataGenerator.generatePersona({ did: author.did, keyId: author.keyId });
const didResolver = TestStubGenerator.createDidResolverStub(mismatchingPersona);
const messageStore = stubInterface();
const dataStore = stubInterface();
const recordsDeleteHandler = new RecordsDeleteHandler(didResolver, messageStore, dataStore, eventLog, eventStream);
const reply = yield recordsDeleteHandler.handle({ tenant, message });
expect(reply.status.code).to.equal(401);
}));
it('should return 400 if fail parsing the message', () => __awaiter(this, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsDelete();
const tenant = author.did;
// setting up a stub method resolver & message store
const messageStore = stubInterface();
const dataStore = stubInterface();
const recordsDeleteHandler = new RecordsDeleteHandler(didResolver, messageStore, dataStore, eventLog, eventStream);
// stub the `parse()` function to throw an error
sinon.stub(RecordsDelete, 'parse').throws('anyError');
const reply = yield recordsDeleteHandler.handle({ tenant, message });
expect(reply.status.code).to.equal(400);
}));
});
}
//# sourceMappingURL=records-delete.spec.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,591 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import chai, { expect } from 'chai';
import friendRoleProtocolDefinition from '../vectors/protocol-definitions/friend-role.json' assert { type: 'json' };
import threadRoleProtocolDefinition from '../vectors/protocol-definitions/thread-role.json' assert { type: 'json' };
import { Jws } from '../../src/utils/jws.js';
import { Message } from '../../src/core/message.js';
import { RecordsSubscribe } from '../../src/interfaces/records-subscribe.js';
import { RecordsSubscribeHandler } from '../../src/handlers/records-subscribe.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { Dwn, Time } from '../../src/index.js';
import { DwnErrorCode, DwnInterfaceName, DwnMethodName } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testRecordsSubscribeHandler() {
describe('RecordsSubscribeHandler.handle()', () => {
describe('EventStream disabled', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
dwn = yield Dwn.create({
didResolver,
messageStore,
dataStore,
eventLog,
});
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should respond with a 501 if subscriptions are not supported', () => __awaiter(this, void 0, void 0, function* () {
yield dwn.close(); // close the original dwn instance
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog }); // leave out eventStream
const alice = yield TestDataGenerator.generateDidKeyPersona();
// attempt to subscribe
const { message } = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
});
const subscriptionMessageReply = yield dwn.processMessage(alice.did, message, { subscriptionHandler: (_) => { } });
expect(subscriptionMessageReply.status.code).to.equal(501, subscriptionMessageReply.status.detail);
expect(subscriptionMessageReply.status.detail).to.include(DwnErrorCode.RecordsSubscribeEventStreamUnimplemented);
}));
});
describe('functional tests', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should return a subscription object', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const recordsSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { schema: 'some-schema' },
});
// Send records subscribe message
const reply = yield dwn.processMessage(alice.did, recordsSubscribe.message, { subscriptionHandler: () => { } });
expect(reply.status.code).to.equal(200);
expect(reply.subscription).to.exist;
}));
it('should return 400 if protocol is not normalized', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// subscribe for non-normalized protocol
const recordsSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { protocol: 'example.com/' },
});
// overwrite protocol because #create auto-normalizes protocol
recordsSubscribe.message.descriptor.filter.protocol = 'example.com/';
// Re-create auth because we altered the descriptor after signing
recordsSubscribe.message.authorization = yield Message.createAuthorization({
descriptor: recordsSubscribe.message.descriptor,
signer: Jws.createSigner(alice)
});
// Send records subscribe message
const reply = yield dwn.processMessage(alice.did, recordsSubscribe.message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(DwnErrorCode.UrlProtocolNotNormalized);
}));
it('should return 400 if schema is not normalized', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// subscribe for non-normalized schema
const recordsSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { schema: 'example.com/' },
});
// overwrite schema because #create auto-normalizes schema
recordsSubscribe.message.descriptor.filter.schema = 'example.com/';
// Re-create auth because we altered the descriptor after signing
recordsSubscribe.message.authorization = yield Message.createAuthorization({
descriptor: recordsSubscribe.message.descriptor,
signer: Jws.createSigner(alice)
});
// Send records subscribe message
const reply = yield dwn.processMessage(alice.did, recordsSubscribe.message);
expect(reply.status.code).to.equal(400);
expect(reply.status.detail).to.contain(DwnErrorCode.UrlSchemaNotNormalized);
}));
it('should return 400 if published is set to false and a datePublished range is provided', () => __awaiter(this, void 0, void 0, function* () {
const fromDatePublished = Time.getCurrentTimestamp();
const alice = yield TestDataGenerator.generateDidKeyPersona();
// set to true so create does not fail
const recordSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { datePublished: { from: fromDatePublished }, published: true }
});
// set to false
recordSubscribe.message.descriptor.filter.published = false;
const subscribeResponse = yield dwn.processMessage(alice.did, recordSubscribe.message);
expect(subscribeResponse.status.code).to.equal(400);
expect(subscribeResponse.status.detail).to.contain('descriptor/filter/published: must be equal to one of the allowed values');
}));
it('should return 401 for anonymous subscriptions that filter explicitly for unpublished records', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// create an unpublished record
const draftWrite = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'post' });
const draftWriteReply = yield dwn.processMessage(alice.did, draftWrite.message, { dataStream: draftWrite.dataStream });
expect(draftWriteReply.status.code).to.equal(202);
// validate that alice can subscribe
const unpublishedPostSubscribe = yield TestDataGenerator.generateRecordsSubscribe({ author: alice, filter: { schema: 'post', published: false } });
const unpublishedPostReply = yield dwn.processMessage(alice.did, unpublishedPostSubscribe.message, { subscriptionHandler: () => { } });
expect(unpublishedPostReply.status.code).to.equal(200);
expect(unpublishedPostReply.subscription).to.exist;
// anonymous subscribe for unpublished records
const unpublishedAnonymous = yield RecordsSubscribe.create({ filter: { schema: 'post', published: false } });
const anonymousPostReply = yield dwn.processMessage(alice.did, unpublishedAnonymous.message);
expect(anonymousPostReply.status.code).to.equal(401);
expect(anonymousPostReply.status.detail).contains('Missing JWS');
expect(anonymousPostReply.subscription).to.not.exist;
}));
it('should return 401 if signature check fails', () => __awaiter(this, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsSubscribe();
const tenant = author.did;
// setting up a stub did resolver & message store
// intentionally not supplying the public key so a different public key is generated to simulate invalid signature
const mismatchingPersona = yield TestDataGenerator.generatePersona({ did: author.did, keyId: author.keyId });
const didResolver = TestStubGenerator.createDidResolverStub(mismatchingPersona);
const messageStore = stubInterface();
const eventStream = stubInterface();
const recordsSubscribeHandler = new RecordsSubscribeHandler(didResolver, messageStore, eventStream);
const reply = yield recordsSubscribeHandler.handle({ tenant, message, subscriptionHandler: () => { } });
expect(reply.status.code).to.equal(401);
}));
it('should return 400 if fail parsing the message', () => __awaiter(this, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsSubscribe();
const tenant = author.did;
// setting up a stub method resolver & message store
const didResolver = TestStubGenerator.createDidResolverStub(author);
const messageStore = stubInterface();
const eventStream = stubInterface();
const recordsSubscribeHandler = new RecordsSubscribeHandler(didResolver, messageStore, eventStream);
// stub the `parse()` function to throw an error
sinon.stub(RecordsSubscribe, 'parse').throws('anyError');
const reply = yield recordsSubscribeHandler.handle({ tenant, message, subscriptionHandler: () => { } });
expect(reply.status.code).to.equal(400);
}));
describe('protocol based subscriptions', () => {
it('does not try protocol authorization if protocolRole is not invoked', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a thread and writes some chat messages. Alice addresses
// only one chat message to Bob. Bob subscribes by protocol URI without invoking a protocolRole,
// and he is able to receive the message addressed to him.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = threadRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
const messageCids = [];
const addCid = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
const bobSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: {
published: false,
protocol: protocolDefinition.protocol,
}
});
const subscriptionReply = yield dwn.processMessage(alice.did, bobSubscription.message, { subscriptionHandler: addCid });
expect(subscriptionReply.status.code).to.equal(200);
expect(subscriptionReply.subscription).to.exist;
// Alice writes a 'thread' record
const threadRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'thread',
});
const threadRoleReply = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRoleReply.status.code).to.equal(202);
// Alice writes one 'chat' record addressed to Bob
const chatRecordForBob = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
published: false,
parentContextId: threadRecord.message.contextId,
data: new TextEncoder().encode('Bob can read this cuz he is my friend'),
});
const chatRecordForBobReply = yield dwn.processMessage(alice.did, chatRecordForBob.message, { dataStream: chatRecordForBob.dataStream });
expect(chatRecordForBobReply.status.code).to.equal(202);
// Alice writes two 'chat' records NOT addressed to Bob
for (let i = 0; i < 2; i++) {
const chatRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: alice.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
published: false,
parentContextId: threadRecord.message.contextId,
data: new TextEncoder().encode('Bob cannot read this'),
});
const chatReply = yield dwn.processMessage(alice.did, chatRecord.message, { dataStream: chatRecord.dataStream });
expect(chatReply.status.code).to.equal(202);
}
expect(messageCids.length).to.equal(1, 'before delete');
expect(messageCids[0]).to.equal(yield Message.getCid(chatRecordForBob.message));
}));
it('should allows role authorized subscriptions', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice creates a thread and writes some chat messages writes a chat message. Bob invokes his
// thread member role in order to subscribe to the chat messages.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = friendRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
const filter = {
published: false,
protocol: protocolDefinition.protocol,
protocolPath: 'chat'
};
const noRoleRecords = [];
const addNoRole = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
if (message.descriptor.method === DwnMethodName.Write) {
const recordsWriteMessage = message;
noRoleRecords.push(recordsWriteMessage.recordId);
}
});
// subscribe without role, expect no messages
const noRoleSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter
});
const subscriptionReply = yield dwn.processMessage(alice.did, noRoleSubscription.message, { subscriptionHandler: addNoRole });
expect(subscriptionReply.status.code).to.equal(200);
expect(subscriptionReply.subscription).to.exist;
// Alice writes a 'friend' root-level role record with Bob as recipient
const friendRoleRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'friend',
data: new TextEncoder().encode('Bob is my friend'),
});
const friendRoleReply = yield dwn.processMessage(alice.did, friendRoleRecord.message, { dataStream: friendRoleRecord.dataStream });
expect(friendRoleReply.status.code).to.equal(202);
const recordIds = [];
const addRecord = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
if (message.descriptor.method === DwnMethodName.Write) {
const recordsWriteMessage = message;
recordIds.push(recordsWriteMessage.recordId);
}
});
// subscribe with friend role
const bobSubscriptionWithRole = yield TestDataGenerator.generateRecordsSubscribe({
filter,
author: bob,
protocolRole: 'friend',
});
const subscriptionWithRoleReply = yield dwn.processMessage(alice.did, bobSubscriptionWithRole.message, { subscriptionHandler: addRecord });
expect(subscriptionWithRoleReply.status.code).to.equal(200);
expect(subscriptionWithRoleReply.subscription).to.exist;
// Alice writes three 'chat' records
const chatRecordIds = [];
for (let i = 0; i < 3; i++) {
const chatRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: alice.did,
protocol: protocolDefinition.protocol,
protocolPath: 'chat',
published: false,
data: new TextEncoder().encode('Bob can read this cuz he is my friend'),
});
const chatReply = yield dwn.processMessage(alice.did, chatRecord.message, { dataStream: chatRecord.dataStream });
expect(chatReply.status.code).to.equal(202);
chatRecordIds.push(chatRecord.message.recordId);
}
// there should not be any messages in the subscription without a friend role.
expect(noRoleRecords.length).to.equal(0);
// should have all chat messages
expect(recordIds).to.have.members(chatRecordIds);
}));
it('allows protocol authorized subscriptions', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice writes some chat messages.
// Bob, having a thread/participant record, can subscribe to the chat.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = threadRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a 'thread' record
const threadRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'thread',
});
const threadRoleReply = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRoleReply.status.code).to.equal(202);
const filter = {
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
contextId: threadRecord.message.contextId,
};
const noRoleRecords = [];
const addNoRole = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
if (message.descriptor.interface === DwnInterfaceName.Records && message.descriptor.method === DwnMethodName.Write) {
const recordsWriteMessage = message;
noRoleRecords.push(recordsWriteMessage.recordId);
}
});
// subscribe without role, expect no messages
const noRoleSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter
});
const subscriptionReply = yield dwn.processMessage(alice.did, noRoleSubscription.message, { subscriptionHandler: addNoRole });
expect(subscriptionReply.status.code).to.equal(200);
expect(subscriptionReply.subscription).to.exist;
// Alice writes a 'participant' role record with Bob as recipient
const participantRoleRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/participant',
parentContextId: threadRecord.message.contextId,
data: new TextEncoder().encode('Bob is my friend'),
});
const participantRoleReply = yield dwn.processMessage(alice.did, participantRoleRecord.message, { dataStream: participantRoleRecord.dataStream });
expect(participantRoleReply.status.code).to.equal(202);
const recordIds = [];
const addRecord = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
if (message.descriptor.method === DwnMethodName.Write) {
const recordsWriteMessage = message;
recordIds.push(recordsWriteMessage.recordId);
}
});
// subscribe with the participant role
const bobSubscriptionWithRole = yield TestDataGenerator.generateRecordsSubscribe({
filter,
author: bob,
protocolRole: 'thread/participant',
});
const subscriptionWithRoleReply = yield dwn.processMessage(alice.did, bobSubscriptionWithRole.message, { subscriptionHandler: addRecord });
expect(subscriptionWithRoleReply.status.code).to.equal(200);
expect(subscriptionWithRoleReply.subscription).to.exist;
// Alice writes three 'chat' records
const chatRecordIds = [];
for (let i = 0; i < 3; i++) {
const chatRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: alice.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
published: false,
parentContextId: threadRecord.message.contextId,
data: new TextEncoder().encode('Bob can read this cuz he is my friend'),
});
const chatReply = yield dwn.processMessage(alice.did, chatRecord.message, { dataStream: chatRecord.dataStream });
expect(chatReply.status.code).to.equal(202);
chatRecordIds.push(chatRecord.message.recordId);
}
// there should not be any messages in the subscription without a participant role.
expect(noRoleRecords.length).to.equal(0);
// should have all chat messages.
expect(recordIds).to.have.members(chatRecordIds);
}));
it('does not execute protocol subscriptions where protocolPath is missing from the filter', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice assigns Bob a friend role and writes some chat messages. Bob invokes his role to subscribe those messages,
// but his subscription filter does not include protocolPath.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = friendRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a 'friend' root-level role record with Bob as recipient
const friendRoleRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'friend',
data: new TextEncoder().encode('Bob is my friend'),
});
const friendRoleReply = yield dwn.processMessage(alice.did, friendRoleRecord.message, { dataStream: friendRoleRecord.dataStream });
expect(friendRoleReply.status.code).to.equal(202);
// Bob invokes his friendRole to subscribe but does not have `protocolPath` in the filter
const chatSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: {
protocol: protocolDefinition.protocol,
// protocolPath deliberately omitted
},
protocolRole: 'friend',
});
const chatSubscribeReply = yield dwn.processMessage(alice.did, chatSubscribe.message);
expect(chatSubscribeReply.status.code).to.equal(400);
expect(chatSubscribeReply.status.detail).to.contain(DwnErrorCode.RecordsSubscribeFilterMissingRequiredProperties);
expect(chatSubscribeReply.subscription).to.not.exist;
}));
it('does not execute context role authorized subscriptions where contextId is missing from the filter', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice gives Bob a role allowing him to access a particular chat thread.
// But Bob's filter does not contain a contextId so the subscription fails.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = threadRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a 'thread' record
const threadRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'thread',
});
const threadRoleReply = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRoleReply.status.code).to.equal(202);
// Alice writes a 'friend' root-level role record with Bob as recipient
const participantRoleRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'thread/participant',
parentContextId: threadRecord.message.contextId,
data: new TextEncoder().encode('Bob is my friend'),
});
const participantRoleReply = yield dwn.processMessage(alice.did, participantRoleRecord.message, { dataStream: participantRoleRecord.dataStream });
expect(participantRoleReply.status.code).to.equal(202);
// Bob invokes his thread participant role to subscribe but omits the contextId
const chatSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
// contextId deliberately omitted
},
protocolRole: 'thread/participant',
});
const chatSubscribeReply = yield dwn.processMessage(alice.did, chatSubscribe.message);
expect(chatSubscribeReply.status.code).to.eq(401);
expect(chatSubscribeReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMissingContextId);
expect(chatSubscribeReply.subscription).to.not.exist;
}));
it('rejects role authorized subscriptions if the request author does not have a matching root-level role', () => __awaiter(this, void 0, void 0, function* () {
// scenario: Alice installs a chat protocol.
// Bob invokes a root-level role within that protocol to subscribe but fails because he does not actually have a role.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = friendRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Bob invokes a friendRole he does not have to subscribe to the records
const chatSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'chat',
},
protocolRole: 'friend',
});
const chatSubscribeReply = yield dwn.processMessage(alice.did, chatSubscribe.message);
expect(chatSubscribeReply.status.code).to.eq(401);
expect(chatSubscribeReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound);
expect(chatSubscribeReply.subscription).to.not.exist;
}));
it('rejects role authorized subscriptions where the subscription author does not have a matching context role', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const protocolDefinition = threadRoleProtocolDefinition;
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// Alice writes a 'thread' record
const threadRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'thread',
});
const threadRoleReply = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRoleReply.status.code).to.equal(202);
// Bob invokes his a `thread/participant` role which he does not have to subscribe to the records
const chatSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'thread/chat',
contextId: threadRecord.message.contextId,
},
protocolRole: 'thread/participant',
});
const chatSubscribeReply = yield dwn.processMessage(alice.did, chatSubscribe.message);
expect(chatSubscribeReply.status.code).to.eq(401);
expect(chatSubscribeReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound);
expect(chatSubscribeReply.subscription).to.not.exist;
}));
});
});
});
}
//# sourceMappingURL=records-subscribe.spec.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,79 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { EventsGet } from '../../src/interfaces/events-get.js';
import { expect } from 'chai';
import { Jws } from '../../src/index.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
describe('EventsGet Message', () => {
describe('create', () => {
it('creates an EventsGet message', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const eventsGet = yield EventsGet.create({
cursor: {
messageCid: 'messageCid',
value: 'value'
},
signer: Jws.createSigner(alice)
});
const { message } = eventsGet;
expect(message.descriptor).to.exist;
expect(message.descriptor.cursor).to.eql({ messageCid: 'messageCid', value: 'value' });
expect(message.authorization).to.exist;
}));
it('does not require a cursor', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const eventsGet = yield EventsGet.create({
signer: yield Jws.createSigner(alice)
});
const message = eventsGet.message;
expect(message.descriptor).to.exist;
expect(message.descriptor.cursor).to.not.exist;
expect(message.authorization).to.exist;
}));
});
describe('parse', () => {
it('parses a message into an EventsGet instance', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const eventsGet = yield EventsGet.create({
cursor: {
messageCid: 'messageCid',
value: 'value'
},
signer: Jws.createSigner(alice)
});
const parsed = yield EventsGet.parse(eventsGet.message);
expect(parsed).to.be.instanceof(EventsGet);
const expectedMessageCid = yield Message.getCid(eventsGet.message);
const messageCid = yield Message.getCid(parsed.message);
expect(messageCid).to.equal(expectedMessageCid);
}));
it('throws an exception if message is not a valid EventsGet message', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const eventsGet = yield EventsGet.create({
cursor: {
messageCid: 'messageCid',
value: 'value'
},
signer: Jws.createSigner(alice)
});
const { message } = eventsGet;
message['hehe'] = 'troll';
try {
yield EventsGet.parse(message);
expect.fail();
}
catch (e) {
expect(e.message).to.include('additional properties');
}
}));
});
});
//# sourceMappingURL=events-get.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"events-get.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/events-get.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,139 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { EventsQuery } from '../../src/interfaces/events-query.js';
import { Jws } from '../../src/utils/jws.js';
import { Message } from '../../src/core/message.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
chai.use(chaiAsPromised);
describe('EventsQuery Message', () => {
describe('create()', () => {
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'anything' }],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
expect(eventsQuery.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
signer: Jws.createSigner(alice),
filters: [{ protocol: 'example.com/' }],
};
const eventsQuery = yield EventsQuery.create(options);
const message = eventsQuery.message;
expect(message.descriptor.filters.length).to.equal(1);
expect(message.descriptor.filters[0].protocol).to.eq('http://example.com');
}));
it('should auto-normalize schema URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
signer: Jws.createSigner(alice),
filters: [{ schema: 'example.com/' }],
};
const eventsQuery = yield EventsQuery.create(options);
const message = eventsQuery.message;
expect(message.descriptor.filters.length).to.equal(1);
expect(message.descriptor.filters[0].schema).to.eq('http://example.com');
}));
it('throws an exception if message has no filters', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQueryPromise = EventsQuery.create({
filters: [],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
yield expect(eventsQueryPromise).to.eventually.be.rejectedWith('fewer than 1 items');
}));
it('removes empty filters', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
// single empty filter fails
const eventsQueryPromise = EventsQuery.create({
filters: [{}],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
yield expect(eventsQueryPromise).to.eventually.be.rejectedWith('fewer than 1 items');
// empty filter gets removed, valid filter remains
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'schema' }, {}],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
expect(eventsQuery.message.descriptor.filters.length).to.equal(1);
}));
});
describe('parse', () => {
it('parses a message into an EventsQuery instance', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'anything' }],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
const parsed = yield EventsQuery.parse(eventsQuery.message);
expect(parsed).to.be.instanceof(EventsQuery);
const expectedMessageCid = yield Message.getCid(eventsQuery.message);
const messageCid = yield Message.getCid(parsed.message);
expect(messageCid).to.equal(expectedMessageCid);
}));
it('throws an exception if message is not a valid EventsQuery message', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'anything' }],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
const { message } = eventsQuery;
message.descriptor['bad_property'] = 'property';
const eventsQueryPromise = EventsQuery.parse(message);
yield expect(eventsQueryPromise).to.eventually.be.rejectedWith('must NOT have additional properties');
}));
it('throws an exception if message has no filters', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'anything' }],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
const { message } = eventsQuery;
message.descriptor.filters = []; //empty out the filters
const eventsQueryPromise = EventsQuery.parse(message);
yield expect(eventsQueryPromise).to.eventually.be.rejectedWith('fewer than 1 items');
}));
it('throws an exception if message has an empty filter', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = yield EventsQuery.create({
filters: [{ schema: 'anything' }],
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
const { message } = eventsQuery;
message.descriptor.filters.push({}); // add an empty filter
const eventsQueryPromise = EventsQuery.parse(message);
yield expect(eventsQueryPromise).to.eventually.be.rejectedWith('must NOT have fewer than 1 properties');
}));
});
});
//# sourceMappingURL=events-query.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { authorizeOwner } from '../../src/core/auth.js';
import { EventsSubscribe } from '../../src/interfaces/events-subscribe.js';
import { DwnInterfaceName, DwnMethodName, Jws, TestDataGenerator, Time } from '../../src/index.js';
import { expect } from 'chai';
describe('EventsSubscribe', () => {
describe('create()', () => {
it('should be able to create and authorize EventsSubscribe', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const timestamp = Time.getCurrentTimestamp();
const eventsSubscribe = yield EventsSubscribe.create({
signer: Jws.createSigner(alice),
messageTimestamp: timestamp,
});
const message = eventsSubscribe.message;
expect(message.descriptor.interface).to.eql(DwnInterfaceName.Events);
expect(message.descriptor.method).to.eql(DwnMethodName.Subscribe);
expect(message.authorization).to.exist;
expect(message.descriptor.messageTimestamp).to.equal(timestamp);
// EventsSubscribe authorizes against owner
yield authorizeOwner(alice.did, eventsSubscribe);
}));
});
});
//# sourceMappingURL=events-subscribe.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"events-subscribe.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/events-subscribe.spec.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAEnG,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,wDAAwD,EAAE,GAAS,EAAE;YACtE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,qBAAqB,EAAE,CAAC;YAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7C,MAAM,eAAe,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC;gBACnD,MAAM,EAAa,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;gBAC1C,gBAAgB,EAAG,SAAS;aAC7B,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACrE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;YAClE,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAEhE,2CAA2C;YAC3C,MAAM,cAAc,CAAC,KAAK,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;QACnD,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,90 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { expect } from 'chai';
import { Jws } from '../../src/index.js';
import { Message } from '../../src/core/message.js';
import { MessagesGet } from '../../src/index.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
describe('MessagesGet Message', () => {
describe('create', () => {
it('creates a MessagesGet message', () => __awaiter(void 0, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(message);
const messagesGet = yield MessagesGet.create({
signer: yield Jws.createSigner(author),
messageCids: [messageCid]
});
expect(messagesGet.message.authorization).to.exist;
expect(messagesGet.message.descriptor).to.exist;
expect(messagesGet.message.descriptor.messageCids.length).to.equal(1);
expect(messagesGet.message.descriptor.messageCids).to.include(messageCid);
}));
it('throws an error if at least 1 message cid isnt provided', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
try {
yield MessagesGet.create({
signer: yield Jws.createSigner(alice),
messageCids: []
});
expect.fail();
}
catch (e) {
// error message auto-generated by AJV
expect(e.message).to.include('/descriptor/messageCids: must NOT have fewer than 1 items');
}
}));
it('throws an error if an invalid CID is provided', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
try {
yield MessagesGet.create({
signer: yield Jws.createSigner(alice),
messageCids: ['abcd']
});
expect.fail();
}
catch (e) {
expect(e.message).to.include('is not a valid CID');
}
}));
});
describe('parse', () => {
it('parses a message into a MessagesGet instance', () => __awaiter(void 0, void 0, void 0, function* () {
const { author, message } = yield TestDataGenerator.generateRecordsWrite();
let messageCid = yield Message.getCid(message);
const messagesGet = yield MessagesGet.create({
signer: yield Jws.createSigner(author),
messageCids: [messageCid]
});
const parsed = yield MessagesGet.parse(messagesGet.message);
expect(parsed).to.be.instanceof(MessagesGet);
const expectedMessageCid = yield Message.getCid(messagesGet.message);
messageCid = yield Message.getCid(parsed.message);
expect(messageCid).to.equal(expectedMessageCid);
}));
it('throws an exception if messageCids contains an invalid cid', () => __awaiter(void 0, void 0, void 0, function* () {
const { author, message: recordsWriteMessage } = yield TestDataGenerator.generateRecordsWrite();
const messageCid = yield Message.getCid(recordsWriteMessage);
const messagesGet = yield MessagesGet.create({
signer: yield Jws.createSigner(author),
messageCids: [messageCid]
});
const message = messagesGet.toJSON();
message.descriptor.messageCids = ['abcd'];
try {
yield MessagesGet.parse(message);
expect.fail();
}
catch (e) {
expect(e.message).to.include('is not a valid CID');
}
}));
});
});
//# sourceMappingURL=messages-get.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"messages-get.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/messages-get.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,378 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import { Jws } from '../../src/utils/jws.js';
import { ProtocolAction } from '../../src/types/protocols-types.js';
import { ProtocolsConfigure } from '../../src/interfaces/protocols-configure.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Message } from '../../src/index.js';
chai.use(chaiAsPromised);
describe('ProtocolsConfigure', () => {
describe('parse()', () => {
it('should throw if protocol definitions has record nesting more than 10 level deep', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
foo: {},
},
structure: {}
};
// create a record hierarchy with 11 levels of nesting
let currentLevel = definition.structure;
for (let i = 0; i < 11; i++) {
currentLevel.foo = {};
currentLevel = currentLevel.foo;
}
// we need to manually created an invalid protocol definition,
// because the SDK `create()` method will not allow us to create an invalid definition
const descriptor = {
interface: DwnInterfaceName.Protocols,
method: DwnMethodName.Configure,
messageTimestamp: Time.getCurrentTimestamp(),
definition
};
const alice = yield TestDataGenerator.generatePersona();
const authorization = yield Message.createAuthorization({
descriptor,
signer: Jws.createSigner(alice)
});
const message = { descriptor, authorization };
const parsePromise = ProtocolsConfigure.parse(message);
yield expect(parsePromise).to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureRecordNestingDepthExceeded);
}));
});
describe('create()', () => {
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const definition = Object.assign({}, dexProtocolDefinition);
const protocolsConfigure = yield ProtocolsConfigure.create({
messageTimestamp: currentTime,
definition,
signer: Jws.createSigner(alice),
});
expect(protocolsConfigure.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URI', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const definition = Object.assign(Object.assign({}, dexProtocolDefinition), { protocol: 'example.com/' });
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
definition,
};
const protocolsConfig = yield ProtocolsConfigure.create(options);
const message = protocolsConfig.message;
expect(message.descriptor.definition.protocol).to.eq('http://example.com');
}));
it('should auto-normalize schema URIs', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const nonNormalizedDexProtocol = Object.assign({}, dexProtocolDefinition);
nonNormalizedDexProtocol.types.ask.schema = 'ask';
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
protocol: 'example.com/',
definition: nonNormalizedDexProtocol
};
const protocolsConfig = yield ProtocolsConfigure.create(options);
const message = protocolsConfig.message;
expect(message.descriptor.definition.types.ask.schema).to.eq('http://ask');
}));
describe('protocol definition validations', () => {
it('should not allow a record in protocol structure to reference a non-existent record type', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
record: {},
},
structure: {
undeclaredRecord: {} // non-existent record type
}
};
const alice = yield TestDataGenerator.generatePersona();
const createPromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createPromise).to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidRuleSetRecordType);
}));
it('should allow `role` property in an `action` to have protocol path to a role record.', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
rootRole: {},
firstLevel: {},
secondLevel: {}
},
structure: {
rootRole: {
$role: true,
secondLevel: {
$actions: [{
role: 'rootRole',
can: [ProtocolAction.Create]
}]
}
},
firstLevel: {
$actions: [{
role: 'rootRole',
can: [ProtocolAction.Create]
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const protocolsConfigure = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
expect(protocolsConfigure.message.descriptor.definition).not.to.be.undefined;
}));
it('should allow `role` property in an `action` that have protocol path to a role record.', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
thread: {},
participant: {},
chat: {}
},
structure: {
thread: {
participant: {
$role: true,
},
chat: {
$actions: [{
role: 'thread/participant',
can: [ProtocolAction.Create]
}]
}
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const protocolsConfigure = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
expect(protocolsConfigure.message.descriptor.definition).not.to.be.undefined;
}));
it('rejects protocol definitions with `role` actions that contain invalid roles', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
foo: {},
bar: {},
},
structure: {
foo: {
// $role: true // deliberated omitted
},
bar: {
$actions: [{
role: 'foo',
can: ['read']
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise)
.to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureRoleDoesNotExistAtGivenPath);
}));
it('rejects protocol definitions with actions that contain `of` and `who` is `anyone`', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$actions: [{
who: 'anyone',
of: 'message',
can: ['read']
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise)
.to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionOfNotAllowed);
}));
it('rejects protocol definitions with actions that have direct-recipient-can rules with actions other than delete or update', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$actions: [{
who: 'recipient',
can: ['read'] // not allowed, should be either delete or update
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise)
.to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidRecipientOfAction);
}));
it('rejects protocol definitions with actions that don\'t contain `of` and `who` is `author`', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$actions: [{
who: 'author',
// of : 'message', // Intentionally missing
can: ['read']
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise)
.to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionMissingOf);
}));
it('rejects protocol definitions with `can query` in non-role rules', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$actions: [{
who: 'author',
of: 'message',
can: ['query']
}]
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise)
.to.be.rejected;
}));
it('allows $size min and max to be set on a protocol path', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$size: {
min: 1,
max: 1000
}
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const protocolsConfigure = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
expect(protocolsConfigure.message.descriptor.definition).not.to.be.undefined;
}));
it('allows $size max to be set on a protocol path (min defaults to 0)', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$size: {
max: 1000
}
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const protocolsConfigure = yield ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
expect(protocolsConfigure.message.descriptor.definition).not.to.be.undefined;
}));
it('rejects $size when max is less than min', () => __awaiter(void 0, void 0, void 0, function* () {
const definition = {
published: true,
protocol: 'http://example.com',
types: {
message: {},
},
structure: {
message: {
$size: {
min: 1000,
max: 1
}
}
}
};
const alice = yield TestDataGenerator.generatePersona();
const createProtocolsConfigurePromise = ProtocolsConfigure.create({
signer: Jws.createSigner(alice),
definition
});
yield expect(createProtocolsConfigurePromise).to.eventually.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidSize);
}));
});
});
});
//# sourceMappingURL=protocols-configure.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import { Jws } from '../../src/index.js';
import { ProtocolsQuery } from '../../src/interfaces/protocols-query.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
chai.use(chaiAsPromised);
describe('ProtocolsQuery', () => {
describe('create()', () => {
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const protocolsQuery = yield ProtocolsQuery.create({
filter: { protocol: 'anyValue' },
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
expect(protocolsQuery.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URI', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { protocol: 'example.com/' },
definition: dexProtocolDefinition
};
const protocolsConfig = yield ProtocolsQuery.create(options);
const message = protocolsConfig.message;
expect(message.descriptor.filter.protocol).to.eq('http://example.com');
}));
});
});
//# sourceMappingURL=protocols-query.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"protocols-query.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/protocols-query.spec.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAIpC,OAAO,qBAAqB,MAAM,0CAA0C,CAAC,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;AACrG,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAE/C,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAEzB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,8CAA8C,EAAE,GAAS,EAAE;YAC5D,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC;YAExD,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC;gBACjD,MAAM,EAAa,EAAE,QAAQ,EAAE,UAAU,EAAE;gBAC3C,gBAAgB,EAAG,WAAW;gBAC9B,MAAM,EAAa,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;aAC3C,CAAC,CAAC;YAEH,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACnF,CAAC,CAAA,CAAC,CAAC;QAGH,EAAE,CAAC,oCAAoC,EAAE,GAAS,EAAE;YAClD,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC;YAExD,MAAM,OAAO,GAAG;gBACd,SAAS,EAAI,KAAK,CAAC,GAAG;gBACtB,IAAI,EAAS,iBAAiB,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC9C,UAAU,EAAG,kBAAkB;gBAC/B,MAAM,EAAO,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;gBACpC,MAAM,EAAO,EAAE,QAAQ,EAAE,cAAc,EAAE;gBACzC,UAAU,EAAG,qBAAqB;aACnC,CAAC;YACF,MAAM,eAAe,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAE7D,MAAM,OAAO,GAAG,eAAe,CAAC,OAAgC,CAAC;YAEjE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC;QAC1E,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,39 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import { Jws } from '../../src/index.js';
import { RecordsDelete } from '../../src/interfaces/records-delete.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
chai.use(chaiAsPromised);
describe('RecordsDelete', () => {
describe('create()', () => {
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const recordsDelete = yield RecordsDelete.create({
recordId: 'anything',
signer: Jws.createSigner(alice),
messageTimestamp: currentTime
});
expect(recordsDelete.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-fill `messageTimestamp` if not given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const recordsDelete = yield RecordsDelete.create({
recordId: 'anything',
signer: Jws.createSigner(alice)
});
expect(recordsDelete.message.descriptor.messageTimestamp).to.exist;
}));
});
});
//# sourceMappingURL=records-delete.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"records-delete.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/records-delete.spec.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAE/C,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAEzB,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,8CAA8C,EAAE,GAAS,EAAE;YAC5D,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC;YAExD,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC/C,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC;gBAC/C,QAAQ,EAAW,UAAU;gBAC7B,MAAM,EAAa,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;gBAC1C,gBAAgB,EAAG,WAAW;aAC/B,CAAC,CAAC;YAEH,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAClF,CAAC,CAAA,CAAC,CAAC;QAEH,EAAE,CAAC,kDAAkD,EAAE,GAAS,EAAE;YAChE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC;YAExD,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC;gBAC/C,QAAQ,EAAG,UAAU;gBACrB,MAAM,EAAK,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC;aACnC,CAAC,CAAC;YAEH,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC;QACrE,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,85 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import { RecordsQuery } from '../../src/interfaces/records-query.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
import { DateSort, DwnErrorCode, Jws } from '../../src/index.js';
chai.use(chaiAsPromised);
describe('RecordsQuery', () => {
describe('create()', () => {
it('should not allow published to be set to false with a datePublished filter also set', () => __awaiter(void 0, void 0, void 0, function* () {
// test control
const randomDate = TestDataGenerator.randomTimestamp();
const recordQueryControl = TestDataGenerator.generateRecordsQuery({
filter: { datePublished: { from: randomDate, }, published: true }
});
yield expect(recordQueryControl).to.eventually.not.be.rejected;
const recordQueryRejected = TestDataGenerator.generateRecordsQuery({
filter: { datePublished: { from: randomDate }, published: false }
});
yield expect(recordQueryRejected).to.eventually.be.rejectedWith('descriptor/filter/published: must be equal to one of the allowed values');
}));
it('should not allow published to be set to false with a dateSort set to sorting by `PublishedAscending` or `PublishedDescending`', () => __awaiter(void 0, void 0, void 0, function* () {
// test control
const recordQueryControl = TestDataGenerator.generateRecordsQuery({
filter: { published: true },
dateSort: DateSort.PublishedAscending,
});
yield expect(recordQueryControl).to.eventually.not.be.rejected;
const recordQueryRejected = TestDataGenerator.generateRecordsQuery({
filter: { published: false },
dateSort: DateSort.PublishedAscending,
});
yield expect(recordQueryRejected).to.eventually.be.rejectedWith(DwnErrorCode.RecordsQueryCreateFilterPublishedSortInvalid);
}));
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const recordsQuery = yield RecordsQuery.create({
filter: { schema: 'anything' },
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
expect(recordsQuery.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { protocol: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsQuery.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.protocol).to.eq('http://example.com');
}));
it('should auto-normalize schema URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { schema: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsQuery.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.schema).to.eq('http://example.com');
}));
});
});
//# sourceMappingURL=records-query.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"records-query.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/records-query.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import { Jws } from '../../src/index.js';
import { RecordsRead } from '../../src/interfaces/records-read.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
chai.use(chaiAsPromised);
describe('RecordsRead', () => {
describe('create()', () => {
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const recordsRead = yield RecordsRead.create({
filter: {
recordId: 'anything',
},
signer: Jws.createSigner(alice),
messageTimestamp: currentTime
});
expect(recordsRead.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { protocol: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsRead.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.protocol).to.eq('http://example.com');
}));
it('should auto-normalize schema URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { schema: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsRead.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.schema).to.eq('http://example.com');
}));
});
});
//# sourceMappingURL=records-read.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"records-read.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/records-read.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import dexProtocolDefinition from '../vectors/protocol-definitions/dex.json' assert { type: 'json' };
import { Jws } from '../../src/utils/jws.js';
import { RecordsSubscribe } from '../../src/interfaces/records-subscribe.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
chai.use(chaiAsPromised);
describe('RecordsSubscribe', () => {
describe('create()', () => {
it('should not allow published to be set to false with a datePublished filter also set', () => __awaiter(void 0, void 0, void 0, function* () {
// test control
const randomDate = TestDataGenerator.randomTimestamp();
const recordQueryControl = TestDataGenerator.generateRecordsQuery({
filter: { datePublished: { from: randomDate, }, published: true }
});
yield expect(recordQueryControl).to.eventually.not.be.rejected;
const recordQueryRejected = TestDataGenerator.generateRecordsQuery({
filter: { datePublished: { from: randomDate }, published: false }
});
yield expect(recordQueryRejected).to.eventually.be.rejectedWith('descriptor/filter/published: must be equal to one of the allowed values');
}));
it('should use `messageTimestamp` as is if given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const recordsQuery = yield RecordsSubscribe.create({
filter: { schema: 'anything' },
messageTimestamp: currentTime,
signer: Jws.createSigner(alice),
});
expect(recordsQuery.message.descriptor.messageTimestamp).to.equal(currentTime);
}));
it('should auto-normalize protocol URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { protocol: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsSubscribe.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.protocol).to.eq('http://example.com');
}));
it('should auto-normalize schema URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
filter: { schema: 'example.com/' },
definition: dexProtocolDefinition
};
const recordsQuery = yield RecordsSubscribe.create(options);
const message = recordsQuery.message;
expect(message.descriptor.filter.schema).to.eq('http://example.com');
}));
});
});
//# sourceMappingURL=records-subscribe.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"records-subscribe.spec.js","sourceRoot":"","sources":["../../../../tests/interfaces/records-subscribe.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,421 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import Sinon from 'sinon';
import chai, { expect } from 'chai';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { RecordsWrite } from '../../src/interfaces/records-write.js';
import { RecordsWriteHandler } from '../../src/handlers/records-write.js';
import { stubInterface } from 'ts-sinon';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { Time } from '../../src/utils/time.js';
import { DwnInterfaceName, DwnMethodName, Encoder, Jws, KeyDerivationScheme, Message, PermissionsProtocol } from '../../src/index.js';
chai.use(chaiAsPromised);
describe('RecordsWrite', () => {
describe('create()', () => {
it('should be able to create and authorize a valid RecordsWrite message', () => __awaiter(void 0, void 0, void 0, function* () {
// testing `create()` first
const alice = yield TestDataGenerator.generatePersona();
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
dateCreated: '2022-10-14T10:20:30.405060Z',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
signer: Jws.createSigner(alice)
};
const recordsWrite = yield RecordsWrite.create(options);
const message = recordsWrite.message;
expect(message.authorization).to.exist;
expect(message.descriptor.dataFormat).to.equal(options.dataFormat);
expect(message.descriptor.dateCreated).to.equal(options.dateCreated);
expect(message.recordId).to.equal(options.recordId);
const messageStoreStub = stubInterface();
yield RecordsWriteHandler['authorizeRecordsWrite'](alice.did, recordsWrite, messageStoreStub);
}));
it('should be able to auto-fill `datePublished` when `published` set to `true` but `datePublished` not given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
published: true,
signer: Jws.createSigner(alice)
};
const recordsWrite = yield RecordsWrite.create(options);
const message = recordsWrite.message;
expect(message.descriptor.datePublished).to.exist;
}));
it('should not allow `data` and `dataCid` to be both defined or undefined', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
// testing `data` and `dataCid` both defined
const options1 = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataCid: yield TestDataGenerator.randomCborSha256Cid(),
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
published: true,
signer: Jws.createSigner(alice)
};
const createPromise1 = RecordsWrite.create(options1);
yield expect(createPromise1).to.be.rejectedWith(DwnErrorCode.RecordsWriteCreateDataAndDataCidMutuallyExclusive);
// testing `data` and `dataCid` both undefined
const options2 = {
recipient: alice.did,
// intentionally showing both `data` and `dataCid` are undefined
// data : TestDataGenerator.randomBytes(10),
// dataCid : await TestDataGenerator.randomCborSha256Cid(),
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
published: true,
signer: Jws.createSigner(alice)
};
const createPromise2 = RecordsWrite.create(options2);
yield expect(createPromise2).to.be.rejectedWith(DwnErrorCode.RecordsWriteCreateDataAndDataCidMutuallyExclusive);
}));
it('should required `dataCid` and `dataSize` to be both defined or undefined at the same time', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options1 = {
recipient: alice.did,
dataCid: yield TestDataGenerator.randomCborSha256Cid(),
// dataSize : 123, // intentionally missing
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
published: true,
signer: Jws.createSigner(alice)
};
const createPromise1 = RecordsWrite.create(options1);
yield expect(createPromise1).to.be.rejectedWith('`dataCid` and `dataSize` must both be defined or undefined at the same time');
const options2 = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
// dataCid : await TestDataGenerator.randomCborSha256Cid(), // intentionally missing
dataSize: 123,
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
published: true,
signer: Jws.createSigner(alice)
};
const createPromise2 = RecordsWrite.create(options2);
yield expect(createPromise2).to.be.rejectedWith('`dataCid` and `dataSize` must both be defined or undefined at the same time');
}));
it('should auto-normalize protocol URL', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options = {
recipient: alice.did,
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
signer: Jws.createSigner(alice),
protocol: 'example.com/',
protocolPath: 'example',
schema: 'http://foo.bar/schema'
};
const recordsWrite = yield RecordsWrite.create(options);
const message = recordsWrite.message;
expect(message.descriptor.protocol).to.eq('http://example.com');
}));
it('should required `protocol` and `protocolPath` to be both defined or undefined at the same time', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const options1 = {
recipient: alice.did,
protocol: 'http://example.com',
// protocolPath : 'foo/bar', // intentionally missing
dataCid: yield TestDataGenerator.randomCborSha256Cid(),
dataSize: 123,
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
signer: Jws.createSigner(alice)
};
const createPromise1 = RecordsWrite.create(options1);
yield expect(createPromise1).to.be.rejectedWith('`protocol` and `protocolPath` must both be defined or undefined at the same time');
const options2 = {
recipient: alice.did,
// protocol : 'http://example.com', // intentionally missing
protocolPath: 'foo/bar',
data: TestDataGenerator.randomBytes(10),
dataCid: yield TestDataGenerator.randomCborSha256Cid(),
dataSize: 123,
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
signer: Jws.createSigner(alice)
};
const createPromise2 = RecordsWrite.create(options2);
yield expect(createPromise2).to.be.rejectedWith('`protocol` and `protocolPath` must both be defined or undefined at the same time');
}));
it('should be able to create a RecordsWrite successfully using a custom signer', () => __awaiter(void 0, void 0, void 0, function* () {
// create a custom signer
const hardCodedSignature = Encoder.stringToBytes('some_hard_coded_signature');
class CustomSigner {
constructor() {
this.keyId = 'did:example:alice#key1';
this.algorithm = 'unused';
}
sign(_content) {
return __awaiter(this, void 0, void 0, function* () {
return hardCodedSignature;
});
}
}
const signer = new CustomSigner();
const options = {
schema: 'http://any-schema.com',
protocol: 'http://example.com',
protocolPath: 'foo/bar',
dataCid: yield TestDataGenerator.randomCborSha256Cid(),
dataSize: 123,
dataFormat: 'application/json',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
signer
};
const recordsWrite = yield RecordsWrite.create(options);
expect(recordsWrite.message.authorization.signature.signatures[0].signature).to.equal(Encoder.bytesToBase64Url(hardCodedSignature));
}));
it('should throw if attempting to use `protocols` key derivation encryption scheme on non-protocol-based record', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
const encryptionInput = {
initializationVector: dataEncryptionInitializationVector,
key: dataEncryptionKey,
keyEncryptionInputs: [{
publicKeyId: alice.keyId,
publicKey: alice.keyPair.publicJwk,
derivationScheme: KeyDerivationScheme.ProtocolPath
}]
};
// intentionally generating a record that is not protocol-based
const createPromise = RecordsWrite.create({
signer: Jws.createSigner(alice),
dataFormat: 'application/json',
data: TestDataGenerator.randomBytes(10),
encryptionInput
});
yield expect(createPromise).to.be.rejectedWith(DwnErrorCode.RecordsWriteMissingProtocol);
}));
it('should throw if attempting to use `schemas` key derivation encryption scheme on a record without `schema`', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const dataEncryptionInitializationVector = TestDataGenerator.randomBytes(16);
const dataEncryptionKey = TestDataGenerator.randomBytes(32);
const encryptionInput = {
initializationVector: dataEncryptionInitializationVector,
key: dataEncryptionKey,
keyEncryptionInputs: [{
publicKeyId: alice.keyId,
publicKey: alice.keyPair.publicJwk,
derivationScheme: KeyDerivationScheme.Schemas
}]
};
// intentionally generating a record that is without `schema`
const createPromise = RecordsWrite.create({
signer: Jws.createSigner(alice),
dataFormat: 'application/octet-stream',
data: TestDataGenerator.randomBytes(10),
encryptionInput
});
yield expect(createPromise).to.be.rejectedWith(DwnErrorCode.RecordsWriteMissingSchema);
}));
it('should throw if delegated grant is given but signer is not given', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: 'chat'
};
const grantToBob = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
description: 'Allow Bob to write as me in chat protocol',
grantedTo: bob.did,
scope
});
const createPromise = RecordsWrite.create({
delegatedGrant: grantToBob.recordsWrite.message,
dataFormat: 'application/octet-stream',
data: TestDataGenerator.randomBytes(10),
});
yield expect(createPromise).to.be.rejectedWith(DwnErrorCode.RecordsWriteCreateMissingSigner);
}));
});
describe('createFrom()', () => {
it('should create a RecordsWrite with `published` set to `true` with just `publishedDate` given', () => __awaiter(void 0, void 0, void 0, function* () {
const { author, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({
published: false
});
const write = yield RecordsWrite.createFrom({
recordsWriteMessage: recordsWrite.message,
datePublished: Time.getCurrentTimestamp(),
signer: Jws.createSigner(author)
});
expect(write.message.descriptor.published).to.be.true;
}));
it('replace tags with updated tags, if tags do not exist in createFrom remove them', () => __awaiter(void 0, void 0, void 0, function* () {
// create a record with tags
const { author, message, recordsWrite } = yield TestDataGenerator.generateRecordsWrite({
tags: {
tag1: ['value1', 'value2']
}
});
expect(message.descriptor.tags).to.exist;
expect(message.descriptor.tags.tag1).to.exist;
expect(message.descriptor.tags.tag1).to.have.members(['value1', 'value2']);
// update the record's tags
const write = yield RecordsWrite.createFrom({
recordsWriteMessage: recordsWrite.message,
signer: Jws.createSigner(author),
tags: {
tag2: ['value1', 'value2', 'value3']
}
});
expect(write.message.descriptor.tags).to.exist;
expect(write.message.descriptor.tags.tag1).to.not.exist;
expect(write.message.descriptor.tags.tag2).to.exist;
expect(write.message.descriptor.tags.tag2).to.have.members(['value1', 'value2', 'value3']);
// update without tags
const write2 = yield RecordsWrite.createFrom({
recordsWriteMessage: write.message,
signer: Jws.createSigner(author),
});
expect(write2.message.descriptor.tags).to.not.exist;
}));
});
describe('parse()', () => {
xit('should invoke JSON schema validation when parsing a RecordsWrite', () => __awaiter(void 0, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generatePersona();
const recordsWrite = yield RecordsWrite.create({
signer: Jws.createSigner(alice),
dataFormat: 'application/octet-stream',
data: TestDataGenerator.randomBytes(10),
});
const validateJsonSchemaSpy = Sinon.spy(Message, 'validateJsonSchema');
yield RecordsWrite.parse(recordsWrite.message);
expect(validateJsonSchemaSpy.called).to.be.true;
}));
});
describe('isSignedByAuthorDelegate()', () => {
it('should return false if the given RecordsWrite is not signed at all', () => __awaiter(void 0, void 0, void 0, function* () {
const data = new TextEncoder().encode('any data');
const recordsWrite = yield RecordsWrite.create({
protocol: 'unused',
protocolPath: 'unused',
schema: 'unused',
dataFormat: 'unused',
data
});
const isSignedByAuthorDelegate = recordsWrite.isSignedByAuthorDelegate;
expect(isSignedByAuthorDelegate).to.be.false;
}));
});
describe('isSignedByOwnerDelegate()', () => {
it('should return false if the given RecordsWrite is not signed at all', () => __awaiter(void 0, void 0, void 0, function* () {
const data = new TextEncoder().encode('any data');
const recordsWrite = yield RecordsWrite.create({
protocol: 'unused',
protocolPath: 'unused',
schema: 'unused',
dataFormat: 'unused',
data
});
const isSignedByOwnerDelegate = recordsWrite.isSignedByOwnerDelegate;
expect(isSignedByOwnerDelegate).to.be.false;
}));
});
describe('isInitialWrite()', () => {
it('should return false if given message is not a RecordsWrite', () => __awaiter(void 0, void 0, void 0, function* () {
const { message } = yield TestDataGenerator.generateRecordsQuery();
const isInitialWrite = yield RecordsWrite.isInitialWrite(message);
expect(isInitialWrite).to.be.false;
}));
});
describe('getEntryId()', () => {
it('should throw if the given author is undefined', () => __awaiter(void 0, void 0, void 0, function* () {
const { message } = yield TestDataGenerator.generateRecordsWrite();
const author = undefined;
expect(RecordsWrite.getEntryId(author, message.descriptor)).to.be.rejectedWith(DwnErrorCode.RecordsWriteGetEntryIdUndefinedAuthor);
}));
});
describe('signAsOwner()', () => {
it('should throw if the RecordsWrite is not signed by an author yet', () => __awaiter(void 0, void 0, void 0, function* () {
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
dateCreated: '2023-07-27T10:20:30.405060Z',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
};
const recordsWrite = yield RecordsWrite.create(options);
expect(recordsWrite.author).to.not.exist;
expect(recordsWrite.signaturePayload).to.not.exist;
const alice = yield TestDataGenerator.generateDidKeyPersona();
yield expect(recordsWrite.signAsOwner(Jws.createSigner(alice))).to.be.rejectedWith(DwnErrorCode.RecordsWriteSignAsOwnerUnknownAuthor);
expect(recordsWrite.owner).to.be.undefined;
expect(recordsWrite.ownerSignaturePayload).to.be.undefined;
}));
});
describe('signAsOwnerDelegate()', () => {
it('should throw if the RecordsWrite is not signed by an author yet', () => __awaiter(void 0, void 0, void 0, function* () {
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
dateCreated: '2023-07-27T10:20:30.405060Z',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
};
const recordsWrite = yield RecordsWrite.create(options);
expect(recordsWrite.author).to.not.exist;
expect(recordsWrite.signaturePayload).to.not.exist;
// create a delegated grant
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const scope = {
interface: DwnInterfaceName.Records,
method: DwnMethodName.Write,
protocol: 'chat'
};
const ownerDelegatedGrant = yield PermissionsProtocol.createGrant({
signer: Jws.createSigner(alice),
delegated: true,
dateExpires: Time.createOffsetTimestamp({ seconds: 100 }),
grantedTo: bob.did,
scope
});
yield expect(recordsWrite.signAsOwnerDelegate(Jws.createSigner(bob), ownerDelegatedGrant.recordsWrite.message))
.to.be.rejectedWith(DwnErrorCode.RecordsWriteSignAsOwnerDelegateUnknownAuthor);
expect(recordsWrite.owner).to.be.undefined;
expect(recordsWrite.ownerSignaturePayload).to.be.undefined;
}));
});
describe('ownerSignatureSigner()', () => {
it('should return `undefined` if owner signature is not present in the message', () => __awaiter(void 0, void 0, void 0, function* () {
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
dateCreated: '2023-07-27T10:20:30.405060Z',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
};
const recordsWrite = yield RecordsWrite.create(options);
expect(recordsWrite.ownerSignatureSigner).to.be.undefined;
}));
});
describe('message', () => {
it('should throw if attempting to access the message of a RecordsWrite that is not given authorization signature input', () => __awaiter(void 0, void 0, void 0, function* () {
const options = {
data: TestDataGenerator.randomBytes(10),
dataFormat: 'application/json',
dateCreated: '2023-07-27T10:20:30.405060Z',
recordId: yield TestDataGenerator.randomCborSha256Cid(),
};
const recordsWrite = yield RecordsWrite.create(options);
expect(recordsWrite.author).to.not.exist;
expect(recordsWrite.signaturePayload).to.not.exist;
expect(() => recordsWrite.message).to.throw(DwnErrorCode.RecordsWriteMissingSigner);
}));
});
});
//# sourceMappingURL=records-write.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,216 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import { GeneralJwsBuilder } from '../../../src/jose/jws/general/builder.js';
import { GeneralJwsVerifier } from '../../../src/jose/jws/general/verifier.js';
import { Jws } from '../../../src/utils/jws.js';
import { PrivateKeySigner } from '../../../src/index.js';
import { signatureAlgorithms } from '../../../src/jose/algorithms/signing/signature-algorithms.js';
import sinon from 'sinon';
import { UniversalResolver } from '@web5/dids';
const { Ed25519, secp256k1 } = signatureAlgorithms;
const secp256r1 = signatureAlgorithms['P-256'];
chai.use(chaiAsPromised);
describe('General JWS Sign/Verify', () => {
afterEach(() => {
// restores all fakes, stubs, spies etc. not restoring causes a memory leak.
// more info here: https://sinonjs.org/releases/v13/general-setup/
sinon.restore();
});
it('should sign and verify secp256k1 signature using a key vector correctly', () => __awaiter(void 0, void 0, void 0, function* () {
const { privateJwk, publicJwk } = yield secp256k1.generateKeyPair();
const payloadBytes = new TextEncoder().encode('anyPayloadValue');
const keyId = 'did:jank:alice#key1';
const jwsBuilder = yield GeneralJwsBuilder.create(payloadBytes, [new PrivateKeySigner({ privateJwk, keyId })]);
const jws = jwsBuilder.getJws();
const mockResolutionResult = {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [{
id: keyId,
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: publicJwk
}]
},
didDocumentMetadata: {}
};
const resolverStub = sinon.createStubInstance(UniversalResolver, {
// @ts-ignore
resolve: sinon.stub().withArgs('did:jank:alice').resolves(mockResolutionResult),
});
const verificationResult = yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
expect(verificationResult.signers.length).to.equal(1);
expect(verificationResult.signers).to.include('did:jank:alice');
}));
it('should sign and verify secp256r1 signature using a key vector correctly', () => __awaiter(void 0, void 0, void 0, function* () {
const { privateJwk, publicJwk } = yield secp256r1.generateKeyPair();
const payloadBytes = new TextEncoder().encode('anyPayloadValue');
const keyId = 'did:jank:alice#key1';
const jwsBuilder = yield GeneralJwsBuilder.create(payloadBytes, [
new PrivateKeySigner({ privateJwk, keyId }),
]);
const jws = jwsBuilder.getJws();
const mockResolutionResult = {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [
{
id: keyId,
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: publicJwk,
},
],
},
didDocumentMetadata: {},
};
const resolverStub = sinon.createStubInstance(UniversalResolver, {
// @ts-ignore
resolve: sinon
.stub()
.withArgs('did:jank:alice')
.resolves(mockResolutionResult),
});
const verificationResult = yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
expect(verificationResult.signers.length).to.equal(1);
expect(verificationResult.signers).to.include('did:jank:alice');
}));
it('should sign and verify ed25519 signature using a key vector correctly', () => __awaiter(void 0, void 0, void 0, function* () {
const { privateJwk, publicJwk } = yield Ed25519.generateKeyPair();
const payloadBytes = new TextEncoder().encode('anyPayloadValue');
const keyId = 'did:jank:alice#key1';
const jwsBuilder = yield GeneralJwsBuilder.create(payloadBytes, [new PrivateKeySigner({ privateJwk, keyId })]);
const jws = jwsBuilder.getJws();
const mockResolutionResult = {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [{
id: 'did:jank:alice#key1',
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: publicJwk
}]
},
didDocumentMetadata: {}
};
const resolverStub = sinon.createStubInstance(UniversalResolver, {
// @ts-ignore
resolve: sinon.stub().withArgs('did:jank:alice').resolves(mockResolutionResult)
});
const verificationResult = yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
expect(verificationResult.signers.length).to.equal(1);
expect(verificationResult.signers).to.include('did:jank:alice');
}));
it('should support multiple signatures using different key types', () => __awaiter(void 0, void 0, void 0, function* () {
const secp256k1Keys = yield secp256k1.generateKeyPair();
const ed25519Keys = yield Ed25519.generateKeyPair();
const alice = {
did: 'did:jank:alice',
privateJwk: secp256k1Keys.privateJwk,
jwkPublic: secp256k1Keys.publicJwk,
keyId: 'did:jank:alice#key1',
mockResolutionResult: {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [{
id: 'did:jank:alice#key1',
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: secp256k1Keys.publicJwk
}]
},
didDocumentMetadata: {}
}
};
const bob = {
did: 'did:jank:bob',
privateJwk: ed25519Keys.privateJwk,
jwkPublic: ed25519Keys.publicJwk,
keyId: 'did:jank:bob#key1',
mockResolutionResult: {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [{
id: 'did:jank:bob#key1',
type: 'JsonWebKey2020',
controller: 'did:jank:bob',
publicKeyJwk: ed25519Keys.publicJwk,
}]
},
didDocumentMetadata: {}
}
};
const signers = [
new PrivateKeySigner({ privateJwk: alice.privateJwk, keyId: alice.keyId }),
new PrivateKeySigner({ privateJwk: bob.privateJwk, keyId: bob.keyId })
];
const payloadBytes = new TextEncoder().encode('anyPayloadValue');
const jwsBuilder = yield GeneralJwsBuilder.create(payloadBytes, signers);
const jws = jwsBuilder.getJws();
const resolveStub = sinon.stub();
resolveStub.withArgs('did:jank:alice').resolves(alice.mockResolutionResult);
resolveStub.withArgs('did:jank:bob').resolves(bob.mockResolutionResult);
const resolverStub = sinon.createStubInstance(UniversalResolver, {
// @ts-ignore
resolve: resolveStub
});
const verificationResult = yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
expect(verificationResult.signers.length).to.equal(2);
expect(verificationResult.signers).to.include(alice.did);
expect(verificationResult.signers).to.include(bob.did);
}));
it('should not verify the same signature more than once', () => __awaiter(void 0, void 0, void 0, function* () {
// scenario: include two signatures in the JWS,
// repeated calls to verifySignature should only verify each of the signature once,
// resulting total of 2 calls to `Jws.verifySignature()` and 2 calls to cache the results.
const { privateJwk: privateJwkEd25519, publicJwk: publicJwkEd25519 } = yield Ed25519.generateKeyPair();
const { privateJwk: privateJwkSecp256k1, publicJwk: publicJwkSecp256k1 } = yield secp256k1.generateKeyPair();
const payloadBytes = new TextEncoder().encode('anyPayloadValue');
const keyId1 = 'did:jank:alice#key1';
const keyId2 = 'did:jank:alice#key2';
const jwsBuilder = yield GeneralJwsBuilder.create(payloadBytes, [
new PrivateKeySigner({ privateJwk: privateJwkEd25519, keyId: keyId1 }),
new PrivateKeySigner({ privateJwk: privateJwkSecp256k1, keyId: keyId2 })
]);
const jws = jwsBuilder.getJws();
const mockResolutionResult = {
didResolutionMetadata: {},
didDocument: {
verificationMethod: [{
id: 'did:jank:alice#key1',
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: publicJwkEd25519
}, {
id: 'did:jank:alice#key2',
type: 'JsonWebKey2020',
controller: 'did:jank:alice',
publicKeyJwk: publicJwkSecp256k1
}]
},
didDocumentMetadata: {}
};
const resolverStub = sinon.createStubInstance(UniversalResolver, {
// @ts-ignore
resolve: sinon.stub().withArgs('did:jank:alice').resolves(mockResolutionResult)
});
const verifySignatureSpy = sinon.spy(Jws, 'verifySignature');
const cacheSetSpy = sinon.spy(GeneralJwsVerifier.singleton.cache, 'set');
// intentionally calling verifySignatures() multiple times on the same JWS
yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
yield GeneralJwsVerifier.verifySignatures(jws, resolverStub);
sinon.assert.calledTwice(verifySignatureSpy);
sinon.assert.calledTwice(cacheSetSpy);
}));
});
//# sourceMappingURL=general.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,217 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import threadRoleProtocolDefinition from '../vectors/protocol-definitions/thread-role.json' assert { type: 'json' };
import { authenticate } from '../../src/core/auth.js';
import { Encoder } from '../../src/index.js';
import { HdKey } from '../../src/utils/hd-key.js';
import { KeyDerivationScheme } from '../../src/utils/hd-key.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { TestStubGenerator } from '../utils/test-stub-generator.js';
import chai, { expect } from 'chai';
import { DataStream, Dwn, Jws, Protocols, ProtocolsConfigure, ProtocolsQuery, Records, RecordsRead } from '../../src/index.js';
import { DidKey, UniversalResolver } from '@web5/dids';
chai.use(chaiAsPromised);
export function testEndToEndScenarios() {
describe('End-to-end Scenarios Spanning Features', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should support a multi-participant encrypted chat protocol', () => __awaiter(this, void 0, void 0, function* () {
// Scenario:
// 1. Alice starts a chat thread
// 2. Alice adds Bob as a participant with [symmetric key] encrypted using [Bob's participant-level public key]
// 3. Alice writes a chat message(s) in the thread
// 4. Alice sends an invite to Bob's DWN with the [context/thread ID]
// 5. Bob fetches the invite from his DWN and obtains the [context/thread ID]
// 6. Bob fetches the entire thread using the [context/thread ID]
// 7. Bob is able to decrypt all thread content
// creating Alice and Bob persona and setting up a stub DID resolver
const alice = yield TestDataGenerator.generatePersona();
const bob = yield TestDataGenerator.generatePersona();
TestStubGenerator.stubDidResolver(didResolver, [alice, bob]);
const protocolDefinition = threadRoleProtocolDefinition;
// Alice configures chat protocol with encryption
const protocolDefinitionForAlice = yield Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, alice.keyId, alice.keyPair.privateJwk);
const protocolsConfigureForAlice = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: protocolDefinitionForAlice
});
const protocolsConfigureForAliceReply = yield dwn.processMessage(alice.did, protocolsConfigureForAlice.message);
expect(protocolsConfigureForAliceReply.status.code).to.equal(202);
// Bob configures chat protocol with encryption
const protocolDefinitionForBob = yield Protocols.deriveAndInjectPublicEncryptionKeys(protocolDefinition, bob.keyId, bob.keyPair.privateJwk);
const protocolsConfigureForBob = yield TestDataGenerator.generateProtocolsConfigure({
author: bob,
protocolDefinition: protocolDefinitionForBob
});
const protocolsConfigureReply = yield dwn.processMessage(bob.did, protocolsConfigureForBob.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 1. Alice starts a chat thread writing to her own DWN
const threadBytes = Encoder.objectToBytes({ title: 'Top Secret' });
const threadRecord = yield TestDataGenerator.generateProtocolEncryptedRecordsWrite({
plaintextBytes: threadBytes,
author: alice,
protocolDefinition: protocolDefinition,
protocolPath: 'thread',
encryptSymmetricKeyWithProtocolPathDerivedKey: false,
encryptSymmetricKeyWithProtocolContextDerivedKey: true
});
const threadRecordReply1 = yield dwn.processMessage(alice.did, threadRecord.message, { dataStream: threadRecord.dataStream });
expect(threadRecordReply1.status.code).to.equal(202);
// 2. Alice adds Bob as a participant giving him the [context-derived private key] encrypted using [Bob's participant-level public key]
// the context-derived key to be used for encrypting symmetric keys
const aliceRootKey = {
rootKeyId: alice.keyId,
derivationScheme: KeyDerivationScheme.ProtocolContext,
derivedPrivateKey: alice.keyPair.privateJwk
};
const contextDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(threadRecord.message.contextId);
const contextDerivedPrivateKey = yield HdKey.derivePrivateKey(aliceRootKey, contextDerivationPath);
const contextDerivedPublicKey = threadRecord.encryptionInput.keyEncryptionInputs[0].publicKey;
// Alice queries Bob's DWN for Bob's chat protocol definition containing public key declarations
const protocolsQuery = yield ProtocolsQuery.create({
filter: { protocol: threadRoleProtocolDefinition.protocol }
});
const protocolsQueryReply = yield dwn.processMessage(bob.did, protocolsQuery.message);
const protocolConfigureMessageOfBobFetched = protocolsQueryReply.entries[0];
// Alice verifies that the chat protocol definition is authored by Bob
yield authenticate(protocolConfigureMessageOfBobFetched.authorization, didResolver);
const protocolsConfigureOfBobFetched = yield ProtocolsConfigure.parse(protocolConfigureMessageOfBobFetched);
expect(protocolsConfigureOfBobFetched.author).to.equal(bob.did);
// generate the encrypted participant record using Bob's protocol configuration fetched
const participantBobRecord = yield TestDataGenerator.generateProtocolEncryptedRecordsWrite({
plaintextBytes: Encoder.objectToBytes(contextDerivedPrivateKey),
author: alice,
recipient: bob.did,
protocolDefinition: protocolsConfigureOfBobFetched.message.descriptor.definition,
protocolPath: 'thread/participant',
protocolParentContextId: threadRecord.message.contextId,
encryptSymmetricKeyWithProtocolPathDerivedKey: true,
encryptSymmetricKeyWithProtocolContextDerivedKey: false // this could be `true` also, but mostly orthogonal to the scenario
});
const participantRecordReply = yield dwn.processMessage(alice.did, participantBobRecord.message, { dataStream: participantBobRecord.dataStream });
expect(participantRecordReply.status.code).to.equal(202);
// 3. Alice writes a chat message(s) in the thread
const messageByAlice = 'Message from Alice';
const chatMessageByAlice = yield TestDataGenerator.generateProtocolEncryptedRecordsWrite({
plaintextBytes: Encoder.stringToBytes(messageByAlice),
author: alice,
recipient: alice.did,
protocolDefinition: protocolDefinition,
protocolPath: 'thread/chat',
protocolParentContextId: threadRecord.message.contextId,
protocolContextDerivingRootKeyId: aliceRootKey.rootKeyId,
protocolContextDerivedPublicJwk: contextDerivedPublicKey,
encryptSymmetricKeyWithProtocolPathDerivedKey: false,
encryptSymmetricKeyWithProtocolContextDerivedKey: true
});
const chatMessageReply = yield dwn.processMessage(alice.did, chatMessageByAlice.message, { dataStream: chatMessageByAlice.dataStream });
expect(chatMessageReply.status.code).to.equal(202);
// Assume the below steps can be done since it is a common DWN usage pattern
// 4. Alice sends an invite to Bob's DWN with the [context/thread ID]
// 5. Bob fetches the invite from his DWN and obtains the [context/thread ID]
// 6. Bob fetches the entire thread using the [context/thread ID]
// Test that Bob is able to read his 'participant' role to obtain the context-derived private key for message decryption.
// He doesn't need to invoke the role because recipients of a record are always authorized to read it
const participantRead = yield RecordsRead.create({
signer: Jws.createSigner(bob),
filter: {
protocolPath: 'thread/participant',
recipient: bob.did,
contextId: threadRecord.message.contextId
},
});
const participantReadReply = yield dwn.processMessage(alice.did, participantRead.message);
expect(participantReadReply.status.code).to.equal(200);
// Test that Bob is able to read the thread root record
const threadRead = yield RecordsRead.create({
signer: Jws.createSigner(bob),
filter: {
protocolPath: 'thread',
contextId: threadRecord.message.contextId
},
protocolRole: 'thread/participant'
});
const threadReadReply = yield dwn.processMessage(alice.did, threadRead.message);
expect(threadReadReply.status.code).to.equal(200);
expect(threadReadReply.record).to.exist;
// Test Bob can invoke his 'participant' role to read the chat message
// TODO: #555 - We currently lack role-authorized RecordsQuery for a realistic scenario (https://github.com/TBD54566975/dwn-sdk-js/issues/555)
const chatRead = yield RecordsRead.create({
signer: Jws.createSigner(bob),
filter: {
protocolPath: 'thread/chat',
contextId: threadRecord.message.contextId
},
protocolRole: 'thread/participant'
});
const chatReadReply = yield dwn.processMessage(alice.did, chatRead.message);
expect(chatReadReply.status.code).to.equal(200);
expect(chatReadReply.record).to.exist;
// 7. Bob is able to decrypt all thread content
// Bob decrypts the participant message to obtain the context-derived private key
const bobRootKey = {
rootKeyId: bob.keyId,
derivationScheme: KeyDerivationScheme.ProtocolPath,
derivedPrivateKey: bob.keyPair.privateJwk
};
const participantRecordFetched = participantReadReply.record;
const encryptedContextDerivedPrivateKeyBytes = yield DataStream.toBytes(participantRecordFetched.data); // to create streams for testing
const derivationPathFromProtocolPath = Records.constructKeyDerivationPathUsingProtocolPathScheme(participantRecordFetched.descriptor);
const bobProtocolPathDerivedPrivateKey = yield HdKey.derivePrivateKey(bobRootKey, derivationPathFromProtocolPath);
const decryptedContextDerivedKeyStream = yield Records.decrypt(participantRecordFetched, bobProtocolPathDerivedPrivateKey, DataStream.fromBytes(encryptedContextDerivedPrivateKeyBytes));
const decryptedContextDerivedPrivateKey = yield DataStream.toObject(decryptedContextDerivedKeyStream);
expect(decryptedContextDerivedPrivateKey).to.deep.equal(contextDerivedPrivateKey);
// Arguably unrelated to the scenario, but let's sanity check that Bob's root key can also decrypt the encrypted context-derived private key
const decryptedContextDerivedKeyStream2 = yield Records.decrypt(participantRecordFetched, bobRootKey, DataStream.fromBytes(encryptedContextDerivedPrivateKeyBytes));
const decryptedContextDerivedPrivateKey2 = yield DataStream.toObject(decryptedContextDerivedKeyStream2);
expect(decryptedContextDerivedPrivateKey2).to.deep.equal(contextDerivedPrivateKey);
// Verify that Bob can now decrypt Alice's chat thread record using the decrypted context-derived key
const decryptedChatThread = yield Records.decrypt(threadReadReply.record, decryptedContextDerivedPrivateKey, threadReadReply.record.data);
expect(yield DataStream.toBytes(decryptedChatThread)).to.deep.equal(threadBytes);
// Verify that Bob can now decrypt Alice's chat message using the decrypted context-derived key
const encryptedChatMessageBytes = yield DataStream.toBytes(chatReadReply.record.data); // to create streams for testing
const decryptedChatMessage = yield Records.decrypt(chatReadReply.record, decryptedContextDerivedPrivateKey, DataStream.fromBytes(encryptedChatMessageBytes));
expect(yield DataStream.toBytes(decryptedChatMessage)).to.deep.equal(Encoder.stringToBytes(messageByAlice));
// Arguably unrelated to the scenario, but let's also sanity check that Alice's root key can also decrypt the encrypted chat message
const decryptedChatMessageStream2 = yield Records.decrypt(chatReadReply.record, aliceRootKey, DataStream.fromBytes(encryptedChatMessageBytes));
expect(yield DataStream.toBytes(decryptedChatMessageStream2)).to.deep.equal(Encoder.stringToBytes(messageByAlice));
}));
});
}
//# sourceMappingURL=end-to-end-tests.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,961 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import freeForAll from '../vectors/protocol-definitions/free-for-all.json' assert { type: 'json' };
import threadProtocol from '../vectors/protocol-definitions/thread-role.json' assert { type: 'json' };
import { expect } from 'chai';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { Dwn, DwnConstant, DwnInterfaceName, DwnMethodName, Message, Time } from '../../src/index.js';
export function testEventsQueryScenarios() {
describe('events query tests', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('supports multiple filter types', () => __awaiter(this, void 0, void 0, function* () {
var _a;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const record = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const protocol = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
// insert data
const recordReply = yield dwn.processMessage(alice.did, record.message, { dataStream: record.dataStream });
const protocolReply = yield dwn.processMessage(alice.did, protocol.message);
expect(recordReply.status.code).to.equal(202, 'RecordsWrite');
expect(protocolReply.status.code).to.equal(202, 'ProtocolConfigure');
const eventsQueryRecords = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [
{ recordId: record.message.recordId },
{ protocol: protocol.message.descriptor.definition.protocol } // ProtocolConfigure
],
});
const recordEventsReply = yield dwn.processMessage(alice.did, eventsQueryRecords.message);
expect(recordEventsReply.status.code).to.equal(200);
expect((_a = recordEventsReply.entries) === null || _a === void 0 ? void 0 : _a.length).to.equal(2);
expect(recordEventsReply.entries).to.have.members([
yield Message.getCid(record.message),
yield Message.getCid(protocol.message),
]);
}));
it('filters by interface type', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice creates 2 different types of messages (RecordsWrite, ProtocolsConfigure)
// alice queries for messages from each interface respectively (Records, Protocols)
// alice creates 2 additional messages (RecordsDelete, ProtocolsRevoke)
// alice queries for messages for each interface respectively providing a cursor.
var _b, _c, _d, _e;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const record = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const protocol = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
// insert data
const recordReply = yield dwn.processMessage(alice.did, record.message, { dataStream: record.dataStream });
const protocolReply = yield dwn.processMessage(alice.did, protocol.message);
expect(recordReply.status.code).to.equal(202, 'RecordsWrite');
expect(protocolReply.status.code).to.equal(202, 'ProtocolConfigure');
let eventsQueryRecords = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Records }],
});
const recordEventsReply = yield dwn.processMessage(alice.did, eventsQueryRecords.message);
expect(recordEventsReply.status.code).to.equal(200);
expect((_b = recordEventsReply.entries) === null || _b === void 0 ? void 0 : _b.length).to.equal(1);
expect(recordEventsReply.entries[0]).to.equal(yield Message.getCid(record.message));
let eventsQueryProtocols = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Protocols }],
});
const protocolEventsReply = yield dwn.processMessage(alice.did, eventsQueryProtocols.message);
expect(protocolEventsReply.status.code).to.equal(200);
expect((_c = protocolEventsReply.entries) === null || _c === void 0 ? void 0 : _c.length).to.equal(1);
expect(protocolEventsReply.entries[0]).to.equal(yield Message.getCid(protocol.message));
// insert additional data to query beyond a cursor
const recordDelete = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: record.message.recordId });
const recordDeleteReply = yield dwn.processMessage(alice.did, recordDelete.message);
expect(recordDeleteReply.status.code).to.equal(202, 'RecordsDelete');
// query after cursor
eventsQueryRecords = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Records }],
cursor: recordEventsReply.cursor, // the cursor from the prior query
});
const recordEventsReplyAfterCursor = yield dwn.processMessage(alice.did, eventsQueryRecords.message);
expect(recordEventsReplyAfterCursor.status.code).to.equal(200);
expect((_d = recordEventsReplyAfterCursor.entries) === null || _d === void 0 ? void 0 : _d.length).to.equal(1);
expect(recordEventsReplyAfterCursor.entries[0]).to.equal(yield Message.getCid(recordDelete.message));
eventsQueryProtocols = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Protocols }],
cursor: protocolEventsReply.cursor, // the cursor from the prior query
});
const protocolEventsReplyAfterCursor = yield dwn.processMessage(alice.did, eventsQueryProtocols.message);
expect(protocolEventsReplyAfterCursor.status.code).to.equal(200);
expect((_e = protocolEventsReplyAfterCursor.entries) === null || _e === void 0 ? void 0 : _e.length).to.equal(0); // no new messages
}));
it('filters by method type', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice creates a variety of Messages (RecordsWrite, RecordsDelete, ProtocolConfigure)
// alice queries for only RecordsWrite messages
// alice creates more messages to query beyond a cursor
var _f, _g;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// write 1
const record1 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const record1Reply = yield dwn.processMessage(alice.did, record1.message, { dataStream: record1.dataStream });
expect(record1Reply.status.code).to.equal(202, 'RecordsWrite');
// other messages
const protocol = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
const protocolReply = yield dwn.processMessage(alice.did, protocol.message);
expect(protocolReply.status.code).to.equal(202, 'ProtocolConfigure');
// write 2
const record2 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const record2Reply = yield dwn.processMessage(alice.did, record2.message, { dataStream: record2.dataStream });
expect(record2Reply.status.code).to.equal(202, 'RecordsWrite');
// delete write 1
const delete1 = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: record1.message.recordId });
const delete1Reply = yield dwn.processMessage(alice.did, delete1.message);
expect(delete1Reply.status.code).to.equal(202, 'RecordsDelete');
let recordsWriteEvents = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Records, method: DwnMethodName.Write }]
});
const recordsWriteEventsReply = yield dwn.processMessage(alice.did, recordsWriteEvents.message);
expect(recordsWriteEventsReply.status.code).to.equal(200);
expect((_f = recordsWriteEventsReply.entries) === null || _f === void 0 ? void 0 : _f.length).to.equal(2);
expect(recordsWriteEventsReply.entries[0]).to.equal(yield Message.getCid(record1.message));
expect(recordsWriteEventsReply.entries[1]).to.equal(yield Message.getCid(record2.message));
// additional messages
const record2Update = yield TestDataGenerator.generateFromRecordsWrite({ author: alice, existingWrite: record2.recordsWrite });
const record2UpdateReply = yield dwn.processMessage(alice.did, record2Update.message, { dataStream: record2Update.dataStream });
expect(record2UpdateReply.status.code).to.equal(202, 'RecordsDelete');
recordsWriteEvents = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ interface: DwnInterfaceName.Records, method: DwnMethodName.Write }],
cursor: recordsWriteEventsReply.cursor,
});
const recordsWriteEventsReplyAfterCursor = yield dwn.processMessage(alice.did, recordsWriteEvents.message);
expect(recordsWriteEventsReplyAfterCursor.status.code).to.equal(200);
expect((_g = recordsWriteEventsReplyAfterCursor.entries) === null || _g === void 0 ? void 0 : _g.length).to.equal(1);
expect(recordsWriteEventsReplyAfterCursor.entries[0]).to.equal(yield Message.getCid(record2Update.message));
}));
it('filters by a dateUpdated (messageTimestamp) range across different message types', () => __awaiter(this, void 0, void 0, function* () {
var _h, _j;
// scenario:
// alice creates (2) messages, (RecordsWrite and ProtocolsConfigure)
// each message on the first date of the year (2021, 2022 and 2023 respectively.
// alice queries for all records beyond the last day of 2021 and should return 1 of the 2 messages (ProtocolConfigure)
// alice then creates a RecordsDelete message for the original RecordsWrite
// alice queries once again however supplying a cursor of the last message from the prior query, returning the RecordsDelete message.
const firstDayOf2021 = Time.createTimestamp({ year: 2021, month: 1, day: 1 });
const firstDayOf2023 = Time.createTimestamp({ year: 2023, month: 1, day: 1 });
const alice = yield TestDataGenerator.generateDidKeyPersona();
const write = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2021, messageTimestamp: firstDayOf2021 });
const protocol = yield TestDataGenerator.generateProtocolsConfigure({ author: alice, messageTimestamp: firstDayOf2023 });
// insert data
const writeReply = yield dwn.processMessage(alice.did, write.message, { dataStream: write.dataStream });
const protocolReply = yield dwn.processMessage(alice.did, protocol.message);
expect(writeReply.status.code).to.equal(202, 'RecordsWrite');
expect(protocolReply.status.code).to.equal(202, 'ProtocolConfigure');
// query from last day of 2021
const lastDayOf2021 = Time.createTimestamp({ year: 2021, month: 12, day: 31 });
let eventsQuery1 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateUpdated: { from: lastDayOf2021 } }],
});
let reply1 = yield dwn.processMessage(alice.did, eventsQuery1.message);
expect(reply1.status.code).to.equal(200);
expect((_h = reply1.entries) === null || _h === void 0 ? void 0 : _h.length).to.equal(1);
expect(reply1.entries[0]).to.equal(yield Message.getCid(protocol.message));
// delete the RecordsWrite
const delete1 = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write.message.recordId });
const delete1Reply = yield dwn.processMessage(alice.did, delete1.message);
expect(delete1Reply.status.code).to.equal(202);
eventsQuery1 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateUpdated: { from: lastDayOf2021 } }],
cursor: reply1.cursor
});
reply1 = yield dwn.processMessage(alice.did, eventsQuery1.message);
expect(reply1.status.code).to.equal(200);
expect((_j = reply1.entries) === null || _j === void 0 ? void 0 : _j.length).to.equal(1);
expect(reply1.entries[0]).to.equal(yield Message.getCid(delete1.message));
}));
it('filters by dateCreated', () => __awaiter(this, void 0, void 0, function* () {
var _k, _l, _m, _o, _p, _q, _r, _s;
// scenario: 4 records, created on first of 2021, 2022, 2023, 2024 respectively, only the first 2 records
const firstDayOf2021 = Time.createTimestamp({ year: 2021, month: 1, day: 1 });
const firstDayOf2022 = Time.createTimestamp({ year: 2022, month: 1, day: 1 });
const firstDayOf2023 = Time.createTimestamp({ year: 2023, month: 1, day: 1 });
const firstDayOf2024 = Time.createTimestamp({ year: 2024, month: 1, day: 1 });
const alice = yield TestDataGenerator.generateDidKeyPersona();
const write1 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2021, messageTimestamp: firstDayOf2021 });
const write2 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2022, messageTimestamp: firstDayOf2022 });
const write3 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2023, messageTimestamp: firstDayOf2023 });
const write4 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2024, messageTimestamp: firstDayOf2024 });
// insert data
const writeReply1 = yield dwn.processMessage(alice.did, write1.message, { dataStream: write1.dataStream });
const writeReply2 = yield dwn.processMessage(alice.did, write2.message, { dataStream: write2.dataStream });
const writeReply3 = yield dwn.processMessage(alice.did, write3.message, { dataStream: write3.dataStream });
const writeReply4 = yield dwn.processMessage(alice.did, write4.message, { dataStream: write4.dataStream });
expect(writeReply1.status.code).to.equal(202);
expect(writeReply2.status.code).to.equal(202);
expect(writeReply3.status.code).to.equal(202);
expect(writeReply4.status.code).to.equal(202);
// testing `from` range with a limit
const lastDayOf2021 = Time.createTimestamp({ year: 2021, month: 12, day: 31 });
let fromLastDayOf2021 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: lastDayOf2021 } }],
});
let fromLastDayOf2021Reply = yield dwn.processMessage(alice.did, fromLastDayOf2021.message);
expect(fromLastDayOf2021Reply.status.code).to.equal(200);
expect((_k = fromLastDayOf2021Reply.entries) === null || _k === void 0 ? void 0 : _k.length).to.equal(3);
expect(fromLastDayOf2021Reply.entries[0]).to.equal(yield Message.getCid(write2.message));
expect(fromLastDayOf2021Reply.entries[1]).to.equal(yield Message.getCid(write3.message));
expect(fromLastDayOf2021Reply.entries[2]).to.equal(yield Message.getCid(write4.message));
// testing `to` range
const lastDayOf2022 = Time.createTimestamp({ year: 2022, month: 12, day: 31 });
let toLastDayOf2022 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { to: lastDayOf2022 } }],
});
let toLastDayOf2022Reply = yield dwn.processMessage(alice.did, toLastDayOf2022.message);
expect(toLastDayOf2022Reply.status.code).to.equal(200);
expect((_l = toLastDayOf2022Reply.entries) === null || _l === void 0 ? void 0 : _l.length).to.equal(2);
expect(toLastDayOf2022Reply.entries[0]).to.equal(yield Message.getCid(write1.message));
expect(toLastDayOf2022Reply.entries[1]).to.equal(yield Message.getCid(write2.message));
// testing `from` and `to` range
const lastDayOf2023 = Time.createTimestamp({ year: 2023, month: 12, day: 31 });
let fromLastDay2022ToLastDay2023 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: lastDayOf2022, to: lastDayOf2023 } }],
});
let fromLastDayOf2022ToLastDay2023Reply = yield dwn.processMessage(alice.did, fromLastDay2022ToLastDay2023.message);
expect(fromLastDayOf2022ToLastDay2023Reply.status.code).to.equal(200);
expect((_m = fromLastDayOf2022ToLastDay2023Reply.entries) === null || _m === void 0 ? void 0 : _m.length).to.equal(1);
expect(fromLastDayOf2022ToLastDay2023Reply.entries[0]).to.equal(yield Message.getCid(write3.message));
// testing edge case where value equals `from` and `to`
let fromFirstDay2022ToFirstDay2023 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: firstDayOf2022, to: firstDayOf2023 } }],
});
let fromFirstDay2022ToFirstDay2023Reply = yield dwn.processMessage(alice.did, fromFirstDay2022ToFirstDay2023.message);
expect(fromFirstDay2022ToFirstDay2023Reply.status.code).to.equal(200);
expect((_o = fromFirstDay2022ToFirstDay2023Reply.entries) === null || _o === void 0 ? void 0 : _o.length).to.equal(1);
expect(fromFirstDay2022ToFirstDay2023Reply.entries[0]).to.equal(yield Message.getCid(write2.message));
// add an additional records to match against the previous queries
const write5 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: lastDayOf2022, messageTimestamp: lastDayOf2022 });
const writeReply5 = yield dwn.processMessage(alice.did, write5.message, { dataStream: write5.dataStream });
expect(writeReply5.status.code).to.equal(202);
const write6 = yield TestDataGenerator.generateRecordsWrite({ author: alice, dateCreated: firstDayOf2021, messageTimestamp: firstDayOf2021 });
const writeReply6 = yield dwn.processMessage(alice.did, write6.message, { dataStream: write6.dataStream });
expect(writeReply6.status.code).to.equal(202);
fromLastDayOf2021 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: lastDayOf2021 } }],
cursor: fromLastDayOf2021Reply.cursor
});
fromLastDayOf2021Reply = yield dwn.processMessage(alice.did, fromLastDayOf2021.message);
expect(fromLastDayOf2021Reply.status.code).to.equal(200);
expect((_p = fromLastDayOf2021Reply.entries) === null || _p === void 0 ? void 0 : _p.length).to.equal(1);
expect(fromLastDayOf2021Reply.entries[0]).to.equal(yield Message.getCid(write5.message));
toLastDayOf2022 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { to: lastDayOf2022 } }],
cursor: toLastDayOf2022Reply.cursor,
});
toLastDayOf2022Reply = yield dwn.processMessage(alice.did, toLastDayOf2022.message);
expect(toLastDayOf2022Reply.status.code).to.equal(200);
expect((_q = toLastDayOf2022Reply.entries) === null || _q === void 0 ? void 0 : _q.length).to.equal(1);
expect(toLastDayOf2022Reply.entries[0]).to.equal(yield Message.getCid(write6.message));
fromLastDay2022ToLastDay2023 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: lastDayOf2022, to: lastDayOf2023 } }],
cursor: fromLastDayOf2022ToLastDay2023Reply.cursor,
});
fromLastDayOf2022ToLastDay2023Reply = yield dwn.processMessage(alice.did, fromLastDay2022ToLastDay2023.message);
expect(fromLastDayOf2022ToLastDay2023Reply.status.code).to.equal(200);
expect((_r = fromLastDayOf2022ToLastDay2023Reply.entries) === null || _r === void 0 ? void 0 : _r.length).to.equal(1);
expect(fromLastDayOf2021Reply.entries[0]).to.equal(yield Message.getCid(write5.message));
// testing edge case where value equals `from` and `to`
fromFirstDay2022ToFirstDay2023 = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ dateCreated: { from: firstDayOf2022, to: firstDayOf2023 } }],
cursor: fromFirstDay2022ToFirstDay2023Reply.cursor,
});
fromFirstDay2022ToFirstDay2023Reply = yield dwn.processMessage(alice.did, fromFirstDay2022ToFirstDay2023.message);
expect(fromFirstDay2022ToFirstDay2023Reply.status.code).to.equal(200);
expect((_s = fromFirstDay2022ToFirstDay2023Reply.entries) === null || _s === void 0 ? void 0 : _s.length).to.equal(1);
expect(fromLastDayOf2021Reply.entries[0]).to.equal(yield Message.getCid(write5.message));
}));
it('filters by a protocol across different message types', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice creates (3) different message types all related to "proto1" (Configure, RecordsWrite, RecordsDelete)
// alice creates (3) different message types all related to "proto2" (Configure, RecordsWrite, RecordsDelete)
// when issuing an EventsQuery for the specific protocol, only Events related to it should be returned.
// alice then creates an additional messages to query after a cursor
var _t, _u, _v, _w;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// create a proto1
const protoConf1 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign(Object.assign({}, freeForAll), { protocol: 'proto1' })
});
const postProperties = {
protocolPath: 'post',
schema: freeForAll.types.post.schema,
dataFormat: freeForAll.types.post.dataFormats[0],
};
const proto1 = protoConf1.message.descriptor.definition.protocol;
const protoConf1Response = yield dwn.processMessage(alice.did, protoConf1.message);
expect(protoConf1Response.status.code).equals(202);
// create a proto2
const protoConf2 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign(Object.assign({}, freeForAll), { protocol: 'proto2' })
});
const proto2 = protoConf2.message.descriptor.definition.protocol;
const protoConf2Response = yield dwn.processMessage(alice.did, protoConf2.message);
expect(protoConf2Response.status.code).equals(202);
// create a record for proto1
const write1proto1 = yield TestDataGenerator.generateRecordsWrite(Object.assign({ author: alice, protocol: proto1 }, postProperties));
const write1Response = yield dwn.processMessage(alice.did, write1proto1.message, { dataStream: write1proto1.dataStream });
expect(write1Response.status.code).equals(202);
// create a record for proto2
const write1proto2 = yield TestDataGenerator.generateRecordsWrite(Object.assign({ author: alice, protocol: proto2 }, postProperties));
const write1Proto2Response = yield dwn.processMessage(alice.did, write1proto2.message, { dataStream: write1proto2.dataStream });
expect(write1Proto2Response.status.code).equals(202);
// filter for proto1
let proto1EventsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: proto1 }]
});
let proto1EventsReply = yield dwn.processMessage(alice.did, proto1EventsQuery.message);
expect(proto1EventsReply.status.code).equals(200);
expect((_t = proto1EventsReply.entries) === null || _t === void 0 ? void 0 : _t.length).equals(2);
// check order of events returned.
expect(proto1EventsReply.entries[0]).to.equal(yield Message.getCid(protoConf1.message));
expect(proto1EventsReply.entries[1]).to.equal(yield Message.getCid(write1proto1.message));
// filter for proto2
let proto2EventsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: proto2 }]
});
let proto2EventsReply = yield dwn.processMessage(alice.did, proto2EventsQuery.message);
expect(proto2EventsReply.status.code).equals(200);
expect((_u = proto2EventsReply.entries) === null || _u === void 0 ? void 0 : _u.length).equals(2);
// check order of events returned.
expect(proto2EventsReply.entries[0]).to.equal(yield Message.getCid(protoConf2.message));
expect(proto2EventsReply.entries[1]).to.equal(yield Message.getCid(write1proto2.message));
// delete proto1 message
const deleteProto1Message = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write1proto1.message.recordId });
const deleteProto1MessageReply = yield dwn.processMessage(alice.did, deleteProto1Message.message);
expect(deleteProto1MessageReply.status.code).to.equal(202);
// delete proto2 message
const deleteProto2Message = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write1proto2.message.recordId });
const deleteProto2MessageReply = yield dwn.processMessage(alice.did, deleteProto2Message.message);
expect(deleteProto2MessageReply.status.code).to.equal(202);
//query messages beyond the cursor
proto1EventsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: proto1 }],
cursor: proto1EventsReply.cursor,
});
proto1EventsReply = yield dwn.processMessage(alice.did, proto1EventsQuery.message);
expect(proto1EventsReply.status.code).equals(200);
expect((_v = proto1EventsReply.entries) === null || _v === void 0 ? void 0 : _v.length).equals(1);
expect(proto1EventsReply.entries[0]).to.equal(yield Message.getCid(deleteProto1Message.message));
//query messages beyond the cursor
proto2EventsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: proto2 }],
cursor: proto2EventsReply.cursor,
});
proto2EventsReply = yield dwn.processMessage(alice.did, proto2EventsQuery.message);
expect(proto2EventsReply.status.code).equals(200);
expect((_w = proto2EventsReply.entries) === null || _w === void 0 ? void 0 : _w.length).equals(1);
expect(proto2EventsReply.entries[0]).to.equal(yield Message.getCid(deleteProto2Message.message));
}));
it('filters by protocol, protocolPath & parentId', () => __awaiter(this, void 0, void 0, function* () {
// scenario: get all messages across a protocol & protocolPath combo
// alice installs a protocol and creates a thread
// alice adds bob and carol as participants
// alice, bob, and carol all create messages
// alice filter for 'thread', 'thread/participants' and 'thread/messages'
// alice deletes carol participant message
// alice filters for 'thread/participant' after a cursor
var _x, _y, _z, _0, _1;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// create protocol
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, threadProtocol)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
// alice creates thread
const thread = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread'
});
const threadReply = yield dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream });
expect(threadReply.status.code).to.equal(202);
// add bob as participant
const bobParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const bobParticipantReply = yield dwn.processMessage(alice.did, bobParticipant.message, { dataStream: bobParticipant.dataStream });
expect(bobParticipantReply.status.code).to.equal(202);
// add carol as participant
const carolParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const carolParticipantReply = yield dwn.processMessage(alice.did, carolParticipant.message, { dataStream: carolParticipant.dataStream });
expect(carolParticipantReply.status.code).to.equal(202);
// add a message to protocol1
const message1 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message1Reply = yield dwn.processMessage(alice.did, message1.message, { dataStream: message1.dataStream });
expect(message1Reply.status.code).to.equal(202);
const message2 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message2Reply = yield dwn.processMessage(alice.did, message2.message, { dataStream: message2.dataStream });
expect(message2Reply.status.code).to.equal(202);
const message3 = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message3Reply = yield dwn.processMessage(alice.did, message3.message, { dataStream: message3.dataStream });
expect(message3Reply.status.code).to.equal(202);
// query for thread
const threadQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: protocol, protocolPath: 'thread' }],
});
const threadQueryReply = yield dwn.processMessage(alice.did, threadQuery.message);
expect(threadQueryReply.status.code).to.equal(200);
expect((_x = threadQueryReply.entries) === null || _x === void 0 ? void 0 : _x.length).to.equal(1);
expect(threadQueryReply.entries[0]).to.equal(yield Message.getCid(thread.message));
// query for participants
let participantsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: protocol, protocolPath: 'thread/participant', parentId: thread.message.recordId }],
});
let participantsQueryReply = yield dwn.processMessage(alice.did, participantsQuery.message);
expect(participantsQueryReply.status.code).to.equal(200);
expect((_y = participantsQueryReply.entries) === null || _y === void 0 ? void 0 : _y.length).to.equal(2);
expect(participantsQueryReply.entries[0]).to.equal(yield Message.getCid(bobParticipant.message));
expect(participantsQueryReply.entries[1]).to.equal(yield Message.getCid(carolParticipant.message));
// query for chats
let chatQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: protocol, protocolPath: 'thread/chat', parentId: thread.message.recordId }],
});
let chatQueryReply = yield dwn.processMessage(alice.did, chatQuery.message);
expect(chatQueryReply.status.code).to.equal(200);
expect((_z = chatQueryReply.entries) === null || _z === void 0 ? void 0 : _z.length).to.equal(3);
expect(chatQueryReply.entries[0]).to.equal(yield Message.getCid(message1.message));
expect(chatQueryReply.entries[1]).to.equal(yield Message.getCid(message2.message));
expect(chatQueryReply.entries[2]).to.equal(yield Message.getCid(message3.message));
// delete carol participant
const deleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: carolParticipant.message.recordId
});
const deleteCarolReply = yield dwn.processMessage(alice.did, deleteCarol.message);
expect(deleteCarolReply.status.code).to.equal(202);
// query for participants past the cursor
participantsQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: protocol, protocolPath: 'thread/participant', parentId: thread.message.recordId }],
cursor: participantsQueryReply.cursor
});
participantsQueryReply = yield dwn.processMessage(alice.did, participantsQuery.message);
expect(participantsQueryReply.status.code).to.equal(200);
expect((_0 = participantsQueryReply.entries) === null || _0 === void 0 ? void 0 : _0.length).to.equal(1);
expect(participantsQueryReply.entries[0]).to.equal(yield Message.getCid(deleteCarol.message));
// query for chats beyond the cursor as a control, should have none.
chatQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ protocol: protocol, protocolPath: 'thread/chat', parentId: thread.message.recordId }],
cursor: chatQueryReply.cursor
});
chatQueryReply = yield dwn.processMessage(alice.did, chatQuery.message);
expect(chatQueryReply.status.code).to.equal(200);
expect((_1 = chatQueryReply.entries) === null || _1 === void 0 ? void 0 : _1.length).to.equal(0);
}));
it('filters by recipient', () => __awaiter(this, void 0, void 0, function* () {
// scenario: alice installs a free-for-all protocol and makes posts with both bob and carol as recipients
// carol and bob also make posts with alice as a recipient
// alice queries for events meant for specific recipients
// alice then makes another message to query for using the pervious as a cursor
var _2, _3, _4;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, freeForAll)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
const postProperties = {
protocol: protocol,
protocolPath: 'post',
schema: freeForAll.types.post.schema,
dataFormat: freeForAll.types.post.dataFormats[0],
};
const messageFromAliceToBob = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: bob.did }));
const messageFromAliceToBobReply = yield dwn.processMessage(alice.did, messageFromAliceToBob.message, { dataStream: messageFromAliceToBob.dataStream });
expect(messageFromAliceToBobReply.status.code).to.equal(202);
const messageFromAliceToCarol = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: carol.did }));
const messageFromAliceToCarolReply = yield dwn.processMessage(alice.did, messageFromAliceToCarol.message, { dataStream: messageFromAliceToCarol.dataStream });
expect(messageFromAliceToCarolReply.status.code).to.equal(202);
const messageFromBobToAlice = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: bob, recipient: alice.did }));
const messageFromBobToAliceReply = yield dwn.processMessage(alice.did, messageFromBobToAlice.message, { dataStream: messageFromBobToAlice.dataStream });
expect(messageFromBobToAliceReply.status.code).to.equal(202);
const messageFromCarolToAlice = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: carol, recipient: alice.did }));
const messageFromCarolToAliceReply = yield dwn.processMessage(alice.did, messageFromCarolToAlice.message, { dataStream: messageFromCarolToAlice.dataStream });
expect(messageFromCarolToAliceReply.status.code).to.equal(202);
let authorQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ recipient: alice.did }]
});
let authorQueryReply = yield dwn.processMessage(alice.did, authorQuery.message);
expect(authorQueryReply.status.code).to.equal(200);
expect((_2 = authorQueryReply.entries) === null || _2 === void 0 ? void 0 : _2.length).to.equal(2);
expect(authorQueryReply.entries[0]).to.equal(yield Message.getCid(messageFromBobToAlice.message));
expect(authorQueryReply.entries[1]).to.equal(yield Message.getCid(messageFromCarolToAlice.message));
authorQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ recipient: bob.did }]
});
authorQueryReply = yield dwn.processMessage(alice.did, authorQuery.message);
expect(authorQueryReply.status.code).to.equal(200);
expect((_3 = authorQueryReply.entries) === null || _3 === void 0 ? void 0 : _3.length).to.equal(1);
expect(authorQueryReply.entries[0]).to.equal(yield Message.getCid(messageFromAliceToBob.message));
// add another message
const messageFromAliceToBob2 = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: bob.did }));
const messageFromAliceToBob2Reply = yield dwn.processMessage(alice.did, messageFromAliceToBob2.message, { dataStream: messageFromAliceToBob2.dataStream });
expect(messageFromAliceToBob2Reply.status.code).to.equal(202);
authorQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ recipient: bob.did }],
cursor: authorQueryReply.cursor,
});
authorQueryReply = yield dwn.processMessage(alice.did, authorQuery.message);
expect(authorQueryReply.status.code).to.equal(200);
expect((_4 = authorQueryReply.entries) === null || _4 === void 0 ? void 0 : _4.length).to.equal(1);
expect(authorQueryReply.entries[0]).to.equal(yield Message.getCid(messageFromAliceToBob2.message));
}));
it('filters by schema', () => __awaiter(this, void 0, void 0, function* () {
var _5, _6, _7, _8;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const schema1Message1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const schema1Message1Reply = yield dwn.processMessage(alice.did, schema1Message1.message, { dataStream: schema1Message1.dataStream });
expect(schema1Message1Reply.status.code).to.equal(202);
const schema2Message1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema2'
});
const schema2Message1Reply = yield dwn.processMessage(alice.did, schema2Message1.message, { dataStream: schema2Message1.dataStream });
expect(schema2Message1Reply.status.code).to.equal(202);
const schema2Message2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema2'
});
const schema2Message2Reply = yield dwn.processMessage(alice.did, schema2Message2.message, { dataStream: schema2Message2.dataStream });
expect(schema2Message2Reply.status.code).to.equal(202);
let schema1Query = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }],
});
let schema1QueryReply = yield dwn.processMessage(alice.did, schema1Query.message);
expect(schema1QueryReply.status.code).to.equal(200);
expect((_5 = schema1QueryReply.entries) === null || _5 === void 0 ? void 0 : _5.length).to.equal(1);
expect(schema1QueryReply.entries[0]).to.equal(yield Message.getCid(schema1Message1.message));
let schema2Query = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema2' }],
});
let schema2QueryReply = yield dwn.processMessage(alice.did, schema2Query.message);
expect(schema2QueryReply.status.code).to.equal(200);
expect((_6 = schema2QueryReply.entries) === null || _6 === void 0 ? void 0 : _6.length).to.equal(2);
expect(schema2QueryReply.entries[0]).to.equal(yield Message.getCid(schema2Message1.message));
expect(schema2QueryReply.entries[1]).to.equal(yield Message.getCid(schema2Message2.message));
const schema1Message2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const schema1Message2Reply = yield dwn.processMessage(alice.did, schema1Message2.message, { dataStream: schema1Message2.dataStream });
expect(schema1Message2Reply.status.code).to.equal(202);
schema1Query = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema1' }],
cursor: schema1QueryReply.cursor,
});
schema1QueryReply = yield dwn.processMessage(alice.did, schema1Query.message);
expect(schema1QueryReply.status.code).to.equal(200);
expect((_7 = schema1QueryReply.entries) === null || _7 === void 0 ? void 0 : _7.length).to.equal(1);
expect(schema1QueryReply.entries[0]).to.equal(yield Message.getCid(schema1Message2.message));
schema2Query = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ schema: 'schema2' }],
cursor: schema2QueryReply.cursor,
});
schema2QueryReply = yield dwn.processMessage(alice.did, schema2Query.message);
expect(schema2QueryReply.status.code).to.equal(200);
expect((_8 = schema2QueryReply.entries) === null || _8 === void 0 ? void 0 : _8.length).to.equal(0);
}));
it('filters by recordId', () => __awaiter(this, void 0, void 0, function* () {
var _9, _10;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// a write as a control, will not show up in query
const controlWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const write2Reply = yield dwn.processMessage(alice.did, controlWrite.message, { dataStream: controlWrite.dataStream });
expect(write2Reply.status.code).to.equal(202);
const write = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const write1Reply = yield dwn.processMessage(alice.did, write.message, { dataStream: write.dataStream });
expect(write1Reply.status.code).to.equal(202);
const update = yield TestDataGenerator.generateFromRecordsWrite({
author: alice,
existingWrite: write.recordsWrite,
});
const updateReply = yield dwn.processMessage(alice.did, update.message, { dataStream: update.dataStream });
expect(updateReply.status.code).to.equal(202);
let recordQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ recordId: write.message.recordId }],
});
let recordQueryReply = yield dwn.processMessage(alice.did, recordQuery.message);
expect(recordQueryReply.status.code).to.equal(200);
expect((_9 = recordQueryReply.entries) === null || _9 === void 0 ? void 0 : _9.length).to.equal(2);
expect(recordQueryReply.entries[0]).to.equal(yield Message.getCid(write.message));
expect(recordQueryReply.entries[1]).to.equal(yield Message.getCid(update.message));
const deleteRecord = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: write.message.recordId,
});
const deleteRecordReply = yield dwn.processMessage(alice.did, deleteRecord.message);
expect(deleteRecordReply.status.code).to.equal(202);
recordQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{ recordId: write.message.recordId }],
cursor: recordQueryReply.cursor,
});
recordQueryReply = yield dwn.processMessage(alice.did, recordQuery.message);
expect(recordQueryReply.status.code).to.equal(200);
expect((_10 = recordQueryReply.entries) === null || _10 === void 0 ? void 0 : _10.length).to.equal(1);
expect(recordQueryReply.entries[0]).to.equal(yield Message.getCid(deleteRecord.message));
}));
it('filters by dataFormat', () => __awaiter(this, void 0, void 0, function* () {
// scenario: alice stores different file types and needs events relating to `image/jpeg`
// alice creates 3 files, one of them `image/jpeg`
// alice queries for `image/jpeg` retrieving the one message
// alice adds another image to query for using the prior image as a cursor
var _11, _12;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const textFile = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'application/text'
});
const textFileReply = yield dwn.processMessage(alice.did, textFile.message, { dataStream: textFile.dataStream });
expect(textFileReply.status.code).to.equal(202);
const jsonData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'application/json'
});
const jsonDataReply = yield dwn.processMessage(alice.did, jsonData.message, { dataStream: jsonData.dataStream });
expect(jsonDataReply.status.code).to.equal(202);
const imageData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'image/jpeg'
});
const imageDataReply = yield dwn.processMessage(alice.did, imageData.message, { dataStream: imageData.dataStream });
expect(imageDataReply.status.code).to.equal(202);
//get image data
let imageQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
dataFormat: 'image/jpeg'
}]
});
let imageQueryReply = yield dwn.processMessage(alice.did, imageQuery.message);
expect(imageQueryReply.status.code).to.equal(200);
expect((_11 = imageQueryReply.entries) === null || _11 === void 0 ? void 0 : _11.length).to.equal(1);
expect(imageQueryReply.entries[0]).to.equal(yield Message.getCid(imageData.message));
// add another image
const imageData2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'image/jpeg'
});
const imageData2Reply = yield dwn.processMessage(alice.did, imageData2.message, { dataStream: imageData2.dataStream });
expect(imageData2Reply.status.code).to.equal(202);
imageQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
dataFormat: 'image/jpeg'
}],
cursor: imageQueryReply.cursor,
});
imageQueryReply = yield dwn.processMessage(alice.did, imageQuery.message);
expect(imageQueryReply.status.code).to.equal(200);
expect((_12 = imageQueryReply.entries) === null || _12 === void 0 ? void 0 : _12.length).to.equal(1);
expect(imageQueryReply.entries[0]).to.equal(yield Message.getCid(imageData2.message));
}));
;
it('filters by dataSize', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice inserts both small and large data
// alice requests events for messages with data size under a threshold
var _13, _14;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const smallSize1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const smallSize1Reply = yield dwn.processMessage(alice.did, smallSize1.message, { dataStream: smallSize1.dataStream });
expect(smallSize1Reply.status.code).to.equal(202);
const largeSize = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data: TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded + 1)
});
const largeSizeReply = yield dwn.processMessage(alice.did, largeSize.message, { dataStream: largeSize.dataStream });
expect(largeSizeReply.status.code).to.equal(202);
const smallSize2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const smallSize2Reply = yield dwn.processMessage(alice.did, smallSize2.message, { dataStream: smallSize2.dataStream });
expect(smallSize2Reply.status.code).to.equal(202);
//get large sizes
let largeSizeQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
dataSize: { gte: DwnConstant.maxDataSizeAllowedToBeEncoded + 1 }
}]
});
let largeSizeQueryReply = yield dwn.processMessage(alice.did, largeSizeQuery.message);
expect(largeSizeQueryReply.status.code).to.equal(200);
expect((_13 = largeSizeQueryReply.entries) === null || _13 === void 0 ? void 0 : _13.length).to.equal(1);
expect(largeSizeQueryReply.entries[0]).to.equal(yield Message.getCid(largeSize.message));
const largeSize2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data: TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded + 1)
});
const largeSize2Reply = yield dwn.processMessage(alice.did, largeSize2.message, { dataStream: largeSize2.dataStream });
expect(largeSize2Reply.status.code).to.equal(202);
largeSizeQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
dataSize: { gte: DwnConstant.maxDataSizeAllowedToBeEncoded + 1 }
}],
cursor: largeSizeQueryReply.cursor,
});
largeSizeQueryReply = yield dwn.processMessage(alice.did, largeSizeQuery.message);
expect(largeSizeQueryReply.status.code).to.equal(200);
expect((_14 = largeSizeQueryReply.entries) === null || _14 === void 0 ? void 0 : _14.length).to.equal(1);
expect(largeSizeQueryReply.entries[0]).to.equal(yield Message.getCid(largeSize2.message));
}));
it('filters by contextId', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice configures a chat protocols and creates 2 chat threads
// alice invites bob as participant in thread1 and carol in thread2
// alice writes messages to both bob and carol in their respective threads
// alice queries for events related to thread1 (gets the configure, bob participant, and chats to bob)
// alice writes more messages to both bob and carol in their respective threads
// alice queries for events beyond the latest from the last query, retrieving the additional messages to bob
var _15, _16;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, threadProtocol)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
// alice creates 2 threads
const thread1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread',
});
const thread1Reply = yield dwn.processMessage(alice.did, thread1.message, { dataStream: thread1.dataStream });
expect(thread1Reply.status.code).to.equal(202);
const thread2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread',
});
const thread2Reply = yield dwn.processMessage(alice.did, thread2.message, { dataStream: thread2.dataStream });
expect(thread2Reply.status.code).to.equal(202);
// alice adds bob as a participant to thread 1
const bobParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread1.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const bobParticipantReply = yield dwn.processMessage(alice.did, bobParticipant.message, { dataStream: bobParticipant.dataStream });
expect(bobParticipantReply.status.code).to.equal(202);
// alice adds carol as a participant to thread 1
const carolParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread2.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const carolParticipantReply = yield dwn.processMessage(alice.did, carolParticipant.message, { dataStream: carolParticipant.dataStream });
expect(carolParticipantReply.status.code).to.equal(202);
// alice writes a message to bob on thread 1
const thread1Chat1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread1.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
});
const thread1Chat1Reply = yield dwn.processMessage(alice.did, thread1Chat1.message, { dataStream: thread1Chat1.dataStream });
expect(thread1Chat1Reply.status.code).to.equal(202);
// alice writes a message to carol on thread 2
const thread2Chat1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread2.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
});
const thread2Chat1Reply = yield dwn.processMessage(alice.did, thread2Chat1.message, { dataStream: thread2Chat1.dataStream });
expect(thread2Chat1Reply.status.code).to.equal(202);
// alice writes another message to bob on thread 1
const thread1Chat2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread1.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
});
const chatMessage2Reply = yield dwn.processMessage(alice.did, thread1Chat2.message, { dataStream: thread1Chat2.dataStream });
expect(chatMessage2Reply.status.code).to.equal(202);
// alice queries events for thread1
let threadContextQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
protocol: protocol,
contextId: thread1.message.contextId,
}],
});
let threadContextQueryReply = yield dwn.processMessage(alice.did, threadContextQuery.message);
expect(threadContextQueryReply.status.code).to.equal(200);
expect((_15 = threadContextQueryReply.entries) === null || _15 === void 0 ? void 0 : _15.length).to.equal(4);
expect(threadContextQueryReply.entries[0]).to.equal(yield Message.getCid(thread1.message));
expect(threadContextQueryReply.entries[1]).to.equal(yield Message.getCid(bobParticipant.message));
expect(threadContextQueryReply.entries[2]).to.equal(yield Message.getCid(thread1Chat1.message));
expect(threadContextQueryReply.entries[3]).to.equal(yield Message.getCid(thread1Chat2.message));
// alice adds more chats to both threads
const thread1Chat3 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread1.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
});
const thread1Chat3Reply = yield dwn.processMessage(alice.did, thread1Chat3.message, { dataStream: thread1Chat3.dataStream });
expect(thread1Chat3Reply.status.code).to.equal(202);
const thread2Chat2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread2.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
});
const thread2Chat2Reply = yield dwn.processMessage(alice.did, thread2Chat2.message, { dataStream: thread2Chat2.dataStream });
expect(thread2Chat2Reply.status.code).to.equal(202);
// query beyond a cursor
threadContextQuery = yield TestDataGenerator.generateEventsQuery({
author: alice,
filters: [{
protocol: protocol,
contextId: thread1.message.contextId,
}],
cursor: threadContextQueryReply.cursor,
});
threadContextQueryReply = yield dwn.processMessage(alice.did, threadContextQuery.message);
expect(threadContextQueryReply.status.code).to.equal(200);
expect((_16 = threadContextQueryReply.entries) === null || _16 === void 0 ? void 0 : _16.length).to.equal(1);
expect(threadContextQueryReply.entries[0]).to.equal(yield Message.getCid(thread1Chat3.message));
}));
});
}
;
//# sourceMappingURL=events-query.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,283 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';
import slackProtocolDefinition from '../vectors/protocol-definitions/slack.json' assert { type: 'json' };
import chai, { expect } from 'chai';
import { Dwn } from '../../src/dwn.js';
import { DwnErrorCode } from '../../src/core/dwn-error.js';
import { Jws } from '../../src/utils/jws.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { RecordsQuery, RecordsRead } from '../../src/index.js';
chai.use(chaiAsPromised);
export function testNestedRoleScenarios() {
describe('Nested role scenarios', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
sinon.restore(); // wipe all previous stubs/spies/mocks/fakes
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
it('should support Slack-like protocol with community and gated channels', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// 1. Alice installs the Slack-like protocol
// 2. Alice creates a community
// 3. Alice can assign Bob as an `admin` in the community
// 4. Bob can invoke his `admin` role to perform actions:
// 4a. Bob can read the community record
// 4b. Bob can create gated-channels 1 & 2 in the community
// 5. Bob as the creator/author of the channels can add participants in the gated-channels
// 5a. Bob can add himself and Carol as participants in the gated-channel 1
// 5b. Bob can add himself and Daniel as participants in the gated-channel 2
// 6. Carol can read the gated channel 2 record by invoking her child participant role to the gated channel 2
// 7. Carol CANNOT add anyone as a participant in the gated-channel 2 since she is not a participant in the channel
// 8. Carol CANNOT add Daniel as another participant in the gated-channel without invoking her role
// 9. Carol can invoke her participant role to add Daniel as another participant in the gated-channel
// 10. Bob can invoke the participant role to write a chat message in the channel
// 11. Carol can invoke the participant role to read chat messages in the channel
// 12. Carol can invoke the participant role to react to Bob's chat message in the channel
// 13. Mallory CANNOT invoke the participant role (which she is not given) to read the chat messages in the channel
// 14. Mallory CANNOT invoke the participant role (which she is not given) to write a chat message in the channel
var _a, _b, _c;
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const daniel = yield TestDataGenerator.generateDidKeyPersona();
const mallory = yield TestDataGenerator.generateDidKeyPersona(); // unauthorized person
const protocolDefinition = slackProtocolDefinition;
// 1. Alice installs the Slack-like protocol
const protocolsConfig = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = yield dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);
// 2. Alice creates a community
const communityRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocolDefinition.protocol,
protocolPath: 'community'
});
const communityRecordReply = yield dwn.processMessage(alice.did, communityRecord.message, { dataStream: communityRecord.dataStream });
expect(communityRecordReply.status.code).to.equal(202);
// 3. Alice can assign Bob as an 'admin' in the community
const communityAdminBobRecord = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/admin',
parentContextId: communityRecord.message.contextId,
});
const communityAdminBobRecordReply = yield dwn.processMessage(alice.did, communityAdminBobRecord.message, { dataStream: communityAdminBobRecord.dataStream });
expect(communityAdminBobRecordReply.status.code).to.equal(202);
// 4. Bob can invoke his `admin` role to perform actions:
// 4a. Bob can read the community record
const bobCommunityRead = yield RecordsRead.create({
signer: Jws.createSigner(bob),
protocolRole: 'community/admin',
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'community',
contextId: communityRecord.message.contextId
}
});
const bobCommunityReadReply = yield dwn.processMessage(alice.did, bobCommunityRead.message);
expect(bobCommunityReadReply.status.code).to.equal(200);
expect((_a = bobCommunityReadReply.record) === null || _a === void 0 ? void 0 : _a.recordId).to.equal(communityRecord.message.recordId);
// 4b. Bob can create gated-channels 1 & 2 in the community
const channel1Record = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolRole: 'community/admin',
protocolPath: 'community/gatedChannel',
parentContextId: communityRecord.message.contextId
});
const channel1RecordReply = yield dwn.processMessage(alice.did, channel1Record.message, { dataStream: channel1Record.dataStream });
expect(channel1RecordReply.status.code).to.equal(202);
const channel2Record = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolRole: 'community/admin',
protocolPath: 'community/gatedChannel',
parentContextId: communityRecord.message.contextId
});
const channel2RecordReply = yield dwn.processMessage(alice.did, channel2Record.message, { dataStream: channel2Record.dataStream });
expect(channel2RecordReply.status.code).to.equal(202);
// 5. Bob as the creator/author of the channels can add participants in the gated-channels
// 5a. Bob can add himself and Carol as participants in the gated-channel 1
const channel1ParticipantBobRecord = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel1Record.message.contextId,
});
const channel1ParticipantBobRecordReply = yield dwn.processMessage(alice.did, channel1ParticipantBobRecord.message, { dataStream: channel1ParticipantBobRecord.dataStream });
expect(channel1ParticipantBobRecordReply.status.code).to.equal(202);
const channel1ParticipantCarolRecord = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel1Record.message.contextId,
});
const channel1ParticipantCarolRecordReply = yield dwn.processMessage(alice.did, channel1ParticipantCarolRecord.message, { dataStream: channel1ParticipantCarolRecord.dataStream });
expect(channel1ParticipantCarolRecordReply.status.code).to.equal(202);
// 5b. Bob can add himself and Daniel as participants in the gated-channel 2
const channel2ParticipantBobRecord = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: bob.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel2Record.message.contextId,
});
const channel2ParticipantBobRecordReply = yield dwn.processMessage(alice.did, channel2ParticipantBobRecord.message, { dataStream: channel2ParticipantBobRecord.dataStream });
expect(channel2ParticipantBobRecordReply.status.code).to.equal(202);
const channel2ParticipantDanielRecord = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: daniel.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel2Record.message.contextId,
});
const channel2ParticipantDanielRecordReply = yield dwn.processMessage(alice.did, channel2ParticipantDanielRecord.message, { dataStream: channel2ParticipantDanielRecord.dataStream });
expect(channel2ParticipantDanielRecordReply.status.code).to.equal(202);
// 6. Carol can read the gated channel 1 record by invoking her child participant role to the gated channel 1
const carolRead = yield RecordsRead.create({
signer: Jws.createSigner(carol),
protocolRole: 'community/gatedChannel/participant',
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel',
contextId: channel1Record.message.contextId
}
});
const carolReadReply = yield dwn.processMessage(alice.did, carolRead.message);
expect(carolReadReply.status.code).to.equal(200);
expect((_b = carolReadReply.record) === null || _b === void 0 ? void 0 : _b.recordId).to.equal(channel1Record.message.recordId);
// 7. Carol CANNOT add anyone as a participant in the gated-channel 2 since she is not a participant in the channel
const participantCarolRecord = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: carol.did,
protocol: protocolDefinition.protocol,
protocolRole: 'community/gatedChannel/participant',
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel2Record.message.contextId
});
const participantCarolRecordReply = yield dwn.processMessage(alice.did, participantCarolRecord.message, { dataStream: participantCarolRecord.dataStream });
expect(participantCarolRecordReply.status.code).to.equal(401);
expect(participantCarolRecordReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound);
// 8. Carol CANNOT add Daniel as another participant in the gated-channel without invoking her role
const participantDanielRecordAttempt1 = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: daniel.did,
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel1Record.message.contextId,
});
const participantDanielRecordAttempt1Reply = yield dwn.processMessage(alice.did, participantDanielRecordAttempt1.message, { dataStream: participantDanielRecordAttempt1.dataStream });
expect(participantDanielRecordAttempt1Reply.status.code).to.equal(401);
expect(participantDanielRecordAttempt1Reply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationActionNotAllowed);
// 9. Carol can invoke her participant role to add Daniel as another participant in the gated-channel
const participantDanielRecordAttempt2 = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: daniel.did,
protocol: protocolDefinition.protocol,
protocolRole: 'community/gatedChannel/participant',
protocolPath: 'community/gatedChannel/participant',
parentContextId: channel1Record.message.contextId,
});
const participantDanielRecordAttempt2Reply = yield dwn.processMessage(alice.did, participantDanielRecordAttempt2.message, { dataStream: participantDanielRecordAttempt2.dataStream });
expect(participantDanielRecordAttempt2Reply.status.code).to.equal(202);
// 10. Bob can invoke the participant role to write a chat message in the channel
const bobChatMessage = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolRole: 'community/gatedChannel/participant',
protocolPath: 'community/gatedChannel/message',
parentContextId: channel1Record.message.contextId
});
const bobChatMessageReply = yield dwn.processMessage(alice.did, bobChatMessage.message, { dataStream: bobChatMessage.dataStream });
expect(bobChatMessageReply.status.code).to.equal(202);
// 11. Carol can invoke the participant role to read chat messages in the channel
const carolQuery = yield RecordsQuery.create({
signer: Jws.createSigner(carol),
protocolRole: 'community/gatedChannel/participant',
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/message',
contextId: channel1Record.message.contextId
}
});
const carolQueryReply = yield dwn.processMessage(alice.did, carolQuery.message);
expect(carolQueryReply.status.code).to.equal(200);
expect((_c = carolQueryReply.entries) === null || _c === void 0 ? void 0 : _c[0].recordId).to.equal(bobChatMessage.message.recordId);
// 12. Carol can invoke the participant role to react to Bob's chat message in the channel
const carolReaction = yield TestDataGenerator.generateRecordsWrite({
author: bob,
protocol: protocolDefinition.protocol,
protocolRole: 'community/gatedChannel/participant',
protocolPath: 'community/gatedChannel/message/reaction',
parentContextId: bobChatMessage.message.contextId
});
const carolReactionReply = yield dwn.processMessage(alice.did, carolReaction.message, { dataStream: carolReaction.dataStream });
expect(carolReactionReply.status.code).to.equal(202);
// 13. Mallory CANNOT invoke the participant role (which she is not given) to read the chat messages in the channel
const malloryQuery = yield RecordsQuery.create({
signer: Jws.createSigner(mallory),
protocolRole: 'community/gatedChannel/participant',
filter: {
protocol: protocolDefinition.protocol,
protocolPath: 'community/gatedChannel/message',
contextId: channel1Record.message.contextId
}
});
const malloryQueryReply = yield dwn.processMessage(alice.did, malloryQuery.message);
expect(malloryQueryReply.status.code).to.equal(401);
expect(malloryQueryReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound);
// 14. Mallory CANNOT invoke the participant role (which she is not given) to write a chat message in the channel
const malloryChatMessage = yield TestDataGenerator.generateRecordsWrite({
author: mallory,
protocol: protocolDefinition.protocol,
protocolRole: 'community/gatedChannel/participant',
protocolPath: 'community/gatedChannel/message',
parentContextId: channel1Record.message.contextId
});
const malloryChatMessageReply = yield dwn.processMessage(alice.did, malloryChatMessage.message, { dataStream: malloryChatMessage.dataStream });
expect(malloryChatMessageReply.status.code).to.equal(401);
expect(malloryChatMessageReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound);
}));
});
}
//# sourceMappingURL=nested-roles.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,963 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import freeForAll from '../vectors/protocol-definitions/free-for-all.json' assert { type: 'json' };
import threadProtocol from '../vectors/protocol-definitions/thread-role.json' assert { type: 'json' };
import { TestDataGenerator } from '../utils/test-data-generator.js';
import { TestEventStream } from '../test-event-stream.js';
import { TestStores } from '../test-stores.js';
import { Time } from '../../src/utils/time.js';
import { DidKey, UniversalResolver } from '@web5/dids';
import { Dwn, DwnConstant, DwnInterfaceName, DwnMethodName, Message } from '../../src/index.js';
import { expect } from 'chai';
export function testSubscriptionScenarios() {
describe('subscriptions', () => {
let didResolver;
let messageStore;
let dataStore;
let eventLog;
let eventStream;
let dwn;
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(this, void 0, void 0, function* () {
didResolver = new UniversalResolver({ didResolvers: [DidKey] });
const stores = TestStores.get();
messageStore = stores.messageStore;
dataStore = stores.dataStore;
eventLog = stores.eventLog;
eventStream = TestEventStream.get();
dwn = yield Dwn.create({ didResolver, messageStore, dataStore, eventLog, eventStream });
}));
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
// clean up before each test rather than after so that a test does not depend on other tests to do the clean up
yield messageStore.clear();
yield dataStore.clear();
yield eventLog.clear();
}));
after(() => __awaiter(this, void 0, void 0, function* () {
yield dwn.close();
}));
describe('events subscribe', () => {
it('all events', () => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// create a handler that adds the messageCid of each message to an array.
const messageCids = [];
const handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
// subscribe to all messages
const eventsSubscription = yield TestDataGenerator.generateEventsSubscribe({ author: alice });
const eventsSubscriptionReply = yield dwn.processMessage(alice.did, eventsSubscription.message, { subscriptionHandler: handler });
expect(eventsSubscriptionReply.status.code).to.equal(200);
expect((_a = eventsSubscriptionReply.subscription) === null || _a === void 0 ? void 0 : _a.id).to.equal(yield Message.getCid(eventsSubscription.message));
// generate various messages
const write1 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const write1MessageCid = yield Message.getCid(write1.message);
const write1Reply = yield dwn.processMessage(alice.did, write1.message, { dataStream: write1.dataStream });
expect(write1Reply.status.code).to.equal(202);
const protocol1 = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
const protocol1MessageCid = yield Message.getCid(protocol1.message);
const protocol1Reply = yield dwn.processMessage(alice.did, protocol1.message);
expect(protocol1Reply.status.code).to.equal(202);
const deleteWrite1 = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write1.message.recordId });
const delete1MessageCid = yield Message.getCid(deleteWrite1.message);
const deleteWrite1Reply = yield dwn.processMessage(alice.did, deleteWrite1.message);
expect(deleteWrite1Reply.status.code).to.equal(202);
// unregister the handler
yield ((_b = eventsSubscriptionReply.subscription) === null || _b === void 0 ? void 0 : _b.close());
// create a message after
const write2 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const write2Reply = yield dwn.processMessage(alice.did, write2.message, { dataStream: write2.dataStream });
expect(write2Reply.status.code).to.equal(202);
yield Time.minimalSleep();
// test the messageCids array for the appropriate messages
expect(messageCids.length).to.equal(3);
expect(messageCids).to.eql([write1MessageCid, protocol1MessageCid, delete1MessageCid]);
}));
it('filters by interface type', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice subscribes to 2 different message interfaces (Records, Grants)
// alice creates (2) messages, (RecordsWrite and ProtocolsConfigure)
// alice checks that each handler emitted the appropriate message
// alice deletes the record, and revokes the grant
// alice checks that the Records and Protocols handlers emitted the appropriate message
const alice = yield TestDataGenerator.generateDidKeyPersona();
// subscribe to records
const recordsInterfaceSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ interface: DwnInterfaceName.Records }]
});
const recordsMessageCids = [];
const recordsSubscribeHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
recordsMessageCids.push(messageCid);
});
const recordsInterfaceSubscriptionReply = yield dwn.processMessage(alice.did, recordsInterfaceSubscription.message, { subscriptionHandler: recordsSubscribeHandler });
expect(recordsInterfaceSubscriptionReply.status.code).to.equal(200);
expect(recordsInterfaceSubscriptionReply.subscription).to.exist;
// subscribe to protocols
const protocolsInterfaceSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ interface: DwnInterfaceName.Protocols }]
});
const protocolsMessageCids = [];
const protocolsSubscribeHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
protocolsMessageCids.push(messageCid);
});
const protocolsInterfaceSubscriptionReply = yield dwn.processMessage(alice.did, protocolsInterfaceSubscription.message, { subscriptionHandler: protocolsSubscribeHandler });
expect(protocolsInterfaceSubscriptionReply.status.code).to.equal(200);
expect(protocolsInterfaceSubscriptionReply.subscription).to.exist;
// create one of each message types
const record = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const protocol = yield TestDataGenerator.generateProtocolsConfigure({ author: alice });
// insert data
const recordReply = yield dwn.processMessage(alice.did, record.message, { dataStream: record.dataStream });
const protocolReply = yield dwn.processMessage(alice.did, protocol.message);
expect(recordReply.status.code).to.equal(202, 'RecordsWrite');
expect(protocolReply.status.code).to.equal(202, 'ProtocolConfigure');
// check record message
expect(recordsMessageCids.length).to.equal(1);
expect(recordsMessageCids).to.have.members([yield Message.getCid(record.message)]);
// check protocols message
expect(protocolsMessageCids.length).to.equal(1);
expect(protocolsMessageCids).to.have.members([yield Message.getCid(protocol.message)]);
// insert additional data to query beyond a cursor
const recordDelete = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: record.message.recordId });
const recordDeleteReply = yield dwn.processMessage(alice.did, recordDelete.message);
expect(recordDeleteReply.status.code).to.equal(202, 'RecordsDelete');
// check record messages to include the delete message
expect(recordsMessageCids.length).to.equal(2);
expect(recordsMessageCids).to.include.members([yield Message.getCid(recordDelete.message)]);
// protocols remains unchanged
expect(protocolsMessageCids.length).to.equal(1);
}));
it('filters by method type', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice creates a variety of Messages (RecordsWrite, RecordsDelete, ProtocolConfigure)
// alice queries for only RecordsWrite messages
// alice creates more messages to query beyond a cursor
const alice = yield TestDataGenerator.generateDidKeyPersona();
// subscribe to records write
const recordsWriteSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ interface: DwnInterfaceName.Records, method: DwnMethodName.Write }]
});
const recordsMessageCids = [];
const recordsSubscribeHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
recordsMessageCids.push(messageCid);
});
const recordsWriteSubscriptionReply = yield dwn.processMessage(alice.did, recordsWriteSubscription.message, { subscriptionHandler: recordsSubscribeHandler });
expect(recordsWriteSubscriptionReply.status.code).to.equal(200);
expect(recordsWriteSubscriptionReply.subscription).to.exist;
// create one of each message types
const record = yield TestDataGenerator.generateRecordsWrite({ author: alice });
// insert data
const recordReply = yield dwn.processMessage(alice.did, record.message, { dataStream: record.dataStream });
expect(recordReply.status.code).to.equal(202, 'RecordsWrite');
// sleep to make sure event was processed and added to array asynchronously
yield Time.minimalSleep();
// check record message
expect(recordsMessageCids.length).to.equal(1);
expect(recordsMessageCids).to.have.members([yield Message.getCid(record.message)]);
// delete the message
const recordDelete = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: record.message.recordId });
const recordDeleteReply = yield dwn.processMessage(alice.did, recordDelete.message);
expect(recordDeleteReply.status.code).to.equal(202, 'RecordsDelete');
// check record messages remain unchanged and do not include the delete since we only subscribe to writes
expect(recordsMessageCids.length).to.equal(1);
}));
it('filters by a protocol across different message types', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice creates (3) different message types all related to "proto1" (Configure, RecordsWrite, RecordsDelete)
// alice creates (3) different message types all related to "proto2" (Configure, RecordsWrite, RecordsDelete)
// when subscribing for a specific protocol, only Messages related to it should be emitted.
const alice = yield TestDataGenerator.generateDidKeyPersona();
const proto1Messages = [];
const proto1Handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
proto1Messages.push(yield Message.getCid(message));
});
const proto1Subscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ protocol: 'http://proto1' }]
});
const proto1SubscriptionReply = yield dwn.processMessage(alice.did, proto1Subscription.message, {
subscriptionHandler: proto1Handler
});
expect(proto1SubscriptionReply.status.code).to.equal(200);
expect(proto1SubscriptionReply.subscription).to.exist;
const proto2Messages = [];
const proto2Handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
proto2Messages.push(yield Message.getCid(message));
});
const proto2Subscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ protocol: 'http://proto2' }]
});
const proto2SubscriptionReply = yield dwn.processMessage(alice.did, proto2Subscription.message, {
subscriptionHandler: proto2Handler
});
expect(proto2SubscriptionReply.status.code).to.equal(200);
expect(proto2SubscriptionReply.subscription).to.exist;
// create a proto1
const protoConf1 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign(Object.assign({}, freeForAll), { protocol: 'http://proto1' })
});
const postProperties = {
protocolPath: 'post',
schema: freeForAll.types.post.schema,
dataFormat: freeForAll.types.post.dataFormats[0],
};
const proto1 = protoConf1.message.descriptor.definition.protocol;
const protoConf1Response = yield dwn.processMessage(alice.did, protoConf1.message);
expect(protoConf1Response.status.code).equals(202);
// create a proto2
const protoConf2 = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign(Object.assign({}, freeForAll), { protocol: 'http://proto2' })
});
const proto2 = protoConf2.message.descriptor.definition.protocol;
const protoConf2Response = yield dwn.processMessage(alice.did, protoConf2.message);
expect(protoConf2Response.status.code).equals(202);
// create a record for proto1
const write1proto1 = yield TestDataGenerator.generateRecordsWrite(Object.assign({ author: alice, protocol: proto1 }, postProperties));
const write1Response = yield dwn.processMessage(alice.did, write1proto1.message, { dataStream: write1proto1.dataStream });
expect(write1Response.status.code).equals(202);
// create a record for proto2
const write1proto2 = yield TestDataGenerator.generateRecordsWrite(Object.assign({ author: alice, protocol: proto2 }, postProperties));
const write1Proto2Response = yield dwn.processMessage(alice.did, write1proto2.message, { dataStream: write1proto2.dataStream });
expect(write1Proto2Response.status.code).equals(202);
// check for proto1 messages
expect(proto1Messages.length).to.equal(2);
expect(proto1Messages).to.have.members([yield Message.getCid(protoConf1.message), yield Message.getCid(write1proto1.message)]);
// check for proto2 messages
expect(proto2Messages.length).to.equal(2);
expect(proto2Messages).to.have.members([yield Message.getCid(protoConf2.message), yield Message.getCid(write1proto2.message)]);
// delete proto1 message
const deleteProto1Message = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write1proto1.message.recordId });
const deleteProto1MessageReply = yield dwn.processMessage(alice.did, deleteProto1Message.message);
expect(deleteProto1MessageReply.status.code).to.equal(202);
// delete proto2 message
const deleteProto2Message = yield TestDataGenerator.generateRecordsDelete({ author: alice, recordId: write1proto2.message.recordId });
const deleteProto2MessageReply = yield dwn.processMessage(alice.did, deleteProto2Message.message);
expect(deleteProto2MessageReply.status.code).to.equal(202);
// check for the delete in proto1 messages
expect(proto1Messages.length).to.equal(3);
expect(proto1Messages).to.include.members([yield Message.getCid(deleteProto1Message.message)]);
// check for the delete in proto2 messages
expect(proto2Messages.length).to.equal(3);
expect(proto2Messages).to.include.members([yield Message.getCid(deleteProto2Message.message)]);
}));
it('filters by protocol & parentId across multiple protocolPaths', () => __awaiter(this, void 0, void 0, function* () {
// scenario: subscribe to multiple protocolPaths for a given protocol and parentId
// alice installs a protocol and creates a thread
// alice subscribes to update to that thread, it's participant as well as thread chats
// alice adds bob and carol as participants to the thread
// alice, bob, and carol all create messages
// alice deletes carol participant message
// alice checks that the correct messages were omitted
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// create protocol
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, threadProtocol)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
// alice creates thread
const thread = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread'
});
const threadReply = yield dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream });
expect(threadReply.status.code).to.equal(202);
// subscribe to this thread's events
const messages = [];
const subscriptionHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
messages.push(yield Message.getCid(message));
});
const threadSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [
{ protocol: protocol, protocolPath: 'thread', parentId: thread.message.recordId },
{ protocol: protocol, protocolPath: 'thread/participant', parentId: thread.message.recordId },
{ protocol: protocol, protocolPath: 'thread/chat', parentId: thread.message.recordId } // chat updates
],
});
const threadSubscriptionReply = yield dwn.processMessage(alice.did, threadSubscription.message, {
subscriptionHandler
});
expect(threadSubscriptionReply.status.code).to.equal(200);
expect(threadSubscriptionReply.subscription).to.exist;
// add bob as participant
const bobParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const bobParticipantReply = yield dwn.processMessage(alice.did, bobParticipant.message, { dataStream: bobParticipant.dataStream });
expect(bobParticipantReply.status.code).to.equal(202);
// add carol as participant
const carolParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const carolParticipantReply = yield dwn.processMessage(alice.did, carolParticipant.message, { dataStream: carolParticipant.dataStream });
expect(carolParticipantReply.status.code).to.equal(202);
// add another thread as a control, will not show up in handled events
const additionalThread = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread'
});
const additionalThreadReply = yield dwn.processMessage(alice.did, additionalThread.message, { dataStream: additionalThread.dataStream });
expect(additionalThreadReply.status.code).to.equal(202);
// sleep to allow all messages to be processed by the handler message
yield Time.minimalSleep();
expect(messages.length).to.equal(2);
expect(messages).to.have.members([
yield Message.getCid(bobParticipant.message),
yield Message.getCid(carolParticipant.message),
]);
// add a message to protocol1
const message1 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message1Reply = yield dwn.processMessage(alice.did, message1.message, { dataStream: message1.dataStream });
expect(message1Reply.status.code).to.equal(202);
const message2 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message2Reply = yield dwn.processMessage(alice.did, message2.message, { dataStream: message2.dataStream });
expect(message2Reply.status.code).to.equal(202);
const message3 = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message3Reply = yield dwn.processMessage(alice.did, message3.message, { dataStream: message3.dataStream });
expect(message3Reply.status.code).to.equal(202);
// sleep in order to allow messages to process and check for the added messages
yield Time.minimalSleep();
expect(messages.length).to.equal(5);
expect(messages).to.include.members([
yield Message.getCid(message1.message),
yield Message.getCid(message2.message),
yield Message.getCid(message3.message),
]);
// delete carol participant
const deleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: carolParticipant.message.recordId
});
const deleteCarolReply = yield dwn.processMessage(alice.did, deleteCarol.message);
expect(deleteCarolReply.status.code).to.equal(202);
// sleep in order to allow messages to process and check for the delete message
yield Time.minimalSleep();
expect(messages.length).to.equal(6);
expect(messages).to.include.members([
yield Message.getCid(deleteCarol.message)
]);
}));
it('filters by schema', () => __awaiter(this, void 0, void 0, function* () {
var _c, _d, _e;
const alice = yield TestDataGenerator.generateDidKeyPersona();
// we will add messageCids to these arrays as they are received by their handler to check against later
const schema1Messages = [];
const schema2Messages = [];
// we add a handler to the subscription and add the messageCid to the appropriate array
const schema1Handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
schema1Messages.push(messageCid);
});
// subscribe to schema1 messages
const schema1Subscription = yield TestDataGenerator.generateEventsSubscribe({ author: alice, filters: [{ schema: 'http://schema1' }] });
const schema1SubscriptionReply = yield dwn.processMessage(alice.did, schema1Subscription.message, { subscriptionHandler: schema1Handler });
expect(schema1SubscriptionReply.status.code).to.equal(200);
expect((_c = schema1SubscriptionReply.subscription) === null || _c === void 0 ? void 0 : _c.id).to.equal(yield Message.getCid(schema1Subscription.message));
// we add a handler to the subscription and add the messageCid to the appropriate array
const schema2Handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
schema2Messages.push(messageCid);
});
// subscribe to schema2 messages
const schema2Subscription = yield TestDataGenerator.generateEventsSubscribe({ author: alice, filters: [{ schema: 'http://schema2' }] });
const schema2SubscriptionReply = yield dwn.processMessage(alice.did, schema2Subscription.message, { subscriptionHandler: schema2Handler });
expect(schema2SubscriptionReply.status.code).to.equal(200);
expect((_d = schema2SubscriptionReply.subscription) === null || _d === void 0 ? void 0 : _d.id).to.equal(yield Message.getCid(schema2Subscription.message));
// create some random record, will not show up in records subscription
const write1Random = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const write1RandomResponse = yield dwn.processMessage(alice.did, write1Random.message, { dataStream: write1Random.dataStream });
expect(write1RandomResponse.status.code).to.equal(202);
// create a record for schema1
const write1schema1 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1' });
const write1Response = yield dwn.processMessage(alice.did, write1schema1.message, { dataStream: write1schema1.dataStream });
expect(write1Response.status.code).equals(202);
// create a record for schema2
const write1schema2 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema2' });
const write1Proto2Response = yield dwn.processMessage(alice.did, write1schema2.message, { dataStream: write1schema2.dataStream });
expect(write1Proto2Response.status.code).equals(202);
expect(schema1Messages.length).to.equal(1, 'schema1');
expect(schema1Messages).to.include(yield Message.getCid(write1schema1.message));
expect(schema2Messages.length).to.equal(1, 'schema2');
expect(schema2Messages).to.include(yield Message.getCid(write1schema2.message));
// remove listener for schema1
(_e = schema1SubscriptionReply.subscription) === null || _e === void 0 ? void 0 : _e.close();
// create another record for schema1
const write2proto1 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1' });
const write2Response = yield dwn.processMessage(alice.did, write2proto1.message, { dataStream: write2proto1.dataStream });
expect(write2Response.status.code).equals(202);
// create another record for schema2
const write2schema2 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema2' });
const write2Schema2Response = yield dwn.processMessage(alice.did, write2schema2.message, { dataStream: write2schema2.dataStream });
expect(write2Schema2Response.status.code).equals(202);
// schema1 messages from handler do not change.
expect(schema1Messages.length).to.equal(1, 'schema1 after close()');
expect(schema1Messages).to.include(yield Message.getCid(write1schema1.message));
// schema2 messages from handler have the new message.
expect(schema2Messages.length).to.equal(2, 'schema2 after close()');
expect(schema2Messages).to.have.members([yield Message.getCid(write1schema2.message), yield Message.getCid(write2schema2.message)]);
}));
it('filters by recordId', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const write = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const write1Reply = yield dwn.processMessage(alice.did, write.message, { dataStream: write.dataStream });
expect(write1Reply.status.code).to.equal(202);
// create a subscription and capture the messages associated with the record
const messages = [];
const subscriptionHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
messages.push(yield Message.getCid(message));
});
const recordIdSubscribe = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ recordId: write.message.recordId }]
});
const recordIdSubscribeReply = yield dwn.processMessage(alice.did, recordIdSubscribe.message, {
subscriptionHandler
});
expect(recordIdSubscribeReply.status.code).to.equal(200);
// a write as a control, will not show up in subscription
const controlWrite = yield TestDataGenerator.generateRecordsWrite({
author: alice,
schema: 'schema1'
});
const write2Reply = yield dwn.processMessage(alice.did, controlWrite.message, { dataStream: controlWrite.dataStream });
expect(write2Reply.status.code).to.equal(202);
// update record
const update = yield TestDataGenerator.generateFromRecordsWrite({
author: alice,
existingWrite: write.recordsWrite,
});
const updateReply = yield dwn.processMessage(alice.did, update.message, { dataStream: update.dataStream });
expect(updateReply.status.code).to.equal(202);
// sleep to allow all messages to be processed by the handler message
yield Time.minimalSleep();
expect(messages.length).to.equal(1);
expect(messages).to.have.members([yield Message.getCid(update.message)]);
const deleteRecord = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: write.message.recordId,
});
const deleteRecordReply = yield dwn.processMessage(alice.did, deleteRecord.message);
expect(deleteRecordReply.status.code).to.equal(202);
// sleep to allow all messages to be processed by the handler message
yield Time.minimalSleep();
expect(messages.length).to.equal(2);
expect(messages).to.include.members([yield Message.getCid(deleteRecord.message)]);
}));
it('filters by recipient', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
const receivedMessages = [];
const handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
receivedMessages.push(messageCid);
});
const recipientSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ recipient: alice.did }]
});
const authorQueryReply = yield dwn.processMessage(alice.did, recipientSubscription.message, { subscriptionHandler: handler });
expect(authorQueryReply.status.code).to.equal(200);
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, freeForAll)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
const postProperties = {
protocol: protocol,
protocolPath: 'post',
schema: freeForAll.types.post.schema,
dataFormat: freeForAll.types.post.dataFormats[0],
};
const messageFromBobToAlice = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: bob, recipient: alice.did }));
const messageFromBobToAliceReply = yield dwn.processMessage(alice.did, messageFromBobToAlice.message, { dataStream: messageFromBobToAlice.dataStream });
expect(messageFromBobToAliceReply.status.code).to.equal(202);
const messageFromCarolToAlice = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: carol, recipient: alice.did }));
const messageFromCarolToAliceReply = yield dwn.processMessage(alice.did, messageFromCarolToAlice.message, { dataStream: messageFromCarolToAlice.dataStream });
expect(messageFromCarolToAliceReply.status.code).to.equal(202);
const messageFromAliceToBob = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: bob.did }));
const messageFromAliceToBobReply = yield dwn.processMessage(alice.did, messageFromAliceToBob.message, { dataStream: messageFromAliceToBob.dataStream });
expect(messageFromAliceToBobReply.status.code).to.equal(202);
const messageFromAliceToCarol = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: carol.did }));
const messageFromAliceToCarolReply = yield dwn.processMessage(alice.did, messageFromAliceToCarol.message, { dataStream: messageFromAliceToCarol.dataStream });
expect(messageFromAliceToCarolReply.status.code).to.equal(202);
expect(receivedMessages).to.have.members([
yield Message.getCid(messageFromBobToAlice.message),
yield Message.getCid(messageFromCarolToAlice.message)
]);
// add another message
const messageFromAliceToBob2 = yield TestDataGenerator.generateRecordsWrite(Object.assign(Object.assign({}, postProperties), { author: alice, recipient: bob.did }));
const messageFromAliceToBob2Reply = yield dwn.processMessage(alice.did, messageFromAliceToBob2.message, { dataStream: messageFromAliceToBob2.dataStream });
expect(messageFromAliceToBob2Reply.status.code).to.equal(202);
expect(receivedMessages).to.not.include.members([yield Message.getCid(messageFromAliceToBob2.message)]);
}));
it('filters by dataFormat', () => __awaiter(this, void 0, void 0, function* () {
// Scenario: Alice subscribes events relating to `image/jpeg` after which a number of record messages of various data formats are processed
// 1. Alice subscribes for `image/jpeg` records
// 2. Alice creates 3 files, one of them `image/jpeg`
// 3. Alice receives the one `image/jpeg` message
// 4. Alice adds another image
// 5. Alice receives the other `image/jpeg` message
const alice = yield TestDataGenerator.generateDidKeyPersona();
const imageMessages = [];
const imageHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
imageMessages.push(yield Message.getCid(message));
});
// alice subscribes to image/jpeg changes
const imageSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ dataFormat: 'image/jpeg' }]
});
const imageSubscriptionReply = yield dwn.processMessage(alice.did, imageSubscription.message, {
subscriptionHandler: imageHandler
});
expect(imageSubscriptionReply.status.code).to.equal(200);
// write a text file
const textFile = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'application/text'
});
const textFileReply = yield dwn.processMessage(alice.did, textFile.message, { dataStream: textFile.dataStream });
expect(textFileReply.status.code).to.equal(202);
// write an application/json file
const jsonData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'application/json'
});
const jsonDataReply = yield dwn.processMessage(alice.did, jsonData.message, { dataStream: jsonData.dataStream });
expect(jsonDataReply.status.code).to.equal(202);
// write an image
const imageData = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'image/jpeg'
});
const imageDataReply = yield dwn.processMessage(alice.did, imageData.message, { dataStream: imageData.dataStream });
expect(imageDataReply.status.code).to.equal(202);
// wait for messages to emit and handler to process
yield Time.minimalSleep();
expect(imageMessages.length).to.equal(1);
expect(imageMessages).to.have.members([yield Message.getCid(imageData.message)]);
// add another image
const imageData2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
dataFormat: 'image/jpeg'
});
const imageData2Reply = yield dwn.processMessage(alice.did, imageData2.message, { dataStream: imageData2.dataStream });
expect(imageData2Reply.status.code).to.equal(202);
// wait for messages to emit and handler to process
yield Time.minimalSleep();
expect(imageMessages.length).to.equal(2);
// check that the new image and the delete messages were emitted
expect(imageMessages).to.include.members([
yield Message.getCid(imageData2.message)
]);
}));
;
it('filters by dataSize', () => __awaiter(this, void 0, void 0, function* () {
// scenario:
// alice subscribes to messages with data size under a threshold
// alice inserts both small and large data
const alice = yield TestDataGenerator.generateDidKeyPersona();
const smallMessages = [];
const subscriptionHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
smallMessages.push(yield Message.getCid(message));
});
const smallMessageSubscription = yield TestDataGenerator.generateEventsSubscribe({
author: alice,
filters: [{ dataSize: { lte: DwnConstant.maxDataSizeAllowedToBeEncoded } }]
});
const smallMessageSubscriptionReply = yield dwn.processMessage(alice.did, smallMessageSubscription.message, {
subscriptionHandler,
});
expect(smallMessageSubscriptionReply.status.code).to.equal(200);
// add a small data size record
const smallSize1 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const smallSize1Reply = yield dwn.processMessage(alice.did, smallSize1.message, { dataStream: smallSize1.dataStream });
expect(smallSize1Reply.status.code).to.equal(202);
// add a large data size record
const largeSize = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data: TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded + 1)
});
const largeSizeReply = yield dwn.processMessage(alice.did, largeSize.message, { dataStream: largeSize.dataStream });
expect(largeSizeReply.status.code).to.equal(202);
// wait for message handler to process and check results
yield Time.minimalSleep();
expect(smallMessages.length).to.equal(1);
expect(smallMessages).to.have.members([yield Message.getCid(smallSize1.message)]);
// add another small record
const smallSize2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
});
const smallSize2Reply = yield dwn.processMessage(alice.did, smallSize2.message, { dataStream: smallSize2.dataStream });
expect(smallSize2Reply.status.code).to.equal(202);
// add another large record
const largeSize2 = yield TestDataGenerator.generateRecordsWrite({
author: alice,
data: TestDataGenerator.randomBytes(DwnConstant.maxDataSizeAllowedToBeEncoded + 1)
});
const largeSize2Reply = yield dwn.processMessage(alice.did, largeSize2.message, { dataStream: largeSize2.dataStream });
expect(largeSize2Reply.status.code).to.equal(202);
// wait for message handler to process and check results
yield Time.minimalSleep();
expect(smallMessages.length).to.equal(2);
expect(smallMessages).to.include.members([yield Message.getCid(smallSize2.message)]);
}));
it('does not emit events after subscription is closed', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
// messageCids of events
const messageCids = [];
const handler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
const messageCid = yield Message.getCid(message);
messageCids.push(messageCid);
});
// subscribe to all events
const eventsSubscription = yield TestDataGenerator.generateEventsSubscribe({ author: alice });
const eventsSubscriptionReply = yield dwn.processMessage(alice.did, eventsSubscription.message, { subscriptionHandler: handler });
expect(eventsSubscriptionReply.status.code).to.equal(200);
expect(messageCids.length).to.equal(0); // no events exist yet
const record1 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const record1Reply = yield dwn.processMessage(alice.did, record1.message, { dataStream: record1.dataStream });
expect(record1Reply.status.code).to.equal(202);
const record1MessageCid = yield Message.getCid(record1.message);
expect(messageCids.length).to.equal(1); // message exists
expect(messageCids).to.eql([record1MessageCid]);
// unsubscribe, this should be used as clean up.
yield eventsSubscriptionReply.subscription.close();
// write another message.
const record2 = yield TestDataGenerator.generateRecordsWrite({ author: alice });
const record2Reply = yield dwn.processMessage(alice.did, record2.message, { dataStream: record2.dataStream });
expect(record2Reply.status.code).to.equal(202);
// sleep to make sure events have some time to emit.
yield Time.minimalSleep();
expect(messageCids.length).to.equal(1); // same as before
expect(messageCids).to.eql([record1MessageCid]);
}));
});
describe('records subscribe', () => {
it('allows for anonymous subscriptions to published records', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const messages = [];
const subscriptionHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
messages.push(yield Message.getCid(message));
});
const anonymousSubscription = yield TestDataGenerator.generateRecordsSubscribe({
anonymous: true,
filter: { schema: 'http://schema1' }
});
const anonymousSubscriptionReply = yield dwn.processMessage(alice.did, anonymousSubscription.message, {
subscriptionHandler
});
expect(anonymousSubscriptionReply.status.code).to.equal(200);
expect(anonymousSubscriptionReply.subscription).to.exist;
const write1 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1', published: true });
const write1Reply = yield dwn.processMessage(alice.did, write1.message, { dataStream: write1.dataStream });
expect(write1Reply.status.code).to.equal(202);
const write2 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1', published: true });
const write2Reply = yield dwn.processMessage(alice.did, write2.message, { dataStream: write2.dataStream });
expect(write2Reply.status.code).to.equal(202);
// will not be emitted as it is not explicitly published
const writeNotPublished = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1' });
const writeNotPublishedReply = yield dwn.processMessage(alice.did, writeNotPublished.message, { dataStream: writeNotPublished.dataStream });
expect(writeNotPublishedReply.status.code).to.equal(202);
// await for handler to receive and process the message
yield Time.minimalSleep();
expect(messages.length).to.equal(2);
expect(messages).to.have.members([
yield Message.getCid(write1.message),
yield Message.getCid(write2.message),
]);
}));
it('allows authorized subscriptions to records intended for a recipient', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// bob subscribes to any messages he's authorized to see
const bobMessages = [];
const bobSubscribeHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
bobMessages.push(yield Message.getCid(message));
});
const bobSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: bob,
filter: { schema: 'http://schema1' }
});
const bobSubscribeReply = yield dwn.processMessage(alice.did, bobSubscribe.message, {
subscriptionHandler: bobSubscribeHandler
});
expect(bobSubscribeReply.status.code).to.equal(200);
expect(bobSubscribeReply.subscription).to.exist;
// carol subscribes to any messages she's the recipient of.
const carolMessages = [];
const carolSubscribeHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message } = event;
carolMessages.push(yield Message.getCid(message));
});
const carolSubscribe = yield TestDataGenerator.generateRecordsSubscribe({
author: carol,
filter: { schema: 'http://schema1', recipient: carol.did }
});
const carolSubscribeReply = yield dwn.processMessage(alice.did, carolSubscribe.message, {
subscriptionHandler: carolSubscribeHandler
});
expect(carolSubscribeReply.status.code).to.equal(200);
expect(carolSubscribeReply.subscription).to.exist;
const write1 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1', recipient: bob.did });
const write1Reply = yield dwn.processMessage(alice.did, write1.message, { dataStream: write1.dataStream });
expect(write1Reply.status.code).to.equal(202);
const write2 = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1', recipient: bob.did });
const write2Reply = yield dwn.processMessage(alice.did, write2.message, { dataStream: write2.dataStream });
expect(write2Reply.status.code).to.equal(202);
// message for carol only
const writeForCarol = yield TestDataGenerator.generateRecordsWrite({ author: alice, schema: 'http://schema1', recipient: carol.did });
const writeForCarolReply = yield dwn.processMessage(alice.did, writeForCarol.message, { dataStream: writeForCarol.dataStream });
expect(writeForCarolReply.status.code).to.equal(202);
// await for handler to receive and process the message
yield Time.minimalSleep();
expect(bobMessages.length).to.equal(2);
expect(bobMessages).to.have.members([
yield Message.getCid(write1.message),
yield Message.getCid(write2.message),
]);
expect(carolMessages.length).to.equal(1);
expect(carolMessages).to.have.members([
yield Message.getCid(writeForCarol.message),
]);
}));
it('filters by protocol & parentId across multiple protocolPaths', () => __awaiter(this, void 0, void 0, function* () {
// scenario: subscribe to multiple protocolPaths for a given protocol and parentId
// alice installs a protocol and creates a thread
// alice subscribes to update to that thread, it's participant as well as thread chats
// alice adds bob and carol as participants to the thread
// alice, bob, and carol all create messages
// alice deletes carol participant message
// alice checks that the correct messages were omitted
const alice = yield TestDataGenerator.generateDidKeyPersona();
const bob = yield TestDataGenerator.generateDidKeyPersona();
const carol = yield TestDataGenerator.generateDidKeyPersona();
// create protocol
const protocolConfigure = yield TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition: Object.assign({}, threadProtocol)
});
const protocolConfigureReply = yield dwn.processMessage(alice.did, protocolConfigure.message);
expect(protocolConfigureReply.status.code).to.equal(202);
const protocol = protocolConfigure.message.descriptor.definition.protocol;
// alice creates thread
const thread = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread'
});
const threadReply = yield dwn.processMessage(alice.did, thread.message, { dataStream: thread.dataStream });
expect(threadReply.status.code).to.equal(202);
// subscribe to this thread's events
const messages = [];
const initialWrites = [];
const subscriptionHandler = (event) => __awaiter(this, void 0, void 0, function* () {
const { message, initialWrite } = event;
if (initialWrite !== undefined) {
initialWrites.push(yield Message.getCid(initialWrite));
}
messages.push(yield Message.getCid(message));
});
const threadSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { protocol: protocol, protocolPath: 'thread', parentId: thread.message.recordId }, // thread updates
});
const threadSubscriptionReply = yield dwn.processMessage(alice.did, threadSubscription.message, {
subscriptionHandler
});
expect(threadSubscriptionReply.status.code).to.equal(200);
expect(threadSubscriptionReply.subscription).to.exist;
const participantSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { protocol: protocol, protocolPath: 'thread/participant', parentId: thread.message.recordId }, // participant updates
});
const participantSubscriptionReply = yield dwn.processMessage(alice.did, participantSubscription.message, {
subscriptionHandler
});
expect(participantSubscriptionReply.status.code).to.equal(200);
expect(participantSubscriptionReply.subscription).to.exist;
const chatSubscription = yield TestDataGenerator.generateRecordsSubscribe({
author: alice,
filter: { protocol: protocol, protocolPath: 'thread/chat', parentId: thread.message.recordId } // chat updates
});
const chatSubscriptionReply = yield dwn.processMessage(alice.did, chatSubscription.message, {
subscriptionHandler
});
expect(chatSubscriptionReply.status.code).to.equal(200);
expect(chatSubscriptionReply.subscription).to.exist;
// add bob as participant
const bobParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: bob.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const bobParticipantReply = yield dwn.processMessage(alice.did, bobParticipant.message, { dataStream: bobParticipant.dataStream });
expect(bobParticipantReply.status.code).to.equal(202);
// add carol as participant
const carolParticipant = yield TestDataGenerator.generateRecordsWrite({
author: alice,
recipient: carol.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/participant'
});
const carolParticipantReply = yield dwn.processMessage(alice.did, carolParticipant.message, { dataStream: carolParticipant.dataStream });
expect(carolParticipantReply.status.code).to.equal(202);
// add another thread as a control, will not show up in handled events
const additionalThread = yield TestDataGenerator.generateRecordsWrite({
author: alice,
protocol: protocol,
protocolPath: 'thread'
});
const additionalThreadReply = yield dwn.processMessage(alice.did, additionalThread.message, { dataStream: additionalThread.dataStream });
expect(additionalThreadReply.status.code).to.equal(202);
// sleep to allow all messages to be processed by the handler message
yield Time.minimalSleep();
expect(messages.length).to.equal(2);
expect(messages).to.have.members([
yield Message.getCid(bobParticipant.message),
yield Message.getCid(carolParticipant.message),
]);
// add a message to protocol1
const message1 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message1Reply = yield dwn.processMessage(alice.did, message1.message, { dataStream: message1.dataStream });
expect(message1Reply.status.code).to.equal(202);
const message2 = yield TestDataGenerator.generateRecordsWrite({
author: bob,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message2Reply = yield dwn.processMessage(alice.did, message2.message, { dataStream: message2.dataStream });
expect(message2Reply.status.code).to.equal(202);
const message3 = yield TestDataGenerator.generateRecordsWrite({
author: carol,
recipient: alice.did,
parentContextId: thread.message.contextId,
protocol: protocol,
protocolPath: 'thread/chat',
protocolRole: 'thread/participant',
});
const message3Reply = yield dwn.processMessage(alice.did, message3.message, { dataStream: message3.dataStream });
expect(message3Reply.status.code).to.equal(202);
// sleep in order to allow messages to process and check for the added messages
yield Time.minimalSleep();
expect(messages.length).to.equal(5);
expect(messages).to.include.members([
yield Message.getCid(message1.message),
yield Message.getCid(message2.message),
yield Message.getCid(message3.message),
]);
// delete carol participant
const deleteCarol = yield TestDataGenerator.generateRecordsDelete({
author: alice,
recordId: carolParticipant.message.recordId
});
const deleteCarolReply = yield dwn.processMessage(alice.did, deleteCarol.message);
expect(deleteCarolReply.status.code).to.equal(202);
// sleep in order to allow messages to process and check for the delete message
yield Time.minimalSleep();
expect(messages.length).to.equal(6);
expect(messages).to.include.members([
yield Message.getCid(deleteCarol.message)
]);
// check the initial write was included with the delete
expect(initialWrites).to.include.members([
yield Message.getCid(carolParticipant.message)
]);
}));
});
});
}
//# sourceMappingURL=subscriptions.spec.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
import { TestSuite } from './test-suite.js';
describe('Store dependent tests', () => {
TestSuite.runStoreDependentTests();
});
//# sourceMappingURL=store-dependent-tests.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"store-dependent-tests.spec.js","sourceRoot":"","sources":["../../../tests/store-dependent-tests.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,SAAS,CAAC,sBAAsB,EAAE,CAAC;AACrC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,79 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import { BlockstoreMock } from '../../src/store/blockstore-mock.js';
import { DataStream } from '../../src/index.js';
import { importer } from 'ipfs-unixfs-importer';
import { MemoryBlockstore } from 'blockstore-core';
import { TestDataGenerator } from '../utils/test-data-generator.js';
chai.use(chaiAsPromised);
describe('BlockstoreMock', () => {
it('should facilitate the same CID computation as other implementations', () => __awaiter(void 0, void 0, void 0, function* () {
var _a, e_1, _b, _c, _d, e_2, _e, _f;
let dataSizeInBytes = 10;
// iterate through order of magnitude in size until hitting 10MB
// to ensure that the same CID is computed for the same data with the MockBlockstore as with the MemoryBlockstore
while (dataSizeInBytes <= 10000000) {
const dataBytes = TestDataGenerator.randomBytes(dataSizeInBytes);
const dataStreamForMemoryBlockstore = DataStream.fromBytes(dataBytes);
const dataStreamForMockBlockstore = DataStream.fromBytes(dataBytes);
const asyncDataBlocksByMemoryBlockstore = importer([{ content: dataStreamForMemoryBlockstore }], new MemoryBlockstore(), { cidVersion: 1 });
const asyncDataBlocksByMockBlockstore = importer([{ content: dataStreamForMockBlockstore }], new BlockstoreMock(), { cidVersion: 1 });
// NOTE: the last block contains the root CID
let blockByMemoryBlockstore;
try {
for (var _g = true, asyncDataBlocksByMemoryBlockstore_1 = (e_1 = void 0, __asyncValues(asyncDataBlocksByMemoryBlockstore)), asyncDataBlocksByMemoryBlockstore_1_1; asyncDataBlocksByMemoryBlockstore_1_1 = yield asyncDataBlocksByMemoryBlockstore_1.next(), _a = asyncDataBlocksByMemoryBlockstore_1_1.done, !_a; _g = true) {
_c = asyncDataBlocksByMemoryBlockstore_1_1.value;
_g = false;
blockByMemoryBlockstore = _c;
;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_g && !_a && (_b = asyncDataBlocksByMemoryBlockstore_1.return)) yield _b.call(asyncDataBlocksByMemoryBlockstore_1);
}
finally { if (e_1) throw e_1.error; }
}
const dataCidByMemoryBlockstore = blockByMemoryBlockstore ? blockByMemoryBlockstore.cid.toString() : '';
let blockByMockBlockstore;
try {
for (var _h = true, asyncDataBlocksByMockBlockstore_1 = (e_2 = void 0, __asyncValues(asyncDataBlocksByMockBlockstore)), asyncDataBlocksByMockBlockstore_1_1; asyncDataBlocksByMockBlockstore_1_1 = yield asyncDataBlocksByMockBlockstore_1.next(), _d = asyncDataBlocksByMockBlockstore_1_1.done, !_d; _h = true) {
_f = asyncDataBlocksByMockBlockstore_1_1.value;
_h = false;
blockByMockBlockstore = _f;
;
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_h && !_d && (_e = asyncDataBlocksByMockBlockstore_1.return)) yield _e.call(asyncDataBlocksByMockBlockstore_1);
}
finally { if (e_2) throw e_2.error; }
}
const dataCidByMockBlockstore = blockByMockBlockstore ? blockByMockBlockstore.cid.toString() : '';
expect(dataCidByMockBlockstore).to.exist;
expect(dataCidByMockBlockstore.length).to.be.greaterThan(0);
expect(dataCidByMockBlockstore).to.be.equal(dataCidByMemoryBlockstore);
dataSizeInBytes *= 10;
}
}));
});
//# sourceMappingURL=blockstore-mock.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"blockstore-mock.spec.js","sourceRoot":"","sources":["../../../../tests/store/blockstore-mock.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEpE,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAEzB,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,qEAAqE,EAAE,GAAS,EAAE;;QAEnF,IAAI,eAAe,GAAG,EAAE,CAAC;QAEzB,gEAAgE;QAChE,iHAAiH;QACjH,OAAO,eAAe,IAAI,QAAU,EAAE;YACpC,MAAM,SAAS,GAAG,iBAAiB,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC;YACjE,MAAM,6BAA6B,GAAG,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACtE,MAAM,2BAA2B,GAAG,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAEpE,MAAM,iCAAiC,GAAG,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC,EAAE,IAAI,gBAAgB,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;YAC5I,MAAM,+BAA+B,GAAG,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC,EAAE,IAAI,cAAc,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;YAEtI,6CAA6C;YAC7C,IAAI,uBAAuB,CAAC;;gBAC5B,KAAsC,eAAA,qDAAA,cAAA,iCAAiC,CAAA,CAAA,uCAAA,6JAAE;oBAAnC,iDAAiC;oBAAjC,WAAiC;oBAA5D,uBAAuB,KAAA,CAAA;oBAAyC,CAAC;iBAAE;;;;;;;;;YAC9E,MAAM,yBAAyB,GAAG,uBAAuB,CAAC,CAAC,CAAC,uBAAuB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAExG,IAAI,qBAAqB,CAAC;;gBAC1B,KAAoC,eAAA,mDAAA,cAAA,+BAA+B,CAAA,CAAA,qCAAA,uJAAE;oBAAjC,+CAA+B;oBAA/B,WAA+B;oBAAxD,qBAAqB,KAAA,CAAA;oBAAuC,CAAC;iBAAE;;;;;;;;;YAC1E,MAAM,uBAAuB,GAAG,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAElG,MAAM,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC;YACzC,MAAM,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC5D,MAAM,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAEvE,eAAe,IAAI,EAAE,CAAC;SACvB;IACH,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,146 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import { ArrayUtility } from '../../src/utils/array.js';
import { Cid } from '../../src/utils/cid.js';
import { DataStoreLevel } from '../../src/store/data-store-level.js';
import { DataStream } from '../../src/index.js';
import { TestDataGenerator } from '../utils/test-data-generator.js';
chai.use(chaiAsPromised);
let store;
describe('DataStoreLevel Test Suite', () => {
before(() => __awaiter(void 0, void 0, void 0, function* () {
store = new DataStoreLevel({ blockstoreLocation: 'TEST-DATASTORE' });
yield store.open();
}));
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
yield store.clear(); // clean up before each test rather than after so that a test does not depend on other tests to do the clean up
}));
after(() => __awaiter(void 0, void 0, void 0, function* () {
yield store.close();
}));
describe('put', function () {
it('should return the correct size of the data stored', () => __awaiter(this, void 0, void 0, function* () {
const tenant = yield TestDataGenerator.randomCborSha256Cid();
const recordId = yield TestDataGenerator.randomCborSha256Cid();
let dataSizeInBytes = 10;
// iterate through order of magnitude in size until hitting 10MB
while (dataSizeInBytes <= 10000000) {
const dataBytes = TestDataGenerator.randomBytes(dataSizeInBytes);
const dataStream = DataStream.fromBytes(dataBytes);
const dataCid = yield Cid.computeDagPbCidFromBytes(dataBytes);
const { dataSize } = yield store.put(tenant, recordId, dataCid, dataStream);
expect(dataSize).to.equal(dataSizeInBytes);
const result = (yield store.get(tenant, recordId, dataCid));
const storedDataBytes = yield DataStream.toBytes(result.dataStream);
expect(storedDataBytes).to.eql(dataBytes);
dataSizeInBytes *= 10;
}
}));
it('should duplicate same data if written to different tenants', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.randomCborSha256Cid();
const bob = yield TestDataGenerator.randomCborSha256Cid();
const dataBytes = TestDataGenerator.randomBytes(100);
const dataCid = yield Cid.computeDagPbCidFromBytes(dataBytes);
// write data to alice's DWN
const aliceDataStream = DataStream.fromBytes(dataBytes);
const aliceRecordId = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(alice, aliceRecordId, dataCid, aliceDataStream);
// write same data to bob's DWN
const bobDataStream = DataStream.fromBytes(dataBytes);
const bobRecordId = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(bob, bobRecordId, dataCid, bobDataStream);
// verify that both alice and bob's blockstore have their own reference to data CID
const blockstoreOfAliceRecord = yield store['getBlockstoreForStoringData'](alice, aliceRecordId, dataCid);
const blockstoreOfBobRecord = yield store['getBlockstoreForStoringData'](bob, bobRecordId, dataCid);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfAliceRecord.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfBobRecord.db.keys())).to.eventually.eql([dataCid]);
}));
});
describe('get', function () {
it('should return `undefined if unable to find the data specified`', () => __awaiter(this, void 0, void 0, function* () {
const tenant = yield TestDataGenerator.randomCborSha256Cid();
const recordId = yield TestDataGenerator.randomCborSha256Cid();
const randomCid = yield TestDataGenerator.randomCborSha256Cid();
const result = yield store.get(tenant, recordId, randomCid);
expect(result).to.be.undefined;
}));
it('should return `undefined` if the dataCid is different than the dataStream`', () => __awaiter(this, void 0, void 0, function* () {
const tenant = yield TestDataGenerator.randomCborSha256Cid();
const recordId = yield TestDataGenerator.randomCborSha256Cid();
const randomCid = yield TestDataGenerator.randomCborSha256Cid();
const dataBytes = TestDataGenerator.randomBytes(10000000);
const dataStream = DataStream.fromBytes(dataBytes);
yield store.put(tenant, recordId, randomCid, dataStream);
const result = yield store.get(tenant, recordId, randomCid);
expect(result).to.be.undefined;
}));
});
describe('delete', function () {
it('should not leave anything behind when deleting the root CID', () => __awaiter(this, void 0, void 0, function* () {
const tenant = yield TestDataGenerator.randomCborSha256Cid();
const recordId = yield TestDataGenerator.randomCborSha256Cid();
const dataBytes = TestDataGenerator.randomBytes(10000000);
const dataStream = DataStream.fromBytes(dataBytes);
const dataCid = yield Cid.computeDagPbCidFromBytes(dataBytes);
yield store.put(tenant, recordId, dataCid, dataStream);
const keysBeforeDelete = yield ArrayUtility.fromAsyncGenerator(store.blockstore.db.keys());
expect(keysBeforeDelete.length).to.equal(40);
yield store.delete(tenant, recordId, dataCid);
const keysAfterDelete = yield ArrayUtility.fromAsyncGenerator(store.blockstore.db.keys());
expect(keysAfterDelete.length).to.equal(0);
}));
it('should only delete data in the sublevel of the corresponding record', () => __awaiter(this, void 0, void 0, function* () {
const alice = yield TestDataGenerator.randomCborSha256Cid();
const bob = yield TestDataGenerator.randomCborSha256Cid();
const dataBytes = TestDataGenerator.randomBytes(100);
const dataCid = yield Cid.computeDagPbCidFromBytes(dataBytes);
// alice writes a records with data
const dataStream1 = DataStream.fromBytes(dataBytes);
const recordId1 = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(alice, recordId1, dataCid, dataStream1);
// alice writes a different record with same data again
const dataStream2 = DataStream.fromBytes(dataBytes);
const recordId2 = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(alice, recordId2, dataCid, dataStream2);
// bob writes a records with same data
const dataStream3 = DataStream.fromBytes(dataBytes);
const recordId3 = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(bob, recordId3, dataCid, dataStream3);
// bob writes a different record with same data again
const dataStream4 = DataStream.fromBytes(dataBytes);
const recordId4 = yield TestDataGenerator.randomCborSha256Cid();
yield store.put(bob, recordId4, dataCid, dataStream4);
// verify that all 4 records have reference to the same data CID
const blockstoreOfRecord1 = yield store['getBlockstoreForStoringData'](alice, recordId1, dataCid);
const blockstoreOfRecord2 = yield store['getBlockstoreForStoringData'](alice, recordId2, dataCid);
const blockstoreOfRecord3 = yield store['getBlockstoreForStoringData'](bob, recordId3, dataCid);
const blockstoreOfRecord4 = yield store['getBlockstoreForStoringData'](bob, recordId4, dataCid);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord1.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord2.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord3.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord4.db.keys())).to.eventually.eql([dataCid]);
// alice deletes one of the two records
yield store.delete(alice, recordId1, dataCid);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord1.db.keys())).to.eventually.eql([]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord2.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord3.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord4.db.keys())).to.eventually.eql([dataCid]);
// alice deletes the other record
yield store.delete(alice, recordId2, dataCid);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord1.db.keys())).to.eventually.eql([]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord2.db.keys())).to.eventually.eql([]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord3.db.keys())).to.eventually.eql([dataCid]);
yield expect(ArrayUtility.fromAsyncGenerator(blockstoreOfRecord4.db.keys())).to.eventually.eql([dataCid]);
}));
});
});
//# sourceMappingURL=data-store-level.spec.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,48 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { createLevelDatabase } from '../../src/store/level-wrapper.js';
import { expect } from 'chai';
import { MessageStoreLevel } from '../../src/store/message-store-level.js';
let messageStore;
describe('MessageStoreLevel Test Suite', () => {
// important to follow the `before` and `after` pattern to initialize and clean the stores in tests
// so that different test suites can reuse the same backend store for testing
before(() => __awaiter(void 0, void 0, void 0, function* () {
messageStore = new MessageStoreLevel({
blockstoreLocation: 'TEST-MESSAGESTORE',
indexLocation: 'TEST-INDEX'
});
yield messageStore.open();
}));
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
yield messageStore.clear(); // clean up before each test rather than after so that a test does not depend on other tests to do the clean up
}));
after(() => __awaiter(void 0, void 0, void 0, function* () {
yield messageStore.close();
}));
describe('createLevelDatabase', function () {
it('should be called if provided', () => __awaiter(this, void 0, void 0, function* () {
// need to close the message store instance first before creating a new one with the same name below
yield messageStore.close();
const locations = new Set;
messageStore = new MessageStoreLevel({
blockstoreLocation: 'TEST-MESSAGESTORE',
indexLocation: 'TEST-INDEX',
createLevelDatabase(location, options) {
locations.add(location);
return createLevelDatabase(location, options);
}
});
yield messageStore.open();
expect(locations).to.eql(new Set(['TEST-MESSAGESTORE', 'TEST-INDEX']));
}));
});
});
//# sourceMappingURL=message-store-level.spec.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message-store-level.spec.js","sourceRoot":"","sources":["../../../../tests/store/message-store-level.spec.ts"],"names":[],"mappings":";;;;;;;;;AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAE3E,IAAI,YAA0B,CAAC;AAE/B,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,mGAAmG;IACnG,6EAA6E;IAC7E,MAAM,CAAC,GAAS,EAAE;QAChB,YAAY,GAAG,IAAI,iBAAiB,CAAC;YACnC,kBAAkB,EAAG,mBAAmB;YACxC,aAAa,EAAQ,YAAY;SAClC,CAAC,CAAC;QACH,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC,CAAA,CAAC,CAAC;IAEH,UAAU,CAAC,GAAS,EAAE;QACpB,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,+GAA+G;IAC7I,CAAC,CAAA,CAAC,CAAC;IAEH,KAAK,CAAC,GAAS,EAAE;QACf,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC,CAAA,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE;QAC9B,EAAE,CAAC,8BAA8B,EAAE,GAAS,EAAE;YAC5C,oGAAoG;YACpG,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;YAE3B,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;YAE1B,YAAY,GAAG,IAAI,iBAAiB,CAAC;gBACnC,kBAAkB,EAAG,mBAAmB;gBACxC,aAAa,EAAQ,YAAY;gBACjC,mBAAmB,CAAI,QAAgB,EAAE,OAAuC;oBAC9E,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oBACxB,OAAO,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChD,CAAC;aACF,CAAC,CAAC;YACH,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;YAE1B,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAE,mBAAmB,EAAE,YAAY,CAAE,CAAC,CAAC,CAAC;QAC3E,CAAC,CAAA,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}

Some files were not shown because too many files have changed in this diff Show More