feat(adapters): 接入 CloudCone/Zeabur/Dynadot/新网/Agnes 五个平台
线上平台列表里 cloudcone、zeabur、xinwang、dynadot、agnes 的 sdk_type 仍为空, 平台卡片只能显示「未对接」,用户无法同步。按各自真实 API 能力补齐: - CloudCone:实例清单 + 开机/关机/重启;无公开账单接口,不声明余额与续费 - Zeabur:服务器清单 + 重启;毫核/MB 归一化为核/GB,未采集时不伪造规格 - Dynadot:域名清单(含到期日)+ 账户余额;set_dns2 位置参数无法安全 CRUD - 新网:代理 API 按域名逐个查到期日;无列表接口,需在账号里填 domains 清单 - Agnes:/models 校验 Key;无余额接口,如实标记不支持 配套改动: - _align_preset_providers 为库中为空的 sdk_type 回填种子值(启动即生效), 避免新适配器上线后线上仍停留在「未对接」 - 适配器可声明 plain_config,代理号/域名等非密钥字段前端按明文输入 - PRESET_PROVIDERS 五条记录填上对应 sdk_type 测试:445 passed(新增 tests/test_platform_adapters.py),vitest 62 passed。
This commit is contained in:
@@ -26,8 +26,9 @@ from app.core.seed import PRESET_PROVIDERS, seed_providers
|
||||
from app.database import _align_preset_providers
|
||||
from app.models.provider import Provider
|
||||
|
||||
#: 线上 GET /api/providers 抓下来的真实状态(2026-10-11),
|
||||
#: 用它做 fixture 才能保证迁移验证的是生产形态而不是理想形态
|
||||
#: 线上 GET /api/providers 抓下来的真实状态(2026-10-26),
|
||||
#: 用它做 fixture 才能保证迁移验证的是生产形态而不是理想形态。
|
||||
#: cloudcone/zeabur/dynadot/xinwang/agnes 上线前都是 None,靠启动迁移补 sdk_type。
|
||||
LIVE_PROVIDERS = [
|
||||
("agnes", None, "ai_agent"),
|
||||
("deepseek", "deepseek-api", "ai_agent"),
|
||||
@@ -35,19 +36,19 @@ LIVE_PROVIDERS = [
|
||||
("minimax", "minimax-api", "ai_agent"),
|
||||
("openai", "openai-api", "ai_agent"),
|
||||
("cloudflare", "cloudflare-api", "cloudflare,dns,ssl_cert,cdn"),
|
||||
("dynadot", "dynadot-api", "domain,dns"),
|
||||
("dynadot", None, "domain,dns"),
|
||||
("namesilo", "namesilo-api", "domain,dns"),
|
||||
("xinwang", None, "domain,dns"),
|
||||
("aliyun", "aliyun-sdk", "vps,domain,ssl_cert,cdn,dns"),
|
||||
("aliyun-intl", "alibabacloud-sdk", "vps,domain,ssl_cert,cdn,dns,ai_agent,other"),
|
||||
("cloudcone", "cloudcone-api", "vps"),
|
||||
("cloudcone", None, "vps"),
|
||||
("digitalocean", "do-api", "vps,domain"),
|
||||
("linode", "linode-api", "vps"),
|
||||
("tencent", "tencent-sdk", "vps,domain,ssl_cert,cdn,dns"),
|
||||
("tencent-intl", "tencent-intl-sdk", "vps,domain,ssl_cert,cdn,dns"),
|
||||
("tothost", None, "vps"),
|
||||
("tothost", "whmcs-panel", "vps"),
|
||||
("vultr", "vultr-api", "vps"),
|
||||
("zeabur", "zeabur-api", "vps,domain"),
|
||||
("zeabur", None, "vps,domain"),
|
||||
]
|
||||
|
||||
|
||||
@@ -118,11 +119,25 @@ def test_seed_providers_is_idempotent(live_db):
|
||||
|
||||
# ----------------------------- 迁移行为 ----------------------------- #
|
||||
def test_migration_clears_bogus_sdk_type(live_db):
|
||||
"""cloudcone/zeabur/dynadot 声明的适配器从未实现,必须清空"""
|
||||
"""库里声明了注册表没有的 sdk_type(旧版本残留)必须清空"""
|
||||
with live_db.begin() as conn:
|
||||
conn.execute(sa.text("UPDATE providers SET sdk_type = 'vultr-v1-removed' WHERE slug = 'vultr'"))
|
||||
_run_migration(live_db)
|
||||
assert _snapshot(live_db)["vultr"][0] is None
|
||||
|
||||
|
||||
def test_migration_backfills_missing_sdk_type(live_db):
|
||||
"""新增适配器上线后,库里 sdk_type 为空的预设平台必须补上,否则卡片永远不可同步"""
|
||||
_run_migration(live_db)
|
||||
snap = _snapshot(live_db)
|
||||
for slug in ("cloudcone", "zeabur", "dynadot"):
|
||||
assert snap[slug][0] is None, f"{slug}.sdk_type 未清空:{snap[slug][0]!r}"
|
||||
for slug, expected in (
|
||||
("cloudcone", "cloudcone-api"),
|
||||
("zeabur", "zeabur-api"),
|
||||
("dynadot", "dynadot-api"),
|
||||
("xinwang", "xinnet-api"),
|
||||
("agnes", "agnes-api"),
|
||||
):
|
||||
assert snap[slug][0] == expected, f"{slug}.sdk_type 未回填:{snap[slug][0]!r}"
|
||||
|
||||
|
||||
def test_migration_keeps_valid_sdk_type(live_db):
|
||||
@@ -155,10 +170,13 @@ def test_migration_adds_missing_service_tags(live_db):
|
||||
|
||||
|
||||
def test_migration_ignores_user_created_providers(live_db):
|
||||
"""tothost 不在种子里,一个字都不能动"""
|
||||
before = _snapshot(live_db)["tothost"]
|
||||
"""用户自建平台不在种子里,一个字都不能动"""
|
||||
with Session(live_db) as s:
|
||||
s.add(Provider(slug="myhost", name="MyHost", category="vps", services="vps"))
|
||||
s.commit()
|
||||
before = _snapshot(live_db)["myhost"]
|
||||
_run_migration(live_db)
|
||||
assert _snapshot(live_db)["tothost"] == before
|
||||
assert _snapshot(live_db)["myhost"] == before
|
||||
|
||||
|
||||
def test_migration_is_idempotent(live_db):
|
||||
|
||||
Reference in New Issue
Block a user