From ad92abebb148357c16efa71f8e3e9f601c7415c0 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Tue, 28 Aug 2018 12:36:03 +0100 Subject: [PATCH] Install Octobox as a GitHub App (#806) * Lock pg gem to 1.0.0 1.1.0 and 1.1.1 are broken on alpine linux * Install Octobox as a GitHub App --- app/controllers/application_controller.rb | 9 +- app/controllers/hooks_controller.rb | 138 ++++++ app/controllers/notifications_controller.rb | 4 +- app/controllers/sessions_controller.rb | 2 +- app/helpers/notifications_helper.rb | 4 - app/models/app_installation.rb | 7 + app/models/notification.rb | 18 +- app/models/repository.rb | 5 + app/models/subject.rb | 3 + app/models/user.rb | 16 +- app/services/download_service.rb | 2 +- .../notifications/_notification.html.erb | 12 +- app/views/pages/home.html.erb | 6 + app/views/users/edit.html.erb | 7 + .../initializers/filter_parameter_logging.rb | 2 +- config/initializers/omniauth.rb | 14 + config/routes.rb | 2 + config/secrets.yml | 14 +- .../20180717112311_add_app_token_to_users.rb | 6 + ...20180806154015_create_app_installations.rb | 17 + ...add_app_installation_id_to_repositories.rb | 5 + db/schema.rb | 17 + docs/INSTALLATION.md | 3 + lib/octobox.rb | 4 + lib/octobox/configurator.rb | 28 +- test/controllers/hooks_controller_test.rb | 53 ++ .../notifications_controller_test.rb | 2 + test/controllers/sessions_controller_test.rb | 3 +- test/factories/app_installation.rb | 7 + .../github_app_authorization.json | 23 + .../github_webhooks/installation.json | 81 ++++ .../installation_repositories.json | 85 ++++ .../github_webhooks/issue_comment.json | 205 ++++++++ test/fixtures/github_webhooks/issues.json | 174 +++++++ test/fixtures/github_webhooks/label.json | 132 +++++ .../github_webhooks/pull_request.json | 453 ++++++++++++++++++ test/models/app_installation_test.rb | 28 ++ test/models/repository_test.rb | 10 + test/models/user_test.rb | 5 - 39 files changed, 1568 insertions(+), 38 deletions(-) create mode 100644 app/controllers/hooks_controller.rb create mode 100644 app/models/app_installation.rb create mode 100644 db/migrate/20180717112311_add_app_token_to_users.rb create mode 100644 db/migrate/20180806154015_create_app_installations.rb create mode 100644 db/migrate/20180817165201_add_app_installation_id_to_repositories.rb create mode 100644 test/controllers/hooks_controller_test.rb create mode 100644 test/factories/app_installation.rb create mode 100644 test/fixtures/github_webhooks/github_app_authorization.json create mode 100644 test/fixtures/github_webhooks/installation.json create mode 100644 test/fixtures/github_webhooks/installation_repositories.json create mode 100644 test/fixtures/github_webhooks/issue_comment.json create mode 100644 test/fixtures/github_webhooks/issues.json create mode 100644 test/fixtures/github_webhooks/label.json create mode 100644 test/fixtures/github_webhooks/pull_request.json create mode 100644 test/models/app_installation_test.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 06bd2499..e77978e8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,7 +3,7 @@ class ApplicationController < ActionController::Base API_HEADER = 'X-Octobox-API' protect_from_forgery with: :exception, unless: -> { octobox_api_request? } - helper_method :current_user, :logged_in?, :initial_sync? + helper_method :current_user, :logged_in?, :initial_sync?, :display_subject? before_action :authenticate_user! before_action :check_access_token_present @@ -24,6 +24,11 @@ class ApplicationController < ActionController::Base private + def display_subject? + return true if Octobox.config.fetch_subject + Octobox.config.github_app && current_user && current_user.app_token.present? + end + def add_user_info_to_bugsnag(notification) return unless logged_in? @@ -53,7 +58,7 @@ class ApplicationController < ActionController::Base def check_access_token_present if current_user && current_user.access_token.nil? cookies.delete(:user_id) - redirect_to root_url + redirect_to login_path end end diff --git a/app/controllers/hooks_controller.rb b/app/controllers/hooks_controller.rb new file mode 100644 index 00000000..0c372282 --- /dev/null +++ b/app/controllers/hooks_controller.rb @@ -0,0 +1,138 @@ +class HooksController < ApplicationController + skip_before_action :verify_authenticity_token + skip_before_action :authenticate_user! + before_action :authenticate_github_request! + + def create + case event_header + when 'issues', 'issue_comment' + remote_subject = payload.issue + subject = Subject.find_or_create_by(url: remote_subject.url) + subject.update({ + state: remote_subject.state, + author: remote_subject.user.login, + html_url: remote_subject.html_url, + created_at: remote_subject.created_at, + updated_at: remote_subject.updated_at + }) + subject.sync_involved_users + when 'pull_request' + remote_subject = payload.pull_request + subject = Subject.find_or_create_by(url: remote_subject.url) + subject.update({ + state: remote_subject.merged_at.present? ? 'merged' : remote_subject.state, + author: remote_subject.user.login, + html_url: remote_subject.html_url, + created_at: remote_subject.created_at, + updated_at: remote_subject.updated_at + }) + subject.sync_involved_users + when 'label' + if payload.action == 'edited' + repository = Repository.find_by_github_id(payload.repository.id) + return if repository.nil? + subjects = repository.subjects.label(payload.changes.name.from) + subjects.each do |subject| + n = subject.notifications.first + n.try(:send, :update_subject, true) + end + end + when 'installation' + case payload.action + when 'created' + app_installation = AppInstallation.create({ + github_id: payload.installation.id, + app_id: payload.installation.app_id, + account_login: payload.installation.account.login, + account_id: payload.installation.account.id, + account_type: payload.installation.account.type, + target_type: payload.installation.target_type, + target_id: payload.installation.target_id, + permission_pull_requests: payload.installation.permissions.pull_requests, + permission_issues: payload.installation.permissions.issues + }) + + payload.repositories.each do |remote_repository| + repository = Repository.find_or_create_by(github_id: remote_repository.id) + + repository.update_attributes({ + full_name: remote_repository.full_name, + private: remote_repository.private, + owner: remote_repository.full_name.split('/').first, + github_id: remote_repository.id, + last_synced_at: Time.current, + app_installation_id: app_installation.id + }) + + repository.notifications.each{|n| n.send :update_subject, true } + end + when 'deleted' + AppInstallation.find_by_github_id(payload.installation.id).try(:destroy) + end + + when 'installation_repositories' + app_installation = AppInstallation.find_by_github_id(payload.installation.id) + return unless app_installation.present? + payload.repositories_added.each do |remote_repository| + repository = app_installation.repositories.find_or_create_by(github_id: remote_repository.id) + + repository.update_attributes({ + full_name: remote_repository.full_name, + private: remote_repository.private, + owner: remote_repository.full_name.split('/').first, + github_id: remote_repository.id, + last_synced_at: Time.current, + app_installation_id: app_installation.id + }) + + repository.notifications.each{|n| n.send :update_subject, true } + end + + payload.repositories_removed.each do |remote_repository| + repository = app_installation.repositories.find_by_github_id(remote_repository.id) + next unless repository.present? + repository.subjects.each(&:destroy) + repository.destroy + end + when 'github_app_authorization' + user = User.find_by_github_id(payload.sender.id) + user.update_attributes(app_token: nil) if user.present? + end + + head :no_content + end + + private + + HMAC_DIGEST = OpenSSL::Digest.new('sha1') + + def authenticate_github_request! + secret = Rails.application.secrets.github_webhook_secret + + return unless secret.present? + + expected_signature = "sha1=#{OpenSSL::HMAC.hexdigest(HMAC_DIGEST, secret, request_body)}" + if signature_header != expected_signature + raise ActiveSupport::MessageVerifier::InvalidSignature + end + end + + def request_body + @request_body ||= ( + request.body.rewind + request.body.read + ) + end + + def payload + @payload ||= JSON.parse(request_body, object_class: OpenStruct) + end + + def signature_header + request.headers['X-Hub-Signature'] + end + + def event_header + request.headers['X-GitHub-Event'] + end +end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index ae28c625..d52f37bb 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -76,7 +76,7 @@ class NotificationsController < ApplicationController @reasons = scope.reorder(nil).distinct.group(:reason).count @unread_repositories = scope.reorder(nil).distinct.group(:repository_full_name).count - if Octobox.config.fetch_subject + if display_subject? @states = scope.reorder(nil).distinct.joins(:subject).group('subjects.state').count @unlabelled = scope.reorder(nil).unlabelled.count @bot_notifications = scope.reorder(nil).bot_author.count @@ -241,7 +241,7 @@ class NotificationsController < ApplicationController end def notifications_for_presentation - eager_load_relation = Octobox.config.fetch_subject ? [{subject: :labels}, :repository] : nil + eager_load_relation = display_subject? ? [{subject: :labels}, :repository] : nil scope = current_user.notifications.includes(eager_load_relation) if params[:q].present? diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 18b82f64..bd272374 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -9,7 +9,7 @@ class SessionsController < ApplicationController def create user = User.find_by_auth_hash(auth_hash) || User.new - user.assign_from_auth_hash(auth_hash) + user.assign_from_auth_hash(auth_hash, params[:provider]) cookies.permanent.signed[:user_id] = {value: user.id, httponly: true} diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 0defbbe3..cd498c89 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -226,8 +226,4 @@ module NotificationsHelper return true unless param == :repo params[:owner].blank? end - - def display_subject? - Octobox.config.fetch_subject - end end diff --git a/app/models/app_installation.rb b/app/models/app_installation.rb new file mode 100644 index 00000000..8b0b4d3f --- /dev/null +++ b/app/models/app_installation.rb @@ -0,0 +1,7 @@ +class AppInstallation < ApplicationRecord + has_many :repositories, dependent: :destroy + + validates :github_id, presence: true, uniqueness: true + validates :account_login, presence: true + validates :account_id, presence: true +end diff --git a/app/models/notification.rb b/app/models/notification.rb index 3baa98b0..808eea7b 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -72,7 +72,7 @@ class Notification < ApplicationRecord end def state - return unless Octobox.config.fetch_subject + return unless display_subject? subject.try(:state) end @@ -115,7 +115,7 @@ class Notification < ApplicationRecord end def expanded_subject_url - return subject_url unless Octobox.config.fetch_subject + return subject_url unless Octobox.config.subjects_enabled? subject.try(:html_url) || subject_url # Use the sync'd HTML URL if possible, else the API one end @@ -146,14 +146,22 @@ class Notification < ApplicationRecord update_repository end + def github_app_installed? + user.app_token.present? && repository.try(:github_app_installed?) + end + def subjectable? SUBJECTABLE_TYPES.include?(subject_type) end + def display_subject? + github_app_installed? || Octobox.config.fetch_subject + end + private def download_subject - user.github_client.get(subject_url) + user.subject_client.get(subject_url) # If permissions changed and the user hasn't accepted, we get a 401 # We may receive a 403 Forbidden or a 403 Not Available @@ -169,7 +177,7 @@ class Notification < ApplicationRecord def update_subject(force = false) return unless subjectable? - return unless Octobox.config.fetch_subject + return unless display_subject? # skip syncing if the notification was updated around the same time as subject return if !force && subject != nil && updated_at - subject.updated_at < 2.seconds @@ -219,7 +227,7 @@ class Notification < ApplicationRecord end def update_repository - return unless Octobox.config.fetch_subject + return unless display_subject? return if repository != nil && updated_at - repository.updated_at < 2.seconds remote_repository = download_repository diff --git a/app/models/repository.rb b/app/models/repository.rb index 263eaa93..7c47ce84 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,9 +1,14 @@ class Repository < ApplicationRecord has_many :notifications, foreign_key: :repository_full_name, primary_key: :full_name + belongs_to :app_installation validates :full_name, presence: true, uniqueness: true validates :github_id, uniqueness: true + def github_app_installed? + app_installation_id.present? + end + def subjects Subject.repository(full_name) end diff --git a/app/models/subject.rb b/app/models/subject.rb index 01f71271..961d2be7 100644 --- a/app/models/subject.rb +++ b/app/models/subject.rb @@ -9,6 +9,8 @@ class Subject < ApplicationRecord scope :label, ->(label_name) { joins(:labels).where(Label.arel_table[:name].matches(label_name)) } scope :repository, ->(full_name) { where(arel_table[:url].matches("%/repos/#{full_name}/%")) } + after_update :sync_involved_users + def author_url "#{Octobox.config.github_domain}#{author_url_path}" end @@ -37,6 +39,7 @@ class Subject < ApplicationRecord end def sync_involved_users + return unless Octobox.github_app? user_ids.each { |user_id| SyncNotificationsWorker.perform_async(user_id) } end diff --git a/app/models/user.rb b/app/models/user.rb index d979d4a3..37ca1fcd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,6 +2,7 @@ class User < ApplicationRecord attr_encrypted :access_token, key: Octobox.config.attr_encyrption_key attr_encrypted :personal_access_token, key: Octobox.config.attr_encyrption_key + attr_encrypted :app_token, key: Octobox.config.attr_encyrption_key has_secure_token :api_token has_many :notifications, dependent: :delete_all @@ -15,7 +16,7 @@ class User < ApplicationRecord }.freeze validates :github_id, presence: true, uniqueness: true - validates :encrypted_access_token, presence: true, uniqueness: true + validates :encrypted_access_token, uniqueness: true validates :github_login, presence: true validates :refresh_interval, numericality: { only_integer: true, @@ -45,11 +46,12 @@ class User < ApplicationRecord User.find_by(github_id: auth_hash['uid']) end - def assign_from_auth_hash(auth_hash) + def assign_from_auth_hash(auth_hash, app = 'github') + token_field = app == 'github' ? :access_token : :app_token github_attributes = { github_id: auth_hash['uid'], github_login: auth_hash['info']['nickname'], - access_token: auth_hash.dig('credentials', 'token') + token_field => auth_hash.dig('credentials', 'token') } update_attributes(github_attributes) @@ -73,6 +75,14 @@ class User < ApplicationRecord @github_client end + def subject_client + Octokit::Client.new(access_token: subject_token, auto_paginate: true) + end + + def subject_token + app_token || effective_access_token + end + def github_avatar_url "#{Octobox.config.github_domain}/#{github_login}.png" end diff --git a/app/services/download_service.rb b/app/services/download_service.rb index 03dbc0be..c50b6014 100644 --- a/app/services/download_service.rb +++ b/app/services/download_service.rb @@ -64,7 +64,7 @@ class DownloadService def process_notifications(notifications, unarchive: false) return if notifications.blank? - eager_load_relation = Octobox.config.fetch_subject ? :subject : nil + eager_load_relation = Octobox.config.subjects_enabled? ? :subject : nil existing_notifications = user.notifications.includes(eager_load_relation).where(github_id: notifications.map(&:id)) notifications.reject{|n| !unarchive && n.unread }.each do |notification| n = existing_notifications.find{|en| en.github_id == notification.id.to_i} diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 1fc579b4..7c1a43f6 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -21,13 +21,13 @@ <% end %> - + <%= octicon notification_icon(notification.subject_type, notification.state), :height => 16, title: notification_icon_title(notification.subject_type, notification.state), data: {toggle: 'tooltip'} %> <%= link_to notification.subject_title, notification.web_url, target: '_blank', rel: "noopener", class: 'link', onclick: "markRead(#{notification.id})" %> - <% if display_subject? %> + <% if notification.display_subject? %> <% if notification.subject %> <% notification.subject.labels.each do |label| %> <%= link_to emojify(label.name), root_path(filtered_params(label: label.name)), class: "badge d-sm", style: "background-color: ##{label.color}; color: #{label.text_color}" %> @@ -42,15 +42,15 @@ <% end %> - <% if display_subject? %> - + + <% if notification.display_subject? %> <% if notification.subject %> <%= link_to notification.subject.author, root_path(filtered_params(author: notification.subject.author)), class: 'text-muted', title: 'Author', data: {toggle: 'tooltip'} %> <% end %> - - <% end %> + <% end %> + <%= link_to notification.reason.humanize, root_path(filtered_params(reason: notification.reason)), class: "badge badge-#{reason_label(notification.reason)}" %> diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index fd3ca026..ce00612a 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -9,6 +9,12 @@ <%= octicon 'mark-github', height: 22 %> Sign in with GitHub <% end %> + <% if Octobox.github_app? && !Octobox.config.octobox_io %> + <%= link_to Octobox.config.app_install_url, class: 'btn btn-outline-dark btn-lg' do %> + <%= octicon 'briefcase', height: 22 %> + Install the GitHub App + <% end %> + <% end %>

Star diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 1210ad35..03ee84f7 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -108,6 +108,13 @@ <%= octicon 'tools' %>  Manage Oauth Access on GitHub <% end %> + + <% if Octobox.github_app? && current_user.app_token.present? %> + <%= link_to "#{Octobox.config.github_domain}/settings/connections/applications/#{Rails.application.secrets.github_app_client_id}", class: 'btn btn-outline-dark' do %> + <%= octicon 'octoface' %>  + Manage App Access on GitHub + <% end %> + <% end %> diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 10dd9c83..706cebc4 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,4 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password, :personal_access_token, :access_token] +Rails.application.config.filter_parameters += [:password, :personal_access_token, :access_token, :app_token] diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index 16a68f16..8e7b587d 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -1,4 +1,11 @@ # frozen_string_literal: true +module OmniAuth + module Strategies + class GithubApp < GitHub + end + end +end + Rails.application.config.middleware.use OmniAuth::Builder do site = Octobox.config.github_api_prefix authorize_url = "#{Octobox.config.github_domain}/login/oauth/authorize" @@ -9,4 +16,11 @@ Rails.application.config.middleware.use OmniAuth::Builder do Rails.application.secrets.github_client_secret, client_options: { site: site, authorize_url: authorize_url, token_url: token_url }, scope: Octobox.config.scopes + + if Octobox.github_app? + provider :github_app, + Rails.application.secrets.github_app_client_id, + Rails.application.secrets.github_app_client_secret, + client_options: { site: site, authorize_url: authorize_url, token_url: token_url } + end end diff --git a/config/routes.rb b/config/routes.rb index 709b0b79..2251471d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -42,6 +42,8 @@ Rails.application.routes.draw do end end + post '/hooks/github', to: 'hooks#create' + if Octobox.config.octobox_io get '/privacy', to: 'pages#privacy' get '/terms', to: 'pages#terms' diff --git a/config/secrets.yml b/config/secrets.yml index 963d98e4..cab8da6b 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,11 +11,15 @@ # if you're sharing your code publicly. default: &default - github_client_id: <%= ENV['GITHUB_CLIENT_ID'] %> - github_client_secret: <%= ENV['GITHUB_CLIENT_SECRET'] %> - google_analytics_id: <%= ENV['GA_ANALYTICS_ID'] %> - bugsnag_api_key: <%= ENV['BUGSNAG_API_KEY'] %> - bugsnag_js_api_key: <%= ENV['BUGSNAG_JS_API_KEY'] %> + github_client_id: <%= ENV['GITHUB_CLIENT_ID'] %> + github_client_secret: <%= ENV['GITHUB_CLIENT_SECRET'] %> + google_analytics_id: <%= ENV['GA_ANALYTICS_ID'] %> + bugsnag_api_key: <%= ENV['BUGSNAG_API_KEY'] %> + bugsnag_js_api_key: <%= ENV['BUGSNAG_JS_API_KEY'] %> + github_webhook_secret: <%= ENV['GITHUB_WEBHOOK_SECRET'] %> + github_app_id: <%= ENV['GITHUB_APP_ID'] %> + github_app_client_id: <%= ENV['GITHUB_APP_CLIENT_ID'] %> + github_app_client_secret: <%= ENV['GITHUB_APP_CLIENT_SECRET'] %> development: <<: *default diff --git a/db/migrate/20180717112311_add_app_token_to_users.rb b/db/migrate/20180717112311_add_app_token_to_users.rb new file mode 100644 index 00000000..cc0da1a4 --- /dev/null +++ b/db/migrate/20180717112311_add_app_token_to_users.rb @@ -0,0 +1,6 @@ +class AddAppTokenToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :encrypted_app_token, :string + add_column :users, :encrypted_app_token_iv, :string + end +end diff --git a/db/migrate/20180806154015_create_app_installations.rb b/db/migrate/20180806154015_create_app_installations.rb new file mode 100644 index 00000000..9e21e934 --- /dev/null +++ b/db/migrate/20180806154015_create_app_installations.rb @@ -0,0 +1,17 @@ +class CreateAppInstallations < ActiveRecord::Migration[5.2] + def change + create_table :app_installations do |t| + t.integer :github_id + t.integer :app_id + t.string :account_login + t.integer :account_id + t.string :account_type + t.string :target_type + t.integer :target_id + t.string :permission_pull_requests + t.string :permission_issues + + t.timestamps + end + end +end diff --git a/db/migrate/20180817165201_add_app_installation_id_to_repositories.rb b/db/migrate/20180817165201_add_app_installation_id_to_repositories.rb new file mode 100644 index 00000000..823293fc --- /dev/null +++ b/db/migrate/20180817165201_add_app_installation_id_to_repositories.rb @@ -0,0 +1,5 @@ +class AddAppInstallationIdToRepositories < ActiveRecord::Migration[5.2] + def change + add_column :repositories, :app_installation_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index e5d2a27b..550e5a93 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -16,6 +16,20 @@ ActiveRecord::Schema.define(version: 2018_08_17_172203) do enable_extension "pg_stat_statements" enable_extension "plpgsql" + create_table "app_installations", force: :cascade do |t| + t.integer "github_id" + t.integer "app_id" + t.string "account_login" + t.integer "account_id" + t.string "account_type" + t.string "target_type" + t.integer "target_id" + t.string "permission_pull_requests" + t.string "permission_issues" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "labels", force: :cascade do |t| t.string "name" t.string "color" @@ -58,6 +72,7 @@ ActiveRecord::Schema.define(version: 2018_08_17_172203) do t.datetime "last_synced_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "app_installation_id" t.index ["full_name"], name: "index_repositories_on_full_name", unique: true end @@ -85,6 +100,8 @@ ActiveRecord::Schema.define(version: 2018_08_17_172203) do t.string "encrypted_access_token_iv" t.string "encrypted_personal_access_token" t.string "encrypted_personal_access_token_iv" + t.string "encrypted_app_token" + t.string "encrypted_app_token_iv" t.index ["api_token"], name: "index_users_on_api_token", unique: true t.index ["github_id"], name: "index_users_on_github_id", unique: true end diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index c99cab21..2ce4b924 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -29,6 +29,7 @@ in your GitHub settings for Octobox to work. * [Downloading subjects](#downloading-subjects) * [API Documentation](#api-documentation) * [Google Analytics](#google-analytics) +* [Running Octobox as a GitHub App](#api-documentation) # Installation ## Database Selection @@ -359,3 +360,5 @@ This is included by default in the container build using `Dockerfile`. To includ To enable Google analytics tracking set the following environment variable: GA_ANALYTICS_ID=UA-XXXXXX-XX + +## Running Octobox as a GitHub App diff --git a/lib/octobox.rb b/lib/octobox.rb index 6f4553bb..c28a7cad 100644 --- a/lib/octobox.rb +++ b/lib/octobox.rb @@ -21,5 +21,9 @@ module Octobox def restricted_access_enabled? config.restricted_access_enabled end + + def github_app? + config.github_app + end end end diff --git a/lib/octobox/configurator.rb b/lib/octobox/configurator.rb index c652818a..26d8e9e2 100644 --- a/lib/octobox/configurator.rb +++ b/lib/octobox/configurator.rb @@ -32,17 +32,26 @@ module Octobox def scopes default_scopes = 'notifications' - default_scopes += ', read:org' if Octobox.restricted_access_enabled? - default_scopes += ', repo' if fetch_subject + default_scopes += ', read:org' if !github_app && Octobox.restricted_access_enabled? + default_scopes += ', repo' if !github_app && fetch_subject ENV.fetch('GITHUB_SCOPE', default_scopes) end + def github_app + @github_app || ENV['GITHUB_APP_ID'].present? + end + attr_writer :github_app + def fetch_subject @fetch_subject || (ENV['FETCH_SUBJECT'].try(:downcase) == "true") end attr_writer :fetch_subject + def subjects_enabled? + github_app || fetch_subject + end + def personal_access_tokens_enabled @personal_access_tokens_enabled || ENV['PERSONAL_ACCESS_TOKENS_ENABLED'].present? end @@ -108,6 +117,21 @@ module Octobox end attr_writer :source_repo + def app_install_url + "#{app_url}/installations/new" + end + attr_writer :app_install_url + + def app_url + "#{github_domain}/apps/#{app_slug}" + end + attr_writer :app_url + + def app_slug + ENV['GITHUB_APP_SLUG'] + end + attr_writer :app_slug + def octobox_io @octobox_io || ENV['OCTOBOX_IO'].present? end diff --git a/test/controllers/hooks_controller_test.rb b/test/controllers/hooks_controller_test.rb new file mode 100644 index 00000000..307c88bb --- /dev/null +++ b/test/controllers/hooks_controller_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true +require 'test_helper' + +class HooksControllerTest < ActionController::TestCase + test 'labels webhook payload' do + @request.headers['X-GitHub-Event'] = 'label' + fixture = 'label.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'github_app_authorization webhook payload' do + @request.headers['X-GitHub-Event'] = 'github_app_authorization' + fixture = 'github_app_authorization.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'installation_repositories webhook payload' do + @request.headers['X-GitHub-Event'] = 'installation_repositories' + fixture = 'installation_repositories.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'installation webhook payload' do + @request.headers['X-GitHub-Event'] = 'installation' + fixture = 'installation.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'issues webhook payload' do + @request.headers['X-GitHub-Event'] = 'issues' + fixture = 'issues.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'pull_request webhook payload' do + @request.headers['X-GitHub-Event'] = 'pull_request' + fixture = 'pull_request.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end + + test 'issue_comment webhook payload' do + @request.headers['X-GitHub-Event'] = 'issue_comment' + fixture = 'issue_comment.json' + post :create, body: File.read("#{Rails.root}/test/fixtures/github_webhooks/#{fixture}") + assert_response :success + end +end diff --git a/test/controllers/notifications_controller_test.rb b/test/controllers/notifications_controller_test.rb index cb644b69..ce3d4376 100644 --- a/test/controllers/notifications_controller_test.rb +++ b/test/controllers/notifications_controller_test.rb @@ -3,6 +3,7 @@ require 'test_helper' class NotificationsControllerTest < ActionDispatch::IntegrationTest setup do + Octobox.config.stubs(:github_app).returns(false) stub_fetch_subject_enabled(value: false) stub_notifications_request stub_repository_request @@ -112,6 +113,7 @@ class NotificationsControllerTest < ActionDispatch::IntegrationTest test 'renders notifications filtered by label' do stub_fetch_subject_enabled + Octobox.config.stubs(:github_app).returns(false) sign_in_as(@user) get '/' diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index cc03002b..2945e8a3 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -3,6 +3,7 @@ require 'test_helper' class SessionsControllerTest < ActionDispatch::IntegrationTest setup do + Octobox.config.stubs(:github_app).returns(false) @notifications_request = stub_notifications_request(body: '[]') @user = create(:user) end @@ -13,7 +14,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest @user.save(validate: false) # Requires access token get '/settings' - assert_redirected_to root_path + assert_redirected_to login_path end test 'GET #new redirects to /auth/github' do diff --git a/test/factories/app_installation.rb b/test/factories/app_installation.rb new file mode 100644 index 00000000..3d32c7da --- /dev/null +++ b/test/factories/app_installation.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :app_installation do + sequence(:github_id, 1000000){|n| n} + account_login { 'andrew' } + account_id { 1060 } + end +end diff --git a/test/fixtures/github_webhooks/github_app_authorization.json b/test/fixtures/github_webhooks/github_app_authorization.json new file mode 100644 index 00000000..e9a4b7c9 --- /dev/null +++ b/test/fixtures/github_webhooks/github_app_authorization.json @@ -0,0 +1,23 @@ +{ + "action": "revoked", + "sender": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/test/fixtures/github_webhooks/installation.json b/test/fixtures/github_webhooks/installation.json new file mode 100644 index 00000000..f03e0a44 --- /dev/null +++ b/test/fixtures/github_webhooks/installation.json @@ -0,0 +1,81 @@ +{ + "action": "created", + "installation": { + "id": 293804, + "account": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "repository_selection": "selected", + "access_tokens_url": "https://api.github.com/installations/293804/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/settings/installations/293804", + "app_id": 14761, + "target_id": 1060, + "target_type": "User", + "permissions": { + "issues": "read", + "metadata": "read", + "pull_requests": "read" + }, + "events": [ + "issues", + "issue_comment", + "label", + "milestone", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "repository", + "watch" + ], + "created_at": 1534869839, + "updated_at": 1534869839, + "single_file_name": null + }, + "repositories": [ + { + "id": 139431141, + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "private": true + } + ], + "sender": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/test/fixtures/github_webhooks/installation_repositories.json b/test/fixtures/github_webhooks/installation_repositories.json new file mode 100644 index 00000000..8f68b435 --- /dev/null +++ b/test/fixtures/github_webhooks/installation_repositories.json @@ -0,0 +1,85 @@ +{ + "action": "added", + "installation": { + "id": 293804, + "account": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "repository_selection": "selected", + "access_tokens_url": "https://api.github.com/installations/293804/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/settings/installations/293804", + "app_id": 14761, + "target_id": 1060, + "target_type": "User", + "permissions": { + "pull_requests": "read", + "issues": "read", + "metadata": "read" + }, + "events": [ + "issues", + "issue_comment", + "label", + "milestone", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "repository", + "watch" + ], + "created_at": 1534869839, + "updated_at": 1534869839, + "single_file_name": null + }, + "repository_selection": "selected", + "repositories_added": [ + { + "id": 96149167, + "name": "chardstorage", + "full_name": "andrew/chardstorage", + "private": true + } + ], + "repositories_removed": [ + + ], + "sender": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/test/fixtures/github_webhooks/issue_comment.json b/test/fixtures/github_webhooks/issue_comment.json new file mode 100644 index 00000000..9250d123 --- /dev/null +++ b/test/fixtures/github_webhooks/issue_comment.json @@ -0,0 +1,205 @@ +{ + "action": "created", + "issue": { + "url": "https://api.github.com/repos/andrew/no-rabbits/issues/5", + "repository_url": "https://api.github.com/repos/andrew/no-rabbits", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/labels{/name}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/comments", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/events", + "html_url": "https://github.com/andrew/no-rabbits/issues/5", + "id": 350082489, + "node_id": "MDU6SXNzdWUzNTAwODI0ODk=", + "number": 5, + "title": "I love carrots", + "user": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 1023072455, + "node_id": "MDU6TGFiZWwxMDIzMDcyNDU1", + "url": "https://api.github.com/repos/andrew/no-rabbits/labels/%F0%9F%A5%95%F0%9F%A5%95carrot", + "name": "πŸ₯•πŸ₯•carrot", + "color": "080bcc", + "default": false + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 0, + "created_at": "2018-08-13T15:36:47Z", + "updated_at": "2018-08-28T11:06:13Z", + "closed_at": null, + "author_association": "COLLABORATOR", + "body": "πŸ₯• πŸ₯• πŸ₯• πŸ₯• " + }, + "comment": { + "url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments/416544845", + "html_url": "https://github.com/andrew/no-rabbits/issues/5#issuecomment-416544845", + "issue_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5", + "id": 416544845, + "node_id": "MDEyOklzc3VlQ29tbWVudDQxNjU0NDg0NQ==", + "user": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2018-08-28T11:06:13Z", + "updated_at": "2018-08-28T11:06:13Z", + "author_association": "COLLABORATOR", + "body": "issue!" + }, + "repository": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 3, + "license": null, + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 293804 + } +} diff --git a/test/fixtures/github_webhooks/issues.json b/test/fixtures/github_webhooks/issues.json new file mode 100644 index 00000000..47307810 --- /dev/null +++ b/test/fixtures/github_webhooks/issues.json @@ -0,0 +1,174 @@ +{ + "action": "reopened", + "issue": { + "url": "https://api.github.com/repos/andrew/no-rabbits/issues/5", + "repository_url": "https://api.github.com/repos/andrew/no-rabbits", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/labels{/name}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/comments", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/5/events", + "html_url": "https://github.com/andrew/no-rabbits/issues/5", + "id": 350082489, + "node_id": "MDU6SXNzdWUzNTAwODI0ODk=", + "number": 5, + "title": "I love carrots", + "user": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 1023072455, + "node_id": "MDU6TGFiZWwxMDIzMDcyNDU1", + "url": "https://api.github.com/repos/andrew/no-rabbits/labels/%F0%9F%A5%95:carrot:", + "name": "πŸ₯•:carrot:", + "color": "080bcc", + "default": false + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 0, + "created_at": "2018-08-13T15:36:47Z", + "updated_at": "2018-08-22T10:33:07Z", + "closed_at": null, + "author_association": "COLLABORATOR", + "body": "πŸ₯• πŸ₯• πŸ₯• πŸ₯• " + }, + "repository": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 3, + "license": null, + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 293804 + } +} diff --git a/test/fixtures/github_webhooks/label.json b/test/fixtures/github_webhooks/label.json new file mode 100644 index 00000000..c27f615f --- /dev/null +++ b/test/fixtures/github_webhooks/label.json @@ -0,0 +1,132 @@ +{ + "action": "edited", + "label": { + "id": 1023072455, + "node_id": "MDU6TGFiZWwxMDIzMDcyNDU1", + "url": "https://api.github.com/repos/andrew/no-rabbits/labels/%F0%9F%A5%95%F0%9F%A5%95:carrot:", + "name": "πŸ₯•πŸ₯•:carrot:", + "color": "080bcc", + "default": false + }, + "changes": { + "name": { + "from": "πŸ₯•:carrot:" + } + }, + "repository": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 3, + "license": null, + "forks": 0, + "open_issues": 3, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 293804 + } +} diff --git a/test/fixtures/github_webhooks/pull_request.json b/test/fixtures/github_webhooks/pull_request.json new file mode 100644 index 00000000..86e1dca8 --- /dev/null +++ b/test/fixtures/github_webhooks/pull_request.json @@ -0,0 +1,453 @@ +{ + "action": "reopened", + "number": 3, + "pull_request": { + "url": "https://api.github.com/repos/andrew/no-rabbits/pulls/3", + "id": 204166844, + "node_id": "MDExOlB1bGxSZXF1ZXN0MjA0MTY2ODQ0", + "html_url": "https://github.com/andrew/no-rabbits/pull/3", + "diff_url": "https://github.com/andrew/no-rabbits/pull/3.diff", + "patch_url": "https://github.com/andrew/no-rabbits/pull/3.patch", + "issue_url": "https://api.github.com/repos/andrew/no-rabbits/issues/3", + "number": 3, + "state": "open", + "locked": false, + "title": "Update README.md", + "user": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "body": "", + "created_at": "2018-07-26T14:45:31Z", + "updated_at": "2018-08-22T10:32:06Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "208f8c09b3c88b2c3caad3d587325deb3172d6ac", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + + ], + "milestone": null, + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/pulls/3/commits", + "review_comments_url": "https://api.github.com/repos/andrew/no-rabbits/pulls/3/comments", + "review_comment_url": "https://api.github.com/repos/andrew/no-rabbits/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/issues/3/comments", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/5c1875a4873e5d12219a343cc7e075e87e5f420f", + "head": { + "label": "andrew:bertie-patch-1", + "ref": "bertie-patch-1", + "sha": "5c1875a4873e5d12219a343cc7e075e87e5f420f", + "user": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "andrew:master", + "ref": "master", + "sha": "3632305028b86b5764c8b8398aefc1473525bdbc", + "user": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/andrew/no-rabbits/pulls/3" + }, + "html": { + "href": "https://github.com/andrew/no-rabbits/pull/3" + }, + "issue": { + "href": "https://api.github.com/repos/andrew/no-rabbits/issues/3" + }, + "comments": { + "href": "https://api.github.com/repos/andrew/no-rabbits/issues/3/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/andrew/no-rabbits/pulls/3/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/andrew/no-rabbits/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/andrew/no-rabbits/pulls/3/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/andrew/no-rabbits/statuses/5c1875a4873e5d12219a343cc7e075e87e5f420f" + } + }, + "author_association": "COLLABORATOR", + "merged": false, + "mergeable": null, + "rebaseable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 2, + "deletions": 0, + "changed_files": 1 + }, + "repository": { + "id": 139431141, + "node_id": "MDEwOlJlcG9zaXRvcnkxMzk0MzExNDE=", + "name": "no-rabbits", + "full_name": "andrew/no-rabbits", + "owner": { + "login": "andrew", + "id": 1060, + "node_id": "MDQ6VXNlcjEwNjA=", + "avatar_url": "https://avatars2.githubusercontent.com/u/1060?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/andrew", + "html_url": "https://github.com/andrew", + "followers_url": "https://api.github.com/users/andrew/followers", + "following_url": "https://api.github.com/users/andrew/following{/other_user}", + "gists_url": "https://api.github.com/users/andrew/gists{/gist_id}", + "starred_url": "https://api.github.com/users/andrew/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/andrew/subscriptions", + "organizations_url": "https://api.github.com/users/andrew/orgs", + "repos_url": "https://api.github.com/users/andrew/repos", + "events_url": "https://api.github.com/users/andrew/events{/privacy}", + "received_events_url": "https://api.github.com/users/andrew/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/andrew/no-rabbits", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/andrew/no-rabbits", + "forks_url": "https://api.github.com/repos/andrew/no-rabbits/forks", + "keys_url": "https://api.github.com/repos/andrew/no-rabbits/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/andrew/no-rabbits/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/andrew/no-rabbits/teams", + "hooks_url": "https://api.github.com/repos/andrew/no-rabbits/hooks", + "issue_events_url": "https://api.github.com/repos/andrew/no-rabbits/issues/events{/number}", + "events_url": "https://api.github.com/repos/andrew/no-rabbits/events", + "assignees_url": "https://api.github.com/repos/andrew/no-rabbits/assignees{/user}", + "branches_url": "https://api.github.com/repos/andrew/no-rabbits/branches{/branch}", + "tags_url": "https://api.github.com/repos/andrew/no-rabbits/tags", + "blobs_url": "https://api.github.com/repos/andrew/no-rabbits/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/andrew/no-rabbits/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/andrew/no-rabbits/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/andrew/no-rabbits/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/andrew/no-rabbits/statuses/{sha}", + "languages_url": "https://api.github.com/repos/andrew/no-rabbits/languages", + "stargazers_url": "https://api.github.com/repos/andrew/no-rabbits/stargazers", + "contributors_url": "https://api.github.com/repos/andrew/no-rabbits/contributors", + "subscribers_url": "https://api.github.com/repos/andrew/no-rabbits/subscribers", + "subscription_url": "https://api.github.com/repos/andrew/no-rabbits/subscription", + "commits_url": "https://api.github.com/repos/andrew/no-rabbits/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/andrew/no-rabbits/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/andrew/no-rabbits/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/andrew/no-rabbits/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/andrew/no-rabbits/contents/{+path}", + "compare_url": "https://api.github.com/repos/andrew/no-rabbits/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/andrew/no-rabbits/merges", + "archive_url": "https://api.github.com/repos/andrew/no-rabbits/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/andrew/no-rabbits/downloads", + "issues_url": "https://api.github.com/repos/andrew/no-rabbits/issues{/number}", + "pulls_url": "https://api.github.com/repos/andrew/no-rabbits/pulls{/number}", + "milestones_url": "https://api.github.com/repos/andrew/no-rabbits/milestones{/number}", + "notifications_url": "https://api.github.com/repos/andrew/no-rabbits/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/andrew/no-rabbits/labels{/name}", + "releases_url": "https://api.github.com/repos/andrew/no-rabbits/releases{/id}", + "deployments_url": "https://api.github.com/repos/andrew/no-rabbits/deployments", + "created_at": "2018-07-02T10:57:41Z", + "updated_at": "2018-08-02T09:03:24Z", + "pushed_at": "2018-08-02T09:03:22Z", + "git_url": "git://github.com/andrew/no-rabbits.git", + "ssh_url": "git@github.com:andrew/no-rabbits.git", + "clone_url": "https://github.com/andrew/no-rabbits.git", + "svn_url": "https://github.com/andrew/no-rabbits", + "homepage": null, + "size": 3, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "bertie", + "id": 7251939, + "node_id": "MDQ6VXNlcjcyNTE5Mzk=", + "avatar_url": "https://avatars0.githubusercontent.com/u/7251939?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bertie", + "html_url": "https://github.com/bertie", + "followers_url": "https://api.github.com/users/bertie/followers", + "following_url": "https://api.github.com/users/bertie/following{/other_user}", + "gists_url": "https://api.github.com/users/bertie/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bertie/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bertie/subscriptions", + "organizations_url": "https://api.github.com/users/bertie/orgs", + "repos_url": "https://api.github.com/users/bertie/repos", + "events_url": "https://api.github.com/users/bertie/events{/privacy}", + "received_events_url": "https://api.github.com/users/bertie/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 293804 + } +} diff --git a/test/models/app_installation_test.rb b/test/models/app_installation_test.rb new file mode 100644 index 00000000..75b5758e --- /dev/null +++ b/test/models/app_installation_test.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +require 'test_helper' + +class AppInstallationTest < ActiveSupport::TestCase + setup do + @app_installation = create(:app_installation) + end + + test 'must have a github id' do + @app_installation.github_id = nil + refute @app_installation.valid? + end + + test 'must have a unique github_id' do + app_installation = build(:app_installation, github_id: @app_installation.github_id) + refute app_installation.valid? + end + + test 'must have an account login' do + @app_installation.account_login = nil + refute @app_installation.valid? + end + + test 'must have a account id' do + @app_installation.account_id = nil + refute @app_installation.valid? + end +end diff --git a/test/models/repository_test.rb b/test/models/repository_test.rb index 9bf3b448..eb0d9b78 100644 --- a/test/models/repository_test.rb +++ b/test/models/repository_test.rb @@ -20,6 +20,16 @@ class RepositoryTest < ActiveSupport::TestCase repository = build(:repository, full_name: @repository.full_name) refute repository.valid? end + + test 'github_app_installed if app_installation_id present' do + @repository.app_installation_id = 1 + assert @repository.github_app_installed? + end + + test 'github_app_installed if app_installation_id missing' do + @repository.app_installation_id = nil + refute @repository.github_app_installed? + end test 'finds subjects by full_name' do subject = create(:subject, url: "https://api.github.com/repos/#{@repository.full_name}/issues/1") diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 83701163..3ba43285 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -23,11 +23,6 @@ class UserTest < ActiveSupport::TestCase refute user.valid? end - test 'must have an access_token' do - @user.access_token = nil - refute @user.valid? - end - test 'must have a unique access_token' do user = User.create(github_id: 42, access_token: @user.access_token) refute user.valid?