require_relative '../../../test_helper'require_relative '../../../../lib/docs' class DocsAbstractStoreTest < Minitest::Spec InvalidPathError = Docs::AbstractStore::InvalidPathError LockError = Docs::AbstractStore::LockError let :path do '/' end let :store do Docs::AbstractStore.new(@path || path).tap do |store| store.extend FakeInstrumentation end end describe ".new" do it "raises an error with a relative path" do assert_raises ArgumentError do Docs::AbstractStore.new 'path' end end it "sets #root_path" do @path = '/path' assert_equal @path, store.root_path end it "expands #root_path" do @path = '/path/..' assert_equal '/', store.root_path end it "sets #working_path" do assert_equal store.root_path, store.working_path end end describe "#root_path" do it "can't be overwritten" do @path = '/path' store.root_path << '/..' assert_equal '/path', store.root_path end end describe "#working_path" do it "can't be overwritten" do @path = '/path' store.working_path << '/..' assert_equal '/path', store.working_path end end describe "#open" do it "raises an error when the store is locked" do assert_raises LockError do store.send :lock, &-> { store.open 'dir' } end end context "with a relative path" do it "updates #working_path relative to #root_path" do 2.times { store.open 'dir' } assert_equal File.join(path, 'dir'), store.working_path end it "expands the new #working_path" do store.open './dir/../' assert_equal path, store.working_path end it "raises an error when the new #working_path is outside of #root_path" do @path = '/dir' assert_raises InvalidPathError do store.open '../dir2' end end end context "with an absolute path" do it "updates #working_path" do store.open File.join(path, 'dir') assert_equal File.join(path, 'dir'), store.working_path end it "expands the new #working_path" do store.open File.join(path, 'dir/..') assert_equal path, store.working_path end it "raises an error when the new #working_path is outside of #root_path" do @path = '/dir' assert_raises InvalidPathError do store.open '/dir2' end end end context "with a block" do it "calls the block" do store.open('dir') { @called = true } assert @called end it "returns the block's return value" do assert_equal 1, store.open('dir') { 1 } end it "updates #working_path while calling the block" do store.open 'dir' do assert_equal File.join(path, 'dir'), store.working_path end end it "resets #working_path to its previous value afterward" do store.open('dir') store.open('dir2') {} assert_equal File.join(path, 'dir'), store.working_path end it "resets #working_path even when the block fails" do assert_raises RuntimeError do store.open('dir') { raise } end assert_equal path, store.working_path end end end describe "#close" do it "resets #working_path to #root_path" do 2.times { store.open 'dir' } store.close assert_equal path, store.working_path end it "raises an error when the store is locked" do assert_raises LockError do store.send :lock, &-> { store.close } end end end describe "#expand_path" do context "when #working_path is '/'" do before do store.open '/' end it "returns '/path' with './path'" do assert_equal '/path', store.expand_path('./path') end it "returns '/path' with '/path'" do assert_equal '/path', store.expand_path('/path') end end context "when #working_path is '/dir'" do before do store.open '/dir' end it "returns '/dir/path' with './path'" do assert_equal '/dir/path', store.expand_path('./path') end it "returns '/dir/path' with 'path/../path'" do assert_equal '/dir/path', store.expand_path('path/../path') end it "returns '/dir/path' with '/dir/path'" do assert_equal '/dir/path', store.expand_path('/dir/path') end it "raises an error with '..'" do assert_raises InvalidPathError do store.expand_path '..' end end it "raises an error with '/'" do assert_raises InvalidPathError do store.expand_path '/' end end end end describe "#read" do it "raises an error with a path outside of #working_path" do @path = '/path' assert_raises InvalidPathError do store.read '../file' end end it "returns nil when the file doesn't exist" do dont_allow(store).read_file stub(store).file_exist?('/file') { false } assert_nil store.read('file') end it "returns #read_file when the file exists" do stub(store).read_file('/file') { 1 } stub(store).file_exist?('/file') { true } assert_equal 1, store.read('file') end end describe "#write" do it "raises an error with a path outside of #working_path" do @path = '/path' assert_raises InvalidPathError do store.write '../file', '' end end context "when the file doesn't exist" do before do stub(store).file_exist?('/file') { false } stub(store).create_file end it "returns #create_file" do stub(store).create_file('/file', '') { 1 } assert_equal 1, store.write('file', '') end it "instrument 'create'" do store.write 'file', '' assert store.last_instrumentation assert_equal 'create.store', store.last_instrumentation[:event] assert_equal '/file', store.last_instrumentation[:payload][:path] end end context "when the file exists" do before do stub(store).file_exist?('/file') { true } stub(store).update_file end it "returns #update_file" do stub(store).update_file('/file', '') { 1 } assert_equal 1, store.write('file', '') end it "instruments 'update'" do store.write 'file', '' assert store.last_instrumentation assert_equal 'update.store', store.last_instrumentation[:event] assert_equal '/file', store.last_instrumentation[:payload][:path] end end end describe "#delete" do it "raises an error with a path outside og #working_path" do @path = '/path' assert_raises InvalidPathError do store.delete '../file' end end it "returns nil when the file doesn't exist" do dont_allow(store).delete_file stub(store).file_exist?('/file') { false } assert_nil store.delete('file') end context "when the file exists" do before do stub(store).file_exist?('/file') { true } stub(store).delete_file end it "calls #delete_file" do mock(store).delete_file('/file') store.delete 'file' end it "returns true" do assert store.delete('file') end it "instruments 'destroy'" do store.delete 'file' assert store.last_instrumentation assert_equal 'destroy.store', store.last_instrumentation[:event] assert_equal '/file', store.last_instrumentation[:payload][:path] end end end describe "exist?" do it "raises an error with a path outside of #working_path" do @path = '/path' assert_raises InvalidPathError do store.exist? '../file' end end it "returns #file_exist?" do stub(store).file_exist?('/file') { 1 } assert_equal 1, store.exist?('file') end end describe "mtime" do it "raises an error with a path outside of #working_path" do @path = '/path' assert_raises InvalidPathError do store.mtime '../file' end end it "returns nil when the file doesn't exist" do stub(store).file_exist?('/file') { false } dont_allow(store).file_mtime assert_nil store.mtime('file') end it "returns #file_mtime when the file exists" do stub(store).file_exist?('/file') { true } stub(store).file_mtime('/file') { 1 } assert_equal 1, store.mtime('file') end end describe "#size" do it "raises an error with a path outside of #working_path" do @path = '/path' assert_raises InvalidPathError do store.size '../file' end end it "returns nil when the file doesn't exist" do stub(store).file_exist?('/file') { false } dont_allow(store).file_size assert_nil store.size('file') end it "returns #file_size when the file exists" do stub(store).file_exist?('/file') { true } stub(store).file_size('/file') { 1 } assert_equal 1, store.size('file') end end describe "#each" do it "calls #list_files with #working_path" do store.open 'dir' block = Proc.new {} mock(store).list_files(File.join(path, 'dir'), &block) store.each(&block) end end describe "#replace" do before do stub(store).file_exist? stub(store).create_file stub(store).delete_file end def stub_paths(*paths) stub(store).each { |&block| paths.each(&block) } end it "calls the block" do store.replace { @called = true } assert @called end it "returns the block's return value" do assert_equal 1, store.replace { 1 } end it "locks the store while calling the block" do assert_raises LockError do store.replace { store.open('dir') } end store.open 'dir' end context "with a path" do it "opens the path while calling the block" do store.replace 'dir' do assert_equal File.join(path, 'dir'), store.working_path end end end context "when the block writes no files" do it "doesn't delete files" do stub_paths '/', '/file' dont_allow(store).delete_file store.replace {} end end context "when the block writes files" do it "deletes untouched files" do stub_paths '/', '/dir', '/dir/file', '/dir/file2', '/dir2' mock(store).delete_file('/dir/file2').then.delete_file('/dir2') store.replace { store.write 'dir/file', '' } end it "doesn't delete touched files" do stub_paths '/', '/dir', '/dir/(file)' dont_allow(store).delete_file store.replace { store.write 'dir/(file)', '' } end end context "when the block fails" do it "doesn't delete files" do stub_paths '/', '/file' dont_allow(store).delete_file assert_raises RuntimeError do store.replace { store.write 'file2', ''; raise } end end it "unlocks the store afterward" do assert_raises RuntimeError do store.replace { raise } end store.open 'dir' end end context "when called multiple times" do before do stub_paths '/', '/file' end it "deletes untouched files that were touched the previous time" do store.replace { store.write 'file', '' } mock(store).delete_file '/file' store.replace { store.write 'file2', '' } end it "deletes untouched files that were touched and failed the previous time" do assert_raises RuntimeError do store.replace { store.write 'file', ''; raise } end mock(store).delete_file '/file' store.replace { store.write 'file2', '' } end end endend
require_relative '../../../test_helper'
require_relative '../../../../lib/docs'
class DocsAbstractStoreTest < Minitest::Spec
InvalidPathError = Docs::AbstractStore::InvalidPathError
LockError = Docs::AbstractStore::LockError
let :path do
'/'
end
let :store do
Docs::AbstractStore.new(@path || path).tap do |store|
store.extend FakeInstrumentation
end
end
describe ".new" do
it "raises an error with a relative path" do
assert_raises ArgumentError do
Docs::AbstractStore.new 'path'
end
end
it "sets #root_path" do
@path = '/path'
assert_equal @path, store.root_path
end
it "expands #root_path" do
@path = '/path/..'
assert_equal '/', store.root_path
end
it "sets #working_path" do
assert_equal store.root_path, store.working_path
end
end
describe "#root_path" do
it "can't be overwritten" do
@path = '/path'
store.root_path << '/..'
assert_equal '/path', store.root_path
end
end
describe "#working_path" do
it "can't be overwritten" do
@path = '/path'
store.working_path << '/..'
assert_equal '/path', store.working_path
end
end
describe "#open" do
it "raises an error when the store is locked" do
assert_raises LockError do
store.send :lock, &-> { store.open 'dir' }
end
end
context "with a relative path" do
it "updates #working_path relative to #root_path" do
2.times { store.open 'dir' }
assert_equal File.join(path, 'dir'), store.working_path
end
it "expands the new #working_path" do
store.open './dir/../'
assert_equal path, store.working_path
end
it "raises an error when the new #working_path is outside of #root_path" do
@path = '/dir'
assert_raises InvalidPathError do
store.open '../dir2'
end
end
end
context "with an absolute path" do
it "updates #working_path" do
store.open File.join(path, 'dir')
assert_equal File.join(path, 'dir'), store.working_path
end
it "expands the new #working_path" do
store.open File.join(path, 'dir/..')
assert_equal path, store.working_path
end
it "raises an error when the new #working_path is outside of #root_path" do
@path = '/dir'
assert_raises InvalidPathError do
store.open '/dir2'
end
end
end
context "with a block" do
it "calls the block" do
store.open('dir') { @called = true }
assert @called
end
it "returns the block's return value" do
assert_equal 1, store.open('dir') { 1 }
end
it "updates #working_path while calling the block" do
store.open 'dir' do
assert_equal File.join(path, 'dir'), store.working_path
end
end
it "resets #working_path to its previous value afterward" do
store.open('dir')
store.open('dir2') {}
assert_equal File.join(path, 'dir'), store.working_path
end
it "resets #working_path even when the block fails" do
assert_raises RuntimeError do
store.open('dir') { raise }
end
assert_equal path, store.working_path
end
end
end
describe "#close" do
it "resets #working_path to #root_path" do
2.times { store.open 'dir' }
store.close
assert_equal path, store.working_path
end
it "raises an error when the store is locked" do
assert_raises LockError do
store.send :lock, &-> { store.close }
end
end
end
describe "#expand_path" do
context "when #working_path is '/'" do
before do
store.open '/'
end
it "returns '/path' with './path'" do
assert_equal '/path', store.expand_path('./path')
end
it "returns '/path' with '/path'" do
assert_equal '/path', store.expand_path('/path')
end
end
context "when #working_path is '/dir'" do
before do
store.open '/dir'
end
it "returns '/dir/path' with './path'" do
assert_equal '/dir/path', store.expand_path('./path')
end
it "returns '/dir/path' with 'path/../path'" do
assert_equal '/dir/path', store.expand_path('path/../path')
end
it "returns '/dir/path' with '/dir/path'" do
assert_equal '/dir/path', store.expand_path('/dir/path')
end
it "raises an error with '..'" do
assert_raises InvalidPathError do
store.expand_path '..'
end
end
it "raises an error with '/'" do
assert_raises InvalidPathError do
store.expand_path '/'
end
end
end
end
describe "#read" do
it "raises an error with a path outside of #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.read '../file'
end
end
it "returns nil when the file doesn't exist" do
dont_allow(store).read_file
stub(store).file_exist?('/file') { false }
assert_nil store.read('file')
end
it "returns #read_file when the file exists" do
stub(store).read_file('/file') { 1 }
stub(store).file_exist?('/file') { true }
assert_equal 1, store.read('file')
end
end
describe "#write" do
it "raises an error with a path outside of #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.write '../file', ''
end
end
context "when the file doesn't exist" do
before do
stub(store).file_exist?('/file') { false }
stub(store).create_file
end
it "returns #create_file" do
stub(store).create_file('/file', '') { 1 }
assert_equal 1, store.write('file', '')
end
it "instrument 'create'" do
store.write 'file', ''
assert store.last_instrumentation
assert_equal 'create.store', store.last_instrumentation[:event]
assert_equal '/file', store.last_instrumentation[:payload][:path]
end
end
context "when the file exists" do
before do
stub(store).file_exist?('/file') { true }
stub(store).update_file
end
it "returns #update_file" do
stub(store).update_file('/file', '') { 1 }
assert_equal 1, store.write('file', '')
end
it "instruments 'update'" do
store.write 'file', ''
assert store.last_instrumentation
assert_equal 'update.store', store.last_instrumentation[:event]
assert_equal '/file', store.last_instrumentation[:payload][:path]
end
end
end
describe "#delete" do
it "raises an error with a path outside og #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.delete '../file'
end
end
it "returns nil when the file doesn't exist" do
dont_allow(store).delete_file
stub(store).file_exist?('/file') { false }
assert_nil store.delete('file')
end
context "when the file exists" do
before do
stub(store).file_exist?('/file') { true }
stub(store).delete_file
end
it "calls #delete_file" do
mock(store).delete_file('/file')
store.delete 'file'
end
it "returns true" do
assert store.delete('file')
end
it "instruments 'destroy'" do
store.delete 'file'
assert store.last_instrumentation
assert_equal 'destroy.store', store.last_instrumentation[:event]
assert_equal '/file', store.last_instrumentation[:payload][:path]
end
end
end
describe "exist?" do
it "raises an error with a path outside of #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.exist? '../file'
end
end
it "returns #file_exist?" do
stub(store).file_exist?('/file') { 1 }
assert_equal 1, store.exist?('file')
end
end
describe "mtime" do
it "raises an error with a path outside of #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.mtime '../file'
end
end
it "returns nil when the file doesn't exist" do
stub(store).file_exist?('/file') { false }
dont_allow(store).file_mtime
assert_nil store.mtime('file')
end
it "returns #file_mtime when the file exists" do
stub(store).file_exist?('/file') { true }
stub(store).file_mtime('/file') { 1 }
assert_equal 1, store.mtime('file')
end
end
describe "#size" do
it "raises an error with a path outside of #working_path" do
@path = '/path'
assert_raises InvalidPathError do
store.size '../file'
end
end
it "returns nil when the file doesn't exist" do
stub(store).file_exist?('/file') { false }
dont_allow(store).file_size
assert_nil store.size('file')
end
it "returns #file_size when the file exists" do
stub(store).file_exist?('/file') { true }
stub(store).file_size('/file') { 1 }
assert_equal 1, store.size('file')
end
end
describe "#each" do
it "calls #list_files with #working_path" do
store.open 'dir'
block = Proc.new {}
mock(store).list_files(File.join(path, 'dir'), &block)
store.each(&block)
end
end
describe "#replace" do
before do
stub(store).file_exist?
stub(store).create_file
stub(store).delete_file
end
def stub_paths(*paths)
stub(store).each { |&block| paths.each(&block) }
end
it "calls the block" do
store.replace { @called = true }
assert @called
end
it "returns the block's return value" do
assert_equal 1, store.replace { 1 }
end
it "locks the store while calling the block" do
assert_raises LockError do
store.replace { store.open('dir') }
end
store.open 'dir'
end
context "with a path" do
it "opens the path while calling the block" do
store.replace 'dir' do
assert_equal File.join(path, 'dir'), store.working_path
end
end
end
context "when the block writes no files" do
it "doesn't delete files" do
stub_paths '/', '/file'
dont_allow(store).delete_file
store.replace {}
end
end
context "when the block writes files" do
it "deletes untouched files" do
stub_paths '/', '/dir', '/dir/file', '/dir/file2', '/dir2'
mock(store).delete_file('/dir/file2').then.delete_file('/dir2')
store.replace { store.write 'dir/file', '' }
end
it "doesn't delete touched files" do
stub_paths '/', '/dir', '/dir/(file)'
dont_allow(store).delete_file
store.replace { store.write 'dir/(file)', '' }
end
end
context "when the block fails" do
it "doesn't delete files" do
stub_paths '/', '/file'
dont_allow(store).delete_file
assert_raises RuntimeError do
store.replace { store.write 'file2', ''; raise }
end
end
it "unlocks the store afterward" do
assert_raises RuntimeError do
store.replace { raise }
end
store.open 'dir'
end
end
context "when called multiple times" do
before do
stub_paths '/', '/file'
end
it "deletes untouched files that were touched the previous time" do
store.replace { store.write 'file', '' }
mock(store).delete_file '/file'
store.replace { store.write 'file2', '' }
end
it "deletes untouched files that were touched and failed the previous time" do
assert_raises RuntimeError do
store.replace { store.write 'file', ''; raise }
end
mock(store).delete_file '/file'
store.replace { store.write 'file2', '' }
end
end
end
end