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
133 changes: 132 additions & 1 deletion apps/web/__tests__/unit/developer-api-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ describe("rate limit headers", () => {
"X-RateLimit-Limit",
"X-RateLimit-Remaining",
"X-RateLimit-Reset",
"X-Request-Id",
]);
});
});
Expand Down Expand Up @@ -639,7 +640,8 @@ describe("browser access", () => {
"access-control-allow-methods": "GET,POST,OPTIONS",
"access-control-allow-headers": "Content-Type,Authorization",
"access-control-expose-headers":
"Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset",
"Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,X-Request-Id",
"cache-control": "private, no-store",
vary: "Access-Control-Request-Headers",
});
});
Expand Down Expand Up @@ -924,3 +926,132 @@ describe("usage", () => {
);
});
});

describe("Idempotency-Key on create upload", () => {
const create = (headers: Record<string, string>) =>
call(rest, "POST", "/api/developer/v1/uploads", {
key: freshKey("csk"),
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify({ name: "Demo" }),
});

it("passes a valid key to the store", async () => {
const response = await create({ "Idempotency-Key": "retry-upload-0001" });

expect(response.status).toBe(200);
expect(mocks.createVideo).toHaveBeenCalledExactlyOnceWith({
appId: "app-1",
name: "Demo",
idempotencyKey: "retry-upload-0001",
});
});

it.each([
["too short", "short"],
["a colon", "retry:upload:0001"],
["a space", "retry upload 0001"],
["too long", "k".repeat(129)],
])("refuses a key with %s before creating anything", async (_label, key) => {
const response = await create({ "Idempotency-Key": key });

expect(response.status).toBe(400);
expect(await response.json()).toMatchObject({
code: "invalid_idempotency_key",
error: "Idempotency-Key is not valid",
});
expect(mocks.createVideo).not.toHaveBeenCalled();
});
});

describe("response headers on the developer APIs", () => {
const PRIVATE = "private, no-store";

it("marks every secret-key API answer private, errors and limits included", async () => {
const key = freshKey("csk");
const answers = [
await call(rest, "GET", "/api/developer/v1/usage", { key }),
await call(rest, "GET", "/api/developer/v1/usage"),
await call(rest, "GET", "/api/developer/v1/nope", { key }),
await call(rest, "POST", "/api/developer/v1/usage", { key }),
];
for (
let request = 3;
request < DEVELOPER_KEY_REQUESTS_PER_MINUTE;
request++
) {
await call(rest, "GET", "/api/developer/v1/usage", { key });
}
answers.push(await call(rest, "GET", "/api/developer/v1/usage", { key }));

expect(answers.map((response) => response.status)).toEqual([
200, 401, 404, 405, 429,
]);
for (const response of answers) {
expect(response.headers.get("Cache-Control")).toBe(PRIVATE);
}
});

it("marks the upload token API answers private too", async () => {
const answers = [
await call(
sdk,
"POST",
"/api/developer/sdk/v1/upload/multipart/initiate",
{
key: freshKey("cut"),
headers: { "Content-Type": "application/json" },
body: "{}",
},
),
await call(
sdk,
"POST",
"/api/developer/sdk/v1/upload/multipart/initiate",
{
headers: { "Content-Type": "application/json" },
body: "{}",
},
),
];

expect(answers.map((response) => response.status)).toEqual([200, 401]);
for (const response of answers) {
expect(response.headers.get("Cache-Control")).toBe(PRIVATE);
}
});

it("echoes the platform request id and lets a page read it", async () => {
const requestId = "fra1::iad1::abcde-1763031000000-0123456789ab";
const rested = await call(rest, "GET", "/api/developer/v1/usage", {
key: freshKey("csk"),
headers: { "x-vercel-id": requestId },
});
const sdked = await call(
sdk,
"POST",
"/api/developer/sdk/v1/upload/multipart/initiate",
{
key: freshKey("cut"),
headers: {
Origin: "https://app.example",
"Content-Type": "application/json",
"x-vercel-id": requestId,
},
body: "{}",
},
);

expect(rested.headers.get("X-Request-Id")).toBe(requestId);
expect(sdked.headers.get("X-Request-Id")).toBe(requestId);
expect(
sdked.headers
.get("Access-Control-Expose-Headers")
?.split(",")
.map((name) => name.trim()),
).toContain("X-Request-Id");
const withoutId = await call(rest, "GET", "/api/developer/v1/usage", {
key: freshKey("csk"),
});
expect(withoutId.headers.get("X-Request-Id")).toBeNull();
});
});
1 change: 1 addition & 0 deletions apps/web/__tests__/unit/developer-transcript-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const REQUEST_SOURCES = [
"lib/developer-transcript-store.ts",
"lib/developer-transcript-billing.ts",
"app/api/developer/v1/[...route]/transcripts.ts",
"lib/developer-ids.ts",
];

