currentComputerAuthorization.test.ts 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. // @vitest-environment node
  2. import { afterEach, beforeEach, expect, it, vi } from 'vitest'
  3. import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
  4. import { tmpdir } from 'node:os'
  5. import { join } from 'node:path'
  6. import { DesktopHostCore } from '../../desktop/environment-host.mjs'
  7. import { authorizeCurrentComputer } from '../../desktop/computer-authorization.mjs'
  8. import { dialog } from 'electron'
  9. vi.mock('electron', () => ({
  10. app: { isPackaged: false }, BrowserWindow: class {}, session: {},
  11. desktopCapturer: {}, shell: {},
  12. dialog: { showMessageBox: vi.fn() },
  13. screen: { on: vi.fn(), removeListener: vi.fn() },
  14. powerMonitor: { on: vi.fn(), removeListener: vi.fn() },
  15. systemPreferences: { isTrustedAccessibilityClient: () => true, getMediaAccessStatus: () => 'granted' },
  16. }))
  17. let home: string, core: any
  18. const owner = 'a'.repeat(64)
  19. beforeEach(() => {
  20. home = mkdtempSync(join(tmpdir(), 'native-authorization-'))
  21. core = new DesktopHostCore({ root: home, dataRoot: home })
  22. core.platform = { computer: true }
  23. vi.mocked(dialog.showMessageBox).mockResolvedValue({ response: 1, checkboxChecked: false })
  24. })
  25. afterEach(async () => { await core.close(); rmSync(home, { recursive: true, force: true }); vi.clearAllMocks() })
  26. const request = (scope = 'full', isCurrent = () => true, context = { owner, account: '当前账号' }) => {
  27. const fetchImpl = vi.fn().mockResolvedValue({ ok: true, json: async () => context })
  28. return { fetchImpl, promise: authorizeCurrentComputer({ scope, core, origin: 'https://example.test', cookies: [{ name: 'session', value: 'test' }], fetchImpl, isCurrent }) }
  29. }
  30. it('grants only the current native core after reading the authenticated account context', async () => {
  31. const { promise, fetchImpl } = request()
  32. await expect(promise).resolves.toEqual({ ok: true })
  33. expect(core.approvedFull.has(owner)).toBe(true)
  34. expect(core.approved.size).toBe(0)
  35. expect(JSON.parse(readFileSync(join(home, 'computer-permissions.json'), 'utf8'))).toEqual({ screen: [], full: [owner] })
  36. expect(fetchImpl.mock.calls[0][0].pathname).toBe('/api/app/desktop-environment/authorization')
  37. expect(fetchImpl.mock.calls[0][1]).toMatchObject({ method: 'GET', redirect: 'error' })
  38. })
  39. it('rejects server-dispatched authorization without showing a dialog', async () => {
  40. await expect(core.execute({ operation: 'authorize', scope: 'full', owner, deadline: Date.now() + 10000 })).rejects.toThrow('当前设备')
  41. expect(dialog.showMessageBox).not.toHaveBeenCalled()
  42. expect(core.approvedFull.size).toBe(0)
  43. })
  44. it('does not grant if the client connection changes while confirmation is open', async () => {
  45. let current = true
  46. vi.mocked(dialog.showMessageBox).mockImplementation(async () => { current = false; return { response: 1, checkboxChecked: false } })
  47. await expect(request('full', () => current).promise).rejects.toThrow('已取消')
  48. expect(core.approvedFull.size).toBe(0)
  49. })
  50. it('rejects an all-devices scope before fetching or confirming', async () => {
  51. const { promise, fetchImpl } = request('all')
  52. await expect(promise).rejects.toThrow('授权范围无效')
  53. expect(fetchImpl).not.toHaveBeenCalled()
  54. expect(dialog.showMessageBox).not.toHaveBeenCalled()
  55. })
  56. it('leaves grants unchanged when confirmation is cancelled', async () => {
  57. vi.mocked(dialog.showMessageBox).mockResolvedValue({ response: 0, checkboxChecked: false })
  58. await expect(request().promise).rejects.toThrow('已取消')
  59. expect(core.approvedFull.size).toBe(0)
  60. })