GitHubBackupSettingsPermissions.test.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * The Git Restore button must respect github:restore client-side (#2656).
  3. *
  4. * All three restore endpoints are gated on GITHUB_RESTORE server-side, so a
  5. * user without it gets a 403 the moment the modal opens its preview. Offering
  6. * the button anyway is an action that cannot work.
  7. *
  8. * Scoped to the button on purpose: the backup card itself stays visible,
  9. * because configuring backups is a separate permission.
  10. */
  11. import { describe, it, expect, afterEach } from 'vitest';
  12. import { screen, waitFor } from '@testing-library/react';
  13. import { http, HttpResponse } from 'msw';
  14. import { render } from '../utils';
  15. import { server } from '../mocks/server';
  16. import { GitHubBackupSettings } from '../../components/GitHubBackupSettings';
  17. import { setAuthToken } from '../../api/client';
  18. afterEach(() => {
  19. server.resetHandlers();
  20. setAuthToken(null);
  21. });
  22. /** A configured backup, which is what makes the action row render at all. */
  23. function mockConfiguredBackup() {
  24. server.use(
  25. http.get('*/api/v1/github-backup/config', () =>
  26. HttpResponse.json({
  27. id: 1,
  28. provider: 'github',
  29. repository_url: 'https://github.com/test/repo',
  30. branch: 'main',
  31. enabled: true,
  32. schedule_enabled: false,
  33. schedule_type: 'daily',
  34. schedule_time: '02:00',
  35. backup_kprofiles: true,
  36. backup_cloud_profiles: false,
  37. backup_spools: true,
  38. backup_archives: true,
  39. backup_settings: true,
  40. last_backup_at: null,
  41. last_backup_status: null,
  42. }),
  43. ),
  44. http.get('*/api/v1/github-backup/status', () =>
  45. HttpResponse.json({
  46. configured: true,
  47. enabled: true,
  48. is_running: false,
  49. restore_running: false,
  50. progress: null,
  51. last_backup_at: null,
  52. last_backup_status: null,
  53. next_run: null,
  54. }),
  55. ),
  56. http.get('*/api/v1/github-backup/logs', () => HttpResponse.json([])),
  57. );
  58. }
  59. function mockUserWith(permissions: string[]) {
  60. setAuthToken('test-token', 'session');
  61. server.use(
  62. http.get('*/api/v1/auth/status', () =>
  63. HttpResponse.json({ auth_enabled: true, requires_setup: false }),
  64. ),
  65. http.get('*/api/v1/auth/me', () =>
  66. HttpResponse.json({ id: 1, username: 'operator', is_admin: false, permissions }),
  67. ),
  68. );
  69. }
  70. describe('GitHubBackupSettings - github:restore gate', () => {
  71. it('hides the Restore from Git button without the permission', async () => {
  72. mockConfiguredBackup();
  73. mockUserWith(['settings:read', 'settings:update']);
  74. render(<GitHubBackupSettings />);
  75. // Wait for the action row itself, so an absent button is a real absence
  76. // rather than the card simply not having rendered yet.
  77. await waitFor(() => expect(screen.getByRole('button', { name: /Backup Now/i })).toBeInTheDocument());
  78. expect(screen.queryByRole('button', { name: /Restore from Git/i })).not.toBeInTheDocument();
  79. });
  80. it('shows it when the user has github:restore', async () => {
  81. mockConfiguredBackup();
  82. mockUserWith(['settings:read', 'github:restore']);
  83. render(<GitHubBackupSettings />);
  84. await waitFor(() =>
  85. expect(screen.getByRole('button', { name: /Restore from Git/i })).toBeInTheDocument(),
  86. );
  87. });
  88. it('shows it when auth is disabled entirely', async () => {
  89. // hasPermission returns true with auth off, and it must stay that way -
  90. // a single-user instance has no permissions to grant.
  91. mockConfiguredBackup();
  92. server.use(
  93. http.get('*/api/v1/auth/status', () =>
  94. HttpResponse.json({ auth_enabled: false, requires_setup: false }),
  95. ),
  96. );
  97. render(<GitHubBackupSettings />);
  98. await waitFor(() =>
  99. expect(screen.getByRole('button', { name: /Restore from Git/i })).toBeInTheDocument(),
  100. );
  101. });
  102. });