1
0

api.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { Page, WebSocketRoute } from '@playwright/test'
  2. // Mock data
  3. export const mockPatterns = [
  4. { path: 'patterns/star.thr', name: 'star.thr', category: 'geometric', date_modified: Date.now(), coordinates_count: 150 },
  5. { path: 'patterns/spiral.thr', name: 'spiral.thr', category: 'organic', date_modified: Date.now(), coordinates_count: 200 },
  6. { path: 'patterns/wave.thr', name: 'wave.thr', category: 'organic', date_modified: Date.now(), coordinates_count: 175 },
  7. ]
  8. export const mockPlaylists = {
  9. default: ['patterns/star.thr', 'patterns/spiral.thr'],
  10. favorites: ['patterns/star.thr'],
  11. }
  12. // Mutable status for simulating playback
  13. let currentStatus = {
  14. is_running: false,
  15. is_paused: false,
  16. current_file: null as string | null,
  17. speed: 100,
  18. progress: 0,
  19. playlist_mode: false,
  20. playlist_name: null as string | null,
  21. queue: [] as string[],
  22. connection_status: 'connected',
  23. theta: 0,
  24. rho: 0.5,
  25. }
  26. export function resetMockStatus() {
  27. currentStatus = {
  28. is_running: false,
  29. is_paused: false,
  30. current_file: null,
  31. speed: 100,
  32. progress: 0,
  33. playlist_mode: false,
  34. playlist_name: null,
  35. queue: [],
  36. connection_status: 'connected',
  37. theta: 0,
  38. rho: 0.5,
  39. }
  40. }
  41. export async function setupApiMocks(page: Page) {
  42. // Pattern endpoints
  43. await page.route('**/list_theta_rho_files_with_metadata', async route => {
  44. await route.fulfill({ json: mockPatterns })
  45. })
  46. await page.route('**/list_theta_rho_files', async route => {
  47. await route.fulfill({ json: mockPatterns.map(p => ({ name: p.name, path: p.path })) })
  48. })
  49. await page.route('**/preview_thr_batch', async route => {
  50. const request = route.request()
  51. const body = request.postDataJSON() as { files: string[] }
  52. const previews: Record<string, unknown> = {}
  53. for (const file of body?.files || []) {
  54. previews[file] = {
  55. image_data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
  56. first_coordinate: { x: 0, y: 0 },
  57. last_coordinate: { x: 100, y: 100 },
  58. }
  59. }
  60. await route.fulfill({ json: previews })
  61. })
  62. await page.route('**/run_theta_rho', async route => {
  63. const body = route.request().postDataJSON() as { file_name?: string; file?: string }
  64. const file = body?.file_name || body?.file || null
  65. currentStatus.is_running = true
  66. currentStatus.current_file = file
  67. await route.fulfill({ json: { success: true } })
  68. })
  69. // Playlist endpoints
  70. await page.route('**/list_all_playlists', async route => {
  71. await route.fulfill({ json: Object.keys(mockPlaylists) })
  72. })
  73. await page.route('**/get_playlist**', async route => {
  74. const url = new URL(route.request().url())
  75. const name = url.searchParams.get('name') || ''
  76. await route.fulfill({
  77. json: { name, files: mockPlaylists[name as keyof typeof mockPlaylists] || [] }
  78. })
  79. })
  80. await page.route('**/run_playlist', async route => {
  81. const body = route.request().postDataJSON() as { playlist_name?: string; name?: string }
  82. // Support both playlist_name (actual API) and name (legacy)
  83. const playlistName = body?.playlist_name || body?.name
  84. const playlist = mockPlaylists[playlistName as keyof typeof mockPlaylists]
  85. if (playlist && playlist.length > 0) {
  86. currentStatus.is_running = true
  87. currentStatus.playlist_mode = true
  88. currentStatus.playlist_name = playlistName || null
  89. currentStatus.current_file = playlist[0]
  90. currentStatus.queue = playlist.slice(1)
  91. }
  92. await route.fulfill({ json: { success: true } })
  93. })
  94. // Playback control endpoints
  95. await page.route('**/pause_execution', async route => {
  96. currentStatus.is_paused = true
  97. await route.fulfill({ json: { success: true } })
  98. })
  99. await page.route('**/resume_execution', async route => {
  100. currentStatus.is_paused = false
  101. await route.fulfill({ json: { success: true } })
  102. })
  103. await page.route('**/stop_execution', async route => {
  104. currentStatus.is_running = false
  105. currentStatus.is_paused = false
  106. currentStatus.current_file = null
  107. currentStatus.playlist_mode = false
  108. currentStatus.queue = []
  109. await route.fulfill({ json: { success: true } })
  110. })
  111. // Status endpoint
  112. await page.route('**/serial_status', async route => {
  113. await route.fulfill({ json: currentStatus })
  114. })
  115. // Table info (for TableContext)
  116. await page.route('**/api/table-info', async route => {
  117. await route.fulfill({
  118. json: { id: 'test-table', name: 'Test Table', version: '1.0.0' }
  119. })
  120. })
  121. // Settings
  122. await page.route('**/api/settings', async route => {
  123. await route.fulfill({ json: { app: { name: 'Dune Weaver' } } })
  124. })
  125. // Known tables
  126. await page.route('**/api/known-tables', async route => {
  127. await route.fulfill({ json: { tables: [] } })
  128. })
  129. // Pattern history
  130. await page.route('**/api/pattern_history_all', async route => {
  131. await route.fulfill({ json: {} })
  132. })
  133. // Logs endpoint
  134. await page.route('**/api/logs**', async route => {
  135. await route.fulfill({ json: { logs: [], total: 0, has_more: false } })
  136. })
  137. // Pattern history (individual)
  138. await page.route('**/api/pattern_history/**', async route => {
  139. await route.fulfill({ json: { actual_time_formatted: null, speed: null } })
  140. })
  141. // Serial ports
  142. await page.route('**/list_serial_ports', async route => {
  143. await route.fulfill({ json: [] })
  144. })
  145. // Static files - return 200 with placeholder
  146. await page.route('**/static/**', async route => {
  147. // Return a 1x1 transparent PNG for images
  148. const base64Png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
  149. await route.fulfill({
  150. status: 200,
  151. contentType: 'image/png',
  152. body: Buffer.from(base64Png, 'base64'),
  153. })
  154. })
  155. // WebSocket mocking - critical for bypassing the "Connecting to Backend" overlay
  156. // The Layout component shows a blocking overlay until WebSocket connects
  157. await page.routeWebSocket('**/ws/status', (ws: WebSocketRoute) => {
  158. // Don't connect to server - we're mocking everything
  159. // Send status updates to simulate backend status messages
  160. const statusMessage = JSON.stringify({
  161. type: 'status_update',
  162. data: {
  163. ...currentStatus,
  164. connection_status: true,
  165. is_homing: false,
  166. }
  167. })
  168. // Send initial status immediately after connection
  169. // The client's onopen handler will fire, setting isBackendConnected = true
  170. setTimeout(() => {
  171. ws.send(statusMessage)
  172. }, 100)
  173. // Send periodic updates
  174. const interval = setInterval(() => {
  175. ws.send(statusMessage)
  176. }, 1000)
  177. ws.onClose(() => {
  178. clearInterval(interval)
  179. })
  180. })
  181. // Mock other WebSocket endpoints
  182. await page.routeWebSocket('**/ws/logs', (_ws: WebSocketRoute) => {
  183. // Just accept the connection - don't need to send anything
  184. })
  185. await page.routeWebSocket('**/ws/cache-progress', (ws: WebSocketRoute) => {
  186. // Send "not running" status
  187. setTimeout(() => {
  188. ws.send(JSON.stringify({
  189. type: 'cache_progress',
  190. data: { is_running: false, stage: 'idle' }
  191. }))
  192. }, 100)
  193. })
  194. }
  195. export function getMockStatus() {
  196. return { ...currentStatus }
  197. }
  198. export function setMockStatus(updates: Partial<typeof currentStatus>) {
  199. Object.assign(currentStatus, updates)
  200. }