mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 09:01:17 +00:00
Configure lefthook formatter checks (#231)
* Add lefthook formatter checks * Fix SDK mode hydration * Stabilize SDK mode integration test
This commit is contained in:
parent
0471214d65
commit
d2346bafb3
282 changed files with 5840 additions and 8399 deletions
|
|
@ -1,9 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
deriveFallbackTitle,
|
||||
resolveCreateFlowDecision,
|
||||
sanitizeBranchName
|
||||
} from "../src/services/create-flow.js";
|
||||
import { deriveFallbackTitle, resolveCreateFlowDecision, sanitizeBranchName } from "../src/services/create-flow.js";
|
||||
|
||||
describe("create flow decision", () => {
|
||||
it("derives a conventional-style fallback title from task text", () => {
|
||||
|
|
@ -25,7 +21,7 @@ describe("create flow decision", () => {
|
|||
const resolved = resolveCreateFlowDecision({
|
||||
task: "Add auth",
|
||||
localBranches: ["feat-add-auth"],
|
||||
handoffBranches: ["feat-add-auth-2"]
|
||||
handoffBranches: ["feat-add-auth-2"],
|
||||
});
|
||||
|
||||
expect(resolved.title).toBe("feat: Add auth");
|
||||
|
|
@ -38,8 +34,8 @@ describe("create flow decision", () => {
|
|||
task: "new task",
|
||||
explicitBranchName: "existing-branch",
|
||||
localBranches: ["existing-branch"],
|
||||
handoffBranches: []
|
||||
})
|
||||
handoffBranches: [],
|
||||
}),
|
||||
).toThrow("already exists");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ function createProviderWithClient(client: DaytonaClientLike): DaytonaProvider {
|
|||
apiKey: "test-key",
|
||||
image: "ubuntu:24.04",
|
||||
},
|
||||
daytonaDriver
|
||||
daytonaDriver,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ describe("daytona provider snapshot image behavior", () => {
|
|||
});
|
||||
|
||||
const startCommand = client.executedCommands.find((command) =>
|
||||
command.includes("nohup env SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS=240000 sandbox-agent server")
|
||||
command.includes("nohup env SANDBOX_AGENT_ACP_REQUEST_TIMEOUT_MS=240000 sandbox-agent server"),
|
||||
);
|
||||
|
||||
const joined = client.executedCommands.join("\n");
|
||||
|
|
@ -149,13 +149,15 @@ describe("daytona provider snapshot image behavior", () => {
|
|||
|
||||
try {
|
||||
const provider = createProviderWithClient(hangingClient);
|
||||
await expect(provider.createSandbox({
|
||||
workspaceId: "default",
|
||||
repoId: "repo-1",
|
||||
repoRemote: "https://github.com/acme/repo.git",
|
||||
branchName: "feature/test",
|
||||
handoffId: "handoff-timeout",
|
||||
})).rejects.toThrow("daytona create sandbox timed out after 120ms");
|
||||
await expect(
|
||||
provider.createSandbox({
|
||||
workspaceId: "default",
|
||||
repoId: "repo-1",
|
||||
repoRemote: "https://github.com/acme/repo.git",
|
||||
branchName: "feature/test",
|
||||
handoffId: "handoff-timeout",
|
||||
}),
|
||||
).rejects.toThrow("daytona create sandbox timed out after 120ms");
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env.HF_DAYTONA_REQUEST_TIMEOUT_MS;
|
||||
|
|
@ -173,7 +175,7 @@ describe("daytona provider snapshot image behavior", () => {
|
|||
workspaceId: "default",
|
||||
sandboxId: "sandbox-1",
|
||||
command: "echo backend-push",
|
||||
label: "manual push"
|
||||
label: "manual push",
|
||||
});
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ import { chmodSync, mkdtempSync, writeFileSync, readFileSync } from "node:fs";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
gitSpiceAvailable,
|
||||
gitSpiceListStack,
|
||||
gitSpiceRestackSubtree
|
||||
} from "../src/integrations/git-spice/index.js";
|
||||
import { gitSpiceAvailable, gitSpiceListStack, gitSpiceRestackSubtree } from "../src/integrations/git-spice/index.js";
|
||||
|
||||
function makeTempDir(prefix: string): string {
|
||||
return mkdtempSync(join(tmpdir(), prefix));
|
||||
|
|
@ -17,10 +13,7 @@ function writeScript(path: string, body: string): void {
|
|||
chmodSync(path, 0o755);
|
||||
}
|
||||
|
||||
async function withEnv<T>(
|
||||
updates: Record<string, string | undefined>,
|
||||
fn: () => Promise<T>
|
||||
): Promise<T> {
|
||||
async function withEnv<T>(updates: Record<string, string | undefined>, fn: () => Promise<T>): Promise<T> {
|
||||
const previous = new Map<string, string | undefined>();
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
previous.set(key, process.env[key]);
|
||||
|
|
@ -57,21 +50,21 @@ describe("git-spice integration", () => {
|
|||
"fi",
|
||||
'if [ \"$1\" = \"log\" ]; then',
|
||||
" echo 'noise line'",
|
||||
" echo '{\"branch\":\"feature/a\",\"parent\":\"main\"}'",
|
||||
' echo \'{"branch":"feature/a","parent":"main"}\'',
|
||||
" echo '{bad json'",
|
||||
" echo '{\"name\":\"feature/b\",\"parentBranch\":\"feature/a\"}'",
|
||||
" echo '{\"name\":\"feature/a\",\"parent\":\"main\"}'",
|
||||
' echo \'{"name":"feature/b","parentBranch":"feature/a"}\'',
|
||||
' echo \'{"name":"feature/a","parent":"main"}\'',
|
||||
" exit 0",
|
||||
"fi",
|
||||
"exit 1"
|
||||
].join("\n")
|
||||
"exit 1",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
await withEnv({ HF_GIT_SPICE_BIN: scriptPath }, async () => {
|
||||
const rows = await gitSpiceListStack(repoPath);
|
||||
expect(rows).toEqual([
|
||||
{ branchName: "feature/a", parentBranch: "main" },
|
||||
{ branchName: "feature/b", parentBranch: "feature/a" }
|
||||
{ branchName: "feature/b", parentBranch: "feature/a" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -94,18 +87,18 @@ describe("git-spice integration", () => {
|
|||
'if [ \"$1\" = \"branch\" ] && [ \"$2\" = \"restack\" ] && [ \"$5\" = \"--no-prompt\" ]; then',
|
||||
" exit 0",
|
||||
"fi",
|
||||
"exit 1"
|
||||
].join("\n")
|
||||
"exit 1",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
await withEnv(
|
||||
{
|
||||
HF_GIT_SPICE_BIN: scriptPath,
|
||||
SPICE_LOG_PATH: logPath
|
||||
SPICE_LOG_PATH: logPath,
|
||||
},
|
||||
async () => {
|
||||
await gitSpiceRestackSubtree(repoPath, "feature/a");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const lines = readFileSync(logPath, "utf8")
|
||||
|
|
@ -125,12 +118,12 @@ describe("git-spice integration", () => {
|
|||
await withEnv(
|
||||
{
|
||||
HF_GIT_SPICE_BIN: "/non-existent/hf-git-spice-binary",
|
||||
PATH: "/non-existent/bin"
|
||||
PATH: "/non-existent/bin",
|
||||
},
|
||||
async () => {
|
||||
const available = await gitSpiceAvailable(repoPath);
|
||||
expect(available).toBe(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ export function createTestConfig(overrides?: Partial<AppConfig>): AppConfig {
|
|||
backend: {
|
||||
host: "127.0.0.1",
|
||||
port: 7741,
|
||||
dbPath: join(
|
||||
tmpdir(),
|
||||
`hf-test-${Date.now()}-${Math.random().toString(16).slice(2)}.db`
|
||||
),
|
||||
dbPath: join(tmpdir(), `hf-test-${Date.now()}-${Math.random().toString(16).slice(2)}.db`),
|
||||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
|
|
@ -29,10 +26,7 @@ export function createTestConfig(overrides?: Partial<AppConfig>): AppConfig {
|
|||
});
|
||||
}
|
||||
|
||||
export function createTestRuntimeContext(
|
||||
driver: BackendDriver,
|
||||
configOverrides?: Partial<AppConfig>
|
||||
): { config: AppConfig } {
|
||||
export function createTestRuntimeContext(driver: BackendDriver, configOverrides?: Partial<AppConfig>): { config: AppConfig } {
|
||||
const config = createTestConfig(configOverrides);
|
||||
const providers = createProviderRegistry(config, driver);
|
||||
initActorRuntimeContext(config, providers, undefined, driver);
|
||||
|
|
|
|||
|
|
@ -62,18 +62,14 @@ export function createTestGithubDriver(overrides?: Partial<GithubDriver>): Githu
|
|||
};
|
||||
}
|
||||
|
||||
export function createTestSandboxAgentDriver(
|
||||
overrides?: Partial<SandboxAgentDriver>
|
||||
): SandboxAgentDriver {
|
||||
export function createTestSandboxAgentDriver(overrides?: Partial<SandboxAgentDriver>): SandboxAgentDriver {
|
||||
return {
|
||||
createClient: (_opts) => createTestSandboxAgentClient(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTestSandboxAgentClient(
|
||||
overrides?: Partial<SandboxAgentClientLike>
|
||||
): SandboxAgentClientLike {
|
||||
export function createTestSandboxAgentClient(overrides?: Partial<SandboxAgentClientLike>): SandboxAgentClientLike {
|
||||
return {
|
||||
createSession: async (_prompt) => ({ id: "test-session-1", status: "running" }),
|
||||
sessionStatus: async (sessionId) => ({ id: sessionId, status: "running" }),
|
||||
|
|
@ -92,18 +88,14 @@ export function createTestSandboxAgentClient(
|
|||
};
|
||||
}
|
||||
|
||||
export function createTestDaytonaDriver(
|
||||
overrides?: Partial<DaytonaDriver>
|
||||
): DaytonaDriver {
|
||||
export function createTestDaytonaDriver(overrides?: Partial<DaytonaDriver>): DaytonaDriver {
|
||||
return {
|
||||
createClient: (_opts) => createTestDaytonaClient(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTestDaytonaClient(
|
||||
overrides?: Partial<DaytonaClientLike>
|
||||
): DaytonaClientLike {
|
||||
export function createTestDaytonaClient(overrides?: Partial<DaytonaClientLike>): DaytonaClientLike {
|
||||
return {
|
||||
createSandbox: async () => ({ id: "sandbox-test-1", state: "started" }),
|
||||
getSandbox: async (sandboxId) => ({ id: sandboxId, state: "started" }),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
projectKey,
|
||||
projectPrSyncKey,
|
||||
sandboxInstanceKey,
|
||||
workspaceKey
|
||||
workspaceKey,
|
||||
} from "../src/actors/keys.js";
|
||||
|
||||
describe("actor keys", () => {
|
||||
|
|
@ -20,7 +20,7 @@ describe("actor keys", () => {
|
|||
historyKey("default", "repo"),
|
||||
projectPrSyncKey("default", "repo"),
|
||||
projectBranchSyncKey("default", "repo"),
|
||||
handoffStatusSyncKey("default", "repo", "handoff", "sandbox-1", "session-1")
|
||||
handoffStatusSyncKey("default", "repo", "handoff", "sandbox-1", "session-1"),
|
||||
];
|
||||
|
||||
for (const key of keys) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe("malformed URI handling", () => {
|
|||
fetch: async (_req: Request): Promise<Response> => {
|
||||
// Simulate what happens when rivetkit's router encounters a malformed URI
|
||||
throw new URIError("URI malformed");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const safeFetch = async (req: Request): Promise<Response> => {
|
||||
|
|
@ -30,7 +30,7 @@ describe("malformed URI handling", () => {
|
|||
const mockApp = {
|
||||
fetch: async (_req: Request): Promise<Response> => {
|
||||
throw new TypeError("some other error");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const safeFetch = async (req: Request): Promise<Response> => {
|
||||
|
|
@ -51,7 +51,7 @@ describe("malformed URI handling", () => {
|
|||
const mockApp = {
|
||||
fetch: async (_req: Request): Promise<Response> => {
|
||||
return new Response("OK", { status: 200 });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const safeFetch = async (req: Request): Promise<Response> => {
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ function makeConfig(): AppConfig {
|
|||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
backup_retention_days: 7
|
||||
backup_retention_days: 7,
|
||||
},
|
||||
providers: {
|
||||
local: {},
|
||||
daytona: { image: "ubuntu:24.04" }
|
||||
}
|
||||
daytona: { image: "ubuntu:24.04" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ describe("provider registry", () => {
|
|||
apiKey: "test-token",
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(registry.defaultProviderId()).toBe("daytona");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,33 +3,23 @@ import { normalizeRemoteUrl, repoIdFromRemote } from "../src/services/repo.js";
|
|||
|
||||
describe("normalizeRemoteUrl", () => {
|
||||
test("accepts GitHub shorthand owner/repo", () => {
|
||||
expect(normalizeRemoteUrl("rivet-dev/openhandoff")).toBe(
|
||||
"https://github.com/rivet-dev/openhandoff.git"
|
||||
);
|
||||
expect(normalizeRemoteUrl("rivet-dev/openhandoff")).toBe("https://github.com/rivet-dev/openhandoff.git");
|
||||
});
|
||||
|
||||
test("accepts github.com/owner/repo without scheme", () => {
|
||||
expect(normalizeRemoteUrl("github.com/rivet-dev/openhandoff")).toBe(
|
||||
"https://github.com/rivet-dev/openhandoff.git"
|
||||
);
|
||||
expect(normalizeRemoteUrl("github.com/rivet-dev/openhandoff")).toBe("https://github.com/rivet-dev/openhandoff.git");
|
||||
});
|
||||
|
||||
test("canonicalizes GitHub repo URLs without .git", () => {
|
||||
expect(normalizeRemoteUrl("https://github.com/rivet-dev/openhandoff")).toBe(
|
||||
"https://github.com/rivet-dev/openhandoff.git"
|
||||
);
|
||||
expect(normalizeRemoteUrl("https://github.com/rivet-dev/openhandoff")).toBe("https://github.com/rivet-dev/openhandoff.git");
|
||||
});
|
||||
|
||||
test("canonicalizes GitHub non-clone URLs (e.g. /tree/main)", () => {
|
||||
expect(normalizeRemoteUrl("https://github.com/rivet-dev/openhandoff/tree/main")).toBe(
|
||||
"https://github.com/rivet-dev/openhandoff.git"
|
||||
);
|
||||
expect(normalizeRemoteUrl("https://github.com/rivet-dev/openhandoff/tree/main")).toBe("https://github.com/rivet-dev/openhandoff.git");
|
||||
});
|
||||
|
||||
test("does not rewrite scp-style ssh remotes", () => {
|
||||
expect(normalizeRemoteUrl("git@github.com:rivet-dev/openhandoff.git")).toBe(
|
||||
"git@github.com:rivet-dev/openhandoff.git"
|
||||
);
|
||||
expect(normalizeRemoteUrl("git@github.com:rivet-dev/openhandoff.git")).toBe("git@github.com:rivet-dev/openhandoff.git");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
normalizeParentBranch,
|
||||
parentLookupFromStack,
|
||||
sortBranchesForOverview,
|
||||
} from "../src/actors/project/stack-model.js";
|
||||
import { normalizeParentBranch, parentLookupFromStack, sortBranchesForOverview } from "../src/actors/project/stack-model.js";
|
||||
|
||||
describe("stack-model", () => {
|
||||
it("normalizes self-parent references to null", () => {
|
||||
|
|
@ -33,12 +29,6 @@ describe("stack-model", () => {
|
|||
{ branchName: "cycle-b", parentBranch: "cycle-a", updatedAt: 250 },
|
||||
]);
|
||||
|
||||
expect(rows.map((row) => row.branchName)).toEqual([
|
||||
"main",
|
||||
"feature/a",
|
||||
"feature/b",
|
||||
"cycle-a",
|
||||
"cycle-b",
|
||||
]);
|
||||
expect(rows.map((row) => row.branchName)).toEqual(["main", "feature/a", "feature/b", "cycle-a", "cycle-b"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,11 +24,7 @@ function createRepo(): { repoPath: string } {
|
|||
return { repoPath };
|
||||
}
|
||||
|
||||
async function waitForWorkspaceRows(
|
||||
ws: any,
|
||||
workspaceId: string,
|
||||
expectedCount: number
|
||||
) {
|
||||
async function waitForWorkspaceRows(ws: any, workspaceId: string, expectedCount: number) {
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
const rows = await ws.listHandoffs({ workspaceId });
|
||||
if (rows.length >= expectedCount) {
|
||||
|
|
@ -40,18 +36,16 @@ async function waitForWorkspaceRows(
|
|||
}
|
||||
|
||||
describe("workspace isolation", () => {
|
||||
it.skipIf(!runActorIntegration)(
|
||||
"keeps handoff lists isolated by workspace",
|
||||
async (t) => {
|
||||
it.skipIf(!runActorIntegration)("keeps handoff lists isolated by workspace", async (t) => {
|
||||
const testDriver = createTestDriver();
|
||||
createTestRuntimeContext(testDriver);
|
||||
|
||||
const { client } = await setupTest(t, registry);
|
||||
const wsA = await client.workspace.getOrCreate(workspaceKey("alpha"), {
|
||||
createWithInput: "alpha"
|
||||
createWithInput: "alpha",
|
||||
});
|
||||
const wsB = await client.workspace.getOrCreate(workspaceKey("beta"), {
|
||||
createWithInput: "beta"
|
||||
createWithInput: "beta",
|
||||
});
|
||||
|
||||
const { repoPath } = createRepo();
|
||||
|
|
@ -64,7 +58,7 @@ describe("workspace isolation", () => {
|
|||
task: "task A",
|
||||
providerId: "daytona",
|
||||
explicitBranchName: "feature/a",
|
||||
explicitTitle: "A"
|
||||
explicitTitle: "A",
|
||||
});
|
||||
|
||||
await wsB.createHandoff({
|
||||
|
|
@ -73,7 +67,7 @@ describe("workspace isolation", () => {
|
|||
task: "task B",
|
||||
providerId: "daytona",
|
||||
explicitBranchName: "feature/b",
|
||||
explicitTitle: "B"
|
||||
explicitTitle: "B",
|
||||
});
|
||||
|
||||
const aRows = await waitForWorkspaceRows(wsA, "alpha", 1);
|
||||
|
|
@ -84,6 +78,5 @@ describe("workspace isolation", () => {
|
|||
expect(aRows[0]?.workspaceId).toBe("alpha");
|
||||
expect(bRows[0]?.workspaceId).toBe("beta");
|
||||
expect(aRows[0]?.handoffId).not.toBe(bRows[0]?.handoffId);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue