Skip to content

Commit 16ccf16

Browse files
committed
feat: add test suites and improve documentation
Node.js reference app: - Add Jest configuration with coverage thresholds - Add comprehensive health check tests with mocked dependencies - Add configuration tests - Add application entry point tests TypeScript API-first reference app: - Add Jest configuration for TypeScript with ts-jest - Add comprehensive health check tests - Add configuration and entry point tests - Add supertest and @types/supertest dependencies Documentation: - Document AppRole secret_id renewal process with commands - Add automated renewal instructions (crontab) - Add renewal timeline table Configuration: - Fix .env.example network IPs to match 4-tier architecture - Organize IPs by network segment (vault/data/app/observability)
1 parent b189505 commit 16ccf16

13 files changed

Lines changed: 1283 additions & 62 deletions

File tree

.env.example

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,37 @@ VAULT_TOKEN=
2626
# Or leave empty and it will be read from ~/.config/vault/root-token
2727

2828
# ===========================================================================
29-
# Docker Network IP Addresses (172.20.0.0/16)
29+
# Docker Network IP Addresses (4-Tier Segmentation)
3030
# ===========================================================================
31-
# Static IP addresses for services in the dev-services network
31+
# Static IP addresses for services across 4 network segments:
32+
# - vault-network: 172.20.1.0/24 (secrets management)
33+
# - data-network: 172.20.2.0/24 (databases, redis, rabbitmq)
34+
# - app-network: 172.20.3.0/24 (forgejo, reference APIs)
35+
# - observability-network: 172.20.4.0/24 (prometheus, grafana, loki)
36+
#
3237
# Change these if you need custom IP assignments or have conflicts
3338
#
34-
# Default assignments:
35-
POSTGRES_IP=172.20.0.10
36-
PGBOUNCER_IP=172.20.0.11
37-
MYSQL_IP=172.20.0.12
38-
REDIS_1_IP=172.20.0.13
39-
RABBITMQ_IP=172.20.0.14
40-
MONGODB_IP=172.20.0.15
41-
REDIS_2_IP=172.20.0.16
42-
REDIS_3_IP=172.20.0.17
43-
FORGEJO_IP=172.20.0.20
44-
VAULT_IP=172.20.0.21
45-
REFERENCE_API_IP=172.20.0.100
46-
PROMETHEUS_IP=172.20.0.101
47-
GRAFANA_IP=172.20.0.102
48-
LOKI_IP=172.20.0.103
39+
# Vault Network (172.20.1.x):
40+
VAULT_IP=172.20.1.5
41+
42+
# Data Network (172.20.2.x):
43+
POSTGRES_IP=172.20.2.10
44+
PGBOUNCER_IP=172.20.2.11
45+
MYSQL_IP=172.20.2.12
46+
REDIS_1_IP=172.20.2.13
47+
RABBITMQ_IP=172.20.2.14
48+
MONGODB_IP=172.20.2.15
49+
REDIS_2_IP=172.20.2.16
50+
REDIS_3_IP=172.20.2.17
51+
52+
# App Network (172.20.3.x):
53+
FORGEJO_IP=172.20.3.20
54+
REFERENCE_API_IP=172.20.3.100
55+
56+
# Observability Network (172.20.4.x):
57+
PROMETHEUS_IP=172.20.4.10
58+
GRAFANA_IP=172.20.4.20
59+
LOKI_IP=172.20.4.30
4960

5061
# ===========================================================================
5162
# TLS Configuration (Enabled by Default)

docs/VAULT.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,57 @@ vault write -f -field=secret_id auth/approle/role/reference-api/secret-id > ~/.c
303303
- AppRole implementation: `reference-apps/fastapi/app/services/vault.py`
304304
- Docker configuration: `docker-compose.yml` (line 879)
305305

306+
#### AppRole Secret ID Renewal
307+
308+
**⚠️ IMPORTANT:** AppRole secret_ids have a **30-day TTL**. Services will fail to authenticate after expiry.
309+
310+
**Check Secret ID Expiry:**
311+
```bash
312+
export VAULT_ADDR=http://localhost:8200
313+
export VAULT_TOKEN=$(cat ~/.config/vault/root-token)
314+
315+
# Check remaining TTL for a service (e.g., reference-api)
316+
vault write auth/approle/role/reference-api/secret-id-accessor/lookup \
317+
secret_id_accessor=$(cat ~/.config/vault/approles/reference-api/secret-id-accessor 2>/dev/null)
318+
```
319+
320+
**Renew Secret IDs (Before Expiry):**
321+
```bash
322+
# Renew all AppRole secret_ids
323+
./scripts/vault-approle-bootstrap.sh --renew-secrets
324+
325+
# Or renew for a specific service
326+
SERVICE=reference-api
327+
vault write -f -field=secret_id auth/approle/role/${SERVICE}/secret-id \
328+
> ~/.config/vault/approles/${SERVICE}/secret-id
329+
330+
# Save the accessor for future lookups
331+
vault write -f -field=secret_id_accessor auth/approle/role/${SERVICE}/secret-id \
332+
> ~/.config/vault/approles/${SERVICE}/secret-id-accessor
333+
334+
# Restart the service to pick up new credentials
335+
docker compose restart ${SERVICE}
336+
```
337+
338+
**Automated Renewal (Recommended for Production):**
339+
340+
Add to crontab to renew secret_ids weekly:
341+
```bash
342+
# Edit crontab
343+
crontab -e
344+
345+
# Add this line (runs every Sunday at 3 AM)
346+
0 3 * * 0 cd /path/to/devstack-core && ./scripts/vault-approle-bootstrap.sh --renew-secrets >> /var/log/vault-approle-renewal.log 2>&1
347+
```
348+
349+
**Renewal Timeline:**
350+
| Event | TTL Remaining | Action |
351+
|-------|---------------|--------|
352+
| Created | 30 days | Normal operation |
353+
| Week 3 | 7-14 days | Consider renewal |
354+
| Week 4 | < 7 days | **Renew immediately** |
355+
| Expired | 0 days | Service authentication fails |
356+
306357
### SSL/TLS Certificate Management
307358

