win32.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * Unit tests for the Windows durable namespace helper with a mocked kernel32
  3. * binding. The real JSONL suite exercises the helper on native Windows; these
  4. * tests keep the Win32 error mapping and race handling covered on every host.
  5. */
  6. import { afterEach, describe, expect, it, vi } from 'vitest'
  7. import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
  8. import { mkdtemp, rm, writeFile } from 'node:fs/promises'
  9. import { tmpdir } from 'node:os'
  10. import { join } from 'node:path'
  11. const MOVEFILE_WRITE_THROUGH = 0x00000008
  12. const ERROR_FILE_NOT_FOUND = 2
  13. const ERROR_PATH_NOT_FOUND = 3
  14. const ERROR_ACCESS_DENIED = 5
  15. const ERROR_NOT_SAME_DEVICE = 17
  16. const ERROR_FILE_EXISTS = 80
  17. const ERROR_INVALID_NAME = 123
  18. const ERROR_ALREADY_EXISTS = 183
  19. type MoveFileExW = (existing: string, replacement: string, flags: number, setLastError: (code: number) => void) => number
  20. const roots: string[] = []
  21. function stripNamespace(path: string): string {
  22. if (path.startsWith('\\\\?\\UNC\\')) return `\\\\${path.slice('\\\\?\\UNC\\'.length)}`
  23. if (path.startsWith('\\\\?\\')) return path.slice('\\\\?\\'.length)
  24. return path
  25. }
  26. async function tempRoot(): Promise<string> {
  27. const dir = await mkdtemp(join(tmpdir(), 'dsh-jsonl-win32-'))
  28. roots.push(dir)
  29. return dir
  30. }
  31. async function importWithMove(moveFileExW: MoveFileExW): Promise<typeof import('../src/win32.ts')> {
  32. vi.resetModules()
  33. vi.doMock('koffi', () => {
  34. let lastError = 0
  35. const setLastError = (code: number): void => { lastError = code }
  36. const move: MoveFileExW = (existing, replacement, flags, setError) => {
  37. const ok = moveFileExW(existing, replacement, flags, setError)
  38. lastError = ok === 0 ? lastError : 0
  39. return ok
  40. }
  41. return {
  42. default: {
  43. load: () => ({
  44. func: (_convention: string, name: string, result: string) => {
  45. if (name === 'MoveFileExW') return (existing: string, replacement: string, flags: number) => {
  46. expect(result).toBe('int')
  47. const ok = move(existing, replacement, flags, setLastError)
  48. return ok
  49. }
  50. return () => lastError
  51. },
  52. }),
  53. },
  54. }
  55. })
  56. return import('../src/win32.ts')
  57. }
  58. async function importWithError(code: number): Promise<typeof import('../src/win32.ts')> {
  59. vi.resetModules()
  60. vi.doMock('koffi', () => ({
  61. default: {
  62. load: () => ({
  63. func: (_convention: string, name: string) => {
  64. if (name === 'MoveFileExW') return () => 0
  65. return () => code
  66. },
  67. }),
  68. },
  69. }))
  70. return import('../src/win32.ts')
  71. }
  72. async function importWithFilesystemMove(): Promise<typeof import('../src/win32.ts')> {
  73. return importWithMove((existing, replacement, flags, setLastError) => {
  74. expect(flags).toBe(MOVEFILE_WRITE_THROUGH)
  75. const from = stripNamespace(existing)
  76. const to = stripNamespace(replacement)
  77. if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return 0 }
  78. if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return 0 }
  79. renameSync(from, to)
  80. return 1
  81. })
  82. }
  83. afterEach(async () => {
  84. vi.doUnmock('koffi')
  85. vi.resetModules()
  86. for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true })
  87. })
  88. describe('Windows durable namespace helpers', () => {
  89. it('publishes a new file with write-through MoveFileExW semantics', async () => {
  90. const { publishNewFileWin32 } = await importWithFilesystemMove()
  91. const root = await tempRoot()
  92. const tmp = join(root, 'log.tmp')
  93. const final = join(root, 'log.jsonl')
  94. await writeFile(tmp, 'content')
  95. await publishNewFileWin32(tmp, final)
  96. expect(existsSync(tmp)).toBe(false)
  97. expect(readFileSync(final, 'utf8')).toBe('content')
  98. })
  99. it('maps Win32 publish failures to Node-style errno codes', async () => {
  100. const cases = [
  101. [ERROR_FILE_NOT_FOUND, 'ENOENT'],
  102. [ERROR_PATH_NOT_FOUND, 'ENOENT'],
  103. [ERROR_ACCESS_DENIED, 'EACCES'],
  104. [ERROR_NOT_SAME_DEVICE, 'EXDEV'],
  105. [ERROR_FILE_EXISTS, 'EEXIST'],
  106. [ERROR_ALREADY_EXISTS, 'EEXIST'],
  107. [ERROR_INVALID_NAME, 'EINVAL'],
  108. [9999, 'EIO'],
  109. ] as const
  110. for (const [win32Code, code] of cases) {
  111. const { publishNewFileWin32 } = await importWithError(win32Code)
  112. await expect(publishNewFileWin32('from', 'to')).rejects.toMatchObject({ code, win32Code, path: 'from', dest: 'to' })
  113. }
  114. })
  115. it('creates missing directories through staging siblings and tolerates an already-created race', async () => {
  116. const root = await tempRoot()
  117. const raced = join(root, 'raced')
  118. const { ensureDurableDirectoryWin32 } = await importWithMove((existing, replacement, flags, setLastError) => {
  119. expect(flags).toBe(MOVEFILE_WRITE_THROUGH)
  120. const from = stripNamespace(existing)
  121. const to = stripNamespace(replacement)
  122. if (to === raced) {
  123. mkdirSync(to)
  124. setLastError(ERROR_ALREADY_EXISTS)
  125. return 0
  126. }
  127. if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return 0 }
  128. if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return 0 }
  129. renameSync(from, to)
  130. return 1
  131. })
  132. await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
  133. expect(existsSync(join(root, 'a', 'b'))).toBe(true)
  134. await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
  135. await ensureDurableDirectoryWin32(raced)
  136. expect(existsSync(raced)).toBe(true)
  137. })
  138. it('surfaces directory publication failures other than an existing-target race', async () => {
  139. const { ensureDurableDirectoryWin32 } = await importWithError(ERROR_ACCESS_DENIED)
  140. const root = await tempRoot()
  141. await expect(ensureDurableDirectoryWin32(join(root, 'denied'))).rejects.toMatchObject({ code: 'EACCES' })
  142. })
  143. it('rejects a non-directory component instead of treating it as missing', async () => {
  144. const { ensureDurableDirectoryWin32 } = await importWithFilesystemMove()
  145. const root = await tempRoot()
  146. const blocked = join(root, 'blocked')
  147. writeFileSync(blocked, 'x')
  148. await expect(ensureDurableDirectoryWin32(join(blocked, 'child'))).rejects.toMatchObject({ code: 'ENOTDIR' })
  149. })
  150. })