Files
hyperframes/packages/cli/src/server/studioServer.test.ts
T
Vance Ingalls 71fd96bbf1 Merge pull request #2854 from heygen-com/feat/canary-rollouts
feat(core): percentage-based canary rollouts + calibration experiment
2026-08-03 22:35:37 -07:00

123 lines
4.5 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { loadHyperframeRuntimeSource } from "@hyperframes/core";
import { loadRuntimeSource } from "./runtimeSource.js";
import { createStudioServer, type StudioServer } from "./studioServer.js";
describe("loadRuntimeSource", () => {
it("loads runtime source from the published core entrypoint", async () => {
await expect(loadRuntimeSource()).resolves.toBe(loadHyperframeRuntimeSource());
});
});
describe("Studio thumbnail GPU capture plumbing", () => {
it("uses the shared auto probe, resolved launch mode, requirement guard, and completion-aware seek", () => {
const source = readFileSync(new URL("./studioServer.ts", import.meta.url), "utf8");
expect(source).toContain("resolveCaptureBrowserGpuMode");
expect(source).toContain("{ browserGpuMode: resolvedGpuMode }");
expect(source).toContain("assertWebGpuRequirement");
expect(source).toContain("await seekCompositionTimeline(page, opts.seekTime");
});
});
describe("createStudioServer autoProxy plumbing", () => {
const dirs: string[] = [];
let server: StudioServer | undefined;
function tmpProject(): string {
const dir = mkdtempSync(join(tmpdir(), "hf-studio-server-test-"));
dirs.push(dir);
return dir;
}
afterEach(() => {
server?.watcher.close();
server = undefined;
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
});
it("hyperframes.json media.autoProxy=false flows through to the adapter", () => {
const projectDir = tmpProject();
writeFileSync(
join(projectDir, "hyperframes.json"),
JSON.stringify({ media: { autoProxy: false } }),
);
server = createStudioServer({ projectDir });
expect(server.adapter.autoProxy).toBe(false);
});
it("defaults the adapter to autoProxy=true when neither option nor config disables it", () => {
server = createStudioServer({ projectDir: tmpProject() });
expect(server.adapter.autoProxy).toBe(true);
});
it("an explicit option (the preview command's resolved --proxy flag) wins over config", () => {
const projectDir = tmpProject();
writeFileSync(
join(projectDir, "hyperframes.json"),
JSON.stringify({ media: { autoProxy: false } }),
);
server = createStudioServer({ projectDir, autoProxy: true });
expect(server.adapter.autoProxy).toBe(true);
});
it("advertises the GPU policy used for thumbnail capture", async () => {
const projectDir = tmpProject();
server = createStudioServer({ projectDir, browserGpuMode: "software" });
const response = await server.app.request("/__hyperframes_config");
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({ browserGpuMode: "software" });
});
});
describe("host guarding on identity-bearing responses", () => {
const dirs: string[] = [];
let server: StudioServer | undefined;
function tmpProject(): string {
const dir = mkdtempSync(join(tmpdir(), "hf-studio-host-test-"));
dirs.push(dir);
return dir;
}
afterEach(() => {
server?.watcher.close();
server = undefined;
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
});
// NOTE: the SPA-injection branch itself is covered in telemetryIdentity.test.ts
// via buildStudioHeadScriptsForHost. It cannot be asserted here: this route
// only reaches the injection branch when packages/studio/dist is built,
// which is true locally and false in the CI test lane, so a route-level
// assertion on the returned HTML passes on a dev box and fails in CI.
it("refuses the identity endpoint for a hostile Host", async () => {
server = createStudioServer({ projectDir: tmpProject() });
const res = await server.app.request("/api/telemetry-identity", {
headers: { host: "evil.example.com" },
});
expect(res.status).toBe(403);
expect(await res.text()).not.toContain('distinctId":"');
});
it("serves the identity endpoint on a loopback Host", async () => {
server = createStudioServer({ projectDir: tmpProject() });
const res = await server.app.request("/api/telemetry-identity", {
headers: { host: "127.0.0.1:5173" },
});
expect(res.status).toBe(200);
// The seed is no longer served here at all — Studio gets decisions
// injected instead, so nothing needs it over HTTP.
expect(Object.keys((await res.json()) as object)).toEqual(["distinctId"]);
});
});