diff --git a/app/models/repository/github.rb b/app/models/repository/github.rb
index 6e607e0..43011af 100644
--- a/app/models/repository/github.rb
+++ b/app/models/repository/github.rb
@@ -34,6 +34,7 @@ def branches
     scm.branches
   end
 
+  # リモートリポジトリの最新状況を取得し、changesetsに反映する
   def fetch_changesets(options = {})
     opts = options.merge({
       last_committed_date: extra_info&.send(:[], "last_committed_date"),
@@ -56,6 +57,8 @@ def fetch_changesets(options = {})
     save(validate: false)
   end
 
+  # revisionオブジェクトの配列を引数に受け取る
+  # changesetsテーブルに保存する(すでに保存済みのrevisionはスキップ)
   def save_revisions!(revisions, revisions_copy)
     limit = 100
     offset = 0
@@ -96,6 +99,8 @@ def save_revisions!(revisions, revisions_copy)
   end
   private :save_revisions!
 
+  # nameにコミットのSHA1ハッシュを受け取る
+  # nameにリビジョン名が一致する、もしくはscmidに先頭一致するchangeset一件を返す
   def find_changeset_by_name(name)
     if name.present?
       changesets.find_by(revision: name.to_s) ||
@@ -103,6 +108,9 @@ def find_changeset_by_name(name)
     end
   end
 
+  # pathにファイルパス、identifierにコミットのshaを受け取る
+  # scmから引数に該当するエントリを取得し配列で返す
+  # pathが空(ルート)でidentifierがdefault_branchの場合は、ファイルセットの参照時キャッシュを使用する
   def scm_entries(path=nil, identifier=nil)
     is_using_cache = using_root_fileset_cache?(path, identifier)
 
@@ -120,9 +128,9 @@ def scm_entries(path=nil, identifier=nil)
           path: fileset.path,
           kind: fileset.size.blank? ? 'dir': 'file',
           size: fileset.size,
-          author: latest_changeset.committer,
           lastrev: Redmine::Scm::Adapters::Revision.new(
             identifier: latest_changeset.identifier,
+            author: latest_changeset.committer,
             time: latest_changeset.committed_on
           ),
         )
@@ -132,7 +140,6 @@ def scm_entries(path=nil, identifier=nil)
     # Not found in cache, get entries from SCM
     if entries.blank?
       entries = scm.entries(path, identifier, :report_last_commit => report_last_commit)
-
       # Save as cache
       if changeset.present?
         GithubAdapterRootFileset.where(repository_id: self.id, revision: identifier).delete_all
@@ -152,6 +159,9 @@ def scm_entries(path=nil, identifier=nil)
     entries
   end
 
+  # pathにファイルパス、revにコミットのshaもしくはブランチ名を受け取る
+  # rev時点のpath以下のファイルに該当するchengesetsを取得し配列で返す
+  # revがデフォルトブランチ以外の場合、未反映のrevisionを保存しchangesetsテーブルに保存する
   def latest_changesets(path, rev, limit = 10)
     revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
 
@@ -165,6 +175,8 @@ def latest_changesets(path, rev, limit = 10)
     changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
   end
 
+  # このリポジトリが削除された場合のクリーンアップ処理
+  # 紐づくGithubAdapterRootFilesetを削除する
   def clear_changesets
     super
     GithubAdapterRootFileset.where(repository_id: self.id).delete_all
@@ -177,6 +189,8 @@ def default_branch
   def properties(path, rev)
   end
 
+  # scm_entries内でキャッシュを使用するかどうかの判定
+  # pathがルートで、identifierがデフォルトブランチ(またはデフォルトブランチのSHA1ハッシュ)の場合、true を返す
   def using_root_fileset_cache?(path, identifier)
     return false if path.present?
     return false if identifier.blank?
diff --git a/dot.github/workflows/ci.yml b/dot.github/workflows/ci.yml
new file mode 100644
index 0000000..c8c3d87
--- /dev/null
+++ b/dot.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
+# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
+
+name: Run plugin Test
+
+on: push
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    container:
+      image: redmine:4.2.10-bullseye
+    env:
+      RAILS_ENV: test
+    steps:
+    - name: Setup Env
+      run: |
+        ln -s /usr/src/redmine ./redmine
+        git config --global --add safe.directory /usr/src/redmine/plugins/redmine_github_adapter
+    - uses: actions/checkout@v3
+      with:
+        path: ./redmine/plugins/redmine_github_adapter
+    - name: Run tests
+      run: |
+        cd redmine
+        /docker-entrypoint.sh rails s --help
+        bundle config unset without --local
+        bundle install
+        bundle exec rake redmine:plugins:migrate
+        bundle exec rake redmine:plugins:test RAILS_ENV=test
+
diff --git a/lib/redmine/scm/adapters/github_adapter.rb b/lib/redmine/scm/adapters/github_adapter.rb
index 5841c03..69481e5 100644
--- a/lib/redmine/scm/adapters/github_adapter.rb
+++ b/lib/redmine/scm/adapters/github_adapter.rb
@@ -22,6 +22,8 @@ def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
           end
         end
 
+        # レシーバに紐づくGithubBranchオブジェクトを配列にし、ブランチ名でソートして返す
+        # 対象となるGithubBranchオブジェクトが存在しない場合、空の配列を返す
         def branches
           return @branches if @branches
           @branches = []
@@ -41,6 +43,11 @@ def branches
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # pathにファイル・ディレクトリのパス, identifierにコミットのSHAを受け取る
+        # identifierに該当するコミットの、path以下に含まれる各ファイルのエントリを配列にして返す
+        # pathが空の場合rootディレクトリを、identifierが空の場合HEADコミットを対象とする
+        # 対象となるファイルが存在しない場合、空の配列を返す
+        # report_last_commitオプションにtrueが与えられた場合、各エントリにファイルの最新コミット情報を含める
         def entries(path=nil, identifier=nil, options={})
           identifier = 'HEAD' if identifier.nil?
 
@@ -67,12 +74,17 @@ def entries(path=nil, identifier=nil, options={})
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # revにコミットのSHAもしくはブランチ名を受け取る
+        # 引数に該当するコミットをすべて取得し、その中で最新コミットのSHAを返す
         def revision_to_sha(rev)
           Octokit.commits(@repos, rev, { per_page: 1 }).map(&:sha).first
         rescue Octokit::Error => e
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # pathにファイル・ディレクトリのパス、revにコミットのSHAもしくはブランチ名を受け取る
+        # revに該当するコミット以前でpath以下に変更があった最新のコミットを取得し、Revisionオブジェクトとして返す
+        # 引数pathが与えられなかった場合、もしくは該当するコミットが存在しない場合nilを返す
         def lastrev(path, rev)
           return if path.nil?
 
@@ -92,16 +104,20 @@ def lastrev(path, rev)
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # pathにtreeオブジェクトのshaを受け取る
+        # treeの最上位に位置するファイル・ディレクトリ名を返す
         def get_path_name(path)
-
           Octokit.commits(@repos).map {|c|
             Octokit.tree(@repos, c.commit.tree.sha).tree.map{|b| [b.sha, b.path] }
           }.flatten.each_slice(2).to_h[path]
-
         rescue Octokit::Error => e
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # pathにファイル・ディレクトリのパス, identifier_from/identifier_toにコミットのshaを受け取る
+        # 引数で与えられた条件に合致するコミットをRevisionオブジェクトの配列として返す
+        # allオプションがtrueの場合、リポジトリの全てのrevisionを取得する
+        # 配列はコミット日時の降順でソートされる
         def revisions(path, identifier_from, identifier_to, options={})
           path ||= ''
           revs = Revisions.new
@@ -116,7 +132,6 @@ def revisions(path, identifier_from, identifier_to, options={})
             0.step do |i|
               start_page = i * MAX_PAGES + 1
               github_commits = Octokit.commits(@repos, api_opts.merge(page: start_page))
-
               # if fetched latest commit, github_commits.length is 1, and github_commits[0][:sha] == latest_committed_id
               return [] if i == 0 && github_commits.none?{ |commit| commit.sha != options[:last_committed_id] }
 
@@ -165,6 +180,8 @@ def revisions(path, identifier_from, identifier_to, options={})
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # 複数のrevisionオブジェクトを配列として受け取り、該当コミットの変更状況をhashにする
+        # 作成したhashを各revisionオブジェクトのpathsパラメータに追記する
         def get_filechanges_and_append_to(revisions)
           revisions.each do |revision|
             commit_diff = Octokit.commit(@repos, revision.identifier)
@@ -191,6 +208,9 @@ def get_filechanges_and_append_to(revisions)
           raise CommandFailed, handle_octokit_error(e)
         end
 
+        # pathにファイルのパス, identifier_from/identifier_toにコミットのSHAを受け取る
+        # pathで指定されたファイル内容について、identifier~で指定されたコミット間の差分箇所を特定する
+        # 特定した差分箇所に追記を行い、文字列の配列として返す
         def diff(path, identifier_from, identifier_to=nil)
           path ||= ''
           diff = []
@@ -245,6 +265,8 @@ def annotate(path, identifier=nil)
           nil
         end
 
+        # デフォルトブランチ名を文字列にして返す
+        # ブランチが1つも無い場合nilを返す
         def default_branch
           return if branches.blank?
 
@@ -255,13 +277,17 @@ def default_branch
           ).to_s
         end
 
