Skip to content

Commit

Permalink
Add CanCreateRelationshipUseCase and handling of expired Relationship…
Browse files Browse the repository at this point in the history
…Templates (#212)

* Fix: throw error when responding to Request of expired RelationshipTemplate

* fix: throw error when try to create Relationship with expired RelationshipTemplate

* test: add first version of test

* feat: adjust auxiliary function for RelationshipTemplate

* feat: adjust createTemplate testUtil function

* fix: return error instead of throwing error

* test: temporary changes for tests

* fix: test by using auxiliary function

* test: shortened test duration

* chore: comment out code more consistently

* chore: make default expirationDateTime recognizable

* chore: add error for delay function

* feat: LocalRequest is expired when source RelationshipTemplate is expired

* test: add test for CreateRelationshipUseCase

* chore: remove comments

* test: add tests for updateStatusBasedOnTemplateExpiration function

* chore: adjust isExpired function for reasons of consistency

* fix: ensure to throw error only if no other is more relevant

* chore: adjust test naming to be more appropriate

* fix: ensure more precisely to throw error only if no other is more relevant

* feat: throw error only for onNewRelationship

* fix: prettier

* chore: renaming variables and use $in operator

* feat: implement canCreate route for Relationships

* chore: use CanCreateRelationshipUseCase in other use cases

* chore: use CanCreateRelationshipUseCase in more other use cases

* chore: rename error message relationshipAlreadyExists

* refactor: naming of response variable of CanCreateRelationshipUseCase

* fix: do not use CanCreateRelationshipUseCase in other UseCases

* feat: add noRequestToAccept error for CanCreateRelationshipUseCase

* refactor: do not expose the term LocalRequest in error messages

* feat: expose CanCreateRelationshipUseCase to facade

* test: should not create Relationship with CreateRelationshipUseCase if RelationshipTemplateContent used

* feat: use CreateRelationshipUseCase only for ArbitraryRelationshipTemplateContent

* fix: missing imports

* fix: unused import

* chore: build schema of CanCreateRelationshipUseCase

* refactor: choose more appropriate variable name

* chore: reduce CanCreateRelationshipUseCase to split pull requests

* chore: reduce CanCreateRelationshipUseCase to split pull requests

* fix: error message of CreateRelationship use case

* feat: update expiry of Requests also within CreateRelationshipUseCase

* fix: error message used for contradicting cases

* feat: only allow Request which expires after RelationshipTemplate for new Relationships

* test: request for new Relationship cannot expire after RelationshipTemplate

* fix: tests for expiration date of Requests

* refactor: rename to error for Relationships instead of RelationshipTemplates

* chore: remove unused Runtime error

* refactor: throw only Runtime errors in use cases

* refactor: add function for getting existing Relationships

* refactor: reuse CreateRelationshipRequest for CanCreateUseCase

* fix: incorrect use of recordNotFound error

* feat: define canSendRelationship method in Controller

* fix: failed Result for CanAcceptIncomingRequest and CanRejectIncomingRequest use cases

* feat: add canCreateRelationship Backbone service

* fix: value not used for sendRelationship method

* fix: value not used for sendRelationship method

* feat: use canCreate route of Backbone

* fix: request type of canCreateRelationship Backbone route

* fix: canCreateRelationship Backbone route

* fix: return value of canCreateRelationship Backbone route

* refactor: use variable for peerAddress

* chore: do not use imprecise Backbone result

* fix: return value of canSendRelationship method

* feat: use canSendRelationship method in corresponding use case

* chore: remove unneccessary type conversion

* feat: incorporate RelationshipTemplate expiry in canSendRelationship method

* refactor: CanCreateRelationshipUseCase

* feat: taking rejected Requests into account

* refactor: return code and message instead of error to avoid misunderstandings

* refactor: take rejected Requests at a lower level into account

* chore: simply code because cases handled at a lower level

* feat: handle expired RelationshipTemplate error appropriately

* test: does not change status of rejected Request when RelationshipTemplate expires

* fix: tests

* chore: update Backbone version

* test: automatic update of Request of expired RelationshipTemplate

* test: CanCreateRelationshipUseCase

* fix: LocalRequest test

* refactor: rename temporary error message

* chore: remove todo comments

* feat: apply Request expiry update in GetIncomingRequest(s)UseCase

* test: update Request status to expired when querying Requests if RelationshipTemplate expired

* chore: remove unused Runtime error relationshipCurrentlyExists

* chore: use imported sleep function instead of own delay function

* feat: add minimum of type validation for creationContent of CanCreateRelationshipUseCase

* feat: integrate comments

* fix: adjust test

* feat: generate expiration date for Request in onNewRelationship property if none is set

* refactor: use elvis operator

* test: remove assertIsCanCreateRelationshipFailureResponse

* feat: update expirationDate of Request if RelationshipTemplate expires

* test: set expiresAt of Request if none provided

* fix: clone template content in tests

* feat: remove 10 second tolerance

* refactor: move validation into controller

* refactor: getExistingRelationship

* feat: add min to CoreDate

* refactor: remove updates from create rel and get request use cases

* feat/refactor: update after received, update during get

* refactor: adapt local request

* refactor: rename incoming requests getter

* refactor: remove now unnecessary validation

* fix: remove debugging stuff from tests

* fix: consumption

* fix: runtime tests

* test: reduce sleep duration after removing tolerance

* test/fix: add tests and fix revealed errors

* test: typos and status settings

* chore: remove unused package

* test: sleep longer

* refactor/test: getOutgoingRequestWithUpdateExpiry and use it

* refactor: try-catch in sendRelationship

* fix: adapt the tests to the refactoring

* fix: more adaptations

* feat: canCreate never throws

* feat: canCreate with error codes

* refactor: use regex, debugging cleanup

* fix: variable access

* fix: unfound typescript-ioc module

* fix: unused caught error

* fix: unknown transportServices1 variable

* fix: failing test of DeciderModule

* fix: shady instanceOf check

* refactor: add empty line

* refactor: switch some lines in tests

* test: proper variable naming

* refactor: remove redundant test

* refactor: rephrase of test name to give more context

* fix: copy-paste error in tests

* chore: bump backbone

* refactor: remove unused code from request controller

* test: emphazise tests expecting errors

* refactor: re-simplify naming

* fix: do not get the template for every incoming request query

* refactor: de-dupe code

* refactor: remove empty line again

* refactor: distinguish success and failure response of CanCreateRelationshipUseCase

* fix: use correct CanCreateRelationshipResonse type in tests

* refactor: remove unused expiration code

* refactor: simplify

* fix: satisfy compiler

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Milena Czierlinski <[email protected]>
Co-authored-by: mkuhn <[email protected]>
Co-authored-by: Magnus Kuhn <[email protected]>
Co-authored-by: Julian König <[email protected]>
  • Loading branch information
6 people authored Oct 22, 2024
1 parent 9876cf3 commit 7b0bb79
Show file tree
Hide file tree
Showing 26 changed files with 741 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .dev/compose.backbone.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
BACKBONE_VERSION=6.10.0
BACKBONE_VERSION=6.13.2
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export class ConsumptionController {
this,
this.transport.eventBus,
this.accountController.identity,
this.accountController.relationships
this.accountController.relationships,
this.accountController.relationshipTemplates
).init();

const notificationItemProcessorRegistry = new NotificationItemProcessorRegistry(this, this.getDefaultNotificationItemProcessors());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export class IncomingRequestsController extends ConsumptionBaseController {
private readonly identity: { address: CoreAddress },
private readonly relationshipResolver: {
getRelationshipToIdentity(id: CoreAddress): Promise<Relationship | undefined>;
getExistingRelationshipToIdentity(id: CoreAddress): Promise<Relationship | undefined>;
},
private readonly relationshipTemplateResolver: {
getRelationshipTemplate(id: CoreId): Promise<RelationshipTemplate | undefined>;
}
) {
super(ConsumptionControllerName.RequestsController, parent);
Expand All @@ -60,6 +64,11 @@ export class IncomingRequestsController extends ConsumptionBaseController {
statusLog: []
});

if (!(await this.relationshipResolver.getExistingRelationshipToIdentity(CoreAddress.from(infoFromSource.peer))) && infoFromSource.expiresAt) {
request.content.expiresAt = CoreDate.min(infoFromSource.expiresAt, request.content.expiresAt);
request.updateStatusBasedOnExpiration();
}

await this.localRequests.create(request);

this.eventBus.publish(new IncomingRequestReceivedEvent(this.identity.address.toString(), request));
Expand Down Expand Up @@ -95,7 +104,8 @@ export class IncomingRequestsController extends ConsumptionBaseController {
source: {
reference: template.id,
type: "RelationshipTemplate"
}
},
expiresAt: template.cache!.expiresAt
};
}