const codesIn = (source: string) => {
Expand Down
45 changes: 42 additions & 3 deletions apps/web/__tests__/unit/developer-transcript-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1139,10 +1139,49 @@ describe("deleting", () => {
"developer/app-1/transcripts/tr-1/result.json",
]);
expect(await getTranscript("app-1", "tr-1")).toBeNull();
expect(await deleteTranscript("app-1", "tr-1")).toMatchObject({
ok: false,
status: 404,
});

it("answers a repeat delete of a deleted transcript with success and clears its files again", async () => {
fake().seed(developerTranscripts, {
id: "tr-1",
appId: "app-1",
status: "complete",
sourceType: "upload",
text: "secret words",
});
expect(await deleteTranscript("app-1", "tr-1")).toEqual({ ok: true });
const deletedAt = transcripts()[0]?.deletedAt;
context.deleted = [];

expect(await deleteTranscript("app-1", "tr-1")).toEqual({ ok: true });

expect(transcripts()[0]?.deletedAt).toEqual(deletedAt);
expect(context.deleted).toEqual([
"developer/_uploads/app-1/t-tr-1",
"developer/app-1/transcripts/tr-1/audio.mp3",
"developer/app-1/transcripts/tr-1/result.json",
]);
});

it("still answers 404 for a transcript that never existed, or another app's deleted one", async () => {
fake().seed(developerTranscripts, {
id: "tr-2",
appId: "app-2",
status: "complete",
sourceType: "upload",
text: "other app",
});
expect(await deleteTranscript("app-2", "tr-2")).toEqual({ ok: true });
context.deleted = [];

for (const id of ["tr-2", "never-existed"]) {
expect(await deleteTranscript("app-1", id)).toMatchObject({
ok: false,
status: 404,
code: "transcript_not_found",
});
}
expect(context.deleted).toEqual([]);
});

it("does not delete another app's transcript", async () => {
Expand Down
9 changes: 2 additions & 7 deletions apps/web/__tests__/unit/developer-video-captions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,9 +842,7 @@ describe("the caption file of a video", () => {
"storage refused the delete",
);
context.stuckKeys.delete(EN_KEY);
expect(await deleteTranscript("app-1", "tr-1")).toMatchObject({
status: 404,
});
expect(await deleteTranscript("app-1", "tr-1")).toEqual({ ok: true });
expect(context.objects.has(EN_KEY)).toBe(false);
});

Expand All @@ -860,10 +858,7 @@ describe("the caption file of a video", () => {
);
expect(context.objects.get(EN_KEY)).toContain("newer words");

expect(await deleteTranscript("app-1", "tr-new")).toMatchObject({
ok: false,
status: 404,
});
expect(await deleteTranscript("app-1", "tr-new")).toEqual({ ok: true });
expect(context.objects.get(EN_KEY)).toContain("older words");
expect(context.objects.get(EN_KEY)).not.toContain("newer words");
});
Expand Down
Loading
Loading