+        # pathにファイル・ディレクトリのパス, identifierにコミットのSHAを受け取る
+        # identifierに該当するコミットの、pathに指定されたファイル・ディレクトリのエントリを返す
+        # identifierが空の場合、HEADコミットを対象にする
+        # pathが空の場合、トップレベルのエントリを返す
         def entry(path=nil, identifier=nil)
           identifier ||= 'HEAD'
           if path.blank?
             # Root entry
             Entry.new(:path => '', :kind => 'dir')
           else
-            es = entries(path, identifier, {report_last_commit: true })
+            es = entries(path, identifier, {report_last_commit: false })
             content = es&.find {|e| e.name == path} || es&.first
 
             Entry.new({
@@ -273,12 +299,15 @@ def entry(path=nil, identifier=nil)
           end
         end
 
+        # identifierにSHAが該当するコミットの、pathに指定されたファイルの内容を文字列で返す
+        # identifierが空の場合、HEADコミットを対象にする
         def cat(path, identifier=nil)
           identifier = 'HEAD' if identifier.nil?
 
           begin
             blob = Octokit.contents(@repos, path: path, ref: identifier)
             url = blob.download_url
+
           rescue Octokit::NotFound
             commit = Octokit.commit(@repos, identifier).files.select{|c| c.filename == path }.first
             blob = Octokit.blob(@repos, commit.sha)
@@ -287,7 +316,6 @@ def cat(path, identifier=nil)
           Octokit.get(url)
           content_type = Octokit.last_response.headers['content-type'].slice(/charset=.+$/)&.gsub("charset=", "")
           return '' if content_type == "binary" || content_type.nil?
-
           content = blob.encoding == "base64" ? Base64.decode64(blob.content) : blob.content
           content.force_encoding 'utf-8'
 
diff --git a/test/fixtures/changesets.yml b/test/fixtures/changesets.yml
new file mode 100644
index 0000000..fdb95c4
--- /dev/null
+++ b/test/fixtures/changesets.yml
@@ -0,0 +1,10 @@
+default:
+  id: 1
+  repository_id: 1
+  revision: 'shashasha'
+  committer: 'author_name'
+  committed_on: 2023-01-01
+  comments: 'message'
+  commit_date: 2023-01-01
+  scmid: 'shashasha'
+  user_id: 
\ No newline at end of file
diff --git a/test/fixtures/repositories.yml b/test/fixtures/repositories.yml
new file mode 100644
index 0000000..134c53e
--- /dev/null
+++ b/test/fixtures/repositories.yml
@@ -0,0 +1,14 @@
+default:
+  id: 1
+  project_id: 1
+  url: 'https://github.com/farend/redmine_github_repo.git'
+  login: ''
+  password: ''
+  identifier: 'test_project'
+  type: 'Repository::Github'
+  path_encoding: nil
+  log_encoding: nil
+  is_default: true
+  extra_info:
+    'last_committed_date': ''
+    'last_committed_id': ''
diff --git a/test/test_helper.rb b/test/test_helper.rb
index ea19ced..9b8f301 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,3 +1,20 @@
 # Load the Redmine helper
 require 'minitest/mock'
 require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
+
+module Redmine
+  module PluginFixturesLoader
+    def self.included(base)
+      base.class_eval do
+        def self.plugin_fixtures(*symbols)
+          ActiveRecord::FixtureSet.create_fixtures(File.dirname(__FILE__) + '/fixtures/', symbols)
+        end
+      end
+    end
+  end
+end
+
+## ユニットテスト
+unless ActiveSupport::TestCase.included_modules.include?(Redmine::PluginFixturesLoader)
+  ActiveSupport::TestCase.send :include, Redmine::PluginFixturesLoader
+end
\ No newline at end of file
diff --git a/test/unit/github_adapter_test.rb b/test/unit/github_adapter_test.rb
index cdb1991..4a83268 100644
--- a/test/unit/github_adapter_test.rb
+++ b/test/unit/github_adapter_test.rb
@@ -8,7 +8,6 @@ def setup
 
   def test_branches_Githubの戻り値が空の場合
     Octokit.stub(:branches, build_mock([]) {|repo, options|
-      # 引数のアサーションをしておく
       assert_equal @repo, repo
       assert_equal 1, options[:page]
       assert_equal 100, options[:per_page]
@@ -20,10 +19,14 @@ def test_branches_Githubの戻り値が空の場合
   end
 
   def test_branches_Githubの戻り値が1つある場合
-    branch = OctokitBranch.new(name: 'main', commit: OctkoitCommit.new(sha: 'shashasha'))
+    branch = OctokitBranch.new(name: 'main', commit: OctokitCommit.new(sha: 'shashasha'))
 
-    Octokit.stub(:branches, build_mock([branch], []) { |repos, options|
-      assert options[:page]
+    pages = [1, 2]
+
+    Octokit.stub(:branches, build_mock([branch], []) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal pages.shift, options[:page]
+      assert_equal 100, options[:per_page]
     }) do
       branches = @scm.branches
 
@@ -34,10 +37,574 @@ def test_branches_Githubの戻り値が1つある場合
     end
   end
 
+  def test_branches_Githubの戻り値が複数ある場合
+    branches = ['main', 'dev', 'feat'].sort.map.with_index{ |name, i|
+      OctokitBranch.new(name: name, commit: OctokitCommit.new(sha: "shashasha#{i}"))
+    }
+
+    pages = [1, 2]
+
+    Octokit.stub(:branches, build_mock(branches, []) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal pages.shift, options[:page]
+      assert_equal 100, options[:per_page]
+    }) do
+      results = @scm.branches
+
+      assert_equal branches.length, results.length
+      results.each.with_index do |branch,i|
+        assert_equal branches[i].name, branch.to_s
+        assert_equal "shashasha#{i}", branch.revision
+        assert_equal "shashasha#{i}", branch.scmid
+      end
+    end
+  end
+
+  def test_branches_Githubに未ソートのブランチが与えられた場合
+    branches = ['bbb', 'ccc', 'aaa'].map{ |name|
+      OctokitBranch.new(name: name, commit: OctokitCommit.new(sha: "sha#{name}"))
+    }
+
+    sorted_branches =  ['aaa', 'bbb', 'ccc']
+    pages = [1, 2]
+
+    Octokit.stub(:branches, build_mock(branches, []) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal pages.shift, options[:page]
+      assert_equal 100, options[:per_page]
+    }) do
+      results = @scm.branches
+
+      assert_equal branches.length, results.length
+      results.each.with_index do |branch, i|
+        assert_equal sorted_branches[i], branch.to_s
+        assert_equal "sha#{sorted_branches[i]}", branch.revision
+        assert_equal "sha#{sorted_branches[i]}", branch.scmid
+      end
+    end
+  end
+
+  def test_entries_Githubの戻り値が空の場合
+    Octokit.stub(:contents, build_mock([]) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal nil, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entries = @scm.entries
+      assert_equal 0, entries.length
+    end
+  end
+
+  def test_entries_Githubの戻り値が1つある場合
+    content = OctokitContent.new(name: 'test.md', path: 'farend/redmine_github_repo', type: 'file', size: 256)
+
+    Octokit.stub(:contents, build_mock([content]) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal nil, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entries = @scm.entries
+      assert_equal 1, entries.length
+      assert_equal 'test.md', entries[0].name
+      assert_equal 'farend/redmine_github_repo', entries[0].path
+      assert_equal 'file', entries[0].kind
+      assert_equal 256, entries[0].size
+    end
+  end
+
+  def test_entries_Githubの戻り値が複数ある場合
+    contents = ['test.md', 'test.txt'].sort.map{ |name|
+      OctokitContent.new(name: name, path: 'farend/redmine_github_repo', type: 'file', size: 256)
+    }
+
+    Octokit.stub(:contents, build_mock(contents) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal nil, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entries = @scm.entries
+      assert_equal 2, entries.length
+      assert_equal 'test.md', entries[0].name
+      assert_equal 'farend/redmine_github_repo', entries[0].path
+      assert_equal 'test.txt', entries[1].name
+      assert_equal 'farend/redmine_github_repo', entries[1].path
+    end
+  end
+
+  def test_entries_Githubに未ソートのコンテンツが返却される場合
+    contents = ['bbb.md', 'ccc.md', 'aaa.md'].map{ |name|
+      OctokitContent.new(name: name, path: 'farend/redmine_github_repo', type: 'file', size: 256)
+    }
+
+    Octokit.stub(:contents, build_mock(contents) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal nil, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entries = @scm.entries
+      assert_equal 3, entries.size
+      assert_equal 'aaa.md', entries[0].name
+      assert_equal 'bbb.md', entries[1].name
+      assert_equal 'ccc.md', entries[2].name
+    end
+  end
+
+  def test_entries_Githubのreport_last_commitオプションにtrueが与えられる場合
+    content = OctokitContent.new(name: 'test.md', path: 'farend/redmine_github_repo', type: 'file', size: 256)
+    lastrev = OctokitRevision.new(identifier: 'shashasha')
+
+    Octokit.stub(:contents, build_mock(content) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal nil, options[:path]
+      assert_equal 'shashasha', options[:ref]
+    }) do
+      @scm.stub(:lastrev, build_mock(lastrev) { |repo, rev|
+        assert_equal @repo, repo
+        assert_equal 'shashasha', rev
+      }) do
+        entries = @scm.entries(nil, 'shashasha', {report_last_commit: true})
+        assert_equal 'test.md', entries[0].name
+        assert_equal 'shashasha', entries[0].lastrev.identifier
+      end
+    end
+  end
+
+  def test_revision_to_sha_GithubにコミットのSHAを渡される場合
+    commit = OctokitCommit.new(sha: 'shashasha')
+
+    Octokit.stub(:commits, build_mock([commit]) { |repo, rev, options|
+      assert_equal @repo, repo
+      assert_equal 'shashasha', rev
+      assert_equal 1, options[:per_page]
+    }) do
+      assert_equal 'shashasha', @scm.revision_to_sha('shashasha')
+    end
+  end
+
+  def test_revision_to_sha_Githubにブランチ名を渡される場合
+    branch = OctokitBranch.new(name: 'main', sha: 'shashasha')
+
+    Octokit.stub(:commits, build_mock([branch]) { |repo, rev, options|
+      assert_equal @repo, repo
+      assert_equal 'main', rev
+      assert_equal 1, options[:per_page]
+    }) do
+      assert_equal 'shashasha', @scm.revision_to_sha('main')
+    end
+  end
+
+  def test_lastrev_GithubのAPIの戻り値が返ってくることを確認
+    commit = OctokitCommit.new(
+      sha: 'shashasha',
+      commit: OctokitRevision.new(
+        identifier: 'shashasha',
+        author: OctokitAuthor.new(name: 'AuthorName'),
+        committer: OctokitCommiter.new(date: '2023-01-01 00:00:00')
+      )
+    )
+
+    Octokit.stub(:commits, build_mock([commit]) { |repo, rev, options|
+      assert_equal @repo, repo
+      assert_equal 'shashasha', rev
+      assert_equal @repo, options[:path]
+      assert_equal 1, options[:per_page]
+    }) do
+      revision = @scm.lastrev('farend/redmine_github_repo', 'shashasha')
+
+      assert_equal 'shashasha', revision.identifier
+      assert_equal 'AuthorName', revision.author
+      assert_equal '2023-01-01 00:00:00', revision.time
+    end
+  end
+
+  def test_lastrev_Githubのpath引数が省略された場合
+    Octokit.stub(:commits, build_mock([]) { |repo, rev, options|
+      assert_equal nil, repo
+      assert_equal 'shashasha', rev
+      assert_equal @repo, options[:path]
+      assert_equal 1, options[:per_page]
+    }) do
+      assert_nil @scm.lastrev(nil, 'shashasha')
+    end
+  end
+
+  def test_lastrev_Githubの引数に該当するコミットが存在しない場合
+    Octokit.stub(:commits, build_mock([]) { |repo, rev, options|
+      assert_equal @repo, repo
+      assert_equal 'shashasha', rev
+      assert_equal @repo, options[:path]
+      assert_equal 1, options[:per_page]
+    }) do
+      assert_nil @scm.lastrev(@repo, 'shashasha')
+    end
+  end
+
+  def test_get_path_name_GithubのAPIで取得したトップディレクトリが取得できること
+    tree = OctokitTree.new(
+      tree:[OctokitContent.new(sha: 'shashasha', path: 'farend/redmine_github_repo')],
+      sha: 'shashasha'
+    )
+    commit = OctokitCommit.new(sha: 'shashasha', commit: OctokitRevision.new(tree: tree))
+
+    Octokit.stub(:commits, build_mock([commit]) { |repo|
+      assert_equal @repo, repo
+    }) do
+      Octokit.stub(:tree, build_mock(tree) { |repo, sha|
+        assert_equal @repo, repo
+        assert_equal 'shashasha', sha
+      }) do
+        assert_equal @repo, @scm.get_path_name('shashasha')
+      end
+    end
+  end
+
+  def test_revisions_Githubの戻り値が1つある場合
+    commit = OctokitCommit.new(
+      sha: 'shashato',
+      commit: OctokitRevision.new(
+        identifier: 'shashato',
+        author: OctokitAuthor.new(name: 'AuthorName'),
+        committer: OctokitCommiter.new(date: '2023-01-01 00:00:00'),
+        message: 'commit message'
+      ),
+      parents: [OctokitCommit.new(sha: 'shashafrom')]
+    )
+
+    Octokit.stub(:commits, build_mock([commit], []) { |repo|
+      assert_equal @repo, repo
+    }) do
+      revisions = @scm.revisions(@repo, 'shashato', 'shashafrom', { per_page: 1 } )
+
+      assert_equal 1, revisions.size
+      assert_equal 'shashato', revisions[0].identifier
+      assert_equal 'commit message', revisions[0].message
+      assert_equal '2023-01-01 00:00:00', revisions[0].time
+      assert_equal 'shashafrom', revisions[0].parents[0]
+    end
+  end
+
+  def test_revisions_GithubのAPIレスポンスに複数のコミットがある場合
+    parents = [ OctokitCommit.new(sha: "shashasha0") ]
+    commits = 3.times.map { |i|
+      author = OctokitAuthor.new(name: "Author#{i}")
+      committer = OctokitCommiter.new(date: "2023-01-00 0#{i}:00:00")
+      rev = OctokitRevision.new(identifier: "shashasha#{i+1}", author: author, committer: committer, message: 'commit message')
+      commit = OctokitCommit.new(sha: "shashasha#{i+1}", commit: rev, parents: [ parents[i] ])
+      parents << commit
+      commit
+    }
+
+    Octokit.stub(:commits, build_mock(commits) { |repo|
+      assert_equal @repo, repo
+    }) do
+      revisions = @scm.revisions(@repo, 'shashato', 'shashafrom', { per_page: 1 })
+
+      assert_equal 3, revisions.size
+
+      revisions.each_with_index {|rev, i|
+        assert_equal "shashasha#{i+1}", rev.identifier
+        assert_equal "2023-01-00 0#{i}:00:00", rev.time
+        assert_equal 1, rev.parents.length
+        assert_equal "shashasha#{i}", rev.parents.first
+      }
+    end
+  end
+
+  def test_revisions_Githubのallオプションにtrueが与えられる場合
+    parents = [ OctokitCommit.new(sha: "shashasha0") ]
+    commits = 3.times.map { |i|
+      author = OctokitAuthor.new(name: "Author#{i}")
+      committer = OctokitCommiter.new(date: "2023-01-00 0#{i}:00:00")
+      rev = OctokitRevision.new(identifier: "shashasha#{i+1}", author: author,
+                                committer: committer, message: 'commit message')
+      commit = OctokitCommit.new(sha: "shashasha#{i+1}", commit: rev, parents: [parents[i]])
+      parents << commit
+      commit
+    }
+    Octokit.stub(:commits, build_mock(commits, commits, []) { |repo, api_opts|
+      assert_equal @repo, repo
+      assert_equal true, api_opts[:all]
+    }) do
+      revisions = @scm.revisions(@repo, nil, nil, { per_page: 1, all: true})
+
+      assert_equal 3, revisions.size
+      assert_equal 1, revisions.last.parents.size
+      assert_equal "shashasha2", revisions.last.parents.last
+      assert_equal 'Author2', revisions.last.author
+    end
+  end
+
+  def test_get_filechanges_and_append_to_Githubに1つのrevisisonが渡される場合
+    commit = OctokitCommit.new(
+      sha: "shashasha", files: [
+        TestFile.new(status: "added", filename: "add.md"),
+        TestFile.new(status: "modified", filename: "mod.md"),
+      ]
+    )
+
+    Octokit.stub(:commit, build_mock(commit) { |repo, sha|
+      assert_equal @repo, repo
+      assert_equal "shashasha", sha
+    }) do
+      rev = OctokitRevision.new(identifier: "shashasha", paths: nil)
+
+      @scm.get_filechanges_and_append_to([rev])
+
+      assert_equal 'A', rev.paths[0][:action]
+      assert_equal 'add.md', rev.paths[0][:path]
+      assert_equal 'M', rev.paths[1][:action]
+      assert_equal 'mod.md', rev.paths[1][:path]
+    end
+  end
+
+  def test_diff_GithubAPIで追加差分のあるファイルパスとコミットSHAが返される場合
+    filename = "farend/redmine_github_repo/README.md"
+    commit = OctokitCommit.new(sha: 'shashasha', files: [TestFile.new(status: 'added', filename: filename)])
+
+    Octokit.stub(:commit, build_mock(commit) { |repo, identifier_from, options|
+      assert_equal @repo, repo
+      assert_equal 'shashasha', identifier_from
+      assert_equal filename, options[:path]
+    }) do
+      @scm.stub(:cat, build_mock("added_content") { |path, identifier|
+        assert_equal filename, path
+        assert_equal 'shashasha', identifier
+      }) do
+        diffs = @scm.diff(filename, "shashasha")
+        assert_equal [
+          "diff",
+          "--- /dev/null",
+          "+++ b/#{filename}",
+          "@@ -0,0 +1,2 @@",
+          "+added_content"
+        ], diffs
+      end
+    end
+  end
+
+  def test_diff_GithubAPIでファイル名変更差分のあるファイルパスとコミットshaが返される場合
+    file_from = TestFile.new(status: 'added', filename: "farend/redmine_github_repo/README.md")
+    file_to = TestFile.new(status: 'renamed', filename: "farend/redmine_github_repo/RENAME.md", previous_filename: "farend/redmine_github_repo/README.md")
+    compare = OctokitCompare.new(
+      base_commit: OctokitCommit.new(sha: 'shashafrom'),
+      commits: [OctokitCommit.new(sha: 'shashato')],
+      files: [file_to, file_from]
+    )
+
+    Octokit.stub(:compare, build_mock(compare) { |repo, identifier_to, identifier_from, options|
+      assert_equal @repo, repo
+      assert_equal 'shashato', identifier_to
+      assert_equal 'shashafrom', identifier_from
+      assert_equal 'farend/redmine_github_repo', options[:path]
+    }) do
+      @scm.stub(:cat, build_mock("added_content") { |path, identifier|
+        assert_equal 'farend/redmine_github_repo/README.md', path
+        assert_equal 'shashafrom', identifier
+      }) do
+        diffs = @scm.diff('farend/redmine_github_repo', 'shashafrom', 'shashato')
+        assert_equal [
+          "diff",
+          "--- a/#{file_from.filename}",
+          "+++ b/#{file_to.filename}",
+          "diff",
+          "--- /dev/null",
+          "+++ b/#{file_from.filename}",
+          "@@ -0,0 +1,2 @@",
+          "+added_content"
+        ], diffs
+      end
+    end
+  end
+
+  def test_diff_GithubAPIで変更差分のあるファイルパスとコミットshaが返される場合
+    file_from = TestFile.new(status: 'added', filename: "farend/redmine_github_repo/README.md")
+    file_to = TestFile.new(status: 'modifies', filename: "farend/redmine_github_repo/README.md", patch:'+mod_line')
+    compare = OctokitCompare.new(
+      base_commit: OctokitCommit.new(sha: 'shashafrom'),
+      commits: [OctokitCommit.new(sha: 'shashato')],
+      files: [file_to, file_from]
+    )
+
+    Octokit.stub(:compare, build_mock(compare) { |repo, identifier_to, identifier_from, options|
+      assert_equal @repo, repo
+      assert_equal 'shashato', identifier_to
+      assert_equal 'shashafrom', identifier_from
+      assert_equal 'farend/redmine_github_repo/README.md', options[:path]
+    }) do
+      @scm.stub(:cat, build_mock("added_content") { |path, identifier|
+        assert_equal 'farend/redmine_github_repo/README.md', path
+        assert_equal 'shashafrom', identifier
+      }) do
+          diffs = @scm.diff('farend/redmine_github_repo/README.md', "shashafrom", "shashato")
+          assert_equal [
+            "diff",
+            "--- a/#{file_from.filename}",
+            "+++ b/#{file_to.filename}",
+            "#{file_to.patch}",
+            "diff",
+            "--- /dev/null",
+            "+++ b/#{file_from.filename}",
+            "@@ -0,0 +1,2 @@",
+            "+added_content"
+          ], diffs
+      end
+    end
+  end
+
+  def test_diff_GithubAPIに削除差分のあるファイルパスとコミットshaが渡される場合
+    file_from = TestFile.new(status: 'added', filename: "farend/redmine_github_repo/README.md")
+    file_to = TestFile.new(status: 'removed', filename: "farend/redmine_github_repo/README.md", patch:'-add_line')
+    compare = OctokitCompare.new(
+      base_commit: OctokitCommit.new(sha: 'shashafrom'),
+      commits: [OctokitCommit.new(sha: 'shashato')],
+      files: [file_from, file_to]
+    )
+
+    Octokit.stub(:compare, build_mock(compare) { |repo, identifier_to, identifier_from, options|
+      assert_equal @repo, repo
+      assert_equal 'shashato', identifier_to
+      assert_equal 'shashafrom', identifier_from
+      assert_equal 'farend/redmine_github_repo/README.md', options[:path]
+    }) do
+      @scm.stub(:cat, build_mock('add_line', []) { |path, identifier|
+        assert_equal 'farend/redmine_github_repo/README.md', path
+      }) do
+        diffs = @scm.diff('farend/redmine_github_repo/README.md', "shashafrom", "shashato")
+        assert_equal [
+          "diff",
+          "--- /dev/null",
+          "+++ b/#{file_from.filename}",
+          "@@ -0,0 +1,2 @@",
+          "+add_line",
+          "diff",
+          "--- a/#{file_from.filename}",
+          "+++ /dev/null",
+          "@@ -1,2 +0,0 @@",
+          "-[]"
+        ], diffs
+      end
+    end
+  end
+
+  def test_default_branch_GithubAPIの戻り値が存在する場合
+    branch = OctokitBranch.new(name: 'main', commit: OctokitCommit.new(sha: 'shashasha'))
+    Octokit.stub(:branches, build_mock([branch], []) { |repo|
+      assert_equal @repo, repo
+    }) do
+      assert_equal 'main', @scm.default_branch
+    end
+  end
+
+  def test_default_branch_GithubAPIの戻り値が存在しない場合
+    Octokit.stub(:branches, build_mock([]) { |repo|
+      assert_equal @repo, repo
+    }) do
+      assert_nil @scm.default_branch
+    end
+  end
+
+  def test_entry_対象ファイルのpathを引数として渡す場合
+    content = OctokitContent.new(name: 'README.md', path: @repo, type: 'file', size: 256)
+
+    Octokit.stub(:contents, build_mock([content]) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal 'README.md', options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entry = @scm.entry('README.md')
+      assert_equal 'README.md', entry.name
+      assert_equal 'file', entry.kind
+      assert_equal @repo, entry.path
+      assert_equal 256, entry.size
+    end
+  end
+
+  def test_entry_path引数を渡さない場合
+    Octokit.stub(:contents, build_mock([]) { |repo, options|
+      assert_equal @repo, repo
+      assert_equal @repo, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      entry = @scm.entry
+      assert_equal 'dir', entry.kind
+      assert_equal '', entry.path
+    end
+  end
+
+  def test_cat_GithubAPIでテキストファイルを取得する場合
+    blob = OctokitContent.new(path: @repo, sha: 'shashasha', download_url: 'http://download/url',
+                              encoding: 'utf-8', content: 'test_content')
+    last_response = OctokitGet.new(headers:{ "content-type" => 'text/html; charset=utf-8'})
+    Octokit.stub(:contents, build_mock(blob) { |path, options|
+      assert_equal @repo, path
+      assert_equal @repo, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      Octokit.stub(:get, build_mock(nil) { |url|
+        assert_equal 'http://download/url', url
+      }) do
+        Octokit.stub(:last_response, build_mock(last_response)) do
+          body = @scm.cat(@repo)
+          assert_equal 'test_content', body
+        end
+      end
+    end
+  end
+
+  def test_cat_GithubAPIでbase64エンコードされたテキストファイルを取得する場合
+    blob = OctokitContent.new(path: @repo, sha: 'shashasha', download_url: 'http://download/url',
+                              encoding: 'base64', content: 'dGVzdF9jb250ZW50')
+    last_response = OctokitGet.new(headers:{ "content-type" => 'text/html; charset=base64'})
+    Octokit.stub(:contents, build_mock(blob, []) { |path, options|
+      assert_equal @repo, path
+      assert_equal @repo, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      Octokit.stub(:get, build_mock(nil) { |url|
+        assert_equal 'http://download/url', url
+      }) do
+        Octokit.stub(:last_response, build_mock(last_response)) do
+          body = @scm.cat(@repo)
+          assert_equal 'test_content', body
+        end
+      end
+    end
+  end
+
+  def test_cat_GithubAPIでバイナリファイルを取得する場合
+    blob = OctokitContent.new(path: @repo, sha: 'shashasha', download_url: 'http://download/url')
+    last_response = OctokitGet.new(headers:{ "content-type" => 'binary'})
+    Octokit.stub(:contents, build_mock(blob, []) { |path, options|
+      assert_equal @repo, path
+      assert_equal @repo, options[:path]
+      assert_equal 'HEAD', options[:ref]
+    }) do
+      Octokit.stub(:get, build_mock(nil) { |url|
+        assert_equal 'http://download/url', url
+      }) do
+        Octokit.stub(:last_response, build_mock(last_response)) do
+          body = @scm.cat(@repo)
+          assert_equal '', body
+        end
+      end
+    end
+  end
+
   ## 以下、Octokitのモックに使う部品たち ##
 
-  OctokitBranch = Struct.new(:name, :commit, keyword_init: true)
-  OctkoitCommit = Struct.new(:sha, keyword_init: true)
+  OctokitBranch = Struct.new(:name, :commit, :sha, keyword_init: true)
+  OctokitCommit = Struct.new(:sha, :commit, :parents, :files, keyword_init: true)
+  OctokitContent = Struct.new(:sha, :name, :path, :type, :size, :download_url,
+                              :content, :encoding, keyword_init: true)
+  OctokitRevision = Struct.new(:identifier, :author, :committer, :tree,
+                               :message, :paths, keyword_init: true)
+  OctokitAuthor = Struct.new(:name, keyword_init: true)
+  OctokitCommiter = Struct.new(:date, keyword_init: true)
+  OctokitTree = Struct.new(:tree, :sha, keyword_init: true)
+  OctokitCompare = Struct.new(:base_commit, :commits, :files, keyword_init: true)
+  OctokitGet = Struct.new(:headers, keyword_init: true)
+  TestFile = Struct.new(:status, :filename, :previous_filename,
+                        :from_revision, :patch, keyword_init: true)
 
   def build_mock(*returns, &proc)
     mock = Minitest::Mock.new
diff --git a/test/unit/github_test.rb b/test/unit/github_test.rb
new file mode 100644
index 0000000..e17b494
--- /dev/null
+++ b/test/unit/github_test.rb
@@ -0,0 +1,471 @@
+require File.expand_path('../../test_helper', __FILE__)
+
+class GithubTest < ActiveSupport::TestCase
+  plugin_fixtures :repositories, :changesets
+
+  def setup
+    @repository = Repository::Github.find(1)
+    @default_changeset = Changeset.find(1)
+    @scm = @repository.scm
+    @repo = "farend/redmine_github_repo"
+    @author = OctokitAuthor.new(name: 'author_name')
+  end
+
+  def test_fetch_changesets_Githubでchangesetsが追加される場合
+    file = TestFile.new(status: "added", filename: "README.md")
+    rev = OctokitRevision.new(identifier: 'addedsha', scmid: 'addedsha', author: @author,
+                              parents: ['shashasha'], paths: nil, time: Time.gm(2023, 2, 1), message: 'added')
+
+    commit = OctokitCommit.new(sha: 'addedsha', files: [file], parents: OctokitCommit.new(sha: 'shashasha'))
+
+    @scm.stub(:revisions, build_mock([rev]) { |path, identifier_from, identifier_to|
+      assert_equal '', path
+      assert_equal nil, identifier_from
+      assert_equal nil, identifier_to
+    }) do
+      Octokit.stub(:commit, build_mock(commit) { |repo, identifier|
+        assert_equal @repo, repo
+        assert_equal 'addedsha', identifier
+      }) do
+        @repository.fetch_changesets
+        assert_equal 2, @repository.changesets.size
+        assert_equal @repository.id, @repository.changesets.first[:repository_id]
+
+        extra_info = @repository[:extra_info]
+
+        assert_equal Time.gm(2023, 2, 1), extra_info['last_committed_date']
+        assert_equal 'addedsha', extra_info['last_committed_id']
+      end
+    end
+  end
+
+  def test_fetch_changesets_Githubでchangesetsに追加が無い場合
+    rev = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                              paths: nil, time: Time.gm(2023, 1, 1), message: 'message')
+
+    @scm.stub(:revisions, build_mock([rev]) { |path, identifier_from, identifier_to|
+      assert_equal '', path
+      assert_equal nil, identifier_from
+      assert_equal nil, identifier_to
+    }) do
+        @repository.fetch_changesets
+        assert_equal 1, @repository.changesets.size
+        assert_equal @repository.id, @repository.changesets.first[:repository_id]
+
+        extra_info = @repository[:extra_info]
+
+        assert_equal Time.gm(2023, 1, 1), extra_info['last_committed_date']
+        assert_equal 'shashasha', extra_info['last_committed_id']
+    end
+  end
+
+  def test_save_revisions_Githubでchangesetsが追加される場合
+    rev = OctokitRevision.new(identifier: 'addedsha', scmid: 'addedsha', author: @author,
+                              parents: ['shashasha'], paths: nil, time: Time.gm(2023, 2, 1), message: 'added')
+    file = TestFile.new(status: "added", filename: "README.md")
+    commit = OctokitCommit.new(sha: 'addedsha', files: [file], parents: OctokitCommit.new(sha: 'shashasha'))
+
+    Octokit.stub(:commit, build_mock(commit) { |repo, identifier|
+      assert_equal @repo, repo
+      assert_equal 'addedsha', identifier
+    }) do
+      @repository.send(:save_revisions!, [rev], [rev])
+
+      assert_equal 2, @repository.changesets.size
+      assert_equal @repository.id, @repository.changesets.first[:repository_id]
+      assert_equal rev.identifier, @repository.changesets.first[:revision]
+      assert_equal rev.scmid, @repository.changesets.first[:scmid]
+      assert_equal rev.author.name, @repository.changesets.first[:committer]
+      assert_equal rev.time, @repository.changesets.first[:committed_on]
+      assert_equal rev.message, @repository.changesets.first[:comments]
+
+      assert_equal file.filename, @repository.changesets.first.filechanges.first.path
+      assert_equal "A", @repository.changesets.first.filechanges.first.action
+      assert_nil   @repository.changesets.first.filechanges.first.from_path
+
+      assert_equal 1, @repository.changesets.first.parents.length
+      assert_equal @default_changeset, @repository.changesets.first.parents.first
+    end
+  end
+
+  def test_save_revisions_Githubでchangesetsが追加されない場合
+    rev = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                              paths: nil, time: Time.gm(2023, 1, 1), message: 'message')
+
+    @repository.send(:save_revisions!, [rev], [rev])
+
+    assert_equal 1, @repository.changesets.size
+    assert_equal @default_changeset.id, @repository.changesets.first.id
+  end
+
+  def test_find_changeset_by_name_Githubで引数にリビジョン名が一致するchangesetが存在する場合
+    found_changeset = @repository.find_changeset_by_name('shashasha')
+    assert_equal @default_changeset, found_changeset
+  end
+
+  def test_find_changeset_by_name_Githubで引数にscmidが部分一致するchangesetが存在する場合
+    found_changeset = @repository.find_changeset_by_name('sha')
+    assert_equal @default_changeset, found_changeset
+  end
+
+  def test_find_changeset_by_name_Githubで引数に該当するchangesetが存在しない場合
+    found_changeset = @repository.find_changeset_by_name('ahsahsahs')
+    assert_nil found_changeset
+  end
+
+  def test_scm_entries_Githubでルートファイルのキャッシュが存在しないし使われない場合
+    rev = OctokitRevision.new(identifier: 'mock-value', scmid: 'mock-value', author: @author,
+                              time: Time.gm(2023, 1, 1), message: 'message')
+    entry = RepositoryEntry.new(path: "README.md", size: 256, lastrev: rev.identifier)
+
+    @scm.stub(:entries, build_mock([entry]) { |repository_id, revision|
+      assert_equal  'README.md', repository_id
+      assert_equal 'shashasha', revision
+    }) do
+      entries = @repository.scm_entries('README.md', 'shashasha')
+
+      assert_equal 1, entries.size
+      assert_equal 'README.md', entries[0].path
+      assert_equal 256, entries[0].size
+      assert_equal 'mock-value', entries[0].lastrev
+
+      assert_equal 0, GithubAdapterRootFileset.all.size
+    end
+  end
+
+  def test_scm_entries_Githubでルートファイルのキャッシュは存在するが使用しない場合
+    rev = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                              time: Time.gm(2023, 1, 1), message: 'message')
+    entry = RepositoryEntry.new(path: "README.md", size: 256, lastrev: rev.identifier)
+
+    GithubAdapterRootFileset.create!(
+      repository_id: @repository.id,
+      revision: rev.identifier,
+      changeset_id: @default_changeset.id,
+      path: entry.path,
+      size: entry.size,
+      latest_commitid: rev.identifier
+    )
+
+    @scm.stub(:entries, build_mock([entry]) { |repository_id, revision|
+      assert_equal  'README.md', repository_id
+      assert_equal 'shashasha', revision
+    }) do
+      entries = @repository.scm_entries('README.md', 'shashasha')
+
+      assert_equal 1, entries.size
+      assert_equal 'README.md', entries[0].path
+      assert_equal 256, entries[0].size
+      assert_equal 'shashasha', entries[0].lastrev
+
+      assert_equal 1, GithubAdapterRootFileset.all.size
+    end
+  end
+
+  def test_scm_entries_Githubでルートファイルのキャッシュが存在し使用する場合
+    main_changeset = Changeset.create!(
+      repository_id: @repository.id,
+      revision: "sha1-abc",
+      committer: 'author_name',
+      committed_on: '2023-01-01',
+      comments: 'message',
+      commit_date: '2023-01-01',
+      scmid: 'sha1-abc'
+    )
+    GithubAdapterRootFileset.create!(
+      repository_id: @repository.id,
+      revision: "sha1-abc",
+      changeset_id: main_changeset.id,
+      path: "README.md",
+      size: 256,
+      latest_commitid: "shashasha"
+    )
+
+    @scm.stub(:default_branch, build_mock('main')) do
+      @scm.stub(:revision_to_sha, build_mock('sha1-abc') { |identifier|
+        assert_equal 'main', identifier
+      }) do
+        entries = @repository.scm_entries('', 'main')
+
+        assert_equal 1, entries.size
+        assert_equal 'README.md', entries[0].name
+        assert_equal 'README.md', entries[0].path
+        assert_equal 'file', entries[0].kind
+        assert_equal 256, entries[0].size
+        assert_equal 'author_name', entries[0].author
+        assert_equal 'shashasha', entries[0].lastrev.identifier
+        assert_equal Time.parse("2023-01-01 00:00:00"), entries[0].lastrev.time
+
+        assert_equal 1, GithubAdapterRootFileset.all.size
+      end
+    end
+  end
+
+  def test_scm_entries_Githubでルートファイルのキャッシュは存在しないが使用する場合
+    main_changeset = Changeset.create!(
+      repository_id: @repository.id,
+      revision: "sha1-abc",
+      committer: 'author_name',
+      committed_on: '2023-01-01',
+      comments: 'message',
+      commit_date: '2023-01-01',
+      scmid: 'sha1-abc'
+    )
+    # scm.entries の戻り値
+    entry = RepositoryEntry.new(path: "README.md", size: 256, lastrev: OctokitRevision.new(
+      identifier: 'shashasha', scmid: 'shashasha', author: @author, time: Time.gm(2023, 1, 1), message: 'message'
+      )
+    )
+
+    @scm.stub(:default_branch, build_mock('main')) do
+      @scm.stub(:revision_to_sha, build_mock('sha1-abc') { |identifier|
+        assert_equal 'main', identifier
+      }) do
+        @scm.stub(:entries, build_mock([entry]) { |path, identifier|
+          assert_equal '', path
+          assert_equal 'main', identifier
+        }) do
+          entries = @repository.scm_entries('', 'main')
+
+          assert_equal 1, entries.size
+          assert_equal 'README.md', entries[0].path
+          assert_equal 256, entries[0].size
+          assert_equal 'shashasha', entries[0].lastrev.identifier
+          assert_equal Time.gm(2023, 1, 1), entries[0].lastrev.time
+
+          # 作成された RootFileset の確認
+          assert_equal 1, GithubAdapterRootFileset.all.size
+          assert_equal @repository.id, GithubAdapterRootFileset.first.repository_id
+          assert_equal 'main', GithubAdapterRootFileset.first.revision
+          assert_equal main_changeset.id, GithubAdapterRootFileset.first.changeset_id
+          assert_equal entry.path, GithubAdapterRootFileset.first.path
+          assert_equal entry.size.to_s, GithubAdapterRootFileset.first.size
+          assert_equal entry.lastrev.identifier, GithubAdapterRootFileset.first.latest_commitid
+        end
+      end
+    end
+  end
+
+  def test_latest_changesets_Githubでidentifierにデフォルトブランチ名が与えられ未反映のrevisionが存在しない場合
+    rev = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                              time: Time.parse("2023-01-01 00:00:00"), message: 'message')
+
+    @scm.stub(:revisions, build_mock([rev]) { |path, identifier_from, identifier_to|
+      assert_equal 'README.md', path
+      assert_equal nil, identifier_from
+      assert_equal 'main', identifier_to
+    }) do
+      @repository.stub(:default_branch, build_mock('main')) do
+        assert_no_difference 'Changeset.count' do
+
+          latest_changesets = @repository.latest_changesets('README.md', 'main')
+
+          assert_equal 1, latest_changesets.size
+          assert_equal 'shashasha', latest_changesets.first.revision
+          assert_equal 'shashasha', latest_changesets.first.scmid
+          assert_equal 'message', latest_changesets.first.comments
+          assert_equal Time.parse("2023-01-01 00:00:00"), latest_changesets.first.committed_on
+        end
+      end
+    end
+  end
+
+  def test_latest_changesets_Githubでidentifierにデフォルトブランチ名が与えられ未反映のrevisionが存在した場合
+    rev1 = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                               time: Time.gm(2023, 1, 1), message: 'message')
+    rev2 = OctokitRevision.new(identifier: 'latestsha', scmid: 'latestsha', author: @author,
+                               time: Time.gm(2023, 1, 1), message: 'latest')
+    cgs2 = Changeset.create!(
+      repository_id: @repository.id,
+      revision: "latestsha",
+      committer: 'author_name',
+      committed_on: Time.parse('2023-01-02'),
+      comments: 'latest message',
+      commit_date: '2023-01-02',
+      scmid: 'latestsha'
+    )
+
+    @scm.stub(:revisions, build_mock([rev1, rev2]) { |path, identifier_from, identifier_to|
+      assert_equal 'README.md', path
+      assert_equal nil, identifier_from
+      assert_equal 'main', identifier_to
+    }) do
+      @repository.stub(:default_branch, build_mock('main')) do
+        assert_no_difference 'Changeset.count' do
+
+          latest_changesets = @repository.latest_changesets('README.md', 'main')
+
+          assert_equal 2, latest_changesets.size
+          assert_equal 'latestsha', latest_changesets.first.revision
+          assert_equal 'latestsha', latest_changesets.first.scmid
+          assert_equal 'latest message', latest_changesets.first.comments
+          assert_equal Time.parse("2023-01-02"), latest_changesets.first.committed_on
+
+          assert_equal 'shashasha', latest_changesets.last.revision
+          assert_equal 'shashasha', latest_changesets.last.scmid
+          assert_equal 'message', latest_changesets.last.comments
+          assert_equal Time.parse("2023-01-01"), latest_changesets.last.committed_on
+        end
+      end
+    end
+  end
+
+  def test_latest_changesets_GithubでidentifierにデフォルトブランチでないSHA1ハッシュが与えられ未反映のrevisionが存在する場合
+    rev1 = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                               time: Time.gm(2023, 1, 1), message: 'message')
+    rev2 = OctokitRevision.new(identifier: 'latestsha', scmid: 'latestsha', author: @author,
+                               time: Time.gm(2023, 2, 1), message: 'latest')
+    commit = OctokitCommit.new(sha: 'latestsha', files: [TestFile.new(status: "added", filename: "README.md")],
+                               parents: OctokitCommit.new(sha: 'shashasha'))
+
+    @scm.stub(:revisions, build_mock([rev1, rev2]) { |path, identifier_from, identifier_to|
+      assert_equal 'README.md', path
+      assert_equal nil, identifier_from
+      assert_equal 'latestsha', identifier_to
+    }) do
+      @repository.stub(:default_branch, build_mock('main')) do
+        Octokit.stub(:commit, build_mock(commit) { |repo, identifier|
+          assert_equal @repo, repo
+          assert_equal 'latestsha', identifier
+        }) do
+          assert_difference 'Changeset.count', 1 do
+
+            latest_changesets = @repository.latest_changesets('README.md', 'latestsha')
+
+            assert_equal 2, latest_changesets.size
+            assert_equal 'latestsha', latest_changesets.first.revision
+            assert_equal 'latestsha', latest_changesets.first.scmid
+            assert_equal 'latest', latest_changesets.first.comments
+            assert_equal Time.gm(2023, 2, 1), latest_changesets.first.committed_on
+
+            assert_equal 'shashasha', latest_changesets.last.revision
+            assert_equal 'shashasha', latest_changesets.last.scmid
+            assert_equal 'message', latest_changesets.last.comments
+            assert_equal Time.parse("2023-01-01"), latest_changesets.last.committed_on
+          end
+        end
+      end
+    end
+  end
+
+  def test_latest_changesets_GithubでidentifierにデフォルトブランチでないSHA1ハッシュが与えられ未反映のrevisionが存在しない場合
+    rev = OctokitRevision.new(identifier: 'shashasha', scmid: 'shashasha', author: @author,
+                              time: Time.parse("2023-01-01 00:00:00"), message: 'message')
+
+    @scm.stub(:revisions, build_mock([rev]) { |path, identifier_from, identifier_to|
+      assert_equal 'README.md', path
+      assert_equal nil, identifier_from
+      assert_equal 'shashasha', identifier_to
+    }) do
+      @repository.stub(:default_branch, build_mock('main')) do
+        assert_no_difference 'Changeset.count' do
+
+          latest_changesets = @repository.latest_changesets('README.md', 'shashasha')
+
+          assert_equal 1, latest_changesets.size
+          assert_equal 'shashasha', latest_changesets.first.revision
+          assert_equal 'shashasha', latest_changesets.first.scmid
+          assert_equal 'message', latest_changesets.first.comments
+          assert_equal Time.parse("2023-01-01 00:00:00"), latest_changesets.first.committed_on
+        end
+      end
+    end
+  end
+
+  def test_using_root_fileset_cache_Githubでpathが指定されている場合
+    is_using_cache = @repository.send(:using_root_fileset_cache?, 'README.md', 'main')
+
+    assert !is_using_cache
+  end
+
+  def test_using_root_fileset_cache_Githubでキャッシュが存在せずidentifierにデフォルトブランチ名以外を指定した場合
+    @scm.stub(:default_branch, build_mock('main')) do
+      is_using_cache = @repository.send(:using_root_fileset_cache?, '', 'feature_branch')
+
+      assert !is_using_cache
+    end
+  end
+
+  def test_using_root_fileset_cache_Githubでキャッシュが存在せずidentifierにデフォルトブランチ名を指定した場合
+    @scm.stub(:default_branch, build_mock('main')) do
+      is_using_cache = @repository.send(:using_root_fileset_cache?, '', 'main')
+
+      assert is_using_cache
+    end
+  end
+
+  def test_using_root_fileset_cache_Githubでキャッシュが存在しidentifierとしてキャッシュのrevisionが指定された場合
+    GithubAdapterRootFileset.create!(
+      repository_id: @repository.id,
+      revision: 'shashasha',
+      changeset_id: @default_changeset.id,
+      path: "README.md",
+      size: 256,
+      latest_commitid: 'shashasha'
+    )
+
+    @scm.stub(:default_branch, build_mock('main')) do
+      is_using_cache = @repository.send(:using_root_fileset_cache?, '', 'shashasha')
+
+      assert is_using_cache
+    end
+  end
+
+  def test_using_root_fileset_cache_Githubでキャッシュが存在しidentifierにデフォルトブランチ名を受け取った場合
+    GithubAdapterRootFileset.create!(
+      repository_id: @repository.id,
+      revision: 'shashasha',
+      changeset_id: @default_changeset.id,
+      path: "README.md",
+      size: 256,
+      latest_commitid: 'shashasha'
+    )
+
+    @scm.stub(:default_branch, build_mock('main')) do
+      is_using_cache = @repository.send(:using_root_fileset_cache?, '', 'main')
+
+      assert is_using_cache
+    end
+  end
+
+  def test_using_root_fileset_cache_Githubでキャッシュが存在しidentifierに別のコミットIDが指定されている場合
+    GithubAdapterRootFileset.create!(
+      repository_id: @repository.id,
+      revision: 'shashasha',
+      changeset_id: @default_changeset.id,
+      path: "README.md",
+      size: 256,
+      latest_commitid: 'shashasha'
+    )
+
+    @scm.stub(:default_branch, build_mock('main')) do
+      is_using_cache = @repository.send(:using_root_fileset_cache?, '', 'othershasha')
+
+      assert !is_using_cache
+    end
+  end
+
+  ## 以下、Octokitのモックに使う部品たち ##
+  OctokitRevision = Struct.new(:identifier, :scmid, :author, :committer, :tree,
+                               :message, :paths, :time, :parents, keyword_init: true)
+  OctokitCommit = Struct.new(:sha, :commit, :parents, :files, keyword_init: true)
+  OctokitContent = Struct.new(:sha, :name, :path, :type, :size, :download_url,
+                              :content, :encoding, keyword_init: true)
+  OctokitAuthor = Struct.new(:name, keyword_init: true)
+  RepositoryChangeset = Struct.new(:repository, :id, :revision, :scmid, :comitter,
+                                   :committed_on, :comments, keyword_init: true)
+  RepositoryEntry = Struct.new(:path, :size, :lastrev, keyword_init: true)
+  TestFile = Struct.new(:status, :filename, :previous_filename,
+                        :from_revision, :patch, keyword_init: true)
+  # メソッドの定義
+  OctokitAuthor.define_method(:to_s) { self.name }
+
+  def build_mock(*returns, &proc)
+    mock = Minitest::Mock.new
+    Array.wrap(returns).each do |ret|
+      mock.expect(:call, ret, &proc)
+    end
+    mock
+  end
+
+end