Back to Repositories

Testing FileStore Operations Management in DevDocs

This test suite validates the FileStore functionality in the DevDocs documentation system, focusing on file operations and storage management. It ensures reliable file handling and storage operations for the documentation platform.

Test Coverage Overview

The test suite provides comprehensive coverage of file storage operations including:

  • Basic file operations (read, write, delete)
  • Directory management and recursive operations
  • File metadata handling (mtime, size)
  • Path traversal and enumeration
  • Edge cases for file existence and directory structures

Implementation Analysis

The testing approach utilizes Minitest’s spec-style syntax with focused unit tests for each FileStore method. It implements before/after hooks for test isolation and employs helper methods for file manipulation.

The tests use temporary file system operations and cleanup to ensure test independence and reliability.

Technical Details

  • Testing Framework: Minitest::Spec
  • File System Utilities: FileUtils
  • Temporary File Handling: Tempfile
  • Test Isolation: Directory cleanup after each test
  • Path Management: File.join and expand_path

Best Practices Demonstrated

The test suite exemplifies robust testing practices including proper test isolation, comprehensive edge case coverage, and clean test organization.

  • Isolated test environment using temporary directories
  • Thorough cleanup after tests
  • Helper methods for common operations
  • Consistent test structure and naming

freecodecamp/devdocs

test/lib/docs/storage/file_store_test.rb

            
require_relative '../../../test_helper'
require_relative '../../../../lib/docs'

class DocsFileStoreTest < Minitest::Spec
  let :store do
    Docs::FileStore.new(tmp_path)
  end

  after do
    FileUtils.rm_rf "#{tmp_path}/."
  end

  def expand_path(path)
    File.join(tmp_path, path)
  end

  def read(path)
    File.read expand_path(path)
  end

  def write(path, content)
    File.write expand_path(path), content
  end

  def exists?(path)
    File.exist? expand_path(path)
  end

  def touch(path)
    FileUtils.touch expand_path(path)
  end

  def mkpath(path)
    FileUtils.mkpath expand_path(path)
  end

  describe "#read" do
    it "reads a file" do
      write 'file', 'content'
      assert_equal 'content', store.read('file')
    end
  end

  describe "#write" do
    context "with a string" do
      it "creates the file when it doesn't exist" do
        store.write 'file', 'content'
        assert exists?('file')
        assert_equal 'content', read('file')
      end

      it "updates the file when it exists" do
        touch 'file'
        store.write 'file', 'content'
        assert_equal 'content', read('file')
      end
    end

    context "with a Tempfile" do
      let :file do
        Tempfile.new('tmp').tap do |file|
          file.write 'content'
          file.close
        end
      end

      it "creates the file when it doesn't exist" do
        store.write 'file', file
        assert exists?('file')
        assert_equal 'content', read('file')
      end

      it "updates the file when it exists" do
        touch 'file'
        store.write 'file', file
        assert_equal 'content', read('file')
      end
    end

    it "recursively creates directories" do
      store.write '1/2/file', ''
      assert exists?('1/2/file')
    end
  end

  describe "#delete" do
    it "deletes a file" do
      touch 'file'
      store.delete 'file'
      refute exists?('file')
    end

    it "deletes a directory" do
      mkpath '1/2'
      touch '1/2/file'
      store.delete '1'
      refute exists?('1/2/exist')
      refute exists?('1/2')
      refute exists?('1')
    end
  end

  describe "#exist?" do
    it "returns true when the file exists" do
      touch 'file'
      assert store.exist?('file')
    end

    it "returns false when the file doesn't exist" do
      refute store.exist?('file')
    end
  end

  describe "#mtime" do
    it "returns the file modification time" do
      touch 'file'
      created_at = Time.now.round - 86400
      modified_at = created_at + 1
      File.utime created_at, modified_at, expand_path('file')
      assert_equal modified_at, store.mtime('file')
    end
  end

  describe "#size" do
    it "returns the file's size" do
      write 'file', 'content'
      assert_equal File.size(expand_path('file')), store.size('file')
    end
  end

  describe "#each" do
    let :paths do
      paths = []
      store.each { |path| paths << path.remove(tmp_path) }
      paths
    end

    it "yields file paths" do
      touch 'file'
      assert_equal ['/file'], paths
    end

    it "yields directory paths" do
      mkpath 'dir'
      assert_equal ['/dir'], paths
    end

    it "yields file paths recursively" do
      mkpath 'dir'
      touch 'dir/file'
      assert_includes paths, '/dir/file'
    end

    it "yields directory paths recursively" do
      mkpath 'dir/dir'
      assert_includes paths, '/dir/dir'
    end

    it "doesn't yield file paths that start with '.'" do
      touch '.file'
      assert_empty paths
    end

    it "doesn't yield directory paths that start with '.'" do
      mkpath '.dir'
      assert_empty paths
    end

    it "yields directories before what's inside them" do
      mkpath 'dir'
      touch 'dir/file'
      assert paths.index('/dir') < paths.index('/dir/file')
    end

    context "when the block deletes the directory" do
      it "stops yielding what was inside it" do
        mkpath 'dir'
        touch 'dir/file'
        store.each do |path|
          (@paths ||= []) << path
          FileUtils.rm_rf(path) if path == expand_path('dir')
        end
        refute_includes @paths, expand_path('dir/file')
      end
    end
  end
end