From 73ed0157fc986cdb054855d4050b619138cce9e6 Mon Sep 17 00:00:00 2001 From: NguyenND Date: Tue, 4 Aug 2026 13:24:43 -0400 Subject: [PATCH] Aug 4 - Update code to follow up - MT11 b --- tests/conftest.py | 50 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ae457e9..46a7a3c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,15 +10,54 @@ whole suite can run with no MySQL, no Redis, no Stripe, and no real secrets. The pure-logic tests (SLA math, webhook helpers) never touch the DB. The routing-inertness test uses the in-memory SQLite engine only. + +Why these are ASSIGNED and not `setdefault` +------------------------------------------- +`os.environ.setdefault` is a no-op when the key is already present, so running +pytest from a shell that exports the deployment's variables — or under anything +that loads `.env` before pytest starts — silently handed the suite the REAL +configuration. Two consequences, both observed: + + * `MULTI_TENANT_ENABLED=true` engages Host→tenant resolution. The test client + sends Host `localhost`, `resolve_tenant()` returns None, and + `app/tenancy/middleware.py::_tenant_resolver` returns the "workspace not + found" page with status 404 for EVERY request, before any view runs. The + whole suite fails with `assert 404 == `. + * `DATABASE_URL` pointing at MySQL would make the `db.drop_all()` in each + module's `client` fixture run against a real database. + +The values below are therefore forced. Anything a developer needs to vary they +can vary in the fixture, not through ambient environment. `load_dotenv()` in +config.py runs later with `override=False`, so these win. """ import os +import sys # ── Import-time env (must be set BEFORE `import app`) ──────────────────────── -os.environ.setdefault('SECRET_KEY', 'test-secret-key') -os.environ.setdefault('DATABASE_URL', 'sqlite:///:memory:') -os.environ.setdefault('MULTI_TENANT_ENABLED', 'false') -os.environ.setdefault('BILLING_ENABLED', 'false') +# Assigned, NOT setdefault — see the module docstring. +os.environ['SECRET_KEY'] = 'test-secret-key' +os.environ['DATABASE_URL'] = 'sqlite:///:memory:' +os.environ['MULTI_TENANT_ENABLED'] = 'false' +os.environ['BILLING_ENABLED'] = 'false' +# The apex-host branch of the tenant resolver runs even with the MT flag off, so +# pin the base domain rather than inheriting it. 'jqc.app' is config.py's own +# default and is what tests/test_landing.py asserts against; the test client's +# Host ('localhost') can never match it. +os.environ['TENANT_BASE_DOMAIN'] = 'jqc.app' +# Control-plane vars must be absent: their presence is what lets the resolver +# reach a real control DB instead of failing loudly. +os.environ.pop('CONTROL_DATABASE_URL', None) +os.environ.pop('CONTROL_FERNET_KEY', None) + +# ── Belt-and-braces guard ──────────────────────────────────────────────────── +# Every module-level `client` fixture calls db.drop_all(). If anything ever +# re-points DATABASE_URL at a real server, refuse to run rather than drop it. +if not os.environ['DATABASE_URL'].startswith('sqlite:'): + sys.exit( + f"REFUSING TO RUN: DATABASE_URL is {os.environ['DATABASE_URL']!r}, not " + f"SQLite. The test fixtures call db.drop_all()." + ) import pytest # noqa: E402 @@ -29,6 +68,9 @@ def app(): from app import create_app application = create_app('default') application.config.update(TESTING=True, WTF_CSRF_ENABLED=False, SQLALCHEMY_ECHO=False) + # Belt-and-braces again at the app layer: config.py could pick these up from + # a source we have not anticipated. A test run must never resolve tenants. + application.config.update(MULTI_TENANT_ENABLED=False, BILLING_ENABLED=False) return application