mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-12 12:49:14 +02:00
769 lines
21 KiB
TypeScript
769 lines
21 KiB
TypeScript
/**
|
|
* Every client method sends exactly the request the app documents.
|
|
*
|
|
* The table below is the whole public surface. Each row names a method, the
|
|
* arguments to call it with, and the request that must appear on the wire: the
|
|
* verb, the concrete path, the query string and the JSON body. `operation` is
|
|
* the path template the app publishes, which ties this file to
|
|
* `OPERATIONS` and, through it, to `sdk/api-paths.json`.
|
|
*/
|
|
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import { OPERATIONS } from "../src/index.mts";
|
|
import { withClient } from "./support.mts";
|
|
|
|
interface Case {
|
|
method: string;
|
|
args: unknown[];
|
|
verb: string;
|
|
path: string;
|
|
body: unknown;
|
|
query?: Record<string, string>;
|
|
operation: string;
|
|
}
|
|
|
|
const LOCATOR = { role: "button", name: "Sign in" };
|
|
|
|
const CASES: Case[] = [
|
|
// -- profiles ------------------------------------------------------------
|
|
{
|
|
method: "listProfiles",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/profiles",
|
|
body: null,
|
|
operation: "GET /v1/profiles",
|
|
},
|
|
{
|
|
method: "getProfile",
|
|
args: ["p1"],
|
|
verb: "GET",
|
|
path: "/v1/profiles/p1",
|
|
body: null,
|
|
operation: "GET /v1/profiles/{id}",
|
|
},
|
|
{
|
|
method: "createProfile",
|
|
args: [{ name: "Shopper", browser: "wayfern", tags: ["eu"], ephemeral: true }],
|
|
verb: "POST",
|
|
path: "/v1/profiles",
|
|
body: { name: "Shopper", browser: "wayfern", tags: ["eu"], ephemeral: true },
|
|
operation: "POST /v1/profiles",
|
|
},
|
|
{
|
|
method: "createProfile",
|
|
args: [{ name: "Bare", browser: "wayfern", version: undefined }],
|
|
verb: "POST",
|
|
path: "/v1/profiles",
|
|
body: { name: "Bare", browser: "wayfern" },
|
|
operation: "POST /v1/profiles",
|
|
},
|
|
{
|
|
method: "updateProfile",
|
|
args: ["p1", { name: "Renamed", proxy_id: "", clear_on_close: false }],
|
|
verb: "PUT",
|
|
path: "/v1/profiles/p1",
|
|
body: { name: "Renamed", proxy_id: "", clear_on_close: false },
|
|
operation: "PUT /v1/profiles/{id}",
|
|
},
|
|
{
|
|
method: "deleteProfile",
|
|
args: ["p1"],
|
|
verb: "DELETE",
|
|
path: "/v1/profiles/p1",
|
|
body: null,
|
|
operation: "DELETE /v1/profiles/{id}",
|
|
},
|
|
{
|
|
method: "runProfile",
|
|
args: ["p1", { url: "https://example.com", headless: true }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/run",
|
|
body: { url: "https://example.com", headless: true },
|
|
operation: "POST /v1/profiles/{id}/run",
|
|
},
|
|
{
|
|
method: "runProfileRemote",
|
|
args: ["p1", { url: "https://example.com" }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/run-remote",
|
|
body: { url: "https://example.com" },
|
|
operation: "POST /v1/profiles/{id}/run-remote",
|
|
},
|
|
{
|
|
method: "setProfileCloudSync",
|
|
args: ["p1", "Regular"],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/cloud-sync",
|
|
body: { mode: "Regular" },
|
|
operation: "POST /v1/profiles/{id}/cloud-sync",
|
|
},
|
|
{
|
|
method: "openUrl",
|
|
args: ["p1", "https://example.com/page"],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/open-url",
|
|
body: { url: "https://example.com/page" },
|
|
operation: "POST /v1/profiles/{id}/open-url",
|
|
},
|
|
{
|
|
method: "killProfile",
|
|
args: ["p1"],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/kill",
|
|
body: null,
|
|
operation: "POST /v1/profiles/{id}/kill",
|
|
},
|
|
{
|
|
method: "batchRunProfiles",
|
|
args: [["p1", "p2"], { headless: false }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/batch/run",
|
|
body: { profile_ids: ["p1", "p2"], headless: false },
|
|
operation: "POST /v1/profiles/batch/run",
|
|
},
|
|
{
|
|
method: "batchStopProfiles",
|
|
args: [["p1", "p2"]],
|
|
verb: "POST",
|
|
path: "/v1/profiles/batch/stop",
|
|
body: { profile_ids: ["p1", "p2"] },
|
|
operation: "POST /v1/profiles/batch/stop",
|
|
},
|
|
{
|
|
method: "distributeProxies",
|
|
args: [
|
|
[
|
|
{ profile_id: "p1", proxy_id: "x1" },
|
|
{ profile_id: "p2", proxy_id: "x2" },
|
|
],
|
|
],
|
|
verb: "POST",
|
|
path: "/v1/profiles/distribute-proxies",
|
|
body: {
|
|
pairs: [
|
|
{ profile_id: "p1", proxy_id: "x1" },
|
|
{ profile_id: "p2", proxy_id: "x2" },
|
|
],
|
|
},
|
|
operation: "POST /v1/profiles/distribute-proxies",
|
|
},
|
|
{
|
|
method: "detectImportProfiles",
|
|
args: [{ folder: "/Users/x/Chrome" }],
|
|
verb: "GET",
|
|
path: "/v1/profiles/import/detect",
|
|
body: null,
|
|
query: { folder: "/Users/x/Chrome" },
|
|
operation: "GET /v1/profiles/import/detect",
|
|
},
|
|
{
|
|
method: "detectImportProfiles",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/profiles/import/detect",
|
|
body: null,
|
|
operation: "GET /v1/profiles/import/detect",
|
|
},
|
|
{
|
|
method: "importProfiles",
|
|
args: [
|
|
[{ source_path: "/tmp/src", new_profile_name: "Imported" }],
|
|
{ duplicate_strategy: "skip" },
|
|
],
|
|
verb: "POST",
|
|
path: "/v1/profiles/import",
|
|
body: {
|
|
items: [{ source_path: "/tmp/src", new_profile_name: "Imported" }],
|
|
duplicate_strategy: "skip",
|
|
},
|
|
operation: "POST /v1/profiles/import",
|
|
},
|
|
{
|
|
method: "importProfileCookies",
|
|
args: ["p1", "[]"],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/cookies/import",
|
|
body: { content: "[]" },
|
|
operation: "POST /v1/profiles/{id}/cookies/import",
|
|
},
|
|
// -- agent ---------------------------------------------------------------
|
|
{
|
|
method: "agentPerceive",
|
|
args: ["p1", { viewport_only: true, max_bytes: 2048 }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/perceive",
|
|
body: { viewport_only: true, max_bytes: 2048 },
|
|
operation: "POST /v1/profiles/{id}/agent/perceive",
|
|
},
|
|
{
|
|
method: "agentPerceive",
|
|
args: ["p1"],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/perceive",
|
|
body: {},
|
|
operation: "POST /v1/profiles/{id}/agent/perceive",
|
|
},
|
|
{
|
|
method: "agentResolveLocator",
|
|
args: ["p1", { locator: LOCATOR, candidate_limit: 5 }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/resolve-locator",
|
|
body: { locator: LOCATOR, candidate_limit: 5 },
|
|
operation: "POST /v1/profiles/{id}/agent/resolve-locator",
|
|
},
|
|
{
|
|
method: "agentClick",
|
|
args: ["p1", { locator: LOCATOR, button: "right", click_count: 2 }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/click",
|
|
body: { locator: LOCATOR, button: "right", click_count: 2 },
|
|
operation: "POST /v1/profiles/{id}/agent/click",
|
|
},
|
|
{
|
|
method: "agentType",
|
|
args: ["p1", { locator: LOCATOR, text: "hello", clear_first: false, wpm: 55 }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/type",
|
|
body: { locator: LOCATOR, text: "hello", clear_first: false, wpm: 55 },
|
|
operation: "POST /v1/profiles/{id}/agent/type",
|
|
},
|
|
{
|
|
method: "agentExtract",
|
|
args: [
|
|
"p1",
|
|
{
|
|
container: { role: "listitem" },
|
|
field_map: [{ key: "title", locator: { role: "heading" }, source: "text" }],
|
|
max_pages: 3,
|
|
},
|
|
],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/extract",
|
|
body: {
|
|
container: { role: "listitem" },
|
|
field_map: [{ key: "title", locator: { role: "heading" }, source: "text" }],
|
|
max_pages: 3,
|
|
},
|
|
operation: "POST /v1/profiles/{id}/agent/extract",
|
|
},
|
|
{
|
|
method: "agentPick",
|
|
args: ["p1", { timeout_ms: 15000 }],
|
|
verb: "POST",
|
|
path: "/v1/profiles/p1/agent/pick",
|
|
body: { timeout_ms: 15000 },
|
|
operation: "POST /v1/profiles/{id}/agent/pick",
|
|
},
|
|
// -- remote sessions -----------------------------------------------------
|
|
{
|
|
method: "listRemoteSessions",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/remote-sessions",
|
|
body: null,
|
|
operation: "GET /v1/remote-sessions",
|
|
},
|
|
{
|
|
method: "getRemoteSession",
|
|
args: ["s1"],
|
|
verb: "GET",
|
|
path: "/v1/remote-sessions/s1",
|
|
body: null,
|
|
operation: "GET /v1/remote-sessions/{id}",
|
|
},
|
|
{
|
|
method: "stopRemoteSession",
|
|
args: ["s1"],
|
|
verb: "DELETE",
|
|
path: "/v1/remote-sessions/s1",
|
|
body: null,
|
|
operation: "DELETE /v1/remote-sessions/{id}",
|
|
},
|
|
{
|
|
method: "getRemoteHours",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/remote-hours",
|
|
body: null,
|
|
operation: "GET /v1/remote-hours",
|
|
},
|
|
// -- cookie bot ----------------------------------------------------------
|
|
{
|
|
method: "listCookieBotSchedules",
|
|
args: [{ scope: "team" }],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/schedules",
|
|
body: null,
|
|
query: { scope: "team" },
|
|
operation: "GET /v1/cookie-bot/schedules",
|
|
},
|
|
{
|
|
method: "getCookieBotSchedule",
|
|
args: ["p1"],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/schedules/p1",
|
|
body: null,
|
|
operation: "GET /v1/cookie-bot/schedules/{profile_id}",
|
|
},
|
|
{
|
|
method: "setCookieBotSchedule",
|
|
args: [
|
|
"p1",
|
|
{
|
|
enabled: true,
|
|
run_at_minute: 120,
|
|
days_mask: 31,
|
|
timezone: "Europe/Berlin",
|
|
preset: "steady",
|
|
max_minutes: 45,
|
|
sites: ["https://example.com"],
|
|
acknowledge_conflict: true,
|
|
},
|
|
],
|
|
verb: "PUT",
|
|
path: "/v1/cookie-bot/schedules/p1",
|
|
body: {
|
|
enabled: true,
|
|
run_at_minute: 120,
|
|
days_mask: 31,
|
|
timezone: "Europe/Berlin",
|
|
preset: "steady",
|
|
max_minutes: 45,
|
|
sites: ["https://example.com"],
|
|
acknowledge_conflict: true,
|
|
},
|
|
operation: "PUT /v1/cookie-bot/schedules/{profile_id}",
|
|
},
|
|
{
|
|
method: "deleteCookieBotSchedule",
|
|
args: ["p1"],
|
|
verb: "DELETE",
|
|
path: "/v1/cookie-bot/schedules/p1",
|
|
body: null,
|
|
operation: "DELETE /v1/cookie-bot/schedules/{profile_id}",
|
|
},
|
|
{
|
|
method: "getCookieBotConflicts",
|
|
args: ["p1", { run_at_minute: 90, timezone: "UTC", days_mask: 7 }],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/conflicts",
|
|
body: null,
|
|
query: { profile_id: "p1", run_at_minute: "90", timezone: "UTC", days_mask: "7" },
|
|
operation: "GET /v1/cookie-bot/conflicts",
|
|
},
|
|
{
|
|
method: "listCookieBotRuns",
|
|
args: [{ profile_id: "p1", limit: 10, before: "cursor-1" }],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/runs",
|
|
body: null,
|
|
query: { profile_id: "p1", limit: "10", before: "cursor-1" },
|
|
operation: "GET /v1/cookie-bot/runs",
|
|
},
|
|
{
|
|
method: "startCookieBotRun",
|
|
args: [{ profile_id: "p1", max_minutes: 30 }],
|
|
verb: "POST",
|
|
path: "/v1/cookie-bot/runs",
|
|
body: { profile_id: "p1", max_minutes: 30 },
|
|
operation: "POST /v1/cookie-bot/runs",
|
|
},
|
|
{
|
|
method: "cancelCookieBotRun",
|
|
args: ["r1"],
|
|
verb: "DELETE",
|
|
path: "/v1/cookie-bot/runs/r1",
|
|
body: null,
|
|
operation: "DELETE /v1/cookie-bot/runs/{run_id}",
|
|
},
|
|
{
|
|
method: "listCookieBotPresets",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/presets",
|
|
body: null,
|
|
operation: "GET /v1/cookie-bot/presets",
|
|
},
|
|
{
|
|
method: "getCookieBotUsage",
|
|
args: [{ period: "2026-08" }],
|
|
verb: "GET",
|
|
path: "/v1/cookie-bot/usage",
|
|
body: null,
|
|
query: { period: "2026-08" },
|
|
operation: "GET /v1/cookie-bot/usage",
|
|
},
|
|
// -- groups and tags -----------------------------------------------------
|
|
{
|
|
method: "listGroups",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/groups",
|
|
body: null,
|
|
operation: "GET /v1/groups",
|
|
},
|
|
{
|
|
method: "getGroup",
|
|
args: ["g1"],
|
|
verb: "GET",
|
|
path: "/v1/groups/g1",
|
|
body: null,
|
|
operation: "GET /v1/groups/{id}",
|
|
},
|
|
{
|
|
method: "createGroup",
|
|
args: ["Retail"],
|
|
verb: "POST",
|
|
path: "/v1/groups",
|
|
body: { name: "Retail" },
|
|
operation: "POST /v1/groups",
|
|
},
|
|
{
|
|
method: "updateGroup",
|
|
args: ["g1", "Retail EU"],
|
|
verb: "PUT",
|
|
path: "/v1/groups/g1",
|
|
body: { name: "Retail EU" },
|
|
operation: "PUT /v1/groups/{id}",
|
|
},
|
|
{
|
|
method: "deleteGroup",
|
|
args: ["g1"],
|
|
verb: "DELETE",
|
|
path: "/v1/groups/g1",
|
|
body: null,
|
|
operation: "DELETE /v1/groups/{id}",
|
|
},
|
|
{
|
|
method: "listTags",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/tags",
|
|
body: null,
|
|
operation: "GET /v1/tags",
|
|
},
|
|
// -- proxies -------------------------------------------------------------
|
|
{
|
|
method: "listProxies",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/proxies",
|
|
body: null,
|
|
operation: "GET /v1/proxies",
|
|
},
|
|
{
|
|
method: "getProxy",
|
|
args: ["x1"],
|
|
verb: "GET",
|
|
path: "/v1/proxies/x1",
|
|
body: null,
|
|
operation: "GET /v1/proxies/{id}",
|
|
},
|
|
{
|
|
method: "createProxy",
|
|
args: [{ name: "EU", proxy_settings: { proxy_type: "http", host: "h", port: 8080 } }],
|
|
verb: "POST",
|
|
path: "/v1/proxies",
|
|
body: { name: "EU", proxy_settings: { proxy_type: "http", host: "h", port: 8080 } },
|
|
operation: "POST /v1/proxies",
|
|
},
|
|
{
|
|
method: "updateProxy",
|
|
args: ["x1", { name: "EU 2" }],
|
|
verb: "PUT",
|
|
path: "/v1/proxies/x1",
|
|
body: { name: "EU 2" },
|
|
operation: "PUT /v1/proxies/{id}",
|
|
},
|
|
{
|
|
method: "deleteProxy",
|
|
args: ["x1"],
|
|
verb: "DELETE",
|
|
path: "/v1/proxies/x1",
|
|
body: null,
|
|
operation: "DELETE /v1/proxies/{id}",
|
|
},
|
|
{
|
|
method: "importProxies",
|
|
args: [{ format: "txt", content: "h:1:u:p", name_prefix: "EU" }],
|
|
verb: "POST",
|
|
path: "/v1/proxies/import",
|
|
body: { format: "txt", content: "h:1:u:p", name_prefix: "EU" },
|
|
operation: "POST /v1/proxies/import",
|
|
},
|
|
// -- vpns ----------------------------------------------------------------
|
|
{
|
|
method: "listVpns",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/vpns",
|
|
body: null,
|
|
operation: "GET /v1/vpns",
|
|
},
|
|
{
|
|
method: "getVpn",
|
|
args: ["v1"],
|
|
verb: "GET",
|
|
path: "/v1/vpns/v1",
|
|
body: null,
|
|
operation: "GET /v1/vpns/{id}",
|
|
},
|
|
{
|
|
method: "exportVpn",
|
|
args: ["v1"],
|
|
verb: "GET",
|
|
path: "/v1/vpns/v1/export",
|
|
body: null,
|
|
operation: "GET /v1/vpns/{id}/export",
|
|
},
|
|
{
|
|
method: "importVpn",
|
|
args: [{ content: "[Interface]", filename: "eu.conf" }],
|
|
verb: "POST",
|
|
path: "/v1/vpns/import",
|
|
body: { content: "[Interface]", filename: "eu.conf" },
|
|
operation: "POST /v1/vpns/import",
|
|
},
|
|
{
|
|
method: "createVpn",
|
|
args: [{ name: "EU", vpn_type: "WireGuard", config_data: "[Interface]" }],
|
|
verb: "POST",
|
|
path: "/v1/vpns",
|
|
body: { name: "EU", vpn_type: "WireGuard", config_data: "[Interface]" },
|
|
operation: "POST /v1/vpns",
|
|
},
|
|
{
|
|
method: "updateVpn",
|
|
args: ["v1", "EU 2"],
|
|
verb: "PUT",
|
|
path: "/v1/vpns/v1",
|
|
body: { name: "EU 2" },
|
|
operation: "PUT /v1/vpns/{id}",
|
|
},
|
|
{
|
|
method: "deleteVpn",
|
|
args: ["v1"],
|
|
verb: "DELETE",
|
|
path: "/v1/vpns/v1",
|
|
body: null,
|
|
operation: "DELETE /v1/vpns/{id}",
|
|
},
|
|
// -- extensions ----------------------------------------------------------
|
|
{
|
|
method: "listExtensions",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/extensions",
|
|
body: null,
|
|
operation: "GET /v1/extensions",
|
|
},
|
|
{
|
|
method: "getExtension",
|
|
args: ["e1"],
|
|
verb: "GET",
|
|
path: "/v1/extensions/e1",
|
|
body: null,
|
|
operation: "GET /v1/extensions/{id}",
|
|
},
|
|
{
|
|
method: "createExtension",
|
|
args: [{ name: "Blocker", file_name: "b.crx", file_data_base64: "AAAA" }],
|
|
verb: "POST",
|
|
path: "/v1/extensions",
|
|
body: { name: "Blocker", file_name: "b.crx", file_data_base64: "AAAA" },
|
|
operation: "POST /v1/extensions",
|
|
},
|
|
{
|
|
method: "updateExtension",
|
|
args: ["e1", { name: "Blocker 2", link: true }],
|
|
verb: "PUT",
|
|
path: "/v1/extensions/e1",
|
|
body: { name: "Blocker 2", link: true },
|
|
operation: "PUT /v1/extensions/{id}",
|
|
},
|
|
{
|
|
method: "deleteExtension",
|
|
args: ["e1"],
|
|
verb: "DELETE",
|
|
path: "/v1/extensions/e1",
|
|
body: null,
|
|
operation: "DELETE /v1/extensions/{id}",
|
|
},
|
|
{
|
|
method: "listExtensionGroups",
|
|
args: [],
|
|
verb: "GET",
|
|
path: "/v1/extension-groups",
|
|
body: null,
|
|
operation: "GET /v1/extension-groups",
|
|
},
|
|
{
|
|
method: "getExtensionGroup",
|
|
args: ["eg1"],
|
|
verb: "GET",
|
|
path: "/v1/extension-groups/eg1",
|
|
body: null,
|
|
operation: "GET /v1/extension-groups/{id}",
|
|
},
|
|
{
|
|
method: "createExtensionGroup",
|
|
args: ["Adblock set"],
|
|
verb: "POST",
|
|
path: "/v1/extension-groups",
|
|
body: { name: "Adblock set" },
|
|
operation: "POST /v1/extension-groups",
|
|
},
|
|
{
|
|
method: "updateExtensionGroup",
|
|
args: ["eg1", { extension_ids: ["e1", "e2"] }],
|
|
verb: "PUT",
|
|
path: "/v1/extension-groups/eg1",
|
|
body: { extension_ids: ["e1", "e2"] },
|
|
operation: "PUT /v1/extension-groups/{id}",
|
|
},
|
|
{
|
|
method: "deleteExtensionGroup",
|
|
args: ["eg1"],
|
|
verb: "DELETE",
|
|
path: "/v1/extension-groups/eg1",
|
|
body: null,
|
|
operation: "DELETE /v1/extension-groups/{id}",
|
|
},
|
|
{
|
|
method: "addExtensionToGroup",
|
|
args: ["eg1", "e1"],
|
|
verb: "POST",
|
|
path: "/v1/extension-groups/eg1/extensions/e1",
|
|
body: null,
|
|
operation: "POST /v1/extension-groups/{id}/extensions/{extension_id}",
|
|
},
|
|
{
|
|
method: "removeExtensionFromGroup",
|
|
args: ["eg1", "e1"],
|
|
verb: "DELETE",
|
|
path: "/v1/extension-groups/eg1/extensions/e1",
|
|
body: null,
|
|
operation: "DELETE /v1/extension-groups/{id}/extensions/{extension_id}",
|
|
},
|
|
// -- browsers ------------------------------------------------------------
|
|
{
|
|
method: "downloadBrowser",
|
|
args: [{ browser: "wayfern", version: "152.0.1" }],
|
|
verb: "POST",
|
|
path: "/v1/browsers/download",
|
|
body: { browser: "wayfern", version: "152.0.1" },
|
|
operation: "POST /v1/browsers/download",
|
|
},
|
|
{
|
|
method: "listBrowserVersions",
|
|
args: ["wayfern"],
|
|
verb: "GET",
|
|
path: "/v1/browsers/wayfern/versions",
|
|
body: null,
|
|
operation: "GET /v1/browsers/{browser}/versions",
|
|
},
|
|
{
|
|
method: "isBrowserDownloaded",
|
|
args: ["wayfern", "152.0.1"],
|
|
verb: "GET",
|
|
path: "/v1/browsers/wayfern/versions/152.0.1/downloaded",
|
|
body: null,
|
|
operation: "GET /v1/browsers/{browser}/versions/{version}/downloaded",
|
|
},
|
|
];
|
|
|
|
for (const [index, expected] of CASES.entries()) {
|
|
test(`${expected.method} sends the documented request [${index}]`, async () => {
|
|
await withClient(async (client, fake) => {
|
|
const callable = (client as unknown as Record<string, (...args: unknown[]) => Promise<unknown>>)[
|
|
expected.method
|
|
];
|
|
assert.equal(typeof callable, "function", `${expected.method} is not a method`);
|
|
await callable.call(client, ...expected.args);
|
|
|
|
const sent = fake.last;
|
|
assert.equal(sent.method, expected.verb);
|
|
assert.equal(sent.path, expected.path);
|
|
assert.deepEqual(sent.query, expected.query ?? {});
|
|
assert.deepEqual(sent.json, expected.body);
|
|
assert.equal(OPERATIONS.get(expected.operation), expected.method);
|
|
});
|
|
});
|
|
}
|
|
|
|
test("every wrapped operation has a request test", () => {
|
|
const covered = new Set(CASES.map((entry) => entry.method));
|
|
const missing = [...OPERATIONS.values()].filter((name) => !covered.has(name)).sort();
|
|
assert.deepEqual(missing, [], `these wrapped operations have no request test: ${missing}`);
|
|
});
|
|
|
|
test("the token travels as a bearer header", async () => {
|
|
await withClient(async (client, fake) => {
|
|
await client.listProfiles();
|
|
assert.equal(fake.last.headers.authorization, "Bearer test-token-abc123");
|
|
assert.equal(fake.last.headers.accept, "application/json");
|
|
assert.equal(
|
|
fake.last.headers["content-type"],
|
|
undefined,
|
|
"a GET must not claim to carry JSON",
|
|
);
|
|
});
|
|
});
|
|
|
|
test("a body is sent as JSON", async () => {
|
|
await withClient(async (client, fake) => {
|
|
await client.createGroup("Retail");
|
|
assert.equal(fake.last.headers["content-type"], "application/json");
|
|
assert.equal(fake.last.rawBody, '{"name":"Retail"}');
|
|
});
|
|
});
|
|
|
|
test("path ids are escaped", async () => {
|
|
await withClient(async (client, fake) => {
|
|
await client.getProfile("a/b c?d");
|
|
assert.equal(fake.last.path, "/v1/profiles/a%2Fb%20c%3Fd");
|
|
});
|
|
});
|
|
|
|
test("undefined arguments are left out of the body", async () => {
|
|
await withClient(async (client, fake) => {
|
|
await client.updateProfile("p1", { name: "Only this", version: undefined });
|
|
assert.deepEqual(fake.last.json, { name: "Only this" });
|
|
});
|
|
});
|
|
|
|
test("an empty string still reaches the app", async () => {
|
|
// `proxy_id: ""` is how the app is told to detach a proxy, so it must survive.
|
|
await withClient(async (client, fake) => {
|
|
await client.updateProfile("p1", { proxy_id: "" });
|
|
assert.deepEqual(fake.last.json, { proxy_id: "" });
|
|
});
|
|
});
|
|
|
|
test("a no-content answer becomes undefined", async () => {
|
|
await withClient(async (client, fake) => {
|
|
fake.enqueueEmpty(204);
|
|
assert.equal(await client.deleteProfile("p1"), undefined);
|
|
});
|
|
});
|
|
|
|
test("a JSON answer is returned as sent", async () => {
|
|
await withClient(async (client, fake) => {
|
|
fake.enqueueJson({ profiles: [{ id: "p1", name: "Shopper" }], total: 1 });
|
|
assert.deepEqual(await client.listProfiles(), {
|
|
profiles: [{ id: "p1", name: "Shopper" }],
|
|
total: 1,
|
|
});
|
|
});
|
|
});
|
|
|
|
test("a bare boolean answer is returned", async () => {
|
|
await withClient(async (client, fake) => {
|
|
fake.enqueueJson(true);
|
|
assert.equal(await client.isBrowserDownloaded("wayfern", "152.0.1"), true);
|
|
});
|
|
});
|