End-state: a single web service (Next.js dashboard) per deployment, no
side-by-side Flask UI. The image name 'cm-web' now points at the Next.js
build; the legacy 'cm-web-next' tag is no longer published.
Changes:
- Delete app/cm_web_view.py and the Flask docker/web/Dockerfile.
- Rename docker/web-next/ → docker/web/ (Next.js Dockerfile takes the
cm-web slot).
- docker-compose.yml: drop the web-view service. Rename web-next → web,
container ${CM_DEPLOY_NAME}-web-next → ${CM_DEPLOY_NAME}-web, image
cm-web-next → cm-web, named volume web-next-auth-data → web-auth-data.
transfer-bot's depends_on no longer references web-view (vestigial
startup ordering, never a runtime dependency).
- docker-compose.override.yml: same rename, dockerfile path updated.
- envs: drop CM_WEB_NEXT_HOST_PORT. Repurpose CM_WEB_HOST_PORT for the
Next.js port (8010 dev, 8011 rex, 8012 siong) — same numeric values
formerly held by CM_WEB_NEXT_HOST_PORT, so aaPanel routes don't move.
- scripts/dev.sh: drops web-view + web-next from up/reset-db/logs;
--remove-orphans still cleans up legacy containers from before cutover.
- scripts/publish.sh: drop the cm-web-next build target.
- tests/test_debug_enabled.py: drop app.cm_web_view from the helper
matrix (cm_api is now the only Flask entrypoint with _debug_enabled).
- AGENTS.md / README.md / docs/aapanel-hardening.md: rewrite Flask-era
references; add migration steps for existing stacks; update aaPanel
port references (8000/8001/8005 → 8010/8011/8012).
- .gitignore: add .env, .venv/, .playwright-mcp/, node_modules/, .next/
so 'git add -A' can't accidentally stage secrets or build artifacts.
Operator action required to upgrade an existing deployment:
1. .env: drop CM_WEB_NEXT_HOST_PORT line. Set CM_WEB_HOST_PORT to
what CM_WEB_NEXT_HOST_PORT was. Make sure CM_AUTH_SECRET is set.
2. aaPanel: if proxy_pass pointed at the legacy Flask port
(8000/8001/8005), switch it to the new one (8010/8011/8012).
3. Pull the new cm-web image (Next.js) and redeploy the stack. The
old ${CM_DEPLOY_NAME}-web-view and ${CM_DEPLOY_NAME}-web-next
containers will be replaced by a single ${CM_DEPLOY_NAME}-web.
Verified locally: docker-compose YAML parses; transfer-bot runtime is
unchanged (only depends_on tidied); 38-test python suite passes.
84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
"""Regression tests for the _debug_enabled helper.
|
|
|
|
`app.cm_api` defines a private _debug_enabled() function that parses the
|
|
CM_DEBUG environment variable. This test runs a parametrized matrix
|
|
against it. Historically `app.cm_web_view` had its own copy that this
|
|
suite kept in lockstep — that module retired in the B4 cutover, leaving
|
|
only cm_api as the consumer. The matrix-style harness stays so the next
|
|
Flask entrypoint can be wired in by appending one line to HELPER_MODULES.
|
|
"""
|
|
|
|
import os
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
# Import the module at top-level (before any mock.patch.dict with
|
|
# clear=True), so module-load-time os.getenv() reads see the real
|
|
# environment. The patches inside individual tests then only affect the
|
|
# helper's runtime read of CM_DEBUG.
|
|
import app.cm_api
|
|
|
|
|
|
# Modules expected to expose a private _debug_enabled() helper.
|
|
# Add new entries here if more Flask entrypoints adopt the same toggle.
|
|
HELPER_MODULES = (
|
|
app.cm_api,
|
|
)
|
|
|
|
|
|
# (env_value, expected_result). env_value=None means CM_DEBUG is unset.
|
|
CASES = (
|
|
(None, False),
|
|
("", False),
|
|
("false", False),
|
|
("False", False),
|
|
("FALSE", False),
|
|
("0", False),
|
|
("no", False),
|
|
("anything-else", False),
|
|
("true", True),
|
|
("True", True),
|
|
("TRUE", True),
|
|
("1", True),
|
|
("yes", True),
|
|
("YES", True),
|
|
(" true ", True),
|
|
)
|
|
|
|
|
|
class DebugEnabledTests(unittest.TestCase):
|
|
def _resolve(self, module):
|
|
return getattr(module, "_debug_enabled", None)
|
|
|
|
def test_helper_exists_on_every_module(self):
|
|
for module in HELPER_MODULES:
|
|
with self.subTest(module=module.__name__):
|
|
helper = self._resolve(module)
|
|
self.assertTrue(
|
|
callable(helper),
|
|
f"{module.__name__}._debug_enabled must be callable",
|
|
)
|
|
|
|
def test_parses_cm_debug_consistently(self):
|
|
for module in HELPER_MODULES:
|
|
helper = self._resolve(module)
|
|
if helper is None:
|
|
self.fail(
|
|
f"{module.__name__}._debug_enabled is missing — "
|
|
"make test_helper_exists_on_every_module pass first"
|
|
)
|
|
for env_value, expected in CASES:
|
|
with self.subTest(module=module.__name__, env=env_value):
|
|
env = {} if env_value is None else {"CM_DEBUG": env_value}
|
|
with mock.patch.dict(os.environ, env, clear=True):
|
|
self.assertEqual(
|
|
helper(),
|
|
expected,
|
|
f"{module.__name__}._debug_enabled() should be "
|
|
f"{expected!r} for CM_DEBUG={env_value!r}",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|