feat(P02): core/env.synthesize_local_env — local env synthesizer (REQ-330, backend-engineer)
---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).
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""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"
|
||||
)
|
||||
Reference in New Issue
Block a user