Configure Playwright for headless CI and add Gitea workflow with container support
This commit is contained in:
@@ -132,6 +132,10 @@ app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Line of Sight Backend running on port ${PORT}`);
|
||||
});
|
||||
if (require.main === module) {
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Line of Sight Backend running on port ${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { app, calculateDestination };
|
||||
|
||||
Generated
+132
-1
@@ -16,7 +16,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^30.3.0",
|
||||
"nodemon": "^3.1.14"
|
||||
"nodemon": "^3.1.14",
|
||||
"supertest": "^7.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -932,6 +933,27 @@
|
||||
"@tybys/wasm-util": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@paralleldrive/cuid2": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
|
||||
"integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -1427,6 +1449,12 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -1921,6 +1949,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
|
||||
"integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1969,6 +2006,12 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
||||
@@ -2064,6 +2107,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
|
||||
@@ -2329,6 +2382,12 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fb-watchman": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||
@@ -2456,6 +2515,23 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
|
||||
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"dezalgo": "^1.0.4",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -3677,6 +3753,27 @@
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
@@ -4832,6 +4929,40 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
|
||||
"integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.1",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.7",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.5",
|
||||
"formidable": "^3.5.4",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supertest": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
|
||||
"integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cookie-signature": "^1.2.2",
|
||||
"methods": "^1.1.2",
|
||||
"superagent": "^10.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^30.3.0",
|
||||
"nodemon": "^3.1.14"
|
||||
"nodemon": "^3.1.14",
|
||||
"supertest": "^7.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,74 @@
|
||||
/**
|
||||
* Placeholder test file for Line of Sight Backend
|
||||
*
|
||||
* TODO: Add real tests for:
|
||||
* - API endpoint validation
|
||||
* - Geospatial calculations
|
||||
* - Database queries
|
||||
* - Error handling
|
||||
*/
|
||||
const request = require('supertest');
|
||||
const { app, calculateDestination } = require('../app/server');
|
||||
|
||||
// Mock pg Pool
|
||||
jest.mock('pg', () => {
|
||||
const mPool = {
|
||||
query: jest.fn(),
|
||||
on: jest.fn(),
|
||||
end: jest.fn(),
|
||||
};
|
||||
return { Pool: jest.fn(() => mPool) };
|
||||
});
|
||||
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool();
|
||||
|
||||
describe('Line of Sight Backend', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('calculateDestination', () => {
|
||||
test('should calculate correct destination for North (0 degrees)', () => {
|
||||
const start = { lat: 0, lon: 0 };
|
||||
const dest = calculateDestination(start.lat, start.lon, 0, 111.12); // ~1 degree North
|
||||
expect(dest.lat).toBeCloseTo(1, 1);
|
||||
expect(dest.lon).toBeCloseTo(0, 1);
|
||||
});
|
||||
|
||||
test('should calculate correct destination for East (90 degrees)', () => {
|
||||
const start = { lat: 0, lon: 0 };
|
||||
const dest = calculateDestination(start.lat, start.lon, 90, 111.12); // ~1 degree East at equator
|
||||
expect(dest.lat).toBeCloseTo(0, 1);
|
||||
expect(dest.lon).toBeCloseTo(1, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/health', () => {
|
||||
test('should return 200 OK', () => {
|
||||
// TODO: Implement health check test
|
||||
expect(true).toBe(true);
|
||||
test('should return 200 OK', async () => {
|
||||
const response = await request(app).get('/api/health');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.status).toBe('ok');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/line-of-sight', () => {
|
||||
test('should return valid response structure', () => {
|
||||
// TODO: Implement line of sight API test
|
||||
expect(true).toBe(true);
|
||||
test('should return valid response structure', async () => {
|
||||
// Mock the water checks and the main city query
|
||||
pool.query
|
||||
.mockResolvedValueOnce({ rows: [{ has_land: true }] }) // Simplified for tests, it actually calls many times
|
||||
.mockResolvedValue({ rows: [{ id: 1, name: 'London', population: 9000000, country: 'GB', lat: 51.5, lon: -0.1, distance_off_line_km: 0, distance_from_start_km: 0, pos_on_line: 0 }] });
|
||||
|
||||
const response = await request(app)
|
||||
.get('/api/line-of-sight')
|
||||
.query({ lat: 51.5, lon: -0.1, direction: 45, tolerance: 50 });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.conurbations).toBeDefined();
|
||||
expect(response.body.data.line_coordinates).toBeDefined();
|
||||
});
|
||||
|
||||
test('should handle missing parameters', () => {
|
||||
// TODO: Implement error handling test
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
test('should handle database errors', async () => {
|
||||
pool.query.mockRejectedValue(new Error('DB Error'));
|
||||
|
||||
describe('Geospatial Calculations', () => {
|
||||
test('should calculate great circle path', () => {
|
||||
// TODO: Implement geospatial calculation tests
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
const response = await request(app)
|
||||
.get('/api/line-of-sight')
|
||||
.query({ lat: 51.5, lon: -0.1, direction: 45, tolerance: 50 });
|
||||
|
||||
test('should filter cities within tolerance', () => {
|
||||
// TODO: Implement tolerance filtering tests
|
||||
expect(true).toBe(true);
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body.success).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user