Skip to content

Commit

Permalink
test query and subscribe ability to read data of records
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Oct 18, 2024
1 parent d7a6958 commit 0a53308
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 120 deletions.
1 change: 1 addition & 0 deletions packages/api/src/dwn-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,7 @@ export class DwnApi {
connectedDid : this.connectedDid,
delegateDid : this.delegateDid,
permissionsApi : this.permissionsApi,
protocolRole : request.message.protocolRole,
request
})
};
Expand Down
267 changes: 150 additions & 117 deletions packages/api/tests/dwn-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DwnApi } from '../src/dwn-api.js';
import { testDwnUrl } from './utils/test-config.js';
import emailProtocolDefinition from './fixtures/protocol-definitions/email.json' assert { type: 'json' };
import photosProtocolDefinition from './fixtures/protocol-definitions/photos.json' assert { type: 'json' };
import notesProtocolDefinition from './fixtures/protocol-definitions/notes.json' assert { type: 'json' };
import { DwnConstant, DwnInterfaceName, DwnMethodName, Jws, PermissionsProtocol, Poller, Time } from '@tbd54566975/dwn-sdk-js';
import { PermissionGrant } from '../src/permission-grant.js';
import { Record } from '../src/record.js';
Expand Down Expand Up @@ -1055,123 +1056,6 @@ describe('DwnApi', () => {
expect(await result.record?.data.json()).to.deep.equal(dataJson);
});

it('ensure that a protocolRole used to query is also used to read the data of the result', async () => {
// Configure the photos protocol on Alice and Bob's local and remote DWNs.
const { status: bobProtocolStatus, protocol: bobProtocol } = await dwnBob.protocols.configure({
message: {
definition: photosProtocolDefinition
}
});
expect(bobProtocolStatus.code).to.equal(202);
const { status: bobRemoteProtocolStatus } = await bobProtocol.send(bobDid.uri);
expect(bobRemoteProtocolStatus.code).to.equal(202);

// Bob creates an album
const { status: albumCreateStatus, record: albumRecord } = await dwnBob.records.create({
data : 'My Album',
message : {
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album',
schema : photosProtocolDefinition.types.album.schema,
dataFormat : 'text/plain'
}
});
expect(albumCreateStatus.code).to.equal(202);
const { status: albumSendStatus } = await albumRecord.send();
expect(albumSendStatus.code).to.equal(202);

// Bob makes Alice a `participant` and sends the record to her and his own remote node.
const { status: participantCreateStatus, record: participantRecord} = await dwnBob.records.create({
data : 'test',
message : {
parentContextId : albumRecord.contextId,
recipient : aliceDid.uri,
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album/participant',
schema : photosProtocolDefinition.types.participant.schema,
dataFormat : 'text/plain'
}
});
expect(participantCreateStatus.code).to.equal(202);
const { status: bobParticipantSendStatus } = await participantRecord.send(bobDid.uri);
expect(bobParticipantSendStatus.code).to.equal(202);

// bob adds 3 photos to the album
for (let i = 0; i < 3; i++) {
const { status: photoCreateStatus, record: photoRecord } = await dwnBob.records.create({
data : TestDataGenerator.randomString(DwnConstant.maxDataSizeAllowedToBeEncoded + 1),
message : {
parentContextId : albumRecord.contextId,
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album/photo',
schema : photosProtocolDefinition.types.photo.schema,
dataFormat : 'text/plain',
}
});
expect(photoCreateStatus.code).to.equal(202);
const { status: photoSendStatus } = await photoRecord.send();
expect(photoSendStatus.code).to.equal(202);
}

// alice uses the role to add a photo to the album
const { status: photoCreateStatusAlice, record: photoRecordAlice } = await dwnAlice.records.create({
store : false,
data : TestDataGenerator.randomString(DwnConstant.maxDataSizeAllowedToBeEncoded + 1),
message : {
parentContextId : albumRecord.contextId,
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album/photo',
protocolRole : 'album/participant',
schema : photosProtocolDefinition.types.photo.schema,
dataFormat : 'text/plain'
}
});
expect(photoCreateStatusAlice.code).to.equal(202);
const { status: albumSendStatusAlice } = await photoRecordAlice.send(bobDid.uri);
expect(albumSendStatusAlice.code).to.equal(202);

//SANITY: Alice attempts to fetch the photos without the role, she should only see her own photo
const { status: alicePhotosReadResultWithoutRole, records: alicePhotosRecordsWithoutRole } = await dwnAlice.records.query({
from : bobDid.uri,
message : {
filter: {
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album/photo',
contextId : albumRecord.contextId
}
}
});
expect(alicePhotosReadResultWithoutRole.code).to.equal(200);
expect(alicePhotosRecordsWithoutRole).to.exist;
expect(alicePhotosRecordsWithoutRole).to.have.lengthOf(1);

// Attempt to read the data of the photo, which should succeed
const readResultWithoutRole = await alicePhotosRecordsWithoutRole[0].data.text();
expect(readResultWithoutRole.length).to.equal(DwnConstant.maxDataSizeAllowedToBeEncoded + 1);

// Alice fetches all of the photos from the album
const alicePhotosReadResult = await dwnAlice.records.query({
from : bobDid.uri,
message : {
protocolRole : 'album/participant',
filter : {
protocol : photosProtocolDefinition.protocol,
protocolPath : 'album/photo',
contextId : albumRecord.contextId
}
}
});
expect(alicePhotosReadResult.status.code).to.equal(200);
expect(alicePhotosReadResult.records).to.exist;
expect(alicePhotosReadResult.records).to.have.lengthOf(4);

// attempt to read data from the photos
for (const record of alicePhotosReadResult.records) {
const readResult = await record.data.text();
expect(readResult.length).to.equal(DwnConstant.maxDataSizeAllowedToBeEncoded + 1);
}
});

