From 0c5265bd31a94f60949766cec7dd65db79908364 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Tue, 25 Feb 2025 14:07:53 +0100 Subject: [PATCH 01/17] feat: remove whitespaces --- .../attributes/AttributesController.ts | 9 +- .../attributes/AttributesController.test.ts | 113 ++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 6e9c13735..c6cef1e6f 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -243,6 +243,7 @@ export class AttributesController extends ConsumptionBaseController { throw ConsumptionCoreErrors.attributes.wrongOwnerOfRepositoryAttribute(); } + params.content.value = this.trimAttributeValue(params.content.value); const parsedParams = CreateRepositoryAttributeParams.from(params); let localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), @@ -404,6 +405,7 @@ export class AttributesController extends ConsumptionBaseController { successorParams: IAttributeSuccessorParams | AttributeSuccessorParamsJSON, validate = true ): Promise<{ predecessor: LocalAttribute; successor: LocalAttribute }> { + successorParams.content.value = this.trimAttributeValue(successorParams.content.value as AttributeValues.Identity.Json | AttributeValues.Identity.Interface); const parsedSuccessorParams = AttributeSuccessorParams.from(successorParams); if (validate) { @@ -1299,7 +1301,7 @@ export class AttributesController extends ConsumptionBaseController { content: { "@type": "IdentityAttribute", owner: this.identity.address.toString(), - value: value + value: this.trimAttributeValue(value) } }); queryForRepositoryAttributeDuplicates["succeededBy"] = { $exists: false }; @@ -1330,6 +1332,11 @@ export class AttributesController extends ConsumptionBaseController { }); } + private trimAttributeValue(value: AttributeValues.Identity.Json | AttributeValues.Identity.Interface): AttributeValues.Identity.Json | AttributeValues.Identity.Interface { + const entries = Object.entries(value).map((entry) => (typeof entry[1] === "string" ? [entry[0], entry[1].trim()] : entry)); + return Object.fromEntries(entries); + } + public async getAttributeTagCollection(): Promise { const backboneTagCollection = (await this.attributeTagClient.getTagCollection()).value; return AttributeTagCollection.from(backboneTagCollection); diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index be5f90689..2e49cdacd 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -4,6 +4,7 @@ import { BirthYear, City, Country, + DisplayName, EMailAddress, HouseNumber, IdentityAttribute, @@ -105,6 +106,21 @@ describe("AttributesController", function () { mockEventBus.expectPublishedEvents(AttributeCreatedEvent); }); + test("should trim whitespace for a RepositoryAttribute", async function () { + const params: ICreateRepositoryAttributeParams = { + content: IdentityAttribute.from({ + value: { + "@type": "DisplayName", + value: " aDisplayName\n" + }, + owner: consumptionController.accountController.identity.address + }) + }; + + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute(params); + expect((repositoryAttribute.content.value as DisplayName).value).toBe("aDisplayName"); + }); + test("should create a new attribute of type SchematizedXML", async function () { const params: ICreateRepositoryAttributeParams = { content: IdentityAttribute.from({ @@ -163,6 +179,45 @@ describe("AttributesController", function () { expect(attributesAfterCreate).toHaveLength(6); }); + test("should trim whitespace when creating a complex RepositoryAttribute and its children", async function () { + const identityAttribute = IdentityAttribute.from({ + value: { + "@type": "StreetAddress", + recipient: "\taRecipient\r", + street: "\vaStreet\f", + houseNo: " aHouseNo\u00a0", + zipCode: " aZipCode\u2028", + city: " aCity ", + country: " DE " + }, + validTo: CoreDate.utc(), + owner: consumptionController.accountController.identity.address + }); + + const address = await consumptionController.attributes.createRepositoryAttribute({ + content: identityAttribute + }); + + expect(address).toBeInstanceOf(LocalAttribute); + expect(address.content).toBeInstanceOf(IdentityAttribute); + expect((address.content.value as StreetAddress).recipient).toBe("aRecipient"); + expect((address.content.value as StreetAddress).street).toBe("aStreet"); + expect((address.content.value as StreetAddress).houseNo).toBe("aHouseNo"); + expect((address.content.value as StreetAddress).zipCode).toBe("aZipCode"); + expect((address.content.value as StreetAddress).city).toBe("aCity"); + expect((address.content.value as StreetAddress).country).toBe("DE"); + + const childAttributes = await consumptionController.attributes.getLocalAttributes({ + parentId: address.id.toString() + }); + expect(childAttributes).toHaveLength(5); + expect((childAttributes[0].content.value as Street).value).toBe("aStreet"); + expect((childAttributes[1].content.value as HouseNumber).value).toBe("aHouseNo"); + expect((childAttributes[2].content.value as ZipCode).value).toBe("aZipCode"); + expect((childAttributes[3].content.value as City).value).toBe("aCity"); + expect((childAttributes[4].content.value as Street).value).toBe("DE"); + }); + test("should trigger an AttributeCreatedEvent for each created child Attribute of a complex Attribute", async function () { await consumptionController.attributes.getLocalAttributes(); @@ -1777,6 +1832,31 @@ describe("AttributesController", function () { expect((successor.content.value.toJSON() as any).value).toBe("US"); }); + test("should trim whitespace when succeeding a repository attribute", async function () { + const predecessor = await consumptionController.attributes.createRepositoryAttribute({ + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: "DE" + }, + owner: consumptionController.accountController.identity.address + }) + }); + const successorParams: IAttributeSuccessorParams = { + content: IdentityAttribute.from({ + value: { + "@type": "Nationality", + value: " US " + }, + owner: consumptionController.accountController.identity.address + }) + }; + + const { predecessor: _updatedPredecessor, successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams); + expect(successor).toBeDefined(); + expect((successor.content.value.toJSON() as any).value).toBe("US"); + }); + test("should succeed a repository attribute updating tags but not the value", async function () { const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ @@ -2031,6 +2111,39 @@ describe("AttributesController", function () { } }); + test("should trim whitespace when succeeding a complex repository attribute", async function () { + const version1ChildValues = [" aNewStreet ", " aNewHouseNo ", " aNewZipCode ", " aNewCity ", " DE "]; + + const repoVersion1Params = { + content: IdentityAttribute.from({ + value: { + "@type": "StreetAddress", + recipient: " aNewRecipient ", + street: version1ChildValues[0], + houseNo: version1ChildValues[1], + zipCode: version1ChildValues[2], + city: version1ChildValues[3], + country: version1ChildValues[4] + }, + owner: consumptionController.accountController.identity.address + }) + }; + + const { successor: repoVersion1 } = await consumptionController.attributes.succeedRepositoryAttribute(repoVersion0.id, repoVersion1Params); + expect((repoVersion1.content.value as StreetAddress).recipient).toBe("aNewRecipient"); + + const repoVersion1ChildAttributes = await consumptionController.attributes.getLocalAttributes({ + parentId: repoVersion1.id.toString() + }); + + const numberOfChildAttributes = version0ChildValues.length; + expect(repoVersion1ChildAttributes).toHaveLength(numberOfChildAttributes); + + for (let i = 0; i < numberOfChildAttributes; i++) { + expect(repoVersion1ChildAttributes[i].content.value.toString()).toStrictEqual(version1ChildValues[i]); + } + }); + test("should succeed a complex repository attribute adding an optional child", async function () { repoVersion1Params = { content: IdentityAttribute.from({ From 77a2f5931a5c9be7be527ddd95b4d9b2ee405bed Mon Sep 17 00:00:00 2001 From: mkuhn Date: Wed, 26 Feb 2025 12:08:48 +0100 Subject: [PATCH 02/17] test: access right thing --- .../attributes/AttributesController.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 2e49cdacd..e0d572f1d 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -188,7 +188,7 @@ describe("AttributesController", function () { houseNo: " aHouseNo\u00a0", zipCode: " aZipCode\u2028", city: " aCity ", - country: " DE " + country: "DE" }, validTo: CoreDate.utc(), owner: consumptionController.accountController.identity.address @@ -201,11 +201,11 @@ describe("AttributesController", function () { expect(address).toBeInstanceOf(LocalAttribute); expect(address.content).toBeInstanceOf(IdentityAttribute); expect((address.content.value as StreetAddress).recipient).toBe("aRecipient"); - expect((address.content.value as StreetAddress).street).toBe("aStreet"); - expect((address.content.value as StreetAddress).houseNo).toBe("aHouseNo"); - expect((address.content.value as StreetAddress).zipCode).toBe("aZipCode"); - expect((address.content.value as StreetAddress).city).toBe("aCity"); - expect((address.content.value as StreetAddress).country).toBe("DE"); + expect((address.content.value as StreetAddress).street.value).toBe("aStreet"); + expect((address.content.value as StreetAddress).houseNo.value).toBe("aHouseNo"); + expect((address.content.value as StreetAddress).zipCode.value).toBe("aZipCode"); + expect((address.content.value as StreetAddress).city.value).toBe("aCity"); + expect((address.content.value as StreetAddress).country.value).toBe("DE"); const childAttributes = await consumptionController.attributes.getLocalAttributes({ parentId: address.id.toString() @@ -1836,8 +1836,8 @@ describe("AttributesController", function () { const predecessor = await consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ value: { - "@type": "Nationality", - value: "DE" + "@type": "GivenName", + value: " aGivenName " }, owner: consumptionController.accountController.identity.address }) @@ -1845,8 +1845,8 @@ describe("AttributesController", function () { const successorParams: IAttributeSuccessorParams = { content: IdentityAttribute.from({ value: { - "@type": "Nationality", - value: " US " + "@type": "GivenName", + value: " anotherGivenName " }, owner: consumptionController.accountController.identity.address }) @@ -1854,7 +1854,7 @@ describe("AttributesController", function () { const { predecessor: _updatedPredecessor, successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams); expect(successor).toBeDefined(); - expect((successor.content.value.toJSON() as any).value).toBe("US"); + expect((successor.content.value.toJSON() as any).value).toBe("anotherGivenName"); }); test("should succeed a repository attribute updating tags but not the value", async function () { @@ -2112,7 +2112,7 @@ describe("AttributesController", function () { }); test("should trim whitespace when succeeding a complex repository attribute", async function () { - const version1ChildValues = [" aNewStreet ", " aNewHouseNo ", " aNewZipCode ", " aNewCity ", " DE "]; + const version1ChildValues = [" aNewStreet ", " aNewHouseNo ", " aNewZipCode ", " aNewCity ", "DE"]; const repoVersion1Params = { content: IdentityAttribute.from({ From 64ffc22d8d53027d120b94bf7b2a19883dc0f875 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Wed, 26 Feb 2025 12:09:50 +0100 Subject: [PATCH 03/17] fix: don't overwrite prototypes --- .../src/modules/attributes/AttributesController.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index c6cef1e6f..e21dbc894 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -243,7 +243,7 @@ export class AttributesController extends ConsumptionBaseController { throw ConsumptionCoreErrors.attributes.wrongOwnerOfRepositoryAttribute(); } - params.content.value = this.trimAttributeValue(params.content.value); + this.trimAttributeValue(params.content.value); const parsedParams = CreateRepositoryAttributeParams.from(params); let localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), @@ -405,7 +405,7 @@ export class AttributesController extends ConsumptionBaseController { successorParams: IAttributeSuccessorParams | AttributeSuccessorParamsJSON, validate = true ): Promise<{ predecessor: LocalAttribute; successor: LocalAttribute }> { - successorParams.content.value = this.trimAttributeValue(successorParams.content.value as AttributeValues.Identity.Json | AttributeValues.Identity.Interface); + this.trimAttributeValue(successorParams.content.value as AttributeValues.Identity.Json | AttributeValues.Identity.Interface); const parsedSuccessorParams = AttributeSuccessorParams.from(successorParams); if (validate) { @@ -1332,9 +1332,10 @@ export class AttributesController extends ConsumptionBaseController { }); } - private trimAttributeValue(value: AttributeValues.Identity.Json | AttributeValues.Identity.Interface): AttributeValues.Identity.Json | AttributeValues.Identity.Interface { - const entries = Object.entries(value).map((entry) => (typeof entry[1] === "string" ? [entry[0], entry[1].trim()] : entry)); - return Object.fromEntries(entries); + private trimAttributeValue(value: AttributeValues.Identity.Json | AttributeValues.Identity.Interface): void { + Object.entries(value) + .filter((entry) => typeof entry[1] === "string") + .forEach((entry) => Object.assign(value, { [entry[0]]: entry[1].trim() })); } public async getAttributeTagCollection(): Promise { From 68fc17ffb2e2a933cf2d13debfe63661953b468c Mon Sep 17 00:00:00 2001 From: mkuhn Date: Thu, 27 Feb 2025 08:26:36 +0100 Subject: [PATCH 04/17] refactor/fix: only trim JSON attribute values --- .../modules/attributes/AttributesController.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index e21dbc894..378b42f3c 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -243,8 +243,13 @@ export class AttributesController extends ConsumptionBaseController { throw ConsumptionCoreErrors.attributes.wrongOwnerOfRepositoryAttribute(); } - this.trimAttributeValue(params.content.value); const parsedParams = CreateRepositoryAttributeParams.from(params); + const trimmedAttributeJSON = { + ...parsedParams.content.toJSON(), + value: this.trimAttributeValue(parsedParams.content.value.toJSON() as AttributeValues.Identity.Json) + }; + parsedParams.content = IdentityAttribute.from(trimmedAttributeJSON); + let localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), createdAt: CoreDate.utc(), @@ -405,8 +410,12 @@ export class AttributesController extends ConsumptionBaseController { successorParams: IAttributeSuccessorParams | AttributeSuccessorParamsJSON, validate = true ): Promise<{ predecessor: LocalAttribute; successor: LocalAttribute }> { - this.trimAttributeValue(successorParams.content.value as AttributeValues.Identity.Json | AttributeValues.Identity.Interface); const parsedSuccessorParams = AttributeSuccessorParams.from(successorParams); + const trimmedAttributeJSON = { + ...parsedSuccessorParams.content.toJSON(), + value: this.trimAttributeValue(parsedSuccessorParams.content.value.toJSON() as AttributeValues.Identity.Json) + }; + parsedSuccessorParams.content = IdentityAttribute.from(trimmedAttributeJSON); if (validate) { const validationResult = await this.validateRepositoryAttributeSuccession(predecessorId, parsedSuccessorParams); @@ -1332,10 +1341,12 @@ export class AttributesController extends ConsumptionBaseController { }); } - private trimAttributeValue(value: AttributeValues.Identity.Json | AttributeValues.Identity.Interface): void { + private trimAttributeValue(value: AttributeValues.Identity.Json): AttributeValues.Identity.Json { Object.entries(value) .filter((entry) => typeof entry[1] === "string") .forEach((entry) => Object.assign(value, { [entry[0]]: entry[1].trim() })); + + return value; } public async getAttributeTagCollection(): Promise { From c14c5eb8e91fb9633da28cfe1f229eeee6d9dee9 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Thu, 27 Feb 2025 08:28:04 +0100 Subject: [PATCH 05/17] test: expect trimmed values --- .../test/modules/attributes/AttributesController.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index e0d572f1d..1e4f824fd 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -2113,6 +2113,7 @@ describe("AttributesController", function () { test("should trim whitespace when succeeding a complex repository attribute", async function () { const version1ChildValues = [" aNewStreet ", " aNewHouseNo ", " aNewZipCode ", " aNewCity ", "DE"]; + const trimmedVersion1ChildValues = version1ChildValues.map((value) => value.trim()); const repoVersion1Params = { content: IdentityAttribute.from({ @@ -2140,7 +2141,7 @@ describe("AttributesController", function () { expect(repoVersion1ChildAttributes).toHaveLength(numberOfChildAttributes); for (let i = 0; i < numberOfChildAttributes; i++) { - expect(repoVersion1ChildAttributes[i].content.value.toString()).toStrictEqual(version1ChildValues[i]); + expect(repoVersion1ChildAttributes[i].content.value.toString()).toStrictEqual(trimmedVersion1ChildValues[i]); } }); From 911132e9bdbda7e20d257f02748208a72a7e2065 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Thu, 27 Feb 2025 10:48:05 +0100 Subject: [PATCH 06/17] test: add trimming and duplicate check tests --- .../itemProcessors/createAttribute/Context.ts | 6 +- ...reateAttributeRequestItemProcessor.test.ts | 17 +++ ...oposeAttributeRequestItemProcessor.test.ts | 49 +++++++ .../ReadAttributeRequestItemProcessor.test.ts | 88 ++++++++++++ .../test/consumption/attributes.test.ts | 136 +++++++++++++++++- 5 files changed, 293 insertions(+), 3 deletions(-) diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts index cc621b166..41be7eb28 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts @@ -219,7 +219,7 @@ export class ThenSteps { return Promise.resolve(); } - public async aRepositoryAttributeIsCreated(): Promise { + public async aRepositoryAttributeIsCreated(value?: AttributeValues.Identity.Json): Promise { expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined(); const createdSharedAttribute = await this.context.consumptionController.attributes.getLocalAttribute( @@ -230,9 +230,10 @@ export class ThenSteps { expect(createdRepositoryAttribute).toBeDefined(); expect(createdRepositoryAttribute!.shareInfo).toBeUndefined(); + if (value) expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(value); } - public async anOwnSharedIdentityAttributeIsCreated(): Promise { + public async anOwnSharedIdentityAttributeIsCreated(value?: AttributeValues.Identity.Json): Promise { expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined(); const createdAttribute = await this.context.consumptionController.attributes.getLocalAttribute( @@ -243,6 +244,7 @@ export class ThenSteps { expect(createdAttribute!.shareInfo).toBeDefined(); expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(this.context.peerAddress.toString()); expect(createdAttribute!.shareInfo!.sourceAttribute).toBeDefined(); + if (value) expect(createdAttribute!.content.value.toJSON()).toStrictEqual(value); } public async anOwnSharedRelationshipAttributeIsCreated(): Promise { diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts index 4d951ff67..e3c8c3e03 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -331,6 +331,14 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.anOwnSharedIdentityAttributeIsCreated(); }); + test("in case of an IdentityAttribute: trims the RepositoryAttribute and the own shared IdentityAttribute", async function () { + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" anEighthGivenName ") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); + await Then.aRepositoryAttributeIsCreated(GivenName.from("anEighthGivenName").toJSON()); + await Then.anOwnSharedIdentityAttributeIsCreated(GivenName.from("anEighthGivenName").toJSON()); + }); + test("in case of an IdentityAttribute that already exists as RepositoryAttribute: creates an own shared IdentityAttribute that links to the existing RepositoryAttribute", async function () { const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aSecondGivenName") }); await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aSecondGivenName") }); @@ -340,6 +348,15 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.theSourceAttributeIdOfTheCreatedOwnSharedIdentityAttributeMatches(repositoryAttribute.id); }); + test("in case of an IdentityAttribute where a RepositoryAttribute exists after trimming: creates an own shared IdentityAttribute that links to the existing RepositoryAttribute", async function () { + const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aNinthGivenName") }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" aNinthGivenName ") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); + await Then.anOwnSharedIdentityAttributeIsCreated(); + await Then.theSourceAttributeIdOfTheCreatedOwnSharedIdentityAttributeMatches(repositoryAttribute.id); + }); + test("in case of an IdentityAttribute that already exists as RepositoryAttribute with different tags: merges tags", async function () { await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, tags: ["tag1", "tag2"], value: GivenName.from("aThirdGivenName") }); await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, tags: ["tag1", "tag3"], value: GivenName.from("aThirdGivenName") }); diff --git a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts index f29f7aed1..1b50a1bb6 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts @@ -955,6 +955,55 @@ describe("ProposeAttributeRequestItemProcessor", function () { expect(createdRepositoryAttribute).toBeDefined(); }); + test("in case of accepting with a new IdentityAttribute, trim the newly created RepositoryAttribute as well as the copy for the Recipient", async function () { + const sender = CoreAddress.from("Sender"); + const recipient = accountController.identity.address; + + const requestItem = ProposeAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }), + attribute: TestObjectFactory.createIdentityAttribute() + }); + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptProposeAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + attribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: " aGivenName " + } + } + }; + + const result = await processor.accept(requestItem, acceptParams, incomingRequest); + const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ProposeAttributeAcceptResponseItem).attributeId); + + expect(createdSharedAttribute).toBeDefined(); + expect(createdSharedAttribute!.shareInfo).toBeDefined(); + expect(createdSharedAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); + expect(createdSharedAttribute!.shareInfo!.sourceAttribute).toBeDefined(); + expect(createdSharedAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + + const createdRepositoryAttribute = await consumptionController.attributes.getLocalAttribute(createdSharedAttribute!.shareInfo!.sourceAttribute!); + expect(createdRepositoryAttribute).toBeDefined(); + expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + }); + test("accept with new RelationshipAttribute", async function () { const sender = CoreAddress.from("Sender"); const recipient = accountController.identity.address; diff --git a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts index 54d4b73f4..3f8d6f98a 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts @@ -683,6 +683,48 @@ describe("ReadAttributeRequestItemProcessor", function () { }); }); + test("returns an error trying to answer with a new IdentityAttribute that after trimming is a duplicate of an existing RepositoryAttribute", async function () { + const repositoryAttribute = await consumptionController.attributes.createRepositoryAttribute({ + content: TestObjectFactory.createIdentityAttribute({ + owner: recipient, + value: GivenName.fromAny({ value: "aGivenName" }) + }) + }); + + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const request = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: TestObjectFactory.createIdentityAttribute({ + owner: recipient, + value: GivenName.fromAny({ value: " aGivenName " }) + }).toJSON() + }; + + const result = await processor.canAccept(requestItem, acceptParams, request); + + expect(result).errorValidationResult({ + code: "error.consumption.requests.invalidAcceptParameters", + message: `The new Attribute cannot be created because it has the same content.value as the already existing RepositoryAttribute with id '${repositoryAttribute.id.toString()}'.` + }); + }); + test("returns success responding with a new Attribute that has additional tags than those requested by the IdentityAttributeQuery", async function () { const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, @@ -2197,6 +2239,52 @@ describe("ReadAttributeRequestItemProcessor", function () { expect(createdRepositoryAttribute).toBeDefined(); }); + test("trim the new IdentityAttribute", async function () { + const requestItem = ReadAttributeRequestItem.from({ + mustBeAccepted: true, + query: IdentityAttributeQuery.from({ valueType: "GivenName" }) + }); + const requestId = await ConsumptionIds.request.generate(); + const incomingRequest = LocalRequest.from({ + id: requestId, + createdAt: CoreDate.utc(), + isOwn: false, + peer: sender, + status: LocalRequestStatus.DecisionRequired, + content: Request.from({ + id: requestId, + items: [requestItem] + }), + statusLog: [] + }); + + const acceptParams: AcceptReadAttributeRequestItemParametersWithNewAttributeJSON = { + accept: true, + newAttribute: { + "@type": "IdentityAttribute", + owner: recipient.toString(), + value: { + "@type": "GivenName", + value: " aGivenName " + } + } + }; + + const result = await processor.accept(requestItem, acceptParams, incomingRequest); + expect(result).toBeInstanceOf(ReadAttributeAcceptResponseItem); + const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ReadAttributeAcceptResponseItem).attributeId); + + expect(createdSharedAttribute).toBeDefined(); + expect(createdSharedAttribute!.shareInfo).toBeDefined(); + expect(createdSharedAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); + expect(createdSharedAttribute!.shareInfo!.sourceAttribute).toBeDefined(); + expect(createdSharedAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + + const createdRepositoryAttribute = await consumptionController.attributes.getLocalAttribute(createdSharedAttribute!.shareInfo!.sourceAttribute!); + expect(createdRepositoryAttribute).toBeDefined(); + expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + }); + test("accept with new RelationshipAttribute", async function () { const requestItem = ReadAttributeRequestItem.from({ mustBeAccepted: true, diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 272de016b..73a4c9b67 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -11,6 +11,7 @@ import { RequestItemJSONDerivations, ShareAttributeRequestItem, ShareAttributeRequestItemJSON, + StreetAddressJSON, StreetJSON, ThirdPartyRelationshipAttributeQuery, ThirdPartyRelationshipAttributeQueryOwner, @@ -784,6 +785,29 @@ describe(CanCreateRepositoryAttributeUseCase.name, () => { expect(result.value.code).toBe("error.runtime.attributes.cannotCreateDuplicateRepositoryAttribute"); }); + test("should not allow to create a RepositoryAttribute if there exists a duplicate after trimming", async () => { + const canCreateUntrimmedRepositoryAttributeRequest: CanCreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "GivenName", + value: " aGivenName " + }, + tags: ["tag1", "tag2"] + } + }; + const repositoryAttribute = (await services1.consumption.attributes.createRepositoryAttribute(canCreateRepositoryAttributeRequest)).value; + + const result = await services1.consumption.attributes.canCreateRepositoryAttribute(canCreateUntrimmedRepositoryAttributeRequest); + + assert(!result.value.isSuccess); + + expect(result.value.isSuccess).toBe(false); + expect(result.value.message).toBe( + `The RepositoryAttribute cannot be created because it has the same content.value as the already existing RepositoryAttribute with id '${repositoryAttribute.id.toString()}'.` + ); + expect(result.value.code).toBe("error.runtime.attributes.cannotCreateDuplicateRepositoryAttribute"); + }); + test("should not allow to create a duplicate RepositoryAttribute even if the tags/validFrom/validTo are different", async () => { const createAttributeRequest: CreateRepositoryAttributeRequest = { content: { @@ -905,7 +929,7 @@ describe(CreateRepositoryAttributeUseCase.name, () => { content: { value: { "@type": "GivenName", - value: "Petra Pan" + value: "aGivenName" }, tags: ["tag1", "tag2"] } @@ -918,6 +942,24 @@ describe(CreateRepositoryAttributeUseCase.name, () => { await services1.eventBus.waitForEvent(AttributeCreatedEvent, (e) => e.data.id === attribute.id); }); + test("should trim a repository attribute before creation", async () => { + const request: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "GivenName", + value: " aGivenName " + }, + tags: ["tag1", "tag2"] + } + }; + + const result = await services1.consumption.attributes.createRepositoryAttribute(request); + expect(result).toBeSuccessful(); + const attribute = result.value; + expect(attribute.content.value).toBe("aGivenName"); + await services1.eventBus.waitForEvent(AttributeCreatedEvent, (e) => e.data.id === attribute.id); + }); + test("should create LocalAttributes for each child of a complex repository attribute", async function () { const attributesBeforeCreate = await services1.consumption.attributes.getAttributes({}); const nrAttributesBeforeCreate = attributesBeforeCreate.value.length; @@ -971,6 +1013,67 @@ describe(CreateRepositoryAttributeUseCase.name, () => { await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Country"); }); + test("should trim LocalAttributes for a complex repository attribute and for each child during creation", async function () { + const attributesBeforeCreate = await services1.consumption.attributes.getAttributes({}); + const nrAttributesBeforeCreate = attributesBeforeCreate.value.length; + + const createRepositoryAttributeParams: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "StreetAddress", + recipient: " aRecipient ", + street: " aStreet ", + houseNo: " aHouseNo ", + zipCode: " aZipCode ", + city: " aCity ", + country: "DE" + } + } + }; + const createRepositoryAttributeResult = await services1.consumption.attributes.createRepositoryAttribute(createRepositoryAttributeParams); + expect(createRepositoryAttributeResult).toBeSuccessful(); + const complexRepoAttribute = createRepositoryAttributeResult.value; + + expect((complexRepoAttribute.content.value as StreetAddressJSON).recipient).toBe("aRecipient"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).recipient).toBe("aRecipient"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).street).toBe("aStreet"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).houseNo).toBe("aHouseNo"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).zipCode).toBe("aZipCode"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).city).toBe("aCity"); + expect((complexRepoAttribute.content.value as StreetAddressJSON).country).toBe("DE"); + + const childAttributes = ( + await services1.consumption.attributes.getAttributes({ + query: { + parentId: complexRepoAttribute.id + } + }) + ).value; + + expect(childAttributes).toHaveLength(5); + expect(childAttributes[0].content.value["@type"]).toBe("Street"); + expect((childAttributes[0].content.value as StreetJSON).value).toBe("aStreet"); + expect(childAttributes[1].content.value["@type"]).toBe("HouseNumber"); + expect((childAttributes[1].content.value as HouseNumberJSON).value).toBe("aHouseNo"); + expect(childAttributes[2].content.value["@type"]).toBe("ZipCode"); + expect((childAttributes[2].content.value as ZipCodeJSON).value).toBe("aZipCode"); + expect(childAttributes[3].content.value["@type"]).toBe("City"); + expect((childAttributes[3].content.value as CityJSON).value).toBe("aCity"); + expect(childAttributes[4].content.value["@type"]).toBe("Country"); + expect((childAttributes[4].content.value as CountryJSON).value).toBe("DE"); + + const attributesAfterCreate = (await services1.consumption.attributes.getAttributes({})).value; + const nrAttributesAfterCreate = attributesAfterCreate.length; + expect(nrAttributesAfterCreate).toBe(nrAttributesBeforeCreate + 6); + + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "StreetAddress"); + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Street"); + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "HouseNumber"); + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "ZipCode"); + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "City"); + await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Country"); + }); + test("should create a RepositoryAttribute that is the default if it is the first of its value type", async () => { const request: CreateRepositoryAttributeRequest = { content: { @@ -1123,6 +1226,37 @@ describe(CreateRepositoryAttributeUseCase.name, () => { ); }); + test("should not create a RepositoryAttribute if there would be a duplicate after trimming", async () => { + const request: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "GivenName", + value: "aGivenName" + }, + tags: ["tag1", "tag2"] + } + }; + + const untrimmedRequest: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "GivenName", + value: " aGivenName " + }, + tags: ["tag1", "tag2"] + } + }; + + const result = await services1.consumption.attributes.createRepositoryAttribute(request); + expect(result).toBeSuccessful(); + + const result2 = await services1.consumption.attributes.createRepositoryAttribute(untrimmedRequest); + expect(result2).toBeAnError( + `The RepositoryAttribute cannot be created because it has the same content.value as the already existing RepositoryAttribute with id '${result.value.id.toString()}'.`, + "error.runtime.attributes.cannotCreateDuplicateRepositoryAttribute" + ); + }); + test("should not prevent the creation when the RepositoryAttribute duplicate got succeeded", async () => { const request: CreateRepositoryAttributeRequest = { content: { From 62fade1a5120364023fe42d4644812e1f4e540a2 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Thu, 27 Feb 2025 17:31:43 +0100 Subject: [PATCH 07/17] test: fix test --- packages/runtime/test/consumption/attributes.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 73a4c9b67..5937b73e2 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -3,6 +3,7 @@ import { CityJSON, CountryJSON, DeleteAttributeRequestItem, + GivenNameJSON, HouseNumberJSON, ReadAttributeRequestItem, ReadAttributeRequestItemJSON, @@ -956,7 +957,7 @@ describe(CreateRepositoryAttributeUseCase.name, () => { const result = await services1.consumption.attributes.createRepositoryAttribute(request); expect(result).toBeSuccessful(); const attribute = result.value; - expect(attribute.content.value).toBe("aGivenName"); + expect((attribute.content.value as GivenNameJSON).value).toBe("aGivenName"); await services1.eventBus.waitForEvent(AttributeCreatedEvent, (e) => e.data.id === attribute.id); }); From 495f38a5b0fff64a4ccb084c6a1f0adec1c45f02 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Fri, 28 Feb 2025 11:09:10 +0100 Subject: [PATCH 08/17] refactor/fix: remove whitespaces everywhere --- .../modules/attributes/AttributesController.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 378b42f3c..4d663547e 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -244,11 +244,11 @@ export class AttributesController extends ConsumptionBaseController { } const parsedParams = CreateRepositoryAttributeParams.from(params); - const trimmedAttributeJSON = { + const trimmedAttribute = { ...parsedParams.content.toJSON(), value: this.trimAttributeValue(parsedParams.content.value.toJSON() as AttributeValues.Identity.Json) }; - parsedParams.content = IdentityAttribute.from(trimmedAttributeJSON); + parsedParams.content = IdentityAttribute.from(trimmedAttribute); let localAttribute = LocalAttribute.from({ id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()), @@ -1306,11 +1306,12 @@ export class AttributesController extends ConsumptionBaseController { } public async getRepositoryAttributeWithSameValue(value: AttributeValues.Identity.Json): Promise { + const trimmedValue = this.trimAttributeValue(value); const queryForRepositoryAttributeDuplicates = flattenObject({ content: { "@type": "IdentityAttribute", owner: this.identity.address.toString(), - value: this.trimAttributeValue(value) + value: trimmedValue } }); queryForRepositoryAttributeDuplicates["succeededBy"] = { $exists: false }; @@ -1318,7 +1319,7 @@ export class AttributesController extends ConsumptionBaseController { const matchingRepositoryAttributes = await this.getLocalAttributes(queryForRepositoryAttributeDuplicates); - const repositoryAttributeDuplicate = matchingRepositoryAttributes.find((duplicate) => _.isEqual(duplicate.content.value.toJSON(), value)); + const repositoryAttributeDuplicate = matchingRepositoryAttributes.find((duplicate) => _.isEqual(duplicate.content.value.toJSON(), trimmedValue)); return repositoryAttributeDuplicate; } @@ -1342,11 +1343,8 @@ export class AttributesController extends ConsumptionBaseController { } private trimAttributeValue(value: AttributeValues.Identity.Json): AttributeValues.Identity.Json { - Object.entries(value) - .filter((entry) => typeof entry[1] === "string") - .forEach((entry) => Object.assign(value, { [entry[0]]: entry[1].trim() })); - - return value; + const trimmedEntries = Object.entries(value).map((entry) => (typeof entry[1] === "string" ? [entry[0], entry[1].trim()] : entry)); + return Object.fromEntries(trimmedEntries) as AttributeValues.Identity.Json; } public async getAttributeTagCollection(): Promise { From 82582efd073cf1c6b6a45ee2681f8c1756097896 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Fri, 28 Feb 2025 11:10:05 +0100 Subject: [PATCH 09/17] test: improve tests --- .../attributes/AttributesController.test.ts | 5 +++++ ...reateAttributeRequestItemProcessor.test.ts | 19 ++++++++++++++----- ...oposeAttributeRequestItemProcessor.test.ts | 9 +++------ .../ReadAttributeRequestItemProcessor.test.ts | 9 +++------ .../test/consumption/attributes.test.ts | 1 - 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 1e4f824fd..8f15dc052 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -2132,6 +2132,11 @@ describe("AttributesController", function () { const { successor: repoVersion1 } = await consumptionController.attributes.succeedRepositoryAttribute(repoVersion0.id, repoVersion1Params); expect((repoVersion1.content.value as StreetAddress).recipient).toBe("aNewRecipient"); + expect((repoVersion1.content.value as StreetAddress).street.value).toBe("aStreet"); + expect((repoVersion1.content.value as StreetAddress).houseNo.value).toBe("aHouseNo"); + expect((repoVersion1.content.value as StreetAddress).zipCode.value).toBe("aZipCode"); + expect((repoVersion1.content.value as StreetAddress).city.value).toBe("aCity"); + expect((repoVersion1.content.value as StreetAddress).country.value).toBe("DE"); const repoVersion1ChildAttributes = await consumptionController.attributes.getLocalAttributes({ parentId: repoVersion1.id.toString() diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts index 1cbb1822d..db93d5c2e 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -334,11 +334,11 @@ describe("CreateAttributeRequestItemProcessor", function () { }); test("in case of an IdentityAttribute: trims the RepositoryAttribute and the own shared IdentityAttribute", async function () { - await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" anEighthGivenName ") }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" aGivenName ") }); await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); - await Then.aRepositoryAttributeIsCreated(GivenName.from("anEighthGivenName").toJSON()); - await Then.anOwnSharedIdentityAttributeIsCreated(GivenName.from("anEighthGivenName").toJSON()); + await Then.aRepositoryAttributeIsCreated(GivenName.from("aGivenName").toJSON()); + await Then.anOwnSharedIdentityAttributeIsCreated(GivenName.from("aGivenName").toJSON()); }); test("in case of an IdentityAttribute that already exists as RepositoryAttribute: creates an own shared IdentityAttribute that links to the existing RepositoryAttribute", async function () { @@ -351,8 +351,8 @@ describe("CreateAttributeRequestItemProcessor", function () { }); test("in case of an IdentityAttribute where a RepositoryAttribute exists after trimming: creates an own shared IdentityAttribute that links to the existing RepositoryAttribute", async function () { - const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aNinthGivenName") }); - await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" aNinthGivenName ") }); + const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" aGivenName ") }); await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); await Then.anOwnSharedIdentityAttributeIsCreated(); @@ -368,6 +368,15 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.theTagsOfTheRepositoryAttributeMatch(["tag1", "tag2", "tag3"]); }); + test("in case of an IdentityAttribute that after trimming already exists as RepositoryAttribute with different tags: merges tags", async function () { + await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, tags: ["tag1", "tag2"], value: GivenName.from("aGivenName") }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, tags: ["tag1", "tag3"], value: GivenName.from(" aGivenName ") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); + await Then.anOwnSharedIdentityAttributeIsCreated(GivenName.from("aGivenName").toJSON()); + await Then.theTagsOfTheRepositoryAttributeMatch(["tag1", "tag2", "tag3"]); + }); + test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute: returns an AttributeAlreadySharedAcceptResponseItem", async function () { const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); const ownSharedIdentityAttribute = await Given.anOwnSharedIdentityAttribute({ sourceAttributeId: repositoryAttribute.id, peer: TestIdentity.PEER }); diff --git a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts index 1b50a1bb6..2e2d8e27c 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/proposeAttribute/ProposeAttributeRequestItemProcessor.test.ts @@ -991,17 +991,14 @@ describe("ProposeAttributeRequestItemProcessor", function () { }; const result = await processor.accept(requestItem, acceptParams, incomingRequest); - const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ProposeAttributeAcceptResponseItem).attributeId); + const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ProposeAttributeAcceptResponseItem).attributeId); expect(createdSharedAttribute).toBeDefined(); - expect(createdSharedAttribute!.shareInfo).toBeDefined(); - expect(createdSharedAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); - expect(createdSharedAttribute!.shareInfo!.sourceAttribute).toBeDefined(); - expect(createdSharedAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + expect((createdSharedAttribute!.content.value as GivenName).value).toBe("aGivenName"); const createdRepositoryAttribute = await consumptionController.attributes.getLocalAttribute(createdSharedAttribute!.shareInfo!.sourceAttribute!); expect(createdRepositoryAttribute).toBeDefined(); - expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + expect((createdRepositoryAttribute!.content.value as GivenName).value).toBe("aGivenName"); }); test("accept with new RelationshipAttribute", async function () { diff --git a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts index 3f8d6f98a..6b7a64593 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/readAttribute/ReadAttributeRequestItemProcessor.test.ts @@ -2272,17 +2272,14 @@ describe("ReadAttributeRequestItemProcessor", function () { const result = await processor.accept(requestItem, acceptParams, incomingRequest); expect(result).toBeInstanceOf(ReadAttributeAcceptResponseItem); - const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ReadAttributeAcceptResponseItem).attributeId); + const createdSharedAttribute = await consumptionController.attributes.getLocalAttribute((result as ReadAttributeAcceptResponseItem).attributeId); expect(createdSharedAttribute).toBeDefined(); - expect(createdSharedAttribute!.shareInfo).toBeDefined(); - expect(createdSharedAttribute!.shareInfo!.peer.toString()).toStrictEqual(incomingRequest.peer.toString()); - expect(createdSharedAttribute!.shareInfo!.sourceAttribute).toBeDefined(); - expect(createdSharedAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + expect((createdSharedAttribute!.content.value as GivenName).value).toBe("aGivenName"); const createdRepositoryAttribute = await consumptionController.attributes.getLocalAttribute(createdSharedAttribute!.shareInfo!.sourceAttribute!); expect(createdRepositoryAttribute).toBeDefined(); - expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(GivenName.from({ value: "aGivenName" }).toJSON()); + expect((createdRepositoryAttribute!.content.value as GivenName).value).toBe("aGivenName"); }); test("accept with new RelationshipAttribute", async function () { diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 5937b73e2..f08890293 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1035,7 +1035,6 @@ describe(CreateRepositoryAttributeUseCase.name, () => { expect(createRepositoryAttributeResult).toBeSuccessful(); const complexRepoAttribute = createRepositoryAttributeResult.value; - expect((complexRepoAttribute.content.value as StreetAddressJSON).recipient).toBe("aRecipient"); expect((complexRepoAttribute.content.value as StreetAddressJSON).recipient).toBe("aRecipient"); expect((complexRepoAttribute.content.value as StreetAddressJSON).street).toBe("aStreet"); expect((complexRepoAttribute.content.value as StreetAddressJSON).houseNo).toBe("aHouseNo"); From 22bffa2522ba4777a1517967331963e14a1fb65d Mon Sep 17 00:00:00 2001 From: mkuhn Date: Fri, 28 Feb 2025 12:50:02 +0100 Subject: [PATCH 10/17] test: fix/add tests --- .../attributes/AttributesController.test.ts | 8 ++--- .../test/consumption/attributes.test.ts | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 8f15dc052..c5a87b846 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -2132,10 +2132,10 @@ describe("AttributesController", function () { const { successor: repoVersion1 } = await consumptionController.attributes.succeedRepositoryAttribute(repoVersion0.id, repoVersion1Params); expect((repoVersion1.content.value as StreetAddress).recipient).toBe("aNewRecipient"); - expect((repoVersion1.content.value as StreetAddress).street.value).toBe("aStreet"); - expect((repoVersion1.content.value as StreetAddress).houseNo.value).toBe("aHouseNo"); - expect((repoVersion1.content.value as StreetAddress).zipCode.value).toBe("aZipCode"); - expect((repoVersion1.content.value as StreetAddress).city.value).toBe("aCity"); + expect((repoVersion1.content.value as StreetAddress).street.value).toBe("aNewStreet"); + expect((repoVersion1.content.value as StreetAddress).houseNo.value).toBe("aNewHouseNo"); + expect((repoVersion1.content.value as StreetAddress).zipCode.value).toBe("aNewZipCode"); + expect((repoVersion1.content.value as StreetAddress).city.value).toBe("aNewCity"); expect((repoVersion1.content.value as StreetAddress).country.value).toBe("DE"); const repoVersion1ChildAttributes = await consumptionController.attributes.getLocalAttributes({ diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index f08890293..2e2b0447e 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1697,6 +1697,37 @@ describe(SucceedRepositoryAttributeUseCase.name, () => { }); }); + test("should trim the successor of a repository attribute", async () => { + const createAttributeRequest: CreateRepositoryAttributeRequest = { + content: { + value: { + "@type": "GivenName", + value: "aGivenName" + }, + tags: ["tag1", "tag2"] + } + }; + const predecessor = (await services1.consumption.attributes.createRepositoryAttribute(createAttributeRequest)).value; + + const succeedAttributeRequest: SucceedRepositoryAttributeRequest = { + predecessorId: predecessor.id.toString(), + successorContent: { + value: { + "@type": "GivenName", + value: " anotherGivenName " + }, + tags: ["tag1", "tag2"] + } + }; + const result = await services1.consumption.attributes.succeedRepositoryAttribute(succeedAttributeRequest); + expect(result.isError).toBe(false); + const { predecessor: updatedPredecessor, successor } = result.value; + expect((successor as any).content.value.value).toBe("anotherGivenName"); + await services1.eventBus.waitForEvent(RepositoryAttributeSucceededEvent, (e) => { + return e.data.predecessor.id === updatedPredecessor.id && e.data.successor.id === successor.id; + }); + }); + test("should throw if predecessor id is invalid", async () => { const succeedAttributeRequest: SucceedRepositoryAttributeRequest = { predecessorId: CoreId.from("faulty").toString(), From 13970925b744399ed2a1ad6cf6e17a35f747c0e1 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Fri, 28 Feb 2025 12:50:11 +0100 Subject: [PATCH 11/17] refactor: variable naming --- .../src/modules/attributes/AttributesController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 4d663547e..3497ab7c1 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -411,11 +411,11 @@ export class AttributesController extends ConsumptionBaseController { validate = true ): Promise<{ predecessor: LocalAttribute; successor: LocalAttribute }> { const parsedSuccessorParams = AttributeSuccessorParams.from(successorParams); - const trimmedAttributeJSON = { + const trimmedAttribute = { ...parsedSuccessorParams.content.toJSON(), value: this.trimAttributeValue(parsedSuccessorParams.content.value.toJSON() as AttributeValues.Identity.Json) }; - parsedSuccessorParams.content = IdentityAttribute.from(trimmedAttributeJSON); + parsedSuccessorParams.content = IdentityAttribute.from(trimmedAttribute); if (validate) { const validationResult = await this.validateRepositoryAttributeSuccession(predecessorId, parsedSuccessorParams); From b1aaac994aee07ca2093993918a389e95b3e1d13 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 3 Mar 2025 13:45:26 +0100 Subject: [PATCH 12/17] test: remove unneeded checks --- .../test/modules/attributes/AttributesController.test.ts | 2 +- packages/runtime/test/consumption/attributes.test.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index c5a87b846..294bcc709 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -1852,7 +1852,7 @@ describe("AttributesController", function () { }) }; - const { predecessor: _updatedPredecessor, successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams); + const { successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams); expect(successor).toBeDefined(); expect((successor.content.value.toJSON() as any).value).toBe("anotherGivenName"); }); diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index f9a30ebb4..96fbe112d 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1015,9 +1015,6 @@ describe(CreateRepositoryAttributeUseCase.name, () => { }); test("should trim LocalAttributes for a complex repository attribute and for each child during creation", async function () { - const attributesBeforeCreate = await services1.consumption.attributes.getAttributes({}); - const nrAttributesBeforeCreate = attributesBeforeCreate.value.length; - const createRepositoryAttributeParams: CreateRepositoryAttributeRequest = { content: { value: { @@ -1062,10 +1059,6 @@ describe(CreateRepositoryAttributeUseCase.name, () => { expect(childAttributes[4].content.value["@type"]).toBe("Country"); expect((childAttributes[4].content.value as CountryJSON).value).toBe("DE"); - const attributesAfterCreate = (await services1.consumption.attributes.getAttributes({})).value; - const nrAttributesAfterCreate = attributesAfterCreate.length; - expect(nrAttributesAfterCreate).toBe(nrAttributesBeforeCreate + 6); - await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "StreetAddress"); await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Street"); await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "HouseNumber"); From 9d092edea020e8e219ea7910c58f6a14481e4bdd Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 3 Mar 2025 13:45:39 +0100 Subject: [PATCH 13/17] refactor: method ordering --- .../src/modules/attributes/AttributesController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/consumption/src/modules/attributes/AttributesController.ts b/packages/consumption/src/modules/attributes/AttributesController.ts index 8ff279861..7e95c8e90 100644 --- a/packages/consumption/src/modules/attributes/AttributesController.ts +++ b/packages/consumption/src/modules/attributes/AttributesController.ts @@ -1329,6 +1329,11 @@ export class AttributesController extends ConsumptionBaseController { return repositoryAttributeDuplicate; } + private trimAttributeValue(value: AttributeValues.Identity.Json): AttributeValues.Identity.Json { + const trimmedEntries = Object.entries(value).map((entry) => (typeof entry[1] === "string" ? [entry[0], entry[1].trim()] : entry)); + return Object.fromEntries(trimmedEntries) as AttributeValues.Identity.Json; + } + public async getRelationshipAttributesOfValueTypeToPeerWithGivenKeyAndOwner(key: string, owner: CoreAddress, valueType: string, peer: CoreAddress): Promise { return await this.getLocalAttributes({ "content.@type": "RelationshipAttribute", @@ -1348,11 +1353,6 @@ export class AttributesController extends ConsumptionBaseController { }); } - private trimAttributeValue(value: AttributeValues.Identity.Json): AttributeValues.Identity.Json { - const trimmedEntries = Object.entries(value).map((entry) => (typeof entry[1] === "string" ? [entry[0], entry[1].trim()] : entry)); - return Object.fromEntries(trimmedEntries) as AttributeValues.Identity.Json; - } - public async getAttributeTagCollection(): Promise { const backboneTagCollection = (await this.attributeTagClient.getTagCollection()).value; return AttributeTagCollection.from(backboneTagCollection); From b07c16f63020003f0ff7d00c61fdbb74bc73110e Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 3 Mar 2025 13:49:14 +0100 Subject: [PATCH 14/17] test: remove more checks --- .../test/modules/attributes/AttributesController.test.ts | 5 ----- packages/runtime/test/consumption/attributes.test.ts | 8 -------- 2 files changed, 13 deletions(-) diff --git a/packages/consumption/test/modules/attributes/AttributesController.test.ts b/packages/consumption/test/modules/attributes/AttributesController.test.ts index 294bcc709..b02ff2260 100644 --- a/packages/consumption/test/modules/attributes/AttributesController.test.ts +++ b/packages/consumption/test/modules/attributes/AttributesController.test.ts @@ -198,24 +198,19 @@ describe("AttributesController", function () { content: identityAttribute }); - expect(address).toBeInstanceOf(LocalAttribute); - expect(address.content).toBeInstanceOf(IdentityAttribute); expect((address.content.value as StreetAddress).recipient).toBe("aRecipient"); expect((address.content.value as StreetAddress).street.value).toBe("aStreet"); expect((address.content.value as StreetAddress).houseNo.value).toBe("aHouseNo"); expect((address.content.value as StreetAddress).zipCode.value).toBe("aZipCode"); expect((address.content.value as StreetAddress).city.value).toBe("aCity"); - expect((address.content.value as StreetAddress).country.value).toBe("DE"); const childAttributes = await consumptionController.attributes.getLocalAttributes({ parentId: address.id.toString() }); - expect(childAttributes).toHaveLength(5); expect((childAttributes[0].content.value as Street).value).toBe("aStreet"); expect((childAttributes[1].content.value as HouseNumber).value).toBe("aHouseNo"); expect((childAttributes[2].content.value as ZipCode).value).toBe("aZipCode"); expect((childAttributes[3].content.value as City).value).toBe("aCity"); - expect((childAttributes[4].content.value as Street).value).toBe("DE"); }); test("should trigger an AttributeCreatedEvent for each created child Attribute of a complex Attribute", async function () { diff --git a/packages/runtime/test/consumption/attributes.test.ts b/packages/runtime/test/consumption/attributes.test.ts index 96fbe112d..94b01c9f5 100644 --- a/packages/runtime/test/consumption/attributes.test.ts +++ b/packages/runtime/test/consumption/attributes.test.ts @@ -1037,7 +1037,6 @@ describe(CreateRepositoryAttributeUseCase.name, () => { expect((complexRepoAttribute.content.value as StreetAddressJSON).houseNo).toBe("aHouseNo"); expect((complexRepoAttribute.content.value as StreetAddressJSON).zipCode).toBe("aZipCode"); expect((complexRepoAttribute.content.value as StreetAddressJSON).city).toBe("aCity"); - expect((complexRepoAttribute.content.value as StreetAddressJSON).country).toBe("DE"); const childAttributes = ( await services1.consumption.attributes.getAttributes({ @@ -1047,17 +1046,10 @@ describe(CreateRepositoryAttributeUseCase.name, () => { }) ).value; - expect(childAttributes).toHaveLength(5); - expect(childAttributes[0].content.value["@type"]).toBe("Street"); expect((childAttributes[0].content.value as StreetJSON).value).toBe("aStreet"); - expect(childAttributes[1].content.value["@type"]).toBe("HouseNumber"); expect((childAttributes[1].content.value as HouseNumberJSON).value).toBe("aHouseNo"); - expect(childAttributes[2].content.value["@type"]).toBe("ZipCode"); expect((childAttributes[2].content.value as ZipCodeJSON).value).toBe("aZipCode"); - expect(childAttributes[3].content.value["@type"]).toBe("City"); expect((childAttributes[3].content.value as CityJSON).value).toBe("aCity"); - expect(childAttributes[4].content.value["@type"]).toBe("Country"); - expect((childAttributes[4].content.value as CountryJSON).value).toBe("DE"); await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "StreetAddress"); await expect(services1.eventBus).toHavePublished(AttributeCreatedEvent, (e) => e.data.content.value["@type"] === "Street"); From cb8876a055c7e8c0a1fbcca3020bbf934c3baa09 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Mon, 3 Mar 2025 15:49:06 +0100 Subject: [PATCH 15/17] test: add createAttributeRequestItem tests --- .../itemProcessors/createAttribute/Context.ts | 8 ++++++ ...reateAttributeRequestItemProcessor.test.ts | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts index 41be7eb28..cd8c960c3 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts @@ -297,6 +297,14 @@ export class ThenSteps { expect((repositoryAttribute!.content as IdentityAttribute).tags?.sort()).toStrictEqual(tags.sort()); } + public async theSuccessorAttributeValueMatches(value: AttributeValues.Json): Promise { + const attribute = await this.context.consumptionController.attributes.getLocalAttribute( + (this.context.responseItemAfterAction as AttributeSuccessionAcceptResponseItem).successorId + ); + + expect(attribute!.content.value.toJSON()).toStrictEqual(value); + } + public theCreatedAttributeHasTheAttributeIdFromTheResponseItem(): Promise { expect(this.context.createdAttributeAfterAction.id.toString()).toStrictEqual((this.context.givenResponseItem as CreateAttributeAcceptResponseItem).attributeId.toString()); diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts index db93d5c2e..d1c2ba236 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -386,6 +386,15 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.theIdOfTheAlreadySharedAttributeMatches(ownSharedIdentityAttribute.id); }); + test("in case of an IdentityAttribute that after trimming already exists as own shared IdentityAttribute: returns an AttributeAlreadySharedAcceptResponseItem", async function () { + const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); + const ownSharedIdentityAttribute = await Given.anOwnSharedIdentityAttribute({ sourceAttributeId: repositoryAttribute.id, peer: TestIdentity.PEER }); + await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from(" aGivenName ") }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("AttributeAlreadySharedAcceptResponseItem"); + await Then.theIdOfTheAlreadySharedAttributeMatches(ownSharedIdentityAttribute.id); + }); + test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute with different tags: returns an AttributeSuccessionAcceptResponseItem", async function () { const repositoryAttribute = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, @@ -403,6 +412,24 @@ describe("CreateAttributeRequestItemProcessor", function () { await Then.theTagsOfTheSucceededRepositoryAttributeMatch(["tag1", "tag2", "tag3"]); }); + test("in case of an IdentityAttribute that after trimming already exists as own shared IdentityAttribute with different tags: returns an AttributeSuccessionAcceptResponseItem", async function () { + const repositoryAttribute = await Given.aRepositoryAttribute({ + attributeOwner: TestIdentity.CURRENT_IDENTITY, + tags: ["tag1", "tag2"], + value: GivenName.from("aGivenName") + }); + await Given.anOwnSharedIdentityAttribute({ sourceAttributeId: repositoryAttribute.id, peer: TestIdentity.PEER }); + await Given.aRequestItemWithAnIdentityAttribute({ + attributeOwner: TestIdentity.CURRENT_IDENTITY, + tags: ["tag1", "tag3"], + value: GivenName.from(" aGivenName ") + }); + await When.iCallAccept(); + await Then.theResponseItemShouldBeOfType("AttributeSuccessionAcceptResponseItem"); + await Then.theTagsOfTheSucceededRepositoryAttributeMatch(["tag1", "tag2", "tag3"]); + await Then.theSuccessorAttributeValueMatches(GivenName.from("aGivenName").toJSON()); + }); + test("in case of an IdentityAttribute whose predecessor was shared: returns an AttributeSuccessionAcceptResponseItem", async function () { const repositoryAttributePredecessor = await Given.aRepositoryAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, From d72742ddd191d59157f0f330f4639cf11f0ed1af Mon Sep 17 00:00:00 2001 From: Magnus Kuhn <127854942+Magnus-Kuhn@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:20:58 +0100 Subject: [PATCH 16/17] Update packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts Co-authored-by: Britta Stallknecht <146106656+britsta@users.noreply.github.com> --- .../modules/requests/itemProcessors/createAttribute/Context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts index cd8c960c3..523d80245 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts @@ -297,7 +297,7 @@ export class ThenSteps { expect((repositoryAttribute!.content as IdentityAttribute).tags?.sort()).toStrictEqual(tags.sort()); } - public async theSuccessorAttributeValueMatches(value: AttributeValues.Json): Promise { + public async theSuccessorAttributeValueMatches(value: AttributeValues.Identity.Json): Promise { const attribute = await this.context.consumptionController.attributes.getLocalAttribute( (this.context.responseItemAfterAction as AttributeSuccessionAcceptResponseItem).successorId ); From e37cd7ba6952c33c103c564b984202c333abdd17 Mon Sep 17 00:00:00 2001 From: mkuhn Date: Thu, 6 Mar 2025 14:48:54 +0100 Subject: [PATCH 17/17] refactor: test helper signature --- .../requests/itemProcessors/createAttribute/Context.ts | 8 ++++---- .../CreateAttributeRequestItemProcessor.test.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts index fe9d5f6b2..7abe0a0a8 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts @@ -239,7 +239,7 @@ export class ThenSteps { if (value) expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(value); } - public async anOwnSharedIdentityAttributeIsCreated(sourceAttribute?: CoreId, value?: AttributeValues.Identity.Json): Promise { + public async anOwnSharedIdentityAttributeIsCreated(params?: { sourceAttribute?: CoreId; value?: AttributeValues.Identity.Json }): Promise { expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined(); const createdAttribute = await this.context.consumptionController.attributes.getLocalAttribute( @@ -250,10 +250,10 @@ export class ThenSteps { expect(createdAttribute!.shareInfo).toBeDefined(); expect(createdAttribute!.shareInfo!.peer.toString()).toStrictEqual(this.context.peerAddress.toString()); expect(createdAttribute!.shareInfo!.sourceAttribute).toBeDefined(); - if (value) expect(createdAttribute!.content.value.toJSON()).toStrictEqual(value); + if (params?.value) expect(createdAttribute!.content.value.toJSON()).toStrictEqual(params.value); - if (sourceAttribute) { - expect(createdAttribute!.shareInfo!.sourceAttribute!.toString()).toStrictEqual(sourceAttribute.toString()); + if (params?.sourceAttribute) { + expect(createdAttribute!.shareInfo!.sourceAttribute!.toString()).toStrictEqual(params.sourceAttribute.toString()); } } diff --git a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts index d917a4a59..06898a96c 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/createAttribute/CreateAttributeRequestItemProcessor.test.ts @@ -339,7 +339,7 @@ describe("CreateAttributeRequestItemProcessor", function () { await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); await Then.aRepositoryAttributeIsCreated(GivenName.from("aGivenName").toJSON()); - await Then.anOwnSharedIdentityAttributeIsCreated(undefined, GivenName.from("aGivenName").toJSON()); + await Then.anOwnSharedIdentityAttributeIsCreated({ value: GivenName.from("aGivenName").toJSON() }); }); test("in case of an IdentityAttribute that already exists as RepositoryAttribute: creates an own shared IdentityAttribute that links to the existing RepositoryAttribute", async function () { @@ -374,7 +374,7 @@ describe("CreateAttributeRequestItemProcessor", function () { await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, tags: ["tag1", "tag3"], value: GivenName.from(" aGivenName ") }); await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); - await Then.anOwnSharedIdentityAttributeIsCreated(undefined, GivenName.from("aGivenName").toJSON()); + await Then.anOwnSharedIdentityAttributeIsCreated({ value: GivenName.from("aGivenName").toJSON() }); await Then.theTagsOfTheRepositoryAttributeMatch(["tag1", "tag2", "tag3"]); }); @@ -406,7 +406,7 @@ describe("CreateAttributeRequestItemProcessor", function () { await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); - await Then.anOwnSharedIdentityAttributeIsCreated(repositoryAttribute.id); + await Then.anOwnSharedIdentityAttributeIsCreated({ sourceAttribute: repositoryAttribute.id }); }); test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute but is to be deleted by peer: creates a new own shared IdentityAttribute", async function () { @@ -419,7 +419,7 @@ describe("CreateAttributeRequestItemProcessor", function () { await Given.aRequestItemWithAnIdentityAttribute({ attributeOwner: TestIdentity.CURRENT_IDENTITY, value: GivenName.from("aGivenName") }); await When.iCallAccept(); await Then.theResponseItemShouldBeOfType("CreateAttributeAcceptResponseItem"); - await Then.anOwnSharedIdentityAttributeIsCreated(repositoryAttribute.id); + await Then.anOwnSharedIdentityAttributeIsCreated({ sourceAttribute: repositoryAttribute.id }); }); test("in case of an IdentityAttribute that already exists as own shared IdentityAttribute with different tags: returns an AttributeSuccessionAcceptResponseItem", async function () {