diff --git a/backend/app/routers/websites.py b/backend/app/routers/websites.py index de5234e..0808731 100644 --- a/backend/app/routers/websites.py +++ b/backend/app/routers/websites.py @@ -558,6 +558,11 @@ def import_upstream_keys_as_accounts( old_account_id = row.imported_account_id exists = c.account_exists(row.imported_account_id) if exists is True: + # 顺手回填 imported_target_group_id(老数据升级后可通过重导自动补齐) + new_tgid = body.target_group_map.get(row.group_id) or None + if new_tgid and row.imported_target_group_id != new_tgid: + row.imported_target_group_id = new_tgid + db.commit() items.append(ImportAccountItem( upstream_key_id=row.id, source_group_id=row.group_id, diff --git a/backend/app/services/website_sync.py b/backend/app/services/website_sync.py index a02320f..dd92979 100644 --- a/backend/app/services/website_sync.py +++ b/backend/app/services/website_sync.py @@ -372,11 +372,19 @@ def sync_account_priorities_for_upstream(db: Session, upstream_id: int) -> list[ # priority_assignment: account_id → new_priority priority_assignment: dict[str, int] = {} for comp_key, comp_rows in competitive_buckets.items(): - # 取每行的倍率(查不到则 fallback 1.0) + # 只保留快照中能查到倍率的账号;无数据的账号不参与排序 rated = [ - (row, raw_rate_map.get(f"{row.upstream_id}:{row.group_id}", 1.0)) + (row, raw_rate_map[f"{row.upstream_id}:{row.group_id}"]) for row in comp_rows + if f"{row.upstream_id}:{row.group_id}" in raw_rate_map ] + # 过滤后有效账号不足 2 个 → 此分组无竞争意义,整组跳过 + if len(rated) < 2: + logger.info( + "skip competitive bucket %s for website %s: only %d account(s) have rate data", + comp_key, wid, len(rated), + ) + continue # 组内按倍率升序排序(倍率低 → priority 小 → 优先) unique_rates = sorted(set(r for _, r in rated)) rate_to_prio = {rate: idx + 1 for idx, rate in enumerate(unique_rates)} diff --git a/backend/test_priority_sync.py b/backend/test_priority_sync.py index 931133f..8cdacf9 100644 --- a/backend/test_priority_sync.py +++ b/backend/test_priority_sync.py @@ -282,6 +282,63 @@ def test_single_account_in_mixed_website(db_session, monkeypatch): assert updated_ids == {"A1", "A2"} +def test_missing_rate_skips_entire_competitive_group(db_session, monkeypatch): + """竞争分组内所有账号均无快照倍率 → 有效账号 < 2 → 整组跳过,不调用 update_account。""" + w = Website(name="W1", base_url="http://w1", enabled=True, + auth_config_json="{}", timeout_seconds=30) + u1 = Upstream(name="U1", base_url="http://u1") + db_session.add_all([w, u1]) + db_session.commit() + db_session.refresh(w); db_session.refresh(u1) + + # 故意不插快照 → raw_rate_map 为空 + _make_key(db_session, u1.id, "G1", "K1", "V1", w.id, "A1", imported_target_group_id="TG1") + _make_key(db_session, u1.id, "G2", "K2", "V2", w.id, "A2", imported_target_group_id="TG1") + + update_calls = [] + monkeypatch.setattr( + "app.services.website_sync.Sub2ApiWebsiteClient", + make_mock_client(update_calls), + ) + + results = sync_account_priorities_for_upstream(db_session, u1.id) + + assert update_calls == [], "无快照倍率时不应调用 update_account" + assert results == [] + + +def test_partial_missing_rate_sufficient_accounts_still_updates(db_session, monkeypatch): + """竞争分组内 3 账号,1 个无快照倍率,剩余 2 个有倍率 → 仍有竞争,2 个有倍率的账号正常更新。""" + w = Website(name="W1", base_url="http://w1", enabled=True, + auth_config_json="{}", timeout_seconds=30) + u1 = Upstream(name="U1", base_url="http://u1") + db_session.add_all([w, u1]) + db_session.commit() + db_session.refresh(w); db_session.refresh(u1) + + # G1、G2 有快照,G3 没有快照 + _make_snapshot(db_session, u1.id, {"G1": 1.0, "G2": 2.0}) + + _make_key(db_session, u1.id, "G1", "K1", "V1", w.id, "A1", imported_target_group_id="TG1") + _make_key(db_session, u1.id, "G2", "K2", "V2", w.id, "A2", imported_target_group_id="TG1") + _make_key(db_session, u1.id, "G3", "K3", "V3", w.id, "A3", imported_target_group_id="TG1") + + update_calls = [] + monkeypatch.setattr( + "app.services.website_sync.Sub2ApiWebsiteClient", + make_mock_client(update_calls), + ) + + sync_account_priorities_for_upstream(db_session, u1.id) + + updated = {c[0]: c[1]["priority"] for c in update_calls} + # A3 无快照 → 不参与排序,不被更新 + assert "A3" not in updated + # A1(G1, rate=1.0) → priority=1;A2(G2, rate=2.0) → priority=2 + assert updated["A1"] == 1 + assert updated["A2"] == 2 + + def test_priority_sync_log_structure(db_session, monkeypatch): """日志写入格式验证(需要竞争分组才会写日志)。""" w = Website(name="W1", base_url="http://w1", enabled=True,