Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
192 changes: 190 additions & 2 deletions wework/src/features/workbench/WorkbenchProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,9 @@ function ArchiveRemoteRuntimeTaskProbe() {
>
archive remote task
</button>
<button type="button" onClick={() => void workbench.refreshWorkLists()}>
refresh work lists
</button>
</div>
)
}
Expand Down Expand Up @@ -2804,6 +2807,39 @@ describe('WorkbenchProvider runtime tasks', () => {
expect(screen.getByTestId('device-status')).toHaveTextContent('online')
})

test('does not trigger a cloud sync for socket device events', async () => {
let streamHandlers: ChatStreamHandlers = {}
const subscribe = vi.fn((handlers: ChatStreamHandlers) => {
streamHandlers = handlers
return vi.fn()
})
const cloudListRuntimeWork = vi.fn().mockResolvedValue({
projects: [],
chats: [],
totalTasks: 0,
})
const services = createWorkbenchServices({
chatStream: {
subscribe,
} as unknown as WorkbenchServices['chatStream'],
cloudBackgroundApi: {
listTeams: vi.fn().mockResolvedValue([]),
listDevices: vi.fn().mockResolvedValue([createDevice()]),
listRuntimeWork: cloudListRuntimeWork,
},
})

renderWorkbench(<DeviceStatusProbe />, services)

await waitFor(() => expect(cloudListRuntimeWork).toHaveBeenCalledTimes(1))

await act(async () => {
streamHandlers.onDeviceSlotUpdate?.({ device_id: 'device-1' })
})

expect(cloudListRuntimeWork).toHaveBeenCalledTimes(1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the socket-event test exercise and await the refresh.

Line 2837 uses optional chaining, so the test passes when onDeviceSlotUpdate is not registered. The callback also starts void refreshDevices(...), so the test does not explicitly wait for the local refresh to complete. A cloud request from a regression can occur after Line 2840.

Assert that the handler is registered, invoke it without optional chaining, and wait for a positive local-refresh signal, such as the device-list mock, before checking the cloud call count.

Based on the previous review comment, this repeats the same optional-handler assertion gap.

Proposed assertion
+    expect(streamHandlers.onDeviceSlotUpdate).toEqual(expect.any(Function))
+
     await act(async () => {
-      streamHandlers.onDeviceSlotUpdate?.({ device_id: 'device-1' })
+      streamHandlers.onDeviceSlotUpdate!({ device_id: 'device-1' })
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/workbench/WorkbenchProvider.test.tsx` around lines 2836 -
2840, Update the socket-event test around onDeviceSlotUpdate to assert the
handler is registered, then invoke it directly without optional chaining. Await
a positive local refresh signal, such as the device-list mock completing, before
asserting cloudListRuntimeWork was called once, ensuring any asynchronous
refresh has finished.

})

test('keeps the last confirmed online state when an offline event refresh fails', async () => {
let streamHandlers: ChatStreamHandlers = {}
const subscribe = vi.fn((handlers: ChatStreamHandlers) => {
Expand Down Expand Up @@ -6476,7 +6512,7 @@ describe('WorkbenchProvider runtime tasks', () => {
await waitFor(() => expect(screen.getByTestId('archive-result')).toHaveTextContent('archived'))
})

test('does not restore an archived remote task from the previous cloud snapshot', async () => {
test('archives a remote task locally without triggering a cloud sync', async () => {
const remoteRuntimeWork: RuntimeWorkListResponse = {
projects: [
{
Expand Down Expand Up @@ -6540,15 +6576,167 @@ describe('WorkbenchProvider runtime tasks', () => {
await userEvent.click(screen.getByText('archive remote task'))

await waitFor(() => expect(runtimeWorkApi.archiveConversation).toHaveBeenCalledTimes(1))
await waitFor(() => expect(cloudListRuntimeWork).toHaveBeenCalledTimes(2))
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('')
expect(cloudListRuntimeWork).toHaveBeenCalledTimes(1)

await userEvent.click(screen.getByText('refresh work lists'))
await waitFor(() => expect(cloudListRuntimeWork).toHaveBeenCalledTimes(2))
postArchiveCloudWork.resolve({ projects: [], chats: [], totalTasks: 0 })
await waitFor(() =>
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('')
)
})

test('keeps an archived remote task hidden when the local list refresh fails', async () => {
const remoteRuntimeWork: RuntimeWorkListResponse = {
projects: [
{
project: { key: 'remote-project', name: 'Remote Wegent' },
deviceWorkspaces: [
{
deviceId: 'remote-device',
deviceName: '10.201.3.200',
deviceStatus: 'online',
available: true,
workspacePath: '/srv/Wegent',
workspaceSource: 'remote',
remoteHostId: 'remote-device',
tasks: [
{
taskId: 'remote-task',
workspacePath: '/srv/Wegent',
title: 'Remote task',
runtime: 'codex',
},
],
},
],
totalTasks: 1,
},
],
chats: [],
totalTasks: 1,
}
const runtimeWorkApi = createRuntimeWorkApiMock({
listRuntimeWork: vi.fn().mockRejectedValue(new Error('local list unavailable')),
})
const services = createWorkbenchServices({
runtimeWorkApi: runtimeWorkApi as WorkbenchServices['runtimeWorkApi'],
cloudBackgroundApi: {
listTeams: vi.fn().mockResolvedValue([]),
listDevices: vi.fn().mockResolvedValue([
createDevice({
id: 2,
device_id: 'remote-device',
name: '10.201.3.200',
status: 'online',
is_default: false,
device_type: 'remote',
}),
]),
listRuntimeWork: vi.fn().mockResolvedValue(remoteRuntimeWork),
},
})

renderWorkbench(<ArchiveRemoteRuntimeTaskProbe />, services)

await waitFor(() =>
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('Remote task')
)
runtimeWorkApi.listRuntimeWork.mockClear()
await userEvent.click(screen.getByText('archive remote task'))

await waitFor(() => expect(runtimeWorkApi.archiveConversation).toHaveBeenCalledTimes(1))
expect(runtimeWorkApi.listRuntimeWork).toHaveBeenCalledTimes(1)
await waitFor(() =>
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('')
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})

test('does not trigger a cloud sync for stream events on an archived task', async () => {
const remoteRuntimeWork: RuntimeWorkListResponse = {
projects: [
{
project: { key: 'remote-project', name: 'Remote Wegent' },
deviceWorkspaces: [
{
deviceId: 'remote-device',
deviceName: '10.201.3.200',
deviceStatus: 'online',
available: true,
workspacePath: '/srv/Wegent',
workspaceSource: 'remote',
remoteHostId: 'remote-device',
tasks: [
{
taskId: 'remote-task',
workspacePath: '/srv/Wegent',
title: 'Remote task',
runtime: 'codex',
},
],
},
],
totalTasks: 1,
},
],
chats: [],
totalTasks: 1,
}
let streamHandlers: ChatStreamHandlers = {}
const subscribe = vi.fn((handlers: ChatStreamHandlers) => {
if (hasRuntimeStreamHandler(handlers)) streamHandlers = handlers
return vi.fn()
})
const cloudListRuntimeWork = vi.fn().mockResolvedValue(remoteRuntimeWork)
const runtimeWorkApi = createRuntimeWorkApiMock({
listRuntimeWork: vi.fn().mockResolvedValue({ projects: [], chats: [], totalTasks: 0 }),
})
const services = createWorkbenchServices({
runtimeWorkApi: runtimeWorkApi as WorkbenchServices['runtimeWorkApi'],
chatStream: {
subscribe,
} as unknown as WorkbenchServices['chatStream'],
cloudBackgroundApi: {
listTeams: vi.fn().mockResolvedValue([]),
listDevices: vi.fn().mockResolvedValue([
createDevice({
id: 2,
device_id: 'remote-device',
name: '10.201.3.200',
status: 'online',
is_default: false,
device_type: 'remote',
}),
]),
listRuntimeWork: cloudListRuntimeWork,
},
})

renderWorkbench(<ArchiveRemoteRuntimeTaskProbe />, services)

await waitFor(() =>
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('Remote task')
)
await userEvent.click(screen.getByText('archive remote task'))
await waitFor(() => expect(runtimeWorkApi.archiveConversation).toHaveBeenCalledTimes(1))
await waitFor(() =>
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('')
)
expect(cloudListRuntimeWork).toHaveBeenCalledTimes(1)

expect(typeof streamHandlers.onRuntimeGoalCleared).toBe('function')
await act(async () => {
streamHandlers.onRuntimeGoalCleared!({
deviceId: 'remote-device',
taskId: 'remote-task',
})
})

expect(cloudListRuntimeWork).toHaveBeenCalledTimes(1)
expect(screen.getByTestId('archive-remote-task-titles')).toHaveTextContent('')
})

test('renders streaming runtime task chunks when the socket connects after chat start', async () => {
let streamHandlers: ChatStreamHandlers = {}
const subscribe = vi.fn((handlers: ChatStreamHandlers) => {
Expand Down
4 changes: 2 additions & 2 deletions wework/src/features/workbench/WorkbenchProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ export function WorkbenchProvider({
remoteProjectSyncSignatureRef.current = signature
void executorClient.runtime
.syncRuntimeRemoteProjects({ deviceId: localRuntimeStateDeviceId, projects })
.then(refreshWorkLists)
.then(() => refreshWorkLists())
.catch(error => {
remoteProjectSyncSignatureRef.current = ''
console.warn('[Wework] Failed to sync remote projects into Codex global state', error)
Expand Down Expand Up @@ -1215,7 +1215,7 @@ export function WorkbenchProvider({
)
const stableRefreshWorkLists = useStableEvent(refreshWorkLists)
const refreshRuntimeWorkLists = useStableEvent((address: RuntimeTaskAddress) => {
void stableRefreshWorkLists().catch(error => {
void stableRefreshWorkLists({ syncCloud: false }).catch(error => {
console.warn('[Wework] Runtime work list refresh failed', {
deviceId: address.deviceId,
taskId: address.taskId,
Expand Down
Loading
Loading