GitHubBackupSettings.provider.test.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * Tests for the provider selection UI in GitHubBackupSettings.
  3. */
  4. import { describe, it, expect, vi, beforeEach } from 'vitest';
  5. import { fireEvent, screen, waitFor } from '@testing-library/react';
  6. import { render } from '../utils';
  7. import { GitHubBackupSettings } from '../../components/GitHubBackupSettings';
  8. import { http, HttpResponse } from 'msw';
  9. import { server } from '../mocks/server';
  10. const baseHandlers = () => [
  11. http.get('/api/v1/github-backup/config', () => HttpResponse.json(null)),
  12. http.get('/api/v1/github-backup/status', () =>
  13. HttpResponse.json({
  14. configured: false,
  15. enabled: false,
  16. is_running: false,
  17. progress: null,
  18. last_backup_at: null,
  19. last_backup_status: null,
  20. next_scheduled_run: null,
  21. })
  22. ),
  23. http.get('/api/v1/github-backup/logs', () => HttpResponse.json([])),
  24. http.get('/api/v1/local-backup/status', () =>
  25. HttpResponse.json({
  26. enabled: false,
  27. schedule: 'daily',
  28. time: '03:00',
  29. retention: 5,
  30. path: '',
  31. default_path: '/data/backups',
  32. is_running: false,
  33. last_backup_at: null,
  34. last_status: null,
  35. last_message: null,
  36. next_run: null,
  37. })
  38. ),
  39. http.get('/api/v1/local-backup/backups', () => HttpResponse.json([])),
  40. http.get('/api/v1/cloud/status', () => HttpResponse.json({ is_authenticated: false })),
  41. http.get('/api/v1/printers', () => HttpResponse.json([])),
  42. http.put('/api/v1/settings/', () => HttpResponse.json({})),
  43. ];
  44. describe('GitHubBackupSettings - Provider Selection', () => {
  45. beforeEach(() => {
  46. vi.clearAllMocks();
  47. server.use(...baseHandlers());
  48. });
  49. it('renders provider dropdown with GitHub selected by default', async () => {
  50. render(<GitHubBackupSettings />);
  51. await waitFor(() => {
  52. expect(screen.getByText('Git Provider')).toBeInTheDocument();
  53. });
  54. const select = screen.getByRole('combobox', { name: /git provider/i });
  55. expect(select).toHaveValue('github');
  56. });
  57. it('does not show instance URL field for any provider', async () => {
  58. render(<GitHubBackupSettings />);
  59. await waitFor(() => {
  60. expect(screen.getByText('Git Provider')).toBeInTheDocument();
  61. });
  62. expect(screen.queryByText('Instance URL')).not.toBeInTheDocument();
  63. });
  64. it('loads provider from existing config', async () => {
  65. server.use(
  66. http.get('/api/v1/github-backup/config', () =>
  67. HttpResponse.json({
  68. id: 1,
  69. repository_url: 'https://git.example.com/owner/repo',
  70. has_token: true,
  71. branch: 'main',
  72. provider: 'gitea',
  73. schedule_enabled: false,
  74. schedule_type: 'daily',
  75. backup_kprofiles: true,
  76. backup_cloud_profiles: true,
  77. backup_settings: false,
  78. backup_spools: false,
  79. backup_archives: false,
  80. enabled: true,
  81. last_backup_at: null,
  82. last_backup_status: null,
  83. last_backup_message: null,
  84. last_backup_commit_sha: null,
  85. next_scheduled_run: null,
  86. created_at: '2024-01-01T00:00:00Z',
  87. updated_at: '2024-01-01T00:00:00Z',
  88. })
  89. )
  90. );
  91. render(<GitHubBackupSettings />);
  92. await waitFor(() => {
  93. const select = screen.getByRole('combobox', { name: /git provider/i });
  94. expect(select).toHaveValue('gitea');
  95. });
  96. });
  97. it('renders Forgejo as a separate dropdown option', async () => {
  98. render(<GitHubBackupSettings />);
  99. await waitFor(() => {
  100. expect(screen.getByRole('combobox', { name: /git provider/i })).toBeInTheDocument();
  101. });
  102. const select = screen.getByRole('combobox', { name: /git provider/i });
  103. const options = Array.from((select as HTMLSelectElement).options).map((o) => o.value);
  104. expect(options).toContain('gitea');
  105. expect(options).toContain('forgejo');
  106. });
  107. it('loads forgejo provider from existing config', async () => {
  108. server.use(
  109. http.get('/api/v1/github-backup/config', () =>
  110. HttpResponse.json({
  111. id: 2,
  112. repository_url: 'https://forgejo.example.com/owner/repo',
  113. has_token: true,
  114. branch: 'main',
  115. provider: 'forgejo',
  116. schedule_enabled: false,
  117. schedule_type: 'daily',
  118. backup_kprofiles: true,
  119. backup_cloud_profiles: true,
  120. backup_settings: false,
  121. backup_spools: false,
  122. backup_archives: false,
  123. enabled: true,
  124. last_backup_at: null,
  125. last_backup_status: null,
  126. last_backup_message: null,
  127. last_backup_commit_sha: null,
  128. next_scheduled_run: null,
  129. created_at: '2024-01-01T00:00:00Z',
  130. updated_at: '2024-01-01T00:00:00Z',
  131. })
  132. )
  133. );
  134. render(<GitHubBackupSettings />);
  135. await waitFor(() => {
  136. const select = screen.getByRole('combobox', { name: /git provider/i });
  137. expect(select).toHaveValue('forgejo');
  138. });
  139. });
  140. it('autosaves existing config changes after debounce', async () => {
  141. let patchBody: Record<string, unknown> | null = null;
  142. server.use(
  143. http.get('/api/v1/github-backup/config', () =>
  144. HttpResponse.json({
  145. id: 3,
  146. repository_url: 'https://git.example.com/owner/repo',
  147. has_token: true,
  148. branch: 'main',
  149. provider: 'gitea',
  150. allow_insecure_http: false,
  151. schedule_enabled: false,
  152. schedule_type: 'daily',
  153. backup_kprofiles: true,
  154. backup_cloud_profiles: true,
  155. backup_settings: false,
  156. backup_spools: false,
  157. backup_archives: false,
  158. enabled: true,
  159. last_backup_at: null,
  160. last_backup_status: null,
  161. last_backup_message: null,
  162. last_backup_commit_sha: null,
  163. next_scheduled_run: null,
  164. created_at: '2024-01-01T00:00:00Z',
  165. updated_at: '2024-01-01T00:00:00Z',
  166. })
  167. ),
  168. http.patch('/api/v1/github-backup/config', async ({ request }) => {
  169. patchBody = await request.json() as Record<string, unknown>;
  170. return HttpResponse.json({
  171. id: 3,
  172. repository_url: patchBody.repository_url,
  173. has_token: true,
  174. branch: patchBody.branch,
  175. provider: patchBody.provider,
  176. allow_insecure_http: patchBody.allow_insecure_http,
  177. schedule_enabled: patchBody.schedule_enabled,
  178. schedule_type: patchBody.schedule_type,
  179. backup_kprofiles: patchBody.backup_kprofiles,
  180. backup_cloud_profiles: patchBody.backup_cloud_profiles,
  181. backup_settings: patchBody.backup_settings,
  182. backup_spools: patchBody.backup_spools,
  183. backup_archives: patchBody.backup_archives,
  184. enabled: patchBody.enabled,
  185. last_backup_at: null,
  186. last_backup_status: null,
  187. last_backup_message: null,
  188. last_backup_commit_sha: null,
  189. next_scheduled_run: null,
  190. created_at: '2024-01-01T00:00:00Z',
  191. updated_at: '2024-01-01T00:00:00Z',
  192. });
  193. })
  194. );
  195. render(<GitHubBackupSettings />);
  196. const branchInput = await screen.findByDisplayValue('main');
  197. await waitFor(() => expect(branchInput).toHaveValue('main'));
  198. fireEvent.change(branchInput, { target: { value: 'dev' } });
  199. await waitFor(() => {
  200. expect(patchBody).toEqual({ branch: 'dev' });
  201. }, { timeout: 2000 });
  202. });
  203. it('autosaves provider changes after debounce', async () => {
  204. let patchBody: Record<string, unknown> | null = null;
  205. server.use(
  206. http.get('/api/v1/github-backup/config', () =>
  207. HttpResponse.json({
  208. id: 4,
  209. repository_url: 'https://git.example.com/owner/repo',
  210. has_token: true,
  211. branch: 'main',
  212. provider: 'gitea',
  213. allow_insecure_http: false,
  214. schedule_enabled: false,
  215. schedule_type: 'daily',
  216. backup_kprofiles: true,
  217. backup_cloud_profiles: true,
  218. backup_settings: false,
  219. backup_spools: false,
  220. backup_archives: false,
  221. enabled: true,
  222. last_backup_at: null,
  223. last_backup_status: null,
  224. last_backup_message: null,
  225. last_backup_commit_sha: null,
  226. next_scheduled_run: null,
  227. created_at: '2024-01-01T00:00:00Z',
  228. updated_at: '2024-01-01T00:00:00Z',
  229. })
  230. ),
  231. http.patch('/api/v1/github-backup/config', async ({ request }) => {
  232. patchBody = await request.json() as Record<string, unknown>;
  233. return HttpResponse.json({
  234. id: 4,
  235. repository_url: patchBody.repository_url,
  236. has_token: true,
  237. branch: patchBody.branch,
  238. provider: patchBody.provider,
  239. allow_insecure_http: patchBody.allow_insecure_http,
  240. schedule_enabled: patchBody.schedule_enabled,
  241. schedule_type: patchBody.schedule_type,
  242. backup_kprofiles: patchBody.backup_kprofiles,
  243. backup_cloud_profiles: patchBody.backup_cloud_profiles,
  244. backup_settings: patchBody.backup_settings,
  245. backup_spools: patchBody.backup_spools,
  246. backup_archives: patchBody.backup_archives,
  247. enabled: patchBody.enabled,
  248. last_backup_at: null,
  249. last_backup_status: null,
  250. last_backup_message: null,
  251. last_backup_commit_sha: null,
  252. next_scheduled_run: null,
  253. created_at: '2024-01-01T00:00:00Z',
  254. updated_at: '2024-01-01T00:00:00Z',
  255. });
  256. })
  257. );
  258. render(<GitHubBackupSettings />);
  259. const providerSelect = await screen.findByRole('combobox', { name: /git provider/i });
  260. await waitFor(() => expect(providerSelect).toHaveValue('gitea'));
  261. fireEvent.change(providerSelect, { target: { value: 'forgejo' } });
  262. await waitFor(() => {
  263. expect(patchBody).toEqual({ provider: 'forgejo' });
  264. }, { timeout: 2000 });
  265. });
  266. it('autosaves repository URL changes after debounce', async () => {
  267. let patchBody: Record<string, unknown> | null = null;
  268. server.use(
  269. http.get('/api/v1/github-backup/config', () =>
  270. HttpResponse.json({
  271. id: 5,
  272. repository_url: 'https://git.example.com/owner/repo',
  273. has_token: true,
  274. branch: 'main',
  275. provider: 'gitea',
  276. allow_insecure_http: false,
  277. schedule_enabled: false,
  278. schedule_type: 'daily',
  279. backup_kprofiles: true,
  280. backup_cloud_profiles: true,
  281. backup_settings: false,
  282. backup_spools: false,
  283. backup_archives: false,
  284. enabled: true,
  285. last_backup_at: null,
  286. last_backup_status: null,
  287. last_backup_message: null,
  288. last_backup_commit_sha: null,
  289. next_scheduled_run: null,
  290. created_at: '2024-01-01T00:00:00Z',
  291. updated_at: '2024-01-01T00:00:00Z',
  292. })
  293. ),
  294. http.patch('/api/v1/github-backup/config', async ({ request }) => {
  295. patchBody = await request.json() as Record<string, unknown>;
  296. return HttpResponse.json({
  297. id: 5,
  298. repository_url: patchBody.repository_url,
  299. has_token: true,
  300. branch: patchBody.branch,
  301. provider: patchBody.provider,
  302. allow_insecure_http: patchBody.allow_insecure_http,
  303. schedule_enabled: patchBody.schedule_enabled,
  304. schedule_type: patchBody.schedule_type,
  305. backup_kprofiles: patchBody.backup_kprofiles,
  306. backup_cloud_profiles: patchBody.backup_cloud_profiles,
  307. backup_settings: patchBody.backup_settings,
  308. backup_spools: patchBody.backup_spools,
  309. backup_archives: patchBody.backup_archives,
  310. enabled: patchBody.enabled,
  311. last_backup_at: null,
  312. last_backup_status: null,
  313. last_backup_message: null,
  314. last_backup_commit_sha: null,
  315. next_scheduled_run: null,
  316. created_at: '2024-01-01T00:00:00Z',
  317. updated_at: '2024-01-01T00:00:00Z',
  318. });
  319. })
  320. );
  321. render(<GitHubBackupSettings />);
  322. const repoInput = await screen.findByDisplayValue('https://git.example.com/owner/repo');
  323. fireEvent.change(repoInput, { target: { value: 'https://git.example.com/owner/other-repo' } });
  324. await waitFor(() => {
  325. expect(patchBody).toEqual({
  326. repository_url: 'https://git.example.com/owner/other-repo',
  327. });
  328. }, { timeout: 2000 });
  329. });
  330. it('does not let pending token autosave cancel provider settings autosave', async () => {
  331. let patchBody: Record<string, unknown> | null = null;
  332. let postBody: Record<string, unknown> | null = null;
  333. server.use(
  334. http.get('/api/v1/github-backup/config', () =>
  335. HttpResponse.json({
  336. id: 6,
  337. repository_url: 'http://git.example.com/owner/repo',
  338. has_token: true,
  339. branch: 'main',
  340. provider: 'gitea',
  341. allow_insecure_http: true,
  342. schedule_enabled: false,
  343. schedule_type: 'daily',
  344. backup_kprofiles: true,
  345. backup_cloud_profiles: true,
  346. backup_settings: false,
  347. backup_spools: false,
  348. backup_archives: false,
  349. enabled: true,
  350. last_backup_at: null,
  351. last_backup_status: null,
  352. last_backup_message: null,
  353. last_backup_commit_sha: null,
  354. next_scheduled_run: null,
  355. created_at: '2024-01-01T00:00:00Z',
  356. updated_at: '2024-01-01T00:00:00Z',
  357. })
  358. ),
  359. http.patch('/api/v1/github-backup/config', async ({ request }) => {
  360. patchBody = await request.json() as Record<string, unknown>;
  361. return HttpResponse.json({
  362. id: 6,
  363. repository_url: 'http://git.example.com/owner/repo',
  364. has_token: true,
  365. branch: 'main',
  366. provider: patchBody.provider ?? 'gitea',
  367. allow_insecure_http: true,
  368. schedule_enabled: false,
  369. schedule_type: 'daily',
  370. backup_kprofiles: true,
  371. backup_cloud_profiles: true,
  372. backup_settings: false,
  373. backup_spools: false,
  374. backup_archives: false,
  375. enabled: true,
  376. last_backup_at: null,
  377. last_backup_status: null,
  378. last_backup_message: null,
  379. last_backup_commit_sha: null,
  380. next_scheduled_run: null,
  381. created_at: '2024-01-01T00:00:00Z',
  382. updated_at: '2024-01-01T00:00:00Z',
  383. });
  384. }),
  385. http.post('/api/v1/github-backup/config', async ({ request }) => {
  386. postBody = await request.json() as Record<string, unknown>;
  387. return HttpResponse.json({
  388. id: 6,
  389. repository_url: postBody.repository_url,
  390. has_token: true,
  391. branch: postBody.branch,
  392. provider: postBody.provider,
  393. allow_insecure_http: postBody.allow_insecure_http,
  394. schedule_enabled: postBody.schedule_enabled,
  395. schedule_type: postBody.schedule_type,
  396. backup_kprofiles: postBody.backup_kprofiles,
  397. backup_cloud_profiles: postBody.backup_cloud_profiles,
  398. backup_settings: postBody.backup_settings,
  399. backup_spools: postBody.backup_spools,
  400. backup_archives: postBody.backup_archives,
  401. enabled: postBody.enabled,
  402. last_backup_at: null,
  403. last_backup_status: null,
  404. last_backup_message: null,
  405. last_backup_commit_sha: null,
  406. next_scheduled_run: null,
  407. created_at: '2024-01-01T00:00:00Z',
  408. updated_at: '2024-01-01T00:00:00Z',
  409. });
  410. })
  411. );
  412. render(<GitHubBackupSettings />);
  413. const tokenInput = await screen.findByPlaceholderText('Enter new token to update');
  414. fireEvent.change(tokenInput, { target: { value: 'new-token' } });
  415. const providerSelect = await screen.findByRole('combobox', { name: /git provider/i });
  416. fireEvent.change(providerSelect, { target: { value: 'forgejo' } });
  417. await waitFor(() => {
  418. expect(patchBody).toEqual({ provider: 'forgejo' });
  419. }, { timeout: 2000 });
  420. });
  421. });