/**
* Tests for OrcaCloudView component — covers the four UI phases of the
* paste-based PKCE handshake: disconnected, awaiting-paste, connected,
* and disconnect.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { screen, waitFor, fireEvent } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
import { render } from '../utils';
import { OrcaCloudView } from '../../components/OrcaCloudView';
// JSDOM doesn't implement window.open; the connect flow opens the auth URL
// in a new tab so we stub it to capture the call.
beforeEach(() => {
vi.stubGlobal('open', vi.fn());
});
const noProfilesResponse = { profiles: [] };
describe('OrcaCloudView', () => {
it('shows all four sign-in options when not connected', async () => {
server.use(
http.get('/api/v1/orca-cloud/status', () =>
HttpResponse.json({ connected: false, email: null, user_id: null }),
),
);
render();
await waitFor(() => {
expect(screen.getByText(/Connect to Orca Cloud/i)).toBeInTheDocument();
});
expect(screen.getByRole('button', { name: /Sign in with Google/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Sign in with Apple/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Sign in with GitHub/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Sign in with email and password/i })).toBeInTheDocument();
});
it('passes the selected OAuth provider to auth/start', async () => {
let receivedProvider: string | undefined;
server.use(
http.get('/api/v1/orca-cloud/status', () =>
HttpResponse.json({ connected: false, email: null, user_id: null }),
),
http.post('/api/v1/orca-cloud/auth/start', async ({ request }) => {
const body = (await request.json()) as { provider?: string };
receivedProvider = body.provider;
return HttpResponse.json({ auth_url: 'https://auth.orcaslicer.com/auth/v1/authorize?test=1' });
}),
);
render();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Sign in with Apple/i })).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Sign in with Apple/i }));
await waitFor(() => {
expect(receivedProvider).toBe('apple');
});
expect(window.open).toHaveBeenCalledWith(
'https://auth.orcaslicer.com/auth/v1/authorize?test=1',
'_blank',
'noopener,noreferrer',
);
});
it('connects via email and password without the paste flow', async () => {
let connected = false;
let receivedCreds: { email?: string; password?: string } = {};
server.use(
http.get('/api/v1/orca-cloud/status', () =>
HttpResponse.json(
connected
? { connected: true, email: 'martin@example.com', user_id: 'u1' }
: { connected: false, email: null, user_id: null },
),
),
http.post('/api/v1/orca-cloud/auth/password', async ({ request }) => {
receivedCreds = (await request.json()) as { email?: string; password?: string };
connected = true;
return HttpResponse.json({ connected: true, email: 'martin@example.com', user_id: 'u1' });
}),
http.get('/api/v1/orca-cloud/profiles', () => HttpResponse.json(noProfilesResponse)),
);
render();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Sign in with email and password/i })).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Sign in with email and password/i }));
// The password form replaces the provider picker.
await waitFor(() => {
expect(screen.getByLabelText(/^Email$/i)).toBeInTheDocument();
});
fireEvent.change(screen.getByLabelText(/^Email$/i), { target: { value: 'martin@example.com' } });
fireEvent.change(screen.getByLabelText(/^Password$/i), { target: { value: 'hunter2' } });
// Click the submit button inside the form (not the picker's email button).
const submitButtons = screen.getAllByRole('button', { name: /^Sign in$/i });
fireEvent.click(submitButtons[submitButtons.length - 1]);
await waitFor(() => {
expect(screen.getByText('martin@example.com')).toBeInTheDocument();
});
expect(receivedCreds).toEqual({ email: 'martin@example.com', password: 'hunter2' });
});
it('rejects a URL without a code parameter with a client-side error', async () => {
server.use(
http.get('/api/v1/orca-cloud/status', () =>
HttpResponse.json({ connected: false, email: null, user_id: null }),
),
http.post('/api/v1/orca-cloud/auth/start', () =>
HttpResponse.json({ auth_url: 'https://auth.orcaslicer.com/auth/v1/authorize?test=1' }),
),
);
render();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Sign in with Google/i })).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Sign in with Google/i }));
await waitFor(() => {
expect(screen.getByPlaceholderText(/http:\/\/localhost:41172/i)).toBeInTheDocument();
});
const textarea = screen.getByPlaceholderText(/http:\/\/localhost:41172/i);
// Paste something with no ?code= — the client-side guard should fire
// before we hit the server.
fireEvent.change(textarea, { target: { value: 'http://localhost:41172/callback?error=denied' } });
fireEvent.click(screen.getByRole('button', { name: /Finish connecting/i }));
await waitFor(() => {
expect(screen.getByText(/does not look like an Orca Cloud callback/i)).toBeInTheDocument();
});
});
it('shows the connected state with the email after a successful paste', async () => {
// Start disconnected; after a successful finish the status query refetches
// and returns connected. MSW lets us swap handlers mid-test.
let connected = false;
server.use(
http.get('/api/v1/orca-cloud/status', () => {
return HttpResponse.json(
connected
? { connected: true, email: 'martin@example.com', user_id: 'u1' }
: { connected: false, email: null, user_id: null },
);
}),
http.post('/api/v1/orca-cloud/auth/start', () =>
HttpResponse.json({ auth_url: 'https://auth.orcaslicer.com/auth/v1/authorize?test=1' }),
),
http.post('/api/v1/orca-cloud/auth/finish', () => {
connected = true;
return HttpResponse.json({ connected: true, email: 'martin@example.com', user_id: 'u1' });
}),
http.get('/api/v1/orca-cloud/profiles', () => HttpResponse.json(noProfilesResponse)),
);
render();
await waitFor(() => {
expect(screen.getByRole('button', { name: /Sign in with Google/i })).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Sign in with Google/i }));
await waitFor(() => {
expect(screen.getByPlaceholderText(/http:\/\/localhost:41172/i)).toBeInTheDocument();
});
fireEvent.change(screen.getByPlaceholderText(/http:\/\/localhost:41172/i), {
target: { value: 'http://localhost:41172/callback?code=ABC&state=XYZ' },
});
fireEvent.click(screen.getByRole('button', { name: /Finish connecting/i }));
// After the finish call resolves, the status query is invalidated and
// refetches connected=true → the connection banner appears with the email.
await waitFor(() => {
expect(screen.getByText('martin@example.com')).toBeInTheDocument();
});
expect(screen.getByRole('button', { name: /Disconnect/i })).toBeInTheDocument();
});
it('clears the connection on Disconnect', async () => {
let connected = true;
server.use(
http.get('/api/v1/orca-cloud/status', () =>
HttpResponse.json(
connected
? { connected: true, email: 'martin@example.com', user_id: 'u1' }
: { connected: false, email: null, user_id: null },
),
),
http.get('/api/v1/orca-cloud/profiles', () => HttpResponse.json(noProfilesResponse)),
http.post('/api/v1/orca-cloud/logout', () => {
connected = false;
return HttpResponse.json({ success: true });
}),
);
render();
await waitFor(() => {
expect(screen.getByText('martin@example.com')).toBeInTheDocument();
});
fireEvent.click(screen.getByRole('button', { name: /Disconnect/i }));
await waitFor(() => {
expect(screen.getByText(/Connect to Orca Cloud/i)).toBeInTheDocument();
});
});
});