OrcaCloudView.test.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * Tests for OrcaCloudView — the RFC 8628 device-pairing flow: disconnected
  3. * (Connect button), pairing (code + approval link + waiting), a completed
  4. * poll flipping to connected, a denied poll surfacing an error, and disconnect.
  5. */
  6. import { describe, it, expect } from 'vitest';
  7. import { screen, waitFor, fireEvent } from '@testing-library/react';
  8. import { http, HttpResponse } from 'msw';
  9. import { server } from '../mocks/server';
  10. import { render } from '../utils';
  11. import { OrcaCloudView } from '../../components/OrcaCloudView';
  12. const DEVICE_START = {
  13. user_code: 'ABCD-EF12',
  14. verification_uri: 'https://cloud.orcaslicer.com/app/settings',
  15. verification_uri_complete: 'https://cloud.orcaslicer.com/app/settings?user_code=ABCD-EF12',
  16. interval: 5,
  17. expires_in: 600,
  18. };
  19. const NO_PROFILES = { filament: [], printer: [], process: [] };
  20. const disconnectedStatus = () =>
  21. http.get('/api/v1/orca-cloud/status', () =>
  22. HttpResponse.json({ connected: false, email: null, user_id: null }),
  23. );
  24. describe('OrcaCloudView', () => {
  25. it('shows the Connect button when not connected', async () => {
  26. server.use(disconnectedStatus());
  27. render(<OrcaCloudView />);
  28. await waitFor(() => {
  29. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  30. });
  31. });
  32. it('shows the pairing code and approval link after clicking Connect', async () => {
  33. server.use(
  34. disconnectedStatus(),
  35. http.post('/api/v1/orca-cloud/device/start', () => HttpResponse.json(DEVICE_START)),
  36. http.post('/api/v1/orca-cloud/device/poll', () =>
  37. HttpResponse.json({ status: 'authorization_pending', connected: false, email: null, user_id: null }),
  38. ),
  39. );
  40. render(<OrcaCloudView />);
  41. await waitFor(() => {
  42. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  43. });
  44. fireEvent.click(screen.getByRole('button', { name: /Connect Orca Cloud/i }));
  45. await waitFor(() => {
  46. expect(screen.getByText('ABCD-EF12')).toBeInTheDocument();
  47. });
  48. expect(screen.getByText(/Waiting for you to approve/i)).toBeInTheDocument();
  49. // The approval link points at the verification_uri_complete.
  50. expect(screen.getByRole('link', { name: /Open Orca Cloud approval page/i })).toHaveAttribute(
  51. 'href',
  52. DEVICE_START.verification_uri_complete,
  53. );
  54. });
  55. it('flips to connected once a poll returns complete', async () => {
  56. let connected = false;
  57. server.use(
  58. http.get('/api/v1/orca-cloud/status', () =>
  59. HttpResponse.json(
  60. connected
  61. ? { connected: true, email: null, user_id: 'user-123' }
  62. : { connected: false, email: null, user_id: null },
  63. ),
  64. ),
  65. http.post('/api/v1/orca-cloud/device/start', () => HttpResponse.json(DEVICE_START)),
  66. http.post('/api/v1/orca-cloud/device/poll', () => {
  67. connected = true;
  68. return HttpResponse.json({ status: 'complete', connected: true, email: null, user_id: 'user-123' });
  69. }),
  70. http.get('/api/v1/orca-cloud/profiles', () => HttpResponse.json(NO_PROFILES)),
  71. );
  72. render(<OrcaCloudView />);
  73. await waitFor(() => {
  74. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  75. });
  76. fireEvent.click(screen.getByRole('button', { name: /Connect Orca Cloud/i }));
  77. // The immediate first poll returns complete → status invalidates and the
  78. // connected view (with the Disconnect control) appears. We key off the
  79. // Disconnect button rather than the banner text, since the success toast
  80. // also renders "Connected to Orca Cloud".
  81. await waitFor(() => {
  82. expect(screen.getByRole('button', { name: /Disconnect/i })).toBeInTheDocument();
  83. });
  84. });
  85. it('surfaces an error and returns to Connect when the pairing is denied', async () => {
  86. server.use(
  87. disconnectedStatus(),
  88. http.post('/api/v1/orca-cloud/device/start', () => HttpResponse.json(DEVICE_START)),
  89. http.post('/api/v1/orca-cloud/device/poll', () =>
  90. HttpResponse.json({ status: 'access_denied', connected: false, email: null, user_id: null }),
  91. ),
  92. );
  93. render(<OrcaCloudView />);
  94. await waitFor(() => {
  95. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  96. });
  97. fireEvent.click(screen.getByRole('button', { name: /Connect Orca Cloud/i }));
  98. await waitFor(() => {
  99. expect(screen.getByText(/denied/i)).toBeInTheDocument();
  100. });
  101. // Back on the Connect card, not stuck on the waiting screen.
  102. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  103. expect(screen.queryByText(/Waiting for you to approve/i)).not.toBeInTheDocument();
  104. });
  105. it('clears the connection on Disconnect', async () => {
  106. let connected = true;
  107. server.use(
  108. http.get('/api/v1/orca-cloud/status', () =>
  109. HttpResponse.json(
  110. connected
  111. ? { connected: true, email: null, user_id: 'user-123' }
  112. : { connected: false, email: null, user_id: null },
  113. ),
  114. ),
  115. http.get('/api/v1/orca-cloud/profiles', () => HttpResponse.json(NO_PROFILES)),
  116. http.post('/api/v1/orca-cloud/logout', () => {
  117. connected = false;
  118. return HttpResponse.json({ success: true });
  119. }),
  120. );
  121. render(<OrcaCloudView />);
  122. await waitFor(() => {
  123. expect(screen.getByRole('button', { name: /Disconnect/i })).toBeInTheDocument();
  124. });
  125. fireEvent.click(screen.getByRole('button', { name: /Disconnect/i }));
  126. await waitFor(() => {
  127. expect(screen.getByRole('button', { name: /Connect Orca Cloud/i })).toBeInTheDocument();
  128. });
  129. });
  130. });