Files
paseo/packages/server/src/server/session.workspace-git-watch.test.ts
T
Mohamed Boudra 5aa98f13f2 fix: update workspace test stubs for emission dedup gating
Add missing lastEmittedByWorkspaceId to test subscription stubs,
update activityAt assertion to null, and adjust dedup test to expect
deduplicated emissions.
2026-04-12 12:53:54 +07:00

390 lines
11 KiB
TypeScript

import { describe, expect, test, vi } from "vitest";
import { Session } from "./session.js";
import type {
WorkspaceGitListener,
WorkspaceGitRuntimeSnapshot,
WorkspaceGitService,
} from "./workspace-git-service.js";
function createWorkspaceRuntimeSnapshot(
cwd: string,
overrides?: {
git?: Partial<WorkspaceGitRuntimeSnapshot["git"]>;
github?: Partial<WorkspaceGitRuntimeSnapshot["github"]>;
},
): WorkspaceGitRuntimeSnapshot {
const base: WorkspaceGitRuntimeSnapshot = {
cwd,
git: {
isGit: true,
repoRoot: cwd,
mainRepoRoot: null,
currentBranch: "main",
remoteUrl: "https://github.com/acme/repo.git",
isPaseoOwnedWorktree: false,
isDirty: false,
aheadBehind: { ahead: 0, behind: 0 },
aheadOfOrigin: 0,
behindOfOrigin: 0,
diffStat: { additions: 1, deletions: 0 },
},
github: {
featuresEnabled: true,
pullRequest: null,
error: null,
refreshedAt: "2026-04-12T00:00:00.000Z",
},
};
return {
cwd,
git: {
...base.git,
...overrides?.git,
},
github: {
...base.github,
...overrides?.github,
pullRequest:
overrides?.github && "pullRequest" in overrides.github
? (overrides.github.pullRequest ?? null)
: base.github.pullRequest,
error:
overrides?.github && "error" in overrides.github
? (overrides.github.error ?? null)
: base.github.error,
},
};
}
function createSessionForWorkspaceGitWatchTests(): {
session: Session;
emitted: Array<{ type: string; payload: unknown }>;
workspaceGitService: WorkspaceGitService & {
subscribe: ReturnType<typeof vi.fn>;
peekSnapshot: ReturnType<typeof vi.fn>;
getSnapshot: ReturnType<typeof vi.fn>;
refresh: ReturnType<typeof vi.fn>;
dispose: ReturnType<typeof vi.fn>;
};
subscriptions: Array<{
params: { cwd: string };
listener: WorkspaceGitListener;
unsubscribe: ReturnType<typeof vi.fn>;
}>;
} {
const emitted: Array<{ type: string; payload: unknown }> = [];
const projects = new Map<string, any>();
const workspaces = new Map<string, any>();
const subscriptions: Array<{
params: { cwd: string };
listener: WorkspaceGitListener;
unsubscribe: ReturnType<typeof vi.fn>;
}> = [];
const logger = {
child: () => logger,
trace: vi.fn(),
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const workspaceGitService = {
subscribe: vi.fn(async (params: { cwd: string }, listener: WorkspaceGitListener) => {
const unsubscribe = vi.fn();
subscriptions.push({
params,
listener,
unsubscribe,
});
return {
initial: createWorkspaceRuntimeSnapshot(params.cwd),
unsubscribe,
};
}),
peekSnapshot: vi.fn((cwd: string) => createWorkspaceRuntimeSnapshot(cwd)),
getSnapshot: vi.fn(async (cwd: string) => createWorkspaceRuntimeSnapshot(cwd)),
refresh: vi.fn(async () => {}),
dispose: vi.fn(),
};
const session = new Session({
clientId: "test-client",
onMessage: (message) => emitted.push(message as any),
logger: logger as any,
downloadTokenStore: {} as any,
pushTokenStore: {} as any,
paseoHome: "/tmp/paseo-test",
agentManager: {
subscribe: () => () => {},
listAgents: () => [],
getAgent: () => null,
} as any,
agentStorage: {
list: async () => [],
get: async () => null,
} as any,
projectRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => Array.from(projects.values()),
get: async (projectId: string) => projects.get(projectId) ?? null,
upsert: async (record: any) => {
projects.set(record.projectId, record);
},
archive: async (projectId: string, archivedAt: string) => {
const existing = projects.get(projectId);
if (!existing) {
return;
}
projects.set(projectId, {
...existing,
archivedAt,
updatedAt: archivedAt,
});
},
remove: async (projectId: string) => {
projects.delete(projectId);
},
} as any,
workspaceRegistry: {
initialize: async () => {},
existsOnDisk: async () => true,
list: async () => Array.from(workspaces.values()),
get: async (workspaceId: string) => workspaces.get(workspaceId) ?? null,
upsert: async (record: any) => {
workspaces.set(record.workspaceId, record);
},
archive: async (workspaceId: string, archivedAt: string) => {
const existing = workspaces.get(workspaceId);
if (!existing) {
return;
}
workspaces.set(workspaceId, {
...existing,
archivedAt,
updatedAt: archivedAt,
});
},
remove: async (workspaceId: string) => {
workspaces.delete(workspaceId);
},
} as any,
checkoutDiffManager: {
subscribe: async () => ({
initial: { cwd: "/tmp", files: [], error: null },
unsubscribe: () => {},
}),
scheduleRefreshForCwd: () => {},
getMetrics: () => ({
checkoutDiffTargetCount: 0,
checkoutDiffSubscriptionCount: 0,
checkoutDiffWatcherCount: 0,
checkoutDiffFallbackRefreshTargetCount: 0,
}),
dispose: () => {},
} as any,
workspaceGitService: workspaceGitService as any,
mcpBaseUrl: null,
stt: null,
tts: null,
terminalManager: null,
}) as any;
session.listAgentPayloads = async () => [];
return {
session,
emitted,
workspaceGitService: workspaceGitService as any,
subscriptions,
};
}
describe("workspace git watch targets", () => {
test("emits one workspace_update when the workspace git service emits a changed snapshot", async () => {
const { session, emitted, workspaceGitService, subscriptions } =
createSessionForWorkspaceGitWatchTests();
const sessionAny = session as any;
sessionAny.buildProjectPlacement = async (cwd: string) => ({
projectKey: cwd,
projectName: "repo",
checkout: {
cwd,
isGit: true,
currentBranch: "main",
remoteUrl: "https://github.com/acme/repo.git",
worktreeRoot: cwd,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
},
});
sessionAny.workspaceUpdatesSubscription = {
subscriptionId: "sub-1",
filter: undefined,
isBootstrapping: false,
pendingUpdatesByWorkspaceId: new Map(),
lastEmittedByWorkspaceId: new Map(),
};
sessionAny.reconcileActiveWorkspaceRecords = async () => new Set();
let descriptor = {
id: "/tmp/repo",
projectId: "/tmp/repo",
projectDisplayName: "repo",
projectRootPath: "/tmp/repo",
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: { additions: 1, deletions: 0 },
};
sessionAny.buildWorkspaceDescriptorMap = async () => new Map([[descriptor.id, descriptor]]);
await sessionAny.ensureWorkspaceRegistered("/tmp/repo");
expect(workspaceGitService.subscribe).toHaveBeenCalledWith(
{ cwd: "/tmp/repo" },
expect.any(Function),
);
descriptor = {
...descriptor,
name: "renamed-branch",
};
subscriptions[0]?.listener(
createWorkspaceRuntimeSnapshot("/tmp/repo", {
git: {
currentBranch: "renamed-branch",
},
}),
);
await Promise.resolve();
await Promise.resolve();
const workspaceUpdates = emitted.filter(
(message) => message.type === "workspace_update",
) as any[];
expect(workspaceUpdates).toHaveLength(1);
expect(workspaceUpdates[0]?.payload).toMatchObject({
kind: "upsert",
workspace: {
id: "/tmp/repo",
name: "renamed-branch",
diffStat: { additions: 1, deletions: 0 },
},
});
await session.cleanup();
});
test("checkout_pr_status_request reads pull request status from the workspace git service snapshot", async () => {
const { session, emitted, workspaceGitService } = createSessionForWorkspaceGitWatchTests();
workspaceGitService.getSnapshot.mockResolvedValue(
createWorkspaceRuntimeSnapshot("/tmp/repo", {
github: {
featuresEnabled: true,
pullRequest: {
url: "https://github.com/acme/repo/pull/456",
title: "Runtime centralization",
state: "merged",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: true,
},
refreshedAt: "2026-04-12T00:05:00.000Z",
},
}),
);
await session.handleMessage({
type: "checkout_pr_status_request",
cwd: "/tmp/repo",
requestId: "req-pr-status",
});
expect(workspaceGitService.getSnapshot).toHaveBeenCalledWith("/tmp/repo");
expect(
emitted.find((message) => message.type === "checkout_pr_status_response")?.payload,
).toEqual({
cwd: "/tmp/repo",
status: {
url: "https://github.com/acme/repo/pull/456",
title: "Runtime centralization",
state: "merged",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: true,
},
githubFeaturesEnabled: true,
error: null,
requestId: "req-pr-status",
});
});
test("checkout_pr_status_request explicitly refreshes the focused workspace before reading runtime data", async () => {
const { session, emitted, workspaceGitService } = createSessionForWorkspaceGitWatchTests();
let refreshed = false;
workspaceGitService.refresh.mockImplementation(async () => {
refreshed = true;
});
workspaceGitService.getSnapshot.mockImplementation(async (cwd: string) =>
createWorkspaceRuntimeSnapshot(cwd, {
github: {
pullRequest: refreshed
? {
url: "https://github.com/acme/repo/pull/457",
title: "After explicit refresh",
state: "merged",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: true,
}
: {
url: "https://github.com/acme/repo/pull/456",
title: "Before explicit refresh",
state: "open",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: false,
},
refreshedAt: refreshed ? "2026-04-12T00:10:00.000Z" : "2026-04-12T00:05:00.000Z",
},
}),
);
await session.handleMessage({
type: "checkout_pr_status_request",
cwd: "/tmp/repo",
requestId: "req-pr-refresh",
});
expect(workspaceGitService.refresh).toHaveBeenCalledWith("/tmp/repo", {
priority: "high",
});
expect(
emitted.find((message) => message.type === "checkout_pr_status_response")?.payload,
).toEqual({
cwd: "/tmp/repo",
status: {
url: "https://github.com/acme/repo/pull/457",
title: "After explicit refresh",
state: "merged",
baseRefName: "main",
headRefName: "workspace-git-service",
isMerged: true,
},
githubFeaturesEnabled: true,
error: null,
requestId: "req-pr-refresh",
});
});
});