Skip to content

Commit

Permalink
Trim whitespaces when creating a RepositoryAttribute (#437)
Browse files Browse the repository at this point in the history
* feat: remove whitespaces

* test: access right thing

* fix: don't overwrite prototypes

* refactor/fix: only trim JSON attribute values

* test: expect trimmed values

* test: add trimming and duplicate check tests

* test: fix test

* refactor/fix: remove whitespaces everywhere

* test: improve tests

* test: fix/add tests

* refactor: variable naming

* test: remove unneeded checks

* refactor: method ordering

* test: remove more checks

* test: add createAttributeRequestItem tests

* Update packages/consumption/test/modules/requests/itemProcessors/createAttribute/Context.ts

Co-authored-by: Britta Stallknecht <[email protected]>

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Britta Stallknecht <[email protected]>
  • Loading branch information
3 people authored Mar 6, 2025
1 parent 7f9649d commit bba03c6
Show file tree
Hide file tree
Showing 7 changed files with 484 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ export class AttributesController extends ConsumptionBaseController {
}

const parsedParams = CreateRepositoryAttributeParams.from(params);
const trimmedAttribute = {
...parsedParams.content.toJSON(),
value: this.trimAttributeValue(parsedParams.content.value.toJSON() as AttributeValues.Identity.Json)
};
parsedParams.content = IdentityAttribute.from(trimmedAttribute);

let localAttribute = LocalAttribute.from({
id: parsedParams.id ?? (await ConsumptionIds.attribute.generate()),
createdAt: CoreDate.utc(),
Expand Down Expand Up @@ -405,6 +411,11 @@ export class AttributesController extends ConsumptionBaseController {
validate = true
): Promise<{ predecessor: LocalAttribute; successor: LocalAttribute }> {
const parsedSuccessorParams = AttributeSuccessorParams.from(successorParams);
const trimmedAttribute = {
...parsedSuccessorParams.content.toJSON(),
value: this.trimAttributeValue(parsedSuccessorParams.content.value.toJSON() as AttributeValues.Identity.Json)
};
parsedSuccessorParams.content = IdentityAttribute.from(trimmedAttribute);

if (validate) {
const validationResult = await this.validateRepositoryAttributeSuccession(predecessorId, parsedSuccessorParams);
Expand Down Expand Up @@ -1301,22 +1312,28 @@ export class AttributesController extends ConsumptionBaseController {
}

public async getRepositoryAttributeWithSameValue(value: AttributeValues.Identity.Json): Promise<LocalAttribute | undefined> {
const trimmedValue = this.trimAttributeValue(value);
const queryForRepositoryAttributeDuplicates = flattenObject({
content: {
"@type": "IdentityAttribute",
owner: this.identity.address.toString(),
value: value
value: trimmedValue
}
});
queryForRepositoryAttributeDuplicates["succeededBy"] = { $exists: false };
queryForRepositoryAttributeDuplicates["shareInfo"] = { $exists: false };

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;
}

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<LocalAttribute[]> {
return await this.getLocalAttributes({
"content.@type": "RelationshipAttribute",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BirthYear,
City,
Country,
DisplayName,
EMailAddress,
HouseNumber,
IdentityAttribute,
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -163,6 +179,40 @@ 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.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");

const childAttributes = await consumptionController.attributes.getLocalAttributes({
parentId: address.id.toString()
});
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");
});

test("should trigger an AttributeCreatedEvent for each created child Attribute of a complex Attribute", async function () {
await consumptionController.attributes.getLocalAttributes();

Expand Down Expand Up @@ -1777,6 +1827,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": "GivenName",
value: " aGivenName "
},
owner: consumptionController.accountController.identity.address
})
});
const successorParams: IAttributeSuccessorParams = {
content: IdentityAttribute.from({
value: {
"@type": "GivenName",
value: " anotherGivenName "
},
owner: consumptionController.accountController.identity.address
})
};

const { successor } = await consumptionController.attributes.succeedRepositoryAttribute(predecessor.id, successorParams);
expect(successor).toBeDefined();
expect((successor.content.value.toJSON() as any).value).toBe("anotherGivenName");
});

test("should succeed a repository attribute updating tags but not the value", async function () {
const predecessor = await consumptionController.attributes.createRepositoryAttribute({
content: IdentityAttribute.from({
Expand Down Expand Up @@ -2031,6 +2106,45 @@ 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({
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");
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({
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(trimmedVersion1ChildValues[i]);
}
});

test("should succeed a complex repository attribute adding an optional child", async function () {
repoVersion1Params = {
content: IdentityAttribute.from({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export class ThenSteps {
return Promise.resolve();
}

public async aRepositoryAttributeIsCreated(): Promise<void> {
public async aRepositoryAttributeIsCreated(value?: AttributeValues.Identity.Json): Promise<void> {
expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined();

const createdSharedAttribute = await this.context.consumptionController.attributes.getLocalAttribute(
Expand All @@ -236,9 +236,10 @@ export class ThenSteps {

expect(createdRepositoryAttribute).toBeDefined();
expect(createdRepositoryAttribute!.shareInfo).toBeUndefined();
if (value) expect(createdRepositoryAttribute!.content.value.toJSON()).toStrictEqual(value);
}

public async anOwnSharedIdentityAttributeIsCreated(sourceAttribute?: CoreId): Promise<void> {
public async anOwnSharedIdentityAttributeIsCreated(params?: { sourceAttribute?: CoreId; value?: AttributeValues.Identity.Json }): Promise<void> {
expect((this.context.responseItemAfterAction as CreateAttributeAcceptResponseItem).attributeId).toBeDefined();

const createdAttribute = await this.context.consumptionController.attributes.getLocalAttribute(
Expand All @@ -249,9 +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 (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());
}
}

Expand Down Expand Up @@ -305,6 +307,14 @@ export class ThenSteps {
expect((repositoryAttribute!.content as IdentityAttribute).tags?.sort()).toStrictEqual(tags.sort());
}

public async theSuccessorAttributeValueMatches(value: AttributeValues.Identity.Json): Promise<void> {
const attribute = await this.context.consumptionController.attributes.getLocalAttribute(
(this.context.responseItemAfterAction as AttributeSuccessionAcceptResponseItem).successorId
);

expect(attribute!.content.value.toJSON()).toStrictEqual(value);
}

public theCreatedAttributeHasTheAttributeIdFromTheResponseItem(): Promise<void> {
expect(this.context.createdAttributeAfterAction.id.toString()).toStrictEqual((this.context.givenResponseItem as CreateAttributeAcceptResponseItem).attributeId.toString());

Expand Down
Loading

0 comments on commit bba03c6

Please sign in to comment.