Back to Repositories

Testing Abstract Storage Implementation in DevDocs

This test suite validates the functionality of an abstract storage system in the DevDocs documentation platform. It ensures proper file handling, path management, and storage operations through comprehensive unit testing.

Test Coverage Overview

The test suite provides extensive coverage of the AbstractStore class functionality:

  • Path validation and manipulation
  • File operations (read, write, delete)
  • Directory management and navigation
  • Error handling for invalid paths and locked states
  • Working path and root path integrity

Implementation Analysis

The testing approach uses Minitest with RSpec-style syntax to validate storage operations. It employs stubbing and mocking to isolate components and verify behavior patterns. The implementation focuses on maintaining path security and preventing unauthorized access outside root directories.

Key patterns include:
  • Before/after state validation
  • Exception handling verification
  • Path manipulation safety checks
  • Store locking mechanism validation

Technical Details

Testing infrastructure includes:

  • Minitest::Spec as the testing framework
  • Custom error classes for specific failure scenarios
  • FakeInstrumentation module for monitoring operations
  • Path manipulation utilities
  • File operation mocking/stubbing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive edge case coverage
  • Isolated test scenarios
  • Clear test organization by functionality
  • Proper setup and teardown management
  • Effective use of test doubles
  • Consistent error handling validation

freecodecamp/devdocs

test/lib/docs/storage/abstract_store_test.rb

            
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