Initial import: shopdb-flask multi-site asset platform
Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin framework so sister GE Aerospace sites can adopt it. Product version 0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests. Key entry points: - docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook (own-site and subpath deployment methods) - docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify - docs/IMPORT-API.md - schema-agnostic legacy data import contract - scripts/site_imports/wjf/ - West Jefferson reference import loader - docs/adr/ - twelve accepted architecture decision records
This commit is contained in:
90
tests/test_security_config.py
Normal file
90
tests/test_security_config.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Tests pinning production-config validation behavior."""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from shopdb.config import ProductionConfig, ConfigError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_env(monkeypatch):
|
||||
"""Clear all env vars that ProductionConfig.validate looks at."""
|
||||
for key in ('SECRET_KEY', 'JWT_SECRET_KEY', 'DATABASE_URL', 'CORS_ORIGINS'):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
return monkeypatch
|
||||
|
||||
|
||||
def test_production_validate_raises_on_missing_secret_key(clean_env):
|
||||
"""Empty SECRET_KEY in production must fail loud at boot."""
|
||||
with pytest.raises(ConfigError, match='SECRET_KEY'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_raises_on_dev_secret_key(clean_env):
|
||||
"""The dev fallback must not be accepted in production."""
|
||||
clean_env.setenv('SECRET_KEY', 'dev-secret-key-change-in-production')
|
||||
with pytest.raises(ConfigError, match='SECRET_KEY'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_raises_on_missing_jwt_secret(clean_env):
|
||||
"""Empty JWT_SECRET_KEY in production must fail loud at boot."""
|
||||
clean_env.setenv('SECRET_KEY', 'a-real-strong-key')
|
||||
with pytest.raises(ConfigError, match='JWT_SECRET_KEY'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_raises_on_missing_database_url(clean_env):
|
||||
"""Production must not silently fall back to a localhost MySQL URL."""
|
||||
clean_env.setenv('SECRET_KEY', 'a-real-strong-key')
|
||||
clean_env.setenv('JWT_SECRET_KEY', 'another-strong-key')
|
||||
with pytest.raises(ConfigError, match='DATABASE_URL'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_raises_on_wildcard_cors(clean_env):
|
||||
"""CORS wildcard is rejected in production."""
|
||||
clean_env.setenv('SECRET_KEY', 'a-real-strong-key')
|
||||
clean_env.setenv('JWT_SECRET_KEY', 'another-strong-key')
|
||||
clean_env.setenv('DATABASE_URL', 'mysql+pymysql://u:p@db/shopdb')
|
||||
clean_env.setenv('CORS_ORIGINS', '*')
|
||||
with pytest.raises(ConfigError, match='CORS_ORIGINS'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_raises_on_empty_cors(clean_env):
|
||||
"""Empty CORS allowlist is rejected in production."""
|
||||
clean_env.setenv('SECRET_KEY', 'a-real-strong-key')
|
||||
clean_env.setenv('JWT_SECRET_KEY', 'another-strong-key')
|
||||
clean_env.setenv('DATABASE_URL', 'mysql+pymysql://u:p@db/shopdb')
|
||||
with pytest.raises(ConfigError, match='CORS_ORIGINS'):
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_production_validate_passes_with_complete_config(clean_env):
|
||||
"""All required env vars set with non-default values: validate passes."""
|
||||
clean_env.setenv('SECRET_KEY', 'a-real-strong-key')
|
||||
clean_env.setenv('JWT_SECRET_KEY', 'another-strong-key')
|
||||
clean_env.setenv('DATABASE_URL', 'mysql+pymysql://u:p@db/shopdb')
|
||||
clean_env.setenv('CORS_ORIGINS', 'https://shopdb.example.com')
|
||||
ProductionConfig.validate()
|
||||
|
||||
|
||||
def test_per_plugin_collector_key_loaded_from_env(monkeypatch):
|
||||
"""COLLECTOR_API_KEY_<PLUGIN> is a dynamic env var; create_app must load it.
|
||||
|
||||
from_object only copies class attributes, so per-plugin keys (ADR-006)
|
||||
would be invisible without the explicit env scan in create_app.
|
||||
"""
|
||||
from shopdb import create_app
|
||||
|
||||
monkeypatch.setenv('COLLECTOR_API_KEY_COMPUTERS', 'computers-secret')
|
||||
app = create_app('testing')
|
||||
assert app.config.get('COLLECTOR_API_KEY_COMPUTERS') == 'computers-secret'
|
||||
|
||||
|
||||
def test_employee_db_password_has_no_default():
|
||||
"""No safe default for the employee-DB password: unset means empty."""
|
||||
if 'EMPLOYEE_DB_PASSWORD' not in os.environ:
|
||||
from shopdb.config import Config
|
||||
assert Config.EMPLOYEE_DB_PASSWORD == ''
|
||||
Reference in New Issue
Block a user