From e7b3dde909a5c075e1d9c1383d9adacc9e38017b Mon Sep 17 00:00:00 2001 From: Myrotvorets Date: Wed, 27 Sep 2023 09:24:12 +0300 Subject: [PATCH] Add more tests --- .mocharc.cjs | 2 +- Dockerfile | 2 +- package-lock.json | 72 +++++++++++ package.json | 1 + src/index.mts | 2 +- src/lib/tracing.mts | 2 +- src/server.mts | 4 +- src/services/compare.mts | 9 +- src/services/search.mts | 7 +- src/services/types.mts | 3 + test/unit/services/compare.test.mts | 151 ++++++++++++++++++++++ test/unit/services/fakeclient.mts | 58 +++++++++ test/unit/services/fixtures.mts | 190 ++++++++++++++++++++++++++++ 13 files changed, 490 insertions(+), 13 deletions(-) create mode 100644 src/services/types.mts create mode 100644 test/unit/services/compare.test.mts create mode 100644 test/unit/services/fakeclient.mts create mode 100644 test/unit/services/fixtures.mts diff --git a/.mocharc.cjs b/.mocharc.cjs index ef19c9e1..040da39e 100644 --- a/.mocharc.cjs +++ b/.mocharc.cjs @@ -2,7 +2,7 @@ module.exports = { recursive: true, extension: ['.test.mts'], - 'node-option': ['loader=ts-node/esm', 'no-warnings'], + 'node-option': ['loader=ts-node/esm', 'loader=testdouble', 'no-warnings'], require: 'mocha.setup.mjs', reporter: 'mocha-multi', 'reporter-option': [ diff --git a/Dockerfile b/Dockerfile index 774a3486..37d17838 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ COPY --chown=nobody:nobody ./package.json ./package-lock.json ./tsconfig.json .n RUN \ npm r --package-lock-only \ @myrotvorets/eslint-config-myrotvorets-ts eslint-formatter-gha eslint-plugin-mocha \ - mocha @types/mocha chai @types/chai chai-as-promised @types/chai-as-promised supertest @types/supertest c8 mocha-multi mocha-reporter-gha mocha-reporter-sonarqube \ + mocha @types/mocha chai @types/chai chai-as-promised @types/chai-as-promised supertest @types/supertest testdouble c8 mocha-multi mocha-reporter-gha mocha-reporter-sonarqube \ ts-node nodemon && \ npm ci --ignore-scripts --userconfig .npmrc.local && \ rm -f .npmrc.local && \ diff --git a/package-lock.json b/package-lock.json index 4701fb94..4c0c070e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "mocha-reporter-sonarqube": "^2.0.0", "nodemon": "^3.0.1", "supertest": "^6.3.3", + "testdouble": "^3.19.0", "ts-node": "^10.9.0", "typescript": "^5.2.2" } @@ -4346,6 +4347,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-shared-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", @@ -4619,6 +4629,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -5796,6 +5812,19 @@ "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, + "node_modules/quibble": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/quibble/-/quibble-0.8.0.tgz", + "integrity": "sha512-hFN7qy9BP6H7gU5LtOCMKu1PeVGKO1jV+avf62zbKJI/tmvkNUYqA5NTxR89WUgsxzS0EACsuT11PNOFxPUBvg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "resolve": "^1.22.4" + }, + "engines": { + "node": ">= 0.14.0" + } + }, "node_modules/rambda": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", @@ -6547,6 +6576,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-object-es5": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stringify-object-es5/-/stringify-object-es5-2.5.0.tgz", + "integrity": "sha512-vE7Xdx9ylG4JI16zy7/ObKUB+MtxuMcWlj/WHHr3+yAlQoN6sst2stU9E+2Qs3OrlJw/Pf3loWxL1GauEHf6MA==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-object-es5/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6723,12 +6774,33 @@ "node": ">=8" } }, + "node_modules/testdouble": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/testdouble/-/testdouble-3.19.0.tgz", + "integrity": "sha512-iavgMMYdgnAyB7EGv2prhgtqnvhSI+K/EVPySqW4MwFjEiLJkiLBnWqlCP09ikB1Q2LargMb8W6otImp8cnLdw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "quibble": "^0.8.0", + "stringify-object-es5": "^2.5.0", + "theredoc": "^1.0.0" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/theredoc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/theredoc/-/theredoc-1.0.0.tgz", + "integrity": "sha512-KU3SA3TjRRM932jpNfD3u4Ec3bSvedyo5ITPI7zgWYnKep7BwQQaxlhI9qbO+lKJoRnoAbEVfMcAHRuKVYikDA==", + "dev": true + }, "node_modules/thriftrw": { "version": "3.11.4", "resolved": "https://registry.npmjs.org/thriftrw/-/thriftrw-3.11.4.tgz", diff --git a/package.json b/package.json index feeff0a3..ef01da16 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "mocha-reporter-sonarqube": "^2.0.0", "nodemon": "^3.0.1", "supertest": "^6.3.3", + "testdouble": "^3.19.0", "ts-node": "^10.9.0", "typescript": "^5.2.2" }, diff --git a/src/index.mts b/src/index.mts index af75208d..a2da37bf 100644 --- a/src/index.mts +++ b/src/index.mts @@ -3,4 +3,4 @@ import './lib/tracing.mjs'; import { run } from './server.mjs'; run().catch((e: unknown) => console.error(e)); -/* c8 ignore end */ +/* c8 ignore stop */ diff --git a/src/lib/tracing.mts b/src/lib/tracing.mts index 525b7598..4bfd86ed 100644 --- a/src/lib/tracing.mts +++ b/src/lib/tracing.mts @@ -13,4 +13,4 @@ const configurator = new OpenTelemetryConfigurator({ }); configurator.start(); -/* c8 ignore end */ +/* c8 ignore stop */ diff --git a/src/server.mts b/src/server.mts index 9bc0f036..41276047 100644 --- a/src/server.mts +++ b/src/server.mts @@ -37,7 +37,7 @@ export async function configureApp(app: Express): Promise { app.get('/', (_req, res) => res.redirect('/swagger/')); } } - /* c8 ignore end */ + /* c8 ignore stop */ const tempDir = await mkdtemp(join(tmpdir(), 'identigraf-')); process.once('exit', () => rmSync(tempDir, { force: true, recursive: true, maxRetries: 3 })); @@ -95,4 +95,4 @@ export async function run(): Promise { const server = await createServer(app); server.listen(env.PORT); } -/* c8 ignore end */ +/* c8 ignore stop */ diff --git a/src/services/compare.mts b/src/services/compare.mts index 571169a7..34316741 100644 --- a/src/services/compare.mts +++ b/src/services/compare.mts @@ -1,6 +1,7 @@ import { createReadStream } from 'node:fs'; -import { Client, CompareCompleted, FaceXError } from '@myrotvorets/facex'; +import { type Client, CompareCompleted, FaceXError } from '@myrotvorets/facex'; import { UploadError } from '../lib/uploaderror.mjs'; +import type { File } from './types.mjs'; export class CompareService { private readonly client: Client; @@ -9,7 +10,7 @@ export class CompareService { this.client = client; } - public async upload(files: Express.Multer.File[]): Promise { + public async upload(files: File[]): Promise { if (!Array.isArray(files)) { throw new TypeError('"files" must be an array'); } @@ -19,7 +20,7 @@ export class CompareService { } const startResponse = await this.client.startCompare( - files[0].path ? createReadStream(files[0].path) : files[0].buffer, + files[0].path ? /* c8 ignore next */ createReadStream(files[0].path) : files[0].buffer, files.length - 1, '0', ); @@ -31,7 +32,7 @@ export class CompareService { for (let i = 1; i < files.length; ++i) { // eslint-disable-next-line no-await-in-loop const uploadResponse = await this.client.uploadPhotoForComparison( - files[i].path ? createReadStream(files[i].path) : files[i].buffer, + files[i].path ? /* c8 ignore next */ createReadStream(files[i].path) : files[i].buffer, startResponse.serverRequestID, i, files.length - 1, diff --git a/src/services/search.mts b/src/services/search.mts index 17b4c2c6..b711501b 100644 --- a/src/services/search.mts +++ b/src/services/search.mts @@ -1,15 +1,16 @@ import { createReadStream } from 'node:fs'; import { CapturedFaces, - Client, + type Client, FaceXError, MatchedFaces, SearchCompleted, SearchInProgress, - SearchStats, + type SearchStats, SearchUploadAck, } from '@myrotvorets/facex'; import { UploadError } from '../lib/uploaderror.mjs'; +import type { File } from './types.mjs'; export interface RecoginizedFace { faceID: number; @@ -32,7 +33,7 @@ export class SearchService { this.client.timeout = 3_600_000; } - public async upload(file: Express.Multer.File, minSimilarity: number): Promise { + public async upload(file: File, minSimilarity: number): Promise { const response = await this.client.uploadPhotoForSearch( file.path ? createReadStream(file.path) : file.buffer, undefined, diff --git a/src/services/types.mts b/src/services/types.mts new file mode 100644 index 00000000..caf0c360 --- /dev/null +++ b/src/services/types.mts @@ -0,0 +1,3 @@ +/* c8 ignore start */ +export type File = Pick; +/* c8 ignore stop */ diff --git a/test/unit/services/compare.test.mts b/test/unit/services/compare.test.mts new file mode 100644 index 00000000..dfd12956 --- /dev/null +++ b/test/unit/services/compare.test.mts @@ -0,0 +1,151 @@ +import { expect } from 'chai'; +import { matchers, when } from 'testdouble'; +import { FaceXError } from '@myrotvorets/facex'; +import { UploadError } from '../../../src/lib/uploaderror.mjs'; +import { CompareService } from '../../../src/services/compare.mjs'; +import { File } from '../../../src/services/types.mjs'; +import { FakeClient, getComparisonResultsMock, startCompareMock, uploadPhotoForComparisonMock } from './fakeclient.mjs'; +import { + compareCompletedError, + compareCompletedErrorComment, + compareCompletedNoMatches, + compareCompletedNotRecognized, + compareCompletedPending, + compareCompletedSuccess, + compareCompletedSuccessProcessed, + compareGUID, + compareStatusUnknown, + startCompareAckError, + startCompareAckSuccess, + startCompareAckSuccess_raw, + uploadCompareAckError, + uploadCompareAckSuccess, +} from './fixtures.mjs'; + +describe('CompareService', function () { + let service: CompareService; + + before(function () { + const client = new FakeClient('https://example.com', 'FaceX/Test'); + service = new CompareService(client); + }); + + describe('#upload', function () { + /** + * @see https://github.com/myrotvorets/psb-api-identigraf/security/code-scanning/2 + */ + it('should fail if files is not an array', function () { + // @ts-expect-error -- we intentionally pass a string instead of an array + return expect(service.upload('file')).to.eventually.rejectedWith(TypeError, '"files" must be an array'); + }); + + it('should fail if there are less than two files', function () { + return expect(service.upload([])).to.eventually.rejectedWith(Error, 'Need at least two files'); + }); + + it('should throw an UploadError if startCompare fails on the first photo', function () { + when(startCompareMock(matchers.anything() as Parameters[0], 1, '0')).thenResolve( + startCompareAckError, + ); + + const files: File[] = [ + { originalname: 'file1', buffer: Buffer.from(''), path: '' }, + { originalname: 'file2', buffer: Buffer.from(''), path: '' }, + ]; + + return expect(service.upload(files)) + .to.eventually.rejectedWith(UploadError) + .that.has.property('file', files[0].originalname); + }); + + it('should throw an UploadError if startCompare fails on subsequent photos', function () { + when(startCompareMock(matchers.anything() as Parameters[0], 1, '0')).thenResolve( + startCompareAckSuccess, + ); + + when( + uploadPhotoForComparisonMock( + matchers.anything() as Parameters[0], + startCompareAckSuccess_raw.data.reqID_serv, + 1, + 1, + '1', + ), + ).thenResolve(uploadCompareAckError); + + const files: File[] = [ + { originalname: 'file1', buffer: Buffer.from(''), path: '' }, + { originalname: 'file2', buffer: Buffer.from(''), path: '' }, + ]; + + return expect(service.upload(files)) + .to.eventually.rejectedWith(UploadError) + .that.has.property('file', files[1].originalname); + }); + + it('should succeed if everything is OK', function () { + when(startCompareMock(matchers.anything() as Parameters[0], 1, '0')).thenResolve( + startCompareAckSuccess, + ); + + when( + uploadPhotoForComparisonMock( + matchers.anything() as Parameters[0], + startCompareAckSuccess_raw.data.reqID_serv, + 1, + 1, + '1', + ), + ).thenResolve(uploadCompareAckSuccess); + + const files: File[] = [ + { originalname: 'file1', buffer: Buffer.from(''), path: '' }, + { originalname: 'file2', buffer: Buffer.from(''), path: '' }, + ]; + + return expect(service.upload(files)).to.become(startCompareAckSuccess_raw.data.reqID_serv); + }); + }); + + describe('#status', function () { + it('should fail on unexpected response', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareStatusUnknown); + + return expect(service.status(compareGUID)) + .to.eventually.rejectedWith(FaceXError) + .that.has.property('message', 'Unknown error'); + }); + + it('should return false if the comparison is in progress', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareCompletedPending); + + return expect(service.status(compareGUID)).to.become(false); + }); + + it('should return null if no faces were recognized', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareCompletedNotRecognized); + + return expect(service.status(compareGUID)).to.become(null); + }); + + it('should return an empty object if there are not matches', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareCompletedNoMatches); + + return expect(service.status(compareGUID)).to.eventually.be.an('object').that.is.empty; + }); + + it('should fail on other error', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareCompletedError); + + return expect(service.status(compareGUID)) + .to.eventually.rejectedWith(FaceXError) + .that.has.property('message', compareCompletedErrorComment); + }); + + it('should succeed if everything goes well', function () { + when(getComparisonResultsMock(compareGUID)).thenResolve(compareCompletedSuccess); + + return expect(service.status(compareGUID)).to.become(compareCompletedSuccessProcessed); + }); + }); +}); diff --git a/test/unit/services/fakeclient.mts b/test/unit/services/fakeclient.mts new file mode 100644 index 00000000..6bc09f1d --- /dev/null +++ b/test/unit/services/fakeclient.mts @@ -0,0 +1,58 @@ +/* eslint-disable class-methods-use-this */ +import { FaceXClient } from '@myrotvorets/facex'; +import { func } from 'testdouble'; + +export const uploadPhotoForSearchMock = func(); +export const checkSearchStatusMock = func(); +export const getCapturedFacesMock = func(); +export const getRecognitionStatsMock = func(); +export const getMatchedFacesMock = func(); +export const startCompareMock = func(); +export const uploadPhotoForComparisonMock = func(); +export const getComparisonResultsMock = func(); + +export class FakeClient extends FaceXClient { + public uploadPhotoForSearch( + ...params: Parameters + ): ReturnType { + return uploadPhotoForSearchMock(...params); + } + + public checkSearchStatus( + ...params: Parameters + ): ReturnType { + return checkSearchStatusMock(...params); + } + + public getCapturedFaces( + ...params: Parameters + ): ReturnType { + return getCapturedFacesMock(...params); + } + + public getRecognitionStats( + ...params: Parameters + ): ReturnType { + return getRecognitionStatsMock(...params); + } + + public getMatchedFaces(...params: Parameters): ReturnType { + return getMatchedFacesMock(...params); + } + + public startCompare(...params: Parameters): ReturnType { + return startCompareMock(...params); + } + + public uploadPhotoForComparison( + ...params: Parameters + ): ReturnType { + return uploadPhotoForComparisonMock(...params); + } + + public getComparisonResults( + ...params: Parameters + ): ReturnType { + return getComparisonResultsMock(...params); + } +} diff --git a/test/unit/services/fixtures.mts b/test/unit/services/fixtures.mts new file mode 100644 index 00000000..90514281 --- /dev/null +++ b/test/unit/services/fixtures.mts @@ -0,0 +1,190 @@ +import { PhotoEntry, responseFactory } from '@myrotvorets/facex'; + +export const startCompareAckError = responseFactory({ + ans_type: 16, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: 'dd85c712-0863-4207-acb4-9b5aa9f365f4', + reqID_clnt: '380804d9-ae81-4cb6-80df-eda6fe69ab99', + segment: '0', + datetime: '2021-10-05 15:11:30', + result_code: -1, + results_amount: 0, + comment: 'Error', + fotos: [], + }, +}); + +export const startCompareAckSuccess_raw = { + ans_type: 16, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: 'dd85c712-0863-4207-acb4-9b5aa9f365f4', + reqID_clnt: '380804d9-ae81-4cb6-80df-eda6fe69ab99', + segment: '0', + datetime: '2021-10-05 15:11:30', + result_code: 3, + results_amount: 0, + comment: '', + fotos: [] as PhotoEntry[], + }, +} as const; + +export const startCompareAckSuccess = responseFactory(startCompareAckSuccess_raw); + +export const uploadCompareAckError = responseFactory({ + ans_type: 17, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: 'dd85c712-0863-4207-acb4-9b5aa9f365f4', + reqID_clnt: 'cc22f018-c69c-4fb9-addf-a38fc16160e7', + segment: '0', + datetime: '2021-10-05 15:11:31', + result_code: -1, + results_amount: 0, + comment: 'Error', + fotos: [], + }, +}); + +export const uploadCompareAckSuccess = responseFactory({ + ans_type: 17, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: 'dd85c712-0863-4207-acb4-9b5aa9f365f4', + reqID_clnt: 'cc22f018-c69c-4fb9-addf-a38fc16160e7', + segment: '0', + datetime: '2021-10-05 15:11:31', + result_code: 3, + results_amount: 0, + comment: '', + fotos: [], + }, +}); + +export const compareGUID = '40dd4dc9-d752-4c13-bbf0-ff6b619cf6b0'; + +export const compareStatusUnknown = responseFactory({ + ans_type: 0, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: '093aa5de-68a0-49e7-b130-4545a10cfde0', + segment: '0', + datetime: '2021-10-05 15:11:31', + result_code: 3, + results_amount: 0, + comment: '', + fotos: [], + }, +}); + +export const compareCompletedPending = responseFactory({ + ans_type: 18, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: 'fa56962d-398f-4116-b75f-7b14f7f31648', + segment: '0', + datetime: '2021-10-05 15:11:32', + result_code: 2, + results_amount: 0, + comment: '', + fotos: [], + }, +}); + +export const compareCompletedNotRecognized = responseFactory({ + ans_type: 18, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: 'df5b6f22-84c3-41aa-ac2b-a4587c928ddc', + segment: '0', + datetime: '2021-10-05 15:11:32', + result_code: -3, + results_amount: 0, + comment: '', + fotos: [], + }, +}); + +export const compareCompletedNoMatches = responseFactory({ + ans_type: 18, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: '2acc06a7-d25e-4294-9337-c4f3e07f4168', + segment: '0', + datetime: '2021-10-05 15:11:32', + result_code: -7, + results_amount: 0, + comment: '', + fotos: [], + }, +}); + +export const compareCompletedErrorComment = "He's dead, Jim"; +export const compareCompletedError = responseFactory({ + ans_type: 18, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: '920c1059-8120-483f-9942-3b7a06719d45', + segment: '0', + datetime: '2021-10-05 15:11:32', + result_code: -10000, + results_amount: 0, + comment: compareCompletedErrorComment, + fotos: [], + }, +}); + +export const compareCompletedSuccessProcessed: Record = { + '1': 93, +}; + +export const compareCompletedSuccess = responseFactory({ + ans_type: 18, + signature: '', + data: { + id: 'x', + client_id: 'FaceX/Test', + reqID_serv: compareGUID, + reqID_clnt: 'b65b750b-8f77-485a-b660-3895643a3453', + segment: '0', + datetime: '2021-10-05 15:11:32', + result_code: 3, + results_amount: 0, + comment: compareCompletedErrorComment, + fotos: [ + { + par1: 0, + par2: compareCompletedSuccessProcessed['1'], + par3: 0, + foto: '', + namef: '1', + namel: '', + path: '', + }, + ], + }, +});