This document describes how to run tests for the ContainerSSH demonstration environment.
Tests are run inside Docker containers to ensure consistency with the production environment. There are two types of tests:
- Unit Tests - Test the Flask config server routing logic and authentication in isolation
- Integration Tests - End-to-end tests of the full SSH routing flow
./scripts/run_tests.sh# Unit tests only
./scripts/run_tests.sh unit
# Integration tests only
./scripts/run_tests.sh integrationUnit tests verify the config server's routing logic, authentication, and error handling.
# Build and run unit tests
docker compose -f docker-compose.test.yml build configserver-test
docker compose -f docker-compose.test.yml run --rm configserver-testcd configserver
pip install -r requirements.txt
pip install pytest pytest-cov flask-testing
pytest tests/ -v --cov=app/healthendpoint availability/configendpoint routing logic:- Pattern-based routing (admin, ops, dev, test users)
- Explicit user mappings from
users_map.json - Default routing fallback
/pubkeyauthentication endpoint:- Valid key acceptance
- Invalid key rejection
- Unknown user handling
- Error handling and edge cases
Integration tests verify the complete SSH routing flow through ContainerSSH.
# Full integration test suite
./scripts/run_tests.sh integration
# Or manually:
./scripts/generate_keys.sh # Generate SSH keys
docker compose -f docker-compose.test.yml up -d configserver containerssh vm1 vm2
./scripts/setup.sh # Distribute keys
./scripts/integration_test.sh # Run tests
docker compose -f docker-compose.test.yml down -v- Config server health and API endpoints
- Routing decisions for different user types
- Actual SSH connections through ContainerSSH to backend VMs
- Backend VM connectivity and SSH service availability
- Service key distribution
Tests run automatically on:
- Push to
mainordevelopbranches - Pull requests to
mainordevelop - Manual workflow dispatch
The workflow runs three jobs:
- Unit Tests - Runs in isolated Docker container
- Integration Tests - Full docker-compose stack
- Lint - Code quality checks (flake8, black)
The configserver/Dockerfile uses multi-stage builds:
- base - Common dependencies
- test - Includes pytest and test dependencies
- production - Clean production image
docker-compose.test.yml- Test-specific compose configurationconfigserver/tests/- Unit test filesscripts/integration_test.sh- Integration test suitescripts/run_tests.sh- Unified test runner
Unit tests generate coverage reports:
# HTML coverage report (after running tests)
open configserver/htmlcov/index.html
# Terminal coverage report
docker compose -f docker-compose.test.yml run --rm configserver-test pytest tests/ --cov=app --cov-report=termContainerSSH requires read access to host keys when running in containers. If you see:
failed to load host key /etc/containerssh/keys/host_ed25519 (permission denied)
The fix is already included in test scripts, but if running manually:
chmod 644 containerssh/keys/host_ed25519
chmod 644 containerssh/keys/backend_id_ed25519Note: Making keys world-readable (644) is acceptable for test/demo environments. In production, use proper secrets management or user mapping.
The end-to-end SSH connection tests (Tests 4-5) may be flaky in certain environments due to:
- Platform architecture mismatches (ContainerSSH image is linux/amd64)
- SIGPIPE issues in containerized SSH sessions
- Timing issues with rapid successive connections
Workaround: The tests verify authentication rather than command execution. Tests 1-3, 6-8 provide solid coverage of:
- Config server health and routing logic
- Backend VM connectivity and SSH services
- Service key distribution
If SSH tests fail but other tests pass, the core routing infrastructure is working correctly.
# View detailed test output
docker compose -f docker-compose.test.yml run --rm configserver-test pytest tests/ -vv
# Check for import errors
docker compose -f docker-compose.test.yml run --rm configserver-test python -c "import app"# Check service logs
docker compose -f docker-compose.test.yml logs configserver
docker compose -f docker-compose.test.yml logs containerssh
docker logs backend_vm1_test
# Test config server directly
curl http://localhost:8080/health
curl -X POST http://localhost:8080/config -H "Content-Type: application/json" -d '{"username": "alice"}'
# Test SSH connection manually
ssh -i data/test_keys/alice_id_ed25519 -p 2222 alice@localhost# Stop and remove all test containers
docker compose -f docker-compose.test.yml down -v
# Remove test images
docker compose -f docker-compose.test.yml down --rmi all -v- Create test file in
configserver/tests/test_*.py - Import necessary modules and the Flask app
- Create test class inheriting from
unittest.TestCase - Add test methods (must start with
test_)
Example:
import unittest
from app import app
class MyTestCase(unittest.TestCase):
def setUp(self):
self.app = app
self.app.config['TESTING'] = True
self.client = self.app.test_client()
def test_my_feature(self):
response = self.client.get('/endpoint')
self.assertEqual(response.status_code, 200)Edit scripts/integration_test.sh and add new test cases following the existing pattern:
echo "Test N: My New Test"
if my_test_command; then
test_result "My test description" 0
else
test_result "My test description" 1
fi- Run tests in Docker - Ensures consistency with CI/CD and production
- Run tests before commits - Use
./scripts/run_tests.shbefore pushing - Check coverage - Aim for high coverage on critical routing logic
- Update tests with code changes - Keep tests synchronized with features
- Test edge cases - Include error conditions and boundary cases