win32.spec.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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.doUnmock('node:fs/promises')
  86. vi.doUnmock('node:path')
  87. vi.resetModules()
  88. for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true })
  89. })
  90. describe('Windows durable namespace helpers', () => {
  91. it('keeps drive-root probes native while namespacing descendants', async () => {
  92. const probes: string[] = []
  93. vi.resetModules()
  94. vi.doMock('node:fs/promises', async (importOriginal) => {
  95. const actual = await importOriginal<typeof import('node:fs/promises')>()
  96. return {
  97. ...actual,
  98. stat: async (path: string) => {
  99. probes.push(path)
  100. return { isDirectory: () => true }
  101. },
  102. }
  103. })
  104. vi.doMock('node:path', async (importOriginal) => {
  105. const actual = await importOriginal<typeof import('node:path')>()
  106. return {
  107. ...actual,
  108. join: (...paths: string[]) => actual.win32.join(...paths),
  109. parse: (path: string) => actual.win32.parse(path),
  110. resolve: (...paths: string[]) => actual.win32.resolve(...paths),
  111. toNamespacedPath: (path: string) => actual.win32.toNamespacedPath(path),
  112. }
  113. })
  114. const { ensureDurableDirectoryWin32 } = await import('../src/win32.ts')
  115. await ensureDurableDirectoryWin32('C:\\existing')
  116. expect(probes).toEqual(['C:\\', '\\\\?\\C:\\existing'])
  117. })
  118. it('publishes a new file with write-through MoveFileExW semantics', async () => {
  119. const { publishNewFileWin32 } = await importWithFilesystemMove()
  120. const root = await tempRoot()
  121. const tmp = join(root, 'log.tmp')
  122. const final = join(root, 'log.jsonl')
  123. await writeFile(tmp, 'content')
  124. await publishNewFileWin32(tmp, final)
  125. expect(existsSync(tmp)).toBe(false)
  126. expect(readFileSync(final, 'utf8')).toBe('content')
  127. })
  128. it('maps Win32 publish failures to Node-style errno codes', async () => {
  129. const cases = [
  130. [ERROR_FILE_NOT_FOUND, 'ENOENT'],
  131. [ERROR_PATH_NOT_FOUND, 'ENOENT'],
  132. [ERROR_ACCESS_DENIED, 'EACCES'],
  133. [ERROR_NOT_SAME_DEVICE, 'EXDEV'],
  134. [ERROR_FILE_EXISTS, 'EEXIST'],
  135. [ERROR_ALREADY_EXISTS, 'EEXIST'],
  136. [ERROR_INVALID_NAME, 'EINVAL'],
  137. [9999, 'EIO'],
  138. ] as const
  139. for (const [win32Code, code] of cases) {
  140. const { publishNewFileWin32 } = await importWithError(win32Code)
  141. await expect(publishNewFileWin32('from', 'to')).rejects.toMatchObject({ code, win32Code, path: 'from', dest: 'to' })
  142. }
  143. })
  144. it('creates missing directories through staging siblings and tolerates an already-created race', async () => {
  145. const root = await tempRoot()
  146. const raced = join(root, 'raced')
  147. const { ensureDurableDirectoryWin32 } = await importWithMove((existing, replacement, flags, setLastError) => {
  148. expect(flags).toBe(MOVEFILE_WRITE_THROUGH)
  149. const from = stripNamespace(existing)
  150. const to = stripNamespace(replacement)
  151. if (to === raced) {
  152. mkdirSync(to)
  153. setLastError(ERROR_ALREADY_EXISTS)
  154. return 0
  155. }
  156. if (!existsSync(from)) { setLastError(ERROR_FILE_NOT_FOUND); return 0 }
  157. if (existsSync(to)) { setLastError(ERROR_ALREADY_EXISTS); return 0 }
  158. renameSync(from, to)
  159. return 1
  160. })
  161. await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
  162. expect(existsSync(join(root, 'a', 'b'))).toBe(true)
  163. await ensureDurableDirectoryWin32(join(root, 'a', 'b'))
  164. await ensureDurableDirectoryWin32(raced)
  165. expect(existsSync(raced)).toBe(true)
  166. })
  167. it('keeps staging names valid for a maximum-length target component', async () => {
  168. const { ensureDurableDirectoryWin32 } = await importWithFilesystemMove()
  169. const root = await tempRoot()
  170. const target = join(root, 'x'.repeat(255))
  171. await ensureDurableDirectoryWin32(target)
  172. expect(existsSync(target)).toBe(true)
  173. })
  174. it('surfaces directory publication failures other than an existing-target race', async () => {
  175. const { ensureDurableDirectoryWin32 } = await importWithError(ERROR_ACCESS_DENIED)
  176. const root = await tempRoot()
  177. await expect(ensureDurableDirectoryWin32(join(root, 'denied'))).rejects.toMatchObject({ code: 'EACCES' })
  178. })
  179. it('rejects a non-directory component instead of treating it as missing', async () => {
  180. const { ensureDurableDirectoryWin32 } = await importWithFilesystemMove()
  181. const root = await tempRoot()
  182. const blocked = join(root, 'blocked')
  183. writeFileSync(blocked, 'x')
  184. await expect(ensureDurableDirectoryWin32(join(blocked, 'child'))).rejects.toMatchObject({ code: 'ENOTDIR' })
  185. })
  186. })