Skip to content

Commit

Permalink
Delete incoming requests (#433)
Browse files Browse the repository at this point in the history
* feat: add consumption method

* feat: add runtime usecase

* feat: add delete to facade

* test: add runtime tests

* feat: add consumption tests

* chore: rm todo

* chore: negation
  • Loading branch information
jkoenig134 authored Feb 24, 2025
1 parent b7b26d1 commit de7cd66
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 24 deletions.
7 changes: 7 additions & 0 deletions packages/consumption/src/consumption/ConsumptionCoreErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,13 @@ class Requests {
return new CoreError("error.consumption.requests.cannotCreateRequestWithExpirationDateInPast", "You cannot create a Request with an expiration date that is in the past.");
}

public canOnlyDeleteIncomingRequestThatIsExpired(id: string, status: string) {
return new CoreError(
"error.consumption.requests.canOnlyDeleteIncomingRequestThatIsExpired",
`The incoming Request '${id}' is in status '${status}'. At the moment, you can only delete incoming Requests that are expired.`
);
}

private static readonly _decideValidation = class {
public invalidNumberOfItems(message: string) {
return new ApplicationError("error.consumption.requests.decide.validation.invalidNumberOfItems", message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,14 @@ export class IncomingRequestsController extends ConsumptionBaseController {
await this.localRequests.update(requestDoc, request);
}

public async delete(request: LocalRequest): Promise<void> {
if (request.status !== LocalRequestStatus.Expired) {
throw ConsumptionCoreErrors.requests.canOnlyDeleteIncomingRequestThatIsExpired(request.id.toString(), request.status);
}

await this.localRequests.delete(request);
}

public async deleteRequestsFromPeer(peer: CoreAddress): Promise<void> {
const requests = await this.getIncomingRequests({ peer: peer.toString() });
for (const request of requests) {
Expand Down
83 changes: 60 additions & 23 deletions packages/consumption/test/modules/requests/DeleteRequest.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions";
import { sleep } from "@js-soft/ts-utils";
import { Request } from "@nmshd/content";
import { AccountController, Message, Transport } from "@nmshd/transport";
import { ConsumptionController, LocalRequest } from "../../../src";
import { CoreDate } from "@nmshd/core-types";
import { AccountController, Transport } from "@nmshd/transport";
import { ConsumptionController } from "../../../src";
import { TestUtil } from "../../core/TestUtil";
import { TestRequestItem } from "./testHelpers/TestRequestItem";
import { TestRequestItemProcessor } from "./testHelpers/TestRequestItemProcessor";

let connection: IDatabaseConnection;
let transport: Transport;

describe("Delete requests", function () {
let sAccountController: AccountController;
let sConsumptionController: ConsumptionController;
let rAccountController: AccountController;
let rConsumptionController: ConsumptionController;

let sLocalRequest: LocalRequest;
let rMessageWithRequest: Message;
let rLocalRequest: LocalRequest;

beforeAll(async function () {
connection = await TestUtil.createConnection();
transport = TestUtil.createTransport(connection);
Expand All @@ -29,32 +28,30 @@ describe("Delete requests", function () {
sConsumptionController.incomingRequests["processorRegistry"].registerProcessor(TestRequestItem, TestRequestItemProcessor);
({ accountController: rAccountController, consumptionController: rConsumptionController } = accounts[1]);
rConsumptionController.incomingRequests["processorRegistry"].registerProcessor(TestRequestItem, TestRequestItemProcessor);
});

beforeEach(async function () {
await TestUtil.ensureActiveRelationship(sAccountController, rAccountController);
});

await TestUtil.addRelationship(sAccountController, rAccountController);
sLocalRequest = await sConsumptionController.outgoingRequests.create({
content: Request.from({
items: [TestRequestItem.from({ mustBeAccepted: false })]
}),
afterAll(async function () {
await connection.close();
});

test("requests should be deleted after decomposing", async function () {
const sLocalRequest = await sConsumptionController.outgoingRequests.create({
content: Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })] }),
peer: rAccountController.identity.address
});

await sAccountController.messages.sendMessage({
content: sLocalRequest.content,
recipients: [rAccountController.identity.address]
});
await sAccountController.messages.sendMessage({ content: sLocalRequest.content, recipients: [rAccountController.identity.address] });
const messages = await TestUtil.syncUntilHasMessages(rAccountController);
rMessageWithRequest = messages[0];
rLocalRequest = await rConsumptionController.incomingRequests.received({
const rMessageWithRequest = messages[0];
const rLocalRequest = await rConsumptionController.incomingRequests.received({
receivedRequest: rMessageWithRequest.cache!.content as Request,
requestSourceObject: rMessageWithRequest
});
});

afterAll(async function () {
await connection.close();
});

test("requests should be deleted after decomposing", async function () {
await TestUtil.terminateRelationship(sAccountController, rAccountController);
await TestUtil.decomposeRelationship(sAccountController, sConsumptionController, rAccountController);
await TestUtil.decomposeRelationship(rAccountController, rConsumptionController, sAccountController);
Expand All @@ -63,4 +60,44 @@ describe("Delete requests", function () {
expect(sRequest).toBeUndefined();
expect(rRequest).toBeUndefined();
});

test("should be possible to delete an expired request", async function () {
const sLocalRequest = await sConsumptionController.outgoingRequests.create({
content: Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })], expiresAt: CoreDate.utc().add({ seconds: 3 }) }),
peer: rAccountController.identity.address
});

await sAccountController.messages.sendMessage({ content: sLocalRequest.content, recipients: [rAccountController.identity.address] });
const messages = await TestUtil.syncUntilHasMessages(rAccountController);
const rMessageWithRequest = messages[0];
const rLocalRequest = await rConsumptionController.incomingRequests.received({
receivedRequest: rMessageWithRequest.cache!.content as Request,
requestSourceObject: rMessageWithRequest
});

await sleep(3000);

const requestInDeletion = await rConsumptionController.incomingRequests.getIncomingRequest(rLocalRequest.id);
await rConsumptionController.incomingRequests.delete(requestInDeletion!);

const rRequestAfterDeletion = await rConsumptionController.incomingRequests.getIncomingRequest(sLocalRequest.id);
expect(rRequestAfterDeletion).toBeUndefined();
});

test("should not be possible to delete a non expired request", async function () {
const sLocalRequest = await sConsumptionController.outgoingRequests.create({
content: Request.from({ items: [TestRequestItem.from({ mustBeAccepted: false })] }),
peer: rAccountController.identity.address
});

await sAccountController.messages.sendMessage({ content: sLocalRequest.content, recipients: [rAccountController.identity.address] });
const messages = await TestUtil.syncUntilHasMessages(rAccountController);
const rMessageWithRequest = messages[0];
const rLocalRequest = await rConsumptionController.incomingRequests.received({
receivedRequest: rMessageWithRequest.cache!.content as Request,
requestSourceObject: rMessageWithRequest
});

await expect(rConsumptionController.incomingRequests.delete(rLocalRequest)).rejects.toThrow("error.consumption.requests.canOnlyDeleteIncomingRequestThatIsExpired");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
CheckPrerequisitesOfIncomingRequestUseCase,
CompleteIncomingRequestRequest,
CompleteIncomingRequestUseCase,
DeleteIncomingRequestRequest,
DeleteIncomingRequestUseCase,
GetIncomingRequestRequest,
GetIncomingRequestsRequest,
GetIncomingRequestsUseCase,
Expand All @@ -33,7 +35,8 @@ export class IncomingRequestsFacade {
@Inject private readonly rejectUseCase: RejectIncomingRequestUseCase,
@Inject private readonly completeUseCase: CompleteIncomingRequestUseCase,
@Inject private readonly getRequestUseCase: GetIncomingRequestUseCase,
@Inject private readonly getRequestsUseCase: GetIncomingRequestsUseCase
@Inject private readonly getRequestsUseCase: GetIncomingRequestsUseCase,
@Inject private readonly deleteUseCase: DeleteIncomingRequestUseCase
) {}

public async received(request: ReceivedIncomingRequestRequest): Promise<Result<LocalRequestDTO>> {
Expand Down Expand Up @@ -75,4 +78,8 @@ export class IncomingRequestsFacade {
public async getRequests(request: GetIncomingRequestsRequest): Promise<Result<LocalRequestDTO[]>> {
return await this.getRequestsUseCase.execute(request);
}

public async delete(request: DeleteIncomingRequestRequest): Promise<Result<void>> {
return await this.deleteUseCase.execute(request);
}
}
19 changes: 19 additions & 0 deletions packages/runtime/src/useCases/common/Schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10863,6 +10863,25 @@ export const CreateOutgoingRequestRequest: any = {
}
}

export const DeleteIncomingRequestRequest: any = {
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/DeleteIncomingRequestRequest",
"definitions": {
"DeleteIncomingRequestRequest": {
"type": "object",
"properties": {
"requestId": {
"type": "string"
}
},
"required": [
"requestId"
],
"additionalProperties": false
}
}
}

export const DiscardOutgoingRequestRequest: any = {
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/DiscardOutgoingRequestRequest",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Result } from "@js-soft/ts-utils";
import { IncomingRequestsController, LocalRequest } from "@nmshd/consumption";
import { CoreId } from "@nmshd/core-types";
import { AccountController } from "@nmshd/transport";
import { Inject } from "@nmshd/typescript-ioc";
import { RuntimeErrors, SchemaRepository, SchemaValidator, UseCase } from "../../common";

export interface DeleteIncomingRequestRequest {
requestId: string;
}

class Validator extends SchemaValidator<DeleteIncomingRequestRequest> {
public constructor(@Inject schemaRepository: SchemaRepository) {
super(schemaRepository.getSchema("DeleteIncomingRequestRequest"));
}
}

export class DeleteIncomingRequestUseCase extends UseCase<DeleteIncomingRequestRequest, void> {
public constructor(
@Inject private readonly incomingRequestsController: IncomingRequestsController,
@Inject private readonly accountController: AccountController,
@Inject validator: Validator
) {
super(validator);
}

protected async executeInternal(request: DeleteIncomingRequestRequest): Promise<Result<void>> {
const localRequest = await this.incomingRequestsController.getIncomingRequest(CoreId.from(request.requestId));
if (!localRequest) {
return Result.fail(RuntimeErrors.general.recordNotFound(LocalRequest));
}

await this.incomingRequestsController.delete(localRequest);

await this.accountController.syncDatawallet();

return Result.ok(undefined);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from "./CompleteIncomingRequest";
export * from "./CompleteOutgoingRequest";
export * from "./CreateAndCompleteOutgoingRequestFromRelationshipTemplateResponse";
export * from "./CreateOutgoingRequest";
export * from "./DeleteIncomingRequest";
export * from "./DiscardOutgoingRequest";
export * from "./GetIncomingRequest";
export * from "./GetIncomingRequests";
Expand Down
24 changes: 24 additions & 0 deletions packages/runtime/test/consumption/requests.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,30 @@ describe("Requests", () => {
expect(triggeredEvent).toBeUndefined();
});

test("can not delete a Request when it is not expired", async () => {
const request = (await exchangeTemplateAndReceiverRequiresManualDecision(sRuntimeServices, rRuntimeServices, templateContent)).request;

await expect(rConsumptionServices.incomingRequests.delete({ requestId: request.id })).resolves.toBeAnError(
"is in status 'ManualDecisionRequired'. At the moment, you can only delete incoming Requests that are expired.",
"error.consumption.requests.canOnlyDeleteIncomingRequestThatIsExpired"
);
});

test("can delete a Request when it is expired", async () => {
const request = (await exchangeTemplateAndReceiverRequiresManualDecision(sRuntimeServices, rRuntimeServices, templateContent, DateTime.utc().plus({ seconds: 3 })))
.request;

await sleep(3000);

const rLocalRequest = (await rConsumptionServices.incomingRequests.getRequest({ id: request.id })).value;
expect(rLocalRequest.status).toBe(LocalRequestStatus.Expired);

await expect(rConsumptionServices.incomingRequests.delete({ requestId: request.id })).resolves.toBeSuccessful();

const rLocalRequestAfterDelete = await rConsumptionServices.incomingRequests.getRequest({ id: request.id });
expect(rLocalRequestAfterDelete).toBeAnError("LocalRequest not found.", "error.runtime.recordNotFound");
});

describe.each([
{
action: "Accept"
Expand Down

0 comments on commit de7cd66

Please sign in to comment.