Expand Down Expand Up @@ -392,6 +402,7 @@ export class IncomingRequestsController extends ConsumptionBaseController {
if (!requestDoc) return;

const localRequest = LocalRequest.from(requestDoc);

return await this.updateRequestExpiry(localRequest);
}

Expand Down Expand Up @@ -434,4 +445,5 @@ export class IncomingRequestsController extends ConsumptionBaseController {
interface InfoFromSource {
peer: ICoreAddress;
source: ILocalRequestSource;
expiresAt?: CoreDate;
}
15 changes: 14 additions & 1 deletion packages/consumption/src/modules/requests/local/LocalRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class LocalRequest extends CoreSynchronizable implements ILocalRequest {
public isExpired(comparisonDate: CoreDate = CoreDate.utc()): boolean {
if (!this.content.expiresAt) return false;

return comparisonDate.isAfter(this.content.expiresAt.add({ seconds: 10 }));
return comparisonDate.isAfter(this.content.expiresAt);
}

public updateStatusBasedOnExpiration(comparisonDate: CoreDate = CoreDate.utc()): boolean {
Expand All @@ -130,4 +130,17 @@ export class LocalRequest extends CoreSynchronizable implements ILocalRequest {

return false;
}

public updateExpirationDateBasedOnTemplateExpiration(templateExpiresAt: CoreDate): boolean {
if (this.source?.type !== "RelationshipTemplate") return false;

if (this.status === LocalRequestStatus.Completed || this.status === LocalRequestStatus.Expired) return false;

if (!this.isExpired()) {
this.content.expiresAt = CoreDate.min(templateExpiresAt, this.content.expiresAt);
return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions";
import { IRequest, IRequestItemGroup, RejectResponseItem, Request, RequestItemGroup, ResponseItem, ResponseItemGroup, ResponseItemResult } from "@nmshd/content";
import { CoreDate, CoreId } from "@nmshd/core-types";
import { CoreIdHelper, TransportLoggerFactory } from "@nmshd/transport";
import { CoreIdHelper, RelationshipTemplate, TransportLoggerFactory } from "@nmshd/transport";
import {
ConsumptionIds,
DecideRequestItemGroupParametersJSON,
Expand Down Expand Up @@ -65,6 +65,34 @@ describe("IncomingRequestsController", function () {
await Then.eventHasBeenPublished(IncomingRequestReceivedEvent);
});

test("takes the expiration date from the Template if the Request has no expiration date", async function () {
const timestamp = CoreDate.utc();
const incomingTemplate = TestObjectFactory.createIncomingRelationshipTemplate(timestamp);
await When.iCreateAnIncomingRequestWith({ requestSourceObject: incomingTemplate });
await Then.theRequestHasExpirationDate(timestamp);
await Then.theRequestIsInStatus(LocalRequestStatus.Expired);
});

test("takes the expiration date from the Template if the Request has a later expiration date", async function () {
const timestamp = CoreDate.utc().add({ days: 1 });
const incomingTemplate = TestObjectFactory.createIncomingRelationshipTemplate(timestamp);
await When.iCreateAnIncomingRequestWith({
requestSourceObject: incomingTemplate,
receivedRequest: TestObjectFactory.createRequestWithOneItem({ expiresAt: timestamp.add({ days: 1 }) })
});
await Then.theRequestHasExpirationDate(timestamp);
});

test("takes the expiration date from the Request if the Template has a later expiration date", async function () {
const timestamp = CoreDate.utc().add({ days: 1 });
const incomingTemplate = TestObjectFactory.createIncomingRelationshipTemplate(timestamp.add({ days: 1 }));
await When.iCreateAnIncomingRequestWith({
requestSourceObject: incomingTemplate,
receivedRequest: TestObjectFactory.createRequestWithOneItem({ expiresAt: timestamp })
});
await Then.theRequestHasExpirationDate(timestamp);
});

test("uses the ID of the given Request if it exists", async function () {
const request = TestObjectFactory.createRequestWithOneItem({ id: await CoreIdHelper.notPrefixed.generate() });

Expand Down Expand Up @@ -999,6 +1027,7 @@ describe("IncomingRequestsController", function () {
items: [TestRequestItem.from({ mustBeAccepted: false })]
});
const template = TestObjectFactory.createIncomingIRelationshipTemplate();
context.templateToReturnFromGetTemplate = RelationshipTemplate.from(template);

let cnsRequest = await context.incomingRequestsController.received({
receivedRequest: request,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
ResponseItemResult,
ResponseResult
} from "@nmshd/content";
import { CoreAddress, CoreId, ICoreId } from "@nmshd/core-types";
import { CoreAddress, CoreDate, CoreId, ICoreId } from "@nmshd/core-types";
import { CoreIdHelper, IConfigOverwrite, IMessage, IRelationshipTemplate, Message, Relationship, RelationshipTemplate, SynchronizedCollection, Transport } from "@nmshd/transport";
import {
ConsumptionController,
Expand Down Expand Up @@ -57,6 +57,8 @@ export class RequestsTestsContext {
public currentIdentity: CoreAddress;
public mockEventBus = new MockEventBus();
public relationshipToReturnFromGetRelationshipToIdentity: Relationship | undefined;
public relationshipToReturnFromGetExistingRelationshipToIdentity: Relationship | undefined;
public templateToReturnFromGetTemplate: RelationshipTemplate | undefined;
public consumptionController: ConsumptionController;

private constructor() {
Expand Down Expand Up @@ -110,7 +112,11 @@ export class RequestsTestsContext {
address: account.accountController.identity.address
},
{
getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity)
getRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetRelationshipToIdentity),
getExistingRelationshipToIdentity: () => Promise.resolve(context.relationshipToReturnFromGetExistingRelationshipToIdentity)
},
{
getRelationshipTemplate: () => Promise.resolve(context.templateToReturnFromGetTemplate)
}
);

Expand All @@ -132,6 +138,8 @@ export class RequestsTestsContext {
this.validationResult = undefined;
this.actionToTry = undefined;
this.relationshipToReturnFromGetRelationshipToIdentity = undefined;
this.relationshipToReturnFromGetExistingRelationshipToIdentity = undefined;
this.templateToReturnFromGetTemplate = undefined;

TestRequestItemProcessor.numberOfApplyIncomingResponseItemCalls = 0;

Expand Down Expand Up @@ -222,6 +230,12 @@ export class RequestsGiven {
requestSourceObject: params.requestSource
});

try {
this.context.templateToReturnFromGetTemplate = RelationshipTemplate.from(params.requestSource as any);
} catch (_) {
// the source is not a template
}

await this.moveIncomingRequestToStatus(localRequest, params.status);

this.context.givenLocalRequest = localRequest;
Expand Down Expand Up @@ -306,11 +320,13 @@ export class RequestsGiven {
}

private async moveOutgoingRequestToStatus(localRequest: LocalRequest, status: LocalRequestStatus) {
if (localRequest.status === status) return;
const updatedRequest = await this.context.outgoingRequestsController.getOutgoingRequest(localRequest.id);

if (updatedRequest!.status === status) return;

if (isStatusAAfterStatusB(status, LocalRequestStatus.Draft)) {
await this.context.outgoingRequestsController.sent({
requestId: localRequest.id,
requestId: updatedRequest!.id,
requestSourceObject: TestObjectFactory.createOutgoingIMessage(this.context.currentIdentity)
});
}
Expand Down Expand Up @@ -1039,6 +1055,12 @@ export class RequestsThen {
return Promise.resolve();
}

public theRequestHasExpirationDate(expiresAt: CoreDate): Promise<void> {
expect(this.context.localRequestAfterAction?.content.expiresAt).toStrictEqual(expiresAt);

return Promise.resolve();
}

public theRequestHasCorrectItemCount(itemCount: number): Promise<void> {
expect(this.context.localRequestAfterAction?.content.items).toHaveLength(itemCount);

Expand Down Expand Up @@ -1205,8 +1227,6 @@ function isStatusAAfterStatusB(a: LocalRequestStatus, b: LocalRequestStatus): bo

function getIntegerValue(status: LocalRequestStatus): number {
switch (status) {
case LocalRequestStatus.Expired:
return -1;
case LocalRequestStatus.Draft:
return 0;
case LocalRequestStatus.Open:
Expand All @@ -1215,6 +1235,8 @@ function getIntegerValue(status: LocalRequestStatus): number {
return 2;
case LocalRequestStatus.ManualDecisionRequired:
return 3;
case LocalRequestStatus.Expired:
return 4;
case LocalRequestStatus.Decided:
return 5;
case LocalRequestStatus.Completed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe("LocalRequest", function () {
});

test("adds a status log entry on status change", function () {
const request = TestObjectFactory.createLocalRequestWith({});
const request = TestObjectFactory.createLocalRequestWith({ status: LocalRequestStatus.Draft });
request.changeStatus(LocalRequestStatus.Open);

expect(request.statusLog).toHaveLength(1);
Expand All @@ -50,7 +50,7 @@ describe("LocalRequest", function () {
});

describe("updateStatusBasedOnExpiration", function () {
test("sets the status to expired when the request is expired", function () {
test("sets the status to expired when the Request is expired", function () {
const request = TestObjectFactory.createLocalRequestWith({
contentProperties: {
expiresAt: CoreDate.utc().subtract({ days: 1 })
Expand All @@ -61,18 +61,84 @@ describe("LocalRequest", function () {
expect(request.status).toStrictEqual(LocalRequestStatus.Expired);
});

test("does not change the status when the request is expired but already completed", function () {
test("does not change the status when the Request is expired but already completed", function () {
const request = TestObjectFactory.createLocalRequestWith({ status: LocalRequestStatus.Completed });

request.updateStatusBasedOnExpiration();
expect(request.status).toStrictEqual(LocalRequestStatus.Completed);
});

test("does not change the status when the request is expired but already expired", function () {
test("does not change the status when the Request is expired but already expired", function () {
const request = TestObjectFactory.createLocalRequestWith({ status: LocalRequestStatus.Expired });

request.updateStatusBasedOnExpiration();
expect(request.status).toStrictEqual(LocalRequestStatus.Expired);
});
});

describe("updateExpirationDateBasedOnTemplateExpiration", function () {
test("sets the expiration date if the Request doesn't have an expiration date", function () {
const request = TestObjectFactory.createUnansweredLocalRequestBasedOnTemplateWith({});
expect(request.content.expiresAt).toBeUndefined();
const timestamp = CoreDate.utc().subtract({ days: 1 });

request.updateExpirationDateBasedOnTemplateExpiration(timestamp);
expect(request.content.expiresAt).toStrictEqual(timestamp);
});

test("sets the expiration date if the Request has already been rejected", function () {
const request = TestObjectFactory.createRejectedLocalRequestBasedOnTemplateWith({});

const timestamp = CoreDate.utc().subtract({ days: 1 });

request.updateExpirationDateBasedOnTemplateExpiration(timestamp);
expect(request.content.expiresAt).toStrictEqual(timestamp);
});

test("does not change the expiration date when the Request expires before the Template", function () {
const timestamp = CoreDate.utc().add({ days: 1 });
const request = TestObjectFactory.createUnansweredLocalRequestBasedOnTemplateWith({
contentProperties: {
expiresAt: timestamp
}
});

request.updateExpirationDateBasedOnTemplateExpiration(timestamp.add({ days: 1 }));
expect(request.content.expiresAt).toStrictEqual(timestamp);
});

test("does not change the expiration date when the Request is already completed", function () {
const request = TestObjectFactory.createRejectedLocalRequestBasedOnTemplateWith({
status: LocalRequestStatus.Completed
});

const timestamp = CoreDate.utc().subtract({ days: 1 });

request.updateExpirationDateBasedOnTemplateExpiration(timestamp);
expect(request.content.expiresAt).toBeUndefined();
});

test("does not change the expiration date when the Request is already expired", function () {
const timestamp = CoreDate.utc().subtract({ days: 1 });

const request = TestObjectFactory.createUnansweredLocalRequestBasedOnTemplateWith({
status: LocalRequestStatus.Expired,
contentProperties: {
expiresAt: timestamp
}
});

request.updateExpirationDateBasedOnTemplateExpiration(timestamp.subtract({ days: 1 }));
expect(request.content.expiresAt).toStrictEqual(timestamp);
});

test("does not set the expiration date when Message instead of RelationshipTemplate is used", function () {
const request = TestObjectFactory.createLocalRequestWith({});

const timestamp = CoreDate.utc().subtract({ days: 1 });

request.updateExpirationDateBasedOnTemplateExpiration(timestamp);
expect(request.content.expiresAt).toBeUndefined();
});
});
});
Loading

0 comments on commit 7b0bb79

Please sign in to comment.