import importlib.util import json from pathlib import Path import subprocess import sys import tomllib from types import SimpleNamespace from viano_bug_analysis import deployment from viano_bug_analysis.api.config import APIConfig, Project, Client from viano_bug_analysis.api.sessions import Sessions from viano_bug_analysis.worker.config import WorkerConfig ROOT=Path(__file__).resolve().parents[1] def installer(): spec=importlib.util.spec_from_file_location('linux_install',ROOT/'deploy/install.py') module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) return module def test_install_plan_and_templates(): result=subprocess.run([sys.executable,str(ROOT/'deploy/install.py'),'--dry-run'],capture_output=True,text=True,check=True) assert json.loads(result.stdout)['services_started'] is False files=installer().templates() for name,text in files.items(): if name.endswith('.toml'):tomllib.loads(text) project=tomllib.loads(files['project.toml']) assert project['database']['password_env']=='VIANO_DB_PASSWORD' assert 'password_file' not in project['database'] assert project['source']['commit']=='0'*40 assert tomllib.loads(files['api.toml'])['auth']['users_database'].startswith('/etc/') def test_api_ready_and_missing_machine_credentials(tmp_path,monkeypatch): config=APIConfig(tmp_path,(Project('p','wow','vcore'),),()) monkeypatch.setattr(deployment.APIConfig,'load',lambda path:config) assert deployment.check('api','unused')['status']=='NOT_READY' Sessions(config).users.manage('create_user','alice','example password') assert deployment.check('api','unused')['status']=='READY' config=APIConfig(tmp_path,config.projects,(Client('m','VBA_TEST_MISSING',('p',)),)) monkeypatch.delenv('VBA_TEST_MISSING',raising=False) assert deployment.check('api','unused')['code']=='DEPLOY_MACHINE_CREDENTIALS' def test_worker_checks_only_read_dependencies(tmp_path,monkeypatch): config=APIConfig(tmp_path,(Project('p','wow','vcore'),),()) monkeypatch.setattr(deployment.WorkerConfig,'load',lambda path:SimpleNamespace(intake=config,min_free_bytes=1)) calls=[] research=SimpleNamespace(source=SimpleNamespace(inventory=lambda:calls.append('git')), database=SimpleNamespace(tables=['creature'],query=lambda query:calls.append(query))) def loader(project):return object(),{'project_research':research},'hash' result=deployment.check('worker','unused',loader=loader) assert result['status']=='READY' and result['model_requests']==0 and not result['cloud_api_tested'] assert calls==['git',{'kind':'database.schema','table':'creature'}] calls.clear() assert deployment.check('worker','unused',offline=True,loader=loader)['status']=='CONFIG_READY' assert calls==[] def failing(project):raise RuntimeError('secret-value') assert 'secret-value' not in json.dumps(deployment.check('worker','unused',loader=failing)) def test_units_keep_secrets_separate_and_code_readonly(): for role in ('api','worker'): unit=(ROOT/'deploy'/f'viano-bug-analysis-{role}.service').read_text() assert f'EnvironmentFile=/etc/viano-bug-analysis/{role}.env' in unit assert f'deployment check --role {role}' in unit assert 'ProtectSystem=strict' in unit assert 'ReadWritePaths=/var/lib/viano-bug-analysis' in unit assert 'Restart=always' in unit and '--once' not in unit def test_installed_config_references_resolve(tmp_path): for name,text in installer().templates().items(): (tmp_path/name).write_text(text,encoding='utf-8') config=WorkerConfig.load(tmp_path/'worker.toml') assert config.intake.projects[0].planning_mode=='joint' assert all(path.is_file() for path in config.intake.projects[0].settings.values()) def test_https_proxy_preserves_secure_login_cookie(tmp_path): from starlette.testclient import TestClient from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from viano_bug_analysis.api.app import create_app config=APIConfig(tmp_path,(Project('p','wow','vcore'),),()) app=create_app(config) app.state.sessions.users.manage('create_user','alice','example password') proxied=ProxyHeadersMiddleware(app,trusted_hosts=['127.0.0.1']) with TestClient(proxied,client=('127.0.0.1',1234)) as client: result=client.post('/v1/auth/login',headers={'X-Forwarded-Proto':'https','Origin':'https://testserver'}, json={'username':'alice','password':'example password'}) assert result.status_code==200 assert 'secure' in result.headers['set-cookie'].lower()