Skip to content
Closed
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
110 changes: 110 additions & 0 deletions packages/trigger-sdk/src/v3/envvars.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { apiClientManager, taskContext } from "@trigger.dev/core/v3";
import { configure } from "./auth.js";
import { update } from "./envvars.js";

type CapturedRequest = {
url: string;
method: string | undefined;
authorization: string | undefined;
body?: string;
};

function installFetchSpy() {
const captured: CapturedRequest[] = [];
const originalFetch = globalThis.fetch;

globalThis.fetch = (async (input: any, init?: RequestInit) => {
const url = typeof input === "string" ? input : (input?.url ?? String(input));
const headers = new Headers(init?.headers);
captured.push({
url,
method: init?.method,
authorization: headers.get("authorization") ?? undefined,
body: typeof init?.body === "string" ? init.body : undefined,
});
return new Response(
JSON.stringify({
name: "DATABASE_URL",
value: "postgres://...",
}),
{
status: 200,
headers: { "content-type": "application/json" },
}
);
}) as typeof fetch;
Comment on lines +17 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Test mocks global fetch

The test replaces globalThis.fetch with a custom implementation. Repository guidance prohibits mocks and requires real test infrastructure.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


return {
captured,
restore: () => {
globalThis.fetch = originalFetch;
},
};
}

describe("envvars", () => {
let fetchSpy: ReturnType<typeof installFetchSpy>;

beforeEach(() => {
apiClientManager.disable();
taskContext.disable();
fetchSpy = installFetchSpy();
});

afterEach(() => {
fetchSpy.restore();
apiClientManager.disable();
taskContext.disable();
vi.unstubAllEnvs();
});

describe("update outside task context", () => {
it("successfully updates an environment variable without ReferenceError (#4264)", async () => {
configure({ accessToken: "tr_test_token" });

await update("proj_123", "prod", "DATABASE_URL", {
value: "postgres://localhost:5432/mydb",
});

expect(fetchSpy.captured).toHaveLength(1);
const req = fetchSpy.captured[0]!;
expect(req.url).toContain("/api/v1/projects/proj_123/envvars/prod/DATABASE_URL");
expect(req.method).toBe("PUT");
expect(req.authorization).toBe("Bearer tr_test_token");
expect(req.body).toBe(JSON.stringify({ value: "postgres://localhost:5432/mydb" }));
});

it("throws when name is missing or not a string", async () => {
configure({ accessToken: "tr_test_token" });

expect(() =>
update("proj_123", "prod", undefined as any, { value: "test" })
).toThrow("name is required");
});

it("throws when projectRef is missing", async () => {
configure({ accessToken: "tr_test_token" });

expect(() =>
update("", "prod", "MY_VAR", { value: "test" })
).toThrow("projectRef is required");
});

it("throws when slug is missing", async () => {
configure({ accessToken: "tr_test_token" });

expect(() =>
update("proj_123", undefined as any, "MY_VAR", { value: "test" })
).toThrow("slug is required");
});

it("throws when params is missing", async () => {
configure({ accessToken: "tr_test_token" });

expect(() =>
update("proj_123", "prod", "MY_VAR", undefined as any)
).toThrow("params is required");
});
});
});
6 changes: 5 additions & 1 deletion packages/trigger-sdk/src/v3/envvars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,17 @@ export function update(
throw new Error("projectRef is required");
}

if (typeof nameOrRequestOptions !== "string") {
throw new Error("name is required");
}
Comment on lines +335 to +337

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Empty variable names bypass validation

An empty nameOrRequestOptions passes validation and sends update to an endpoint without a variable name. The update fails as an HTTP routing error.

Suggested change
if (typeof nameOrRequestOptions !== "string") {
throw new Error("name is required");
}
if (!nameOrRequestOptions || typeof nameOrRequestOptions !== "string") {
throw new Error("name is required");
}
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


if (!params) {
throw new Error("params is required");
}

$projectRef = projectRefOrName;
$slug = slugOrParams;
$name = name!;
$name = nameOrRequestOptions;
Comment on lines +335 to +345

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 SDK fix lacks a changeset

This user-visible @trigger.dev/sdk runtime fix has no changeset. Repository guidance requires release notes for package bugs customers can encounter.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

$params = params;
}

Expand Down