Skip to content

Commit

Permalink
handle multiple recipient and author record queries
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Aug 15, 2024
1 parent 708bb64 commit 35b5119
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 14 deletions.
4 changes: 3 additions & 1 deletion src/handlers/records-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ export class RecordsQueryHandler implements MethodHandler {

if (Records.shouldProtocolAuthorize(recordsQuery.signaturePayload!)) {
filters.push(RecordsQueryHandler.buildUnpublishedProtocolAuthorizedRecordsFilter(recordsQuery));
} else if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsQuery.author!)) {
}

if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsQuery.author!)) {
filters.push(RecordsQueryHandler.buildUnpublishedRecordsForQueryAuthorFilter(recordsQuery));
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/handlers/records-subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,15 @@ export class RecordsSubscribeHandler implements MethodHandler {
}

if (Records.filterIncludesUnpublishedRecords(filter)) {
filters.push(RecordsSubscribeHandler.buildUnpublishedRecordsBySubscribeAuthorFilter(recordsSubscribe));
if (Records.shouldBuildUnpublishedAuthorFilter(filter, recordsSubscribe.author!)) {
filters.push(RecordsSubscribeHandler.buildUnpublishedRecordsBySubscribeAuthorFilter(recordsSubscribe));
}

if (Records.shouldProtocolAuthorize(recordsSubscribe.signaturePayload!)) {
filters.push(RecordsSubscribeHandler.buildUnpublishedProtocolAuthorizedRecordsFilter(recordsSubscribe));
} else if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsSubscribe.author!)) {
}

if (Records.shouldBuildUnpublishedRecipientFilter(filter, recordsSubscribe.author!)) {
filters.push(RecordsSubscribeHandler.buildUnpublishedRecordsForSubscribeAuthorFilter(recordsSubscribe));
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/utils/records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,16 @@ export class Records {
filterCopy.contextId = contextIdPrefixFilter;
}

// if the author filter is an array and it's empty, we should remove it from the filter as it will always return no results.
if (Array.isArray(filterCopy.author) && filterCopy.author.length === 0) {
delete filterCopy.author;
}

// if the recipient filter is an array and it's empty, we should remove it from the filter as it will always return no results.
if (Array.isArray(filterCopy.recipient) && filterCopy.recipient.length === 0) {
delete filterCopy.recipient;
}

return filterCopy as Filter;
}

Expand Down
283 changes: 279 additions & 4 deletions tests/handlers/records-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export function testRecordsQueryHandler(): void {
recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
author : [ bob.did ],
author : bob.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
Expand All @@ -318,6 +318,281 @@ export function testRecordsQueryHandler(): void {
expect(queryReply.entries![0].recordId).to.equal(bobAuthorWrite.message.recordId);
});

it('should be able to query by multiple authors', async () => {
// scenario: alice, bob and carol author records into alice's DWN.
// alice is able to query based on multiple authors.

const alice = await TestDataGenerator.generateDidKeyPersona();
const bob = await TestDataGenerator.generateDidKeyPersona();
const carol = await TestDataGenerator.generateDidKeyPersona();

const protocolDefinition = freeForAll;

const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);

const aliceAuthorWrite = await TestDataGenerator.generateRecordsWrite({
author : alice,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const aliceAuthorReply = await dwn.processMessage(alice.did, aliceAuthorWrite.message, { dataStream: aliceAuthorWrite.dataStream });
expect(aliceAuthorReply.status.code).to.equal(202);

const bobAuthorWrite = await TestDataGenerator.generateRecordsWrite({
author : bob,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const bobAuthorReply = await dwn.processMessage(alice.did, bobAuthorWrite.message, { dataStream: bobAuthorWrite.dataStream });
expect(bobAuthorReply.status.code).to.equal(202);

const carolAuthorWrite = await TestDataGenerator.generateRecordsWrite({
author : carol,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const carolAuthorReply = await dwn.processMessage(alice.did, carolAuthorWrite.message, { dataStream: carolAuthorWrite.dataStream });
expect(carolAuthorReply.status.code).to.equal(202);

// alice queries with an empty array, gets all
let recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post',
author : []
}
});
let queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(3);
expect(queryReply.entries?.map(e => e.recordId)).to.have.members([
aliceAuthorWrite.message.recordId,
bobAuthorWrite.message.recordId,
carolAuthorWrite.message.recordId
]);

// filter for alice and bob as authors
recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
author : [alice.did, bob.did],
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
}
});
queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(2);
expect(queryReply.entries?.map(e => e.recordId)).to.have.members([
aliceAuthorWrite.message.recordId,
bobAuthorWrite.message.recordId
]);
});

it('should be able to query by recipient', async () => {
// scenario alice authors records for bob and carol into alice's DWN.
// bob and carol are able to filter for records for them.
const alice = await TestDataGenerator.generateDidKeyPersona();
const bob = await TestDataGenerator.generateDidKeyPersona();
const carol = await TestDataGenerator.generateDidKeyPersona();

const protocolDefinition = freeForAll;

const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);

const aliceToBob = await TestDataGenerator.generateRecordsWrite({
author : alice,
recipient : bob.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const aliceToBobReply = await dwn.processMessage(alice.did, aliceToBob.message, { dataStream: aliceToBob.dataStream });
expect(aliceToBobReply.status.code).to.equal(202);

const aliceToCarol = await TestDataGenerator.generateRecordsWrite({
author : alice,
recipient : carol.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const aliceToCarolReply = await dwn.processMessage(alice.did, aliceToCarol.message, { dataStream: aliceToCarol.dataStream });
expect(aliceToCarolReply.status.code).to.equal(202);

// alice queries with an empty filter, gets both
let recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
}
});
let queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(2);

// filter for bob as recipient
recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
recipient : bob.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
}
});
queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(1);
expect(queryReply.entries![0].recordId).to.equal(aliceToBob.message.recordId);

