Skip to content
Open
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
1 change: 0 additions & 1 deletion lib/coding-agent/__tests__/getCodingAgentPRState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe("getCodingAgentPRState", () => {
it("returns parsed state when key exists", async () => {
const state = {
status: "pr_created",
snapshotId: "snap_abc",
branch: "agent/fix-bug",
repo: "recoupable/api",
prs: [
Expand Down
7 changes: 3 additions & 4 deletions lib/coding-agent/__tests__/handleCodingAgentCallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("handleCodingAgentCallback", () => {
threadId: "slack:C123:1234567890.123456",
status: "pr_created",
branch: "agent/fix-bug-1234",
snapshotId: "snap_abc",

prs: [
{
repo: "recoupable/recoup-api",
Expand All @@ -113,7 +113,7 @@ describe("handleCodingAgentCallback", () => {
threadId: "slack:C123:1234567890.123456",
status: "pr_created",
branch: "agent/fix-bug-1234",
snapshotId: "snap_abc",

stdout: "Created branch agent/fix-bug-1234\nModified 3 files in api/\n",
prs: [
{
Expand Down Expand Up @@ -199,15 +199,14 @@ describe("handleCodingAgentCallback", () => {
const body = {
threadId: "slack:C123:1234567890.123456",
status: "updated",
snapshotId: "snap_new",
};
const request = makeRequest(body);

const response = await handleCodingAgentCallback(request);

expect(response.status).toBe(200);
expect(mockSetState).toHaveBeenCalledWith(
expect.objectContaining({ status: "pr_created", snapshotId: "snap_new" }),
expect.objectContaining({ status: "pr_created" }),
);
expect(mockPost).toHaveBeenCalledWith(expect.objectContaining({ card: expect.anything() }));
});
Expand Down
16 changes: 11 additions & 5 deletions lib/coding-agent/__tests__/handleGitHubWebhook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ const BASE_PAYLOAD = {
},
};

/**
*
* @param body
* @param event
* @param signature
*/
function makeRequest(body: unknown, event = "issue_comment", signature = "valid") {
return {
text: () => Promise.resolve(JSON.stringify(body)),
Expand Down Expand Up @@ -144,7 +150,7 @@ describe("handleGitHubWebhook", () => {
} as Response);
mockGetPRState.mockResolvedValue({
status: "pr_created",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/tasks",
prs: [{ repo: "recoupable/tasks", number: 66, url: "url", baseBranch: "main" }],
Expand All @@ -161,7 +167,7 @@ describe("handleGitHubWebhook", () => {
expect(mockTriggerUpdatePR).toHaveBeenCalledWith(
expect.objectContaining({
feedback: "make the button blue",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/tasks",
}),
Expand Down Expand Up @@ -191,7 +197,7 @@ describe("handleGitHubWebhook", () => {
};
mockGetPRState.mockResolvedValue({
status: "pr_created",
snapshotId: "snap_xyz",

branch: "feature/my-branch",
repo: "recoupable/api",
prs: [{ repo: "recoupable/api", number: 266, url: "url", baseBranch: "test" }],
Expand All @@ -208,7 +214,7 @@ describe("handleGitHubWebhook", () => {
expect(mockTriggerUpdatePR).toHaveBeenCalledWith(
expect.objectContaining({
feedback: "fix the typo",
snapshotId: "snap_xyz",

branch: "feature/my-branch",
repo: "recoupable/api",
}),
Expand All @@ -233,7 +239,7 @@ describe("handleGitHubWebhook", () => {
};
const originalState = {
status: "pr_created" as const,
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/tasks",
prs: [{ repo: "recoupable/tasks", number: 99, url: "url", baseBranch: "main" }],
Expand Down
47 changes: 1 addition & 46 deletions lib/coding-agent/__tests__/handleMergeSuccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,21 @@ vi.mock("../prState", () => ({
deleteCodingAgentPRState: (...args: unknown[]) => mockDeletePRState(...args),
}));

const mockUpsertAccountSnapshot = vi.fn();
vi.mock("@/lib/supabase/account_snapshots/upsertAccountSnapshot", () => ({
upsertAccountSnapshot: (...args: unknown[]) => mockUpsertAccountSnapshot(...args),
}));

const { handleMergeSuccess } = await import("../handleMergeSuccess");

beforeEach(() => {
vi.clearAllMocks();
mockUpsertAccountSnapshot.mockResolvedValue({ data: {}, error: null });
});

describe("handleMergeSuccess", () => {
it("deletes PR state and persists snapshot", async () => {
it("deletes PR state", async () => {
await handleMergeSuccess({
status: "pr_created",
branch: "agent/fix-bug",
snapshotId: "snap_abc123",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
});

expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug");
expect(mockUpsertAccountSnapshot).toHaveBeenCalledWith({
account_id: "04e3aba9-c130-4fb8-8b92-34e95d43e66b",
snapshot_id: "snap_abc123",
expires_at: expect.any(String),
});
});

it("deletes PR state for all repos when PRs span multiple repos", async () => {
Expand All @@ -50,38 +38,6 @@ describe("handleMergeSuccess", () => {
expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/chat", "agent/fix-bug");
});

it("skips snapshot persistence when snapshotId is not in state", async () => {
await handleMergeSuccess({
status: "pr_created",
branch: "agent/fix-bug",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
});

expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug");
expect(mockUpsertAccountSnapshot).not.toHaveBeenCalled();
});

it("logs error but does not throw when snapshot persistence fails", async () => {
mockUpsertAccountSnapshot.mockResolvedValue({
data: null,
error: { message: "db error", code: "500" },
});
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});

await handleMergeSuccess({
status: "pr_created",
branch: "agent/fix-bug",
snapshotId: "snap_abc123",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
});

expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining("failed to persist snapshot"),
expect.anything(),
);
consoleSpy.mockRestore();
});

it("does not throw when deleteCodingAgentPRState throws", async () => {
mockDeletePRState.mockRejectedValue(new Error("Redis connection failed"));
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
Expand All @@ -90,7 +46,6 @@ describe("handleMergeSuccess", () => {
handleMergeSuccess({
status: "pr_created",
branch: "agent/fix-bug",
snapshotId: "snap_abc123",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
}),
).resolves.toBeUndefined();
Expand Down
6 changes: 3 additions & 3 deletions lib/coding-agent/__tests__/handlePRCreated.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("handlePRCreated", () => {
threadId: "slack:C123:ts",
status: "pr_created",
branch: "agent/fix-bug",
snapshotId: "snap_abc",

prs: [
{
repo: "recoupable/api",
Expand All @@ -57,7 +57,7 @@ describe("handlePRCreated", () => {
expect.objectContaining({
status: "pr_created",
branch: "agent/fix-bug",
snapshotId: "snap_abc",

}),
);

Expand All @@ -66,7 +66,7 @@ describe("handlePRCreated", () => {
"agent/fix-bug",
expect.objectContaining({
status: "pr_created",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/api",
}),
Expand Down
7 changes: 3 additions & 4 deletions lib/coding-agent/__tests__/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe("registerOnNewMention", () => {
state: Promise.resolve({
status: "pr_created",
prompt: "original prompt",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
prs: [
{
Expand All @@ -113,7 +113,7 @@ describe("registerOnNewMention", () => {
expect(mockTriggerUpdatePR).toHaveBeenCalledWith(
expect.objectContaining({
feedback: "remove the Project Structure changes",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/tasks",
}),
Expand All @@ -130,7 +130,6 @@ describe("registerOnNewMention", () => {
const { getCodingAgentPRState } = await import("../prState");
vi.mocked(getCodingAgentPRState).mockResolvedValue({
status: "pr_created",
snapshotId: "snap_abc",
branch: "agent/fix-bug",
repo: "recoupable/api",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
Expand Down Expand Up @@ -159,7 +158,7 @@ describe("registerOnNewMention", () => {
expect(mockTriggerUpdatePR).toHaveBeenCalledWith(
expect.objectContaining({
feedback: "make the button blue",
snapshotId: "snap_abc",

branch: "agent/fix-bug",
repo: "recoupable/api",
}),
Expand Down
52 changes: 2 additions & 50 deletions lib/coding-agent/__tests__/onMergeAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,12 @@ vi.mock("../handleMergeSuccess", () => ({
handleMergeSuccess: (...args: unknown[]) => mockHandleMergeSuccess(...args),
}));

const mockDeletePRState = vi.fn();
vi.mock("../prState", () => ({
deleteCodingAgentPRState: (...args: unknown[]) => mockDeletePRState(...args),
}));

const mockUpsertAccountSnapshot = vi.fn();
vi.mock("@/lib/supabase/account_snapshots/upsertAccountSnapshot", () => ({
upsertAccountSnapshot: (...args: unknown[]) => mockUpsertAccountSnapshot(...args),
}));

const { registerOnMergeAction } = await import("../handlers/onMergeAction");

beforeEach(() => {
vi.clearAllMocks();
process.env.GITHUB_TOKEN = "ghp_test";
mockHandleMergeSuccess.mockResolvedValue(undefined);
mockUpsertAccountSnapshot.mockResolvedValue({ data: {}, error: null });
});

/**
Expand Down Expand Up @@ -73,7 +62,7 @@ describe("registerOnMergeAction", () => {
status: "pr_created",
prompt: "fix bug",
branch: "agent/fix-bug",
snapshotId: "snap_abc123",

prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
}),
post: vi.fn(),
Expand All @@ -85,7 +74,7 @@ describe("registerOnMergeAction", () => {
expect(mockMergeGithubPR).toHaveBeenCalledWith("recoupable/api", 42, "ghp_test");
expect(mockThread.setState).toHaveBeenCalledWith({ status: "merged", prs: [] });
expect(mockHandleMergeSuccess).toHaveBeenCalledWith(
expect.objectContaining({ branch: "agent/fix-bug", snapshotId: "snap_abc123" }),
expect.objectContaining({ branch: "agent/fix-bug" }),
);
expect(mockThread.post).toHaveBeenCalledWith("✅ recoupable/api#42 merged.");
});
Expand Down Expand Up @@ -165,41 +154,4 @@ describe("registerOnMergeAction", () => {
);
});

it("merge button click triggers snapshot upsert in Supabase via handleMergeSuccess", async () => {
// Use the real handleMergeSuccess instead of the mock to verify the full chain
const { handleMergeSuccess: realHandleMergeSuccess } =
await vi.importActual<typeof import("../handleMergeSuccess")>("../handleMergeSuccess");
mockHandleMergeSuccess.mockImplementation(realHandleMergeSuccess);
mockMergeGithubPR.mockResolvedValue({ ok: true });

const bot = createMockBot();
registerOnMergeAction(bot);
const handler = bot.onAction.mock.calls[0][0];

const state = {
status: "pr_created" as const,
prompt: "fix bug",
branch: "agent/fix-bug",
snapshotId: "snap_abc123",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
};

const mockThread = {
state: Promise.resolve(state),
post: vi.fn(),
setState: vi.fn(),
};

await handler({ thread: mockThread, actionId: "merge_pr:recoupable/api#42" });

// Verify the full chain: merge → handleMergeSuccess → upsertAccountSnapshot
expect(mockMergeGithubPR).toHaveBeenCalledWith("recoupable/api", 42, "ghp_test");
expect(mockDeletePRState).toHaveBeenCalledWith("recoupable/api", "agent/fix-bug");
expect(mockUpsertAccountSnapshot).toHaveBeenCalledWith(
expect.objectContaining({
snapshot_id: "snap_abc123",
expires_at: expect.any(String),
}),
);
});
});
2 changes: 0 additions & 2 deletions lib/coding-agent/__tests__/onSubscribedMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ describe("registerOnSubscribedMessage", () => {
state: Promise.resolve({
status: "pr_created",
prompt: "fix bug",
snapshotId: "snap_abc",
branch: "agent/fix-bug",
prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }],
}),
Expand All @@ -61,7 +60,6 @@ describe("registerOnSubscribedMessage", () => {
expect(triggerUpdatePR).toHaveBeenCalledWith(
expect.objectContaining({
feedback: "make the button blue",
snapshotId: "snap_abc",
repo: "recoupable/api",
}),
);
Expand Down
6 changes: 3 additions & 3 deletions lib/coding-agent/__tests__/resolvePRState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("resolvePRState", () => {
status: "pr_created",
prompt: "fix bug",
branch: "agent/fix",
snapshotId: "snap_1",

prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }],
};
const thread = createMockThread(threadState);
Expand All @@ -45,7 +45,7 @@ describe("resolvePRState", () => {
const thread = createMockThread(null);
const prState = {
status: "pr_created",
snapshotId: "snap_1",

branch: "agent/fix",
repo: "recoupable/api",
prs: [{ repo: "recoupable/api", number: 1, url: "url", baseBranch: "test" }],
Expand All @@ -59,7 +59,7 @@ describe("resolvePRState", () => {
status: "pr_created",
prompt: "",
branch: "agent/fix",
snapshotId: "snap_1",

prs: prState.prs,
});
});
Expand Down
1 change: 0 additions & 1 deletion lib/coding-agent/__tests__/setCodingAgentPRState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ describe("setCodingAgentPRState", () => {
it("stores serialized state in Redis", async () => {
const state = {
status: "pr_created" as const,
snapshotId: "snap_abc",
branch: "agent/fix-bug",
repo: "recoupable/api",
prs: [{ repo: "recoupable/api", number: 42, url: "url", baseBranch: "test" }],
Expand Down
Loading
Loading