Update CI workflows for Python linting by increasing flake8 line length limit to 150 characters for better readability; adjust service-adapters.yml and ci.yml accordingly; update progress documentation
Some checks failed
API Docs (Node.js Express) / test (16) (push) Failing after 5m42s
API Docs (Node.js Express) / test (20) (push) Has been cancelled
API Docs (Node.js Express) / build (push) Has been cancelled
API Docs (Node.js Express) / security (push) Has been cancelled
API Docs (Node.js Express) / test (18) (push) Has been cancelled
LabFusion CI/CD Pipeline / api-gateway (push) Has been cancelled
LabFusion CI/CD Pipeline / service-adapters (push) Has been cancelled
LabFusion CI/CD Pipeline / api-docs (push) Has been cancelled
LabFusion CI/CD Pipeline / frontend (push) Has been cancelled
LabFusion CI/CD Pipeline / integration-tests (push) Has been cancelled
LabFusion CI/CD Pipeline / security-scan (push) Has been cancelled
Docker Build and Push / build-and-push (push) Has been cancelled
Docker Build and Push / security-scan (push) Has been cancelled
Docker Build and Push / deploy-staging (push) Has been cancelled
Docker Build and Push / deploy-production (push) Has been cancelled
Frontend (React) / test (16) (push) Has been cancelled
Frontend (React) / test (18) (push) Has been cancelled
Frontend (React) / test (20) (push) Has been cancelled
Frontend (React) / build (push) Has been cancelled
Frontend (React) / lighthouse (push) Has been cancelled
Frontend (React) / security (push) Has been cancelled
Integration Tests / performance-tests (push) Has been cancelled
Service Adapters (Python FastAPI) / security (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.1) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.11) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.12) (push) Has been cancelled
Service Adapters (Python FastAPI) / test (3.9) (push) Has been cancelled
Service Adapters (Python FastAPI) / build (push) Has been cancelled
Integration Tests / integration-tests (push) Has been cancelled

This commit is contained in:
glenn schrooyen
2025-09-12 22:42:47 +02:00
parent 8d1755fd52
commit 8c87c84208
11 changed files with 392 additions and 32 deletions

View File

@@ -0,0 +1,172 @@
const request = require('supertest')
const app = require('../server')
// Mock axios to avoid actual HTTP requests during tests
jest.mock('axios')
const axios = require('axios')
describe('API Docs Service', () => {
let server
beforeAll(() => {
// Start the server for testing
server = app.listen(0) // Use random available port
})
afterAll((done) => {
// Close the server after tests
server.close(done)
})
describe('GET /health', () => {
it('should return health status', async () => {
const response = await request(app)
.get('/health')
.expect(200)
expect(response.body).toHaveProperty('status', 'healthy')
expect(response.body).toHaveProperty('timestamp')
expect(new Date(response.body.timestamp)).toBeInstanceOf(Date)
})
})
describe('GET /services', () => {
beforeEach(() => {
// Reset axios mocks
jest.clearAllMocks()
})
it('should return service status for all services', async () => {
// Mock successful health checks for active services
axios.get.mockImplementation((url) => {
if (url.includes('/health')) {
return Promise.resolve({
headers: { 'x-response-time': '50ms' }
})
}
return Promise.reject(new Error('Not found'))
})
const response = await request(app)
.get('/services')
.expect(200)
expect(response.body).toHaveProperty('api-gateway')
expect(response.body).toHaveProperty('service-adapters')
expect(response.body).toHaveProperty('metrics-collector')
expect(response.body).toHaveProperty('notification-service')
// Check structure of service status
Object.values(response.body).forEach(service => {
expect(service).toHaveProperty('name')
expect(service).toHaveProperty('url')
expect(service).toHaveProperty('status')
})
})
it('should handle service health check failures', async () => {
// Mock all health checks to fail
axios.get.mockRejectedValue(new Error('Connection refused'))
const response = await request(app)
.get('/services')
.expect(200)
// All services should show as unhealthy or planned
Object.values(response.body).forEach(service => {
expect(['unhealthy', 'planned']).toContain(service.status)
if (service.status === 'unhealthy') {
expect(service).toHaveProperty('error')
}
})
})
})
describe('GET /openapi.json', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('should return unified OpenAPI specification', async () => {
// Mock service spec responses
axios.get.mockImplementation((url) => {
if (url.includes('/v3/api-docs')) {
return Promise.resolve({
data: {
openapi: '3.0.0',
info: { title: 'API Gateway', version: '1.0.0' },
paths: { '/test': { get: { summary: 'Test endpoint' } } },
components: { schemas: {} }
}
})
}
if (url.includes('/openapi.json')) {
return Promise.resolve({
data: {
openapi: '3.0.0',
info: { title: 'Service Adapters', version: '1.0.0' },
paths: { '/health': { get: { summary: 'Health check' } } },
components: { schemas: {} }
}
})
}
return Promise.reject(new Error('Not found'))
})
const response = await request(app)
.get('/openapi.json')
.expect(200)
expect(response.body).toHaveProperty('openapi', '3.0.0')
expect(response.body).toHaveProperty('info')
expect(response.body).toHaveProperty('paths')
expect(response.body).toHaveProperty('components')
expect(response.body).toHaveProperty('tags')
// Check that paths are prefixed with service names
expect(response.body.paths).toHaveProperty('/api-gateway/test')
expect(response.body.paths).toHaveProperty('/service-adapters/health')
})
it('should handle service spec fetch failures gracefully', async () => {
// Mock all service spec requests to fail
axios.get.mockRejectedValue(new Error('Service unavailable'))
const response = await request(app)
.get('/openapi.json')
.expect(200)
expect(response.body).toHaveProperty('openapi', '3.0.0')
expect(response.body).toHaveProperty('info')
expect(response.body).toHaveProperty('paths')
expect(response.body).toHaveProperty('components')
expect(response.body).toHaveProperty('tags')
// Should still have tags for all services
expect(response.body.tags).toHaveLength(4)
})
})
describe('GET /api-docs.json', () => {
it('should return API docs service specification', async () => {
const response = await request(app)
.get('/api-docs.json')
.expect(200)
expect(response.body).toHaveProperty('openapi', '3.0.0')
expect(response.body).toHaveProperty('info')
expect(response.body.info).toHaveProperty('title', 'LabFusion API Docs Service')
})
})
describe('GET /', () => {
it('should serve Swagger UI', async () => {
const response = await request(app)
.get('/')
.expect(200)
expect(response.text).toContain('swagger-ui')
expect(response.text).toContain('LabFusion API Documentation')
})
})
})

View File

@@ -0,0 +1,13 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.js', '**/?(*.)+(spec|test).js'],
collectCoverageFrom: [
'server.js',
'!node_modules/**',
'!coverage/**'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
testTimeout: 10000,
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
}

View File

@@ -0,0 +1,18 @@
// Jest setup file
// This file runs before each test file
// Mock console methods to reduce noise in tests
global.console = {
...console,
log: jest.fn(),
warn: jest.fn(),
error: jest.fn()
}
// Set test environment variables
process.env.NODE_ENV = 'test'
process.env.PORT = '8083'
process.env.API_GATEWAY_URL = 'http://localhost:8080'
process.env.SERVICE_ADAPTERS_URL = 'http://localhost:8000'
process.env.METRICS_COLLECTOR_URL = 'http://localhost:8081'
process.env.NOTIFICATION_SERVICE_URL = 'http://localhost:8082'

View File

@@ -13,7 +13,7 @@
"cors": "^2.8.5",
"dotenv": "^17.2.2",
"express": "^4.21.2",
"swagger-jsdoc": "^7.0.0-rc.6",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
"devDependencies": {
@@ -55,9 +55,9 @@
"license": "MIT"
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==",
"license": "MIT",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
@@ -65,7 +65,7 @@
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"z-schema": "^4.2.3"
"z-schema": "^5.0.1"
},
"peerDependencies": {
"openapi-types": ">=7"
@@ -2332,11 +2332,13 @@
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
"integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
"license": "MIT",
"optional": true
"engines": {
"node": ">= 6"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
@@ -7333,28 +7335,32 @@
}
},
"node_modules/swagger-jsdoc": {
"version": "7.0.0-rc.6",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-7.0.0-rc.6.tgz",
"integrity": "sha512-LIvIPQxipRaOzIij+HrWOcCWTINE6OeJuqmXCfDkofVcstPVABHRkaAc3D7vrX9s7L0ccH0sH0amwHgN6+SXPg==",
"version": "6.2.8",
"resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz",
"integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==",
"license": "MIT",
"dependencies": {
"commander": "6.2.0",
"doctrine": "3.0.0",
"glob": "7.1.6",
"lodash.mergewith": "4.6.2",
"swagger-parser": "10.0.2",
"lodash.mergewith": "^4.6.2",
"swagger-parser": "^10.0.3",
"yaml": "2.0.0-1"
},
"bin": {
"swagger-jsdoc": "bin/swagger-jsdoc.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/swagger-parser": {
"version": "10.0.2",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz",
"integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==",
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz",
"integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==",
"license": "MIT",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.2"
"@apidevtools/swagger-parser": "10.0.3"
},
"engines": {
"node": ">=10"
@@ -7965,23 +7971,33 @@
}
},
"node_modules/z-schema": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz",
"integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz",
"integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==",
"license": "MIT",
"dependencies": {
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"validator": "^13.6.0"
"validator": "^13.7.0"
},
"bin": {
"z-schema": "bin/z-schema"
},
"engines": {
"node": ">=6.0.0"
"node": ">=8.0.0"
},
"optionalDependencies": {
"commander": "^2.7.1"
"commander": "^9.4.1"
}
},
"node_modules/z-schema/node_modules/commander": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.20.0 || >=14"
}
}
}

View File

@@ -18,7 +18,7 @@
"cors": "^2.8.5",
"dotenv": "^17.2.2",
"express": "^4.21.2",
"swagger-jsdoc": "^7.0.0-rc.6",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
"devDependencies": {

View File

@@ -324,8 +324,13 @@ app.get('/', swaggerUi.setup(null, {
customSiteTitle: 'LabFusion API Documentation'
}))
// Start server
app.listen(PORT, () => {
console.log(`LabFusion API Docs server running on port ${PORT}`)
console.log(`Access the documentation at: http://localhost:${PORT}`)
})
// Export app for testing
module.exports = app
// Start server only if not in test environment
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, () => {
console.log(`LabFusion API Docs server running on port ${PORT}`)
console.log(`Access the documentation at: http://localhost:${PORT}`)
})
}