Skip to content
Open
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
213 changes: 213 additions & 0 deletions backend/src/api/controllers/sep12.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,33 @@ const cryptoMock = {
decrypt: jest.fn((v: string) => v),
};

const storageProviderMock = {
generatePresignedPutUrl: jest.fn().mockResolvedValue('https://mock-bucket.mock.storage/kyc/test/field1/uuid?X-Mock-Signed=1'),
objectExists: jest.fn().mockResolvedValue(true),
};

const uploadStoreMock = {
create: jest.fn(() => ({
uploadId: 'test-uuid',
account: VALID_ACCOUNT,
fieldName: 'id_photo_front',
storageKey: '',
contentType: 'image/jpeg',
expiresAt: new Date(Date.now() + 900 * 1000),
status: 'PENDING',
})),
get: jest.fn(() => ({
uploadId: 'test-uuid',
account: VALID_ACCOUNT,
fieldName: 'id_photo_front',
storageKey: 'kyc/test/uuid/id_photo_front',
contentType: 'image/jpeg',
expiresAt: new Date(Date.now() + 900 * 1000),
status: 'PENDING',
})),
setStatus: jest.fn(),
};

jest.mock('../../lib/prisma', () => ({
__esModule: true,
default: prismaMock,
Expand All @@ -54,6 +81,23 @@ jest.mock('../../services/crypto.service', () => ({
cryptoService: cryptoMock,
}));

jest.mock('../../services/storage-provider.service', () => ({
__esModule: true,
storageProvider: storageProviderMock,
}));

jest.mock('../../services/upload-store.service', () => ({
__esModule: true,
uploadStore: uploadStoreMock,
}));

jest.mock('../../config/env', () => ({
__esModule: true,
config: {
SEP12_MAX_FILE_SIZE_MB: 10,
},
}));

jest.mock('../../utils/logger', () => ({
__esModule: true,
default: {
Expand Down Expand Up @@ -85,6 +129,175 @@ describe('Sep12Controller', () => {
jest.clearAllMocks();
});

describe('getUploadUrl', () => {
it('returns pre-signed URL when all parameters are valid', async () => {
const req = {
body: {
account: VALID_ACCOUNT,
field_name: 'id_photo_front',
content_type: 'image/jpeg',
file_size: '1000000',
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.getUploadUrl(req, res);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
upload_id: 'test-uuid',
url: expect.any(String),
expires_at: expect.any(String),
}));
expect(storageProviderMock.generatePresignedPutUrl).toHaveBeenCalled();
expect(uploadStoreMock.create).toHaveBeenCalled();
expect(uploadStoreMock.setStatus).toHaveBeenCalledWith('test-uuid', 'PENDING');
});

it('returns 400 when required parameters are missing', async () => {
const req = {
body: {
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.getUploadUrl(req, res);

expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({ error: 'account, field_name, content_type, and file_size are required' });
});

it('returns 400 when content type is invalid', async () => {
const req = {
body: {
account: VALID_ACCOUNT,
field_name: 'id_photo_front',
content_type: 'application/zip',
file_size: '1000000',
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.getUploadUrl(req, res);

expect(res.status).toHaveBeenCalledWith(400);
});

it('returns 400 when file size is larger than max allowed', async () => {
const req = {
body: {
account: VALID_ACCOUNT,
field_name: 'id_photo_front',
content_type: 'image/jpeg',
file_size: '110000000',
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.getUploadUrl(req, res);

expect(res.status).toHaveBeenCalledWith(400);
});
});

describe('confirmUpload', () => {
it('confirms upload when upload exists and file is present in storage', async () => {
const req = {
body: {
upload_id: 'test-uuid',
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.confirmUpload(req, res);

expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
upload_id: 'test-uuid',
status: 'COMPLETED',
});
expect(storageProviderMock.objectExists).toHaveBeenCalled();
expect(uploadStoreMock.setStatus).toHaveBeenCalledWith('test-uuid', 'COMPLETED');
});

it('returns 400 when required parameters are missing', async () => {
const req = {
body: {
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.confirmUpload(req, res);

expect(res.status).toHaveBeenCalledWith(400);
});

it('returns 404 when upload record not found', async () => {
uploadStoreMock.get.mockResolvedValueOnce(undefined);
const req = {
body: {
upload_id: 'invalid-uuid',
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.confirmUpload(req, res);

expect(res.status).toHaveBeenCalledWith(404);
});

it('returns 403 when account does not match upload record', async () => {
uploadStoreMock.get.mockResolvedValueOnce({
uploadId: 'test-uuid',
account: 'GBZXN7PIRZGNMHGA7MUUUF4GW3F55GQRQ5UKMJTDEFEKTGW4RHFDQLNZ',
fieldName: 'id_photo_front',
storageKey: 'kyc/test/uuid/id_photo_front',
contentType: 'image/jpeg',
expiresAt: new Date(Date.now() + 900 * 1000),
status: 'PENDING',
});
const req = {
body: {
upload_id: 'test-uuid',
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.confirmUpload(req, res);

expect(res.status).toHaveBeenCalledWith(403);
});

it('returns 422 when file not found in storage', async () => {
storageProviderMock.objectExists.mockResolvedValueOnce(false);
const req = {
body: {
upload_id: 'test-uuid',
account: VALID_ACCOUNT,
},
user: { publicKey: VALID_ACCOUNT },
} as unknown as Request;
const res = makeRes();

await sep12Controller.confirmUpload(req, res);

expect(res.status).toHaveBeenCalledWith(422);
});
});

describe('putCustomer', () => {
it('returns 400 when account is missing', async () => {
const req = {
Expand Down