mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
fix(cli): publish projects through staged uploads (#491)
* fix(cli): publish projects through staged uploads * fix(cli): honor signed upload length header
This commit is contained in:
@@ -37,9 +37,38 @@ describe("createPublishArchive", () => {
|
||||
|
||||
describe("publishProjectArchive", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue(
|
||||
vi.stubEnv("HYPERFRAMES_PUBLISHED_PROJECTS_API_URL", "");
|
||||
vi.stubEnv("HEYGEN_API_URL", "");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("uploads through the staged publish flow and returns the stable project URL", async () => {
|
||||
const dir = makeProjectDir();
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
upload_url:
|
||||
"https://s3.example.com/upload?X-Amz-SignedHeaders=content-length;content-type;host;x-amz-server-side-encryption",
|
||||
upload_key: "ephemeral_store/hyperframes/project_uploads/upload-1/demo.zip",
|
||||
upload_headers: {
|
||||
"content-type": "application/zip",
|
||||
"x-amz-server-side-encryption": "AES256",
|
||||
},
|
||||
content_type: "application/zip",
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
)
|
||||
.mockResolvedValueOnce(new Response(null, { status: 200 }))
|
||||
.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
@@ -52,16 +81,9 @@ describe("publishProjectArchive", () => {
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("uploads the archive and returns the stable project URL", async () => {
|
||||
const dir = makeProjectDir();
|
||||
try {
|
||||
writeFileSync(join(dir, "index.html"), "<html></html>", "utf-8");
|
||||
writeFileSync(join(dir, "styles.css"), "body {}", "utf-8");
|
||||
@@ -73,8 +95,73 @@ describe("publishProjectArchive", () => {
|
||||
projectId: "hfp_123",
|
||||
url: "https://hyperframes.dev/p/hfp_123",
|
||||
});
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"https://api2.heygen.com/v1/hyperframes/projects/publish/upload",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", heygen_route: "canary" },
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"https://s3.example.com/upload?X-Amz-SignedHeaders=content-length;content-type;host;x-amz-server-side-encryption",
|
||||
expect.objectContaining({
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"content-length": expect.any(String),
|
||||
"content-type": "application/zip",
|
||||
"x-amz-server-side-encryption": "AES256",
|
||||
},
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
"https://api2.heygen.com/v1/hyperframes/projects/publish/complete",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json", heygen_route: "canary" },
|
||||
signal: expect.any(AbortSignal),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to the legacy multipart endpoint when staged publish is not deployed", async () => {
|
||||
const dir = makeProjectDir();
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(new Response("not found", { status: 404 }))
|
||||
.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
project_id: "hfp_123",
|
||||
title: "demo",
|
||||
file_count: 2,
|
||||
url: "https://hyperframes.dev/p/hfp_123",
|
||||
claim_token: "claim-token",
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
try {
|
||||
writeFileSync(join(dir, "index.html"), "<html></html>", "utf-8");
|
||||
|
||||
const result = await publishProjectArchive(dir);
|
||||
|
||||
expect(result.projectId).toBe("hfp_123");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"https://api2.heygen.com/v1/hyperframes/projects/publish",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
@@ -86,4 +173,37 @@ describe("publishProjectArchive", () => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not fall back to multipart when a staged S3 upload fails", async () => {
|
||||
const dir = makeProjectDir();
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
upload_url: "https://s3.example.com/upload",
|
||||
upload_key: "ephemeral_store/hyperframes/project_uploads/upload-1/demo.zip",
|
||||
upload_headers: {
|
||||
"content-type": "application/zip",
|
||||
"x-amz-server-side-encryption": "AES256",
|
||||
},
|
||||
content_type: "application/zip",
|
||||
},
|
||||
}),
|
||||
{ status: 200 },
|
||||
),
|
||||
)
|
||||
.mockResolvedValueOnce(new Response("denied", { status: 403 }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
try {
|
||||
writeFileSync(join(dir, "index.html"), "<html></html>", "utf-8");
|
||||
|
||||
await expect(publishProjectArchive(dir)).rejects.toThrow("Failed to upload project archive");
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user