lookup_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package fsext
  2. import (
  3. "errors"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "github.com/charmbracelet/crush/internal/home"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestLookupClosest(t *testing.T) {
  11. tempDir := t.TempDir()
  12. t.Chdir(tempDir)
  13. t.Run("target found in starting directory", func(t *testing.T) {
  14. testDir := t.TempDir()
  15. // Create target file in current directory
  16. targetFile := filepath.Join(testDir, "target.txt")
  17. err := os.WriteFile(targetFile, []byte("test"), 0o644)
  18. require.NoError(t, err)
  19. foundPath, found := LookupClosest(testDir, "target.txt")
  20. require.True(t, found)
  21. require.Equal(t, targetFile, foundPath)
  22. })
  23. t.Run("target found in parent directory", func(t *testing.T) {
  24. testDir := t.TempDir()
  25. // Create subdirectory
  26. subDir := filepath.Join(testDir, "subdir")
  27. err := os.Mkdir(subDir, 0o755)
  28. require.NoError(t, err)
  29. // Create target file in parent directory
  30. targetFile := filepath.Join(testDir, "target.txt")
  31. err = os.WriteFile(targetFile, []byte("test"), 0o644)
  32. require.NoError(t, err)
  33. foundPath, found := LookupClosest(subDir, "target.txt")
  34. require.True(t, found)
  35. require.Equal(t, targetFile, foundPath)
  36. })
  37. t.Run("target found in grandparent directory", func(t *testing.T) {
  38. testDir := t.TempDir()
  39. // Create nested subdirectories
  40. subDir := filepath.Join(testDir, "subdir")
  41. err := os.Mkdir(subDir, 0o755)
  42. require.NoError(t, err)
  43. subSubDir := filepath.Join(subDir, "subsubdir")
  44. err = os.Mkdir(subSubDir, 0o755)
  45. require.NoError(t, err)
  46. // Create target file in grandparent directory
  47. targetFile := filepath.Join(testDir, "target.txt")
  48. err = os.WriteFile(targetFile, []byte("test"), 0o644)
  49. require.NoError(t, err)
  50. foundPath, found := LookupClosest(subSubDir, "target.txt")
  51. require.True(t, found)
  52. require.Equal(t, targetFile, foundPath)
  53. })
  54. t.Run("target not found", func(t *testing.T) {
  55. testDir := t.TempDir()
  56. foundPath, found := LookupClosest(testDir, "nonexistent.txt")
  57. require.False(t, found)
  58. require.Empty(t, foundPath)
  59. })
  60. t.Run("target directory found", func(t *testing.T) {
  61. testDir := t.TempDir()
  62. // Create target directory in current directory
  63. targetDir := filepath.Join(testDir, "targetdir")
  64. err := os.Mkdir(targetDir, 0o755)
  65. require.NoError(t, err)
  66. foundPath, found := LookupClosest(testDir, "targetdir")
  67. require.True(t, found)
  68. require.Equal(t, targetDir, foundPath)
  69. })
  70. t.Run("stops at home directory", func(t *testing.T) {
  71. // This test is limited as we can't easily create files above home directory
  72. // but we can test the behavior by searching from home directory itself
  73. homeDir := home.Dir()
  74. // Search for a file that doesn't exist from home directory
  75. foundPath, found := LookupClosest(homeDir, "nonexistent_file_12345.txt")
  76. require.False(t, found)
  77. require.Empty(t, foundPath)
  78. })
  79. t.Run("invalid starting directory", func(t *testing.T) {
  80. foundPath, found := LookupClosest("/invalid/path/that/does/not/exist", "target.txt")
  81. require.False(t, found)
  82. require.Empty(t, foundPath)
  83. })
  84. t.Run("relative path handling", func(t *testing.T) {
  85. // Create target file in current directory
  86. require.NoError(t, os.WriteFile("target.txt", []byte("test"), 0o644))
  87. // Search using relative path
  88. foundPath, found := LookupClosest(".", "target.txt")
  89. require.True(t, found)
  90. // Resolve symlinks to handle macOS /private/var vs /var discrepancy
  91. expectedPath, err := filepath.EvalSymlinks(filepath.Join(tempDir, "target.txt"))
  92. require.NoError(t, err)
  93. actualPath, err := filepath.EvalSymlinks(foundPath)
  94. require.NoError(t, err)
  95. require.Equal(t, expectedPath, actualPath)
  96. })
  97. }
  98. func TestLookupClosestWithOwnership(t *testing.T) {
  99. // Note: Testing ownership boundaries is difficult in a cross-platform way
  100. // without creating complex directory structures with different owners.
  101. // This test focuses on the basic functionality when ownership checks pass.
  102. tempDir := t.TempDir()
  103. t.Chdir(tempDir)
  104. t.Run("search respects same ownership", func(t *testing.T) {
  105. testDir := t.TempDir()
  106. // Create subdirectory structure
  107. subDir := filepath.Join(testDir, "subdir")
  108. err := os.Mkdir(subDir, 0o755)
  109. require.NoError(t, err)
  110. // Create target file in parent directory
  111. targetFile := filepath.Join(testDir, "target.txt")
  112. err = os.WriteFile(targetFile, []byte("test"), 0o644)
  113. require.NoError(t, err)
  114. // Search should find the target assuming same ownership
  115. foundPath, found := LookupClosest(subDir, "target.txt")
  116. require.True(t, found)
  117. require.Equal(t, targetFile, foundPath)
  118. })
  119. }
  120. func TestLookup(t *testing.T) {
  121. tempDir := t.TempDir()
  122. t.Chdir(tempDir)
  123. t.Run("no targets returns empty slice", func(t *testing.T) {
  124. testDir := t.TempDir()
  125. found, err := Lookup(testDir)
  126. require.NoError(t, err)
  127. require.Empty(t, found)
  128. })
  129. t.Run("single target found in starting directory", func(t *testing.T) {
  130. testDir := t.TempDir()
  131. // Create target file in current directory
  132. targetFile := filepath.Join(testDir, "target.txt")
  133. err := os.WriteFile(targetFile, []byte("test"), 0o644)
  134. require.NoError(t, err)
  135. found, err := Lookup(testDir, "target.txt")
  136. require.NoError(t, err)
  137. require.Len(t, found, 1)
  138. require.Equal(t, targetFile, found[0])
  139. })
  140. t.Run("multiple targets found in starting directory", func(t *testing.T) {
  141. testDir := t.TempDir()
  142. // Create multiple target files in current directory
  143. targetFile1 := filepath.Join(testDir, "target1.txt")
  144. targetFile2 := filepath.Join(testDir, "target2.txt")
  145. targetFile3 := filepath.Join(testDir, "target3.txt")
  146. err := os.WriteFile(targetFile1, []byte("test1"), 0o644)
  147. require.NoError(t, err)
  148. err = os.WriteFile(targetFile2, []byte("test2"), 0o644)
  149. require.NoError(t, err)
  150. err = os.WriteFile(targetFile3, []byte("test3"), 0o644)
  151. require.NoError(t, err)
  152. found, err := Lookup(testDir, "target1.txt", "target2.txt", "target3.txt")
  153. require.NoError(t, err)
  154. require.Len(t, found, 3)
  155. require.Contains(t, found, targetFile1)
  156. require.Contains(t, found, targetFile2)
  157. require.Contains(t, found, targetFile3)
  158. })
  159. t.Run("targets found in parent directories", func(t *testing.T) {
  160. testDir := t.TempDir()
  161. // Create subdirectory
  162. subDir := filepath.Join(testDir, "subdir")
  163. err := os.Mkdir(subDir, 0o755)
  164. require.NoError(t, err)
  165. // Create target files in parent directory
  166. targetFile1 := filepath.Join(testDir, "target1.txt")
  167. targetFile2 := filepath.Join(testDir, "target2.txt")
  168. err = os.WriteFile(targetFile1, []byte("test1"), 0o644)
  169. require.NoError(t, err)
  170. err = os.WriteFile(targetFile2, []byte("test2"), 0o644)
  171. require.NoError(t, err)
  172. found, err := Lookup(subDir, "target1.txt", "target2.txt")
  173. require.NoError(t, err)
  174. require.Len(t, found, 2)
  175. require.Contains(t, found, targetFile1)
  176. require.Contains(t, found, targetFile2)
  177. })
  178. t.Run("targets found across multiple directory levels", func(t *testing.T) {
  179. testDir := t.TempDir()
  180. // Create nested subdirectories
  181. subDir := filepath.Join(testDir, "subdir")
  182. err := os.Mkdir(subDir, 0o755)
  183. require.NoError(t, err)
  184. subSubDir := filepath.Join(subDir, "subsubdir")
  185. err = os.Mkdir(subSubDir, 0o755)
  186. require.NoError(t, err)
  187. // Create target files at different levels
  188. targetFile1 := filepath.Join(testDir, "target1.txt")
  189. targetFile2 := filepath.Join(subDir, "target2.txt")
  190. targetFile3 := filepath.Join(subSubDir, "target3.txt")
  191. err = os.WriteFile(targetFile1, []byte("test1"), 0o644)
  192. require.NoError(t, err)
  193. err = os.WriteFile(targetFile2, []byte("test2"), 0o644)
  194. require.NoError(t, err)
  195. err = os.WriteFile(targetFile3, []byte("test3"), 0o644)
  196. require.NoError(t, err)
  197. found, err := Lookup(subSubDir, "target1.txt", "target2.txt", "target3.txt")
  198. require.NoError(t, err)
  199. require.Len(t, found, 3)
  200. require.Contains(t, found, targetFile1)
  201. require.Contains(t, found, targetFile2)
  202. require.Contains(t, found, targetFile3)
  203. })
  204. t.Run("some targets not found", func(t *testing.T) {
  205. testDir := t.TempDir()
  206. // Create only some target files
  207. targetFile1 := filepath.Join(testDir, "target1.txt")
  208. targetFile2 := filepath.Join(testDir, "target2.txt")
  209. err := os.WriteFile(targetFile1, []byte("test1"), 0o644)
  210. require.NoError(t, err)
  211. err = os.WriteFile(targetFile2, []byte("test2"), 0o644)
  212. require.NoError(t, err)
  213. // Search for existing and non-existing targets
  214. found, err := Lookup(testDir, "target1.txt", "nonexistent.txt", "target2.txt", "another_nonexistent.txt")
  215. require.NoError(t, err)
  216. require.Len(t, found, 2)
  217. require.Contains(t, found, targetFile1)
  218. require.Contains(t, found, targetFile2)
  219. })
  220. t.Run("no targets found", func(t *testing.T) {
  221. testDir := t.TempDir()
  222. found, err := Lookup(testDir, "nonexistent1.txt", "nonexistent2.txt", "nonexistent3.txt")
  223. require.NoError(t, err)
  224. require.Empty(t, found)
  225. })
  226. t.Run("target directories found", func(t *testing.T) {
  227. testDir := t.TempDir()
  228. // Create target directories
  229. targetDir1 := filepath.Join(testDir, "targetdir1")
  230. targetDir2 := filepath.Join(testDir, "targetdir2")
  231. err := os.Mkdir(targetDir1, 0o755)
  232. require.NoError(t, err)
  233. err = os.Mkdir(targetDir2, 0o755)
  234. require.NoError(t, err)
  235. found, err := Lookup(testDir, "targetdir1", "targetdir2")
  236. require.NoError(t, err)
  237. require.Len(t, found, 2)
  238. require.Contains(t, found, targetDir1)
  239. require.Contains(t, found, targetDir2)
  240. })
  241. t.Run("mixed files and directories", func(t *testing.T) {
  242. testDir := t.TempDir()
  243. // Create target files and directories
  244. targetFile := filepath.Join(testDir, "target.txt")
  245. targetDir := filepath.Join(testDir, "targetdir")
  246. err := os.WriteFile(targetFile, []byte("test"), 0o644)
  247. require.NoError(t, err)
  248. err = os.Mkdir(targetDir, 0o755)
  249. require.NoError(t, err)
  250. found, err := Lookup(testDir, "target.txt", "targetdir")
  251. require.NoError(t, err)
  252. require.Len(t, found, 2)
  253. require.Contains(t, found, targetFile)
  254. require.Contains(t, found, targetDir)
  255. })
  256. t.Run("invalid starting directory", func(t *testing.T) {
  257. found, err := Lookup("/invalid/path/that/does/not/exist", "target.txt")
  258. require.Error(t, err)
  259. require.Empty(t, found)
  260. })
  261. t.Run("relative path handling", func(t *testing.T) {
  262. // Create target files in current directory
  263. require.NoError(t, os.WriteFile("target1.txt", []byte("test1"), 0o644))
  264. require.NoError(t, os.WriteFile("target2.txt", []byte("test2"), 0o644))
  265. // Search using relative path
  266. found, err := Lookup(".", "target1.txt", "target2.txt")
  267. require.NoError(t, err)
  268. require.Len(t, found, 2)
  269. // Resolve symlinks to handle macOS /private/var vs /var discrepancy
  270. expectedPath1, err := filepath.EvalSymlinks(filepath.Join(tempDir, "target1.txt"))
  271. require.NoError(t, err)
  272. expectedPath2, err := filepath.EvalSymlinks(filepath.Join(tempDir, "target2.txt"))
  273. require.NoError(t, err)
  274. // Check that found paths match expected paths (order may vary)
  275. foundEvalSymlinks := make([]string, len(found))
  276. for i, path := range found {
  277. evalPath, err := filepath.EvalSymlinks(path)
  278. require.NoError(t, err)
  279. foundEvalSymlinks[i] = evalPath
  280. }
  281. require.Contains(t, foundEvalSymlinks, expectedPath1)
  282. require.Contains(t, foundEvalSymlinks, expectedPath2)
  283. })
  284. }
  285. func TestProbeEnt(t *testing.T) {
  286. t.Run("existing file with correct owner", func(t *testing.T) {
  287. tempDir := t.TempDir()
  288. // Create test file
  289. testFile := filepath.Join(tempDir, "test.txt")
  290. err := os.WriteFile(testFile, []byte("test"), 0o644)
  291. require.NoError(t, err)
  292. // Get owner of temp directory
  293. owner, err := Owner(tempDir)
  294. require.NoError(t, err)
  295. // Test probeEnt with correct owner
  296. err = probeEnt(testFile, owner)
  297. require.NoError(t, err)
  298. })
  299. t.Run("existing directory with correct owner", func(t *testing.T) {
  300. tempDir := t.TempDir()
  301. // Create test directory
  302. testDir := filepath.Join(tempDir, "testdir")
  303. err := os.Mkdir(testDir, 0o755)
  304. require.NoError(t, err)
  305. // Get owner of temp directory
  306. owner, err := Owner(tempDir)
  307. require.NoError(t, err)
  308. // Test probeEnt with correct owner
  309. err = probeEnt(testDir, owner)
  310. require.NoError(t, err)
  311. })
  312. t.Run("nonexistent file", func(t *testing.T) {
  313. tempDir := t.TempDir()
  314. nonexistentFile := filepath.Join(tempDir, "nonexistent.txt")
  315. owner, err := Owner(tempDir)
  316. require.NoError(t, err)
  317. err = probeEnt(nonexistentFile, owner)
  318. require.Error(t, err)
  319. require.True(t, errors.Is(err, os.ErrNotExist))
  320. })
  321. t.Run("nonexistent file in nonexistent directory", func(t *testing.T) {
  322. nonexistentFile := "/this/directory/does/not/exists/nonexistent.txt"
  323. err := probeEnt(nonexistentFile, -1)
  324. require.Error(t, err)
  325. require.True(t, errors.Is(err, os.ErrNotExist))
  326. })
  327. t.Run("ownership bypass with -1", func(t *testing.T) {
  328. tempDir := t.TempDir()
  329. // Create test file
  330. testFile := filepath.Join(tempDir, "test.txt")
  331. err := os.WriteFile(testFile, []byte("test"), 0o644)
  332. require.NoError(t, err)
  333. // Test probeEnt with -1 (bypass ownership check)
  334. err = probeEnt(testFile, -1)
  335. require.NoError(t, err)
  336. })
  337. t.Run("ownership mismatch returns permission error", func(t *testing.T) {
  338. tempDir := t.TempDir()
  339. // Create test file
  340. testFile := filepath.Join(tempDir, "test.txt")
  341. err := os.WriteFile(testFile, []byte("test"), 0o644)
  342. require.NoError(t, err)
  343. // Test probeEnt with different owner (use 9999 which is unlikely to be the actual owner)
  344. err = probeEnt(testFile, 9999)
  345. require.Error(t, err)
  346. require.True(t, errors.Is(err, os.ErrPermission))
  347. })
  348. }