// filter for carol as recipient
recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
recipient : carol.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
}
});
queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(1);
expect(queryReply.entries![0].recordId).to.equal(aliceToCarol.message.recordId);
});

it('should be able to query by multiple recipients', async () => {
// scenario: alice, bob and carol author records for various recipients into alice's DWN.
// alice is able to filter based on multiple recipients

const alice = await TestDataGenerator.generateDidKeyPersona();
const bob = await TestDataGenerator.generateDidKeyPersona();
const carol = await TestDataGenerator.generateDidKeyPersona();

const protocolDefinition = freeForAll;

const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolsConfigureReply.status.code).to.equal(202);

const bobToAliceWrite = await TestDataGenerator.generateRecordsWrite({
author : bob,
recipient : alice.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const bobToAliceReply = await dwn.processMessage(alice.did, bobToAliceWrite.message, { dataStream: bobToAliceWrite.dataStream });
expect(bobToAliceReply.status.code).to.equal(202);

const aliceToBobWrite = await TestDataGenerator.generateRecordsWrite({
author : alice,
recipient : bob.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const aliceToBobReply = await dwn.processMessage(alice.did, aliceToBobWrite.message, { dataStream: aliceToBobWrite.dataStream });
expect(aliceToBobReply.status.code).to.equal(202);

const carolToBobWrite = await TestDataGenerator.generateRecordsWrite({
author : carol,
recipient : bob.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const carolToBobReply = await dwn.processMessage(alice.did, carolToBobWrite.message, { dataStream: carolToBobWrite.dataStream });
expect(carolToBobReply.status.code).to.equal(202);

const aliceToCarolWrite = await TestDataGenerator.generateRecordsWrite({
author : alice,
recipient : carol.did,
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
});
const aliceToCarolReply = await dwn.processMessage(alice.did, aliceToCarolWrite.message, { dataStream: aliceToCarolWrite.dataStream });
expect(aliceToCarolReply.status.code).to.equal(202);

// alice queries with an empty array, gets all
let recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post',
recipient : []
}
});
let queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(4);
expect(queryReply.entries?.map(e => e.recordId)).to.have.members([
bobToAliceWrite.message.recordId,
aliceToBobWrite.message.recordId,
carolToBobWrite.message.recordId,
aliceToCarolWrite.message.recordId
]);

// filter for alice and bob as authors
recordsQuery = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : {
recipient : [alice.did, bob.did],
protocol : protocolDefinition.protocol,
schema : protocolDefinition.types.post.schema,
dataFormat : protocolDefinition.types.post.dataFormats[0],
protocolPath : 'post'
}
});
queryReply = await dwn.processMessage(alice.did, recordsQuery.message);
expect(queryReply.status.code).to.equal(200);
expect(queryReply.entries?.length).to.equal(3);
expect(queryReply.entries?.map(e => e.recordId)).to.have.members([
bobToAliceWrite.message.recordId,
aliceToBobWrite.message.recordId,
carolToBobWrite.message.recordId
]);
});

it('should be able to query for published records', async () => {
const alice = await TestDataGenerator.generateDidKeyPersona();
const bob = await TestDataGenerator.generateDidKeyPersona();
Expand Down Expand Up @@ -1566,7 +1841,7 @@ export function testRecordsQueryHandler(): void {
// filter for public records with carol as recipient
const bobQueryCarolMessageData = await TestDataGenerator.generateRecordsQuery({
author : bob,
filter : { schema, recipient: [ carol.did ] }
filter : { schema, recipient: carol.did }
});
const replyToBobCarolQuery = await dwn.processMessage(alice.did, bobQueryCarolMessageData.message);
expect(replyToBobCarolQuery.status.code).to.equal(200);
Expand All @@ -1576,7 +1851,7 @@ export function testRecordsQueryHandler(): void {
// filter for explicit unpublished public records with carol as recipient, should not return any.
const bobQueryCarolMessageDataUnpublished = await TestDataGenerator.generateRecordsQuery({
author : bob,
filter : { schema, recipient: [ carol.did ], published: false }
filter : { schema, recipient: carol.did, published: false }
});
const replyToBobCarolUnpublishedQuery = await dwn.processMessage(alice.did, bobQueryCarolMessageDataUnpublished.message);
expect(replyToBobCarolUnpublishedQuery.status.code).to.equal(200);
Expand Down Expand Up @@ -1759,7 +2034,7 @@ export function testRecordsQueryHandler(): void {

const bobQueryMessageData = await TestDataGenerator.generateRecordsQuery({
author : alice,
filter : { recipient: [ bob.did ] } // alice as the DWN owner querying bob's records
filter : { recipient: bob.did } // alice as the DWN owner querying bob's records
});

const replyToBobQuery = await dwn.processMessage(alice.did, bobQueryMessageData.message);
Expand Down
2 changes: 1 addition & 1 deletion tests/handlers/records-read.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ export function testRecordsReadHandler(): void {
signer : Jws.createSigner(bob),
filter : {
protocolPath : 'thread/participant',
recipient : [ bob.did ],
recipient : bob.did,
contextId : threadRecord.message.contextId
},
});
Expand Down
Loading

0 comments on commit 35b5119

Please sign in to comment.