Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import dedent from "ts-dedent";
import { beforeEach, describe, it, vitest } from "vitest";
import { describe, it, vitest } from "vitest";
import {
commitAndPush,
generateChangesetHeader,
Expand All @@ -12,18 +12,16 @@ import {
} from "../generate-dependabot-pr-changesets";
import type { Mock } from "vitest";

beforeEach(() => {
vitest.mock("node:child_process", async () => {
return {
spawnSync: vitest.fn(),
};
});
vitest.mock("node:child_process", async () => {
return {
spawnSync: vitest.fn(),
};
});

vitest.mock("node:fs", async () => {
return {
writeFileSync: vitest.fn(),
};
});
vitest.mock("node:fs", async () => {
return {
writeFileSync: vitest.fn(),
};
});

describe("getPackageJsonDiff()", () => {
Expand Down
44 changes: 30 additions & 14 deletions tools/e2e/__tests__/common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
listTmpKVNamespaces,
listTmpR2Buckets,
} from "../common";
import type { Worker } from "../common";

const originalAccountID = process.env.CLOUDFLARE_ACCOUNT_ID;
const MOCK_CLOUDFLARE_ACCOUNT_ID = "mock-cloudflare-account";
Expand Down Expand Up @@ -307,34 +308,49 @@ describe("listTmpE2EWorkers()", () => {
it("makes a REST request and returns a filtered list of workers", async ({
expect,
}) => {
// First page has results
agent
.get("https://api.cloudflare.com")
.intercept({
path: `/client/v4/accounts/${MOCK_CLOUDFLARE_ACCOUNT_ID}/workers/scripts`,
query: { page: 1 },
path: `/client/v4/accounts/${MOCK_CLOUDFLARE_ACCOUNT_ID}/workers/scripts-search`,
query: { page: 1, per_page: 100 },
method: "GET",
})
.reply(
200,
JSON.stringify({
result: [
{ id: "wprker-1", created_on: nowStr },
{ id: "wprker-2", created_on: oldTimeStr },
{ id: "tmp-e2e-worker-1", created_on: nowStr },
{ id: "tmp-e2e-worker-2", created_on: oldTimeStr },
{ id: "wprker-3", created_on: nowStr },
{ id: "wprker-4", created_on: oldTimeStr },
{ id: "tmp-e2e-worker-3", created_on: nowStr },
{ id: "tmp-e2e-worker-4", created_on: oldTimeStr },
{ id: "wprker-5", created_on: nowStr },
{ id: "wprker-6", created_on: oldTimeStr },
],
{ script_name: "wprker-1", created_on: nowStr },
{ script_name: "wprker-2", created_on: oldTimeStr },
{ script_name: "tmp-e2e-worker-1", created_on: nowStr },
{ script_name: "tmp-e2e-worker-2", created_on: oldTimeStr },
{ script_name: "wprker-3", created_on: nowStr },
{ script_name: "wprker-4", created_on: oldTimeStr },
{ script_name: "tmp-e2e-worker-3", created_on: nowStr },
{ script_name: "tmp-e2e-worker-4", created_on: oldTimeStr },
{ script_name: "wprker-5", created_on: nowStr },
{ script_name: "wprker-6", created_on: oldTimeStr },
] satisfies Worker[],
})
);
// Second page is empty
agent
.get("https://api.cloudflare.com")
.intercept({
path: `/client/v4/accounts/${MOCK_CLOUDFLARE_ACCOUNT_ID}/workers/scripts-search`,
query: { page: 2, per_page: 100 },
method: "GET",
})
.reply(
200,
JSON.stringify({
result: [],
})
);

const result = await listTmpE2EWorkers();

expect(result.map((p) => p.id)).toMatchInlineSnapshot(`
expect(result.map((p) => p.script_name)).toMatchInlineSnapshot(`
[
"wprker-2",
"tmp-e2e-worker-2",
Expand Down
98 changes: 66 additions & 32 deletions tools/e2e/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ type ApiErrorBody = {
errors: string[];
};

type ApiSuccessBody = {
result: unknown[];
type ApiSuccessBody<T> = {
result: T[];
};

export type Project = {
Expand All @@ -28,7 +28,7 @@ export type ContainerApplication = {
};

export type Worker = {
id: string;
script_name: string;
created_on: string;
};

Expand Down Expand Up @@ -70,20 +70,27 @@ export type MTlsCertificateResponse = {
};

class ApiError extends Error {
readonly status: number;
readonly statusText: string;
readonly text: Promise<string>;

constructor(
readonly url: string,
readonly init: RequestInit,
readonly response: Response
response: Response
) {
super();
this.status = response.status;
this.statusText = response.statusText;
this.text = response.text();
}
}

async function apiFetchResponse(
path: string,
init = { method: "GET" },
queryParams = {}
): Promise<Response | false> {
): Promise<Response> {
const baseUrl = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}`;
let queryString = new URLSearchParams(queryParams).toString();
if (queryString) {
Expand All @@ -98,14 +105,8 @@ async function apiFetchResponse(
},
});

if (response.status >= 400) {
console.error(
"API Fetch failed",
response.status,
response.statusText,
await response.text()
);
throw { url, init, response };
if (!response.ok || response.status >= 400) {
throw new ApiError(url, init, response);
}

return response;
Expand All @@ -114,23 +115,19 @@ async function apiFetchResponse(
async function apiFetch<T>(
path: string,
method: string,
failSilently = false
queryParams: Record<string, unknown> = {},
failSilently: boolean = false
): Promise<false | T> {
try {
const response = await apiFetchResponse(path, { method });

if (!response || response.ok === false) {
return false;
}

const json = (await response.json()) as ApiSuccessBody;
const response = await apiFetchResponse(path, { method }, queryParams);
const json = (await response.json()) as ApiSuccessBody<T>;
return json.result as T;
} catch (e) {
if (!failSilently) {
if (e instanceof ApiError) {
console.error(e.url, e.init);
console.error(`(${e.response.status}) ${e.response.statusText}`);
const body = (await e.response.json()) as ApiErrorBody;
console.error(`(${e.status}) ${e.statusText}`);
const body = JSON.parse(await e.text) as ApiErrorBody;
console.error(body.errors);
} else {
console.error(e);
Expand All @@ -140,6 +137,40 @@ async function apiFetch<T>(
}
}

async function apiFetchAllPages<T>(
path: string,
queryParams = {}
): Promise<T[]> {
const result: T[] = [];
try {
let page = 1;
while (true) {
const response = await apiFetchResponse(
path,
{ method: "GET" },
{ page, per_page: 100, ...queryParams }
);
const json = (await response.json()) as ApiSuccessBody<T>;
if (json.result.length === 0) {
// We hit an empty page so we are done.
break;
}
result.push(...json.result);
page++;
}
} catch (e) {
if (e instanceof ApiError) {
console.error(e.url, e.init);
console.error(`(${e.status}) ${e.statusText}`);
const body = JSON.parse(await e.text) as ApiErrorBody;
console.error(body.errors);
} else {
console.error(e);
}
}
return result;
}

async function apiFetchList<T>(path: string, queryParams = {}): Promise<T[]> {
try {
let page = 1;
Expand All @@ -159,7 +190,7 @@ async function apiFetchList<T>(path: string, queryParams = {}): Promise<T[]> {
return [];
}

const json = (await response.json()) as ApiSuccessBody;
const json = (await response.json()) as ApiSuccessBody<T>;
if ("result_info" in json && Array.isArray(json.result)) {
const result_info = json.result_info as {
page: number;
Expand Down Expand Up @@ -191,8 +222,8 @@ async function apiFetchList<T>(path: string, queryParams = {}): Promise<T[]> {
} catch (e) {
if (e instanceof ApiError) {
console.error(e.url, e.init);
console.error(`(${e.response.status}) ${e.response.statusText}`);
const body = (await e.response.json()) as ApiErrorBody;
console.error(`(${e.status}) ${e.statusText}`);
const body = JSON.parse(await e.text) as ApiErrorBody;
console.error(body.errors);
} else {
console.error(e);
Expand All @@ -215,18 +246,19 @@ export const deleteProject = async (project: string) => {
};

export const listTmpE2EWorkers = async () => {
return (await apiFetchList<Worker>(`/workers/scripts`)).filter(
const workers = await apiFetchAllPages<Worker>(`/workers/scripts-search`);
return workers.filter(
(p) =>
!p.id.startsWith("preserve-e2e-") &&
p.id !== "stratus-e2e-test-worker" &&
p.id !== "existing-script-test-do-not-delete" &&
!p.script_name.startsWith("preserve-e2e-") &&
p.script_name !== "stratus-e2e-test-worker" &&
p.script_name !== "existing-script-test-do-not-delete" &&
// Workers are more than an hour old
Date.now() - new Date(p.created_on).valueOf() > 1000 * 60 * 60
);
};

export const deleteWorker = async (id: string) => {
return await apiFetch(`/workers/scripts/${id}`, "DELETE");
export const deleteWorker = async (script_name: string) => {
return await apiFetch(`/workers/scripts/${script_name}`, "DELETE");
};

export const listTmpE2EContainerApplications = async () => {
Expand Down Expand Up @@ -275,6 +307,7 @@ export const deleteKVNamespace = async (id: string) => {
return await apiFetch(
`/storage/kv/namespaces/${id}`,
"DELETE",
{},
/* failSilently */ true
);
};
Expand All @@ -299,6 +332,7 @@ export const deleteR2Bucket = async (name: string) => {
return await apiFetch(
`/r2/buckets/${name}`,
"DELETE",
{},
/* failSilently */ true
);
};
Expand Down
8 changes: 4 additions & 4 deletions tools/e2e/e2eCleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ async function deleteWorkers() {
const workersToDelete = await listTmpE2EWorkers();

for (const worker of workersToDelete) {
console.log("Deleting worker: " + worker.id);
if (await deleteWorker(worker.id)) {
console.log(`Successfully deleted Worker ${worker.id}`);
console.log("Deleting worker: " + worker.script_name);
if (await deleteWorker(worker.script_name)) {
console.log(`Successfully deleted Worker ${worker.script_name}`);
} else {
console.log(`Failed to delete Worker ${worker.id}`);
console.log(`Failed to delete Worker ${worker.script_name}`);
}
}

Expand Down
Loading