453 lines
18 KiB
Python
453 lines
18 KiB
Python
"""Round-trip tests for organization config schema.
|
|
|
|
Covers:
|
|
- save() writes schema_version: 2 org config under company_orgs/
|
|
- load() rejects schema_version > 1 with ValueError
|
|
- Export YAML round-trips roles/employees bitwise identical
|
|
|
|
All tests redirect _find_project_root to tmp_path so the real config is untouched.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import yaml
|
|
import pytest
|
|
|
|
|
|
def _write_minimal_corporate(corporate_dir, schema_version: int | None = 1) -> None:
|
|
"""Seed a minimal valid corporate yaml in the given directory."""
|
|
corporate_dir.mkdir(parents=True, exist_ok=True)
|
|
payload: dict = {
|
|
"company": {
|
|
"name": "Test Co",
|
|
"topology": "flat",
|
|
"company_profile": "",
|
|
"execution_model": "sequential",
|
|
"final_decider_role_id": None,
|
|
"company_profiles": [],
|
|
},
|
|
"roles": [],
|
|
"employees": [],
|
|
"escalation_rules": [],
|
|
}
|
|
if schema_version is not None:
|
|
payload = {"schema_version": schema_version, **payload}
|
|
(corporate_dir / "company_corporate_config.yaml").write_text(
|
|
yaml.dump(payload, default_flow_style=False, sort_keys=False),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def test_company_org_written_by_save(tmp_path, monkeypatch):
|
|
"""OPCConfig.save() must emit the corporate org config under company_orgs/."""
|
|
from opc.core import config as cfg_module
|
|
from opc.layer2_organization.company_runtime_profiles import get_builtin_roles
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
|
|
cfg = cfg_module.OPCConfig()
|
|
cfg.save()
|
|
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
index_path = config_dir / "company_index.yaml"
|
|
org_path = config_dir / "company_orgs" / "org_corporate_config.yaml"
|
|
assert org_path.exists(), "save() did not create corporate organization config"
|
|
assert not index_path.exists(), "save() should not create legacy company_index.yaml"
|
|
parsed = yaml.safe_load(org_path.read_text(encoding="utf-8"))
|
|
assert parsed.get("schema_version") == 2
|
|
assert parsed["organization_id"] == "corporate"
|
|
assert parsed["company"]["final_decider_role_id"] == "ceo"
|
|
assert [role["id"] for role in parsed["roles"]] == [role.id for role in get_builtin_roles("corporate")]
|
|
assert "corporate" in parsed["runtime_policies"]
|
|
assert parsed["employees"] == []
|
|
assert parsed["talent_templates"] == []
|
|
|
|
|
|
def test_load_rejects_future_schema(tmp_path, monkeypatch):
|
|
"""OPCConfig.load() must raise ValueError for schema_version > 1."""
|
|
from opc.core import config as cfg_module
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
corporate_dir = tmp_path / "config"
|
|
_write_minimal_corporate(corporate_dir, schema_version=99)
|
|
|
|
with pytest.raises(ValueError, match="schema_version 99"):
|
|
cfg_module.OPCConfig.load(corporate_dir)
|
|
|
|
|
|
def test_load_accepts_current_and_missing_schema_version(tmp_path, monkeypatch):
|
|
"""load() must silently accept schema_version: 1 AND legacy files missing the key."""
|
|
from opc.core import config as cfg_module
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
corporate_dir = tmp_path / "config"
|
|
|
|
# schema_version: 1 — explicit
|
|
_write_minimal_corporate(corporate_dir, schema_version=1)
|
|
cfg1 = cfg_module.OPCConfig.load(corporate_dir)
|
|
assert cfg1 is not None
|
|
|
|
# missing schema_version — legacy compatible
|
|
_write_minimal_corporate(corporate_dir, schema_version=None)
|
|
cfg2 = cfg_module.OPCConfig.load(corporate_dir)
|
|
assert cfg2 is not None
|
|
|
|
|
|
def test_load_uses_corporate_company_payload_even_when_legacy_index_points_custom(tmp_path, monkeypatch):
|
|
"""company_index.yaml must not let a saved custom org replace company mode."""
|
|
from opc.core import config as cfg_module
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
orgs_dir = config_dir / "company_orgs"
|
|
orgs_dir.mkdir(parents=True)
|
|
|
|
corporate_payload = {
|
|
"schema_version": 2,
|
|
"kind": "opc_org_architecture",
|
|
"organization_id": "corporate",
|
|
"organization_name": "Corporate",
|
|
"company": {
|
|
"name": "Corporate",
|
|
"company_profile": "corporate",
|
|
"execution_model": "actor_runtime",
|
|
"company_profiles": ["corporate", "custom"],
|
|
},
|
|
"roles": [],
|
|
"employees": [],
|
|
"escalation_rules": [],
|
|
}
|
|
quantum_payload = {
|
|
**corporate_payload,
|
|
"organization_id": "quantum_harbor",
|
|
"organization_name": "Quantum Harbor",
|
|
"company": {
|
|
**corporate_payload["company"],
|
|
"name": "Quantum Harbor",
|
|
"company_profile": "custom",
|
|
},
|
|
"roles": [{"id": "founder_ceo", "name": "Founder CEO", "responsibility": "Lead"}],
|
|
}
|
|
(orgs_dir / "org_corporate_config.yaml").write_text(
|
|
yaml.dump(corporate_payload, default_flow_style=False, sort_keys=False),
|
|
encoding="utf-8",
|
|
)
|
|
(orgs_dir / "org_quantum_harbor_config.yaml").write_text(
|
|
yaml.dump(quantum_payload, default_flow_style=False, sort_keys=False),
|
|
encoding="utf-8",
|
|
)
|
|
(config_dir / "company_index.yaml").write_text(
|
|
yaml.dump({"schema_version": 1, "active_organization_id": "quantum_harbor"}),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
cfg = cfg_module.OPCConfig.load(config_dir)
|
|
|
|
assert cfg.org.organization_id == "corporate"
|
|
assert cfg.org.company_profile == "corporate"
|
|
assert [role.id for role in cfg.org.roles] == []
|
|
|
|
|
|
def test_company_orgs_does_not_use_company_style_fallback_files(tmp_path, monkeypatch):
|
|
from opc.layer2_organization.company_runtime_profiles import get_builtin_roles
|
|
from opc.core import config as cfg_module
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
company_orgs_dir = config_dir / "company_orgs"
|
|
company_orgs_dir.mkdir(parents=True)
|
|
|
|
corporate_payload = {
|
|
"schema_version": 2,
|
|
"kind": "opc_org_architecture",
|
|
"organization_id": "corporate",
|
|
"organization_name": "Corporate",
|
|
"company": {
|
|
"name": "Corporate",
|
|
"company_profile": "corporate",
|
|
"execution_model": "actor_runtime",
|
|
"company_profiles": ["corporate", "custom"],
|
|
},
|
|
"roles": [],
|
|
"employees": [],
|
|
"escalation_rules": [],
|
|
}
|
|
corporate_payload["roles"] = [
|
|
{"id": "company_style_legacy_role", "name": "Legacy Role", "responsibility": "Should not load"},
|
|
]
|
|
(company_orgs_dir / "company_corporate_config.yaml").write_text(
|
|
yaml.dump(corporate_payload, default_flow_style=False, sort_keys=False),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
cfg = cfg_module.OPCConfig.load(config_dir)
|
|
|
|
assert cfg.org.organization_id == "corporate"
|
|
assert cfg.org.company_profile == "corporate"
|
|
role_ids = [role.id for role in cfg.org.roles]
|
|
assert role_ids == [role.id for role in get_builtin_roles("corporate")]
|
|
assert "company_style_legacy_role" not in role_ids
|
|
assert (company_orgs_dir / "org_corporate_config.yaml").exists()
|
|
|
|
|
|
def test_custom_org_save_writes_saved_org_storage_not_company_index(tmp_path, monkeypatch):
|
|
from opc.core import config as cfg_module
|
|
from opc.core.config import RoleConfig
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
cfg = cfg_module.OPCConfig()
|
|
cfg.org.organization_id = "lab"
|
|
cfg.org.organization_name = "Lab Org"
|
|
cfg.org.organization_config_file = "company_orgs/org_lab_config.yaml"
|
|
cfg.org.company_name = "Lab Org"
|
|
cfg.org.company_profile = "custom"
|
|
cfg.org.roles = [
|
|
RoleConfig(id="director", name="Director", responsibility="Own final decisions"),
|
|
]
|
|
|
|
cfg.save(config_dir)
|
|
|
|
assert (config_dir / "company_orgs" / "org_lab_config.yaml").exists()
|
|
assert yaml.safe_load((config_dir / "org_index.yaml").read_text(encoding="utf-8"))["active_organization_id"] == "lab"
|
|
assert not (config_dir / "company_index.yaml").exists()
|
|
assert not (config_dir / "company_orgs" / "company_lab_config.yaml").exists()
|
|
|
|
|
|
def test_org_payload_filters_runtime_placeholder_employees():
|
|
from opc.core.config import EmployeeConfig, OPCConfig, RoleConfig, build_company_org_payload_from_config
|
|
|
|
cfg = OPCConfig()
|
|
cfg.org.roles = [
|
|
RoleConfig(id="researcher", name="Researcher", responsibility="Research"),
|
|
]
|
|
cfg.org.employees = [
|
|
EmployeeConfig(employee_id="ava", name="Ava Chen", role_id="researcher"),
|
|
EmployeeConfig(
|
|
employee_id="researcher-default-employee",
|
|
template_id="general-default-employee",
|
|
name="Researcher Default Employee",
|
|
role_id="researcher",
|
|
metadata={
|
|
"is_default_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "system_default",
|
|
},
|
|
),
|
|
EmployeeConfig(
|
|
employee_id="researcher-fallback-empty-employee",
|
|
template_id="fallback-empty-employee",
|
|
name="Researcher Fallback Empty Employee",
|
|
role_id="researcher",
|
|
metadata={
|
|
"is_fallback_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "recruitment_fallback",
|
|
},
|
|
),
|
|
EmployeeConfig(
|
|
employee_id="saved-default",
|
|
template_id="general-default-employee",
|
|
name="Saved Default",
|
|
role_id="researcher",
|
|
metadata={
|
|
"is_default_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "system_default",
|
|
"persist_to_org": True,
|
|
},
|
|
),
|
|
]
|
|
|
|
payload = build_company_org_payload_from_config(cfg)
|
|
|
|
assert payload["employees"] == []
|
|
|
|
|
|
def test_custom_org_save_drops_runtime_placeholder_employees(tmp_path, monkeypatch):
|
|
from opc.core import config as cfg_module
|
|
from opc.core.config import EmployeeConfig, RoleConfig
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
cfg = cfg_module.OPCConfig()
|
|
cfg.org.organization_id = "lab"
|
|
cfg.org.organization_name = "Lab Org"
|
|
cfg.org.organization_config_file = "company_orgs/org_lab_config.yaml"
|
|
cfg.org.company_name = "Lab Org"
|
|
cfg.org.company_profile = "custom"
|
|
cfg.org.roles = [
|
|
RoleConfig(id="researcher", name="Researcher", responsibility="Research"),
|
|
]
|
|
cfg.org.employees = [
|
|
EmployeeConfig(employee_id="ava", name="Ava Chen", role_id="researcher"),
|
|
EmployeeConfig(
|
|
employee_id="researcher-default-employee",
|
|
template_id="general-default-employee",
|
|
name="Researcher Default Employee",
|
|
role_id="researcher",
|
|
metadata={
|
|
"is_default_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "system_default",
|
|
},
|
|
),
|
|
]
|
|
|
|
cfg.save(config_dir)
|
|
data = yaml.safe_load((config_dir / "company_orgs" / "org_lab_config.yaml").read_text(encoding="utf-8"))
|
|
|
|
assert data["employees"] == []
|
|
registry_path = tmp_path / ".opc" / "company_state" / "lab" / "employees" / "ava.yaml"
|
|
assert registry_path.exists()
|
|
|
|
|
|
def test_org_config_load_ignores_runtime_placeholder_employees():
|
|
from opc.core.config import OPCConfig
|
|
from opc.core.org_config import apply_org_config_payload_to_config
|
|
|
|
payload = {
|
|
"schema_version": 2,
|
|
"kind": "company_org",
|
|
"organization_id": "lab",
|
|
"organization_name": "Lab Org",
|
|
"company": {"name": "Lab Org", "company_profile": "custom"},
|
|
"roles": [{"id": "researcher", "name": "Researcher", "responsibility": "Research"}],
|
|
"employees": [
|
|
{"employee_id": "ava", "name": "Ava Chen", "role_id": "researcher"},
|
|
{
|
|
"employee_id": "researcher-default-employee",
|
|
"template_id": "general-default-employee",
|
|
"name": "Researcher Default Employee",
|
|
"role_id": "researcher",
|
|
"metadata": {
|
|
"is_default_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "system_default",
|
|
},
|
|
},
|
|
{
|
|
"employee_id": "saved-default",
|
|
"template_id": "general-default-employee",
|
|
"name": "Saved Default",
|
|
"role_id": "researcher",
|
|
"metadata": {
|
|
"is_default_employee": True,
|
|
"auto_created_for_role": "researcher",
|
|
"employee_origin": "system_default",
|
|
"persist_to_org": True,
|
|
},
|
|
},
|
|
],
|
|
"escalation_rules": [],
|
|
}
|
|
|
|
cfg = apply_org_config_payload_to_config(OPCConfig(), payload)
|
|
|
|
assert [employee.employee_id for employee in cfg.org.employees] == ["ava", "saved-default"]
|
|
|
|
|
|
def test_load_self_heals_legacy_company_org_employees_to_registry(tmp_path, monkeypatch):
|
|
from opc.core import config as cfg_module
|
|
|
|
monkeypatch.setattr(cfg_module, "_find_project_root", lambda: tmp_path)
|
|
config_dir = tmp_path / ".opc" / "config"
|
|
org_dir = config_dir / "company_orgs"
|
|
org_dir.mkdir(parents=True)
|
|
org_path = org_dir / "org_corporate_config.yaml"
|
|
org_path.write_text(
|
|
yaml.dump(
|
|
{
|
|
"schema_version": 2,
|
|
"kind": "opc_org_architecture",
|
|
"organization_id": "corporate",
|
|
"organization_name": "Corporate",
|
|
"company": {
|
|
"name": "Corporate",
|
|
"company_profile": "corporate",
|
|
"execution_model": "actor_runtime",
|
|
"company_profiles": ["corporate", "custom"],
|
|
},
|
|
"roles": [],
|
|
"employees": [
|
|
{
|
|
"employee_id": "ceo-finance-investment-analyst",
|
|
"template_id": "finance-investment-analyst",
|
|
"name": "Investment Analyst",
|
|
"role_id": "ceo",
|
|
"description": "Investment research specialist.",
|
|
"category": "finance",
|
|
}
|
|
],
|
|
"escalation_rules": [],
|
|
"runtime_policies": {},
|
|
"talent_templates": [{"id": "legacy-template", "name": "Legacy Template"}],
|
|
"teams": [],
|
|
"team_runtime": {},
|
|
"installed_packages": [],
|
|
"role_serial_queue_enabled": True,
|
|
},
|
|
default_flow_style=False,
|
|
sort_keys=False,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
loaded = cfg_module.OPCConfig.load(config_dir)
|
|
|
|
healed = yaml.safe_load(org_path.read_text(encoding="utf-8"))
|
|
assert healed["employees"] == []
|
|
assert healed["talent_templates"] == []
|
|
registry_path = tmp_path / ".opc" / "company_state" / "corporate" / "employees" / "finance-investment-analyst.yaml"
|
|
assert registry_path.exists()
|
|
assert loaded.org.employees[0].employee_id == "finance-investment-analyst"
|
|
assert loaded.org.employees[0].metadata["legacy_employee_ids"] == ["ceo-finance-investment-analyst"]
|
|
|
|
|
|
def test_export_yaml_roundtrips_roles_and_employees():
|
|
"""The export payload shape must preserve explicit org structure via YAML round-trip."""
|
|
from opc.core.config import OPCConfig, build_company_org_payload_from_config
|
|
from opc.layer2_organization.company_runtime_profiles import get_builtin_roles
|
|
|
|
cfg = OPCConfig()
|
|
corporate_data = build_company_org_payload_from_config(cfg)
|
|
exported = yaml.dump(corporate_data, default_flow_style=False, sort_keys=False)
|
|
parsed = yaml.safe_load(exported)
|
|
|
|
assert parsed["schema_version"] == 2
|
|
assert [role["id"] for role in parsed["roles"]] == [role.id for role in get_builtin_roles("corporate")]
|
|
assert parsed["company"]["final_decider_role_id"] == "ceo"
|
|
assert "corporate" in parsed["runtime_policies"]
|
|
assert parsed["employees"] == []
|
|
assert parsed["talent_templates"] == []
|
|
assert parsed["escalation_rules"] == [e.model_dump() for e in cfg.org.escalation_rules]
|
|
|
|
|
|
def test_custom_org_payload_materializes_effective_policy_when_empty():
|
|
from opc.core.config import OPCConfig, RoleConfig, build_company_org_payload_from_config
|
|
|
|
cfg = OPCConfig()
|
|
cfg.org.organization_id = "hkuds"
|
|
cfg.org.organization_name = "HKUDS"
|
|
cfg.org.company_name = "HKUDS"
|
|
cfg.org.company_profile = "custom"
|
|
cfg.org.final_decider_role_id = "chao"
|
|
cfg.org.roles = [
|
|
RoleConfig(id="chao", name="Chao", responsibility="leader", reports_to="owner"),
|
|
RoleConfig(id="zongwei", name="Zongwei", responsibility="student", reports_to="chao"),
|
|
]
|
|
cfg.org.runtime_policies = {}
|
|
|
|
payload = build_company_org_payload_from_config(
|
|
cfg,
|
|
organization_id="hkuds",
|
|
organization_name="HKUDS",
|
|
force_profile="custom",
|
|
)
|
|
|
|
assert [role["id"] for role in payload["roles"]] == ["chao", "zongwei"]
|
|
assert payload["company"]["final_decider_role_id"] == "chao"
|
|
assert "custom" in payload["runtime_policies"]
|
|
assert payload["runtime_policies"]["custom"]["gate_harness"]["decision_mode"] == "hybrid"
|
|
assert payload["employees"] == []
|
|
assert payload["talent_templates"] == []
|