"""「这个组织现在能不能自动操作」的三种答案。 这一层的价值在于**上层不必各自判断**。所以测试钉的是:每种失配都产生一个 可以直接说给人听的原因,而不是抛一个异常让调用方自己去分类 —— 凭证没配好 是一个预期中的运行状态(登记先行、凭证后到是正常上线顺序),不是错误。 """ from __future__ import annotations import uuid import pytest from app.domains.github_org import driver_access as da from app.domains.github_org import service as svc from app.integrations.models import Integration pytestmark = pytest.mark.asyncio async def _org(db, **kw): payload = {"slug": "pingcap-inc", "display_name": "PingCAP Inc"} payload.update(kw) org = await svc.create_org(db, **payload) await db.commit() return org def _integration(**kw) -> Integration: payload = { "id": uuid.uuid4(), "kind": "github_org", "provider": "github", "name": "GitHub 组织", "slug": f"gh-org-{uuid.uuid4().hex[:8]}", "enabled": True, "config_public": {"auth_mode": "pat"}, } payload.update(kw) return Integration(**payload) async def test_no_integration_is_not_configured(db_session): org = await _org(db_session) res = await da.resolve_org_driver(db_session, org) assert res.automatic is False assert res.reason == da.REASON_NOT_CONFIGURED assert "no enabled GitHub organization integration" in (res.detail or "") async def test_single_enabled_integration_is_used_without_pinning(db_session): """只有一个凭证时不该逼每个组织都去填一遍它 —— 那是在制造漂移。""" db_session.add(_integration()) await db_session.commit() org = await _org(db_session) res = await da.resolve_org_driver(db_session, org) assert res.automatic is True assert res.driver is not None async def test_two_enabled_integrations_refuse_to_guess(db_session): """两个凭证覆盖不同组织集合是正常配置。替管理员挑一个,挑错的后果是 往错误的组织里加人 —— 所以这里明确不猜。""" db_session.add(_integration()) db_session.add(_integration()) await db_session.commit() org = await _org(db_session) res = await da.resolve_org_driver(db_session, org) assert res.automatic is False assert res.reason == da.REASON_NOT_CONFIGURED assert "does not say which one" in (res.detail or "") async def test_pinned_integration_wins_over_the_ambient_one(db_session): pinned = _integration(name="pinned") db_session.add(pinned) db_session.add(_integration(name="other")) await db_session.commit() org = await _org(db_session, integration_id=str(pinned.id)) res = await da.resolve_org_driver(db_session, org) assert res.automatic is True async def test_disabled_integration_is_its_own_reason(db_session): """与「没配」分开:管理员看到的处置动作不同(去启用 vs 去登记)。""" row = _integration(enabled=False) db_session.add(row) await db_session.commit() org = await _org(db_session, integration_id=str(row.id)) res = await da.resolve_org_driver(db_session, org) assert res.automatic is False assert res.reason == da.REASON_DISABLED async def test_wrong_kind_resolves_to_broken_not_to_a_wrong_driver(db_session): """集成行的 kind 被改成别的东西 —— 绝不能拿着一个不相干的驱动去 调用组织成员接口。""" row = _integration( kind="software_provisioning", config_public={"org_slug": "x"}, ) db_session.add(row) await db_session.commit() org = await _org(db_session, integration_id=str(row.id)) res = await da.resolve_org_driver(db_session, org) assert res.automatic is False assert res.reason == da.REASON_BROKEN assert "not a GitHub organization driver" in (res.detail or "") async def test_dangling_integration_pointer_is_reported_not_crashed(db_session): """指向一个不存在的集成 —— 纵深防御,不是一个正常可达的状态。 ``github_orgs.integration_id`` 上有 FK 且是 ``ON DELETE SET NULL``, 所以库里存不出这种行(试过:1452)。分支仍然保留,因为 ``db.get()`` 返回 None 之后紧跟着就是 ``row.enabled`` —— 少这个判断, 任何一次 FK 未生效的场合(例如建表期间 ``FOREIGN_KEY_CHECKS=0``、或 一次手工数据订正)换来的是 AttributeError 而不是一句话。 因此这里**在内存里**构造该状态,并如实记下它在生产上走不到。 """ org = await _org(db_session) org.integration_id = str(uuid.uuid4()) # ``no_autoflush``:解析器内部的第一次 SELECT 会触发 autoflush,把这个 # 只该活在内存里的值推给 FK 去拒。 with db_session.no_autoflush: res = await da.resolve_org_driver(db_session, org) db_session.expunge(org) # 别让它污染同会话的后续断言 assert res.automatic is False assert res.reason == da.REASON_NOT_CONFIGURED assert "no longer exists" in (res.detail or "") # ── 契约:自动化状态必须出现在管理面上 ────────────────────────── async def test_org_list_surfaces_automation_readiness( client, github_admin_headers, db_session, ): """未就绪必须在**列表上**看得见,而不是等第一次真实邀请失败才发现。 这条钉的是契约本身:三个字段都在,而且未就绪时带着一句能直接说给 管理员听的话。 """ await _org(db_session) resp = await client.get("/api/v1/github/orgs", headers=github_admin_headers) assert resp.status_code == 200 row = resp.json()["data"][0] assert row["automation_ready"] is False assert row["automation_reason"] == da.REASON_NOT_CONFIGURED assert row["automation_detail"] async def test_org_list_flips_to_ready_once_a_credential_exists( client, github_admin_headers, db_session, ): db_session.add(_integration()) await db_session.commit() await _org(db_session) resp = await client.get("/api/v1/github/orgs", headers=github_admin_headers) row = resp.json()["data"][0] assert row["automation_ready"] is True assert row["automation_reason"] is None