mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(quota): pool PATCH prunes OLD group/provider combos before re-sync (no orphan qtSd/ on switch)
This commit is contained in:
@@ -65,19 +65,35 @@ export async function PATCH(request: Request, { params }: RouteParams): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
// Combos must be reconciled when the pool's group OR connection set changes,
|
||||
// since the qtSd/ combo name embeds <groupSlug>/<provider>. Each provider in a
|
||||
// group is served by at most one pool, so removing this pool's CURRENT (old)
|
||||
// group+provider combos BEFORE the update — then re-syncing AFTER — cleanly
|
||||
// drops stale combos and mints the new ones, using the existing per-pool
|
||||
// helpers. Without the pre-update removal, a group/provider switch would leave
|
||||
// orphan qtSd/ combos a quota key still sees. Guarded + non-fatal.
|
||||
const combosNeedResync =
|
||||
body !== null &&
|
||||
typeof body === "object" &&
|
||||
("connectionIds" in body || "groupId" in body);
|
||||
if (combosNeedResync) {
|
||||
try {
|
||||
const { removeQuotaCombosForPool } = await import("@/lib/quota/quotaCombos");
|
||||
await removeQuotaCombosForPool(id); // pool still has its OLD group/provider here
|
||||
} catch {
|
||||
// Guard: combo cleanup failure must never break pool update.
|
||||
}
|
||||
}
|
||||
|
||||
const pool = updatePool(id, parsed.data);
|
||||
if (!pool) {
|
||||
return NextResponse.json(buildErrorBody(404, "Pool not found"), { status: 404 });
|
||||
}
|
||||
|
||||
// Re-sync quota combos when connectionIds was explicitly changed.
|
||||
// updatePool already fires a guarded sync internally, but we add an explicit
|
||||
// awaited sync here so the caller's response reflects the updated combo state.
|
||||
// Mirrors the dynamic-import + non-fatal pattern from groups/[id]/route.ts PATCH.
|
||||
if (body !== null && typeof body === "object" && "connectionIds" in body) {
|
||||
if (combosNeedResync) {
|
||||
try {
|
||||
const { syncQuotaCombos } = await import("@/lib/quota/quotaCombos");
|
||||
await syncQuotaCombos(id);
|
||||
await syncQuotaCombos(id); // pool now has its NEW group/provider
|
||||
} catch {
|
||||
// Guard: combo-sync failure must never break pool update callers.
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ const poolsDb = await import("../../src/lib/db/quotaPools.ts");
|
||||
const providersDb = await import("../../src/lib/db/providers.ts");
|
||||
const combosDb = await import("../../src/lib/db/combos.ts");
|
||||
const { createGroup } = await import("../../src/lib/db/quotaGroups.ts");
|
||||
const { syncQuotaCombos } = await import("../../src/lib/quota/quotaCombos.ts");
|
||||
const { syncQuotaCombos, removeQuotaCombosForPool } = await import(
|
||||
"../../src/lib/quota/quotaCombos.ts"
|
||||
);
|
||||
const { PoolUpdateSchema } = await import("../../src/shared/schemas/quota.ts");
|
||||
const { isQuotaModelName, parseQuotaModelName, quotaGroupSlug } = await import(
|
||||
"../../src/lib/quota/quotaModelNaming.ts"
|
||||
@@ -178,6 +180,52 @@ test("updatePool with new connectionIds triggers combo re-sync (openrouter → b
|
||||
assert.deepEqual(reread.connectionIds, [idB], "pool.connectionIds must contain only baidu conn");
|
||||
});
|
||||
|
||||
test("PATCH route sequence (remove→update→sync) prunes OLD-provider combos on switch", async () => {
|
||||
const group = createGroup("RouteSwitchGroup");
|
||||
const groupSlug = quotaGroupSlug(group.name);
|
||||
|
||||
const connA = await providersDb.createProviderConnection({
|
||||
provider: "openrouter",
|
||||
authType: "apikey",
|
||||
name: "rt-or-conn",
|
||||
apiKey: "sk-rt-or",
|
||||
});
|
||||
const idA = (connA as Record<string, unknown>).id as string;
|
||||
const connB = await providersDb.createProviderConnection({
|
||||
provider: "baidu",
|
||||
authType: "apikey",
|
||||
name: "rt-baidu-conn",
|
||||
apiKey: "sk-rt-baidu",
|
||||
});
|
||||
const idB = (connB as Record<string, unknown>).id as string;
|
||||
|
||||
const pool = poolsDb.createPool({ connectionId: idA, name: "Route Switch", groupId: group.id });
|
||||
await syncQuotaCombos(pool.id);
|
||||
assert.ok(
|
||||
(await listQuotaCombos()).some((c) => parseQuotaModelName(c.name)?.provider === "openrouter"),
|
||||
"precondition: openrouter combos exist"
|
||||
);
|
||||
|
||||
// Mirror the PATCH route's connection/group-change path exactly:
|
||||
await removeQuotaCombosForPool(pool.id); // pool still has OLD (openrouter) provider here
|
||||
poolsDb.updatePool(pool.id, { connectionIds: [idB] });
|
||||
await syncQuotaCombos(pool.id); // pool now has NEW (baidu) provider
|
||||
|
||||
const after = await listQuotaCombos();
|
||||
const orphans = after.filter((c) => {
|
||||
const p = parseQuotaModelName(c.name);
|
||||
return p?.groupSlug === groupSlug && p?.provider === "openrouter";
|
||||
});
|
||||
assert.equal(orphans.length, 0, `OLD openrouter combos must be pruned; found ${orphans.length}`);
|
||||
assert.ok(
|
||||
after.some((c) => {
|
||||
const p = parseQuotaModelName(c.name);
|
||||
return p?.groupSlug === groupSlug && p?.provider === "baidu";
|
||||
}),
|
||||
"NEW baidu combos must exist after the switch"
|
||||
);
|
||||
});
|
||||
|
||||
test("updatePool with groupId persists the new group assignment", () => {
|
||||
const groupA = createGroup("GroupAlpha");
|
||||
const groupB = createGroup("GroupBeta");
|
||||
|
||||
Reference in New Issue
Block a user