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