افزودن ارائه‌دهندهٔ agy (Google Antigravity) با لاگین headless و پول چرخشی اکانت

- Adapter اکانت‌محور با چرخش round-robin و بنچ‌شدن خودکار روی ۴۲۹/۴۰۱
- ورود ادمین از طریق URL بازگشتی گوگل (PKCE) و ذخیرهٔ رمزنگاری‌شدهٔ refresh token
- صفحهٔ مدیریت اکانت‌های agy در پنل ادمین به همراه تست و فعال/غیرفعال‌سازی
- مهاجرت Alembic b1c2d3e4f5a6 برای ستون‌های اکانت agy
- seed همیشه کاتالوگ agy را می‌سازد و فقط با AI_DEFAULT_PROVIDER=agy مسیریابی می‌شود
This commit is contained in:
mjalmoudy94
2026-10-13 08:20:27 +03:30
parent 54074f10c3
commit 3d1e98f4fb
25 changed files with 3073 additions and 10 deletions
+57
View File
@@ -111,3 +111,60 @@ describe("error console client", () => {
expect(calls[0]).not.toContain("?");
});
});
describe("agy account client", () => {
afterEach(() => {
vi.unstubAllGlobals();
});
function stubJson(body: unknown) {
const calls: { method: string; url: string; body: string }[] = [];
vi.stubGlobal(
"fetch",
vi.fn(async (url: string, init?: RequestInit) => {
calls.push({
method: init?.method ?? "GET",
url,
body: String(init?.body ?? ""),
});
return new Response(JSON.stringify(body), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
);
return calls;
}
it("posts to the login start endpoint", async () => {
const calls = stubJson({ login_id: "l1", auth_url: "https://accounts.google.com/..." });
await api.agyStartLogin();
expect(calls[0].method).toBe("POST");
expect(calls[0].url).toContain("/api/admin/agy/accounts/login/start");
});
it("sends the pasted callback URL back verbatim", async () => {
const calls = stubJson({ id: "a1", email: "x@gmail.com" });
await api.agyCompleteLogin({
login_id: "l1",
callback_url: "http://localhost:51121/oauth-callback?code=c&state=s",
});
expect(calls[0].url).toContain("/api/admin/agy/accounts/login/complete");
expect(calls[0].body).toContain("callback_url");
});
it("routes activate/deactivate to the right verb", async () => {
const calls = stubJson({ id: "a1" });
await api.agySetAccountActive("a1", false);
await api.agySetAccountActive("a1", true);
expect(calls[0].url).toContain("/api/admin/agy/accounts/a1/deactivate");
expect(calls[1].url).toContain("/api/admin/agy/accounts/a1/activate");
});
it("deletes with the DELETE verb", async () => {
const calls = stubJson(null);
await api.agyDeleteAccount("a1");
expect(calls[0].method).toBe("DELETE");
expect(calls[0].url).toContain("/api/admin/agy/accounts/a1");
});
});