Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
178 changes: 178 additions & 0 deletions backend/cmmty/tests/documents.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { DataSource } from 'typeorm';
import { AppModule } from '../src/app.module';

describe('Documents Integration', () => {
let app: INestApplication;
let dataSource: DataSource;

const mockQueue = {
add: jest.fn(),
};

const mockStellarService = {
createAnchorJob: jest.fn().mockResolvedValue({
jobId: 'stellar-job-123',
}),
};

beforeAll(async () => {
const moduleFixture: TestingModule =
await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider('DOCUMENT_QUEUE')
.useValue(mockQueue)
.overrideProvider('StellarService')
.useValue(mockStellarService)
.compile();

app = moduleFixture.createNestApplication();

await app.init();

dataSource = moduleFixture.get(DataSource);
});

afterEach(async () => {
await dataSource.query(`DELETE FROM documents`);
jest.clearAllMocks();
});

afterAll(async () => {
await app.close();
});

it('uploads document successfully', async () => {
const response = await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
Buffer.from('valid file content'),
{
filename: 'document.pdf',
contentType: 'application/pdf',
},
)
.expect(201);

expect(response.body).toHaveProperty('id');
expect(response.body.status).toBe('PENDING');
});

it('rejects invalid MIME type', async () => {
await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
Buffer.from('invalid content'),
{
filename: 'malware.exe',
contentType: 'application/x-msdownload',
},
)
.expect(400);
});

it('rejects file exceeding 20MB', async () => {
const oversizedBuffer = Buffer.alloc(21 * 1024 * 1024);

await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
oversizedBuffer,
{
filename: 'large.pdf',
contentType: 'application/pdf',
},
)
.expect(400);
});

it('rejects duplicate document hash', async () => {
const fileBuffer = Buffer.from('duplicate file');

await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
fileBuffer,
{
filename: 'doc1.pdf',
contentType: 'application/pdf',
},
)
.expect(201);

await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
fileBuffer,
{
filename: 'doc2.pdf',
contentType: 'application/pdf',
},
)
.expect(409);
});

it('handles status transitions from PENDING → ANALYZING → VERIFIED', async () => {
const uploadResponse = await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
Buffer.from('verification document'),
{
filename: 'verify.pdf',
contentType: 'application/pdf',
},
);

const documentId = uploadResponse.body.id;

await request(app.getHttpServer())
.patch(`/documents/${documentId}/status`)
.send({
status: 'ANALYZING',
})
.expect(200);

const verifiedResponse = await request(app.getHttpServer())
.patch(`/documents/${documentId}/status`)
.send({
status: 'VERIFIED',
})
.expect(200);

expect(verifiedResponse.body.status).toBe('VERIFIED');
});

it('verification endpoint triggers Stellar anchor job', async () => {
const uploadResponse = await request(app.getHttpServer())
.post('/documents/upload')
.attach(
'file',
Buffer.from('stellar verification'),
{
filename: 'stellar.pdf',
contentType: 'application/pdf',
},
);

const documentId = uploadResponse.body.id;

await request(app.getHttpServer())
.post(`/documents/${documentId}/verify`)
.expect(200);

expect(
mockStellarService.createAnchorJob,
).toHaveBeenCalled();

expect(mockQueue.add).toHaveBeenCalled();
});
});
135 changes: 135 additions & 0 deletions backend/cmmty/tests/risk-assessment.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RiskAssessmentService } from '../src/modules/risk/risk-assessment.service';

describe('RiskAssessmentService', () => {
let service: RiskAssessmentService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [RiskAssessmentService],
}).compile();

service = module.get<RiskAssessmentService>(
RiskAssessmentService,
);
});

const baseDocument = {
amount: 100,
country: 'NG',
duplicateHash: false,
suspiciousMetadata: false,
blacklistedSender: false,
invalidSignature: false,
rapidSubmission: false,
tamperedDocument: false,
};

it('detects duplicate hash flag', () => {
const result = service.assess({
...baseDocument,
duplicateHash: true,
});

expect(result.flags).toContain('DUPLICATE_HASH');
});

it('detects suspicious metadata flag', () => {
const result = service.assess({
...baseDocument,
suspiciousMetadata: true,
});

expect(result.flags).toContain(
'SUSPICIOUS_METADATA',
);
});

it('detects blacklisted sender flag', () => {
const result = service.assess({
...baseDocument,
blacklistedSender: true,
});

expect(result.flags).toContain(
'BLACKLISTED_SENDER',
);
});

it('detects invalid signature flag', () => {
const result = service.assess({
...baseDocument,
invalidSignature: true,
});

expect(result.flags).toContain(
'INVALID_SIGNATURE',
);
});

it('detects rapid submission flag', () => {
const result = service.assess({
...baseDocument,
rapidSubmission: true,
});

expect(result.flags).toContain(
'RAPID_SUBMISSION',
);
});

it('detects tampered document flag', () => {
const result = service.assess({
...baseDocument,
tamperedDocument: true,
});

expect(result.flags).toContain(
'TAMPERED_DOCUMENT',
);
});

it('returns score = 0 when no flags exist', () => {
const result = service.assess(baseDocument);

expect(result.score).toBe(0);
expect(result.flags).toHaveLength(0);
});

it('caps score at 100 with multiple high-weight flags', () => {
const result = service.assess({
...baseDocument,
duplicateHash: true,
suspiciousMetadata: true,
blacklistedSender: true,
invalidSignature: true,
rapidSubmission: true,
tamperedDocument: true,
});

expect(result.score).toBeLessThanOrEqual(100);
});

it('triggers all 6 flags simultaneously', () => {
const result = service.assess({
...baseDocument,
duplicateHash: true,
suspiciousMetadata: true,
blacklistedSender: true,
invalidSignature: true,
rapidSubmission: true,
tamperedDocument: true,
});

expect(result.flags).toEqual(
expect.arrayContaining([
'DUPLICATE_HASH',
'SUSPICIOUS_METADATA',
'BLACKLISTED_SENDER',
'INVALID_SIGNATURE',
'RAPID_SUBMISSION',
'TAMPERED_DOCUMENT',
]),
);
});
});
Loading