├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── tachikoma_ai.rb └── tachikoma_ai │ ├── repository.rb │ ├── strategies │ ├── bundler.rb │ └── bundler │ │ ├── gem.rb │ │ └── github_urls.json │ ├── strategy.rb │ ├── tachikoma_extention.rb │ └── version.rb ├── repos └── .keep ├── screenshots └── pullreq.png ├── spec ├── spec_helper.rb ├── tachikoma_ai │ ├── repository_spec.rb │ ├── strategies │ │ ├── bundler │ │ │ └── gem_spec.rb │ │ └── bundler_spec.rb │ └── tachikoma_extention_spec.rb └── tachikoma_ai_spec.rb └── tachikoma_ai.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | .byebug_history 11 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisplayCopNames: true 3 | 4 | Metrics/AbcSize: 5 | Max: 17 6 | 7 | Metrics/LineLength: 8 | Max: 120 9 | 10 | Style/Documentation: 11 | Enabled: false 12 | 13 | Metrics/BlockLength: 14 | Exclude: 15 | - 'spec/**/*.rb' 16 | 17 | Style/RescueStandardError: 18 | Enabled: false 19 | 20 | Layout/IndentHeredoc: 21 | Enabled: false 22 | 23 | Naming/HeredocDelimiterNaming: 24 | Enabled: false 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.6 4 | - 2.4.3 5 | - 2.5.0 6 | before_install: gem install bundler -v 1.16.1 7 | notifications: 8 | email: false 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master (unreleased) 2 | 3 | ## v0.5.0 4 | 5 | - :sparkles: Sort by gem name 6 | - :sparkles: Summarize same gem names 7 | - :sparkles: Display the compare url even if an error occurs 8 | - :sparkles: Add checkbox for tasks 9 | - :bug: Fix a bug where the previous gem does not exist 10 | - :x: Drop support for old Ruby versions (2.0, 2.1.x, 2.2.x) 11 | 12 | ## v0.4.0 13 | 14 | - :sparkles: Fix the compare url when tags not found 15 | 16 | ## v0.3.1 17 | 18 | - :bug: Fix an error that occurs when GitHub API returns 301 19 | 20 | ## v0.3.0 21 | 22 | - :bug: Fix crash when specify a github repository in Gemfile 23 | - :sparkles: Support gems that have the official page 24 | - [lib/tachikoma_ai/strategies/bundler/urls.json](https://github.com/sinsoku/tachikoma_ai/blob/master/lib/tachikoma_ai/strategies/bundler/urls.json) 25 | 26 | ## v0.2.0 27 | 28 | - :sparkles: Support version tag without 'v' prefix 29 | 30 | ## v0.1.0 31 | 32 | - Initial release :tada: 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in tachikoma_ai.gemspec 4 | gemspec 5 | 6 | gem 'byebug' 7 | gem 'codecov', require: false 8 | gem 'webmock' 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 sinsoku 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TachikomaAi 2 | 3 | [![Gem Version](https://badge.fury.io/rb/tachikoma_ai.svg)](https://badge.fury.io/rb/tachikoma_ai) 4 | [![Build Status](https://travis-ci.org/sinsoku/tachikoma_ai.svg?branch=master)](https://travis-ci.org/sinsoku/tachikoma_ai) 5 | [![codecov.io](https://codecov.io/github/sinsoku/tachikoma_ai/coverage.svg?branch=master)](https://codecov.io/github/sinsoku/tachikoma_ai?branch=master) 6 | 7 | TachikomaAi is a artificial intelligence for [sanemat/tachikoma](https://github.com/sanemat/tachikoma). 8 | 9 | ## Features 10 | 11 | - Append comparing urls to Pull Request 12 | 13 | ![](https://raw.github.com/sinsoku/tachikoma_ai/master/screenshots/pullreq.png) 14 | 15 | ## Supported versions 16 | 17 | - Ruby 2.3.3, 2.4.0 18 | 19 | ## Supported strategies 20 | 21 | - Bundler (Ruby) 22 | 23 | ## Installation 24 | 25 | Add this line to Gemfile: 26 | 27 | ```ruby 28 | gem 'tachikoma_ai' 29 | ``` 30 | 31 | And then execute: 32 | 33 | $ bundle 34 | 35 | Load a TachikomaAi in `Rakefile`: 36 | 37 | ```diff 38 | require 'bundler/setup' 39 | require 'tachikoma/tasks' 40 | + require 'tachikoma_ai' 41 | ``` 42 | 43 | ## Development 44 | 45 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 46 | 47 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 48 | 49 | ## Contributing 50 | 51 | Bug reports and pull requests are welcome on GitHub at https://github.com/sinsoku/tachikoma_ai. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct. 52 | 53 | 54 | ## License 55 | 56 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 57 | 58 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new 8 | 9 | task default: %i[rubocop spec] 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'tachikoma_ai' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/tachikoma_ai.rb: -------------------------------------------------------------------------------- 1 | require 'tachikoma_ai/repository' 2 | require 'tachikoma_ai/strategy' 3 | require 'tachikoma_ai/tachikoma_extention' 4 | require 'tachikoma_ai/version' 5 | 6 | module TachikomaAi 7 | module Strategies 8 | autoload :Bundler, 'tachikoma_ai/strategies/bundler' 9 | end 10 | 11 | def self.strategies 12 | @strategies ||= [] 13 | end 14 | end 15 | 16 | require 'tachikoma/application' 17 | Tachikoma::Application.send :prepend, TachikomaAi::TachikomaExtention 18 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/repository.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'json' 3 | 4 | module TachikomaAi 5 | class Repository 6 | attr_reader :url, :owner, :repo 7 | 8 | def initialize(url) 9 | @url = url.gsub(/http:/, 'https:') 10 | @owner, @repo = URI.parse(@url).path.split('/').drop(1) 11 | end 12 | 13 | def compare(start, endd) 14 | s = find_tag(start) 15 | e = find_tag(endd) 16 | base = "https://github.com/#{owner}/#{repo}" 17 | if s.nil? && e.nil? 18 | "#{base} (tags not found)" 19 | elsif e.nil? 20 | "#{base}/compare/#{s}...master" 21 | else 22 | "#{base}/compare/#{s}...#{e}" 23 | end 24 | end 25 | 26 | private 27 | 28 | def api_tags_url 29 | "https://api.github.com/repos/#{owner}/#{repo}/git/refs/tags" 30 | end 31 | 32 | def find_tag(tag) 33 | tags.find { |t| t == "v#{tag}" || t == tag } 34 | end 35 | 36 | def tags 37 | return @tags if @tags 38 | 39 | res = fetch(api_tags_url) 40 | @tags = if res.is_a? Net::HTTPSuccess 41 | json = JSON.parse(res.body) 42 | json.map { |tag| tag['ref'].gsub('refs/tags/', '') } 43 | else 44 | {} 45 | end 46 | end 47 | 48 | def fetch(uri_str, limit = 10) 49 | raise ArgumentError, 'HTTP redirect too deep' if limit.zero? 50 | 51 | response = Net::HTTP.get_response URI.parse(uri_str) 52 | if response.is_a? Net::HTTPRedirection 53 | fetch(response['location'], limit - 1) 54 | else 55 | response 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/strategies/bundler.rb: -------------------------------------------------------------------------------- 1 | require 'tachikoma_ai/strategies/bundler/gem' 2 | require 'bundler' 3 | 4 | module TachikomaAi 5 | module Strategies 6 | class Bundler 7 | include TachikomaAi::Strategy 8 | 9 | def pull_request_body 10 | <<-EOF 11 | ## Compare 12 | #{compare_urls} 13 | 14 | ## Not found 15 | #{homepage_urls} 16 | EOF 17 | end 18 | 19 | private 20 | 21 | def updated_gems 22 | return @updated_gems if @updated_gems 23 | 24 | previous = lockfile('HEAD^') 25 | @updated_gems = diff_specs(previous, lockfile('HEAD')).map do |spec| 26 | before = previous.specs.find { |s| s.name == spec.name } 27 | gem(spec, before) 28 | end.compact.uniq(&:name) 29 | end 30 | 31 | def diff_specs(previous, current) 32 | current.specs.reject { |s| previous.specs.include?(s) } 33 | end 34 | 35 | def lockfile(ref) 36 | ::Bundler::LockfileParser.new(`git show #{ref}:Gemfile.lock`) 37 | end 38 | 39 | def compare_urls 40 | updated_gems.select(&:github_url?).map { |gem| url_with_checkbox(gem.compare_url) }.join("\n") 41 | end 42 | 43 | def url_with_checkbox(url) 44 | "- [ ] #{url}" 45 | end 46 | 47 | def homepage_urls 48 | updated_gems.reject(&:github_url?).map(&:homepage).join("\n") 49 | end 50 | 51 | def gem(spec, before) 52 | return if before.nil? 53 | Gem.new(spec.name, before.version.to_s, spec.version.to_s) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/strategies/bundler/gem.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'json' 3 | 4 | module TachikomaAi 5 | module Strategies 6 | class Bundler 7 | class Gem 8 | major, minor = RUBY_VERSION.split('.') 9 | SPECS_PATH = "vendor/bundle/ruby/#{major}.#{minor}.0/specifications/".freeze 10 | GITHUB_URLS = File.read(File.expand_path('github_urls.json', __dir__)) 11 | GITHUB_URL_JSON = JSON.parse(GITHUB_URLS) 12 | 13 | attr_reader :name, :from, :version 14 | 15 | def initialize(name, from, version) 16 | @name = name 17 | @from = from 18 | @version = version 19 | end 20 | 21 | def github_url? 22 | !github_url.nil? 23 | end 24 | 25 | def github_url 26 | if homepage.include?('github.com') 27 | homepage 28 | elsif GITHUB_URL_JSON.key?(name) 29 | "https://github.com/#{GITHUB_URL_JSON[name]}" 30 | end 31 | end 32 | 33 | def homepage 34 | @homepage ||= if spec 35 | spec.homepage 36 | else 37 | "#{name}-#{version}" 38 | end 39 | end 40 | 41 | def compare_url 42 | Repository.new(github_url).compare(from, version) 43 | rescue => e 44 | "#{github_url}/compare/v#{from}...v#{version} (#{e.class} #{e})" 45 | end 46 | 47 | private 48 | 49 | def spec 50 | ::Gem::Specification.load(spec_path) 51 | end 52 | 53 | def spec_path 54 | "#{SPECS_PATH}/#{name}-#{version}.gemspec" 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/strategies/bundler/github_urls.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_args": "asakusarb/action_args", 3 | "airbrake": "airbrake/airbrake", 4 | "capistrano": "capistrano/capistrano", 5 | "coderay": "rubychan/coderay", 6 | "coffee-script-source": "jessedoyle/coffee-script-source", 7 | "concurrent-ruby": "ruby-concurrency/concurrent-ruby", 8 | "eventmachine": "eventmachine/eventmachine", 9 | "logging": "TwP/logging", 10 | "nokogiri": "sparklemotion/nokogiri", 11 | "puma": "puma/puma", 12 | "rack-mini-profiler": "MiniProfiler/rack-mini-profiler", 13 | "rails_best_practices": "railsbp/rails_best_practices", 14 | "sass": "sass/sass", 15 | "sidekiq": "mperham/sidekiq", 16 | "thin": "macournoyer/thin", 17 | "thor": "erikhuda/thor", 18 | "unicorn": "defunkt/unicorn", 19 | "uniform_notifier": "flyerhzm/uniform_notifier" 20 | } 21 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/strategy.rb: -------------------------------------------------------------------------------- 1 | module TachikomaAi 2 | module Strategy 3 | def self.included(base) 4 | TachikomaAi.strategies << base 5 | end 6 | 7 | def pull_request_body 8 | raise NotImplementedError 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/tachikoma_extention.rb: -------------------------------------------------------------------------------- 1 | module TachikomaAi 2 | module TachikomaExtention 3 | def run(strategy) 4 | begin 5 | @strategy = TachikomaAi::Strategies.const_get(strategy.capitalize).new 6 | rescue NameError 7 | raise LoadError, "Could not find matching strategy for #{strategy}." 8 | end 9 | super 10 | end 11 | 12 | def pull_request 13 | @pull_request_body = @pull_request_body.to_s 14 | if @strategy 15 | Dir.chdir("#{Tachikoma.repos_path}/#{@build_for}") do 16 | @pull_request_body += "\n\n" + @strategy.pull_request_body 17 | end 18 | end 19 | rescue 20 | p $ERROR_INFO 21 | ensure 22 | super 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/tachikoma_ai/version.rb: -------------------------------------------------------------------------------- 1 | module TachikomaAi 2 | VERSION = '0.5.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /repos/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinsoku/tachikoma_ai/a12b6c4378087f5178d146346125a7a52fb54878/repos/.keep -------------------------------------------------------------------------------- /screenshots/pullreq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinsoku/tachikoma_ai/a12b6c4378087f5178d146346125a7a52fb54878/screenshots/pullreq.png -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | 3 | require 'simplecov' 4 | SimpleCov.start 5 | if ENV['CI'] == 'true' 6 | require 'codecov' 7 | SimpleCov.formatters = [ 8 | SimpleCov::Formatter::HTMLFormatter, 9 | SimpleCov::Formatter::Codecov 10 | ] 11 | end 12 | 13 | require 'tachikoma_ai' 14 | require 'webmock/rspec' 15 | -------------------------------------------------------------------------------- /spec/tachikoma_ai/repository_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module TachikomaAi 4 | describe Repository do 5 | describe '#initialize' do 6 | let(:repository) { Repository.new('https://github.com/sinsoku/tachikoma_ai') } 7 | it { expect(repository.owner).to eq 'sinsoku' } 8 | it { expect(repository.repo).to eq 'tachikoma_ai' } 9 | end 10 | 11 | describe '#compare' do 12 | let(:repository) { Repository.new('https://github.com/sinsoku/tachikoma_ai') } 13 | 14 | context 'Response: 200 OK ' do 15 | context 'both tags exist' do 16 | before do 17 | stub_request(:get, repository.send(:api_tags_url)) 18 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}, {"ref":"refs/tags/v0.2.0"}]') 19 | end 20 | subject { repository.compare('0.1.0', '0.2.0') } 21 | it { is_expected.to eq 'https://github.com/sinsoku/tachikoma_ai/compare/v0.1.0...v0.2.0' } 22 | end 23 | 24 | context 'the end tag not exists' do 25 | before do 26 | stub_request(:get, repository.send(:api_tags_url)) 27 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}]') 28 | end 29 | subject { repository.compare('0.1.0', '0.2.0') } 30 | it { is_expected.to eq 'https://github.com/sinsoku/tachikoma_ai/compare/v0.1.0...master' } 31 | end 32 | 33 | context 'both tags not exist' do 34 | before do 35 | stub_request(:get, repository.send(:api_tags_url)) 36 | .to_return(status: 200, body: '[]') 37 | end 38 | subject { repository.compare('0.1.0', '0.2.0') } 39 | it { is_expected.to eq 'https://github.com/sinsoku/tachikoma_ai (tags not found)' } 40 | end 41 | end 42 | 43 | context 'Response: 301 Moved Permanently' do 44 | before do 45 | redirect_url = 'https://api.github.com/repositories/1/git/refs/tags' 46 | stub_request(:get, repository.send(:api_tags_url)) 47 | .to_return(status: 301, headers: { 'Location' => redirect_url }) 48 | stub_request(:get, redirect_url) 49 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}, {"ref":"refs/tags/v0.2.0"}]') 50 | end 51 | subject { repository.compare('0.1.0', '0.2.0') } 52 | it { is_expected.to eq 'https://github.com/sinsoku/tachikoma_ai/compare/v0.1.0...v0.2.0' } 53 | end 54 | 55 | context 'Response: 404 Not Found' do 56 | before do 57 | stub_request(:get, repository.send(:api_tags_url)) 58 | .to_return(status: 404, body: '{"message": "Not Found"}') 59 | end 60 | subject { repository.compare('0.1.0', '0.2.0') } 61 | it { is_expected.to eq 'https://github.com/sinsoku/tachikoma_ai (tags not found)' } 62 | end 63 | end 64 | 65 | describe 'private #find_tag' do 66 | context 'the version tag with "v" prefix on GitHub' do 67 | let(:repository) { Repository.new('https://github.com/sinsoku/tachikoma_ai') } 68 | before do 69 | stub_request(:get, repository.send(:api_tags_url)) 70 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}]') 71 | end 72 | it { expect(repository.send(:find_tag, '0.1.0')).to eq 'v0.1.0' } 73 | end 74 | 75 | context 'the version tag without "v" prefix on GitHub' do 76 | let(:repository) { Repository.new('https://github.com/sinsoku/tachikoma_ai') } 77 | before do 78 | stub_request(:get, repository.send(:api_tags_url)) 79 | .to_return(status: 200, body: '[{"ref":"refs/tags/0.1.0"}]') 80 | end 81 | it { expect(repository.send(:find_tag, '0.1.0')).to eq '0.1.0' } 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/tachikoma_ai/strategies/bundler/gem_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module TachikomaAi 4 | module Strategies 5 | class Bundler 6 | describe Gem do 7 | describe '#compare_url' do 8 | let(:gem) { Gem.new('tachikoma_ai', '0.1.0', '0.2.0') } 9 | let(:repository) { Repository.new('https://github.com/sinsoku/tachikoma_ai') } 10 | let(:compare_url) { 'https://github.com/sinsoku/tachikoma_ai/compare/v0.1.0...v0.2.0' } 11 | 12 | context 'the compare url created successfully' do 13 | before do 14 | allow(gem).to receive(:spec_path) { 'tachikoma_ai.gemspec' } 15 | stub_request(:get, repository.send(:api_tags_url)) 16 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}, {"ref":"refs/tags/v0.2.0"}]') 17 | end 18 | it { expect(gem.compare_url).to eq compare_url } 19 | end 20 | 21 | context 'failed to create the compare url' do 22 | before do 23 | allow(gem).to receive(:spec_path) { 'tachikoma_ai.gemspec' } 24 | allow_any_instance_of(Repository).to receive(:compare) { raise 'something' } 25 | end 26 | it { expect(gem.compare_url).to eq "#{compare_url} (RuntimeError something)" } 27 | end 28 | end 29 | 30 | describe '#homepage' do 31 | context do 32 | let(:gem) { Gem.new('tachikoma_ai', '0.1.0', '0.2.0') } 33 | before { allow(gem).to receive(:spec_path) { 'tachikoma_ai.gemspec' } } 34 | it { expect(gem.homepage).to eq 'https://github.com/sinsoku/tachikoma_ai' } 35 | end 36 | 37 | context 'when specify a github repository in Gemfile' do 38 | let(:gem) { Gem.new('tachikoma_ai', '0.1.0', '0.2.0') } 39 | before { allow(gem).to receive(:spec) { nil } } 40 | it { expect(gem.homepage).to eq 'tachikoma_ai-0.2.0' } 41 | end 42 | end 43 | 44 | describe '#github_url?' do 45 | context 'gem does not include in URLS' do 46 | let(:gem) { Gem.new('tachikoma_ai', '0.1.0', '0.2.0') } 47 | before { allow(gem).to receive(:spec_path) { 'tachikoma_ai.gemspec' } } 48 | it { expect(gem).to be_github_url } 49 | end 50 | 51 | context 'gem include in URLS' do 52 | let(:gem) { Gem.new('sidekiq', '1.0.0', '1.0.1') } 53 | it { expect(gem).to be_github_url } 54 | end 55 | end 56 | 57 | describe 'github_urls.json' do 58 | it 'sort by gem name' do 59 | expect(Gem::GITHUB_URL_JSON.keys).to eq Gem::GITHUB_URL_JSON.keys.sort 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/tachikoma_ai/strategies/bundler_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module TachikomaAi 4 | module Strategies 5 | describe Bundler do 6 | describe '#pull_request_body' do 7 | let(:bundler) { Bundler.new } 8 | 9 | before do 10 | allow(bundler).to receive(:lockfile).and_return(previous, current) 11 | allow_any_instance_of(Bundler::Gem).to receive(:spec_path) { 'tachikoma_ai.gemspec' } 12 | stub_request(:get, 'https://api.github.com/repos/sinsoku/tachikoma_ai/git/refs/tags') 13 | .to_return(status: 200, body: '[{"ref":"refs/tags/v0.1.0"}, {"ref":"refs/tags/v0.2.0"}]') 14 | end 15 | 16 | context 'when successful' do 17 | let(:expected) { 'https://github.com/sinsoku/tachikoma_ai/compare/v0.1.0...v0.2.0' } 18 | 19 | let(:previous) { ::Bundler::LockfileParser.new("GEM\n specs:\n tachikoma_ai (0.1.0)") } 20 | let(:current) { ::Bundler::LockfileParser.new("GEM\n specs:\n tachikoma_ai (0.2.0)") } 21 | 22 | it { expect(bundler.pull_request_body).to include expected } 23 | end 24 | 25 | context 'when unsuccessful' do 26 | let(:expected) { 'activesupport' } 27 | let(:previous) do 28 | ::Bundler::LockfileParser.new( 29 | "GEM\n specs:\n activesupport (4.2.6)\n i18n (~> 0.7)" 30 | ) 31 | end 32 | let(:current) do 33 | ::Bundler::LockfileParser.new( 34 | "GEM\n specs:\n activesupport (5.1.1)\n concurrent-ruby (~> 1.0, >= 1.0.2)" 35 | ) 36 | end 37 | 38 | it { expect(bundler.pull_request_body).not_to include expected } 39 | end 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/tachikoma_ai/tachikoma_extention_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module TachikomaAi 4 | describe TachikomaExtention do 5 | let(:app) { Tachikoma::Application.new } 6 | let(:default_body) { ':hamster:' * 3 } 7 | 8 | before do 9 | # stub each method in the run method except pull_request 10 | allow(app).to receive(:load) 11 | allow(app).to receive(:fetch) 12 | allow(app).to receive(:bundler) 13 | 14 | app.instance_variable_set :@pull_request_body, default_body 15 | allow_any_instance_of(Strategies::Bundler).to receive(:pull_request_body) { 'body' } 16 | allow_any_instance_of(Octokit::Client).to receive(:create_pull_request) 17 | 18 | app.run 'bundler' 19 | end 20 | 21 | it 'should overwrite a pull request body' do 22 | actual = app.instance_variable_get :@pull_request_body 23 | expect(actual).to eq "#{default_body}\n\nbody" 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/tachikoma_ai_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe TachikomaAi do 4 | it 'has a version number' do 5 | expect(TachikomaAi::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /tachikoma_ai.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'tachikoma_ai/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'tachikoma_ai' 7 | spec.version = TachikomaAi::VERSION 8 | spec.authors = ['sinsoku'] 9 | spec.email = ['sinsoku.listy@gmail.com'] 10 | 11 | spec.summary = 'A tachikoma will get a artificial intelligence, and will become a high-spec combat vehicles.' 12 | spec.description = 'Give a comparing function on GitHub to tachikoma' 13 | spec.homepage = 'https://github.com/sinsoku/tachikoma_ai' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.require_paths = ['lib'] 18 | 19 | spec.add_dependency 'tachikoma', '>= 4.2' 20 | 21 | spec.add_development_dependency 'bundler', '~> 1.10' 22 | spec.add_development_dependency 'rake', '~> 10.0' 23 | spec.add_development_dependency 'rspec' 24 | spec.add_development_dependency 'rubocop', '0.54.0' 25 | end 26 | --------------------------------------------------------------------------------