From be699fdd303328cd6a49ae9682beda403d22784a Mon Sep 17 00:00:00 2001
From: Roy Kakkenberg <38660000+Yoronex@users.noreply.github.com>
Date: Tue, 7 Jan 2025 21:52:17 +0100
Subject: [PATCH] feat(summaries): add endpoint to fetch container's summary
(#415)
* feat: add minimal transaction summary service
* feat: add endpoint to fetch container summary
* fix: failing test case
* chore: add reference to PR
* chore: add documentation
---
.../response/transaction-summary-response.ts | 45 ++++++
.../transaction-summary-controller.ts | 89 ++++++++++
src/index.ts | 2 +
src/service/transaction-summary-service.ts | 128 +++++++++++++++
.../transaction-summary-controller.ts | 132 +++++++++++++++
.../service/transaction-summary-service.ts | 152 ++++++++++++++++++
6 files changed, 548 insertions(+)
create mode 100644 src/controller/response/transaction-summary-response.ts
create mode 100644 src/controller/transaction-summary-controller.ts
create mode 100644 src/service/transaction-summary-service.ts
create mode 100644 test/unit/controller/transaction-summary-controller.ts
create mode 100644 test/unit/service/transaction-summary-service.ts
diff --git a/src/controller/response/transaction-summary-response.ts b/src/controller/response/transaction-summary-response.ts
new file mode 100644
index 00000000..2085307d
--- /dev/null
+++ b/src/controller/response/transaction-summary-response.ts
@@ -0,0 +1,45 @@
+/**
+ * SudoSOS back-end API service.
+ * Copyright (C) 2024 Study association GEWIS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * @license
+ */
+
+/**
+ * This is the module page of the transaction summaries.
+ * Not that this module has been created in very strict time constraints,
+ * so its implementation is very minimal.
+ * https://github.com/GEWIS/sudosos-backend/pull/415
+ *
+ * @module transaction-summaries
+ */
+
+import { BaseUserResponse } from './user-response';
+import { DineroObjectResponse } from './dinero-response';
+
+/**
+ * @typedef {object} ContainerSummaryResponse
+ * @property {allOf|BaseUserResponse} user.required
+ * @property {allOf|DineroObjectResponse} totalInclVat.required
+ * @property {integer} amountOfProducts.required
+ * @property {integer} containerId.required
+ */
+export interface ContainerSummaryResponse {
+ user: BaseUserResponse;
+ totalInclVat: DineroObjectResponse;
+ amountOfProducts: number;
+ containerId: number;
+}
diff --git a/src/controller/transaction-summary-controller.ts b/src/controller/transaction-summary-controller.ts
new file mode 100644
index 00000000..ee44bc7f
--- /dev/null
+++ b/src/controller/transaction-summary-controller.ts
@@ -0,0 +1,89 @@
+/**
+ * SudoSOS back-end API service.
+ * Copyright (C) 2024 Study association GEWIS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * @license
+ */
+
+/**
+ * This is the module page of the transaction summaries.
+ * Not that this module has been created in very strict time constraints,
+ * so its implementation is very minimal.
+ * https://github.com/GEWIS/sudosos-backend/pull/415
+ *
+ * @module transaction-summaries
+ */
+
+import { Response } from 'express';
+import log4js, { Logger } from 'log4js';
+import BaseController, { BaseControllerOptions } from './base-controller';
+import Policy from './policy';
+import { RequestWithToken } from '../middleware/token-middleware';
+import TransactionSummaryService from '../service/transaction-summary-service';
+
+export default class TransactionSummaryController extends BaseController {
+ private logger: Logger = log4js.getLogger('TransactionSummaryController');
+
+ public constructor(options: BaseControllerOptions) {
+ super(options);
+ this.logger.level = process.env.LOG_LEVEL;
+ }
+
+ public getPolicy(): Policy {
+ return {
+ '/container/:id(\\d+)': {
+ GET: {
+ policy: async (req) => this.roleManager.can(req.token.roles, 'get', 'all', 'Transaction', ['*']),
+ handler: this.getSingleContainerSummary.bind(this),
+ },
+ },
+ };
+ }
+
+ /**
+ * GET /transactions/summary/container/{id}
+ * @summary Returns a summary of all purchases within a container
+ * @operationId getSingleContainerSummary
+ * @tags transactionSummaries - Operations of the transaction summary controller
+ * @security JWT
+ * @deprecated - Hotfix for Feestcafé "De BAC" - 70s Disco Edition. Do not use for anything else. https://github.com/GEWIS/sudosos-backend/pull/415
+ * @param {integer} id.path.required - The ID of the container
+ * @return {Array} 200 - The requested summary
+ * @return {string} 404 - Not found error
+ * @return {string} 500 - Internal server error
+ */
+ public async getSingleContainerSummary(req: RequestWithToken, res: Response): Promise {
+ const { id: rawId } = req.params;
+ this.logger.trace('Get single container summary of container', rawId, ', by user', req.token.user);
+
+ try {
+ const id = Number(rawId);
+ const summaries = await new TransactionSummaryService().getContainerSummary({ containerId: id });
+ if (summaries.length === 0) {
+ // This also causes a 404 if the container exists, but no transactions have been made.
+ // However, this is a won't fix for now (because time)
+ // https://github.com/GEWIS/sudosos-backend/pull/415
+ res.status(404).json('Container not found.');
+ return;
+ }
+
+ res.status(200).json(summaries.map((s) => TransactionSummaryService.toContainerSummaryResponse(s)));
+ } catch (e) {
+ res.status(500).send('Internal server error.');
+ this.logger.error(e);
+ }
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 6ec92d08..6fedbe8d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -77,6 +77,7 @@ import ServerSettingsStore from './server-settings/server-settings-store';
import SellerPayoutController from './controller/seller-payout-controller';
import { ISettings } from './entity/server-setting';
import ServerSettingsController from './controller/server-settings-controller';
+import TransactionSummaryController from './controller/transaction-summary-controller';
export class Application {
app: express.Express;
@@ -246,6 +247,7 @@ export default async function createApp(): Promise {
application.app.use('/v1/productcategories', new ProductCategoryController(options).getRouter());
application.app.use('/v1/pointsofsale', new PointOfSaleController(options).getRouter());
application.app.use('/v1/transactions', new TransactionController(options).getRouter());
+ application.app.use('/v1/transactions/summary', new TransactionSummaryController(options).getRouter());
application.app.use('/v1/vouchergroups', new VoucherGroupController(options).getRouter());
application.app.use('/v1/transfers', new TransferController(options).getRouter());
application.app.use('/v1/fines', new DebtorController(options).getRouter());
diff --git a/src/service/transaction-summary-service.ts b/src/service/transaction-summary-service.ts
new file mode 100644
index 00000000..a61d5c1a
--- /dev/null
+++ b/src/service/transaction-summary-service.ts
@@ -0,0 +1,128 @@
+/**
+ * SudoSOS back-end API service.
+ * Copyright (C) 2024 Study association GEWIS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * @license
+ */
+
+/**
+ * This is the module page of the transaction summaries.
+ * Not that this module has been created in very strict time constraints,
+ * so its implementation is very minimal.
+ * https://github.com/GEWIS/sudosos-backend/pull/415
+ *
+ * @module transaction-summaries
+ */
+
+import { Dinero } from 'dinero.js';
+import User from '../entity/user/user';
+import WithManager from '../database/with-manager';
+import SubTransactionRow from '../entity/transactions/sub-transaction-row';
+import Transaction from '../entity/transactions/transaction';
+import SubTransaction from '../entity/transactions/sub-transaction';
+import ProductRevision from '../entity/product/product-revision';
+import { SelectQueryBuilder } from 'typeorm';
+import DineroTransformer from '../entity/transformer/dinero-transformer';
+import { ContainerSummaryResponse } from '../controller/response/transaction-summary-response';
+
+interface BaseSummary {
+ user: User;
+ totalInclVat: Dinero;
+ amountOfProducts: number;
+}
+
+interface ProductSummary extends BaseSummary {
+ productId: number;
+}
+
+interface ContainerSummary extends BaseSummary {
+ containerId: number;
+}
+
+interface PointOfSaleSummary extends BaseSummary {
+ pointOfSaleId: number;
+}
+
+interface UserSummary extends BaseSummary {
+ user: User;
+ products: ProductSummary[];
+ containers: ContainerSummary[];
+ pointsOfSale: PointOfSaleSummary[];
+}
+
+interface SummaryFilters {
+ containerId?: number;
+}
+
+/**
+ * Minimal implementation of the summary service.
+ * https://github.com/GEWIS/sudosos-backend/pull/415
+ */
+export default class TransactionSummaryService extends WithManager {
+ public static toContainerSummaryResponse(containerSummary: ContainerSummary): ContainerSummaryResponse {
+ return {
+ user: {
+ id: containerSummary.user.id,
+ firstName: containerSummary.user.firstName,
+ nickname: containerSummary.user.nickname,
+ lastName: containerSummary.user.lastName,
+ },
+ totalInclVat: containerSummary.totalInclVat.toObject(),
+ amountOfProducts: containerSummary.amountOfProducts,
+ containerId: containerSummary.containerId,
+ };
+ }
+
+ private getBaseQueryBuilder(): SelectQueryBuilder {
+ return this.manager.createQueryBuilder(User, 'user')
+ .innerJoinAndSelect(Transaction, 'transaction', 'transaction.fromId = user.id')
+ .innerJoinAndSelect(SubTransaction, 'subTransaction', 'subTransaction.transactionId = transaction.id')
+ // .innerJoinAndSelect(ContainerRevision, 'containerRevision', 'containerRevision.containerId = subTransaction.containerContainerId AND containerRevision.revision = subTransaction.containerRevision')
+ .innerJoinAndSelect(SubTransactionRow, 'subTransactionRow', 'subTransactionRow.subTransactionId = subTransaction.id')
+ .innerJoin(ProductRevision, 'productRevision', 'productRevision.productId = subTransactionRow.productProductId AND productRevision.revision = subTransactionRow.productRevision')
+ .addSelect('sum(subTransactionRow.amount * productRevision.priceInclVat) as totalValueInclVat')
+ .addSelect('sum(subTransactionRow.amount) as totalAmount');
+ }
+
+ private addFilters(query: SelectQueryBuilder, filters?: SummaryFilters): SelectQueryBuilder {
+ if (!filters) return query;
+ if (filters.containerId) query.where('subTransaction.containerContainerId = :containerId', { containerId: filters.containerId });
+ return query;
+ }
+
+ public async getContainerSummary(filters?: SummaryFilters): Promise {
+ const query = this.getBaseQueryBuilder()
+ .groupBy('user.id, subTransaction.containerContainerId');
+
+ const data = await this.addFilters(query, filters)
+ .getRawAndEntities();
+
+ return data.raw.map((r): ContainerSummary => {
+ const user = data.entities.find((u) => u.id === r.user_id);
+ return {
+ user,
+ totalInclVat: DineroTransformer.Instance.from(r.totalValueInclVat),
+ amountOfProducts: Number(r.totalAmount),
+ containerId: r.subTransaction_containerContainerId,
+ };
+ });
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ public async getSummary(filters?: SummaryFilters): Promise {
+ throw new Error('Not yet implemented');
+ }
+}
diff --git a/test/unit/controller/transaction-summary-controller.ts b/test/unit/controller/transaction-summary-controller.ts
new file mode 100644
index 00000000..d432eb44
--- /dev/null
+++ b/test/unit/controller/transaction-summary-controller.ts
@@ -0,0 +1,132 @@
+/**
+ * SudoSOS back-end API service.
+ * Copyright (C) 2024 Study association GEWIS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * @license
+ */
+import { DefaultContext, defaultBefore, finishTestDB } from '../../helpers/test-helpers';
+import TransactionSummaryController from '../../../src/controller/transaction-summary-controller';
+import { ContainerSeeder, PointOfSaleSeeder, RbacSeeder, TransactionSeeder, UserSeeder } from '../../seed';
+import Container from '../../../src/entity/container/container';
+import Transaction from '../../../src/entity/transactions/transaction';
+import { expect, request } from 'chai';
+import User, { UserType } from '../../../src/entity/user/user';
+import { ContainerSummaryResponse } from '../../../src/controller/response/transaction-summary-response';
+import { json } from 'body-parser';
+import TokenMiddleware from '../../../src/middleware/token-middleware';
+
+describe('TransactionSummaryController', () => {
+ let ctx: DefaultContext & {
+ controller: TransactionSummaryController,
+ admin: User,
+ user: User,
+ adminToken: string,
+ userToken: string,
+ containers: Container[],
+ transactions: Transaction[],
+ };
+
+ before(async () => {
+ const d = await defaultBefore();
+
+ const users = await new UserSeeder().seed();
+ const { containers, containerRevisions } = await new ContainerSeeder().seed(users);
+ const { pointOfSaleRevisions } = await new PointOfSaleSeeder().seed(users, containerRevisions);
+ const { transactions } = await new TransactionSeeder().seed(users, pointOfSaleRevisions);
+
+ const all = { all: new Set(['*']) };
+ const roles = await new RbacSeeder().seed([{
+ name: 'Admin',
+ permissions: {
+ Transaction: {
+ create: all,
+ get: all,
+ update: all,
+ delete: all,
+ },
+ },
+ assignmentCheck: async (usr: User) => usr.type === UserType.LOCAL_ADMIN,
+ }]);
+ await d.roleManager.initialize();
+
+ const admin = users.find((u) => u.type === UserType.LOCAL_ADMIN);
+ const user = users.find((u) => u.type === UserType.LOCAL_USER);
+ const adminToken = await d.tokenHandler.signToken(await new RbacSeeder().getToken(admin, roles), 'nonce admin');
+ const userToken = await d.tokenHandler.signToken(await new RbacSeeder().getToken(user, roles), 'nonce user');
+
+ const controller = new TransactionSummaryController({ specification: d.specification, roleManager: d.roleManager });
+ d.app.use(json());
+ d.app.use(new TokenMiddleware({ tokenHandler: d.tokenHandler, refreshFactor: 0.5 }).getMiddleware());
+ d.app.use('/transactions/summary', controller.getRouter());
+
+ ctx = {
+ ...d,
+ controller,
+ admin,
+ user,
+ adminToken,
+ userToken,
+ containers,
+ transactions,
+ };
+ });
+
+ after(async () => {
+ await finishTestDB(ctx.connection);
+ });
+
+ describe('GET /transactions/summary/container/:id', () => {
+ it('should correctly return response', async () => {
+ const container = ctx.containers[0];
+ const res = await request(ctx.app)
+ .get(`/transactions/summary/container/${container.id}`)
+ .set('Authorization', `Bearer ${ctx.adminToken}`);
+
+ expect(res.status).to.equal(200);
+
+ const validation = ctx.specification.validateModel('Array', res.body, false, true);
+ expect(validation.valid).to.be.true;
+
+ const seenUsers = new Set();
+ const body = res.body as ContainerSummaryResponse[];
+ body.forEach((summary) => {
+ expect(summary.containerId).to.equal(container.id);
+ seenUsers.add(summary.user.id);
+ });
+
+ expect(seenUsers.size).to.equal(body.length);
+ });
+
+ it('should return 404 if container does not exist', async () => {
+ const containerId = ctx.containers.length + 1;
+ const res = await request(ctx.app)
+ .get(`/transactions/summary/container/${containerId}`)
+ .set('Authorization', `Bearer ${ctx.adminToken}`);
+
+ expect(res.status).to.equal(404);
+ expect(res.body).to.equal('Container not found.');
+ });
+
+ it('should return 403 if not admin', async () => {
+ const container = ctx.containers[0];
+ const res = await request(ctx.app)
+ .get(`/transactions/summary/container/${container.id}`)
+ .set('Authorization', `Bearer ${ctx.userToken}`);
+
+ expect(res.status).to.equal(403);
+ });
+ });
+});
diff --git a/test/unit/service/transaction-summary-service.ts b/test/unit/service/transaction-summary-service.ts
new file mode 100644
index 00000000..e0ed7599
--- /dev/null
+++ b/test/unit/service/transaction-summary-service.ts
@@ -0,0 +1,152 @@
+/**
+ * SudoSOS back-end API service.
+ * Copyright (C) 2024 Study association GEWIS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ * @license
+ */
+import { defaultBefore, DefaultContext, finishTestDB } from '../../helpers/test-helpers';
+import { ContainerSeeder, PointOfSaleSeeder, TransactionSeeder, UserSeeder } from '../../seed';
+import Container from '../../../src/entity/container/container';
+import User from '../../../src/entity/user/user';
+import Transaction from '../../../src/entity/transactions/transaction';
+import TransactionSummaryService from '../../../src/service/transaction-summary-service';
+import Dinero from 'dinero.js';
+import { expect } from 'chai';
+
+describe('TransactionSummaryService', () => {
+ let ctx: DefaultContext & {
+ users: User[],
+ containers: Container[],
+ transactions: Transaction[],
+ };
+
+ before(async () => {
+ const d = await defaultBefore();
+
+ const users = await new UserSeeder().seed();
+ const { containers, containerRevisions } = await new ContainerSeeder().seed(users);
+ const { pointOfSaleRevisions } = await new PointOfSaleSeeder().seed(users, containerRevisions);
+ const { transactions } = await new TransactionSeeder().seed(users, pointOfSaleRevisions);
+
+ ctx = {
+ ...d,
+ users,
+ containers,
+ transactions,
+ };
+ });
+
+ after(async () => {
+ await finishTestDB(ctx.connection);
+ });
+
+ describe('#getContainerSummary', () => {
+ const calculateActualValues = (user: User, containerId: number) => {
+ const transactions = ctx.transactions.filter((t) => t.from.id === user.id
+ && t.subTransactions.some((st) => st.container.containerId === containerId));
+ const subTransactions = transactions.map((t) => t.subTransactions)
+ .flat()
+ .filter((subTransaction) => subTransaction?.container.containerId === containerId);
+ const subTransactionRows = subTransactions.map((st) => st.subTransactionRows).flat();
+
+ const amountOfProducts = subTransactionRows.reduce((total, str) => total + str.amount, 0);
+ const totalInclVat = subTransactionRows.reduce((total, str) => total.add(str.product.priceInclVat.multiply(str.amount)), Dinero());
+
+ return { amountOfProducts, totalInclVat };
+ };
+
+ it('should return the summary of all user\'s purchases for each container', async () => {
+ const summaries = await new TransactionSummaryService().getContainerSummary();
+ const seenUserIds = new Set();
+
+ let actualTotalValue = Dinero();
+
+ summaries.forEach((summary) => {
+ seenUserIds.add(summary.user.id);
+ const expectedUser = ctx.users.find((u) => u.id === summary.user.id);
+ expect(expectedUser).to.not.be.undefined;
+ expect(expectedUser.firstName).to.equal(summary.user.firstName);
+ expect(expectedUser.lastName).to.equal(summary.user.lastName);
+
+ const { amountOfProducts, totalInclVat } = calculateActualValues(summary.user, summary.containerId);
+ expect(summary.amountOfProducts).to.equal(amountOfProducts);
+ expect(summary.totalInclVat.getAmount()).to.equal(totalInclVat.getAmount());
+
+ actualTotalValue = actualTotalValue.add(summary.totalInclVat);
+ });
+
+ const expectedTotalValue = ctx.transactions.reduce((totalTransaction, t) => {
+ const subTransactionValue = t.subTransactions.reduce((totalSubTransaction, st) => {
+ const subTransactionRowValue = st.subTransactionRows.reduce((totalSubTransactionRow, str) => {
+ return totalSubTransactionRow.add(str.product.priceInclVat.multiply(str.amount));
+ }, Dinero());
+ return totalSubTransaction.add(subTransactionRowValue);
+ }, Dinero());
+ return totalTransaction.add(subTransactionValue);
+ }, Dinero());
+
+ // Sum of all summaries should add up to the complete sum of all transactions
+ expect(actualTotalValue.getAmount()).to.equal(expectedTotalValue.getAmount());
+
+ const missingUsers = ctx.users.filter((u) => !seenUserIds.has(u.id));
+ // If an user is missing, it should be because the user has no (or incorrect) transactions
+ if (missingUsers.length > 0) {
+ missingUsers.forEach((u) => {
+ const transactions = ctx.transactions.filter((t) => t.from.id === u.id);
+ if (transactions.length > 0) {
+ const subTransactions = transactions.map((t) => t.subTransactions).flat();
+ if (subTransactions.length > 0) {
+ const subTransactionRows = subTransactions.map((st) => st.subTransactionRows).flat();
+ // Should have no valid transactions. Otherwise, this user was not included!
+ expect(subTransactionRows.length).to.equal(0);
+ }
+ }
+ });
+ }
+ });
+ it('should filter on container ID', async () => {
+ const container = ctx.containers[0];
+ const summaries = await new TransactionSummaryService().getContainerSummary({ containerId: container.id });
+
+ let actualTotalValue = Dinero();
+
+ summaries.forEach((summary) => {
+ expect(summary.containerId).to.equal(container.id);
+ actualTotalValue = actualTotalValue.add(summary.totalInclVat);
+ });
+
+ const expectedTotalValue = ctx.transactions.reduce((totalTransaction, t) => {
+ const subTransactionValue = t.subTransactions.reduce((totalSubTransaction, st) => {
+ if (st.container.containerId !== container.id) return totalSubTransaction;
+ const subTransactionRowValue = st.subTransactionRows.reduce((totalSubTransactionRow, str) => {
+ return totalSubTransactionRow.add(str.product.priceInclVat.multiply(str.amount));
+ }, Dinero());
+ return totalSubTransaction.add(subTransactionRowValue);
+ }, Dinero());
+ return totalTransaction.add(subTransactionValue);
+ }, Dinero());
+
+ // Sum of all summaries should add up to the complete sum of all transactions using this container
+ expect(actualTotalValue.getAmount()).to.equal(expectedTotalValue.getAmount());
+ });
+ it('should return empty array if container does not exist', async () => {
+ const containerId = ctx.containers.length + 1;
+ const summaries = await new TransactionSummaryService().getContainerSummary({ containerId });
+
+ expect(summaries.length).to.equal(0);
+ });
+ });
+});