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
624 changes: 620 additions & 4 deletions backend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"multer": "^2.2.0",
"pg": "^8.22.0",
"prisma": "^7.8.0",
"sharp": "^0.35.3",
"slugify": "^1.6.9",
"xss": "^1.0.15"
},
Expand Down
7 changes: 7 additions & 0 deletions backend/src/__tests__/env.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Carregado pelo Jest antes de qualquer modulo ser importado
// Garante que os testes usam um banco PostgreSQL isolado
const os = require('os');
const path = require('path');

process.env.NODE_ENV = 'test';
if (process.env.TEST_DATABASE_URL) {
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
Expand All @@ -8,3 +11,7 @@ process.env.JWT_SECRET = 'jest-test-secret-key-nao-usar-em-producao';
process.env.FRONTEND_URL = 'http://localhost:8080';
process.env.PORT = '3001';
process.env.COOKIE_DOMAIN = '';
process.env.CLOUDINARY_CLOUD_NAME = '';
process.env.CLOUDINARY_API_KEY = '';
process.env.CLOUDINARY_API_SECRET = '';
process.env.UPLOADS_DIR = path.join(os.tmpdir(), `comercio-bes-test-uploads-${process.pid}`);
6 changes: 6 additions & 0 deletions backend/src/__tests__/frontendSecurity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function loadFrontendModule(relativePath, exports, context = {}) {
vm.runInNewContext(source, {
module,
URL,
API_ORIGIN: 'https://api.comerciobes.com.br',
window: { location: { origin: 'https://comerciobes.com.br' } },
...context,
}, { filename: relativePath });
Expand Down Expand Up @@ -73,6 +74,8 @@ describe('XSS e Content Security Policy do frontend', () => {
.find(directive => directive.trim().startsWith('script-src'));
expect(scriptSrc).toContain("script-src 'self' https://unpkg.com");
expect(scriptSrc).not.toContain("'unsafe-inline'");
expect(meta[1]).toContain('img-src');
expect(meta[1]).toContain('https://api.comerciobes.com.br');
// A politica portatil nao restringe conexoes: o fallback file:// da #41
// ainda precisa alcancar http://localhost:3000.
expect(meta[1]).not.toContain('connect-src');
Expand Down Expand Up @@ -108,6 +111,9 @@ describe('XSS e Content Security Policy do frontend', () => {
expect(utils.safeImageUrl('data:image/svg+xml,<svg onload=alert(1)>')).toBeNull();
expect(utils.safeImageUrl('https://res.cloudinary.com.evil.test/foto.jpg')).toBeNull();
expect(utils.safeImageUrl('/uploads/../admin/segredo.jpg')).toBeNull();
expect(utils.safeImageUrl('/uploads/comercio-123.webp'))
.toBe('https://api.comerciobes.com.br/uploads/comercio-123.webp');
expect(utils.safeImageUrl('https://evil.test/uploads/comercio-123.webp')).toBeNull();
expect(utils.safeCoordinates(null, null)).toBeNull();
expect(utils.safeCoordinates('', '')).toBeNull();
expect(utils.safeCoordinates(0, 0)).toBeNull();
Expand Down
70 changes: 70 additions & 0 deletions backend/src/__tests__/imageUploadCloudinary.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const fs = require('fs/promises');
const { PassThrough } = require('stream');
const sharp = require('sharp');

process.env.CLOUDINARY_CLOUD_NAME = 'cloud-teste';
process.env.CLOUDINARY_API_KEY = 'key-teste';
process.env.CLOUDINARY_API_SECRET = 'secret-teste';

jest.mock('cloudinary', () => ({
v2: {
config: jest.fn(),
uploader: {
destroy: jest.fn(),
upload_stream: jest.fn(),
},
},
}));

const cloudinary = require('cloudinary').v2;
const { storeUploadedImages } = require('../lib/imageUpload');

describe('Compensacao do Cloudinary', () => {
afterAll(async () => {
process.env.CLOUDINARY_CLOUD_NAME = '';
process.env.CLOUDINARY_API_KEY = '';
process.env.CLOUDINARY_API_SECRET = '';
await fs.rm(process.env.UPLOADS_DIR, { recursive: true, force: true });
});

it('remove os dois IDs deterministas quando o thumbnail falha', async () => {
const uploadOptions = [];
let callNumber = 0;
cloudinary.uploader.destroy.mockResolvedValue({ result: 'ok' });
cloudinary.uploader.upload_stream.mockImplementation((options, callback) => {
uploadOptions.push(options);
callNumber += 1;
const currentCall = callNumber;
const stream = new PassThrough();
stream.on('finish', () => {
if (currentCall === 1) {
callback(null, {
public_id: `comercio-bes/${options.public_id}`,
secure_url: `https://res.cloudinary.com/cloud-teste/${options.public_id}.webp`,
});
} else {
callback(new Error('falha simulada no thumbnail'));
}
});
return stream;
});
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const buffer = await sharp({
create: {
width: 32,
height: 32,
channels: 3,
background: { r: 10, g: 80, b: 160 },
},
}).png().toBuffer();

await expect(storeUploadedImages([{ buffer, mimetype: 'image/png' }]))
.rejects.toMatchObject({ code: 'REMOTE_IMAGE_STORAGE_FAILED', statusCode: 502 });

const expectedIds = uploadOptions.map(options => `comercio-bes/${options.public_id}`);
expect(uploadOptions).toHaveLength(2);
expect(cloudinary.uploader.destroy.mock.calls.map(call => call[0]))
.toEqual(expectedIds);
consoleSpy.mockRestore();
});
});
Loading
Loading