runtime-tree.spec.ts 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, expect, it } from 'vitest'
  5. import { DESKTOP_HOST_PACKAGE, DESKTOP_HOST_RUNTIME_FILES } from '../src/core-package-set.ts'
  6. import { DESKTOP_RUNTIME_FILE, desktopRuntimeId, readDesktopRuntime, runtimePath, verifyDesktopRuntime } from '../src/runtime-tree.ts'
  7. import { runtimeFixture } from './runtime-fixture.ts'
  8. const roots: string[] = []
  9. function fixture(): string {
  10. const root = mkdtempSync(join(tmpdir(), 'desktop-runtime-'))
  11. roots.push(root)
  12. runtimeFixture(join(root, 'dsh'))
  13. return root
  14. }
  15. afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) })
  16. it('verifies a runtime after relocation without depending on build paths', async () => {
  17. const root = fixture()
  18. const before = await verifyDesktopRuntime(join(root, 'dsh'), '1.0.0')
  19. cpSync(join(root, 'dsh'), join(root, 'moved'), { recursive: true })
  20. expect(desktopRuntimeId(await verifyDesktopRuntime(join(root, 'moved'), '1.0.0'))).toBe(desktopRuntimeId(before))
  21. })
  22. it.each(['changed', 'same-size', 'extra', 'missing'])('checks %s runtime bytes only during build verification', async (operation) => {
  23. const dsh = join(fixture(), 'dsh')
  24. const before = readDesktopRuntime(dsh, '1.0.0')
  25. if (operation === 'changed') writeFileSync(join(dsh, 'package.json'), '{}')
  26. if (operation === 'same-size') writeFileSync(join(dsh, 'package.json'), '{"type":"Module"}\n')
  27. if (operation === 'extra') writeFileSync(join(dsh, 'extra'), '')
  28. if (operation === 'missing') rmSync(join(dsh, 'package.json'))
  29. expect(readDesktopRuntime(dsh, '1.0.0')).toEqual(before)
  30. await expect(verifyDesktopRuntime(dsh, '1.0.0')).rejects.toThrow(/integrity/u)
  31. })
  32. it('rejects filesystem links and incompatible targets', async () => {
  33. const dsh = join(fixture(), 'dsh')
  34. await expect(verifyDesktopRuntime(dsh, '1.0.0', { platform: process.platform, arch: 'wrong' })).rejects.toThrow(/incompatible/u)
  35. symlinkSync(join(dsh, 'node_modules'), join(dsh, 'outside'), process.platform === 'win32' ? 'junction' : 'dir')
  36. expect(readDesktopRuntime(dsh, '1.0.0').release.version).toBe('1.0.0')
  37. await expect(verifyDesktopRuntime(dsh, '1.0.0')).rejects.toThrow(/unsupported filesystem/u)
  38. })
  39. it.each(['missing', 'directory'])('rejects a %s Host entry during startup metadata loading', (operation) => {
  40. const dsh = join(fixture(), 'dsh')
  41. const path = join(dsh, 'node_modules', DESKTOP_HOST_PACKAGE, DESKTOP_HOST_RUNTIME_FILES[0])
  42. rmSync(path)
  43. if (operation === 'directory') mkdirSync(path)
  44. expect(() => readDesktopRuntime(dsh, '1.0.0')).toThrow(/missing Host file/u)
  45. })
  46. it('rejects incompatible targets and shared package metadata during startup metadata loading', () => {
  47. const dsh = join(fixture(), 'dsh')
  48. expect(() => readDesktopRuntime(dsh, '2.0.0')).toThrow(/does not match Electron/u)
  49. expect(() => readDesktopRuntime(dsh, '1.0.0', { platform: process.platform, arch: 'wrong' })).toThrow(/incompatible/u)
  50. writeFileSync(join(dsh, 'node_modules', DESKTOP_HOST_PACKAGE, 'package.json'), '{}')
  51. expect(() => readDesktopRuntime(dsh, '1.0.0')).toThrow(/shared package metadata mismatch/u)
  52. })
  53. it('rejects a descriptor that maps a shared package outside node_modules', async () => {
  54. const dsh = join(fixture(), 'dsh')
  55. const path = join(dsh, DESKTOP_RUNTIME_FILE)
  56. const descriptor = JSON.parse(readFileSync(path, 'utf8')) as { sharedPackages: { path: string }[] }
  57. descriptor.sharedPackages[0]!.path = '../outside'
  58. writeFileSync(path, JSON.stringify(descriptor))
  59. await expect(verifyDesktopRuntime(dsh, '1.0.0')).rejects.toThrow(/shared package record/u)
  60. })
  61. it('verifies recorded executable permissions only on Unix', async () => {
  62. const dsh = join(fixture(), 'dsh')
  63. const path = join(dsh, DESKTOP_RUNTIME_FILE)
  64. const descriptor = JSON.parse(readFileSync(path, 'utf8')) as { files: { executable: boolean }[] }
  65. descriptor.files[0]!.executable = !descriptor.files[0]!.executable
  66. writeFileSync(path, JSON.stringify(descriptor))
  67. if (process.platform === 'win32') await expect(verifyDesktopRuntime(dsh, '1.0.0')).resolves.toMatchObject(descriptor)
  68. else await expect(verifyDesktopRuntime(dsh, '1.0.0')).rejects.toThrow(/integrity/u)
  69. })
  70. it.each(['../outside', '/absolute', 'C:/absolute', 'a\\b', 'a//b', './a'])('rejects nonportable path %s', (path) => {
  71. expect(() => runtimePath('/runtime', path)).toThrow(/invalid relative path/u)
  72. })