handlers.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /**
  2. * MSW request handlers for mocking API responses in tests.
  3. */
  4. import { http, HttpResponse } from 'msw';
  5. // Sample data
  6. const mockSmartPlugs = [
  7. {
  8. id: 1,
  9. name: 'Test Plug',
  10. ip_address: '192.168.1.100',
  11. printer_id: 1,
  12. enabled: true,
  13. auto_on: true,
  14. auto_off: true,
  15. off_delay_mode: 'time',
  16. off_delay_minutes: 5,
  17. off_temp_threshold: 70,
  18. username: null,
  19. password: null,
  20. power_alert_enabled: false,
  21. power_alert_high: null,
  22. power_alert_low: null,
  23. power_alert_last_triggered: null,
  24. schedule_enabled: false,
  25. schedule_on_time: null,
  26. schedule_off_time: null,
  27. last_state: 'ON',
  28. last_checked: null,
  29. auto_off_executed: false,
  30. auto_off_pending: false,
  31. auto_off_pending_since: null,
  32. created_at: '2024-01-01T00:00:00Z',
  33. updated_at: '2024-01-01T00:00:00Z',
  34. },
  35. ];
  36. const mockNotificationProviders = [
  37. {
  38. id: 1,
  39. name: 'Test Webhook',
  40. provider_type: 'webhook',
  41. enabled: true,
  42. config: { webhook_url: 'http://test.local/webhook' },
  43. on_print_start: true,
  44. on_print_complete: true,
  45. on_print_failed: true,
  46. on_print_stopped: false,
  47. on_print_progress: false,
  48. on_printer_offline: false,
  49. on_printer_error: false,
  50. on_ai_failure_detection: false,
  51. on_filament_low: false,
  52. on_maintenance_due: false,
  53. on_ams_humidity_high: false,
  54. on_ams_temperature_high: false,
  55. on_ams_ht_humidity_high: false,
  56. on_ams_ht_temperature_high: false,
  57. quiet_hours_enabled: false,
  58. quiet_hours_start: null,
  59. quiet_hours_end: null,
  60. daily_digest_enabled: false,
  61. daily_digest_time: null,
  62. printer_id: null,
  63. last_success: null,
  64. last_error: null,
  65. last_error_at: null,
  66. created_at: '2024-01-01T00:00:00Z',
  67. updated_at: '2024-01-01T00:00:00Z',
  68. },
  69. ];
  70. const mockPrinters = [
  71. {
  72. id: 1,
  73. name: 'Test Printer',
  74. serial_number: '00M09A000000000',
  75. ip_address: '192.168.1.200',
  76. is_active: true,
  77. model: 'X1C',
  78. nozzle_count: 1,
  79. auto_archive: true,
  80. location: null,
  81. created_at: '2024-01-01T00:00:00Z',
  82. updated_at: '2024-01-01T00:00:00Z',
  83. },
  84. ];
  85. // The bulk routes the API client batches per-printer reads into (api/batch.ts)
  86. // answer by asking the matching single-printer route for each id, through
  87. // MSW again. A test that mocks the single route therefore shapes the bulk
  88. // answer too, and ids the single route rejects are left out, as the backend
  89. // leaves out printers outside the caller's scope.
  90. async function fanOut(request: Request, single: (id: string) => string): Promise<Map<string, unknown>> {
  91. const url = new URL(request.url);
  92. const ids = (url.searchParams.get('ids') ?? '').split(',').filter(Boolean);
  93. const answers = new Map<string, unknown>();
  94. await Promise.all(
  95. ids.map(async (id) => {
  96. const headers = new Headers(request.headers);
  97. // Marks the request as the mock's own, for tests counting what the app sent
  98. headers.set('x-test-fan-out', '1');
  99. const response = await fetch(new URL(single(id), url), { headers });
  100. if (response.ok) answers.set(id, await response.json());
  101. }),
  102. );
  103. return answers;
  104. }
  105. const asRecord = (answers: Map<string, unknown>) => HttpResponse.json(Object.fromEntries(answers));
  106. const asList = (answers: Map<string, unknown>) => HttpResponse.json([...answers.values()]);
  107. const bulkHandlers = [
  108. http.get('*/api/v1/bulk/printer-statuses', async ({ request }) =>
  109. asList(await fanOut(request, (id) => `/api/v1/printers/${id}/status`))),
  110. http.get('*/api/v1/bulk/slot-presets', async ({ request }) =>
  111. asRecord(await fanOut(request, (id) => `/api/v1/printers/${id}/slot-presets`))),
  112. http.get('*/api/v1/bulk/ams-labels', async ({ request }) =>
  113. asRecord(await fanOut(request, (id) => `/api/v1/printers/${id}/ams-labels`))),
  114. http.get('*/api/v1/bulk/card-plugs', async ({ request }) => {
  115. const plugs = await fanOut(request, (id) => `/api/v1/smart-plugs/by-printer/${id}`);
  116. const scripts = await fanOut(request, (id) => `/api/v1/smart-plugs/by-printer/${id}/scripts`);
  117. return HttpResponse.json(
  118. Object.fromEntries([...plugs].map(([id, plug]) => [id, { plug, scripts: scripts.get(id) ?? [] }])),
  119. );
  120. }),
  121. http.get('*/api/v1/bulk/ha-sensor-readings', async ({ request }) =>
  122. asRecord(await fanOut(request, (id) => `/api/v1/ha-sensors/by-printer/${id}/readings`))),
  123. http.get('*/api/v1/bulk/firmware-updates', async ({ request }) =>
  124. asList(await fanOut(request, (id) => `/api/v1/firmware/updates/${id}`))),
  125. http.get('*/api/v1/bulk/printer-queues', async ({ request }) => {
  126. const status = new URL(request.url).searchParams.get('status') ?? '';
  127. return asRecord(await fanOut(request, (id) => `/api/v1/queue/?printer_id=${id}&status=${status}`));
  128. }),
  129. http.get('*/api/v1/bulk/archive-folders', async ({ request }) =>
  130. asRecord(await fanOut(request, (id) => `/api/v1/library/folders/by-archive/${id}`))),
  131. ];
  132. export const handlers = [
  133. ...bulkHandlers,
  134. // ========================================================================
  135. // Smart Plugs
  136. // ========================================================================
  137. http.get('/api/v1/smart-plugs/', () => {
  138. return HttpResponse.json(mockSmartPlugs);
  139. }),
  140. http.get('/api/v1/smart-plugs/:id', ({ params }) => {
  141. const plug = mockSmartPlugs.find((p) => p.id === Number(params.id));
  142. if (!plug) {
  143. return new HttpResponse(null, { status: 404 });
  144. }
  145. return HttpResponse.json(plug);
  146. }),
  147. http.post('/api/v1/smart-plugs/', async ({ request }) => {
  148. const body = (await request.json()) as Record<string, unknown>;
  149. const { id: _id, ...baseData } = mockSmartPlugs[0];
  150. const newPlug = {
  151. id: mockSmartPlugs.length + 1,
  152. ...baseData,
  153. ...body,
  154. };
  155. return HttpResponse.json(newPlug);
  156. }),
  157. http.patch('/api/v1/smart-plugs/:id', async ({ params, request }) => {
  158. const body = (await request.json()) as Record<string, unknown>;
  159. const plug = mockSmartPlugs.find((p) => p.id === Number(params.id));
  160. if (!plug) {
  161. return new HttpResponse(null, { status: 404 });
  162. }
  163. return HttpResponse.json({ ...plug, ...body });
  164. }),
  165. http.delete('/api/v1/smart-plugs/:id', ({ params }) => {
  166. const index = mockSmartPlugs.findIndex((p) => p.id === Number(params.id));
  167. if (index === -1) {
  168. return new HttpResponse(null, { status: 404 });
  169. }
  170. return HttpResponse.json({ success: true });
  171. }),
  172. http.get('/api/v1/smart-plugs/:id/status', () => {
  173. return HttpResponse.json({
  174. state: 'ON',
  175. reachable: true,
  176. device_name: 'Test Plug',
  177. energy: {
  178. power: 150.5,
  179. voltage: 120.0,
  180. current: 1.25,
  181. today: 2.5,
  182. total: 100.0,
  183. },
  184. });
  185. }),
  186. http.post('/api/v1/smart-plugs/:id/control', async ({ request }) => {
  187. const body = (await request.json()) as { action: string };
  188. return HttpResponse.json({
  189. success: true,
  190. action: body.action,
  191. });
  192. }),
  193. // ========================================================================
  194. // Notification Providers
  195. // ========================================================================
  196. http.get('/api/v1/notifications/', () => {
  197. return HttpResponse.json(mockNotificationProviders);
  198. }),
  199. http.get('/api/v1/notifications/:id', ({ params }) => {
  200. const provider = mockNotificationProviders.find(
  201. (p) => p.id === Number(params.id)
  202. );
  203. if (!provider) {
  204. return new HttpResponse(null, { status: 404 });
  205. }
  206. return HttpResponse.json(provider);
  207. }),
  208. http.post('/api/v1/notifications/', async ({ request }) => {
  209. const body = (await request.json()) as Record<string, unknown>;
  210. const { id: _id, ...baseData } = mockNotificationProviders[0];
  211. const newProvider = {
  212. id: mockNotificationProviders.length + 1,
  213. ...baseData,
  214. ...body,
  215. };
  216. return HttpResponse.json(newProvider);
  217. }),
  218. http.patch('/api/v1/notifications/:id', async ({ params, request }) => {
  219. const body = (await request.json()) as Record<string, unknown>;
  220. const provider = mockNotificationProviders.find(
  221. (p) => p.id === Number(params.id)
  222. );
  223. if (!provider) {
  224. return new HttpResponse(null, { status: 404 });
  225. }
  226. return HttpResponse.json({ ...provider, ...body });
  227. }),
  228. http.delete('/api/v1/notifications/:id', ({ params }) => {
  229. const index = mockNotificationProviders.findIndex(
  230. (p) => p.id === Number(params.id)
  231. );
  232. if (index === -1) {
  233. return new HttpResponse(null, { status: 404 });
  234. }
  235. return HttpResponse.json({ success: true });
  236. }),
  237. http.post('/api/v1/notifications/:id/test', () => {
  238. return HttpResponse.json({
  239. success: true,
  240. message: 'Test notification sent',
  241. });
  242. }),
  243. // ========================================================================
  244. // Printers
  245. // ========================================================================
  246. http.get('/api/v1/printers/', () => {
  247. return HttpResponse.json(mockPrinters);
  248. }),
  249. http.get('/api/v1/printers/:id', ({ params }) => {
  250. const printer = mockPrinters.find((p) => p.id === Number(params.id));
  251. if (!printer) {
  252. return new HttpResponse(null, { status: 404 });
  253. }
  254. return HttpResponse.json(printer);
  255. }),
  256. http.get('/api/v1/printers/:id/status', ({ params }) => {
  257. return HttpResponse.json({
  258. id: Number(params.id),
  259. name: 'Test Printer',
  260. connected: true,
  261. state: 'IDLE',
  262. progress: 0,
  263. layer_num: 0,
  264. total_layers: 0,
  265. temperatures: {
  266. nozzle: 25,
  267. bed: 25,
  268. chamber: 25,
  269. },
  270. remaining_time: 0,
  271. filename: null,
  272. });
  273. }),
  274. // ========================================================================
  275. // Settings
  276. // ========================================================================
  277. http.get('/api/v1/settings/', () => {
  278. return HttpResponse.json({
  279. auto_archive: true,
  280. save_thumbnails: true,
  281. capture_finish_photo: true,
  282. default_filament_cost: 25.0,
  283. currency: 'USD',
  284. ams_humidity_good: 40,
  285. ams_humidity_fair: 60,
  286. ams_temp_good: 30,
  287. ams_temp_fair: 35,
  288. ams_temp_alarm: null,
  289. });
  290. }),
  291. http.patch('/api/v1/settings/', async ({ request }) => {
  292. const body = (await request.json()) as Record<string, unknown>;
  293. return HttpResponse.json(body);
  294. }),
  295. // ========================================================================
  296. // Auth
  297. // ========================================================================
  298. http.get('*/api/v1/auth/status', () => {
  299. return HttpResponse.json({
  300. auth_enabled: false,
  301. requires_setup: false,
  302. });
  303. }),
  304. http.get('/api/v1/auth/me', () => {
  305. return HttpResponse.json({
  306. id: 1,
  307. username: 'admin',
  308. role: 'admin',
  309. is_active: true,
  310. is_admin: true,
  311. groups: [{ id: 1, name: 'Administrators' }],
  312. permissions: [],
  313. created_at: '2024-01-01T00:00:00Z',
  314. });
  315. }),
  316. // ========================================================================
  317. // Groups
  318. // ========================================================================
  319. http.get('/api/v1/groups/', () => {
  320. return HttpResponse.json([
  321. {
  322. id: 1,
  323. name: 'Administrators',
  324. description: 'Full access to all features',
  325. permissions: ['printers:read', 'settings:update', 'users:create'],
  326. is_system: true,
  327. created_at: '2024-01-01T00:00:00Z',
  328. updated_at: '2024-01-01T00:00:00Z',
  329. },
  330. {
  331. id: 2,
  332. name: 'Operators',
  333. description: 'Control printers and manage content',
  334. permissions: ['printers:read', 'printers:control'],
  335. is_system: true,
  336. created_at: '2024-01-01T00:00:00Z',
  337. updated_at: '2024-01-01T00:00:00Z',
  338. },
  339. {
  340. id: 3,
  341. name: 'Viewers',
  342. description: 'Read-only access',
  343. permissions: ['printers:read'],
  344. is_system: true,
  345. created_at: '2024-01-01T00:00:00Z',
  346. updated_at: '2024-01-01T00:00:00Z',
  347. },
  348. ]);
  349. }),
  350. http.get('/api/v1/groups/permissions', () => {
  351. return HttpResponse.json({
  352. 'Printers': ['printers:read', 'printers:create', 'printers:update', 'printers:delete', 'printers:control'],
  353. 'Archives': ['archives:read', 'archives:create', 'archives:update', 'archives:delete'],
  354. 'Settings': ['settings:read', 'settings:update'],
  355. });
  356. }),
  357. http.post('/api/v1/groups/', async ({ request }) => {
  358. const body = (await request.json()) as Record<string, unknown>;
  359. return HttpResponse.json({
  360. id: 4,
  361. ...body,
  362. is_system: false,
  363. created_at: '2024-01-01T00:00:00Z',
  364. updated_at: '2024-01-01T00:00:00Z',
  365. });
  366. }),
  367. http.patch('/api/v1/groups/:id', async ({ params, request }) => {
  368. const body = (await request.json()) as Record<string, unknown>;
  369. return HttpResponse.json({
  370. id: Number(params.id),
  371. name: 'Updated Group',
  372. ...body,
  373. is_system: false,
  374. created_at: '2024-01-01T00:00:00Z',
  375. updated_at: '2024-01-01T00:00:00Z',
  376. });
  377. }),
  378. http.delete('/api/v1/groups/:id', () => {
  379. return new HttpResponse(null, { status: 204 });
  380. }),
  381. // ========================================================================
  382. // Discovery
  383. // ========================================================================
  384. http.get('/api/v1/discovery/info', () => {
  385. return HttpResponse.json({
  386. is_docker: false,
  387. ssdp_running: false,
  388. scan_running: false,
  389. subnets: ['192.168.1.0/24'],
  390. });
  391. }),
  392. // ========================================================================
  393. // Version / Health
  394. // ========================================================================
  395. // Announcements from the maintainers: none by default.
  396. http.get('/api/v1/announcements', () => {
  397. return HttpResponse.json({ visible: true, announcements: [] });
  398. }),
  399. http.get('/api/v1/version', () => {
  400. return HttpResponse.json({
  401. version: '0.1.5',
  402. build: 'test',
  403. });
  404. }),
  405. http.get('/health', () => {
  406. return HttpResponse.json({ status: 'healthy' });
  407. }),
  408. // ========================================================================
  409. // Archives
  410. // ========================================================================
  411. http.get('/api/v1/archives/:id/plates', ({ params }) => {
  412. const archiveId = Number(params.id);
  413. return HttpResponse.json({
  414. archive_id: Number.isFinite(archiveId) ? archiveId : 0,
  415. filename: 'sample.3mf',
  416. plates: [],
  417. is_multi_plate: false,
  418. });
  419. }),
  420. http.get('/api/v1/archives/:id/filament-requirements', () => {
  421. return HttpResponse.json([]);
  422. }),
  423. // ========================================================================
  424. // Library
  425. // ========================================================================
  426. http.get('/api/v1/library/stats', () => {
  427. return HttpResponse.json({
  428. total_files: 0,
  429. total_size: 0,
  430. total_folders: 0,
  431. });
  432. }),
  433. // ========================================================================
  434. // Read-on-mount fallbacks
  435. // ------------------------------------------------------------------------
  436. // Components fire background fetches when they mount (status badges,
  437. // notification-template loaders, oidc/ldap/2fa probes, etc.). Without
  438. // handlers these fall through to the real network and the rejected promise
  439. // surfaces as an ECONNREFUSED stack trace in test stderr. These minimal
  440. // disabled-state stubs silence the trace. Per-test handlers added via
  441. // server.use(...) still win.
  442. // ========================================================================
  443. // Lists → empty arrays
  444. http.get('/api/v1/archives/', () => HttpResponse.json([])),
  445. http.get('/api/v1/archives/last-per-printer', () => HttpResponse.json([])),
  446. http.get('/api/v1/queue/history', () => HttpResponse.json({ items: [], total: 0, locations: [] })),
  447. http.get('/api/v1/auth/oidc/providers', () => HttpResponse.json([])),
  448. http.get('/api/v1/auth/oidc/providers/all', () => HttpResponse.json([])),
  449. // OIDC icon proxy (#1333). Default returns a tiny PNG; individual tests
  450. // can override via server.use(...).
  451. http.get('/api/v1/auth/oidc/providers/:id/icon', () =>
  452. HttpResponse.arrayBuffer(new Uint8Array([0x89, 0x50, 0x4e, 0x47]).buffer, {
  453. headers: { 'Content-Type': 'image/png' },
  454. }),
  455. ),
  456. http.delete('/api/v1/auth/oidc/providers/:id/icon', () => new HttpResponse(null, { status: 204 })),
  457. http.post('/api/v1/auth/oidc/providers/:id/icon/refresh', ({ params }) =>
  458. HttpResponse.json({
  459. id: Number(params.id),
  460. name: 'MockProv',
  461. issuer_url: 'https://idp.example.com',
  462. client_id: 'c',
  463. scopes: 'openid',
  464. is_enabled: true,
  465. auto_create_users: false,
  466. auto_link_existing_accounts: false,
  467. email_claim: 'email',
  468. require_email_verified: true,
  469. has_icon: true,
  470. }),
  471. ),
  472. http.get('/api/v1/auth/tokens', () => HttpResponse.json([])),
  473. http.get('/api/v1/auth/tokens/all', () => HttpResponse.json([])),
  474. http.get('/api/v1/external-links/', () => HttpResponse.json([])),
  475. http.get('/api/v1/inventory/assignments', () => HttpResponse.json([])),
  476. http.get('/api/v1/inventory/catalog', () => HttpResponse.json([])),
  477. http.get('/api/v1/inventory/colors', () => HttpResponse.json([])),
  478. http.get('/api/v1/inventory/locations', () => HttpResponse.json([])),
  479. http.get('/api/v1/inventory/spools', () => HttpResponse.json([])),
  480. http.get('/api/v1/library/folders', () => HttpResponse.json([])),
  481. http.get('/api/v1/library/folders/by-archive/:id', () => HttpResponse.json([])),
  482. http.get('/api/v1/maintenance/overview', () => HttpResponse.json([])),
  483. http.get('/api/v1/makerworld/recent-imports', () => HttpResponse.json([])),
  484. http.get('/api/v1/notification-templates', () => HttpResponse.json([])),
  485. http.get('/api/v1/pending-uploads/', () => HttpResponse.json([])),
  486. http.get('/api/v1/printers/:id/ams-labels', () => HttpResponse.json([])),
  487. http.get('/api/v1/printers/:id/inventory-remain', () =>
  488. HttpResponse.json({ inventory_remain_g: {}, slot_materials: [] }),
  489. ),
  490. http.get('/api/v1/printers/:id/slot-presets', () => HttpResponse.json([])),
  491. http.get('/api/v1/smart-plugs/by-printer/:id', () => HttpResponse.json([])),
  492. http.get('/api/v1/smart-plugs/by-printer/:id/scripts', () => HttpResponse.json([])),
  493. http.get('/api/v1/spoolbuddy/devices', () => HttpResponse.json([])),
  494. http.get('/api/v1/spoolman/inventory/filaments', () => HttpResponse.json([])),
  495. http.get('/api/v1/spoolman/spools/linked', () => HttpResponse.json([])),
  496. http.get('/api/v1/spoolman/spools/unlinked', () => HttpResponse.json([])),
  497. http.get('/api/v1/users/', () => HttpResponse.json([])),
  498. http.get('/api/v1/users/slim', () => HttpResponse.json([])),
  499. // Status / object endpoints → minimal disabled-state responses
  500. http.get('/api/v1/archives/purge/settings', () =>
  501. HttpResponse.json({ enabled: false, retention_days: 0 })
  502. ),
  503. http.get('/api/v1/auth/2fa/status', () =>
  504. HttpResponse.json({ totp_enabled: false, email_otp_enabled: false, backup_codes_remaining: 0 })
  505. ),
  506. http.get('/api/v1/auth/advanced-auth/status', () =>
  507. HttpResponse.json({
  508. advanced_auth_enabled: false,
  509. smtp_configured: false,
  510. local_login_enabled: true,
  511. autologin_provider_id: null,
  512. })
  513. ),
  514. http.get('/api/v1/auth/ldap/status', () =>
  515. HttpResponse.json({ ldap_enabled: false, ldap_configured: false })
  516. ),
  517. http.get('/api/v1/cloud/status', () =>
  518. HttpResponse.json({ is_authenticated: false, email: null, region: null })
  519. ),
  520. http.get('/api/v1/firmware/updates/:id', () => HttpResponse.json(null)),
  521. http.get('/api/v1/github-backup/status', () =>
  522. HttpResponse.json({
  523. enabled: false,
  524. configured: false,
  525. last_backup_at: null,
  526. last_error: null,
  527. schedule_enabled: false,
  528. })
  529. ),
  530. http.get('/api/v1/library/trash', () => HttpResponse.json({ items: [], total: 0 })),
  531. http.get('/api/v1/library/trash/settings', () =>
  532. HttpResponse.json({ enabled: false, retention_days: 0 })
  533. ),
  534. http.get('/api/v1/obico/status', () =>
  535. HttpResponse.json({
  536. is_running: false,
  537. last_error: null,
  538. per_printer: {},
  539. thresholds: { low: 0, high: 0 },
  540. history: [],
  541. enabled: false,
  542. ml_url: '',
  543. sensitivity: 'medium',
  544. action: 'notify',
  545. poll_interval: 30,
  546. external_url_configured: false,
  547. })
  548. ),
  549. http.get('/api/v1/obico/printer-status', () =>
  550. HttpResponse.json({ enabled: false, monitored_printers: null, per_printer: {}, last_error: null })
  551. ),
  552. // Per-file project print progress (#1897) — empty means "no completed runs"
  553. http.get('/api/v1/projects/:id/file-progress', () => HttpResponse.json([])),
  554. http.get('/api/v1/printers/:id/current-print-user', () => HttpResponse.json(null)),
  555. // What a user who may not read /settings gets instead (useUiSettings).
  556. http.get('/api/v1/settings/ui-flags', () => HttpResponse.json({})),
  557. http.get('/api/v1/settings/ui-preferences', () => HttpResponse.json({})),
  558. http.get('/api/v1/settings/check-ffmpeg', () =>
  559. HttpResponse.json({ available: false, version: null })
  560. ),
  561. http.get('/api/v1/settings/mqtt/status', () =>
  562. HttpResponse.json({ enabled: false, connected: false, broker: '', port: 1883, topic_prefix: '' })
  563. ),
  564. // NOTE: /api/v1/settings/spoolman intentionally NOT stubbed globally —
  565. // src/__tests__/api/client.test.ts uses it as an auth-header canary with
  566. // its own setupServer instance, and a global default would race with that
  567. // file's runtime handlers. Tests that need this endpoint stub it locally.
  568. http.get('/api/v1/settings/virtual-printer', () => HttpResponse.json({})),
  569. http.get('/api/v1/spoolman/status', () =>
  570. HttpResponse.json({ enabled: false, connected: false, url: null })
  571. ),
  572. http.get('/api/v1/system/storage-usage', () =>
  573. HttpResponse.json({
  574. roots: [],
  575. total_bytes: 0,
  576. total_formatted: '0 B',
  577. categories: [],
  578. other_breakdown: [],
  579. scan_errors: 0,
  580. generated_at: '2024-01-01T00:00:00Z',
  581. cache: { hit: false, age_seconds: 0, max_age_seconds: 0 },
  582. })
  583. ),
  584. http.get('/api/v1/updates/check', () =>
  585. HttpResponse.json({ update_available: false, latest_version: null })
  586. ),
  587. http.get('/api/v1/updates/status', () =>
  588. HttpResponse.json({ update_available: false, latest_version: null })
  589. ),
  590. http.get('/api/v1/updates/version', () =>
  591. HttpResponse.json({ version: '0.1.5', commit: 'test' })
  592. ),
  593. http.get('/openapi.json', () =>
  594. HttpResponse.json({ openapi: '3.0.0', info: { title: 'Bambuddy', version: '0.1.5' }, paths: {} })
  595. ),
  596. http.post('/api/v1/cloud/filament-info', () => HttpResponse.json({ filaments: [] })),
  597. http.post('/api/v1/printers/camera/stream-token', () =>
  598. HttpResponse.json({ token: 'test-token', expires_at: '2099-01-01T00:00:00Z' })
  599. ),
  600. http.put('/api/v1/settings/', async ({ request }) => {
  601. const body = (await request.json()) as Record<string, unknown>;
  602. return HttpResponse.json(body);
  603. }),
  604. ];