abstract_store_test.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. require 'test_helper'
  2. require 'docs'
  3. class DocsAbstractStoreTest < MiniTest::Spec
  4. InvalidPathError = Docs::AbstractStore::InvalidPathError
  5. LockError = Docs::AbstractStore::LockError
  6. let :path do
  7. '/'
  8. end
  9. let :store do
  10. Docs::AbstractStore.new(@path || path).tap do |store|
  11. store.extend FakeInstrumentation
  12. end
  13. end
  14. describe ".new" do
  15. it "raises an error with a relative path" do
  16. assert_raises ArgumentError do
  17. Docs::AbstractStore.new 'path'
  18. end
  19. end
  20. it "sets #root_path" do
  21. @path = '/path'
  22. assert_equal @path, store.root_path
  23. end
  24. it "expands #root_path" do
  25. @path = '/path/..'
  26. assert_equal '/', store.root_path
  27. end
  28. it "sets #working_path" do
  29. assert_equal store.root_path, store.working_path
  30. end
  31. end
  32. describe "#root_path" do
  33. it "can't be overwritten" do
  34. @path = '/path'
  35. store.root_path << '/..'
  36. assert_equal '/path', store.root_path
  37. end
  38. end
  39. describe "#working_path" do
  40. it "can't be overwritten" do
  41. @path = '/path'
  42. store.working_path << '/..'
  43. assert_equal '/path', store.working_path
  44. end
  45. end
  46. describe "#open" do
  47. it "raises an error when the store is locked" do
  48. assert_raises LockError do
  49. store.send :lock, &-> { store.open 'dir' }
  50. end
  51. end
  52. context "with a relative path" do
  53. it "updates #working_path relative to #root_path" do
  54. 2.times { store.open 'dir' }
  55. assert_equal File.join(path, 'dir'), store.working_path
  56. end
  57. it "expands the new #working_path" do
  58. store.open './dir/../'
  59. assert_equal path, store.working_path
  60. end
  61. it "raises an error when the new #working_path is outside of #root_path" do
  62. @path = '/dir'
  63. assert_raises InvalidPathError do
  64. store.open '../dir2'
  65. end
  66. end
  67. end
  68. context "with an absolute path" do
  69. it "updates #working_path" do
  70. store.open File.join(path, 'dir')
  71. assert_equal File.join(path, 'dir'), store.working_path
  72. end
  73. it "expands the new #working_path" do
  74. store.open File.join(path, 'dir/..')
  75. assert_equal path, store.working_path
  76. end
  77. it "raises an error when the new #working_path is outside of #root_path" do
  78. @path = '/dir'
  79. assert_raises InvalidPathError do
  80. store.open '/dir2'
  81. end
  82. end
  83. end
  84. context "with a block" do
  85. it "calls the block" do
  86. store.open('dir') { @called = true }
  87. assert @called
  88. end
  89. it "returns the block's return value" do
  90. assert_equal 1, store.open('dir') { 1 }
  91. end
  92. it "updates #working_path while calling the block" do
  93. store.open 'dir' do
  94. assert_equal File.join(path, 'dir'), store.working_path
  95. end
  96. end
  97. it "resets #working_path to its previous value afterward" do
  98. store.open('dir')
  99. store.open('dir2') {}
  100. assert_equal File.join(path, 'dir'), store.working_path
  101. end
  102. it "resets #working_path even when the block fails" do
  103. assert_raises RuntimeError do
  104. store.open('dir') { raise }
  105. end
  106. assert_equal path, store.working_path
  107. end
  108. end
  109. end
  110. describe "#close" do
  111. it "resets #working_path to #root_path" do
  112. 2.times { store.open 'dir' }
  113. store.close
  114. assert_equal path, store.working_path
  115. end
  116. it "raises an error when the store is locked" do
  117. assert_raises LockError do
  118. store.send :lock, &-> { store.close }
  119. end
  120. end
  121. end
  122. describe "#expand_path" do
  123. context "when #working_path is '/'" do
  124. before do
  125. store.open '/'
  126. end
  127. it "returns '/path' with './path'" do
  128. assert_equal '/path', store.expand_path('./path')
  129. end
  130. it "returns '/path' with '/path'" do
  131. assert_equal '/path', store.expand_path('/path')
  132. end
  133. end
  134. context "when #working_path is '/dir'" do
  135. before do
  136. store.open '/dir'
  137. end
  138. it "returns '/dir/path' with './path'" do
  139. assert_equal '/dir/path', store.expand_path('./path')
  140. end
  141. it "returns '/dir/path' with 'path/../path'" do
  142. assert_equal '/dir/path', store.expand_path('path/../path')
  143. end
  144. it "returns '/dir/path' with '/dir/path'" do
  145. assert_equal '/dir/path', store.expand_path('/dir/path')
  146. end
  147. it "raises an error with '..'" do
  148. assert_raises InvalidPathError do
  149. store.expand_path '..'
  150. end
  151. end
  152. it "raises an error with '/'" do
  153. assert_raises InvalidPathError do
  154. store.expand_path '/'
  155. end
  156. end
  157. end
  158. end
  159. describe "#read" do
  160. it "raises an error with a path outside of #working_path" do
  161. @path = '/path'
  162. assert_raises InvalidPathError do
  163. store.read '../file'
  164. end
  165. end
  166. it "returns nil when the file doesn't exist" do
  167. dont_allow(store).read_file
  168. stub(store).file_exist?('/file') { false }
  169. assert_nil store.read('file')
  170. end
  171. it "returns #read_file when the file exists" do
  172. stub(store).read_file('/file') { 1 }
  173. stub(store).file_exist?('/file') { true }
  174. assert_equal 1, store.read('file')
  175. end
  176. end
  177. describe "#write" do
  178. it "raises an error with a path outside of #working_path" do
  179. @path = '/path'
  180. assert_raises InvalidPathError do
  181. store.write '../file', ''
  182. end
  183. end
  184. context "when the file doesn't exist" do
  185. before do
  186. stub(store).file_exist?('/file') { false }
  187. stub(store).create_file
  188. end
  189. it "returns #create_file" do
  190. stub(store).create_file('/file', '') { 1 }
  191. assert_equal 1, store.write('file', '')
  192. end
  193. it "instrument 'create'" do
  194. store.write 'file', ''
  195. assert store.last_instrumentation
  196. assert_equal 'create.store', store.last_instrumentation[:event]
  197. assert_equal '/file', store.last_instrumentation[:payload][:path]
  198. end
  199. end
  200. context "when the file exists" do
  201. before do
  202. stub(store).file_exist?('/file') { true }
  203. stub(store).update_file
  204. end
  205. it "returns #update_file" do
  206. stub(store).update_file('/file', '') { 1 }
  207. assert_equal 1, store.write('file', '')
  208. end
  209. it "instruments 'update'" do
  210. store.write 'file', ''
  211. assert store.last_instrumentation
  212. assert_equal 'update.store', store.last_instrumentation[:event]
  213. assert_equal '/file', store.last_instrumentation[:payload][:path]
  214. end
  215. end
  216. end
  217. describe "#delete" do
  218. it "raises an error with a path outside og #working_path" do
  219. @path = '/path'
  220. assert_raises InvalidPathError do
  221. store.delete '../file'
  222. end
  223. end
  224. it "returns nil when the file doesn't exist" do
  225. dont_allow(store).delete_file
  226. stub(store).file_exist?('/file') { false }
  227. assert_nil store.delete('file')
  228. end
  229. context "when the file exists" do
  230. before do
  231. stub(store).file_exist?('/file') { true }
  232. stub(store).delete_file
  233. end
  234. it "calls #delete_file" do
  235. mock(store).delete_file('/file')
  236. store.delete 'file'
  237. end
  238. it "returns true" do
  239. assert store.delete('file')
  240. end
  241. it "instruments 'destroy'" do
  242. store.delete 'file'
  243. assert store.last_instrumentation
  244. assert_equal 'destroy.store', store.last_instrumentation[:event]
  245. assert_equal '/file', store.last_instrumentation[:payload][:path]
  246. end
  247. end
  248. end
  249. describe "exist?" do
  250. it "raises an error with a path outside of #working_path" do
  251. @path = '/path'
  252. assert_raises InvalidPathError do
  253. store.exist? '../file'
  254. end
  255. end
  256. it "returns #file_exist?" do
  257. stub(store).file_exist?('/file') { 1 }
  258. assert_equal 1, store.exist?('file')
  259. end
  260. end
  261. describe "mtime" do
  262. it "raises an error with a path outside of #working_path" do
  263. @path = '/path'
  264. assert_raises InvalidPathError do
  265. store.mtime '../file'
  266. end
  267. end
  268. it "returns nil when the file doesn't exist" do
  269. stub(store).file_exist?('/file') { false }
  270. dont_allow(store).file_mtime
  271. assert_nil store.mtime('file')
  272. end
  273. it "returns #file_mtime when the file exists" do
  274. stub(store).file_exist?('/file') { true }
  275. stub(store).file_mtime('/file') { 1 }
  276. assert_equal 1, store.mtime('file')
  277. end
  278. end
  279. describe "#size" do
  280. it "raises an error with a path outside of #working_path" do
  281. @path = '/path'
  282. assert_raises InvalidPathError do
  283. store.size '../file'
  284. end
  285. end
  286. it "returns nil when the file doesn't exist" do
  287. stub(store).file_exist?('/file') { false }
  288. dont_allow(store).file_size
  289. assert_nil store.size('file')
  290. end
  291. it "returns #file_size when the file exists" do
  292. stub(store).file_exist?('/file') { true }
  293. stub(store).file_size('/file') { 1 }
  294. assert_equal 1, store.size('file')
  295. end
  296. end
  297. describe "#each" do
  298. it "calls #list_files with #working_path" do
  299. store.open 'dir'
  300. block = Proc.new {}
  301. mock(store).list_files(File.join(path, 'dir'), &block)
  302. store.each(&block)
  303. end
  304. end
  305. describe "#replace" do
  306. before do
  307. stub(store).file_exist?
  308. stub(store).create_file
  309. stub(store).delete_file
  310. end
  311. def stub_paths(*paths)
  312. stub(store).each { |block| paths.each(&block) }
  313. end
  314. it "calls the block" do
  315. store.replace { @called = true }
  316. assert @called
  317. end
  318. it "returns the block's return value" do
  319. assert_equal 1, store.replace { 1 }
  320. end
  321. it "locks the store while calling the block" do
  322. assert_raises LockError do
  323. store.replace { store.open('dir') }
  324. end
  325. store.open 'dir'
  326. end
  327. context "with a path" do
  328. it "opens the path while calling the block" do
  329. store.replace 'dir' do
  330. assert_equal File.join(path, 'dir'), store.working_path
  331. end
  332. end
  333. end
  334. context "when the block writes no files" do
  335. it "doesn't delete files" do
  336. stub_paths '/', '/file'
  337. dont_allow(store).delete_file
  338. store.replace {}
  339. end
  340. end
  341. context "when the block writes files" do
  342. it "deletes untouched files" do
  343. stub_paths '/', '/dir', '/dir/file', '/dir/file2', '/dir2'
  344. mock(store).delete_file('/dir/file2').then.delete_file('/dir2')
  345. store.replace { store.write 'dir/file', '' }
  346. end
  347. it "doesn't delete touched files" do
  348. stub_paths '/', '/dir', '/dir/(file)'
  349. dont_allow(store).delete_file
  350. store.replace { store.write 'dir/(file)', '' }
  351. end
  352. end
  353. context "when the block fails" do
  354. it "doesn't delete files" do
  355. stub_paths '/', '/file'
  356. dont_allow(store).delete_file
  357. assert_raises RuntimeError do
  358. store.replace { store.write 'file2', ''; raise }
  359. end
  360. end
  361. it "unlocks the store afterward" do
  362. assert_raises RuntimeError do
  363. store.replace { raise }
  364. end
  365. store.open 'dir'
  366. end
  367. end
  368. context "when called multiple times" do
  369. before do
  370. stub_paths '/', '/file'
  371. end
  372. it "deletes untouched files that were touched the previous time" do
  373. store.replace { store.write 'file', '' }
  374. mock(store).delete_file '/file'
  375. store.replace { store.write 'file2', '' }
  376. end
  377. it "deletes untouched files that were touched and failed the previous time" do
  378. assert_raises RuntimeError do
  379. store.replace { store.write 'file', ''; raise }
  380. end
  381. mock(store).delete_file '/file'
  382. store.replace { store.write 'file2', '' }
  383. end
  384. end
  385. end
  386. end