Skip to content
Open
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
152 changes: 152 additions & 0 deletions __tests__/export.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const request = require('supertest');
const ExcelJS = require('exceljs');
const { app, pool } = require('../app');

// Mock Authentication
const originalIsAuthenticated = app.request.isAuthenticated;
app.request.isAuthenticated = function() { return true; };
Object.defineProperty(app.request, 'user', {
get: function() { return { id: 123, name: 'Test User' }; }
});

// Mock database pool
jest.mock('../app', () => {
const originalModule = jest.requireActual('../app');

// We replace the pool.query with a jest mock function
originalModule.pool.query = jest.fn();

return originalModule;
});

afterAll(done => {
// Restore authentication mocks
app.request.isAuthenticated = originalIsAuthenticated;
delete app.request.user;

// We use supertest which spins up server internally, so no app.listen needed
done();
});

beforeEach(() => {
pool.query.mockClear();
});

describe('GET /api/export-excel', () => {
test('Happy path: should export excel file with correct data and headers', async () => {
// Mock database responses
pool.query.mockImplementation((queryText, params) => {
if (queryText.includes('SELECT * FROM items WHERE user_id = $1')) {
return Promise.resolve({
rows: [
{ id: 1, name: 'Item 1', type: 'book', description: 'A nice book', room_id: 10, parent_item_id: null },
{ id: 2, name: 'Item 2', type: 'box', description: 'A big box', room_id: null, parent_item_id: 20 },
{ id: 3, name: 'Item 3', type: 'pen', description: 'A blue pen', room_id: null, parent_item_id: null } // no room or parent
]
});
} else if (queryText.includes('SELECT name, house_id FROM rooms')) {
return Promise.resolve({ rows: [{ name: 'Living Room', house_id: 100 }] });
} else if (queryText.includes('SELECT name FROM houses')) {
return Promise.resolve({ rows: [{ name: 'My House' }] });
} else if (queryText.includes('SELECT name FROM items WHERE id = $1 AND user_id = $2')) {
return Promise.resolve({ rows: [{ name: 'Parent Box' }] });
}
return Promise.resolve({ rows: [] });
});

const response = await request(app).get('/api/export-excel').responseType('blob');

// 1. Verify HTTP response
expect(response.status).toBe(200);
expect(response.headers['content-type']).toBe('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
expect(response.headers['content-disposition']).toBe('attachment; filename=inventory_export.xlsx');

// 2. Parse Excel buffer and verify contents
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(response.body);

const worksheet = workbook.getWorksheet('Инвентаризация');
expect(worksheet).toBeDefined();

// Check headers
const headerRow = worksheet.getRow(1).values;
// ExcelJS row values array is 1-indexed, so [0] is empty
expect(headerRow[1]).toBe('Название');
expect(headerRow[2]).toBe('Тип');
expect(headerRow[3]).toBe('Описание');
expect(headerRow[4]).toBe('Дом');
expect(headerRow[5]).toBe('Комната');
expect(headerRow[6]).toBe('Родительский элемент');

// Check data rows
expect(worksheet.rowCount).toBe(4); // 1 header + 3 data rows

const row2 = worksheet.getRow(2).values;
expect(row2[1]).toBe('Item 1');
expect(row2[2]).toBe('book');
expect(row2[3]).toBe('A nice book');
expect(row2[4]).toBe('My House'); // House name
expect(row2[5]).toBe('Living Room'); // Room name
expect(row2[6]).toBe(''); // No parent item

const row3 = worksheet.getRow(3).values;
expect(row3[1]).toBe('Item 2');
expect(row3[2]).toBe('box');
expect(row3[3]).toBe('A big box');
expect(row3[4]).toBe('');
expect(row3[5]).toBe('');
expect(row3[6]).toBe('Parent Box'); // Parent item name

const row4 = worksheet.getRow(4).values;
expect(row4[1]).toBe('Item 3');
expect(row4[2]).toBe('pen');
expect(row4[3]).toBe('A blue pen');
expect(row4[4]).toBe('');
expect(row4[5]).toBe('');
expect(row4[6]).toBe('');
});

test('Error edge case: should handle database errors gracefully', async () => {
// Mock database to throw an error
pool.query.mockImplementation(() => {
return Promise.reject(new Error('Database connection failed'));
});

// Supress console.error for this test to keep output clean
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

const response = await request(app).get('/api/export-excel');

expect(response.status).toBe(500);
expect(response.body).toEqual({ error: 'Ошибка при экспорте данных.' });

consoleSpy.mockRestore();
});

test('Empty data case: should export excel with headers only when user has no items', async () => {
// Mock database to return empty array for items
pool.query.mockImplementation(() => {
return Promise.resolve({ rows: [] });
});

const response = await request(app).get('/api/export-excel').responseType('blob');

expect(response.status).toBe(200);
expect(response.headers['content-type']).toBe('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
expect(response.headers['content-disposition']).toBe('attachment; filename=inventory_export.xlsx');

const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(response.body);

const worksheet = workbook.getWorksheet('Инвентаризация');
expect(worksheet).toBeDefined();

// Check headers still exist
const headerRow = worksheet.getRow(1).values;
expect(headerRow[1]).toBe('Название');
expect(headerRow[6]).toBe('Родительский элемент');

// Check data rows (should be 1 because only the header is present)
expect(worksheet.rowCount).toBe(1);
});
});
Loading