fix(app): handle nullable detail consumers
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import type { FunctionReturnType } from 'convex/server';
|
||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { proxyWorker } from '@/lib/agent-worker-proxy';
|
import { proxyWorker } from '@/lib/agent-worker-proxy';
|
||||||
import { convexAuthNextjsToken } from '@convex-dev/auth/nextjs/server';
|
import { convexAuthNextjsToken } from '@convex-dev/auth/nextjs/server';
|
||||||
@@ -35,7 +36,14 @@ export const POST = async (
|
|||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const details = await fetchQuery(api.threads.get, { threadId }, { token });
|
const details = (await fetchQuery(
|
||||||
|
api.threads.get,
|
||||||
|
{ threadId },
|
||||||
|
{ token },
|
||||||
|
)) as FunctionReturnType<typeof api.threads.get> | null;
|
||||||
|
if (details === null) {
|
||||||
|
return NextResponse.json({ error: 'Thread not found.' }, { status: 404 });
|
||||||
|
}
|
||||||
const latestJob = details.latestJob;
|
const latestJob = details.latestJob;
|
||||||
const canSendToWorker =
|
const canSendToWorker =
|
||||||
latestJob &&
|
latestJob &&
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import type { FunctionReturnType } from 'convex/server';
|
||||||
import type { CSSProperties, PointerEvent as ReactPointerEvent } from 'react';
|
import type { CSSProperties, PointerEvent as ReactPointerEvent } from 'react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useMutation, useQuery } from 'convex/react';
|
import { useMutation, useQuery } from 'convex/react';
|
||||||
@@ -59,7 +60,10 @@ type PendingOverwrite = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AgentWorkspaceShell = ({ jobId }: { jobId: Id<'agentJobs'> }) => {
|
export const AgentWorkspaceShell = ({ jobId }: { jobId: Id<'agentJobs'> }) => {
|
||||||
const job = useQuery(api.agentJobs.get, { jobId });
|
const job = useQuery(api.agentJobs.get, { jobId }) as
|
||||||
|
| FunctionReturnType<typeof api.agentJobs.get>
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
const messages =
|
const messages =
|
||||||
useQuery(api.agentJobs.listMessages, { jobId, limit: 200 }) ?? [];
|
useQuery(api.agentJobs.listMessages, { jobId, limit: 200 }) ?? [];
|
||||||
const events =
|
const events =
|
||||||
@@ -323,6 +327,14 @@ export const AgentWorkspaceShell = ({ jobId }: { jobId: Id<'agentJobs'> }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (job === null) {
|
||||||
|
return (
|
||||||
|
<main className='text-muted-foreground p-6'>
|
||||||
|
This workspace was not found.
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const activeFile = activeFilePath ? files[activeFilePath] : undefined;
|
const activeFile = activeFilePath ? files[activeFilePath] : undefined;
|
||||||
const recoverWorkspace = async () => {
|
const recoverWorkspace = async () => {
|
||||||
if (!job.threadId) return;
|
if (!job.threadId) return;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { AgentWorkspaceShell } from '../../src/components/agent-workspace/agent-workspace-shell';
|
||||||
|
|
||||||
|
const { mockUseMutation, mockUseQuery } = vi.hoisted(() => ({
|
||||||
|
mockUseMutation: vi.fn(() => vi.fn()),
|
||||||
|
mockUseQuery: vi.fn(() => null),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('convex/react', () => ({
|
||||||
|
useMutation: mockUseMutation,
|
||||||
|
useQuery: mockUseQuery,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('sonner', () => ({
|
||||||
|
toast: {
|
||||||
|
error: vi.fn(),
|
||||||
|
success: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AgentWorkspaceShell', () => {
|
||||||
|
it('renders a missing state when the job query returns null', () => {
|
||||||
|
render(<AgentWorkspaceShell jobId={'job-1' as never} />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('This workspace was not found.'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { POST } from '../../src/app/api/threads/[threadId]/message/route';
|
||||||
|
|
||||||
|
const { mockAuthToken, mockFetchMutation, mockFetchQuery, mockProxyWorker } =
|
||||||
|
vi.hoisted(() => ({
|
||||||
|
mockAuthToken: vi.fn(),
|
||||||
|
mockFetchMutation: vi.fn(),
|
||||||
|
mockFetchQuery: vi.fn(),
|
||||||
|
mockProxyWorker: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@convex-dev/auth/nextjs/server', () => ({
|
||||||
|
convexAuthNextjsToken: mockAuthToken,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('convex/nextjs', () => ({
|
||||||
|
fetchMutation: mockFetchMutation,
|
||||||
|
fetchQuery: mockFetchQuery,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/lib/agent-worker-proxy', () => ({
|
||||||
|
proxyWorker: mockProxyWorker,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('thread message route', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockAuthToken.mockResolvedValue('token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 404 when the thread detail query returns null', async () => {
|
||||||
|
mockFetchQuery.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const response = await POST(
|
||||||
|
new Request('http://localhost/api/threads/thread-1/message', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ content: 'Hello' }),
|
||||||
|
}),
|
||||||
|
{ params: Promise.resolve({ threadId: 'thread-1' }) },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.status).toBe(404);
|
||||||
|
await expect(response.json()).resolves.toEqual({
|
||||||
|
error: 'Thread not found.',
|
||||||
|
});
|
||||||
|
expect(mockFetchMutation).not.toHaveBeenCalled();
|
||||||
|
expect(mockProxyWorker).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user