Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Commit

Permalink
Add paging for retrieval of forks (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
poloka authored Aug 31, 2021
1 parent cbd9a9a commit ee109d9
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ assignees: ''

> Before reporting a bug
- [ ] Check the bug is reproducible.
- [ ] Search for [existing issues](https://github.com/poloka/github_bot-ruby/issues).
- [ ] Search for [existing issues](https://github.com/cerner/github_bot-ruby/issues).

**Describe the bug**
A clear and concise description of what the bug is.
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Community Support
url: https://github.com/poloka/github_bot-ruby/discussions
url: https://github.com/cerner/github_bot-ruby/discussions
about: Please ask and answer questions and propose new features.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ gem 'rubocop-performance', '~> 1.8', require: false
gem 'rubocop-rake', '~> 0.5', require: false
gem 'rubocop-rspec', '~> 2.1', require: false
gem 'simplecov', '~> 0.19', require: false
gem 'timecop', '~> 0.9'

# debugging
gem 'debase', '~> 0.2.5.beta2'
Expand Down
26 changes: 23 additions & 3 deletions lib/github_bot/github/payload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,35 @@ def repository_clone_url
# Public: Returns the repository fork URL from the original project with the most
# recent updated forked instance first
#
# Utilizing API: https://docs.github.com/en/rest/reference/repos#forks
# Example: https://api.github.com/repos/octocat/Hello-World/forks?page=1
#
# @return [Array] The array of [String] URLs associated to the forked repositories
def repository_fork_urls
return @repository_fork_urls if @repository_fork_urls

@repository_fork_urls =
[].tap do |ar|
json = URI.parse(repository[:forks_url]).open.read
JSON.parse(json).sort_by { |i| Date.parse i['updated_at'] }.reverse_each do |fork|
ar << fork['clone_url']
# iterate over pages of forks
page_count = 1
forks_url = repository[:forks_url]
loop do
uri = URI.parse(forks_url)
new_query_ar = URI.decode_www_form(String(uri.query)) << ['page', page_count]
uri.query = URI.encode_www_form(new_query_ar)

Rails.logger.info "#{self.class}##{__method__} retrieving #{uri}"

json = uri.open.read
json_response = JSON.parse(json)
break if json_response.empty?

# iterate over each fork and capture the clone_url
json_response.each do |fork|
ar << fork['clone_url']
end

page_count += 1
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/github_bot/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module GithubBot
VERSION = '0.1.0'
VERSION = '0.1.1'

# version module
module Version
Expand Down
57 changes: 57 additions & 0 deletions spec/lib/github_bot/github/check_run_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

require 'spec_helper'
require 'timecop'

RSpec.describe GithubBot::Github::CheckRun do
let(:client) { double }

subject do
described_class.new(
name: 'foo',
repo: 'bar',
sha: 'baz',
client_api: client
)
end

before :each do
expect(client).to receive(:create_check_run)
end

it '#initialize' do
expect(subject).not_to be_nil
end

it '#in_progress!' do
time = Time.iso8601('2020-04-21T00:00:00Z')
Timecop.freeze(time) do
expect(subject).to receive(:update).with(status: 'in_progress', started_at: time)
subject.in_progress!
end
end

it '#complete!' do
time = Time.iso8601('2020-04-21T00:00:00Z')
Timecop.freeze(time) do
expect(subject).to receive(:update).with(
status: 'completed',
conclusion: 'success',
completed_at: time
)
subject.complete!
end
end

it '#action_required!' do
time = Time.iso8601('2020-04-21T00:00:00Z')
Timecop.freeze(time) do
expect(subject).to receive(:update).with(
status: 'completed',
conclusion: 'action_required',
completed_at: time
)
subject.action_required!
end
end
end
156 changes: 156 additions & 0 deletions spec/lib/github_bot/github/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe GithubBot::Github::Client do
let(:request) { double }
let(:client) { double }
let(:removed_file) { double('Sawyer::Resource', type: 'file', status: 'removed') }
let(:added_file) { double('Sawyer::Resource', type: 'file', status: 'added') }
let(:files) { [removed_file, added_file] }
let(:pull_request) { double }

subject { described_class.initialize(request) }

before :each do
described_class.instance_variable_set(:@instance, nil)
subject.instance_variable_set(:@client, client)
end

it '.initialize' do
expect(described_class.initialize(request)).not_to be_nil
end

describe '.instance' do
before :each do
described_class.instance_variable_set(:@instance, nil)
end

context 'when no instance' do
it 'raises error' do
expect { described_class.instance }.to raise_error(StandardError)
end
end

context 'when an instance exists' do
let(:instance) { double }
it 'returns the instance' do
described_class.instance_variable_set(:@instance, instance)
expect(described_class.instance).to eql(instance)
end
end
end

describe '#file_content' do
let(:file) { double('Sawyer::Resource', type: 'file') }
let(:filename) { 'foo' }

before do
allow(file).to receive(:filename).and_return(filename)
allow(subject).to receive(:repository_full_name)
allow(subject).to receive(:head_sha)
end

context 'when content not found' do
it 'returns empty string' do
expect(client).to receive(:contents).and_raise(Octokit::NotFound)
expect(subject.file_content(file)).to be_empty
end
end

context 'when content found' do
context 'when small' do
it 'decodes the response' do
expect(client).to receive(:contents)
expect { subject.file_content(file) }.not_to raise_error
end
end

context 'when too_large' do
let(:exception) { Octokit::Forbidden.new(body: { errors: [code: 'too_large'] }) }
let(:uri_object) { double }
let(:raw_url) { 'https://raw.github.com/foo/bar/main/file.rb'}
let(:parsed_url) { double }

it 'uses the raw url for retrieval' do
expect(client).to receive(:contents).and_raise(exception)
expect(file).to receive(:raw_url).and_return(raw_url)
expect(URI).to receive(:parse).with(raw_url).and_return(parsed_url)
expect(parsed_url).to receive(:open).and_return(uri_object)
expect(uri_object).to receive(:read)
expect { subject.file_content(file) }.not_to raise_error
end
end
end
end

describe '#modified_files' do
it 'returns modified files' do
allow(subject).to receive(:pull_request).and_return(pull_request)
expect(subject).to receive(:repository_full_name)
expect(subject).to receive(:pull_request_number)
expect(client).to receive(:pull_request_files).and_return(files)
expect(subject.modified_files).not_to be_empty
expect(subject.modified_files).to include(added_file)
expect(subject.modified_files).not_to include(removed_file)
end
end

describe '#files' do
it 'returns all files' do
allow(subject).to receive(:pull_request).and_return(pull_request)
expect(subject).to receive(:repository_full_name)
expect(subject).to receive(:pull_request_number)
expect(client).to receive(:pull_request_files).and_return(files)
expect(subject.files).not_to be_empty
expect(subject.files).to include(added_file)
expect(subject.files).to include(removed_file)
end
end

describe '#comment' do
it 'adds a comment' do
expect(subject).to receive(:repository_full_name)
expect(subject).to receive(:pull_request_number)
expect(client).to receive(:add_comment)
subject.comment(message: 'foo')
end
end

describe '#create_check_run' do
it 'creates a check run instance' do
expect(subject).to receive(:repository_full_name)
expect(subject).to receive(:head_sha)
expect(GithubBot::Github::CheckRun).to receive(:new)
subject.create_check_run(name: 'foo')
end
end

describe '#pull_request_details' do
let(:repo_name) { 'foo-delivery/foo-config' }
let(:pull_request_number) { 123 }
let(:pull_request) { { number: pull_request_number } }
let(:pull_request_response) { double }
let(:mock_payload) do
{
repository: {
full_name: repo_name
}
}
end

it 'validates' do
allow(subject).to receive(:payload).and_return(mock_payload)
expect(subject).to receive(:pull_request).and_return(pull_request)
expect(client).to receive(:pull_request).with(
repo_name, pull_request_number
).and_return(pull_request_response)
expect(subject.pull_request_details).to eq(pull_request_response)

# check set of instance variable
expect(subject.instance_variable_get(:@pull_request_details)).to eq(pull_request_response)
expect(client).not_to receive(:pull_request)
expect(subject.pull_request_details).to eq(pull_request_response)
end
end
end
54 changes: 54 additions & 0 deletions spec/lib/github_bot/github/payload_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'spec_helper'

class DummyTestClass
include GithubBot::Github::Payload

def repository; end
end

RSpec.describe GithubBot::Github::Payload do
subject { DummyTestClass.new }

describe '#repository_fork_urls' do
let(:mock_payload) { double }
let(:mock_repository) { double }
let(:mock_parse) { double('URI::HTTP', query: []) }
let(:mock_open) { double }
let(:mock_data) do
[].tap do |ar|
ar << {
updated_at: '2020-10-01T00:00:00Z',
clone_url: 'http://foo.clone.com'
}.with_indifferent_access

ar << {
updated_at: '2020-10-02T00:00:00Z',
clone_url: 'http://bar.clone.com'
}.with_indifferent_access
end
end

before do
end

it 'returns fork urls' do
allow(URI).to receive(:parse).and_return(mock_parse)
allow(mock_parse).to receive(:query=)
allow(mock_parse).to receive(:open).and_return(mock_open)
allow(mock_open).to receive(:read)
allow(subject).to receive(:repository).and_return(mock_repository)
allow(subject).to receive(:payload).and_return(mock_payload)

# first sequence expect to retrieve actual data, second time return empty response
expect(JSON).to receive(:parse).ordered.and_return(mock_data)
expect(JSON).to receive(:parse).ordered.and_return({})

expect(mock_repository).to receive(:[]).with(:forks_url)
urls = subject.repository_fork_urls
expect(urls.length).to be 2
expect(urls).to include('http://bar.clone.com')
end
end
end

0 comments on commit ee109d9

Please sign in to comment.