feat(governance): automate identity and comparison coverage
This commit is contained in:
@@ -1258,8 +1258,8 @@ def test_anon_get_redirects_to_login(anon_client):
|
||||
'/observability/api/health_indicator',
|
||||
]:
|
||||
r = anon_client.get(path)
|
||||
# 308(permanent redirect for trailing slash)或 302(login redirect)皆視為阻擋
|
||||
assert r.status_code in (302, 308), f'{path} 未強制 login (got {r.status_code})'
|
||||
expected = (401,) if '/api/' in path else (302, 308)
|
||||
assert r.status_code in expected, f'{path} 未強制 login (got {r.status_code})'
|
||||
|
||||
|
||||
def test_anon_post_blocked(anon_client):
|
||||
|
||||
@@ -84,6 +84,26 @@ def test_ai_intelligence_runtime_task_copy_stays_short():
|
||||
assert short_task in command_renderer
|
||||
|
||||
|
||||
def test_ai_intelligence_comparison_coverage_has_explicit_scope_and_partition():
|
||||
template = _template()
|
||||
renderer = template.split("function renderGrowthCommandCenter", 1)[1].split(
|
||||
"function renderGrowthCommandTopDecliners",
|
||||
1,
|
||||
)[0]
|
||||
|
||||
assert "同款比價證據 · TOP 50" in template
|
||||
assert "正式資料平台" in template
|
||||
assert "比價覆蓋 · TOP 50" in template
|
||||
assert "具正式比價證據" in template
|
||||
assert "候選 / 來源驗證" in template
|
||||
assert "尚未找到同款" in template
|
||||
assert "比價可用率" not in template
|
||||
assert "needsMapping + reviewCandidateCount" not in renderer
|
||||
assert "commandComparisonReady" in renderer
|
||||
assert "commandCandidateValidation" in renderer
|
||||
assert "commandUnmatched" in renderer
|
||||
|
||||
|
||||
def test_ai_intelligence_detail_meta_and_single_product_avoid_long_reasons():
|
||||
template = _template()
|
||||
detail_config = template.split("function growthDetailConfig", 1)[1].split(
|
||||
|
||||
537
tests/test_auth_identity_governance.py
Normal file
537
tests/test_auth_identity_governance.py
Normal file
@@ -0,0 +1,537 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import Flask
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
import auth
|
||||
import services.permission_service as permission_service_module
|
||||
from database.permission_models import UserPermission
|
||||
from database.user_models import LoginHistory, User
|
||||
from services.auth_identity_service import (
|
||||
build_auth_identity_runtime_readback,
|
||||
required_roles_for_request,
|
||||
resolve_auth_identity_mode,
|
||||
resolve_client_ip,
|
||||
)
|
||||
from services.user_service import UserService
|
||||
from services.permission_service import PermissionService
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def identity_store():
|
||||
engine = create_engine(
|
||||
"sqlite://",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
User.__table__.create(engine)
|
||||
LoginHistory.__table__.create(engine)
|
||||
UserPermission.__table__.create(engine)
|
||||
return sessionmaker(bind=engine)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def identity_user(identity_store):
|
||||
db_session = identity_store()
|
||||
now = datetime.now()
|
||||
user = User(
|
||||
username="operator-admin",
|
||||
password_hash=generate_password_hash("DatabasePass9!", method="pbkdf2:sha256"),
|
||||
role="admin",
|
||||
display_name="Operator Admin",
|
||||
is_active=True,
|
||||
password_changed_at=now,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
user_id = user.id
|
||||
db_session.close()
|
||||
return user_id
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def identity_app(monkeypatch, identity_store, identity_user):
|
||||
monkeypatch.setenv("MOMO_AUTH_IDENTITY_MODE", "hybrid")
|
||||
monkeypatch.setenv("MOMO_TRUSTED_PROXY_CIDRS", "127.0.0.1/32,::1/128")
|
||||
monkeypatch.setattr(auth, "LOGIN_PASSWORD", "LegacyPass9!")
|
||||
monkeypatch.setattr(auth, "get_identity_session", identity_store)
|
||||
|
||||
app = Flask(__name__, template_folder=str(auth.os.path.join(auth.os.path.dirname(__file__), "..", "templates")))
|
||||
app.config.update(
|
||||
TESTING=True,
|
||||
SECRET_KEY="identity-test-secret",
|
||||
SESSION_COOKIE_SECURE=False,
|
||||
)
|
||||
app.jinja_env.globals["csrf_token"] = lambda: "test-csrf"
|
||||
app.add_url_rule(
|
||||
"/",
|
||||
endpoint="dashboard.index",
|
||||
view_func=lambda: "dashboard",
|
||||
)
|
||||
|
||||
@app.route("/protected")
|
||||
@auth.login_required
|
||||
def protected():
|
||||
return "protected"
|
||||
|
||||
auth.init_auth_routes(app)
|
||||
return app
|
||||
|
||||
|
||||
def test_proxy_headers_are_ignored_from_untrusted_peer():
|
||||
assert resolve_client_ip(
|
||||
"203.0.113.9",
|
||||
"198.51.100.7",
|
||||
trusted_proxy_cidrs="127.0.0.1/32",
|
||||
) == "203.0.113.9"
|
||||
assert resolve_client_ip(
|
||||
"127.0.0.1",
|
||||
"198.51.100.7, 127.0.0.1",
|
||||
trusted_proxy_cidrs="127.0.0.1/32",
|
||||
) == "198.51.100.7"
|
||||
|
||||
|
||||
def test_auth_mode_and_role_matrix_fail_closed():
|
||||
assert resolve_auth_identity_mode("auto") == "auto"
|
||||
with pytest.raises(ValueError):
|
||||
resolve_auth_identity_mode("invalid")
|
||||
assert required_roles_for_request("GET", "vendor.list", "vendor") == (
|
||||
"admin",
|
||||
"manager",
|
||||
"user",
|
||||
)
|
||||
assert required_roles_for_request("POST", "vendor.update", "vendor") == (
|
||||
"admin",
|
||||
"manager",
|
||||
)
|
||||
assert required_roles_for_request("GET", "cicd.status", "cicd") == ("admin",)
|
||||
|
||||
|
||||
def test_lockout_is_durable_across_database_sessions(identity_store):
|
||||
for _ in range(5):
|
||||
db_session = identity_store()
|
||||
assert UserService(db_session).record_login(
|
||||
None,
|
||||
"operator-admin",
|
||||
"198.51.100.20",
|
||||
"pytest",
|
||||
LoginHistory.STATUS_FAILED,
|
||||
"invalid_credentials",
|
||||
) is not None
|
||||
db_session.close()
|
||||
|
||||
db_session = identity_store()
|
||||
service = UserService(db_session)
|
||||
locked, remaining, count = service.get_lockout_status(
|
||||
"operator-admin",
|
||||
"198.51.100.20",
|
||||
max_attempts=5,
|
||||
reset_seconds=1800,
|
||||
lockout_seconds=300,
|
||||
)
|
||||
assert locked is True
|
||||
assert remaining > 0
|
||||
assert count == 5
|
||||
|
||||
service.record_login(
|
||||
None,
|
||||
"operator-admin",
|
||||
"198.51.100.20",
|
||||
"pytest",
|
||||
LoginHistory.STATUS_SUCCESS,
|
||||
"auth_source=database",
|
||||
)
|
||||
locked, remaining, count = service.get_lockout_status(
|
||||
"operator-admin",
|
||||
"198.51.100.20",
|
||||
)
|
||||
assert (locked, remaining, count) == (False, 0, 0)
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_database_identity_login_writes_audit_and_versioned_session(
|
||||
identity_app,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
client = identity_app.test_client()
|
||||
response = client.post(
|
||||
"/login",
|
||||
data={"username": "operator-admin", "password": "DatabasePass9!"},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"].endswith("/")
|
||||
with client.session_transaction() as current_session:
|
||||
assert current_session["user_id"] == identity_user
|
||||
assert current_session["role"] == "admin"
|
||||
assert current_session["auth_source"] == "database"
|
||||
assert current_session["identity_version"]
|
||||
|
||||
db_session = identity_store()
|
||||
event = db_session.query(LoginHistory).order_by(LoginHistory.id.desc()).first()
|
||||
assert event.status == LoginHistory.STATUS_SUCCESS
|
||||
assert event.failure_reason == "auth_source=database"
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_identity_change_revokes_existing_session(
|
||||
identity_app,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
client = identity_app.test_client()
|
||||
assert client.post(
|
||||
"/login",
|
||||
data={"username": "operator-admin", "password": "DatabasePass9!"},
|
||||
).status_code == 302
|
||||
|
||||
db_session = identity_store()
|
||||
user = db_session.query(User).filter(User.id == identity_user).one()
|
||||
user.updated_at = datetime.now() + timedelta(seconds=1)
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
response = client.get("/protected")
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"].endswith("/login")
|
||||
with client.session_transaction() as current_session:
|
||||
assert "logged_in" not in current_session
|
||||
|
||||
db_session = identity_store()
|
||||
statuses = [row.status for row in db_session.query(LoginHistory).all()]
|
||||
assert LoginHistory.STATUS_REVOKED in statuses
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_legacy_hybrid_login_audits_real_peer_not_spoofed_header(
|
||||
identity_app,
|
||||
identity_store,
|
||||
):
|
||||
client = identity_app.test_client()
|
||||
response = client.post(
|
||||
"/login",
|
||||
data={"username": "", "password": "LegacyPass9!"},
|
||||
headers={"X-Forwarded-For": "198.51.100.88"},
|
||||
environ_base={"REMOTE_ADDR": "203.0.113.44"},
|
||||
)
|
||||
assert response.status_code == 302
|
||||
with client.session_transaction() as current_session:
|
||||
assert current_session["auth_source"] == "legacy_shared_password"
|
||||
assert current_session["client_ip"] == "203.0.113.44"
|
||||
|
||||
db_session = identity_store()
|
||||
event = db_session.query(LoginHistory).order_by(LoginHistory.id.desc()).first()
|
||||
assert event.ip_address == "203.0.113.44"
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_database_mode_refuses_legacy_authority(
|
||||
monkeypatch,
|
||||
identity_app,
|
||||
):
|
||||
monkeypatch.setenv("MOMO_AUTH_IDENTITY_MODE", "database")
|
||||
client = identity_app.test_client()
|
||||
response = client.post(
|
||||
"/login",
|
||||
data={"username": "", "password": "LegacyPass9!"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
with client.session_transaction() as current_session:
|
||||
assert "logged_in" not in current_session
|
||||
|
||||
|
||||
def test_auto_mode_retires_legacy_session_after_durable_admin_canary(
|
||||
monkeypatch,
|
||||
identity_app,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
monkeypatch.setenv("MOMO_AUTH_IDENTITY_MODE", "auto")
|
||||
client = identity_app.test_client()
|
||||
assert client.post(
|
||||
"/login",
|
||||
data={"username": "", "password": "LegacyPass9!"},
|
||||
).status_code == 302
|
||||
|
||||
db_session = identity_store()
|
||||
service = UserService(db_session)
|
||||
for _ in range(2):
|
||||
assert service.record_login(
|
||||
identity_user,
|
||||
"operator-admin",
|
||||
"127.0.0.1",
|
||||
"pytest",
|
||||
LoginHistory.STATUS_SUCCESS,
|
||||
"auth_source=database",
|
||||
) is not None
|
||||
assert service.get_auth_auto_cutover_state()["ready"] is True
|
||||
db_session.close()
|
||||
|
||||
response = client.get("/protected")
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"].endswith("/login")
|
||||
with client.session_transaction() as current_session:
|
||||
assert "logged_in" not in current_session
|
||||
|
||||
|
||||
def test_unversioned_cookie_is_rejected_outside_test_mode(monkeypatch, identity_store):
|
||||
monkeypatch.setenv("MOMO_AUTH_IDENTITY_MODE", "hybrid")
|
||||
monkeypatch.setattr(auth, "get_identity_session", identity_store)
|
||||
app = Flask(__name__)
|
||||
app.config.update(SECRET_KEY="production-shape-secret", TESTING=False)
|
||||
|
||||
@app.route("/login", endpoint="login")
|
||||
def login_stub():
|
||||
return "login"
|
||||
|
||||
@app.route("/protected-production-shape")
|
||||
@auth.login_required
|
||||
def protected_production_shape():
|
||||
return "protected"
|
||||
|
||||
client = app.test_client()
|
||||
with client.session_transaction() as current_session:
|
||||
current_session["logged_in"] = True
|
||||
response = client.get("/protected-production-shape")
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"].endswith("/login")
|
||||
with client.session_transaction() as current_session:
|
||||
assert "logged_in" not in current_session
|
||||
|
||||
|
||||
def test_runtime_readback_separates_canary_from_closed(identity_store, identity_user):
|
||||
db_session = identity_store()
|
||||
report = build_auth_identity_runtime_readback(db_session, mode="hybrid")
|
||||
assert report["status"] == "canary_ready"
|
||||
assert report["canary_ready"] is True
|
||||
assert report["runtime_closed"] is False
|
||||
assert report["inventory"]["active_admin_count"] == 1
|
||||
assert report["checks"]["legacy_shared_password_retired"] is False
|
||||
|
||||
closed = build_auth_identity_runtime_readback(db_session, mode="database")
|
||||
assert closed["status"] == "completed"
|
||||
assert closed["runtime_closed"] is True
|
||||
assert closed["checks"]["legacy_shared_password_retired"] is True
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_auto_runtime_readback_closes_after_admin_success_threshold(
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
db_session = identity_store()
|
||||
initial = build_auth_identity_runtime_readback(db_session, mode="auto")
|
||||
assert initial["configured_auth_identity_mode"] == "auto"
|
||||
assert initial["auth_identity_mode"] == "hybrid"
|
||||
assert initial["runtime_closed"] is False
|
||||
|
||||
service = UserService(db_session)
|
||||
for _ in range(2):
|
||||
service.record_login(
|
||||
identity_user,
|
||||
"operator-admin",
|
||||
"127.0.0.1",
|
||||
"pytest",
|
||||
LoginHistory.STATUS_SUCCESS,
|
||||
"auth_source=database",
|
||||
)
|
||||
promoted = build_auth_identity_runtime_readback(db_session, mode="auto")
|
||||
assert promoted["configured_auth_identity_mode"] == "auto"
|
||||
assert promoted["auth_identity_mode"] == "database"
|
||||
assert promoted["auto_cutover"]["manual_review_required"] is False
|
||||
assert promoted["auto_cutover"]["database_admin_successes"] == 2
|
||||
assert promoted["runtime_closed"] is True
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_identity_mutations_write_no_secret_audits(
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
audit_context = {
|
||||
"actor_id": identity_user,
|
||||
"ip_address": "198.51.100.40",
|
||||
"user_agent": "pytest-identity-audit",
|
||||
}
|
||||
db_session = identity_store()
|
||||
service = UserService(db_session)
|
||||
user, error = service.create_user(
|
||||
"audit-target",
|
||||
"CreatePass9!",
|
||||
role="admin",
|
||||
created_by=identity_user,
|
||||
audit_context=audit_context,
|
||||
)
|
||||
assert error is None
|
||||
|
||||
success, error = service.update_user(
|
||||
user.id,
|
||||
audit_context=audit_context,
|
||||
role="user",
|
||||
display_name="Audit Target",
|
||||
)
|
||||
assert (success, error) == (True, None)
|
||||
success, error = service.change_password(
|
||||
user.id,
|
||||
"CreatePass9!",
|
||||
"ChangedPass8!",
|
||||
audit_context=audit_context,
|
||||
)
|
||||
assert (success, error) == (True, None)
|
||||
success, error = service.reset_password(
|
||||
user.id,
|
||||
"ResetPass7!",
|
||||
admin_id=identity_user,
|
||||
audit_context=audit_context,
|
||||
)
|
||||
assert (success, error) == (True, None)
|
||||
success, error = service.delete_user(user.id, audit_context=audit_context)
|
||||
assert (success, error) == (True, None)
|
||||
|
||||
events = db_session.query(LoginHistory).filter(
|
||||
LoginHistory.user_id == user.id,
|
||||
).order_by(LoginHistory.id).all()
|
||||
assert [event.status for event in events] == [
|
||||
LoginHistory.STATUS_USER_CREATED,
|
||||
LoginHistory.STATUS_IDENTITY_CHANGED,
|
||||
LoginHistory.STATUS_PASSWORD_CHANGED,
|
||||
LoginHistory.STATUS_PASSWORD_CHANGED,
|
||||
LoginHistory.STATUS_USER_DISABLED,
|
||||
]
|
||||
serialized = " ".join(
|
||||
f"{event.failure_reason or ''} {event.user_agent or ''}"
|
||||
for event in events
|
||||
)
|
||||
assert "CreatePass9!" not in serialized
|
||||
assert "ChangedPass8!" not in serialized
|
||||
assert "ResetPass7!" not in serialized
|
||||
assert all(event.ip_address == "198.51.100.40" for event in events)
|
||||
assert service.get_identity_inventory()["identity_mutation_events"] == 5
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_identity_mutation_and_audit_roll_back_together(
|
||||
monkeypatch,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
db_session = identity_store()
|
||||
|
||||
def fail_commit():
|
||||
raise RuntimeError("forced commit failure")
|
||||
|
||||
monkeypatch.setattr(db_session, "commit", fail_commit)
|
||||
user, error = UserService(db_session).create_user(
|
||||
"rollback-target",
|
||||
"RollbackPass9!",
|
||||
role="admin",
|
||||
created_by=identity_user,
|
||||
audit_context={"actor_id": identity_user},
|
||||
)
|
||||
assert user is None
|
||||
assert "forced commit failure" in error
|
||||
db_session.close()
|
||||
|
||||
verification_session = identity_store()
|
||||
assert verification_session.query(User).filter_by(username="rollback-target").count() == 0
|
||||
assert verification_session.query(LoginHistory).filter_by(
|
||||
username_attempted="rollback-target",
|
||||
).count() == 0
|
||||
verification_session.close()
|
||||
|
||||
|
||||
def test_permission_mutation_and_audit_share_one_transaction(
|
||||
monkeypatch,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
seed_session = identity_store()
|
||||
target = User(
|
||||
username="permission-target",
|
||||
password_hash=generate_password_hash("PermissionPass9!", method="pbkdf2:sha256"),
|
||||
role="user",
|
||||
display_name="Permission Target",
|
||||
is_active=True,
|
||||
password_changed_at=datetime.now(),
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
seed_session.add(target)
|
||||
seed_session.commit()
|
||||
target_id = target.id
|
||||
seed_session.close()
|
||||
monkeypatch.setattr(permission_service_module, "get_session", identity_store)
|
||||
|
||||
success, message = PermissionService.set_user_permissions(
|
||||
target_id,
|
||||
["dashboard.view", "dashboard.view"],
|
||||
granted_by=identity_user,
|
||||
audit_context={
|
||||
"actor_id": identity_user,
|
||||
"ip_address": "198.51.100.41",
|
||||
"user_agent": "pytest-permission-audit",
|
||||
},
|
||||
)
|
||||
assert success is True
|
||||
assert "1 項權限" in message
|
||||
|
||||
verification_session = identity_store()
|
||||
assert verification_session.query(UserPermission).filter_by(user_id=target_id).count() == 1
|
||||
event = verification_session.query(LoginHistory).filter_by(
|
||||
user_id=target_id,
|
||||
status=LoginHistory.STATUS_PERMISSION_CHANGED,
|
||||
).one()
|
||||
assert event.failure_reason == f"actor_id={identity_user};change=replace;count=1"
|
||||
assert event.ip_address == "198.51.100.41"
|
||||
assert event.user_agent == "pytest-permission-audit"
|
||||
verification_session.close()
|
||||
|
||||
|
||||
def test_permission_mutation_and_audit_roll_back_on_commit_failure(
|
||||
monkeypatch,
|
||||
identity_store,
|
||||
identity_user,
|
||||
):
|
||||
seed_session = identity_store()
|
||||
target = User(
|
||||
username="permission-rollback",
|
||||
password_hash=generate_password_hash("PermissionPass9!", method="pbkdf2:sha256"),
|
||||
role="user",
|
||||
display_name="Permission Rollback",
|
||||
is_active=True,
|
||||
password_changed_at=datetime.now(),
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
seed_session.add(target)
|
||||
seed_session.commit()
|
||||
target_id = target.id
|
||||
seed_session.close()
|
||||
|
||||
failing_session = identity_store()
|
||||
|
||||
def fail_commit():
|
||||
raise RuntimeError("forced permission commit failure")
|
||||
|
||||
monkeypatch.setattr(failing_session, "commit", fail_commit)
|
||||
monkeypatch.setattr(permission_service_module, "get_session", lambda: failing_session)
|
||||
success, message = PermissionService.set_user_permissions(
|
||||
target_id,
|
||||
["dashboard.view"],
|
||||
granted_by=identity_user,
|
||||
audit_context={"actor_id": identity_user},
|
||||
)
|
||||
assert success is False
|
||||
assert "forced permission commit failure" in message
|
||||
|
||||
verification_session = identity_store()
|
||||
assert verification_session.query(UserPermission).filter_by(user_id=target_id).count() == 0
|
||||
assert verification_session.query(LoginHistory).filter_by(
|
||||
user_id=target_id,
|
||||
status=LoginHistory.STATUS_PERMISSION_CHANGED,
|
||||
).count() == 0
|
||||
verification_session.close()
|
||||
@@ -384,6 +384,9 @@ def test_sales_analysis_preview_context_cache_avoids_reloading_options(tmp_path,
|
||||
for _ in range(2):
|
||||
with app.test_request_context("/sales_analysis"):
|
||||
session["logged_in"] = True
|
||||
session["role"] = "admin"
|
||||
session["username"] = "legacy-admin"
|
||||
session["auth_source"] = "legacy_shared_password"
|
||||
response = sales_routes.sales_analysis()
|
||||
assert response["no_filter"] is True
|
||||
assert response["all_categories"] == ["美妝"]
|
||||
|
||||
@@ -12,6 +12,9 @@ def _client():
|
||||
client = app.test_client()
|
||||
with client.session_transaction() as session:
|
||||
session["logged_in"] = True
|
||||
session["role"] = "admin"
|
||||
session["username"] = "legacy-admin"
|
||||
session["auth_source"] = "legacy_shared_password"
|
||||
return client
|
||||
|
||||
|
||||
|
||||
@@ -98,6 +98,12 @@ from services.market_intel.schema_db_probe import build_schema_db_probe_plan
|
||||
TEST_APPROVAL_TOKEN = "test-market-intel-approval-token"
|
||||
|
||||
|
||||
def _build_test_app():
|
||||
test_app = Flask(__name__)
|
||||
test_app.config["TESTING"] = True
|
||||
return test_app
|
||||
|
||||
|
||||
def _market_intel_sample_result(batch_id="sample-batch-receipt"):
|
||||
return {
|
||||
"batch_id": batch_id,
|
||||
@@ -2095,7 +2101,7 @@ def test_mcp_tool_contract_detects_missing_router_whitelist():
|
||||
def test_mcp_tool_contract_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2179,7 +2185,7 @@ def test_mcp_activation_runbook_can_be_ready_with_mocked_gates():
|
||||
def test_mcp_activation_runbook_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2262,7 +2268,7 @@ def test_mcp_fetch_gate_can_open_with_mocked_ready_state():
|
||||
def test_mcp_fetch_gate_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2350,7 +2356,7 @@ def test_mcp_completion_audit_detects_missing_internal_contract():
|
||||
def test_mcp_completion_audit_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2448,7 +2454,7 @@ def test_mcp_activation_evidence_blocks_secret_literals_and_write_flags():
|
||||
def test_mcp_activation_evidence_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2559,7 +2565,7 @@ def test_mcp_runtime_smoke_receipt_blocks_incomplete_or_write_receipt():
|
||||
def test_mcp_runtime_smoke_receipt_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2665,7 +2671,7 @@ def test_mcp_runtime_promotion_blocks_partial_or_unsafe_package():
|
||||
def test_mcp_runtime_promotion_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2773,7 +2779,7 @@ def test_mcp_manual_fetch_handoff_blocks_missing_acknowledgement():
|
||||
def test_mcp_manual_fetch_handoff_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -2901,7 +2907,7 @@ def test_mcp_fetch_target_review_blocks_unknown_or_unsafe_target():
|
||||
def test_mcp_fetch_target_review_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3030,7 +3036,7 @@ def test_mcp_fetch_run_package_blocks_unsafe_controls():
|
||||
def test_mcp_fetch_run_package_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3169,7 +3175,7 @@ def test_mcp_fetch_run_readiness_blocks_unsafe_operator_payload():
|
||||
def test_mcp_fetch_run_readiness_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3313,7 +3319,7 @@ def test_mcp_fetch_run_receipt_blocks_unsafe_receipt_payload():
|
||||
def test_mcp_fetch_run_receipt_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3467,7 +3473,7 @@ def test_mcp_fetch_result_parser_review_blocks_unsafe_parser_payload():
|
||||
def test_mcp_fetch_result_parser_review_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3626,7 +3632,7 @@ def test_mcp_fetch_candidate_handoff_review_blocks_unsafe_handoff():
|
||||
def test_mcp_fetch_candidate_handoff_review_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3785,7 +3791,7 @@ def test_mcp_fetch_candidate_queue_review_blocks_unsafe_review():
|
||||
def test_mcp_fetch_candidate_queue_review_route_get_and_post_are_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -3957,7 +3963,7 @@ def test_mcp_fetch_candidate_queue_writer_preflight_blocks_unsafe_preflight():
|
||||
def test_mcp_fetch_candidate_queue_writer_preflight_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -4131,7 +4137,7 @@ def test_mcp_fetch_candidate_queue_writer_cli_review_blocks_unsafe_review():
|
||||
def test_mcp_fetch_candidate_queue_writer_cli_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -4343,7 +4349,7 @@ def test_mcp_fetch_candidate_queue_writer_run_package_review_blocks_unsafe_revie
|
||||
def test_mcp_fetch_candidate_queue_writer_run_package_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -4551,7 +4557,7 @@ def test_mcp_fetch_candidate_queue_writer_run_readiness_blocks_unsafe_review():
|
||||
def test_mcp_fetch_candidate_queue_writer_run_readiness_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -4774,7 +4780,7 @@ def test_mcp_fetch_candidate_queue_writer_run_receipt_review_blocks_unsafe_revie
|
||||
def test_mcp_fetch_candidate_queue_writer_run_receipt_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -5014,7 +5020,7 @@ def test_mcp_fetch_candidate_queue_writer_run_closeout_review_blocks_unsafe_revi
|
||||
def test_mcp_fetch_candidate_queue_writer_run_closeout_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -5275,7 +5281,7 @@ def test_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review_blocks_
|
||||
def test_mcp_fetch_candidate_queue_writer_post_closeout_inventory_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -5531,7 +5537,7 @@ def test_mcp_fetch_candidate_queue_writer_review_handoff_blocks_unsafe_review():
|
||||
def test_mcp_fetch_candidate_queue_writer_review_handoff_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -5758,7 +5764,7 @@ def test_mcp_fetch_candidate_queue_writer_review_inventory_blocks_unsafe_invento
|
||||
def test_mcp_fetch_candidate_queue_writer_review_inventory_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -5945,7 +5951,7 @@ def test_mcp_fetch_candidate_queue_writer_review_inventory_blocks_bad_inventory_
|
||||
def test_mcp_fetch_candidate_queue_writer_review_inventory_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -6198,7 +6204,7 @@ def test_mcp_fetch_candidate_queue_writer_review_decision_blocks_unsafe_decision
|
||||
def test_mcp_fetch_candidate_queue_writer_review_decision_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -6459,7 +6465,7 @@ def test_mcp_fetch_candidate_queue_writer_review_decision_approval_blocks_unsafe
|
||||
def test_mcp_fetch_candidate_queue_writer_review_decision_approval_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -6766,7 +6772,7 @@ def test_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_prefli
|
||||
def test_mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -6867,7 +6873,7 @@ def test_manual_sample_plan_preview_blocks_fetch_and_write():
|
||||
def test_manual_sample_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -6920,7 +6926,7 @@ def test_manual_sample_acceptance_preview_blocks_candidate_import():
|
||||
def test_manual_sample_acceptance_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7043,7 +7049,7 @@ def test_manual_sample_review_evaluator_rejects_bad_result():
|
||||
def test_manual_sample_review_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7117,7 +7123,7 @@ def test_manual_sample_review_evaluation_preview_accepts_payload_without_persist
|
||||
def test_manual_sample_review_evaluate_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7172,7 +7178,7 @@ def test_manual_sample_review_evaluate_route_is_post_only_and_no_write():
|
||||
def test_manual_sample_review_evaluate_rejects_invalid_json_without_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7261,7 +7267,7 @@ def test_manual_sample_candidate_handoff_preview_creates_candidates_without_pers
|
||||
def test_manual_sample_candidate_handoff_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7394,7 +7400,7 @@ def test_manual_sample_candidate_queue_draft_preview_builds_review_items_without
|
||||
def test_manual_sample_candidate_queue_draft_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7535,7 +7541,7 @@ def test_manual_sample_candidate_queue_approval_preview_blocks_write_and_maps_ro
|
||||
def test_manual_sample_candidate_queue_approval_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -7678,7 +7684,7 @@ def test_manual_sample_candidate_queue_transaction_preview_blocks_execution():
|
||||
def test_manual_sample_candidate_queue_transaction_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -13401,7 +13407,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -13476,7 +13482,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -13748,7 +13754,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -14036,7 +14042,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -14346,7 +14352,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -14649,7 +14655,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -14908,7 +14914,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -15181,7 +15187,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -15429,7 +15435,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -15659,7 +15665,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -15896,7 +15902,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -16138,7 +16144,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -16414,7 +16420,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -16691,7 +16697,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -16970,7 +16976,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -17299,7 +17305,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -17561,7 +17567,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -17825,7 +17831,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -18095,7 +18101,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -18383,7 +18389,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -18670,7 +18676,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -18744,7 +18750,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_
|
||||
def test_candidate_queue_writer_preflight_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -18799,7 +18805,7 @@ def test_candidate_queue_writer_status_route_never_leaks_approval_token(monkeypa
|
||||
|
||||
monkeypatch.setenv("MARKET_INTEL_QUEUE_WRITE_APPROVAL", TEST_APPROVAL_TOKEN)
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -18864,7 +18870,7 @@ def test_candidate_queue_writer_status_route_never_leaks_approval_token(monkeypa
|
||||
def test_candidate_queue_writer_status_blocks_invalid_payload():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -18889,7 +18895,7 @@ def test_candidate_queue_writer_status_blocks_invalid_payload():
|
||||
def test_candidate_queue_writer_postwrite_smoke_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -18943,7 +18949,7 @@ def test_candidate_queue_writer_postwrite_smoke_route_is_post_only_and_no_write(
|
||||
def test_candidate_queue_writer_operator_drill_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -18999,7 +19005,7 @@ def test_candidate_queue_writer_operator_drill_route_is_post_only_and_no_write()
|
||||
def test_candidate_queue_writer_run_package_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -19058,7 +19064,7 @@ def test_candidate_queue_writer_run_package_route_is_post_only_and_no_write():
|
||||
def test_candidate_queue_writer_run_readiness_route_is_post_only_and_no_write():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -19347,7 +19353,7 @@ def test_candidate_queue_writer_run_receipt_route_accepts_inline_payload_no_writ
|
||||
sample_result=sample_result
|
||||
)
|
||||
expected_key = transaction["statements"][0]["lookup"]["dedupe_key"]
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -19427,7 +19433,7 @@ def test_candidate_queue_writer_run_closeout_route_is_post_only_and_no_write():
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-closeout-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -19477,7 +19483,7 @@ def test_candidate_queue_review_handoff_route_is_post_only_and_no_write():
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-handoff-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -19528,7 +19534,7 @@ def test_candidate_queue_review_inventory_route_is_post_only_and_no_write():
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-inventory-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19590,7 +19596,7 @@ def test_candidate_queue_review_decision_route_is_post_only_and_no_write():
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19657,7 +19663,7 @@ def test_candidate_queue_review_decision_approval_route_is_post_only_and_no_writ
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-approval-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19730,7 +19736,7 @@ def test_candidate_queue_review_decision_transaction_route_is_post_only_and_no_w
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-transaction-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19811,7 +19817,7 @@ def test_candidate_queue_review_decision_writer_status_route_is_post_only_and_no
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-writer-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19895,7 +19901,7 @@ def test_candidate_queue_review_decision_writer_preflight_route_is_post_only_and
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-preflight-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -19979,7 +19985,7 @@ def test_candidate_queue_review_decision_writer_postwrite_smoke_route_is_post_on
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-postwrite-smoke-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20058,7 +20064,7 @@ def test_candidate_queue_review_decision_writer_operator_drill_route_is_post_onl
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-operator-drill-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20144,7 +20150,7 @@ def test_candidate_queue_review_decision_writer_run_package_route_is_post_only_a
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-run-package-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20228,7 +20234,7 @@ def test_candidate_queue_review_decision_writer_run_readiness_route_is_post_only
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-run-readiness-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20317,7 +20323,7 @@ def test_candidate_queue_review_decision_writer_run_receipt_route_is_post_only_a
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-run-receipt-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20427,7 +20433,7 @@ def test_candidate_queue_review_decision_writer_run_closeout_route_is_post_only_
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
"sample-batch-review-decision-run-closeout-route"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20512,7 +20518,7 @@ def test_candidate_queue_review_decision_post_closeout_inventory_route_is_post_o
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20568,7 +20574,7 @@ def test_candidate_queue_review_completion_archive_route_is_post_only_and_no_wri
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20621,7 +20627,7 @@ def test_candidate_queue_review_archive_summary_route_is_post_only_and_no_write(
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20682,7 +20688,7 @@ def test_candidate_queue_review_ai_summary_preflight_route_is_post_only_and_no_w
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20751,7 +20757,7 @@ def test_candidate_queue_review_ai_summary_run_package_route_is_post_only_and_no
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20823,7 +20829,7 @@ def test_candidate_queue_review_ai_summary_output_receipt_route_is_post_only_and
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20893,7 +20899,7 @@ def test_candidate_queue_review_ai_summary_persistence_preflight_route_is_post_o
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -20964,7 +20970,7 @@ def test_candidate_queue_review_ai_summary_persistence_transaction_route_is_post
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21029,7 +21035,7 @@ def test_candidate_queue_review_ai_summary_persistence_writer_preflight_route_is
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21100,7 +21106,7 @@ def test_candidate_queue_review_ai_summary_persistence_run_package_route_is_post
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21173,7 +21179,7 @@ def test_candidate_queue_review_ai_summary_persistence_run_readiness_route_is_po
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21250,7 +21256,7 @@ def test_candidate_queue_review_ai_summary_persistence_run_receipt_route_is_post
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21327,7 +21333,7 @@ def test_candidate_queue_review_ai_summary_persistence_run_closeout_route_is_pos
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21404,7 +21410,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate_ro
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21478,7 +21484,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_pac
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21557,7 +21563,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_rea
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21644,7 +21650,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_rec
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21725,7 +21731,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeou
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21807,7 +21813,7 @@ def test_candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
from routes.market_intel_review_routes import market_intel_review_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
app.register_blueprint(market_intel_review_bp)
|
||||
@@ -21895,7 +21901,7 @@ def test_candidate_queue_writer_run_receipt_route_is_post_only_and_no_write():
|
||||
fixture = _build_candidate_queue_writer_receipt_fixture(
|
||||
batch_id="sample-batch-route-receipt"
|
||||
)
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -21968,7 +21974,7 @@ def test_scheduler_plan_preview_blocks_job_attachment():
|
||||
def test_scheduler_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22015,7 +22021,7 @@ def test_match_review_plan_preview_blocks_auto_confirm():
|
||||
def test_match_review_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22067,7 +22073,7 @@ def test_opportunity_plan_preview_blocks_alerts_and_ai_summary():
|
||||
def test_opportunity_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22126,7 +22132,7 @@ def test_opportunity_scoring_plan_preview_blocks_scoring_and_alerts():
|
||||
def test_opportunity_scoring_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22184,7 +22190,7 @@ def test_opportunity_evidence_plan_preview_blocks_queries_and_alerts():
|
||||
def test_opportunity_evidence_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22282,7 +22288,7 @@ def test_opportunity_alert_plan_preview_blocks_dispatch_and_llm_calls():
|
||||
def test_opportunity_alert_plan_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -22360,7 +22366,7 @@ def test_mcp_deploy_preflight_ready_when_env_contract_is_present():
|
||||
def test_mcp_deploy_preflight_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -27469,7 +27475,7 @@ def test_migration_apply_drill_sqlite_read_only_reports_already_applied():
|
||||
def test_migration_apply_drill_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -27596,7 +27602,7 @@ def test_migration_catalog_review_sqlite_read_only_detects_partial_schema():
|
||||
def test_migration_catalog_review_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -27671,7 +27677,7 @@ def test_migration_live_smoke_sqlite_not_applied_tolerates_seed_table_missing():
|
||||
def test_migration_live_smoke_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -27828,7 +27834,7 @@ def test_live_db_inventory_sqlite_read_only_counts_market_tables():
|
||||
def test_live_db_inventory_route_is_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -27898,7 +27904,7 @@ def test_seed_writer_cli_status_route_never_leaks_approval_token(monkeypatch):
|
||||
|
||||
monkeypatch.setenv("MARKET_INTEL_SEED_WRITE_APPROVAL", TEST_APPROVAL_TOKEN)
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -28243,7 +28249,7 @@ def test_mcp_professional_source_governance_blocks_unsafe_source():
|
||||
def test_mcp_professional_source_governance_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
@@ -28368,7 +28374,7 @@ def test_mcp_fetch_target_source_governance_review_blocks_missing_source_contrac
|
||||
def test_mcp_fetch_target_source_governance_review_route_get_and_post_preview_only():
|
||||
from routes.market_intel_routes import market_intel_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
app = _build_test_app()
|
||||
app.secret_key = "test-secret"
|
||||
app.register_blueprint(market_intel_bp)
|
||||
client = app.test_client()
|
||||
|
||||
@@ -270,7 +270,24 @@ def test_pchome_growth_opportunities_use_plain_language_and_pause_shopee_coupang
|
||||
assert payload["stats"]["needs_mapping_count"] == 1
|
||||
assert payload["stats"]["catalog_product_count"] == 2
|
||||
assert payload["stats"]["mapped_product_count"] == 1
|
||||
assert payload["stats"]["active_catalog_product_count"] == 2
|
||||
assert payload["stats"]["active_mapped_product_count"] == 1
|
||||
assert payload["stats"]["active_catalog_mapping_rate"] == 50.0
|
||||
assert payload["stats"]["sales_freshness"]["status"] == "critical"
|
||||
contract = payload["stats"]["comparison_metric_contract"]
|
||||
assert contract["numerator"] == 1
|
||||
assert contract["denominator"] == 2
|
||||
assert contract["state_partition"] == {
|
||||
"comparison_ready": 1,
|
||||
"candidate_validation": 0,
|
||||
"unmatched": 1,
|
||||
"total": 2,
|
||||
"mutually_exclusive": True,
|
||||
}
|
||||
assert contract["operational_decision"]["ready"] is False
|
||||
assert contract["operational_decision"]["ready_count"] == 0
|
||||
assert "sales_data_stale" in contract["operational_decision"]["blocked_reason_codes"]
|
||||
assert contract["platform_runtime"]["denominator"] == 15
|
||||
|
||||
actions = {item["pchome_product_id"]: item["recommended_action"]["label"] for item in payload["opportunities"]}
|
||||
assert actions["PCH-1"] == "檢查售價與活動"
|
||||
@@ -282,6 +299,33 @@ def test_pchome_growth_opportunities_use_plain_language_and_pause_shopee_coupang
|
||||
assert all("identity" not in " ".join(item["reason_lines"]).lower() for item in payload["opportunities"])
|
||||
|
||||
|
||||
def test_pchome_growth_coverage_states_are_mutually_exclusive():
|
||||
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
|
||||
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
_seed_growth_tables(engine)
|
||||
_seed_growth_review_candidate_offer(engine)
|
||||
with engine.begin() as conn:
|
||||
conn.execute(text("""
|
||||
INSERT INTO daily_sales_snapshot
|
||||
("商品ID", "商品名稱", snapshot_date, "總業績", "數量", "商品館")
|
||||
VALUES
|
||||
('PCH-REVIEW', 'PChome 待確認商品', '2026-06-14', 100000, 20, '彩妝')
|
||||
"""))
|
||||
|
||||
payload = build_pchome_growth_opportunities(engine, limit=5)
|
||||
stats = payload["stats"]
|
||||
|
||||
assert stats["candidate_count"] == 3
|
||||
assert stats["comparison_ready_count"] == 1
|
||||
assert stats["current_review_candidate_count"] == 1
|
||||
assert stats["candidate_validation_count"] == 1
|
||||
assert stats["unmatched_count"] == 1
|
||||
assert stats["unresolved_count"] == 2
|
||||
assert stats["comparison_ready_count"] + stats["candidate_validation_count"] + stats["unmatched_count"] == 3
|
||||
assert stats["comparison_metric_contract"]["state_partition"]["mutually_exclusive"] is True
|
||||
|
||||
|
||||
def test_momo_review_candidates_return_dual_store_links_and_plain_reasons():
|
||||
from services.external_market_offer_service import list_momo_review_candidates
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import importlib
|
||||
import inspect
|
||||
import ast
|
||||
import json
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
@@ -229,8 +230,9 @@ def test_roi_ai_smoke_and_daily_report_schedules_stay_staggered():
|
||||
assert 'schedule.every().day.at("09:05").do(run_roi_monthly_report_if_new_month)' in source
|
||||
assert 'schedule.every().day.at("09:10").do(run_ai_smoke_daily_summary_task)' in source
|
||||
assert 'schedule.every().day.at("10:30").do(run_pchome_match_backfill_task)' in source
|
||||
assert 'schedule.every().day.at("10:45").do(run_pchome_growth_momo_backfill_task)' in source
|
||||
assert "schedule.every(30).minutes.do(run_pchome_growth_momo_backfill_catchup_task)" in source
|
||||
assert 'schedule.every(30).minutes.do(run_revenue_automation_cycle_background_task)' in source
|
||||
assert 'schedule.every().day.at("10:45").do(run_pchome_growth_momo_backfill_background_task)' in source
|
||||
assert "start_revenue_automation_watchdog()" in source
|
||||
assert "schedule.every(6).hours.do(run_action_plan_hygiene_task)" in source
|
||||
assert "schedule.every(15).minutes.do(run_ollama_111_usage_guard_check)" in source
|
||||
|
||||
@@ -275,6 +277,63 @@ def test_pchome_growth_backfill_catchup_executes_missing_receipt(monkeypatch, tm
|
||||
assert calls == ["executed"]
|
||||
|
||||
|
||||
def test_revenue_automation_cycle_runs_sales_refresh_before_mapping(monkeypatch):
|
||||
run_scheduler = _load_run_scheduler(monkeypatch)
|
||||
calls = []
|
||||
monkeypatch.setattr(run_scheduler, "run_auto_import_task", lambda: calls.append("sales"))
|
||||
monkeypatch.setattr(
|
||||
run_scheduler,
|
||||
"run_pchome_growth_momo_backfill_catchup_task",
|
||||
lambda: calls.append("mapping") or {"status": "executed"},
|
||||
)
|
||||
|
||||
result = run_scheduler.run_revenue_automation_cycle_task()
|
||||
|
||||
assert calls == ["sales", "mapping"]
|
||||
assert result == {"status": "completed", "backfill": {"status": "executed"}}
|
||||
|
||||
|
||||
def test_revenue_automation_background_is_nonblocking_and_rejects_overlap(monkeypatch):
|
||||
run_scheduler = _load_run_scheduler(monkeypatch)
|
||||
monkeypatch.setattr(run_scheduler, "_REVENUE_AUTOMATION_LOCK", threading.Lock())
|
||||
threads = []
|
||||
calls = []
|
||||
|
||||
class DeferredThread:
|
||||
def __init__(self, *, target, daemon, name):
|
||||
self.target = target
|
||||
self.daemon = daemon
|
||||
self.name = name
|
||||
self.started = False
|
||||
threads.append(self)
|
||||
|
||||
def start(self):
|
||||
self.started = True
|
||||
|
||||
monkeypatch.setattr(run_scheduler.threading, "Thread", DeferredThread)
|
||||
|
||||
queued = run_scheduler._launch_revenue_automation_background(
|
||||
"coverage-refresh",
|
||||
lambda: calls.append("executed"),
|
||||
)
|
||||
overlapping = run_scheduler._launch_revenue_automation_background(
|
||||
"duplicate",
|
||||
lambda: calls.append("duplicate"),
|
||||
)
|
||||
|
||||
assert queued == {"status": "queued", "job": "coverage-refresh"}
|
||||
assert overlapping == {
|
||||
"status": "skipped",
|
||||
"reason": "revenue_automation_already_running",
|
||||
}
|
||||
assert threads[0].started is True
|
||||
assert calls == []
|
||||
|
||||
threads[0].target()
|
||||
assert calls == ["executed"]
|
||||
assert run_scheduler._REVENUE_AUTOMATION_LOCK.locked() is False
|
||||
|
||||
|
||||
def test_ollama_111_usage_guard_stays_observational(monkeypatch):
|
||||
run_scheduler = _load_run_scheduler(monkeypatch)
|
||||
source = inspect.getsource(run_scheduler.run_ollama_111_usage_guard_check)
|
||||
|
||||
@@ -24,11 +24,17 @@ def test_security_governance_review_is_honest_and_release_gate_passes():
|
||||
assert report["completion"]["asset_runtime_reconciliation_complete"] is False
|
||||
assert report["completion"]["runtime_closure_passed"] < report["completion"]["runtime_closure_total"]
|
||||
assert report["work_items"][0]["id"] == "SEC-P0-001"
|
||||
assert report["work_items"][1]["id"] == "SEC-P0-002"
|
||||
assert report["work_items"][1]["status"] == "in_progress"
|
||||
assert len(report["work_items"]) == 22
|
||||
assert report["work_items"][9]["id"] == "REL-P0-001"
|
||||
assert any(item["id"] == "QA-P1-001" for item in report["work_items"])
|
||||
assert report["work_items"][-1]["id"] == "CHAOS-P2-001"
|
||||
checks = {item["id"]: item for item in report["checks"]}
|
||||
assert checks["PR.AC-database-identity-rbac"]["status"] == "partial"
|
||||
identity_markers = checks["PR.AC-database-identity-rbac"]["evidence"]["source"]["markers"]
|
||||
assert identity_markers["identity_mutation_audit"] is True
|
||||
assert identity_markers["permission_mutation_audit"] is True
|
||||
access = checks["PR.AA-route-access-control"]["evidence"]
|
||||
assert access["mutating_unguarded_count"] == 0
|
||||
assert access["unclassified_unguarded_count"] == 0
|
||||
@@ -211,6 +217,41 @@ def test_runtime_receipt_rejects_public_metrics_even_when_other_checks_pass(tmp_
|
||||
assert receipt["security_readback_passed"] is False
|
||||
|
||||
|
||||
def test_auth_identity_runtime_receipt_requires_database_only_closure(tmp_path):
|
||||
from services.security_governance_review_service import (
|
||||
_auth_identity_runtime_receipt,
|
||||
)
|
||||
|
||||
governance = tmp_path / "governance"
|
||||
governance.mkdir()
|
||||
path = governance / "auth_identity_runtime_receipt.json"
|
||||
payload = {
|
||||
"receipt_kind": "auth_identity_runtime_readback",
|
||||
"work_item_id": "SEC-P0-002",
|
||||
"status": "canary_ready",
|
||||
"auth_identity_mode": "hybrid",
|
||||
"canary_ready": True,
|
||||
"runtime_closed": False,
|
||||
"inventory": {"users_total": 2, "active_admin_count": 2},
|
||||
"checks": {"legacy_shared_password_retired": False},
|
||||
}
|
||||
path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
canary = _auth_identity_runtime_receipt(tmp_path)
|
||||
assert canary["canary_ready"] is True
|
||||
assert canary["runtime_closed"] is False
|
||||
|
||||
payload.update({
|
||||
"status": "completed",
|
||||
"auth_identity_mode": "database",
|
||||
"runtime_closed": True,
|
||||
"checks": {"legacy_shared_password_retired": True},
|
||||
})
|
||||
path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
closed = _auth_identity_runtime_receipt(tmp_path)
|
||||
assert closed["runtime_closed"] is True
|
||||
assert closed["legacy_shared_password_retired"] is True
|
||||
|
||||
|
||||
def test_sensitive_blueprint_policy_denies_anonymous_api(monkeypatch):
|
||||
import auth
|
||||
from services.http_access_policy_service import enforce_http_access_policy
|
||||
@@ -234,6 +275,9 @@ def test_sensitive_blueprint_policy_denies_anonymous_api(monkeypatch):
|
||||
|
||||
with client.session_transaction() as session:
|
||||
session["logged_in"] = True
|
||||
session["role"] = "admin"
|
||||
session["username"] = "legacy-admin"
|
||||
session["auth_source"] = "legacy_shared_password"
|
||||
authenticated = client.get("/vendor-stockout/api/vendor/list")
|
||||
assert authenticated.status_code == 200
|
||||
|
||||
|
||||
Reference in New Issue
Block a user