308359
**TLS Implementation: Pre-Generated Certificates with Vault-Based Configuration**
Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
1+
/**
2+
* Jest Configuration
3+
*/
4+
15
module.exports = {
26
testEnvironment: 'node',
3-
coverageDirectory: 'coverage',
4-
collectCoverageFrom: ['src/**/*.js'],
57
testMatch: ['**/tests/**/*.test.js'],
8+
collectCoverageFrom: [
9+
'src/**/*.js',
10+
'!src/index.js' // Exclude main entry point from coverage
11+
],
12+
coverageDirectory: 'coverage',
13+
coverageReporters: ['text', 'lcov', 'html'],
14+
coverageThreshold: {
15+
global: {
16+
branches: 50,
17+
functions: 50,
18+
lines: 50,
19+
statements: 50
20+
}
21+
},
22+
setupFilesAfterEnv: ['./tests/setup.js'],
23+
testTimeout: 10000,
624
verbose: true
725
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Configuration Tests
3+
*
4+
* Tests for configuration loading and validation.
5+
*/
6+
7+
describe('Configuration', () => {
8+
const originalEnv = process.env;
9+
10+
beforeEach(() => {
11+
jest.resetModules();
12+
process.env = { ...originalEnv };
13+
});
14+
15+
afterAll(() => {
16+
process.env = originalEnv;
17+
});
18+
19+
it('should load default configuration', () => {
20+
const config = require('../src/config');
21+
22+
expect(config.http.port).toBe(8003);
23+
expect(config.http.host).toBe('0.0.0.0');
24+
expect(config.vault.address).toBe('http://vault:8200');
25+
expect(config.postgres.host).toBe('postgres');
26+
expect(config.mysql.host).toBe('mysql');
27+
expect(config.mongodb.host).toBe('mongodb');
28+
expect(config.redis.host).toBe('redis-1');
29+
expect(config.rabbitmq.host).toBe('rabbitmq');
30+
});
31+
32+
it('should load custom port from environment', () => {
33+
process.env.HTTP_PORT = '9000';
34+
jest.resetModules();
35+
const config = require('../src/config');
36+
37+
expect(config.http.port).toBe(9000);
38+
});
39+
40+
it('should load custom Vault address from environment', () => {
41+
process.env.VAULT_ADDR = 'http://custom-vault:8200';
42+
jest.resetModules();
43+
const config = require('../src/config');
44+
45+
expect(config.vault.address).toBe('http://custom-vault:8200');
46+
});
47+
48+
it('should have correct Redis cluster nodes', () => {
49+
const config = require('../src/config');
50+
51+
expect(config.redis.nodes).toHaveLength(3);
52+
expect(config.redis.nodes[0]).toEqual({ host: 'redis-1', port: 6379 });
53+
expect(config.redis.nodes[1]).toEqual({ host: 'redis-2', port: 6379 });
54+
expect(config.redis.nodes[2]).toEqual({ host: 'redis-3', port: 6379 });
55+
});
56+
57+
it('should have correct application metadata', () => {
58+
const config = require('../src/config');
59+
60+
expect(config.app.name).toBe('DevStack Core Node.js Reference API');
61+
expect(config.app.language).toBe('Node.js');
62+
expect(config.app.framework).toBe('Express');
63+
expect(config.app.version).toBeDefined();
64+
});
65+
66+
it('should enable debug in development', () => {
67+
process.env.NODE_ENV = 'development';
68+
jest.resetModules();
69+
const config = require('../src/config');
70+
71+
expect(config.debug).toBe(true);
72+
});
73+
74+
it('should respect DEBUG environment variable', () => {
75+
process.env.NODE_ENV = 'production';
76+
process.env.DEBUG = 'true';
77+
jest.resetModules();
78+
const config = require('../src/config');
79+
80+
expect(config.debug).toBe(true);
81+
});
82+
83+
it('should parse HTTPS configuration', () => {
84+
process.env.HTTPS_PORT = '8446';
85+
process.env.NODEJS_API_ENABLE_TLS = 'true';
86+
jest.resetModules();
87+
const config = require('../src/config');
88+
89+
expect(config.https.port).toBe(8446);
90+
expect(config.https.enabled).toBe(true);
91+
});
92+
});

0 commit comments

Comments
 (0)