From 33f666a010d9564c259edf1ed645f98ed71bee44 Mon Sep 17 00:00:00 2001 From: Frank Hinek Date: Sat, 9 Dec 2023 16:09:44 -0500 Subject: [PATCH] Improve semantics of Record class Signed-off-by: Frank Hinek --- packages/api/src/dwn-api.ts | 19 +- packages/api/src/record.ts | 53 ++-- packages/api/tests/record.spec.ts | 438 ++++++++++++++---------------- 3 files changed, 237 insertions(+), 273 deletions(-) diff --git a/packages/api/src/dwn-api.ts b/packages/api/src/dwn-api.ts index 04cc47818..a7c0cec10 100644 --- a/packages/api/src/dwn-api.ts +++ b/packages/api/src/dwn-api.ts @@ -270,9 +270,6 @@ export class DwnApi { createFrom: async (request: RecordsCreateFromRequest): Promise => { const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON(); - // Remove target from inherited properties since target is being explicitly defined in method parameters. - delete inheritedProperties.target; - // If `data` is being updated then `dataCid` and `dataSize` must not be present. if (request.data !== undefined) { delete inheritedProperties.dataCid; @@ -372,11 +369,11 @@ export class DwnApi { */ author : RecordsWrite.getAuthor(entry), /** - * Set the `target` DID to currently connected DID so that subsequent calls to + * Set the `connectedDid` to currently connected DID so that subsequent calls to * {@link Record} instance methods, such as `record.update()` are executed on the * local DWN even if the record was returned by a query of a remote DWN. */ - target : this.connectedDid, + connectedDid : this.connectedDid, /** * If the record was returned by a query of a remote DWN, set the `remoteOrigin` to * the DID of the DWN that returned the record. The `remoteOrigin` property will be used @@ -432,11 +429,11 @@ export class DwnApi { */ author : RecordsWrite.getAuthor(responseRecord), /** - * Set the `target` DID to currently connected DID so that subsequent calls to + * Set the `connectedDid` to currently connected DID so that subsequent calls to * {@link Record} instance methods, such as `record.update()` are executed on the * local DWN even if the record was read from a remote DWN. */ - target : this.connectedDid, + connectedDid : this.connectedDid, /** * If the record was returned by reading from a remote DWN, set the `remoteOrigin` to * the DID of the DWN that returned the record. The `remoteOrigin` property will be used @@ -489,14 +486,14 @@ export class DwnApi { * Assume the author is the connected DID since the record was just written to the * local DWN. */ - author : this.connectedDid, - encodedData : dataBlob, + author : this.connectedDid, /** - * Set the `target` DID to currently connected DID so that subsequent calls to + * Set the `connectedDid` to currently connected DID so that subsequent calls to * {@link Record} instance methods, such as `record.update()` are executed on the * local DWN. */ - target : this.connectedDid, + connectedDid : this.connectedDid, + encodedData : dataBlob, ...responseMessage, }; diff --git a/packages/api/src/record.ts b/packages/api/src/record.ts index 0455a4ab4..e162d0cd4 100644 --- a/packages/api/src/record.ts +++ b/packages/api/src/record.ts @@ -20,7 +20,7 @@ import { dataToBlob } from './utils.js'; */ export type RecordOptions = RecordsWriteMessage & { author: string; - target: string; + connectedDid: string; encodedData?: string | Blob; data?: Readable | ReadableStream; remoteOrigin?: string; @@ -37,7 +37,6 @@ export type RecordModel = RecordsWriteDescriptor & { author: string; recordId?: string; - target: string; } /** @@ -68,29 +67,29 @@ export type RecordUpdateOptions = { * @beta */ export class Record implements RecordModel { - // mutable properties - - /** Record's author DID */ - author: string; - - /** Record's target DID */ - target: string; - + // Record instance metadata. private _agent: Web5Agent; + private _connectedDid: string; + private _encodedData?: Blob; + private _readableStream?: Readable; + private _remoteOrigin?: string; + + // Private variables for DWN `RecordsWrite` message properties. + private _author: string; private _attestation?: RecordsWriteMessage['attestation']; private _contextId?: string; private _descriptor: RecordsWriteDescriptor; - private _encodedData?: Blob; private _encryption?: RecordsWriteMessage['encryption']; - private _readableStream?: Readable; private _recordId: string; - private _remoteOrigin?: string; - // Immutable DWN Record properties. + // Getters for immutable DWN Record properties. /** Record's signatures attestation */ get attestation(): RecordsWriteMessage['attestation'] { return this._attestation; } + /** DID that signed the record. */ + get author(): string { return this._author; } + /** Record's context ID */ get contextId() { return this._contextId; } @@ -127,7 +126,7 @@ export class Record implements RecordModel { /** Record's schema */ get schema() { return this._descriptor.schema; } - // Mutable DWN Record properties. + // Getters for mutable DWN Record properties. /** Record's CID */ get dataCid() { return this._descriptor.dataCid; } @@ -150,10 +149,13 @@ export class Record implements RecordModel { constructor(agent: Web5Agent, options: RecordOptions) { this._agent = agent; - /** Store the target and author DIDs that were used to create the message to use for subsequent - * updates, reads, etc. */ - this.author = options.author; - this.target = options.target; + /** Store the author DID that originally signed the message as a convenience for developers, so + * that they don't have to decode the signer's DID from the JWS. */ + this._author = options.author; + + /** Store the currently `connectedDid` so that subsequent message signing is done with the + * connected DID's keys and DWN requests target the connected DID's DWN. */ + this._connectedDid = options.connectedDid; /** If the record was queried or read from a remote DWN, the `remoteOrigin` DID will be * defined. This value is used to send subsequent read requests to the same remote DWN in the @@ -271,7 +273,7 @@ export class Record implements RecordModel { // A. ...a remote DWN if the record was originally queried from a remote DWN. await self.readRecordData({ target: self._remoteOrigin, isRemote: true }) : // B. ...a local DWN if the record was originally queried from the local DWN. - await self.readRecordData({ target: self.target, isRemote: false }); + await self.readRecordData({ target: self._connectedDid, isRemote: false }); } if (!self._readableStream) { @@ -305,7 +307,7 @@ export class Record implements RecordModel { async send(target: string): Promise { const { reply: { status } } = await this._agent.sendDwnRequest({ messageType : DwnInterfaceName.Records + DwnMethodName.Write, - author : this.author, + author : this._connectedDid, dataStream : await this.data.blob(), target : target, messageOptions : this.toJSON(), @@ -338,8 +340,7 @@ export class Record implements RecordModel { published : this.published, recipient : this.recipient, recordId : this.id, - schema : this.schema, - target : this.target, + schema : this.schema }; } @@ -415,11 +416,11 @@ export class Record implements RecordModel { }; const agentResponse = await this._agent.processDwnRequest({ - author : this.author, + author : this._connectedDid, dataStream : dataBlob, messageOptions, messageType : DwnInterfaceName.Records + DwnMethodName.Write, - target : this.target, + target : this._connectedDid, }); const { message, reply: { status } } = agentResponse; @@ -456,7 +457,7 @@ export class Record implements RecordModel { */ private async readRecordData({ target, isRemote }: { target: string, isRemote: boolean }) { const readRequest = { - author : this.author, + author : this._connectedDid, messageOptions : { filter: { recordId: this.id } }, messageType : DwnInterfaceName.Records + DwnMethodName.Read, target, diff --git a/packages/api/tests/record.spec.ts b/packages/api/tests/record.spec.ts index 00fa1b074..1fdfc0a32 100644 --- a/packages/api/tests/record.spec.ts +++ b/packages/api/tests/record.spec.ts @@ -45,7 +45,7 @@ type RecordsWriteTest = RecordsWrite & RecordsWriteMessage; let testDwnUrls: string[] = [testDwnUrl]; -describe.only('Record', () => { +describe('Record', () => { let dataText: string; let dataBlob: Blob; let dataFormat: string; @@ -100,7 +100,6 @@ describe.only('Record', () => { it('should retain all defined properties', async () => { // RecordOptions properties const author = aliceDid.did; - const target = aliceDid.did; // Retrieve `#dwn` service entry. const [ didDwnService ] = didUtils.getServices({ didDocument: aliceDid.document, id: '#dwn' }); @@ -184,14 +183,13 @@ describe.only('Record', () => { // Create record using test RecordsWriteMessage. const record = new Record(testAgent.agent, { ...recordsWrite.message, - encodedData: dataBlob, - target, - author, + encodedData : dataBlob, + author : aliceDid.did, + connectedDid : aliceDid.did }); // Retained Record properties expect(record.author).to.equal(author); - expect(record.target).to.equal(target); // Retained RecordsWriteMessage top-level properties expect(record.contextId).to.equal(recordsWrite.message.contextId); @@ -713,231 +711,6 @@ describe.only('Record', () => { expect(queriedDataBlob.size).to.equal(inputDataBytes.length); }); - - - - - - - - - - - it('returns large data payloads of records signed by another entity after remote dwn.records.query()', async () => { - /** - * WHAT IS BEING TESTED? - * - * We are testing whether a large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) record - * authored/signed by one party (Alice) can be written to another party's DWN (Bob), and that - * recipient (Bob) is able to access the data payload. This test was added to reveal a bug - * that only surfaces when accessing the data (`record.data.*`) of a record signed by a - * different entity a `Record` instance's data, which requires fetching the data from a - * remote DWN. Since the large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) data was not - * returned with the query as `encodedData`, the `Record` instance's data is not available and - * must be fetched from the remote DWN using a `RecordsRead` message. - * - * What made this bug particularly difficult to track down is that the bug only surfaces when - * keys used to sign the record are different than the keys used to fetch the record AND both - * sets of keys are unavailable to the test Agent used by the entity that is attempting to - * fetch the record. In all of the other tests, the same test agent is used to store the keys - * for all entities (e.g., "Alice", "Bob", etc.) so the bug never surfaced. - * - * In this test, Alice is the author of the record and Bob is the recipient. Alice and Bob - * each have their own Agents, DWNs, DIDs, and keys. Alice's DWN is configured to use - * Alice's DID/keys, and Bob's DWN is configured to use Bob's DID/keys. When Alice writes a - * record to Bob's DWN, the record is signed by Alice's keys. When Bob fetches the record from - * his DWN, this test validates that the `RecordsRead` is signed by Bob's keys. - * - * SETUP STEPS: - * S1. Create a second `TestManagedAgent` that only Bob will use. - */ - const testAgentBob = await TestManagedAgent.create({ - agentClass : TestUserAgent, - agentStores : 'memory', - testDataLocation : '__TESTDATA__/AGENT_BOB' - }); - await testAgentBob.createAgentDid(); - /** - * S2. Create a "bob" Identity to author the DWN messages. - */ - ({ did: bobDid } = await testAgentBob.createIdentity({ testDwnUrls })); - await testAgentBob.agent.identityManager.import({ - did : bobDid, - identity : { name: 'Bob', did: bobDid.did }, - kms : 'local' - }); - /** - * S3. Instantiate a new `DwnApi` using Bob's test agent. - */ - dwnBob = new DwnApi({ agent: testAgentBob.agent, connectedDid: bobDid.did }); - /** - * S4. Install the email protocol to both Alice's and Bob's DWNs. - */ - let { protocol: aliceProtocol, status: aliceStatus } = await dwnAlice.protocols.configure({ - message: { definition: emailProtocolDefinition } - }); - expect(aliceStatus.code).to.equal(202); - const { status: alicePushStatus } = await aliceProtocol!.send(aliceDid.did); - expect(alicePushStatus.code).to.equal(202); - const { protocol: bobProtocol, status: bobStatus } = await dwnBob.protocols.configure({ - message: { - definition: emailProtocolDefinition - } - }); - expect(bobStatus.code).to.equal(202); - const { status: bobPushStatus } = await bobProtocol!.send(bobDid.did); - expect(bobPushStatus.code).to.equal(202); - - /** - * TEST STEPS: - * - * 1. Alice creates a record but does NOT store it her local, agent-connected DWN. - */ - const { record, status } = await dwnAlice.records.write({ - data : dataTextExceedingMaxSize, - store : false, - message : { - protocol : emailProtocolDefinition.protocol, - protocolPath : 'email', - schema : emailProtocolDefinition.types.email.schema - } - }); - expect(status.code).to.equal(202); - /** - * 2. Alice writes the record to Bob's remote DWN. - */ - const { status: sendStatus } = await record!.send(bobDid.did); - expect(sendStatus.code).to.equal(202); - /** - * 3. Bob queries his remote DWN for the record that Alice just wrote. - */ - const { records: queryRecordsFrom, status: queryRecordStatusFrom } = await dwnBob.records.query({ - from : bobDid.did, - message : { filter: { recordId: record!.id }} - }); - expect(queryRecordStatusFrom.code).to.equal(200); - /** - * 4. Validate that Bob is able to access the data payload. - */ - const recordData = await queryRecordsFrom[0].data.blob(); - expect(recordData.size).to.equal(dataTextExceedingMaxSize.length); - - // Test clean-up. - await testAgentBob.clearStorage(); - }); - - it.only('returns large data payloads of records signed by another entity after remote dwn.records.query()', async () => { - /** - * WHAT IS BEING TESTED? - * - * We are testing whether a large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) record - * authored/signed by one party (Alice) can be written to another party's DWN (Bob), and that - * recipient (Bob) is able to access the data payload. This test was added to reveal a bug - * that only surfaces when accessing the data (`record.data.*`) of a record signed by a - * different entity a `Record` instance's data, which requires fetching the data from a - * remote DWN. Since the large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) data was not - * returned with the query as `encodedData`, the `Record` instance's data is not available and - * must be fetched from the remote DWN using a `RecordsRead` message. - * - * What made this bug particularly difficult to track down is that the bug only surfaces when - * keys used to sign the record are different than the keys used to fetch the record AND both - * sets of keys are unavailable to the test Agent used by the entity that is attempting to - * fetch the record. In all of the other tests, the same test agent is used to store the keys - * for all entities (e.g., "Alice", "Bob", etc.) so the bug never surfaced. - * - * In this test, Alice is the author of the record and Bob is the recipient. Alice and Bob - * each have their own Agents, DWNs, DIDs, and keys. Alice's DWN is configured to use - * Alice's DID/keys, and Bob's DWN is configured to use Bob's DID/keys. When Alice writes a - * record to Bob's DWN, the record is signed by Alice's keys. When Bob fetches the record from - * his DWN, this test validates that the `RecordsRead` is signed by Bob's keys. - * - * SETUP STEPS: - * S1. Create a second `TestManagedAgent` that only Bob will use. - */ - const testAgentBob = await TestManagedAgent.create({ - agentClass : TestUserAgent, - agentStores : 'memory', - testDataLocation : '__TESTDATA__/AGENT_BOB' - }); - await testAgentBob.createAgentDid(); - /** - * S2. Create a "bob" Identity to author the DWN messages. - */ - ({ did: bobDid } = await testAgentBob.createIdentity({ testDwnUrls })); - await testAgentBob.agent.identityManager.import({ - did : bobDid, - identity : { name: 'Bob', did: bobDid.did }, - kms : 'local' - }); - /** - * S3. Instantiate a new `DwnApi` using Bob's test agent. - */ - dwnBob = new DwnApi({ agent: testAgentBob.agent, connectedDid: bobDid.did }); - /** - * S4. Install the email protocol to both Alice's and Bob's DWNs. - */ - let { protocol: aliceProtocol, status: aliceStatus } = await dwnAlice.protocols.configure({ - message: { definition: emailProtocolDefinition } - }); - expect(aliceStatus.code).to.equal(202); - const { status: alicePushStatus } = await aliceProtocol!.send(aliceDid.did); - expect(alicePushStatus.code).to.equal(202); - const { protocol: bobProtocol, status: bobStatus } = await dwnBob.protocols.configure({ - message: { - definition: emailProtocolDefinition - } - }); - expect(bobStatus.code).to.equal(202); - const { status: bobPushStatus } = await bobProtocol!.send(bobDid.did); - expect(bobPushStatus.code).to.equal(202); - - /** - * TEST STEPS: - * - * 1. Alice creates a record but does NOT store it her local, agent-connected DWN. - */ - const { record, status } = await dwnAlice.records.write({ - data : dataTextExceedingMaxSize, - store : false, - message : { - protocol : emailProtocolDefinition.protocol, - protocolPath : 'email', - schema : emailProtocolDefinition.types.email.schema - } - }); - expect(status.code).to.equal(202); - /** - * 2. Alice writes the record to Bob's remote DWN. - */ - const { status: sendStatus } = await record!.send(bobDid.did); - expect(sendStatus.code).to.equal(202); - /** - * 3. Bob queries his remote DWN for the record that Alice just wrote. - */ - const { records: queryRecordsFrom, status: queryRecordStatusFrom } = await dwnBob.records.query({ - from : bobDid.did, - message : { filter: { recordId: record!.id }} - }); - expect(queryRecordStatusFrom.code).to.equal(200); - /** - * 4. Validate that Bob is write the record to Alice's remote DWN. - */ - const { status: sendStatusToAlice } = await queryRecordsFrom[0]!.send(aliceDid.did); - expect(sendStatusToAlice.code).to.equal(202); - - // Test clean-up. - await testAgentBob.clearStorage(); - }); - - - - - - - - - - it('returns large data payloads after remote dwn.records.read()', async () => { /** Generate data that exceeds the DWN encoded data limit to ensure that the data will have to * be fetched with a RecordsRead when record.data.* is executed. */ @@ -1221,6 +994,201 @@ describe.only('Record', () => { readDataBytes = await NodeStream.consumeToBytes({ readable: readDataStream }); expect(readDataBytes.length).to.equal(inputDataBytes.length); }); + + describe('with two Agents', () => { + let testAgentBob: TestManagedAgent; + + before(async () => { + // Create a second `TestManagedAgent` that only Bob will use. + testAgentBob = await TestManagedAgent.create({ + agentClass : TestUserAgent, + agentStores : 'memory', + testDataLocation : '__TESTDATA__/AGENT_BOB' + }); + }); + + beforeEach(async () => { + await testAgentBob.clearStorage(); + + // Create an Agent DID. + await testAgent.createAgentDid(); + + // Create a new "bob" Identity to author the DWN messages. + ({ did: bobDid } = await testAgentBob.createIdentity({ testDwnUrls })); + await testAgentBob.agent.identityManager.import({ + did : bobDid, + identity : { name: 'Bob', did: bobDid.did }, + kms : 'local' + }); + + // Instantiate a new `DwnApi` using Bob's test agent. + dwnBob = new DwnApi({ agent: testAgentBob.agent, connectedDid: bobDid.did }); + }); + + after(async () => { + await testAgentBob.clearStorage(); + await testAgentBob.closeStorage(); + }); + + it('returns large data payloads of records signed by another entity after remote dwn.records.query()', async () => { + /** + * WHAT IS BEING TESTED? + * + * We are testing whether a large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) record + * authored/signed by one party (Alice) can be written to another party's DWN (Bob), and that + * recipient (Bob) is able to access the data payload. This test was added to reveal a bug + * that only surfaces when accessing the data (`record.data.*`) of a record signed by a + * different entity a `Record` instance's data, which requires fetching the data from a + * remote DWN. Since the large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) data was not + * returned with the query as `encodedData`, the `Record` instance's data is not available and + * must be fetched from the remote DWN using a `RecordsRead` message. + * + * What made this bug particularly difficult to track down is that the bug only surfaces when + * keys used to sign the record are different than the keys used to fetch the record AND both + * sets of keys are unavailable to the test Agent used by the entity that is attempting to + * fetch the record. In all of the other tests, the same test agent is used to store the keys + * for all entities (e.g., "Alice", "Bob", etc.) so the bug never surfaced. + * + * In this test, Alice is the author of the record and Bob is the recipient. Alice and Bob + * each have their own Agents, DWNs, DIDs, and keys. Alice's DWN is configured to use + * Alice's DID/keys, and Bob's DWN is configured to use Bob's DID/keys. When Alice writes a + * record to Bob's DWN, the record is signed by Alice's keys. When Bob fetches the record from + * his DWN, this test validates that the `RecordsRead` is signed by Bob's keys. + * + * SETUP STEPS: + * S1. Install the email protocol to both Alice's and Bob's DWNs. + */ + let { protocol: aliceProtocol, status: aliceStatus } = await dwnAlice.protocols.configure({ + message: { definition: emailProtocolDefinition } + }); + expect(aliceStatus.code).to.equal(202); + const { status: alicePushStatus } = await aliceProtocol!.send(aliceDid.did); + expect(alicePushStatus.code).to.equal(202); + const { protocol: bobProtocol, status: bobStatus } = await dwnBob.protocols.configure({ + message: { + definition: emailProtocolDefinition + } + }); + expect(bobStatus.code).to.equal(202); + const { status: bobPushStatus } = await bobProtocol!.send(bobDid.did); + expect(bobPushStatus.code).to.equal(202); + + /** + * TEST STEPS: + * + * 1. Alice creates a record but does NOT store it her local, agent-connected DWN. + */ + const { record, status } = await dwnAlice.records.write({ + data : dataTextExceedingMaxSize, + store : false, + message : { + protocol : emailProtocolDefinition.protocol, + protocolPath : 'email', + schema : emailProtocolDefinition.types.email.schema + } + }); + expect(status.code).to.equal(202); + /** + * 2. Alice writes the record to Bob's remote DWN. + */ + const { status: sendStatus } = await record!.send(bobDid.did); + expect(sendStatus.code).to.equal(202); + /** + * 3. Bob queries his remote DWN for the record that Alice just wrote. + */ + const { records: queryRecordsFrom, status: queryRecordStatusFrom } = await dwnBob.records.query({ + from : bobDid.did, + message : { filter: { recordId: record!.id }} + }); + expect(queryRecordStatusFrom.code).to.equal(200); + /** + * 4. Validate that Bob is able to access the data payload. + */ + const recordData = await queryRecordsFrom[0].data.blob(); + expect(recordData.size).to.equal(dataTextExceedingMaxSize.length); + }); + + it('fails to return large data payloads of records signed by another entity after remote dwn.records.query()', async () => { + /** + * ! TODO: Fix this once the bug in `dwn-sdk-js` is resolved. + * + * WHAT IS BEING TESTED? + * + * We are testing whether a large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) record + * authored/signed by one party (Alice) can be written to another party's DWN (Bob), and that + * recipient (Bob) is able to access the data payload. This test was added to reveal a bug + * that only surfaces when accessing the data (`record.data.*`) of a record signed by a + * different entity a `Record` instance's data, which requires fetching the data from a + * remote DWN. Since the large (> `DwnConstant.maxDataSizeAllowedToBeEncoded`) data was not + * returned with the query as `encodedData`, the `Record` instance's data is not available and + * must be fetched from the remote DWN using a `RecordsRead` message. + * + * What made this bug particularly difficult to track down is that the bug only surfaces when + * keys used to sign the record are different than the keys used to fetch the record AND both + * sets of keys are unavailable to the test Agent used by the entity that is attempting to + * fetch the record. In all of the other tests, the same test agent is used to store the keys + * for all entities (e.g., "Alice", "Bob", etc.) so the bug never surfaced. + * + * In this test, Alice is the author of the record and Bob is the recipient. Alice and Bob + * each have their own Agents, DWNs, DIDs, and keys. Alice's DWN is configured to use + * Alice's DID/keys, and Bob's DWN is configured to use Bob's DID/keys. When Alice writes a + * record to Bob's DWN, the record is signed by Alice's keys. When Bob fetches the record from + * his DWN, this test validates that the `RecordsRead` is signed by Bob's keys. + * + * SETUP STEPS: + * S1. Install the email protocol to both Alice's and Bob's DWNs. + */ + let { protocol: aliceProtocol, status: aliceStatus } = await dwnAlice.protocols.configure({ + message: { definition: emailProtocolDefinition } + }); + expect(aliceStatus.code).to.equal(202); + const { status: alicePushStatus } = await aliceProtocol!.send(aliceDid.did); + expect(alicePushStatus.code).to.equal(202); + const { protocol: bobProtocol, status: bobStatus } = await dwnBob.protocols.configure({ + message: { + definition: emailProtocolDefinition + } + }); + expect(bobStatus.code).to.equal(202); + const { status: bobPushStatus } = await bobProtocol!.send(bobDid.did); + expect(bobPushStatus.code).to.equal(202); + + /** + * TEST STEPS: + * + * 1. Alice creates a record but does NOT store it her local, agent-connected DWN. + */ + const { record, status } = await dwnAlice.records.write({ + data : dataTextExceedingMaxSize, + store : false, + message : { + protocol : emailProtocolDefinition.protocol, + protocolPath : 'email', + schema : emailProtocolDefinition.types.email.schema + } + }); + expect(status.code).to.equal(202); + /** + * 2. Alice writes the record to Bob's remote DWN. + */ + const { status: sendStatus } = await record!.send(bobDid.did); + expect(sendStatus.code).to.equal(202); + /** + * 3. Bob queries his remote DWN for the record that Alice just wrote. + */ + const { records: queryRecordsFrom, status: queryRecordStatusFrom } = await dwnBob.records.query({ + from : bobDid.did, + message : { filter: { recordId: record!.id }} + }); + expect(queryRecordStatusFrom.code).to.equal(200); + /** + * 4. Validate that Bob is able to write the record to Alice's remote DWN. + */ + const { status: sendStatusToAlice } = await queryRecordsFrom[0]!.send(aliceDid.did); + expect(sendStatusToAlice.code).to.equal(401); + expect(sendStatusToAlice.detail).to.equal(`Cannot read properties of undefined (reading 'authorization')`); + }); + }); }); describe('send()', () => { @@ -1806,7 +1774,6 @@ describe.only('Record', () => { it('should return all defined properties', async () => { // RecordOptions properties const author = aliceDid.did; - const target = aliceDid.did; // Retrieve `#dwn` service entry. const [ didDwnService ] = didUtils.getServices({ didDocument: aliceDid.document, id: '#dwn' }); @@ -1890,9 +1857,9 @@ describe.only('Record', () => { // Create record using test RecordsWriteMessage. const record = new Record(testAgent.agent, { ...recordsWrite.message, - encodedData: dataBlob, - target, - author, + encodedData : dataBlob, + author : aliceDid.did, + connectedDid : aliceDid.did, }); // Call toJSON() method. @@ -1900,7 +1867,6 @@ describe.only('Record', () => { // Retained Record properties. expect(recordJson.author).to.equal(author); - expect(recordJson.target).to.equal(target); // Retained RecordsWriteMessage top-level properties. expect(record.contextId).to.equal(recordsWrite.message.contextId);