3338ec1622
---ci--- project: acdl phase: 2 milestone: v1.28 status: execute persona: backend-engineer --- synthesize_local_env(contract_path, environment) reads a contract YAML and produces a purely synthetic local env dict (account_id=000000000000 placeholder, region='local', local state_backend, local network) that validates against schemas/environment.schema.json. Mirrors the shape of core/environments/*.json + core/onboarding.py:generate_env_file() (shape parity on the required env-binding keys). No cloud provisioning — purely synthetic for nova apply --local. tests/test_local_env.py: 13 tests (schema validation, region/account sentinels, env override, threshold per-env, shape parity, missing-file default).
149 lines
5.9 KiB
Python
149 lines
5.9 KiB
Python
"""REQ-330 tests: core.env.synthesize_local_env — local env synthesizer.
|
|
|
|
Verifies the synthesizer:
|
|
- reads a contract YAML and produces a local env dict
|
|
- the dict mirrors the shape of core/environments/*.json (validates
|
|
against schemas/environment.schema.json)
|
|
- region is "local" + account_id is the placeholder (no real AWS)
|
|
- the environment override wins over the contract's environment field
|
|
- mirrors core/onboarding.py:generate_env_file() shape (same required keys)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import jsonschema
|
|
import pytest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from core.env import synthesize_local_env
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
ENV_SCHEMA_PATH = REPO_ROOT / "schemas" / "environment.schema.json"
|
|
|
|
|
|
@pytest.fixture
|
|
def env_schema():
|
|
return json.loads(ENV_SCHEMA_PATH.read_text())
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_contract(tmp_path):
|
|
"""A minimal contract YAML for the synthesizer to read."""
|
|
contract = """
|
|
id: msvc
|
|
name: microservice
|
|
environment: dev
|
|
infrastructure:
|
|
microservice:
|
|
version: "1.0.0"
|
|
inputs:
|
|
image: nginx:latest
|
|
"""
|
|
p = tmp_path / "contract.yml"
|
|
p.write_text(contract)
|
|
return p
|
|
|
|
|
|
class TestSynthesizeLocalEnv:
|
|
def test_returns_dict_with_required_keys(self, sample_contract, env_schema):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert isinstance(env, dict)
|
|
# The schema-required keys.
|
|
for key in (
|
|
"name", "account_id", "region", "state_backend",
|
|
"network", "runner_role_arn", "autonomy", "confidence_threshold",
|
|
):
|
|
assert key in env, f"missing required key: {key}"
|
|
|
|
def test_validates_against_environment_schema(self, sample_contract, env_schema):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
jsonschema.validate(env, env_schema) # raises on invalid
|
|
|
|
def test_region_is_local(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert env["region"] == "local", "region must be the local sentinel"
|
|
|
|
def test_account_id_is_placeholder(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert env["account_id"] == "000000000000", (
|
|
"account_id must be the placeholder (no real AWS account)"
|
|
)
|
|
|
|
def test_state_backend_is_local(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
sb = env["state_backend"]
|
|
assert sb["bucket"] == "local-tfstate"
|
|
assert sb["lock_table"] == "local-locks"
|
|
|
|
def test_uses_contract_environment_by_default(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert env["name"] == "dev" # the contract's environment field
|
|
|
|
def test_environment_override_wins(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract), environment="qa")
|
|
assert env["name"] == "qa"
|
|
# qa threshold is 0.75 (per-env default)
|
|
assert env["confidence_threshold"] == 0.75
|
|
|
|
def test_confidence_threshold_per_env(self, sample_contract):
|
|
for env_name, expected in (("dev", 0.50), ("qa", 0.75), ("prod", 0.90), ("dr", 0.95)):
|
|
env = synthesize_local_env(str(sample_contract), environment=env_name)
|
|
assert env["confidence_threshold"] == expected, env_name
|
|
|
|
def test_autonomy_is_full(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert env["autonomy"] == "full" # local tier is autonomous
|
|
|
|
def test_no_real_aws_resources(self, sample_contract):
|
|
"""The synthesizer must NOT reference real AWS resources — region
|
|
is 'local', the ARN uses the placeholder account, the bucket is local."""
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert "us-east-1" not in env["region"]
|
|
assert "000000000000" in env["runner_role_arn"]
|
|
assert "local" in env["state_backend"]["bucket"]
|
|
|
|
def test_mirrors_onboarding_env_file_shape(self, sample_contract, env_schema):
|
|
"""The synthesized env has the same core shape as
|
|
core/onboarding.py:generate_env_file() output — both carry the
|
|
schema-required environment-binding keys. (generate_env_file adds
|
|
ownerId/billingTag for the onboarding request path; the synthesizer
|
|
is the local-tier counterpart and omits those — no consumer binding.)"""
|
|
from core.onboarding import generate_env_file
|
|
request = {
|
|
"consumerRepo": "acdl/consumer-a",
|
|
"requestedEnvironment": "dev",
|
|
"ownerId": "team-a",
|
|
"billingTag": "cc-a",
|
|
}
|
|
onboarded = generate_env_file(request)
|
|
# The synthesizer output validates against the env schema.
|
|
synth = synthesize_local_env(str(sample_contract))
|
|
jsonschema.validate(synth, env_schema)
|
|
# Both carry the schema-required environment-binding keys.
|
|
required = {
|
|
"name", "account_id", "region", "state_backend",
|
|
"network", "runner_role_arn", "autonomy", "confidence_threshold",
|
|
}
|
|
assert required <= set(onboarded.keys()), "onboarding output missing required keys"
|
|
assert required <= set(synth.keys()), "synthesizer output missing required keys"
|
|
# The synthesizer omits the onboarding-request-only keys.
|
|
assert "ownerId" not in synth
|
|
assert "billingTag" not in synth
|
|
|
|
def test_missing_contract_file_defaults_to_dev(self, tmp_path):
|
|
"""A non-existent contract path defaults to the dev env (no crash)."""
|
|
env = synthesize_local_env(str(tmp_path / "nonexistent.yml"))
|
|
assert env["name"] == "dev"
|
|
assert env["region"] == "local"
|
|
|
|
def test_description_mentions_contract_id(self, sample_contract):
|
|
env = synthesize_local_env(str(sample_contract))
|
|
assert "msvc" in env["description"], (
|
|
"description should reference the contract id for traceability"
|
|
) |