feat: proxy change-IP link

This commit is contained in:
zhom committed 2026-10-12 00:32:08 +04:00
1 parent 69d3c90a7c
commit 3f07a41fac
25 files changed
+925 -51

No files matched your search

+3
View File
@@ -98,6 +98,9 @@ export const commandCoverage = {
"create_stored_proxy",
"get_stored_proxies",
"update_stored_proxy",
"proxy_rotation::set_proxy_rotation_url",
"proxy_rotation::rotate_proxy_ip",
"proxy_rotation::rotate_proxy_ips",
"delete_stored_proxy",
"check_proxy_validity",
"validate_vless_uri",
+41
View File
@@ -88,6 +88,47 @@ test("profile, group, proxy, tag, metadata, clone, and bulk-delete lifecycle", a
assert.equal(updatedProxy.name, "Updated Proxy");
assert.equal(updatedProxy.updated_at >= proxy.updated_at, true);
// The provider's change-IP link: checked when set, called on request.
assert.match(
await app.invokeError("rotate_proxy_ip", { proxyId: proxy.id }),
/PROXY_ROTATION_URL_MISSING/,
);
assert.match(
await app.invokeError("set_proxy_rotation_url", {
proxyId: proxy.id,
rotationUrl: "ftp://provider.example/change-ip",
}),
/PROXY_ROTATION_URL_INVALID/,
);
const rotationUrl = `${process.env.DONUT_E2E_FIXTURE_URL}/change-ip?key=e2e`;
const linked = await app.invoke("set_proxy_rotation_url", {
proxyId: proxy.id,
rotationUrl: ` ${rotationUrl} `,
});
assert.equal(linked.rotation_url, rotationUrl);
const rotated = await app.invoke("rotate_proxy_ip", {
proxyId: proxy.id,
});
assert.equal(rotated.status, 200);
assert.equal(rotated.error, null);
const rotations = await app.invoke("rotate_proxy_ips", {
proxyIds: [proxy.id, proxy.id, "missing-proxy"],
});
assert.deepEqual(
rotations.map((result) => [result.proxy_id, result.status]),
[
[proxy.id, 200],
["missing-proxy", null],
],
"duplicates are called once and each proxy gets its own result",
);
assert.match(rotations[1].error, /PROXY_NOT_FOUND/);
const unlinked = await app.invoke("set_proxy_rotation_url", {
proxyId: proxy.id,
rotationUrl: "",
});
assert.equal(unlinked.rotation_url, null);
const parsed = await app.invoke("parse_txt_proxies", {
content: [
"http://one.example:8080",
+52
View File
@@ -839,6 +839,58 @@ test("profile table filters, range selection, view settings, and keyboard edits
);
});
test("a proxy with a change-IP link offers Change IP on its row", async () => {
await withApp("ui-proxy-rotation", async (app) => {
const linked = await app.invoke("create_stored_proxy", {
name: "Rotating mobile proxy",
proxySettings: {
proxy_type: "http",
host: "127.0.0.1",
port: 9,
username: null,
password: null,
},
});
await app.invoke("set_proxy_rotation_url", {
proxyId: linked.id,
rotationUrl: `${process.env.DONUT_E2E_FIXTURE_URL}/change-ip?key=ui`,
});
const plain = await app.invoke("create_stored_proxy", {
name: "Static proxy",
proxySettings: {
proxy_type: "http",
host: "127.0.0.1",
port: 9,
username: null,
password: null,
},
});
await app.pressShortcut({
key: "n",
...(process.platform === "darwin" ? { meta: true } : { ctrl: true }),
});
await app.waitFor(
async () => (await tableRows(app, "proxies")).length === 2,
{ description: "proxy table" },
);
const rotateIn = (id) =>
`[data-table-id="proxies"] tr[data-table-row="${id}"] [data-slot="proxy-rotate"]`;
assert.equal(
await app.execute(
"return Boolean(document.querySelector(arguments[0]));",
[rotateIn(plain.id)],
),
false,
"a proxy without a link has no Change IP action",
);
await app.clickSelector(rotateIn(linked.id));
await app.waitForText(
en.proxies.rotation.requested.replace("{{name}}", linked.name),
);
await app.capture("proxy-rotation-requested");
});
});
test("management tables share search, filters, selection, and saved views", async () => {
await withApp("ui-management-tables", async (app) => {
const proxies = [];