diff --git a/.changeset/shy-grapes-juggle.md b/.changeset/shy-grapes-juggle.md new file mode 100644 index 000000000..4cb022f44 --- /dev/null +++ b/.changeset/shy-grapes-juggle.md @@ -0,0 +1,8 @@ +--- +"@web5/agent": patch +"@web5/identity-agent": patch +"@web5/proxy-agent": patch +"@web5/user-agent": patch +--- + +Add ability to update the Identity Metadata name field. diff --git a/packages/agent/src/identity-api.ts b/packages/agent/src/identity-api.ts index f35bab22a..0b1bbd1cd 100644 --- a/packages/agent/src/identity-api.ts +++ b/packages/agent/src/identity-api.ts @@ -270,6 +270,39 @@ export class AgentIdentityApi { + if (!name) { + throw new Error('AgentIdentityApi: Failed to set metadata name due to missing name value.'); + } + + const identity = await this.get({ didUri }); + if (!identity) { + throw new Error(`AgentIdentityApi: Failed to set metadata name due to Identity not found: ${didUri}`); + } + + if (identity.metadata.name === name) { + throw new Error('AgentIdentityApi: No changes detected.'); + } + + // Update the name in the Identity's metadata and store it + await this._store.set({ + id : identity.did.uri, + data : { ...identity.metadata, name }, + agent : this.agent, + tenant : identity.metadata.tenant, + updateExisting : true, + useCache : true + }); + } + /** * Returns the connected Identity, if one is available. * diff --git a/packages/agent/tests/identity-api.spec.ts b/packages/agent/tests/identity-api.spec.ts index 3ce0bcff2..3c038aa1d 100644 --- a/packages/agent/tests/identity-api.spec.ts +++ b/packages/agent/tests/identity-api.spec.ts @@ -440,6 +440,115 @@ describe('AgentIdentityApi', () => { }); }); + describe('setMetadataName', () => { + it('should update the name of an Identity', async () => { + const identity = await testHarness.agent.identity.create({ + metadata : { name: 'Test Identity' }, + didMethod : 'jwk', + didOptions : { + verificationMethods: [{ + algorithm: 'Ed25519' + }] + } + }); + expect(identity.metadata.name).to.equal('Test Identity'); + + // sanity fetch the identity + let storedIdentity = await testHarness.agent.identity.get({ didUri: identity.did.uri }); + expect(storedIdentity).to.exist; + expect(storedIdentity?.metadata.name).to.equal('Test Identity'); + + // update the identity + await testHarness.agent.identity.setMetadataName({ didUri: identity.did.uri, name: 'Updated Identity' }); + + // fetch the updated identity + storedIdentity = await testHarness.agent.identity.get({ didUri: identity.did.uri }); + expect(storedIdentity).to.exist; + expect(storedIdentity?.metadata.name).to.equal('Updated Identity'); + }); + + it('should throw if identity does not exist', async () => { + try { + await testHarness.agent.identity.setMetadataName({ didUri: 'did:method:xyz123', name: 'Updated Identity' }); + expect.fail('Expected an error to be thrown'); + } catch (error: any) { + expect(error.message).to.include('AgentIdentityApi: Failed to set metadata name due to Identity not found'); + } + }); + + it('should throw if name is missing or empty', async () => { + const storeSpy = sinon.spy(testHarness.agent.identity['_store'], 'set'); + const identity = await testHarness.agent.identity.create({ + metadata : { name: 'Test Identity' }, + didMethod : 'jwk', + didOptions : { + verificationMethods: [{ + algorithm: 'Ed25519' + }] + } + }); + + expect(storeSpy.callCount).to.equal(1); + + try { + await testHarness.agent.identity.setMetadataName({ didUri: identity.did.uri, name: '' }); + expect.fail('Expected an error to be thrown'); + } catch (error: any) { + expect(error.message).to.include('Failed to set metadata name due to missing name value'); + } + + try { + await testHarness.agent.identity.setMetadataName({ didUri: identity.did.uri, name: undefined! }); + expect.fail('Expected an error to be thrown'); + } catch (error: any) { + expect(error.message).to.include('Failed to set metadata name due to missing name value'); + } + + // call count should not have changed + expect(storeSpy.callCount).to.equal(1); + + // sanity confirm the name did not change + const storedIdentity = await testHarness.agent.identity.get({ didUri: identity.did.uri }); + expect(storedIdentity).to.exist; + expect(storedIdentity?.metadata.name).to.equal('Test Identity'); + }); + + it('should throw if the updated name is the same as the current name', async () => { + const identity = await testHarness.agent.identity.create({ + metadata : { name: 'Test Identity' }, + didMethod : 'jwk', + didOptions : { + verificationMethods: [{ + algorithm: 'Ed25519' + }] + } + }); + + const storeSpy = sinon.spy(testHarness.agent.identity['_store'], 'set'); + + try { + await testHarness.agent.identity.setMetadataName({ didUri: identity.did.uri, name: 'Test Identity' }); + expect.fail('Expected an error to be thrown'); + } catch (error: any) { + expect(error.message).to.include('AgentIdentityApi: No changes detected'); + } + + // confirm set has not been called + expect(storeSpy.notCalled).to.be.true; + + // sanity update the name to something else + await testHarness.agent.identity.setMetadataName({ didUri: identity.did.uri, name: 'Updated Identity' }); + + // confirm set has been called + expect(storeSpy.calledOnce).to.be.true; + + // confirm the name was updated + const storedIdentity = await testHarness.agent.identity.get({ didUri: identity.did.uri }); + expect(storedIdentity).to.exist; + expect(storedIdentity?.metadata.name).to.equal('Updated Identity'); + }); + }); + describe('connectedIdentity', () => { it('returns a connected Identity', async () => { // create multiple identities, some that are connected, and some that are not