- Implemented a new test suite for production API verification using curl, ensuring all critical endpoints respond correctly and return valid JSON. - Added frontend integration tests to check for critical errors in the UI and verify key UI elements are present and functional. - Created test data for quotes and project calculations to facilitate testing. - Developed a script to check offers in the Ordrestyring system, including detailed task descriptions. - Added tests for generating PDFs from quote data, ensuring the output is valid and contains expected information. - Implemented a test script for the Røsevangen project, integrating with the Ordrestyring API and verifying database entries.
192 lines
6.0 KiB
Plaintext
192 lines
6.0 KiB
Plaintext
const request = require('supertest');
|
||
const app = require('../unified-server.js');
|
||
|
||
describe('Tilbudgivern API Tests', () => {
|
||
|
||
describe('Material Search API', () => {
|
||
test('GET /api/pricing/materials - should return materials', async () => {
|
||
const response = await request(app)
|
||
.get('/api/pricing/materials?search=tagsten')
|
||
.expect('Content-Type', /json/)
|
||
.expect(200);
|
||
|
||
expect(response.body).toHaveProperty('success', true);
|
||
expect(response.body).toHaveProperty('materials');
|
||
expect(Array.isArray(response.body.materials)).toBe(true);
|
||
});
|
||
|
||
test('Material search - case insensitive', async () => {
|
||
const response = await request(app)
|
||
.get('/api/pricing/materials?search=TAGSTEN')
|
||
.expect(200);
|
||
|
||
expect(response.body.success).toBe(true);
|
||
expect(response.body.materials.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('Material search - exact SKU match', async () => {
|
||
const response = await request(app)
|
||
.get('/api/pricing/materials?search=370100')
|
||
.expect(200);
|
||
|
||
expect(response.body.success).toBe(true);
|
||
expect(response.body.materials.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('Material search - SQL injection protection', async () => {
|
||
const maliciousQuery = "'; DROP TABLE bygma_products; --";
|
||
const response = await request(app)
|
||
.get(`/api/pricing/materials?search=${encodeURIComponent(maliciousQuery)}`)
|
||
.expect(200);
|
||
|
||
expect(response.body.success).toBe(true);
|
||
expect(response.body.materials).toBeDefined();
|
||
});
|
||
});
|
||
|
||
describe('Labor API', () => {
|
||
test('POST /api/customer-projects/1/labor - valid data', async () => {
|
||
const laborData = {
|
||
carpenterCount: 2,
|
||
hoursPerDay: 8,
|
||
totalDays: 3,
|
||
hourlyRate: 450
|
||
};
|
||
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/labor')
|
||
.send(laborData)
|
||
.expect('Content-Type', /json/)
|
||
.expect(200);
|
||
|
||
expect(response.body).toHaveProperty('success', true);
|
||
expect(response.body).toHaveProperty('laborId');
|
||
});
|
||
|
||
test('POST /api/customer-projects/1/labor - zero workers should fail', async () => {
|
||
const laborData = {
|
||
carpenterCount: 0,
|
||
hoursPerDay: 8,
|
||
totalDays: 3,
|
||
hourlyRate: 450
|
||
};
|
||
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/labor')
|
||
.send(laborData)
|
||
.expect('Content-Type', /json/)
|
||
.expect(400);
|
||
|
||
expect(response.body).toHaveProperty('success', false);
|
||
expect(response.body.error).toContain('mindst 1 tømrer');
|
||
});
|
||
});
|
||
|
||
describe('Geometry API', () => {
|
||
test('POST /api/customer-projects/1/geometry - pitched roof with vindskede', async () => {
|
||
const geometryData = {
|
||
roofWidth: 10,
|
||
roofLength: 12,
|
||
roofHeight: 3,
|
||
roofType: 'skraat_tag',
|
||
notes: 'Test geometry with vindskede calculation'
|
||
};
|
||
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/geometry')
|
||
.send(geometryData)
|
||
.expect('Content-Type', /json/)
|
||
.expect(200);
|
||
|
||
expect(response.body).toHaveProperty('success', true);
|
||
expect(response.body).toHaveProperty('geometryId');
|
||
expect(response.body).toHaveProperty('area');
|
||
expect(response.body).toHaveProperty('vindskede_lbm');
|
||
expect(response.body.vindskede_lbm).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('Vindskede calculation accuracy', async () => {
|
||
const geometryData = {
|
||
roofWidth: 10,
|
||
roofLength: 12,
|
||
roofHeight: 3,
|
||
roofType: 'skraat_tag'
|
||
};
|
||
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/geometry')
|
||
.send(geometryData)
|
||
.expect(200);
|
||
|
||
// Expected: 2 * sqrt((10/2)² + 3²) = 2 * sqrt(25 + 9) = 2 * sqrt(34) ≈ 11.66
|
||
expect(response.body.vindskede_lbm).toBeCloseTo(11.66, 1);
|
||
});
|
||
|
||
test('Flat roof should have zero vindskede', async () => {
|
||
const geometryData = {
|
||
roofWidth: 10,
|
||
roofLength: 12,
|
||
roofHeight: 0,
|
||
roofType: 'fladt_tag'
|
||
};
|
||
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/geometry')
|
||
.send(geometryData)
|
||
.expect(200);
|
||
|
||
expect(response.body.vindskede_lbm).toBe(0);
|
||
});
|
||
});
|
||
|
||
describe('Price Logic Validation', () => {
|
||
test('Package price calculation: costPrice × (1 + profit%) = listPrice', async () => {
|
||
// Test the price logic through package endpoint
|
||
const response = await request(app)
|
||
.get('/api/packages')
|
||
.expect(200);
|
||
|
||
if (response.body.packages && response.body.packages.length > 0) {
|
||
const pkg = response.body.packages[0];
|
||
const expectedListPrice = pkg.cost_price * (1 + pkg.profit_margin / 100);
|
||
expect(pkg.list_price).toBeCloseTo(expectedListPrice, 2);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('Health Check', () => {
|
||
test('GET /health - should return OK', async () => {
|
||
const response = await request(app)
|
||
.get('/health')
|
||
.expect(200);
|
||
|
||
expect(response.body).toHaveProperty('status', 'OK');
|
||
expect(response.body).toHaveProperty('timestamp');
|
||
});
|
||
});
|
||
|
||
describe('Error Handling', () => {
|
||
test('Invalid project ID should return 404', async () => {
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/99999/labor')
|
||
.send({
|
||
carpenterCount: 2,
|
||
hoursPerDay: 8,
|
||
totalDays: 3,
|
||
hourlyRate: 450
|
||
})
|
||
.expect(400);
|
||
|
||
expect(response.body.success).toBe(false);
|
||
});
|
||
|
||
test('Missing required fields should return 400', async () => {
|
||
const response = await request(app)
|
||
.post('/api/customer-projects/1/geometry')
|
||
.send({})
|
||
.expect(400);
|
||
|
||
expect(response.body.success).toBe(false);
|
||
});
|
||
});
|
||
}); |