Skip to content

Commit

Permalink
Add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
myrotvorets-team committed Sep 27, 2023
1 parent 68360a6 commit e7b3dde
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .mocharc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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': [
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
72 changes: 72 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
2 changes: 1 addition & 1 deletion src/lib/tracing.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ const configurator = new OpenTelemetryConfigurator({
});

configurator.start();
/* c8 ignore end */
/* c8 ignore stop */
4 changes: 2 additions & 2 deletions src/server.mts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function configureApp(app: Express): Promise<void> {
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 }));
Expand Down Expand Up @@ -95,4 +95,4 @@ export async function run(): Promise<void> {
const server = await createServer(app);
server.listen(env.PORT);
}
/* c8 ignore end */
/* c8 ignore stop */
9 changes: 5 additions & 4 deletions src/services/compare.mts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,7 +10,7 @@ export class CompareService {
this.client = client;
}

public async upload(files: Express.Multer.File[]): Promise<string> {
public async upload(files: File[]): Promise<string> {
if (!Array.isArray(files)) {
throw new TypeError('"files" must be an array');
}
Expand All @@ -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',
);
Expand All @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/services/search.mts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -32,7 +33,7 @@ export class SearchService {
this.client.timeout = 3_600_000;
}

public async upload(file: Express.Multer.File, minSimilarity: number): Promise<string> {
public async upload(file: File, minSimilarity: number): Promise<string> {
const response = await this.client.uploadPhotoForSearch(
file.path ? createReadStream(file.path) : file.buffer,
undefined,
Expand Down
3 changes: 3 additions & 0 deletions src/services/types.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* c8 ignore start */
export type File = Pick<Express.Multer.File, 'path' | 'originalname' | 'buffer'>;
/* c8 ignore stop */
151 changes: 151 additions & 0 deletions test/unit/services/compare.test.mts
Original file line number Diff line number Diff line change
@@ -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<typeof startCompareMock>[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<typeof startCompareMock>[0], 1, '0')).thenResolve(
startCompareAckSuccess,
);

when(
uploadPhotoForComparisonMock(
matchers.anything() as Parameters<typeof uploadPhotoForComparisonMock>[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<typeof startCompareMock>[0], 1, '0')).thenResolve(
startCompareAckSuccess,
);

when(
uploadPhotoForComparisonMock(
matchers.anything() as Parameters<typeof uploadPhotoForComparisonMock>[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);
});
});
});
Loading

0 comments on commit e7b3dde

Please sign in to comment.