it('creates a role record for another user that they can use to create role-based records', async () => {
/**
* WHAT IS BEING TESTED?
Expand Down Expand Up @@ -2196,6 +2080,75 @@ describe('DwnApi', () => {
expect(fooBarResult.records![0].id).to.equal(record.id);
expect(fooBarResult.records![0].tags).to.deep.equal({ foo: 'bar' });
});

it('ensures that a protocolRole used to query is also used to read the data of the resulted records', async () => {
// Bob configures the notes protocol for himself
const { status: bobProtocolStatus, protocol: bobProtocol } = await dwnBob.protocols.configure({
message: {
definition: notesProtocolDefinition
}
});
expect(bobProtocolStatus.code).to.equal(202);
const { status: bobRemoteProtocolStatus } = await bobProtocol.send(bobDid.uri);
expect(bobRemoteProtocolStatus.code).to.equal(202);

// Bob creates a few notes ensuring that the data is larger than the max encoded size
// that way the data will be requested with a separate `read` request
const recordData: Map<string, string> = new Map();
for (let i = 0; i < 3; i++) {
const data = TestDataGenerator.randomString(DwnConstant.maxDataSizeAllowedToBeEncoded + 1);
const { status: noteCreateStatus, record: noteRecord } = await dwnBob.records.create({
data,
message: {
protocol : notesProtocolDefinition.protocol,
protocolPath : 'note',
schema : notesProtocolDefinition.types.note.schema,
dataFormat : 'text/plain',
}
});
expect(noteCreateStatus.code).to.equal(202);
const { status: noteSendStatus } = await noteRecord.send();
expect(noteSendStatus.code).to.equal(202);
recordData.set(noteRecord.id, data);
}

// Bob makes Alice a `friend` to allow her to read and comment on his notes
const { status: friendCreateStatus, record: friendRecord} = await dwnBob.records.create({
data : 'friend!',
message : {
recipient : aliceDid.uri,
protocol : notesProtocolDefinition.protocol,
protocolPath : 'friend',
schema : notesProtocolDefinition.types.friend.schema,
dataFormat : 'text/plain'
}
});
expect(friendCreateStatus.code).to.equal(202);
const { status: bobFriendSendStatus } = await friendRecord.send(bobDid.uri);
expect(bobFriendSendStatus.code).to.equal(202);

// alice uses the role to query for the available notes
const { status: notesQueryStatus, records: noteRecords } = await dwnAlice.records.query({
from : bobDid.uri,
message : {
protocolRole : 'friend',
filter : {
protocol : notesProtocolDefinition.protocol,
protocolPath : 'note'
}
}
});
expect(notesQueryStatus.code).to.equal(200);
expect(noteRecords).to.exist;
expect(noteRecords).to.have.lengthOf(3);

// Alice attempts to read the data of the notes, which should succeed
for (const record of noteRecords) {
const readResult = await record.data.text();
const expectedData = recordData.get(record.id);
expect(readResult).to.equal(expectedData);
}
});
});
});

Expand Down Expand Up @@ -2562,6 +2515,86 @@ describe('DwnApi', () => {
expect(record.deleted).to.be.false;
});
});

it('ensures that a protocolRole used to subscribe is also used to read the data of the resulted records', async () => {
// Bob configures the notes protocol for himself
const { status: bobProtocolStatus, protocol: bobProtocol } = await dwnBob.protocols.configure({
message: {
definition: notesProtocolDefinition
}
});
expect(bobProtocolStatus.code).to.equal(202);
const { status: bobRemoteProtocolStatus } = await bobProtocol.send(bobDid.uri);
expect(bobRemoteProtocolStatus.code).to.equal(202);


// Bob makes Alice a `friend` to allow her to read and comment on his notes
const { status: friendCreateStatus, record: friendRecord} = await dwnBob.records.create({
data : 'friend!',
message : {
recipient : aliceDid.uri,
protocol : notesProtocolDefinition.protocol,
protocolPath : 'friend',
schema : notesProtocolDefinition.types.friend.schema,
dataFormat : 'text/plain'
}
});
expect(friendCreateStatus.code).to.equal(202);
const { status: bobFriendSendStatus } = await friendRecord.send(bobDid.uri);
expect(bobFriendSendStatus.code).to.equal(202);

// Alice subscribes to the notes protocol using the role
const notes: Map<string, Record> = new Map();
const subscriptionHandler = async (record: Record) => {
notes.set(record.id, record);
};

// alice uses the role to query for the available notes
const { status: notesSubscribeStatus, subscription } = await dwnAlice.records.subscribe({
from : bobDid.uri,
message : {
protocolRole : 'friend',
filter : {
protocol : notesProtocolDefinition.protocol,
protocolPath : 'note'
}
},
subscriptionHandler
});
expect(notesSubscribeStatus.code).to.equal(200);
expect(subscription).to.exist;

// Bob creates a few notes ensuring that the data is larger than the max encoded size
// that way the data will be requested with a separate `read` request
const recordData: Map<string, string> = new Map();
for (let i = 0; i < 3; i++) {
const data = TestDataGenerator.randomString(DwnConstant.maxDataSizeAllowedToBeEncoded + 1);
const { status: noteCreateStatus, record: noteRecord } = await dwnBob.records.create({
data,
message: {
protocol : notesProtocolDefinition.protocol,
protocolPath : 'note',
schema : notesProtocolDefinition.types.note.schema,
dataFormat : 'text/plain',
}
});
expect(noteCreateStatus.code).to.equal(202);
const { status: noteSendStatus } = await noteRecord.send();
expect(noteSendStatus.code).to.equal(202);
recordData.set(noteRecord.id, data);
}

// poll for the note records to be received
await Poller.pollUntilSuccessOrTimeout(async () => {
expect(notes.size).to.equal(3);
});

for (const record of notes.values()) {
const readResult = await record.data.text();
const expectedData = recordData.get(record.id);
expect(readResult).to.equal(expectedData);
}
});
});
});

Expand Down
48 changes: 48 additions & 0 deletions packages/api/tests/fixtures/protocol-definitions/notes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"protocol": "http://notes-protocol.xyz",
"published": true,
"types": {
"note": {
"schema": "http://notes-protocol.xyz/schema/note",
"dataFormats": [
"text/plain",
"application/json"
]
},
"comment": {
"schema": "http://notes-protocol.xyz/schema/comment",
"dataFormats": [
"text/plain",
"application/json"
]
},
"friend" : {
"schema": "http://notes-protocol.xyz/schema/friend",
"dataFormats": [
"text/plain",
"application/json"
]
}
},
"structure": {
"friend" :{
"$role": true
},
"note": {
"$actions": [
{
"role": "friend",
"can": ["read", "query", "subscribe"]
}
],
"comment": {
"$actions": [
{
"role": "friend",
"can": ["create", "delete", "read", "query", "subscribe"]
}
]
}
}
}
}
6 changes: 3 additions & 3 deletions packages/api/tests/fixtures/protocol-definitions/photos.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
{
"role": "friend",
"can": [
"create", "update", "read", "query", "subscribe"
"create", "update"
]
}
],
Expand All @@ -54,7 +54,7 @@
{
"role": "album/participant",
"can": [
"create", "update", "read", "query", "subscribe"
"create", "update"
]
}
]
Expand All @@ -64,7 +64,7 @@
{
"role": "album/participant",
"can": [
"create", "update", "read", "query", "subscribe"
"create", "update"
]
},
{
Expand Down

0 comments on commit 0a53308

Please sign in to comment.