From 5febf67fa295e7eddb535c8bca51c4c0575849c2 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Wed, 6 Nov 2019 16:50:52 -0500 Subject: [PATCH 01/21] Add GithubErrorHandler --- app/services/github_error_handler.rb | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/services/github_error_handler.rb diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb new file mode 100644 index 000000000..f3d7daa5d --- /dev/null +++ b/app/services/github_error_handler.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module GithubErrorHandler + module_function + + HANDLED_ERRORS = [ + Octokit::AccountSuspended, + GithubPullRequestService::UserNotFoundOnGithubError + ].freeze + + def with_error_handling + yield + rescue *HANDLED_ERRORS => e + process_github_error(e) + end + + def process_github_error(error) + case error + when Octokit::AccountSuspended + process_user_missing_error(error) + when GithubPullRequestService::UserNotFoundOnGithubError + process_user_missing_error(error) + else + raise error + end + end + + def process_user_missing_error(error) + return unless (user_stat = UserStat.where(user_id: error.get_user.id).first) + + user_stat.destroy + end +end From 9b6dcf38aa7f547f72fdb102c415ec134a576598 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Wed, 6 Nov 2019 16:54:58 -0500 Subject: [PATCH 02/21] Use GithubErrorHandler in ImportPrMetadataService --- app/services/import_pr_metadata_service.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/services/import_pr_metadata_service.rb b/app/services/import_pr_metadata_service.rb index efa5f8fc5..723b636fa 100644 --- a/app/services/import_pr_metadata_service.rb +++ b/app/services/import_pr_metadata_service.rb @@ -6,17 +6,12 @@ module ImportPrMetadataService def call(user) pr_service = PullRequestService.new(user, randomize_token: true) - begin + GithubErrorHandler.with_error_handling do pr_data = pr_service.all - rescue GithubPullRequestService::UserNotFoundOnGithubError - user_stat = UserStat.where(user_id: user.id).first - user_stat.destroy - return - end - - pr_data.map do |pr| - ImportOnePrMetadataJob.perform_async(pr.url) + pr_data.map do |pr| + ImportOnePrMetadataJob.perform_async(pr.url) + end end end end From b5b66d00d36682741b212f739f373fb74ecd454d Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 10:07:43 -0500 Subject: [PATCH 03/21] Use GithubErrorHandler in ImportReposMetadataService --- app/services/import_repos_metadata_service.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/services/import_repos_metadata_service.rb b/app/services/import_repos_metadata_service.rb index c7c524a40..3484437f5 100644 --- a/app/services/import_repos_metadata_service.rb +++ b/app/services/import_repos_metadata_service.rb @@ -6,18 +6,13 @@ module ImportReposMetadataService def call(user) pr_service = PullRequestService.new(user, randomize_token: true) - begin + GithubErrorHandler.with_error_handling do pr_data = pr_service.all - rescue GithubPullRequestService::UserNotFoundOnGithubError - user_stat = UserStat.where(user_id: user.id).first - user_stat.destroy - return - end - - pr_data.map do |pr| - repo_id = pr.github_pull_request.repo_id - ImportRepoMetadataJob.perform_async(repo_id) + pr_data.map do |pr| + repo_id = pr.github_pull_request.repo_id + ImportRepoMetadataJob.perform_async(repo_id) + end end end end From b6e46022224610db7b6891404d951112349358c9 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 10:39:12 -0500 Subject: [PATCH 04/21] Add user to UserNotFoundOnGithubError --- app/services/github_pull_request_service.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/services/github_pull_request_service.rb b/app/services/github_pull_request_service.rb index bf4e7d913..5bb1a633f 100644 --- a/app/services/github_pull_request_service.rb +++ b/app/services/github_pull_request_service.rb @@ -3,7 +3,14 @@ # Fetches Pull Requests for a user from the GitHub API # Returns an array of GraphqlPullRequest instances class GithubPullRequestService - class UserNotFoundOnGithubError < StandardError; end + class UserNotFoundOnGithubError < StandardError + attr_reader :user + + def initialize(user) + @user = user + end + end + attr_reader :user PULL_REQUEST_QUERY = <<~GRAPHQL @@ -48,7 +55,7 @@ def pull_requests if response['errors'] if response['errors'][0]['type'] == 'NOT_FOUND' - raise UserNotFoundOnGithubError + raise UserNotFoundOnGithubError.new(user) end end From c538aae321e5e397133f14fa0ff2b0af7aae72c6 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 10:43:33 -0500 Subject: [PATCH 05/21] Add last_error to User --- db/migrate/20191107151321_add_last_error_to_user.rb | 5 +++++ db/schema.rb | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20191107151321_add_last_error_to_user.rb diff --git a/db/migrate/20191107151321_add_last_error_to_user.rb b/db/migrate/20191107151321_add_last_error_to_user.rb new file mode 100644 index 000000000..269eaecae --- /dev/null +++ b/db/migrate/20191107151321_add_last_error_to_user.rb @@ -0,0 +1,5 @@ +class AddLastErrorToUser < ActiveRecord::Migration[5.2] + def change + add_column :users, :last_error, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 26a9c8975..00e3f76b5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,19 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_10_28_180239) do +ActiveRecord::Schema.define(version: 2019_11_07_151321) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "daily_pr_counts", force: :cascade do |t| + t.date "date", null: false + t.integer "count", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["date"], name: "index_daily_pr_counts_on_date", unique: true + end + create_table "issues", force: :cascade do |t| t.string "title", limit: 191, null: false t.bigint "number", null: false @@ -104,6 +112,7 @@ t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "deleted" end create_table "users", force: :cascade do |t| @@ -119,6 +128,7 @@ t.string "state" t.datetime "waiting_since" t.jsonb "receipt" + t.string "last_error" end end From 533a412e8b4bc3f0647e1417beada6057dad8f43 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 11:27:29 -0500 Subject: [PATCH 06/21] Pass user into GithubErrorHadler --- app/services/github_error_handler.rb | 21 +++++++++++---------- app/services/github_pull_request_service.rb | 10 ++-------- app/services/import_pr_metadata_service.rb | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb index f3d7daa5d..e109cf7f1 100644 --- a/app/services/github_error_handler.rb +++ b/app/services/github_error_handler.rb @@ -8,25 +8,26 @@ module GithubErrorHandler GithubPullRequestService::UserNotFoundOnGithubError ].freeze - def with_error_handling + def with_error_handling(user) yield rescue *HANDLED_ERRORS => e - process_github_error(e) + process_github_error(e, user) end - def process_github_error(error) - case error - when Octokit::AccountSuspended - process_user_missing_error(error) - when GithubPullRequestService::UserNotFoundOnGithubError - process_user_missing_error(error) + def process_github_error(error, user) + user.update_attribute(:last_error, error.class) + case error.class.to_s + when 'Octokit::AccountSuspended' + process_user_missing_error(error, user) + when 'GithubPullRequestService::UserNotFoundOnGithubError' + process_user_missing_error(error, user) else raise error end end - def process_user_missing_error(error) - return unless (user_stat = UserStat.where(user_id: error.get_user.id).first) + def process_user_missing_error(_error, user) + return unless (user_stat = UserStat.where(user_id: user.id).first) user_stat.destroy end diff --git a/app/services/github_pull_request_service.rb b/app/services/github_pull_request_service.rb index 5bb1a633f..38e688ffc 100644 --- a/app/services/github_pull_request_service.rb +++ b/app/services/github_pull_request_service.rb @@ -3,13 +3,7 @@ # Fetches Pull Requests for a user from the GitHub API # Returns an array of GraphqlPullRequest instances class GithubPullRequestService - class UserNotFoundOnGithubError < StandardError - attr_reader :user - - def initialize(user) - @user = user - end - end + class UserNotFoundOnGithubError < StandardError; end attr_reader :user @@ -55,7 +49,7 @@ def pull_requests if response['errors'] if response['errors'][0]['type'] == 'NOT_FOUND' - raise UserNotFoundOnGithubError.new(user) + raise UserNotFoundOnGithubError end end diff --git a/app/services/import_pr_metadata_service.rb b/app/services/import_pr_metadata_service.rb index 723b636fa..51c82bd12 100644 --- a/app/services/import_pr_metadata_service.rb +++ b/app/services/import_pr_metadata_service.rb @@ -6,7 +6,7 @@ module ImportPrMetadataService def call(user) pr_service = PullRequestService.new(user, randomize_token: true) - GithubErrorHandler.with_error_handling do + GithubErrorHandler.with_error_handling(user) do pr_data = pr_service.all pr_data.map do |pr| From 8c6c4e438d2779114eb0bc2eec629ffa4be1b41c Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 16:57:46 -0500 Subject: [PATCH 07/21] lint --- app/services/github_error_handler.rb | 2 ++ db/migrate/20191107151321_add_last_error_to_user.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb index e109cf7f1..42ac502e1 100644 --- a/app/services/github_error_handler.rb +++ b/app/services/github_error_handler.rb @@ -15,7 +15,9 @@ def with_error_handling(user) end def process_github_error(error, user) + # rubocop:disable Rails/SkipsModelValidations user.update_attribute(:last_error, error.class) + # rubocop:enable Rails/SkipsModelValidations case error.class.to_s when 'Octokit::AccountSuspended' process_user_missing_error(error, user) diff --git a/db/migrate/20191107151321_add_last_error_to_user.rb b/db/migrate/20191107151321_add_last_error_to_user.rb index 269eaecae..582820ba4 100644 --- a/db/migrate/20191107151321_add_last_error_to_user.rb +++ b/db/migrate/20191107151321_add_last_error_to_user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AddLastErrorToUser < ActiveRecord::Migration[5.2] def change add_column :users, :last_error, :string From e78ac415d6bb89c66a74525b6d36006be4053418 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 17:22:43 -0500 Subject: [PATCH 08/21] Add inactive state to user --- app/models/user.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 75579290e..c74d2c987 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -39,6 +39,10 @@ class User < ApplicationRecord unless: ->(user) { user.sufficient_eligible_prs? } end + event :deactivate do + transition all => :inactive + end + state all - [:new] do validates :terms_acceptance, acceptance: true validates :email, presence: true @@ -90,6 +94,12 @@ def win in: [false], message: 'user has too many sufficient eligible prs' } end + state :inactive do + validates :last_error, inclusion: { + in: ['GithubPullRequestService::UserNotFoundOnGithubError'], + message: "user's last_error must be UserNotFoundOnGithubError" } + end + before_transition do |user, _transition| UserPullRequestSegmentUpdaterService.call(user) end From 8ca2f64359e913aee4eaa3d28dc41f9f081ca4ca Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Thu, 7 Nov 2019 17:25:09 -0500 Subject: [PATCH 09/21] Deactivate user when processing user missing error --- app/models/user.rb | 3 ++- app/services/github_error_handler.rb | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index c74d2c987..f798fe533 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -96,7 +96,8 @@ def win state :inactive do validates :last_error, inclusion: { - in: ['GithubPullRequestService::UserNotFoundOnGithubError'], + in: ['GithubPullRequestService::UserNotFoundOnGithubError', + 'Octokit::AccountSuspended'], message: "user's last_error must be UserNotFoundOnGithubError" } end diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb index 42ac502e1..133597396 100644 --- a/app/services/github_error_handler.rb +++ b/app/services/github_error_handler.rb @@ -32,5 +32,7 @@ def process_user_missing_error(_error, user) return unless (user_stat = UserStat.where(user_id: user.id).first) user_stat.destroy + + user.deactivate end end From 7c6fa4b7d8442a7245414e78bbcc298a93cce698 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Fri, 8 Nov 2019 11:53:17 -0500 Subject: [PATCH 10/21] Fix validations for User#deactivate --- app/models/user.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index f798fe533..b0724e635 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -56,7 +56,7 @@ class User < ApplicationRecord validates :sticker_coupon, absence: true end - state all - %i[new registered waiting] do + state all - %i[new registered waiting inactive] do validates :receipt, presence: true end @@ -98,7 +98,8 @@ def win validates :last_error, inclusion: { in: ['GithubPullRequestService::UserNotFoundOnGithubError', 'Octokit::AccountSuspended'], - message: "user's last_error must be UserNotFoundOnGithubError" } + message: "user's last_error must be + UserNotFoundOnGithubError or AccountSuspended" } end before_transition do |user, _transition| From 48662dc62e817a5872d40c95800c0361c2eb0f36 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Fri, 8 Nov 2019 11:53:57 -0500 Subject: [PATCH 11/21] Fix ImportReposMetadataSerivce to call with_error_handling correctly --- app/services/import_repos_metadata_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/import_repos_metadata_service.rb b/app/services/import_repos_metadata_service.rb index 3484437f5..f52e3847b 100644 --- a/app/services/import_repos_metadata_service.rb +++ b/app/services/import_repos_metadata_service.rb @@ -6,7 +6,7 @@ module ImportReposMetadataService def call(user) pr_service = PullRequestService.new(user, randomize_token: true) - GithubErrorHandler.with_error_handling do + GithubErrorHandler.with_error_handling(user) do pr_data = pr_service.all pr_data.map do |pr| From 119e5e6cef547385e8d0630ba7b0669756c593d6 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Fri, 8 Nov 2019 11:54:50 -0500 Subject: [PATCH 12/21] Add tests for GithubErrorHandler --- app/services/github_error_handler.rb | 4 +- spec/services/github_error_handler_spec.rb | 72 ++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 spec/services/github_error_handler_spec.rb diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb index 133597396..95bd8e36f 100644 --- a/app/services/github_error_handler.rb +++ b/app/services/github_error_handler.rb @@ -29,10 +29,10 @@ def process_github_error(error, user) end def process_user_missing_error(_error, user) + user.deactivate + return unless (user_stat = UserStat.where(user_id: user.id).first) user_stat.destroy - - user.deactivate end end diff --git a/spec/services/github_error_handler_spec.rb b/spec/services/github_error_handler_spec.rb new file mode 100644 index 000000000..6db05271a --- /dev/null +++ b/spec/services/github_error_handler_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe GithubErrorHandler do + let(:user) { FactoryBot.create(:user) } + describe '.process_github_error' do + context 'The error is UserNotFoundOnGithubError' do + let(:error) { GithubPullRequestService::UserNotFoundOnGithubError.new } + + it 'calls process_user_missing_error' do + expect(GithubErrorHandler) + .to receive(:process_user_missing_error).with(error, user) + + GithubErrorHandler.process_github_error(error, user) + end + + it 'correctly updates the last_error column on the user' do + GithubErrorHandler.process_github_error(error, user) + + expect(user.last_error).to eq(error.class.to_s) + end + end + + context 'The error is Octokit::AccountSuspended' do + let(:error) { Octokit::AccountSuspended.new } + + it 'calls process_user_missing_error' do + expect(GithubErrorHandler) + .to receive(:process_user_missing_error).with(error, user) + + GithubErrorHandler.process_github_error(error, user) + end + + it 'correctly updates the last_error column on the user' do + GithubErrorHandler.process_github_error(error, user) + + expect(user.last_error).to eq(error.class.to_s) + end + end + + context 'The error is an unhandled error' do + let(:error) { StandardError.new } + + it 'raises the error' do + expect { GithubErrorHandler.process_github_error(error, user) } + .to raise_error(StandardError) + end + end + end + + describe '.process_user_missing_error' do + let(:error) { GithubPullRequestService::UserNotFoundOnGithubError.new } + it 'destroys the UserStat' do + UserStat.create(user_id: user.id, data: user) + + GithubErrorHandler.process_user_missing_error(error, user) + + expect(UserStat.count).to eq(0) + end + + it 'deactivates the user' do + # to pass user#deactivate validations + allow(user) + .to receive(:last_error).and_return('Octokit::AccountSuspended') + + GithubErrorHandler.process_user_missing_error(error, user) + + expect(user.state).to eq('inactive') + end + end +end From 8b6be09d50d08b4236e9b39ba46199db7bd8f29b Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 14:54:19 -0500 Subject: [PATCH 13/21] Switch case when statement on error rather than error.class.to_s --- app/services/github_error_handler.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/github_error_handler.rb b/app/services/github_error_handler.rb index 95bd8e36f..4c842621b 100644 --- a/app/services/github_error_handler.rb +++ b/app/services/github_error_handler.rb @@ -18,10 +18,10 @@ def process_github_error(error, user) # rubocop:disable Rails/SkipsModelValidations user.update_attribute(:last_error, error.class) # rubocop:enable Rails/SkipsModelValidations - case error.class.to_s - when 'Octokit::AccountSuspended' + case error + when Octokit::AccountSuspended process_user_missing_error(error, user) - when 'GithubPullRequestService::UserNotFoundOnGithubError' + when GithubPullRequestService::UserNotFoundOnGithubError process_user_missing_error(error, user) else raise error From dacbaa91856ce0f9d1cf70a10013c949be78842a Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 15:01:12 -0500 Subject: [PATCH 14/21] Add state_before_inactive to User --- .../20191111195936_add_state_before_inactive_to_user.rb | 7 +++++++ db/schema.rb | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20191111195936_add_state_before_inactive_to_user.rb diff --git a/db/migrate/20191111195936_add_state_before_inactive_to_user.rb b/db/migrate/20191111195936_add_state_before_inactive_to_user.rb new file mode 100644 index 000000000..5e32b7ef7 --- /dev/null +++ b/db/migrate/20191111195936_add_state_before_inactive_to_user.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddStateBeforeInactiveToUser < ActiveRecord::Migration[5.2] + def change + add_column :users, :state_before_inactive, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 00e3f76b5..e735e05d0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_11_07_151321) do +ActiveRecord::Schema.define(version: 2019_11_11_195936) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -129,6 +129,7 @@ t.datetime "waiting_since" t.jsonb "receipt" t.string "last_error" + t.string "state_before_inactive" end end From a884d9ba5d38dd300fb6833e11c11c7daf06575b Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 15:38:07 -0500 Subject: [PATCH 15/21] Add case for #deacitvate to UserStateTransitionSegmentService --- app/services/user_state_transition_segment_service.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/services/user_state_transition_segment_service.rb b/app/services/user_state_transition_segment_service.rb index 7e9770d5d..465cdbec6 100644 --- a/app/services/user_state_transition_segment_service.rb +++ b/app/services/user_state_transition_segment_service.rb @@ -15,6 +15,7 @@ def call(user, transition) when :incomplete then incomplete(user) when :ineligible then ineligible(user) when :won then won(user, transition) + when :deactivate then deactivate(user) end end @@ -61,6 +62,11 @@ def won(user, transition) end end + def deactivate(user) + segment(user).track('user_inactive') + segment(user).identify(state: 'inactive') + end + def segment(user) SegmentService.new(user) end From 8fb6769c9b12d38f91b7549ba9b2d7858be2c2e0 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 15:38:37 -0500 Subject: [PATCH 16/21] Save state_before_inactive on User before transition to inactive --- app/models/user.rb | 6 ++++++ spec/models/user_spec.rb | 25 +++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index b0724e635..f7d38d9bc 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -114,6 +114,8 @@ def win user.receipt = user.scoring_pull_requests end + before_transition to: :inactive, do: :update_state_before_inactive + after_transition to: :waiting, do: :update_waiting_since after_transition to: :completed do |user, _transition| @@ -160,6 +162,10 @@ def update_waiting_since )) end + def update_state_before_inactive + update_attribute(:state_before_inactive, state) + end + def waiting_for_week? return false if waiting_since.nil? diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bb3ddab1e..daf092719 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -551,11 +551,28 @@ expect(user.state).to eq('won_sticker') end end + end - context 'no coupons available' do - it 'does not transition the user' do - user.win - expect(user.state).to eq('completed') + describe '#deactivate' do + before do + allow(UserStateTransitionSegmentService) + .to receive(:deactivate).and_return(true) + + allow_any_instance_of(User) + .to receive(:last_error).and_return('Octokit::AccountSuspended') + end + + context 'the user is in the any state' do + let(:user) { FactoryBot.create(:user) } + + before { user.deactivate } + + it 'transitions the user to the inactive state' do + expect(user.state).to eq('inactive') + end + + it 'saves the previous state as state_before_inactive' do + expect(user.state_before_inactive).to eq('registered') end end end From 09f04249fd2f4cbc379304b95577bac3d37fcf61 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 15:56:21 -0500 Subject: [PATCH 17/21] Linting --- app/models/user.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index f7d38d9bc..05f313c41 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -163,7 +163,9 @@ def update_waiting_since end def update_state_before_inactive + # rubocop:disable Rails/SkipsModelValidations update_attribute(:state_before_inactive, state) + # rubocop:enable Rails/SkipsModelValidations end def waiting_for_week? From ea0e178c45b270c124ac408a7bdcee58f62bba60 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 16:04:50 -0500 Subject: [PATCH 18/21] Fix failing specs --- spec/models/user_spec.rb | 2 +- spec/services/github_error_handler_spec.rb | 7 +++++++ spec/services/import_repos_metadata_service_spec.rb | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index daf092719..235cfde6d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -556,7 +556,7 @@ describe '#deactivate' do before do allow(UserStateTransitionSegmentService) - .to receive(:deactivate).and_return(true) + .to receive(:call).and_return(true) allow_any_instance_of(User) .to receive(:last_error).and_return('Octokit::AccountSuspended') diff --git a/spec/services/github_error_handler_spec.rb b/spec/services/github_error_handler_spec.rb index 6db05271a..2cd5e2937 100644 --- a/spec/services/github_error_handler_spec.rb +++ b/spec/services/github_error_handler_spec.rb @@ -4,6 +4,13 @@ RSpec.describe GithubErrorHandler do let(:user) { FactoryBot.create(:user) } + + # stub API call that will happen due to User#deactivate + before do + allow(UserStateTransitionSegmentService) + .to receive(:call).and_return(true) + end + describe '.process_github_error' do context 'The error is UserNotFoundOnGithubError' do let(:error) { GithubPullRequestService::UserNotFoundOnGithubError.new } diff --git a/spec/services/import_repos_metadata_service_spec.rb b/spec/services/import_repos_metadata_service_spec.rb index 1a5560abf..b74b0bb0a 100644 --- a/spec/services/import_repos_metadata_service_spec.rb +++ b/spec/services/import_repos_metadata_service_spec.rb @@ -5,6 +5,13 @@ RSpec.describe ImportReposMetadataService do describe '.call' do let(:user) { FactoryBot.create(:user) } + + # stub API call that will happen due to User#deactivate + before do + allow(UserStateTransitionSegmentService) + .to receive(:call).and_return(true) + end + context 'The user has no PRs' do before do allow_any_instance_of(PullRequestService) From 6f516f143cbb07d97f4d3bc13835e85670508e1a Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 17:09:07 -0500 Subject: [PATCH 19/21] Use GithubErrorHandler in TryUserTransitionService --- app/services/try_user_transition_service.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/services/try_user_transition_service.rb b/app/services/try_user_transition_service.rb index 32430cf7b..382cc963e 100644 --- a/app/services/try_user_transition_service.rb +++ b/app/services/try_user_transition_service.rb @@ -2,13 +2,15 @@ module TryUserTransitionService def self.call(user) - case user.state - when 'registered' - TryUserTransitionFromRegisteredService.call(user) - when 'waiting' - TryUserTransitionFromWaitingService.call(user) - when 'completed' - TryUserTransitionFromCompletedService.call(user) + GithubErrorHandler.with_error_handling(user) do + case user.state + when 'registered' + TryUserTransitionFromRegisteredService.call(user) + when 'waiting' + TryUserTransitionFromWaitingService.call(user) + when 'completed' + TryUserTransitionFromCompletedService.call(user) + end end end end From 2917b55be7824cadb6a1aff4d5025e17886b2a30 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 17:24:37 -0500 Subject: [PATCH 20/21] Use transition.from to set state_before_inactive on User --- app/models/user.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 05f313c41..6adcf9f94 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -114,7 +114,9 @@ def win user.receipt = user.scoring_pull_requests end - before_transition to: :inactive, do: :update_state_before_inactive + before_transition to: :inactive do |user, transition| + user.update_state_before_inactive(transition) + end after_transition to: :waiting, do: :update_waiting_since @@ -162,9 +164,9 @@ def update_waiting_since )) end - def update_state_before_inactive + def update_state_before_inactive(transition) # rubocop:disable Rails/SkipsModelValidations - update_attribute(:state_before_inactive, state) + update_attribute(:state_before_inactive, transition.from) # rubocop:enable Rails/SkipsModelValidations end From bb360debf27221016cf1774a67d19ad436c868e3 Mon Sep 17 00:00:00 2001 From: Jacob Haff Date: Mon, 11 Nov 2019 17:36:35 -0500 Subject: [PATCH 21/21] Validate presence of state_before_inactive for inactive state --- app/models/user.rb | 2 ++ spec/models/user_spec.rb | 29 ++++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 6adcf9f94..ea7cbacb0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -100,6 +100,8 @@ def win 'Octokit::AccountSuspended'], message: "user's last_error must be UserNotFoundOnGithubError or AccountSuspended" } + + validates :state_before_inactive, presence: true end before_transition do |user, _transition| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 235cfde6d..3abbe301a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -564,15 +564,34 @@ context 'the user is in the any state' do let(:user) { FactoryBot.create(:user) } + context 'the state_before_inactive gets set successfully' do + before { user.deactivate } - before { user.deactivate } + it 'transitions the user to the inactive state' do + expect(user.state).to eq('inactive') + end - it 'transitions the user to the inactive state' do - expect(user.state).to eq('inactive') + it 'saves the previous state as state_before_inactive' do + expect(user.state_before_inactive).to eq('registered') + end end + context 'the state_before_inactive does not get set' do + before do + allow(user).to receive(:state_before_inactive).and_return(nil) + end + + it 'does not transition the user to the inactive state' do + user.deactivate - it 'saves the previous state as state_before_inactive' do - expect(user.state_before_inactive).to eq('registered') + expect(user.state).to_not eq('inactive') + end + + it 'applies the correct errors to the user object' do + user.deactivate + + expect(user.errors.messages[:state_before_inactive].first) + .to eq("can't be blank") + end end end end