From a5db9103ba0cac827bab46b610cc5e29831625f5 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Fri, 8 Mar 2024 10:48:50 -0300 Subject: [PATCH 01/10] CI Features Implementation of new CI features that block abusive use of resources. Creation of tables, users, groups and features that allow better control of CI use. Signed-off-by: Rodrigo Nardi --- .rubocop.yml | 1 + .../20240306093420_create_retry_stage.rb | 21 ++++++++ db/migrate/20240306110430_create_company.rb | 20 +++++++ db/migrate/20240306110450_create_group.rb | 19 +++++++ db/migrate/20240306110454_create_user.rb | 24 +++++++++ db/migrate/20240306155801_create_feature.rb | 26 ++++++++++ ...8094702_alter_group_anonymous_to_public.rb | 15 ++++++ lib/github/build/action.rb | 5 ++ lib/github/build/plan.rb | 19 +++++++ lib/github/build/retry.rb | 2 + lib/github/build_plan.rb | 19 ++++++- lib/github/check.rb | 4 ++ lib/github/re_run/base.rb | 52 +++++++++++++++++++ lib/github/re_run/command.rb | 21 +++++++- lib/github/re_run/comment.rb | 17 ++++-- lib/models/company.rb | 15 ++++++ lib/models/feature.rb | 15 ++++++ lib/models/group.rb | 18 +++++++ lib/models/retry_stage.rb | 23 ++++++++ lib/models/stage.rb | 6 +++ lib/models/user.rb | 18 +++++++ lib/slack_bot/slack_bot.rb | 6 +-- tasks/backfill_github_users.rb | 18 +++++++ 23 files changed, 376 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20240306093420_create_retry_stage.rb create mode 100644 db/migrate/20240306110430_create_company.rb create mode 100644 db/migrate/20240306110450_create_group.rb create mode 100644 db/migrate/20240306110454_create_user.rb create mode 100644 db/migrate/20240306155801_create_feature.rb create mode 100644 db/migrate/20240308094702_alter_group_anonymous_to_public.rb create mode 100644 lib/github/build/plan.rb create mode 100644 lib/models/company.rb create mode 100644 lib/models/feature.rb create mode 100644 lib/models/group.rb create mode 100644 lib/models/retry_stage.rb create mode 100644 lib/models/user.rb create mode 100644 tasks/backfill_github_users.rb diff --git a/.rubocop.yml b/.rubocop.yml index 78b56a8..c21e92e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,7 @@ require: - rubocop-performance AllCops: + TargetRubyVersion: 3.0 NewCops: enable DisplayCopNames: true SuggestExtensions: false diff --git a/db/migrate/20240306093420_create_retry_stage.rb b/db/migrate/20240306093420_create_retry_stage.rb new file mode 100644 index 0000000..9aee931 --- /dev/null +++ b/db/migrate/20240306093420_create_retry_stage.rb @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20231214093515_create_retry_stage.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class CreateRetryStage < ActiveRecord::Migration[6.0] + def change + create_table :retry_stages do |t| + t.text :failure_jobs, null: false + t.timestamps + + t.references :check_suite, index: true, foreign_key: true + t.references :stage, index: true, foreign_key: true + end + end +end diff --git a/db/migrate/20240306110430_create_company.rb b/db/migrate/20240306110430_create_company.rb new file mode 100644 index 0000000..3130c33 --- /dev/null +++ b/db/migrate/20240306110430_create_company.rb @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20231214093515_create_company.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class CreateCompany < ActiveRecord::Migration[6.0] + def change + create_table :companies do |t| + t.string :name, null: false + t.string :email, null: false + t.string :contact, null: false + t.timestamps + end + end +end diff --git a/db/migrate/20240306110450_create_group.rb b/db/migrate/20240306110450_create_group.rb new file mode 100644 index 0000000..db9f102 --- /dev/null +++ b/db/migrate/20240306110450_create_group.rb @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20231214093515_create_group.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class CreateGroup < ActiveRecord::Migration[6.0] + def change + create_table :groups do |t| + t.string :name, null: false + t.boolean :anonymous, null: false, default: true + t.timestamps + end + end +end diff --git a/db/migrate/20240306110454_create_user.rb b/db/migrate/20240306110454_create_user.rb new file mode 100644 index 0000000..73d3138 --- /dev/null +++ b/db/migrate/20240306110454_create_user.rb @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20231214093515_create_user.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class CreateUser < ActiveRecord::Migration[6.0] + def change + create_table :users do |t| + t.string :github_id, null: false + t.string :github_username, null: false + t.string :email, null: true + + t.timestamps + + t.references :company, index: true, foreign_key: true + t.references :group, index: true, foreign_key: true + end + end +end diff --git a/db/migrate/20240306155801_create_feature.rb b/db/migrate/20240306155801_create_feature.rb new file mode 100644 index 0000000..d9b59db --- /dev/null +++ b/db/migrate/20240306155801_create_feature.rb @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20231214093515_create_feature.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class CreateFeature < ActiveRecord::Migration[6.0] + def change + create_table :features do |t| + t.boolean :rerun, null: false, default: true + t.integer :max_rerun_per_pull_request, null: false, default: 3 + t.timestamps + + t.references :group, index: true, foreign_key: true + end + + community = Group.find_by(name: 'Community', anonymous: true) + community = Group.create(name: 'Community', anonymous: true) if community.nil? + + Feature.create(group: community, rerun: true, max_rerun_per_pull_request: 3) + end +end diff --git a/db/migrate/20240308094702_alter_group_anonymous_to_public.rb b/db/migrate/20240308094702_alter_group_anonymous_to_public.rb new file mode 100644 index 0000000..e00fa95 --- /dev/null +++ b/db/migrate/20240308094702_alter_group_anonymous_to_public.rb @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# 20240308094702_alter_group_anonymous_to_public.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +class AlterGroupAnonymousToPublic < ActiveRecord::Migration[6.0] + def change + rename_column :groups, :anonymous, :public + end +end diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index 2db2e7c..3064962 100644 --- a/lib/github/build/action.rb +++ b/lib/github/build/action.rb @@ -42,6 +42,7 @@ def create_summary(rerun: false) def create_jobs(rerun) @jobs.each do |job| ci_job = create_ci_job(job) + next if ci_job.nil? if rerun next unless ci_job.stage.configuration.can_retry? @@ -64,8 +65,12 @@ def stage_with_start_in_progress(ci_job) end def create_ci_job(job) + logger(Logger::INFO, "create_jobs - #{job.inspect}") + stage_config = StageConfiguration.find_by(bamboo_stage_name: job[:stage]) + return if stage_config.nil? + stage = Stage.find_by(check_suite: @check_suite, name: stage_config.github_check_run_name) logger(Logger::INFO, "create_jobs - #{job.inspect} -> #{stage.inspect}") diff --git a/lib/github/build/plan.rb b/lib/github/build/plan.rb new file mode 100644 index 0000000..746765a --- /dev/null +++ b/lib/github/build/plan.rb @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# plan.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +module Github + module Build + class Plan + def initialize(user) + @user = user + end + end + end +end diff --git a/lib/github/build/retry.rb b/lib/github/build/retry.rb index 09590d9..80ff8b7 100644 --- a/lib/github/build/retry.rb +++ b/lib/github/build/retry.rb @@ -34,6 +34,8 @@ def enqueued_stages next if stage.success? + RetryStage.create(check_suite: @check_suite, stage:, failure_jobs: stage.failure_jobs_output) + url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index 3af7f90..082a4d6 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -29,6 +29,7 @@ def initialize(payload, logger_level: Logger::INFO) raise "Invalid payload:\n#{payload}" if @payload.nil? or @payload.empty? @logger.debug 'This is a Pull Request - proceed with branch check' + create_user end def create @@ -68,6 +69,22 @@ def create private + def create_user + @user = User.find_by(github_username: @payload.dig('pull_request', 'user', 'login')) + + return unless @user.nil? + + github = Github::Check.new(nil) + github_user = github.fetch_username(@payload.dig('pull_request', 'user', 'login')) + + @user = + User.create( + github_username: @payload.dig('pull_request', 'user', 'login'), + github_id: github_user[:id], + group: Group.find_by(public: true) + ) + end + def fetch_pull_request @pull_request = PullRequest.find_by(github_pr_id: github_pr, repository: @payload.dig('repository', 'full_name')) @@ -85,7 +102,7 @@ def github_pr def create_pull_request @pull_request = PullRequest.create( - author: @payload.dig('pull_request', 'user', 'login'), + author: @user.github_username, github_pr_id: github_pr, branch_name: @payload.dig('pull_request', 'head', 'ref'), repository: @payload.dig('repository', 'full_name'), diff --git a/lib/github/check.rb b/lib/github/check.rb index 953d3ee..fbe87ed 100644 --- a/lib/github/check.rb +++ b/lib/github/check.rb @@ -52,6 +52,10 @@ def comment_reaction_thumb_up(repo, comment_id) @app.create_issue_comment_reaction(repo, comment_id, '+1') end + def comment_reaction_thumb_down(repo, comment_id) + @app.create_issue_comment_reaction(repo, comment_id, '-1') + end + def create(name) @app.create_check_run( @check_suite.pull_request.repository, diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 5fd5a93..63ac83d 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -27,10 +27,62 @@ def initialize(payload, logger_level: Logger::INFO) @logger_manager << GithubLogger.instance.create('github_app.log', logger_level) @payload = payload + @user = User.find_by(github_username: @payload.dig('comment', 'user', 'login')) + create_user if @user.nil? end private + def create_user + github = Github::Check.new(nil) + github_user = github.fetch_username(@payload.dig('comment', 'user', 'login')) + github_user ||= github.fetch_username(@payload.dig('sender', 'login')) + + @user = User.find_by(github_id: github_user[:id]) + + puts ">>> Github user: #{@user.inspect}" + + return unless @user.nil? + + @user = + User.create( + github_username: @payload.dig('sender', 'login'), + github_id: github_user[:id], + group: Group.find_by(public: true) + ) + end + + def notify_error_rerun(comment_id: nil) + @github_check = Github::Check.new(nil) + + comment_thumb_down(comment_id) unless comment_id.nil? + + logger(Logger::WARN, 'No permission to run') + + [402, 'No permission to run'] + end + + def reach_max_rerun_per_pull_request? + max_rerun = @user.group.feature.max_rerun_per_pull_request + + return false if max_rerun.zero? + + github_check = Github::Check.new(nil) + pull_request_info = github_check.pull_request_info(pr_id, repo) + + if max_rerun < + CheckSuite.where(work_branch: pull_request_info.dig(:head, :ref), re_run: true).count + return true + end + + false + end + + def can_rerun? + logger(Logger::INFO, ">>> User: #{@user} - rerun: #{@user.group.feature.rerun}") + @user.group.feature.rerun + end + def fetch_run_ci_by_pr CheckSuite .joins(:pull_request) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index c3ce3e7..c97f9e9 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -21,7 +21,14 @@ def initialize(payload, logger_level: Logger::INFO) def start return [422, 'Payload can not be blank'] if @payload.nil? or @payload.empty? + return notify_error_rerun if !can_rerun? or reach_max_rerun_per_pull_request? + __run__ + end + + private + + def __run__ logger(Logger::DEBUG, ">>> Github::ReRun::Command - payload: #{@payload.inspect}") check_suite = fetch_check_suite @@ -32,13 +39,25 @@ def start stop_previous_execution + check_suite = create_check_suite(check_suite) + bamboo_plan = start_new_execution(check_suite) ci_jobs(check_suite, bamboo_plan) [201, 'Starting re-run (command)'] end - private + def create_check_suite(check_suite) + CheckSuite.create( + pull_request: check_suite.pull_request, + author: check_suite.author, + commit_sha_ref: check_suite.commit_sha_ref, + work_branch: check_suite.work_branch, + base_sha_ref: check_suite.base_sha_ref, + merge_branch: check_suite.merge_branch, + re_run: true + ) + end def fetch_check_suite CheckSuite diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index b26ebe3..a994d44 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -24,7 +24,14 @@ def initialize(payload, logger_level: Logger::INFO) def start return [422, 'Payload can not be blank'] if @payload.nil? or @payload.empty? return [404, 'Action not found'] unless action? + return notify_error_rerun(comment_id:) if !can_rerun? or reach_max_rerun_per_pull_request? + __run__ + end + + private + + def __run__ logger(Logger::DEBUG, ">>> Github::ReRun::Comment - sha256: #{sha256.inspect}, payload: #{@payload.inspect}") check_suite = sha256_or_comment? @@ -44,8 +51,6 @@ def start [201, 'Starting re-run (comment)'] end - private - def sha256_or_comment? fetch_old_check_suite @@ -70,7 +75,7 @@ def comment_flow def create_check_suite_by_commit(commit, pull_request, pull_request_info) CheckSuite.create( - pull_request: pull_request, + pull_request:, author: @payload.dig('comment', 'user', 'login'), commit_sha_ref: commit[:sha], work_branch: pull_request_info.dig(:head, :ref), @@ -133,6 +138,12 @@ def github_reaction_feedback(comment_id) @github_check.comment_reaction_thumb_up(repo, comment_id) end + def comment_thumb_down(comment_id) + return if comment_id.nil? + + @github_check.comment_reaction_thumb_down(repo, comment_id) + end + def fetch_old_check_suite(sha = sha256) return if sha.nil? diff --git a/lib/models/company.rb b/lib/models/company.rb new file mode 100644 index 0000000..ed66b8f --- /dev/null +++ b/lib/models/company.rb @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# company.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +require 'otr-activerecord' + +class Company < ActiveRecord::Base + has_many :users +end diff --git a/lib/models/feature.rb b/lib/models/feature.rb new file mode 100644 index 0000000..87d54ee --- /dev/null +++ b/lib/models/feature.rb @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# feature.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +require 'otr-activerecord' + +class Feature < ActiveRecord::Base + belongs_to :group +end diff --git a/lib/models/group.rb b/lib/models/group.rb new file mode 100644 index 0000000..3db1033 --- /dev/null +++ b/lib/models/group.rb @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# group.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +require 'otr-activerecord' + +class Group < ActiveRecord::Base + has_many :users + has_one :feature + + validates :public, uniqueness: true +end diff --git a/lib/models/retry_stage.rb b/lib/models/retry_stage.rb new file mode 100644 index 0000000..9dd66ab --- /dev/null +++ b/lib/models/retry_stage.rb @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# retry_stage.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +require 'otr-activerecord' + +class RetryStage < ActiveRecord::Base + belongs_to :check_suite + belongs_to :stage + serialize :failure_jobs, Array + validates :failure_jobs, presence: true + validates :check_suite, presence: true + validates :stage, presence: true + validates :created_at, presence: true + validates :updated_at, presence: true + validates :id, presence: true +end diff --git a/lib/models/stage.rb b/lib/models/stage.rb index 7b23dac..f601904 100644 --- a/lib/models/stage.rb +++ b/lib/models/stage.rb @@ -88,6 +88,12 @@ def refresh_reference(github, agent: 'Github') AuditStatus.create(auditable: self, status: :refresh, agent: agent, created_at: Time.now) end + def failure_jobs_output + jobs.where(status: :failure).map do |job| + "#{job.name} - #{job.topotest_failures.map(&:to_h)}" + end + end + private def in_progress_notification diff --git a/lib/models/user.rb b/lib/models/user.rb new file mode 100644 index 0000000..1cbe674 --- /dev/null +++ b/lib/models/user.rb @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# user.rb +# Part of NetDEF CI System +# +# Copyright (c) 2024 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +require 'otr-activerecord' + +class User < ActiveRecord::Base + belongs_to :company + belongs_to :group + + validates :github_id, presence: true, uniqueness: true +end diff --git a/lib/slack_bot/slack_bot.rb b/lib/slack_bot/slack_bot.rb index 5493090..e7ecf4b 100644 --- a/lib/slack_bot/slack_bot.rb +++ b/lib/slack_bot/slack_bot.rb @@ -159,7 +159,7 @@ def send_success_message(job, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message: message, unfurl_links: false, unfurl_media: false, + body: { message:, unfurl_links: false, unfurl_media: false, slack_user_id: subscription.slack_user_id }.to_json) end @@ -167,7 +167,7 @@ def send_error_message(message, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message: message, slack_user_id: subscription.slack_user_id }.to_json) + body: { message:, slack_user_id: subscription.slack_user_id }.to_json) end def started_finished_notification(check_suite, subscription, started_or_finished: 'Started') @@ -176,7 +176,7 @@ def started_finished_notification(check_suite, subscription, started_or_finished url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message: message, slack_user_id: subscription.slack_user_id }.to_json) + body: { message:, slack_user_id: subscription.slack_user_id }.to_json) end def pull_request_message(check_suite, status) diff --git a/tasks/backfill_github_users.rb b/tasks/backfill_github_users.rb new file mode 100644 index 0000000..8d4b66f --- /dev/null +++ b/tasks/backfill_github_users.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative '../lib/github_ci_app' + +group = Group.find_by(anonymous: true) +group = Group.create(name: 'Community', anonymous: true) if group.nil? + +# Now we can backfill the users from the check suites +CheckSuite.all.group(:author).select(:author).each do |check_suite| + user = User.find_by(github_username: check_suite.author) + + next unless user.nil? + + github = Github::Check.new(check_suite) + github_user = github.fetch_username(check_suite.author) + + User.create(github_username: check_suite.author, github_id: github_user[:id], group: group) +end From 735081f9af88853f4f1d74bd9179c285e4dbe499 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Fri, 8 Mar 2024 10:57:03 -0300 Subject: [PATCH 02/10] CI Features Fixing rubocop issues Signed-off-by: Rodrigo Nardi --- lib/github/build/retry.rb | 2 +- lib/github/re_run/comment.rb | 4 ++-- lib/slack_bot/slack_bot.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/github/build/retry.rb b/lib/github/build/retry.rb index 80ff8b7..bd41045 100644 --- a/lib/github/build/retry.rb +++ b/lib/github/build/retry.rb @@ -34,7 +34,7 @@ def enqueued_stages next if stage.success? - RetryStage.create(check_suite: @check_suite, stage:, failure_jobs: stage.failure_jobs_output) + RetryStage.create(check_suite: @check_suite, stage: stage, failure_jobs: stage.failure_jobs_output) url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index a994d44..36e0617 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -24,7 +24,7 @@ def initialize(payload, logger_level: Logger::INFO) def start return [422, 'Payload can not be blank'] if @payload.nil? or @payload.empty? return [404, 'Action not found'] unless action? - return notify_error_rerun(comment_id:) if !can_rerun? or reach_max_rerun_per_pull_request? + return notify_error_rerun(comment_id: comment_id) if !can_rerun? or reach_max_rerun_per_pull_request? __run__ end @@ -75,7 +75,7 @@ def comment_flow def create_check_suite_by_commit(commit, pull_request, pull_request_info) CheckSuite.create( - pull_request:, + pull_request: pull_request, author: @payload.dig('comment', 'user', 'login'), commit_sha_ref: commit[:sha], work_branch: pull_request_info.dig(:head, :ref), diff --git a/lib/slack_bot/slack_bot.rb b/lib/slack_bot/slack_bot.rb index 8822dce..e2b328c 100644 --- a/lib/slack_bot/slack_bot.rb +++ b/lib/slack_bot/slack_bot.rb @@ -161,7 +161,7 @@ def send_success_message(job, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message:, unfurl_links: false, unfurl_media: false, + body: { message: message, unfurl_links: false, unfurl_media: false, slack_user_id: subscription.slack_user_id }.to_json) end @@ -169,7 +169,7 @@ def send_error_message(message, subscription) url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message:, slack_user_id: subscription.slack_user_id }.to_json) + body: { message: message, slack_user_id: subscription.slack_user_id }.to_json) end def send_cancel_message(message, subscription) @@ -185,7 +185,7 @@ def started_finished_notification(check_suite, subscription, started_or_finished url = "#{GitHubApp::Configuration.instance.config['slack_bot_url']}/github/user" post_request(URI(url), machine: 'slack_bot.netdef.org', - body: { message:, slack_user_id: subscription.slack_user_id }.to_json) + body: { message: message, slack_user_id: subscription.slack_user_id }.to_json) end def pull_request_message(check_suite, status) From 2e28a9a74ca0f01137bc7c0187ec89391f61cc95 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Mon, 11 Mar 2024 11:33:04 -0300 Subject: [PATCH 03/10] CI Features Fixing unit tests Signed-off-by: Rodrigo Nardi --- ...8094702_alter_group_anonymous_to_public.rb | 2 + db/schema.rb | 54 +++++++++++++- lib/github/build/action.rb | 12 +++- lib/github/build_plan.rb | 5 +- lib/github/re_run/base.rb | 7 +- lib/models/group.rb | 2 - spec/app/github_app_spec.rb | 45 +++++++++++- spec/factories/feature.rb | 16 +++++ spec/factories/group.rb | 17 +++++ spec/factories/user.rb | 17 +++++ spec/lib/github/build/action_spec.rb | 2 + .../lib/github/build/unavailable_jobs_spec.rb | 1 + spec/lib/github/build_plan_spec.rb | 26 +++++++ spec/lib/github/re_run/command_spec.rb | 34 ++++++++- spec/lib/github/re_run/comment_spec.rb | 72 ++++++++++++++++--- 15 files changed, 289 insertions(+), 23 deletions(-) create mode 100644 spec/factories/feature.rb create mode 100644 spec/factories/group.rb create mode 100644 spec/factories/user.rb diff --git a/db/migrate/20240308094702_alter_group_anonymous_to_public.rb b/db/migrate/20240308094702_alter_group_anonymous_to_public.rb index e00fa95..e5c4214 100644 --- a/db/migrate/20240308094702_alter_group_anonymous_to_public.rb +++ b/db/migrate/20240308094702_alter_group_anonymous_to_public.rb @@ -11,5 +11,7 @@ class AlterGroupAnonymousToPublic < ActiveRecord::Migration[6.0] def change rename_column :groups, :anonymous, :public + + add_index :groups, [:public], unique: true end end diff --git a/db/schema.rb b/db/schema.rb index d6fb9a9..cf9bf5a 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[7.0].define(version: 2024_02_07_070831) do +ActiveRecord::Schema[7.0].define(version: 2024_03_08_094702) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -55,6 +55,31 @@ t.index ["stage_id"], name: "index_ci_jobs_on_stage_id" end + create_table "companies", force: :cascade do |t| + t.string "name", null: false + t.string "email", null: false + t.string "contact", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "features", force: :cascade do |t| + t.boolean "rerun", default: true, null: false + t.integer "max_rerun_per_pull_request", default: 3, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "group_id" + t.index ["group_id"], name: "index_features_on_group_id" + end + + create_table "groups", force: :cascade do |t| + t.string "name", null: false + t.boolean "public", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["public"], name: "index_groups_on_public", unique: true + end + create_table "plans", force: :cascade do |t| t.string "bamboo_ci_plan_name", null: false t.string "github_repo_name", default: "0", null: false @@ -85,6 +110,16 @@ t.datetime "updated_at", null: false end + create_table "retry_stages", force: :cascade do |t| + t.text "failure_jobs", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "check_suite_id" + t.bigint "stage_id" + t.index ["check_suite_id"], name: "index_retry_stages_on_check_suite_id" + t.index ["stage_id"], name: "index_retry_stages_on_stage_id" + end + create_table "stage_configurations", force: :cascade do |t| t.string "bamboo_stage_name", null: false t.string "github_check_run_name", null: false @@ -119,12 +154,29 @@ t.index ["ci_job_id"], name: "index_topotest_failures_on_ci_job_id" end + create_table "users", force: :cascade do |t| + t.string "github_id", null: false + t.string "github_username", null: false + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "company_id" + t.bigint "group_id" + t.index ["company_id"], name: "index_users_on_company_id" + t.index ["group_id"], name: "index_users_on_group_id" + end + add_foreign_key "check_suites", "pull_requests" add_foreign_key "ci_jobs", "check_suites" add_foreign_key "ci_jobs", "stages" + add_foreign_key "features", "groups" add_foreign_key "plans", "check_suites" add_foreign_key "pull_request_subscriptions", "pull_requests" + add_foreign_key "retry_stages", "check_suites" + add_foreign_key "retry_stages", "stages" add_foreign_key "stages", "check_suites" add_foreign_key "stages", "stage_configurations" add_foreign_key "topotest_failures", "ci_jobs" + add_foreign_key "users", "companies" + add_foreign_key "users", "groups" end diff --git a/lib/github/build/action.rb b/lib/github/build/action.rb index 980e628..29fecff 100644 --- a/lib/github/build/action.rb +++ b/lib/github/build/action.rb @@ -90,7 +90,9 @@ def create_check_run_stage(stage_config) logger(Logger::INFO, ">>> Enqueued #{stage.inspect}") - stage.enqueue(@github, output: initial_output(stage)) + stage_configure_status(stage, stage_config) + + stage end def create_stage(stage_config) @@ -102,13 +104,17 @@ def create_stage(stage_config) status: 'queued', name: name) + stage_configure_status(stage, stage_config) + + stage + end + + def stage_configure_status(stage, stage_config) url = "https://ci1.netdef.org/browse/#{stage.check_suite.bamboo_ci_ref}" output = { title: "#{stage.name} summary", summary: "Uninitialized stage\nDetails at [#{url}](#{url})" } stage.enqueue(@github, output: output) stage.in_progress(@github) if stage_config.start_in_progress? - - stage end def initial_output(ci_job) diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index c71f57f..69c2544 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -73,13 +73,14 @@ def create_user @user = User.find_by(github_username: @payload.dig('pull_request', 'user', 'login')) return unless @user.nil? + return if @payload.dig('pull_request', 'user', 'login').nil? github = Github::Check.new(nil) github_user = github.fetch_username(@payload.dig('pull_request', 'user', 'login')) @user = User.create( - github_username: @payload.dig('pull_request', 'user', 'login'), + github_username: @payload.dig('pull_request', 'user', 'login') , github_id: github_user[:id], group: Group.find_by(public: true) ) @@ -102,7 +103,7 @@ def github_pr def create_pull_request @pull_request = PullRequest.create( - author: @user.github_username, + author: @user&.github_username, github_pr_id: github_pr, branch_name: @payload.dig('pull_request', 'head', 'ref'), repository: @payload.dig('repository', 'full_name'), diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 63ac83d..649d15d 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -42,7 +42,7 @@ def create_user puts ">>> Github user: #{@user.inspect}" - return unless @user.nil? + return if valid_user_and_payload? github_user @user = User.create( @@ -52,6 +52,10 @@ def create_user ) end + def valid_user_and_payload?(github_user) + !@user.nil? or @payload.nil? or @payload.empty? or github_user.nil? or github_user.empty? + end + def notify_error_rerun(comment_id: nil) @github_check = Github::Check.new(nil) @@ -79,7 +83,6 @@ def reach_max_rerun_per_pull_request? end def can_rerun? - logger(Logger::INFO, ">>> User: #{@user} - rerun: #{@user.group.feature.rerun}") @user.group.feature.rerun end diff --git a/lib/models/group.rb b/lib/models/group.rb index 3db1033..4c68487 100644 --- a/lib/models/group.rb +++ b/lib/models/group.rb @@ -13,6 +13,4 @@ class Group < ActiveRecord::Base has_many :users has_one :feature - - validates :public, uniqueness: true end diff --git a/spec/app/github_app_spec.rb b/spec/app/github_app_spec.rb index 365aed9..f3d62a5 100644 --- a/spec/app/github_app_spec.rb +++ b/spec/app/github_app_spec.rb @@ -9,6 +9,11 @@ # frozen_string_literal: true describe 'GithubApp' do + let(:fake_client) { Octokit::Client.new } + let(:fake_github_check) { Github::Check.new(nil) } + let(:fake_plan_run) { BambooCi::PlanRun.new(nil) } + let(:fake_check_run) { create(:check_suite) } + context 'when ping route is called' do it 'returns success' do get '/ping' @@ -204,10 +209,28 @@ } end - let(:payload) { { x: 1, 'action' => 'rerequested' } } + let(:user) { create(:user) } + let(:payload) do + { + x: 1, + 'action' => 'rerequested', + 'sender' => { 'login' => user.github_username }, + 'issue' => { 'number' => 1 }, + 'repository' => { 'full_name' => 'blah' } + } + end before do allow(GitHubApp::Configuration.instance).to receive(:debug?).and_return(false) + + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info).and_return({ head: { ref: '1234' } }) end it 'must returns error' do @@ -226,10 +249,28 @@ } end - let(:payload) { { x: 1, 'action' => 'rerequested' } } + let(:user) { create(:user) } + let(:payload) do + { + x: 1, + 'action' => 'rerequested', + 'sender' => { 'login' => user.github_username }, + 'issue' => { 'number' => 1 }, + 'repository' => { 'full_name' => 'blah' } + } + end before do allow(GitHubApp::Configuration.instance).to receive(:debug?).and_return(false) + + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info).and_return({ head: { ref: '1234' } }) end it 'must returns error' do diff --git a/spec/factories/feature.rb b/spec/factories/feature.rb new file mode 100644 index 0000000..dd053af --- /dev/null +++ b/spec/factories/feature.rb @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# feature.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +FactoryBot.define do + factory :feature do + rerun { true } + max_rerun_per_pull_request { 3 } + end +end diff --git a/spec/factories/group.rb b/spec/factories/group.rb new file mode 100644 index 0000000..b6f6310 --- /dev/null +++ b/spec/factories/group.rb @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# group.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +FactoryBot.define do + factory :group do + name { Faker::App.name } + public { true } + feature { create(:feature) } + end +end diff --git a/spec/factories/user.rb b/spec/factories/user.rb new file mode 100644 index 0000000..cd96ca2 --- /dev/null +++ b/spec/factories/user.rb @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: BSD-2-Clause +# +# user.rb +# Part of NetDEF CI System +# +# Copyright (c) 2023 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# frozen_string_literal: true + +FactoryBot.define do + factory :user do + github_id { Faker::Crypto.md5 } + github_username { Faker::Name.name } + group { create(:group) } + end +end diff --git a/spec/lib/github/build/action_spec.rb b/spec/lib/github/build/action_spec.rb index 1830d2e..7ddc10b 100644 --- a/spec/lib/github/build/action_spec.rb +++ b/spec/lib/github/build/action_spec.rb @@ -55,6 +55,8 @@ before do allow(Stage).to receive(:create).and_return(stage) allow(stage).to receive(:persisted?).and_return(false) + + allow(SlackBot.instance).to receive(:stage_in_progress_notification) end it 'must create a stage' do diff --git a/spec/lib/github/build/unavailable_jobs_spec.rb b/spec/lib/github/build/unavailable_jobs_spec.rb index 81d916c..bfd9e64 100644 --- a/spec/lib/github/build/unavailable_jobs_spec.rb +++ b/spec/lib/github/build/unavailable_jobs_spec.rb @@ -34,6 +34,7 @@ end it 'must change check suite' do + expect(check_suite.reload.ci_jobs.size).to eq(2) unavailable_jobs.update(new_check_suite: new_check_suite) expect(new_check_suite.reload.ci_jobs.size).to eq(1) expect(check_suite.reload.ci_jobs.size).to eq(1) diff --git a/spec/lib/github/build_plan_spec.rb b/spec/lib/github/build_plan_spec.rb index b1307e2..4eddf8d 100644 --- a/spec/lib/github/build_plan_spec.rb +++ b/spec/lib/github/build_plan_spec.rb @@ -65,6 +65,10 @@ allow(fake_github_check).to receive(:queued).and_return(fake_check_run) allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end context 'when action is opened' do @@ -207,6 +211,16 @@ let(:action) { 'fake' } let(:author) { 'Jack The Ripper' } + before do + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + end + it 'must returns an error' do expect(build_plan.create).to eq([405, "Not dealing with action \"#{payload['action']}\" for Pull Request"]) end @@ -238,6 +252,10 @@ allow(CheckSuite).to receive(:create).and_return(check_suite) allow(check_suite).to receive(:persisted?).and_return(false) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end it 'must returns an error' do @@ -257,6 +275,10 @@ allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(400) allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end it 'must returns an error' do @@ -278,6 +300,10 @@ allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return([]) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_check_run) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end it 'must returns an error' do diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 52e61fa..d747c09 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -13,6 +13,7 @@ let(:fake_client) { Octokit::Client.new } let(:fake_github_check) { Github::Check.new(nil) } let(:fake_plan_run) { BambooCi::PlanRun.new(nil) } + let(:group) { create(:group) } before do allow(File).to receive(:read).and_return('') @@ -23,6 +24,16 @@ context 'when receives an empty payload' do let(:payload) { {} } + before do + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_github_check) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + end + it 'must returns error' do expect(rerun.start).to eq([422, 'Payload can not be blank']) end @@ -51,12 +62,17 @@ { 'number' => check_suite.pull_request.github_pr_id } ] }, - 'repository' => { 'full_name' => check_suite.pull_request.repository } + 'repository' => { 'full_name' => check_suite.pull_request.repository }, + 'sender' => { 'login' => check_suite.pull_request.author } } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } + let(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:fake_unavailable_jobs) { Github::Build::UnavailableJobs.new(check_suite) } before do + user + allow(Octokit::Client).to receive(:new).and_return(fake_client) allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) @@ -69,6 +85,12 @@ allow(fake_github_check).to receive(:queued) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:skipped) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.commit_sha_ref } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + + allow(Github::Build::UnavailableJobs).to receive(:new).and_return(fake_unavailable_jobs) + allow(fake_unavailable_jobs).to receive(:update) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -81,7 +103,7 @@ it 'must returns success' do expect(rerun.start).to eq([201, 'Starting re-run (command)']) - expect(check_suites.size).to eq(1) + expect(check_suites.size).to eq(2) expect(check_suites.last.re_run).to be_truthy end end @@ -103,12 +125,15 @@ { 'number' => 0 } ] }, - 'repository' => { 'full_name' => check_suite.pull_request.repository } + 'repository' => { 'full_name' => check_suite.pull_request.repository }, + 'sender' => { 'login' => check_suite.pull_request.author } } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } + let(:user) { create(:user, github_username: check_suite.pull_request.author) } before do + user allow(Octokit::Client).to receive(:new).and_return(fake_client) allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) @@ -119,6 +144,9 @@ allow(fake_github_check).to receive(:cancelled) allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.commit_sha_ref } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 74fd97f..8a75f40 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -27,13 +27,41 @@ context 'when receives an empty payload' do let(:payload) { {} } + before do + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_github_check) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + end + it 'must returns error' do expect(rerun.start).to eq([422, 'Payload can not be blank']) end end context 'when receives an invalid command' do - let(:payload) { { 'action' => 'delete', 'comment' => { 'body' => 'CI:rerun' } } } + let(:payload) do + { + 'action' => 'delete', + 'comment' => { 'body' => 'CI:rerun' }, + 'sender' => { 'login' => 'john' } + } + end + + before do + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_github_check) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: 'blah' } }) + end it 'must returns error' do expect(rerun.start).to eq([404, 'Action not found']) @@ -47,6 +75,8 @@ let(:fake_translation) { create(:stage_configuration) } context 'when receives a valid command' do + let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } let(:ci_jobs) do [ @@ -59,7 +89,8 @@ 'action' => 'created', 'comment' => { 'body' => "CI:rerun ##{check_suite.commit_sha_ref}", 'id' => 1 }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, - 'issue' => { 'number' => check_suite.pull_request.github_pr_id } + 'issue' => { 'number' => check_suite.pull_request.github_pr_id }, + 'sender' => { 'login' => check_suite.pull_request.author } } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } @@ -76,6 +107,9 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:queued) allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.commit_sha_ref } }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -93,6 +127,8 @@ end context 'when receives a valid command but can save' do + let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:pull_request) { create(:pull_request) } let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) } let(:ci_jobs) do @@ -106,7 +142,8 @@ 'action' => 'created', 'comment' => { 'body' => "CI:rerun ##{check_suite.commit_sha_ref}", 'id' => 1 }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, - 'issue' => { 'number' => check_suite.pull_request.github_pr_id } + 'issue' => { 'number' => check_suite.pull_request.github_pr_id }, + 'sender' => { 'login' => check_suite.pull_request.author } } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } @@ -124,6 +161,9 @@ allow(fake_github_check).to receive(:cancelled) allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.commit_sha_ref } }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -141,6 +181,7 @@ end context 'when has two opened PRs' do + let!(:user) { create(:user, github_username: check_suite.pull_request.author) } let(:first_pr) { create(:pull_request, github_pr_id: 12, id: 11, repository: 'test') } let(:second_pr) { create(:pull_request, github_pr_id: 13, id: 12, repository: 'test') } let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: first_pr) } @@ -158,14 +199,15 @@ 'user' => { 'login' => 'John' } }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, - 'issue' => { 'number' => check_suite.pull_request.github_pr_id } + 'issue' => { 'number' => check_suite.pull_request.github_pr_id }, + 'sender' => { 'login' => check_suite.pull_request.author } } end let(:pull_request_info) do { head: { - ref: 'master' + ref: check_suite.commit_sha_ref }, base: { ref: 'test', @@ -192,6 +234,7 @@ allow(fake_github_check).to receive(:cancelled) allow(fake_github_check).to receive(:queued) allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -220,6 +263,8 @@ end context 'when you receive an comment' do + let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) } @@ -237,7 +282,8 @@ 'user' => { 'login' => 'John' } }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, - 'issue' => { 'number' => check_suite.pull_request.github_pr_id } + 'issue' => { 'number' => check_suite.pull_request.github_pr_id }, + 'sender' => { 'login' => check_suite.pull_request.author } } end @@ -271,6 +317,7 @@ allow(fake_github_check).to receive(:cancelled) allow(fake_github_check).to receive(:queued) allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -304,6 +351,7 @@ allow(fake_github_check).to receive(:cancelled) allow(fake_github_check).to receive(:queued) allow(fake_github_check).to receive(:pull_request_info).and_return(pull_request_info) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) allow(fake_plan_run).to receive(:start_plan).and_return(200) @@ -314,6 +362,8 @@ end context 'when you receive an comment and does not exist a PR' do + let!(:user) { create(:user) } + let(:commit_sha) { Faker::Internet.uuid } let(:payload) do @@ -324,7 +374,8 @@ 'user' => { 'login' => 'John' } }, 'repository' => { 'full_name' => 'unit_test' }, - 'issue' => { 'number' => '10' } + 'issue' => { 'number' => '10' }, + 'sender' => { 'login' => 'john' } } end @@ -362,7 +413,10 @@ end context 'when can not save check_suite' do + let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:commit_sha) { Faker::Internet.uuid } + let(:check_suite) { create(:check_suite) } let(:payload) do { @@ -371,7 +425,8 @@ 'body' => 'CI:rerun 000000' }, 'repository' => { 'full_name' => 'unit_test' }, - 'issue' => { 'number' => '10' } + 'issue' => { 'number' => '10' }, + 'sender' => { 'login' => check_suite.pull_request.author } } end @@ -403,6 +458,7 @@ before do create(:plan, github_repo_name: 'unit_test') + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end it 'must returns success' do From 288947beb7a6126533a6f5d3686fca017a7fa5fa Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Wed, 20 Mar 2024 17:10:23 -0300 Subject: [PATCH 04/10] Rubocop Fixing code lint Signed-off-by: Rodrigo Nardi --- lib/github/build_plan.rb | 2 +- lib/github/re_run/command.rb | 12 ------------ spec/lib/github/re_run/command_spec.rb | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index 69c2544..e4e51c9 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -80,7 +80,7 @@ def create_user @user = User.create( - github_username: @payload.dig('pull_request', 'user', 'login') , + github_username: @payload.dig('pull_request', 'user', 'login'), github_id: github_user[:id], group: Group.find_by(public: true) ) diff --git a/lib/github/re_run/command.rb b/lib/github/re_run/command.rb index ecb99ba..c97f9e9 100644 --- a/lib/github/re_run/command.rb +++ b/lib/github/re_run/command.rb @@ -59,18 +59,6 @@ def create_check_suite(check_suite) ) end - def create_check_suite(check_suite) - CheckSuite.create( - pull_request: check_suite.pull_request, - author: check_suite.author, - commit_sha_ref: check_suite.commit_sha_ref, - work_branch: check_suite.work_branch, - base_sha_ref: check_suite.base_sha_ref, - merge_branch: check_suite.merge_branch, - re_run: true - ) - end - def fetch_check_suite CheckSuite .joins(:pull_request) diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 0d34521..da1f47b 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -146,7 +146,7 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: check_suite.commit_sha_ref } }) + .and_return({ head: { ref: check_suite.commit_sha_ref } }) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) From 61ab0a70ee615119748482b1fddddb8a294d57d4 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Wed, 20 Mar 2024 23:24:19 -0300 Subject: [PATCH 05/10] Unit Tests Fixing unit tests Signed-off-by: Rodrigo Nardi --- lib/github/re_run/base.rb | 1 + spec/lib/github/re_run/command_spec.rb | 64 ++++++++++++++++++++++++- spec/lib/github/re_run/comment_spec.rb | 65 ++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 1f8543f..8724d60 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -28,6 +28,7 @@ def initialize(payload, logger_level: Logger::INFO) @payload = payload @user = User.find_by(github_username: @payload.dig('comment', 'user', 'login')) + @user ||= User.find_by(github_username: @payload.dig('sender', 'login')) create_user if @user.nil? end diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index da1f47b..4b1050c 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -67,7 +67,9 @@ } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } - let(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:feature) { create(:feature, max_rerun_per_pull_request: 0) } + let(:group) { create(:group, feature: feature) } + let(:user) { create(:user, github_username: check_suite.pull_request.author, group: group) } let(:fake_unavailable_jobs) { Github::Build::UnavailableJobs.new(check_suite) } before do @@ -162,5 +164,65 @@ expect(rerun.start).to eq([404, 'Failed to fetch a check suite']) end end + + context 'when max_retries is reached' do + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } + let(:ci_jobs) do + [ + { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }, + { name: 'Checkout', job_ref: 'CHK-01', stage: fake_translation.bamboo_stage_name } + ] + end + let(:previous_check_suites) do + create_list(:check_suite, 5, + re_run: true, + pull_request: check_suite.pull_request, + work_branch: check_suite.work_branch) + end + let(:payload) do + { + 'action' => 'created', + 'check_suite' => { + 'head_sha' => check_suite.commit_sha_ref, + 'pull_requests' => [ + { 'number' => 0 } + ] + }, + 'repository' => { 'full_name' => check_suite.pull_request.repository }, + 'sender' => { 'login' => check_suite.pull_request.author } + } + end + let(:group) { create(:group) } + + before do + previous_check_suites + group + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(check_suite) + allow(fake_github_check).to receive(:add_comment) + allow(fake_github_check).to receive(:cancelled) + allow(fake_github_check).to receive(:in_progress) + allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.work_branch } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + + allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) + allow(fake_plan_run).to receive(:start_plan).and_return(200) + allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') + allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01') + + allow(BambooCi::StopPlan).to receive(:stop) + allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) + end + + it 'must returns error' do + expect(rerun.start).to eq([402, 'No permission to run']) + end + end end end diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 8a75f40..83b2e7e 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -332,6 +332,71 @@ expect(check_suite_rerun).not_to be_nil end end + + context 'when max_retries is reached' do + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } + let(:ci_jobs) do + [ + { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }, + { name: 'Checkout', job_ref: 'CHK-01', stage: fake_translation.bamboo_stage_name } + ] + end + let(:previous_check_suites) do + create_list(:check_suite, 5, + re_run: true, + pull_request: check_suite.pull_request, + work_branch: check_suite.work_branch) + end + let(:payload) do + { + 'action' => 'created', + 'comment' => { + 'body' => 'CI:rerun 000000', + 'comment_id' => '10' + }, + 'repository' => { 'full_name' => 'unit_test' }, + 'issue' => { 'number' => '10' }, + 'sender' => { 'login' => check_suite.pull_request.author } + } + end + let(:pull_request_commits) do + [ + { sha: check_suite.commit_sha_ref, date: Time.now } + ] + end + let(:group) { create(:group) } + + before do + previous_check_suites + group + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(check_suite) + allow(fake_github_check).to receive(:add_comment) + allow(fake_github_check).to receive(:cancelled) + allow(fake_github_check).to receive(:in_progress) + allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.work_branch } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:comment_reaction_thumb_down) + + allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) + allow(fake_plan_run).to receive(:start_plan).and_return(200) + allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') + allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01') + + allow(BambooCi::StopPlan).to receive(:stop) + allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) + end + + it 'must returns error' do + expect(rerun.start).to eq([402, 'No permission to run']) + end + end end describe 'alternative scenarios' do From 4fce8ae14fdcd95312c066ea61500609ac009404 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Thu, 21 Mar 2024 09:53:27 -0300 Subject: [PATCH 06/10] Rubocop Fixing lint issues Signed-off-by: Rodrigo Nardi --- spec/lib/github/re_run/command_spec.rb | 2 +- spec/lib/github/re_run/comment_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 4b1050c..8c5741d 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -208,7 +208,7 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: check_suite.work_branch } }) + .and_return({ head: { ref: check_suite.work_branch } }) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 83b2e7e..b1ea59d 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -380,7 +380,7 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: check_suite.work_branch } }) + .and_return({ head: { ref: check_suite.work_branch } }) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(fake_github_check).to receive(:comment_reaction_thumb_down) From 84e5208ee6c3587f3c121f6e6f1e084607c85c06 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Thu, 21 Mar 2024 11:36:02 -0300 Subject: [PATCH 07/10] Code Coverage Increasing code coverage Signed-off-by: Rodrigo Nardi --- lib/github/re_run/base.rb | 16 ++++- lib/github/re_run/comment.rb | 6 -- spec/lib/github/re_run/command_spec.rb | 64 +++++++++++++++++-- spec/lib/github/re_run/comment_spec.rb | 88 +++++++++++++++++++++++++- 4 files changed, 162 insertions(+), 12 deletions(-) diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index 8724d60..4f31662 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -67,6 +67,10 @@ def notify_error_rerun(comment_id: nil) [402, 'No permission to run'] end + def comment_thumb_down(comment_id) + @github_check.comment_reaction_thumb_down(repo, comment_id) + end + def reach_max_rerun_per_pull_request? max_rerun = @user.group.feature.max_rerun_per_pull_request @@ -170,7 +174,17 @@ def action end def pr_id - @payload.dig('issue', 'number') || @payload.dig('check_suite', 'pull_requests')&.last&.[]('number') + pr_id_from_issue || pr_id_from_check_suite + end + + def pr_id_from_issue + @payload.dig('issue', 'number') + end + + def pr_id_from_check_suite + return if @payload.dig('check_suite', 'pull_requests').nil? + + @payload.dig('check_suite', 'pull_requests').last&.[]('number') end def repo diff --git a/lib/github/re_run/comment.rb b/lib/github/re_run/comment.rb index 36e0617..b8716fa 100644 --- a/lib/github/re_run/comment.rb +++ b/lib/github/re_run/comment.rb @@ -138,12 +138,6 @@ def github_reaction_feedback(comment_id) @github_check.comment_reaction_thumb_up(repo, comment_id) end - def comment_thumb_down(comment_id) - return if comment_id.nil? - - @github_check.comment_reaction_thumb_down(repo, comment_id) - end - def fetch_old_check_suite(sha = sha256) return if sha.nil? diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 8c5741d..e4c056a 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -122,6 +122,7 @@ let(:payload) do { 'action' => 'created', + 'issue' => { 'number' => 0 }, 'check_suite' => { 'head_sha' => check_suite.commit_sha_ref, 'pull_requests' => [ @@ -183,10 +184,7 @@ { 'action' => 'created', 'check_suite' => { - 'head_sha' => check_suite.commit_sha_ref, - 'pull_requests' => [ - { 'number' => 0 } - ] + 'head_sha' => check_suite.commit_sha_ref }, 'repository' => { 'full_name' => check_suite.pull_request.repository }, 'sender' => { 'login' => check_suite.pull_request.author } @@ -224,5 +222,63 @@ expect(rerun.start).to eq([402, 'No permission to run']) end end + + context 'when max_retries is reached and has invalid check_suite pull_requests' do + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } + let(:ci_jobs) do + [ + { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }, + { name: 'Checkout', job_ref: 'CHK-01', stage: fake_translation.bamboo_stage_name } + ] + end + let(:previous_check_suites) do + create_list(:check_suite, 5, + re_run: true, + pull_request: check_suite.pull_request, + work_branch: check_suite.work_branch) + end + let(:payload) do + { + 'action' => 'created', + 'check_suite' => { + 'head_sha' => check_suite.commit_sha_ref, + 'pull_requests' => [] + }, + 'repository' => { 'full_name' => check_suite.pull_request.repository }, + 'sender' => { 'login' => check_suite.pull_request.author } + } + end + let(:group) { create(:group) } + + before do + previous_check_suites + group + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(check_suite) + allow(fake_github_check).to receive(:add_comment) + allow(fake_github_check).to receive(:cancelled) + allow(fake_github_check).to receive(:in_progress) + allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.work_branch } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + + allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) + allow(fake_plan_run).to receive(:start_plan).and_return(200) + allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') + allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01') + + allow(BambooCi::StopPlan).to receive(:stop) + allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) + end + + it 'must returns error' do + expect(rerun.start).to eq([402, 'No permission to run']) + end + end end end diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index b1ea59d..09a9330 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -67,6 +67,28 @@ expect(rerun.start).to eq([404, 'Action not found']) end end + + context 'when receives an empty payload - action' do + let(:payload) { { 'comment' => {}, 'sender' => { 'login' => 'john' } } } + + before do + create(:group) + + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(fake_github_check) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: 'abc123' } }) + end + + it 'must returns error' do + expect(rerun.start).to eq([404, 'Action not found']) + end + end end describe 'Valid payload' do @@ -352,7 +374,7 @@ 'action' => 'created', 'comment' => { 'body' => 'CI:rerun 000000', - 'comment_id' => '10' + 'id' => '10' }, 'repository' => { 'full_name' => 'unit_test' }, 'issue' => { 'number' => '10' }, @@ -397,6 +419,70 @@ expect(rerun.start).to eq([402, 'No permission to run']) end end + + context 'when max_retries is reached but does not receive comment_id' do + let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } + let(:ci_jobs) do + [ + { name: 'First Test', job_ref: 'UNIT-TEST-FIRST-1', stage: fake_translation.bamboo_stage_name }, + { name: 'Checkout', job_ref: 'CHK-01', stage: fake_translation.bamboo_stage_name } + ] + end + let(:previous_check_suites) do + create_list(:check_suite, 5, + re_run: true, + pull_request: check_suite.pull_request, + work_branch: check_suite.work_branch) + end + let(:payload) do + { + 'action' => 'created', + 'comment' => { + 'body' => 'CI:rerun 000000' + }, + 'repository' => { 'full_name' => 'unit_test' }, + 'issue' => { 'number' => '10' }, + 'sender' => { 'login' => check_suite.pull_request.author } + } + end + let(:pull_request_commits) do + [ + { sha: check_suite.commit_sha_ref, date: Time.now } + ] + end + let(:group) { create(:group) } + + before do + previous_check_suites + group + allow(Octokit::Client).to receive(:new).and_return(fake_client) + allow(fake_client).to receive(:find_app_installations).and_return([{ 'id' => 1 }]) + allow(fake_client).to receive(:create_app_installation_access_token).and_return({ 'token' => 1 }) + + allow(Github::Check).to receive(:new).and_return(fake_github_check) + allow(fake_github_check).to receive(:create).and_return(check_suite) + allow(fake_github_check).to receive(:add_comment) + allow(fake_github_check).to receive(:cancelled) + allow(fake_github_check).to receive(:in_progress) + allow(fake_github_check).to receive(:comment_reaction_thumb_up) + allow(fake_github_check).to receive(:pull_request_info) + .and_return({ head: { ref: check_suite.work_branch } }) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + allow(fake_github_check).to receive(:comment_reaction_thumb_down) + + allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) + allow(fake_plan_run).to receive(:start_plan).and_return(200) + allow(fake_plan_run).to receive(:bamboo_reference).and_return('UNIT-TEST-1') + allow(fake_plan_run).to receive(:bamboo_reference).and_return('CHK-01') + + allow(BambooCi::StopPlan).to receive(:stop) + allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) + end + + it 'must returns error' do + expect(rerun.start).to eq([402, 'No permission to run']) + end + end end describe 'alternative scenarios' do From 8f1ecbe5d0b9fd819b1a683d642b37196d5db67f Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Thu, 21 Mar 2024 14:19:57 -0300 Subject: [PATCH 08/10] Rubocop Fixing lint issues Signed-off-by: Rodrigo Nardi --- spec/lib/github/re_run/command_spec.rb | 2 +- spec/lib/github/re_run/comment_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index e4c056a..f52de52 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -264,7 +264,7 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: check_suite.work_branch } }) + .and_return({ head: { ref: check_suite.work_branch } }) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(BambooCi::PlanRun).to receive(:new).and_return(fake_plan_run) diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 09a9330..b3ff52f 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -82,7 +82,7 @@ allow(fake_github_check).to receive(:create).and_return(fake_github_check) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: 'abc123' } }) + .and_return({ head: { ref: 'abc123' } }) end it 'must returns error' do @@ -466,7 +466,7 @@ allow(fake_github_check).to receive(:in_progress) allow(fake_github_check).to receive(:comment_reaction_thumb_up) allow(fake_github_check).to receive(:pull_request_info) - .and_return({ head: { ref: check_suite.work_branch } }) + .and_return({ head: { ref: check_suite.work_branch } }) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) allow(fake_github_check).to receive(:comment_reaction_thumb_down) From dfa4f06db1c1bd3a74629892a1bcc9d0feb3a7e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 2 Apr 2024 16:51:30 -0300 Subject: [PATCH 09/10] Ci Features Updating unit tests Signed-off-by: Rodrigo Nardi --- db/migrate/20240306110454_create_user.rb | 24 ------------ .../20240402114622_add_github_users_group.rb | 13 +++---- db/schema.rb | 37 +++++++------------ lib/github/build_plan.rb | 26 +++---------- lib/github/re_run/base.rb | 24 +----------- lib/github/user_info.rb | 35 ++++++++++-------- lib/models/github_user.rb | 2 + lib/models/group.rb | 2 +- spec/app/github_app_spec.rb | 4 +- spec/factories/{user.rb => github_user.rb} | 6 +-- spec/lib/github/build_plan_spec.rb | 9 ++--- spec/lib/github/re_run/command_spec.rb | 4 +- spec/lib/github/re_run/comment_spec.rb | 12 +++--- 13 files changed, 65 insertions(+), 133 deletions(-) delete mode 100644 db/migrate/20240306110454_create_user.rb rename lib/models/user.rb => db/migrate/20240402114622_add_github_users_group.rb (51%) rename spec/factories/{user.rb => github_user.rb} (77%) diff --git a/db/migrate/20240306110454_create_user.rb b/db/migrate/20240306110454_create_user.rb deleted file mode 100644 index 73d3138..0000000 --- a/db/migrate/20240306110454_create_user.rb +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause -# -# 20231214093515_create_user.rb -# Part of NetDEF CI System -# -# Copyright (c) 2023 by -# Network Device Education Foundation, Inc. ("NetDEF") -# -# frozen_string_literal: true - -class CreateUser < ActiveRecord::Migration[6.0] - def change - create_table :users do |t| - t.string :github_id, null: false - t.string :github_username, null: false - t.string :email, null: true - - t.timestamps - - t.references :company, index: true, foreign_key: true - t.references :group, index: true, foreign_key: true - end - end -end diff --git a/lib/models/user.rb b/db/migrate/20240402114622_add_github_users_group.rb similarity index 51% rename from lib/models/user.rb rename to db/migrate/20240402114622_add_github_users_group.rb index 1cbe674..eb92416 100644 --- a/lib/models/user.rb +++ b/db/migrate/20240402114622_add_github_users_group.rb @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # -# user.rb +# 20240402114622_add_github_users_group.rb # Part of NetDEF CI System # # Copyright (c) 2024 by @@ -8,11 +8,8 @@ # # frozen_string_literal: true -require 'otr-activerecord' - -class User < ActiveRecord::Base - belongs_to :company - belongs_to :group - - validates :github_id, presence: true, uniqueness: true +class AddGithubUsersGroup < ActiveRecord::Migration[6.0] + def change + add_reference :github_users, :group, foreign_key: true + end end diff --git a/db/schema.rb b/db/schema.rb index 4a942f5..f86f378 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[7.0].define(version: 2024_03_27_112035) do +ActiveRecord::Schema[7.0].define(version: 2024_04_02_114622) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -94,14 +94,6 @@ t.index ["group_id"], name: "index_features_on_group_id" end - create_table "groups", force: :cascade do |t| - t.string "name", null: false - t.boolean "public", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["public"], name: "index_groups_on_public", unique: true - end - create_table "github_users", force: :cascade do |t| t.string "github_login" t.string "github_username" @@ -113,7 +105,17 @@ t.string "organization_url" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "group_id" t.index ["github_id"], name: "index_github_users_on_github_id", unique: true + t.index ["group_id"], name: "index_github_users_on_group_id" + end + + create_table "groups", force: :cascade do |t| + t.string "name", null: false + t.boolean "public", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["public"], name: "index_groups_on_public", unique: true end create_table "plans", force: :cascade do |t| @@ -192,18 +194,6 @@ t.index ["ci_job_id"], name: "index_topotest_failures_on_ci_job_id" end - create_table "users", force: :cascade do |t| - t.string "github_id", null: false - t.string "github_username", null: false - t.string "email" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "company_id" - t.bigint "group_id" - t.index ["company_id"], name: "index_users_on_company_id" - t.index ["group_id"], name: "index_users_on_group_id" - end - add_foreign_key "audit_retries", "check_suites" add_foreign_key "audit_retries", "github_users" add_foreign_key "check_suites", "github_users" @@ -211,14 +201,13 @@ add_foreign_key "ci_jobs", "check_suites" add_foreign_key "ci_jobs", "stages" add_foreign_key "features", "groups" + add_foreign_key "github_users", "groups" add_foreign_key "plans", "check_suites" add_foreign_key "pull_request_subscriptions", "pull_requests" + add_foreign_key "pull_requests", "github_users" add_foreign_key "retry_stages", "check_suites" add_foreign_key "retry_stages", "stages" - add_foreign_key "pull_requests", "github_users" add_foreign_key "stages", "check_suites" add_foreign_key "stages", "stage_configurations" add_foreign_key "topotest_failures", "ci_jobs" - add_foreign_key "users", "companies" - add_foreign_key "users", "groups" end diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index e8db54f..de4b427 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -30,7 +30,6 @@ def initialize(payload, logger_level: Logger::INFO) raise "Invalid payload:\n#{payload}" if @payload.nil? or @payload.empty? @logger.debug 'This is a Pull Request - proceed with branch check' - create_user end def create @@ -70,23 +69,6 @@ def create private - def create_user - @user = User.find_by(github_username: @payload.dig('pull_request', 'user', 'login')) - - return unless @user.nil? - return if @payload.dig('pull_request', 'user', 'login').nil? - - github = Github::Check.new(nil) - github_user = github.fetch_username(@payload.dig('pull_request', 'user', 'login')) - - @user = - User.create( - github_username: @payload.dig('pull_request', 'user', 'login'), - github_id: github_user[:id], - group: Group.find_by(public: true) - ) - end - def fetch_pull_request @pull_request = PullRequest.find_by(github_pr_id: github_pr, repository: @payload.dig('repository', 'full_name')) @@ -102,16 +84,20 @@ def github_pr end def create_pull_request + user_info = Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id')) + @user = user_info.user + @pull_request = PullRequest.create( - author: @user&.github_username, + author: @user.github_login, github_pr_id: github_pr, branch_name: @payload.dig('pull_request', 'head', 'ref'), repository: @payload.dig('repository', 'full_name'), plan: fetch_plan ) - Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request) + user_info.add_pull_request(@pull_request) + @pull_request end def start_new_execution diff --git a/lib/github/re_run/base.rb b/lib/github/re_run/base.rb index bc03504..65546a8 100644 --- a/lib/github/re_run/base.rb +++ b/lib/github/re_run/base.rb @@ -28,32 +28,12 @@ def initialize(payload, logger_level: Logger::INFO) @logger_manager << GithubLogger.instance.create('github_app.log', logger_level) @payload = payload - @user = User.find_by(github_username: @payload.dig('comment', 'user', 'login')) - @user ||= User.find_by(github_username: @payload.dig('sender', 'login')) - create_user if @user.nil? + + @user = Github::UserInfo.new(@payload.dig('sender', 'id')).user end private - def create_user - github = Github::Check.new(nil) - github_user = github.fetch_username(@payload.dig('comment', 'user', 'login')) - github_user ||= github.fetch_username(@payload.dig('sender', 'login')) - - @user = User.find_by(github_id: github_user[:id]) - - puts ">>> Github user: #{@user.inspect}" - - return if valid_user_and_payload? github_user - - @user = - User.create( - github_username: @payload.dig('sender', 'login'), - github_id: github_user[:id], - group: Group.find_by(public: true) - ) - end - def valid_user_and_payload?(github_user) !@user.nil? or @payload.nil? or @payload.empty? or github_user.nil? or github_user.empty? end diff --git a/lib/github/user_info.rb b/lib/github/user_info.rb index b380f96..99b720a 100644 --- a/lib/github/user_info.rb +++ b/lib/github/user_info.rb @@ -10,6 +10,8 @@ module Github class UserInfo + attr_reader :user + def initialize(github_id, pull_request: nil, check_suite: nil, audit_retry: nil) @github_id = github_id @github = Github::Check.new nil @@ -27,6 +29,21 @@ def initialize(github_id, pull_request: nil, check_suite: nil, audit_retry: nil) fetch end + def add_pull_request(pull_request = @pull_request) + @user.pull_requests << pull_request + @user.save + end + + def add_check_suite(check_suite = @check_suite) + @user.check_suites << check_suite + @user.save + end + + def add_retry(audit_retry = @audit_retry) + @user.audit_retries << audit_retry + @user.save + end + private def fetch @@ -43,21 +60,6 @@ def fetch add_retry unless @audit_retry.nil? end - def add_pull_request - @user.pull_requests << @pull_request - @user.save - end - - def add_check_suite - @user.check_suites << @check_suite - @user.save - end - - def add_retry - @user.audit_retries << @audit_retry - @user.save - end - def update_user_info @user.update( github_login: @info[:login], @@ -78,7 +80,8 @@ def create_user_info github_email: @info[:email], github_type: @info[:type], organization_url: @info[:organizations_url], - github_organization: @info[:company] + github_organization: @info[:company], + group: Group.find_by(public: true) ) end end diff --git a/lib/models/github_user.rb b/lib/models/github_user.rb index e74b5f1..8d30f0a 100644 --- a/lib/models/github_user.rb +++ b/lib/models/github_user.rb @@ -14,4 +14,6 @@ class GithubUser < ActiveRecord::Base has_many :pull_requests, dependent: :nullify has_many :check_suites, dependent: :nullify has_many :audit_retries, dependent: :nullify + + belongs_to :group end diff --git a/lib/models/group.rb b/lib/models/group.rb index 4c68487..5b01e87 100644 --- a/lib/models/group.rb +++ b/lib/models/group.rb @@ -11,6 +11,6 @@ require 'otr-activerecord' class Group < ActiveRecord::Base - has_many :users + has_many :github_users, dependent: :nullify has_one :feature end diff --git a/spec/app/github_app_spec.rb b/spec/app/github_app_spec.rb index f3d62a5..9086558 100644 --- a/spec/app/github_app_spec.rb +++ b/spec/app/github_app_spec.rb @@ -209,7 +209,7 @@ } end - let(:user) { create(:user) } + let(:user) { create(:github_user) } let(:payload) do { x: 1, @@ -249,7 +249,7 @@ } end - let(:user) { create(:user) } + let(:user) { create(:github_user) } let(:payload) do { x: 1, diff --git a/spec/factories/user.rb b/spec/factories/github_user.rb similarity index 77% rename from spec/factories/user.rb rename to spec/factories/github_user.rb index cd96ca2..38cd52e 100644 --- a/spec/factories/user.rb +++ b/spec/factories/github_user.rb @@ -1,6 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # -# user.rb +# github_user.rb # Part of NetDEF CI System # # Copyright (c) 2023 by @@ -9,8 +9,8 @@ # frozen_string_literal: true FactoryBot.define do - factory :user do - github_id { Faker::Crypto.md5 } + factory :github_user do + github_login { Faker::Crypto.md5 } github_username { Faker::Name.name } group { create(:group) } end diff --git a/spec/lib/github/build_plan_spec.rb b/spec/lib/github/build_plan_spec.rb index 6078f23..166334e 100644 --- a/spec/lib/github/build_plan_spec.rb +++ b/spec/lib/github/build_plan_spec.rb @@ -30,6 +30,7 @@ 'number' => pr_number, 'pull_request' => { 'user' => { + 'id' => 123, 'login' => author }, @@ -63,13 +64,9 @@ allow(fake_github_check).to receive(:create).and_return(fake_check_run) allow(fake_github_check).to receive(:in_progress).and_return(fake_check_run) allow(fake_github_check).to receive(:queued).and_return(fake_check_run) - allow(fake_github_check).to receive(:fetch_username).and_return({}) + allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1, login: author, name: author }) allow(BambooCi::RunningPlan).to receive(:fetch).with(fake_plan_run.bamboo_reference).and_return(ci_jobs) - - allow(Github::Check).to receive(:new).and_return(fake_github_check) - allow(fake_github_check).to receive(:create).and_return(fake_check_run) - allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) end context 'when action is opened' do @@ -323,6 +320,8 @@ allow(Github::Check).to receive(:new).and_return(fake_github_check) allow(fake_github_check).to receive(:create).and_return(fake_check_run) allow(fake_github_check).to receive(:fetch_username).and_return({ id: 1 }) + + create(:group) end it 'must returns an error' do diff --git a/spec/lib/github/re_run/command_spec.rb b/spec/lib/github/re_run/command_spec.rb index 57ce51b..966eba3 100644 --- a/spec/lib/github/re_run/command_spec.rb +++ b/spec/lib/github/re_run/command_spec.rb @@ -69,7 +69,7 @@ let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } let(:feature) { create(:feature, max_rerun_per_pull_request: 0) } let(:group) { create(:group, feature: feature) } - let(:user) { create(:user, github_username: check_suite.pull_request.author, group: group) } + let(:user) { create(:github_user, github_username: check_suite.pull_request.author, group: group) } let(:fake_unavailable_jobs) { Github::Build::UnavailableJobs.new(check_suite) } before do @@ -135,7 +135,7 @@ } end let(:check_suites) { CheckSuite.where(commit_sha_ref: check_suite.commit_sha_ref) } - let(:user) { create(:user, github_username: check_suite.pull_request.author) } + let(:user) { create(:github_user, github_username: check_suite.pull_request.author) } before do user diff --git a/spec/lib/github/re_run/comment_spec.rb b/spec/lib/github/re_run/comment_spec.rb index 643325e..a5074ff 100644 --- a/spec/lib/github/re_run/comment_spec.rb +++ b/spec/lib/github/re_run/comment_spec.rb @@ -97,7 +97,7 @@ let(:fake_translation) { create(:stage_configuration) } context 'when receives a valid command' do - let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let!(:user) { create(:github_user, github_username: check_suite.pull_request.author) } let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } let(:ci_jobs) do @@ -150,7 +150,7 @@ end context 'when receives a valid command but can save' do - let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let!(:user) { create(:github_user, github_username: check_suite.pull_request.author) } let(:pull_request) { create(:pull_request) } let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: pull_request) } @@ -205,7 +205,7 @@ end context 'when has two opened PRs' do - let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let!(:user) { create(:github_user, github_username: check_suite.pull_request.author) } let(:first_pr) { create(:pull_request, github_pr_id: 12, id: 11, repository: 'test') } let(:second_pr) { create(:pull_request, github_pr_id: 13, id: 12, repository: 'test') } let(:check_suite) { create(:check_suite, :with_running_ci_jobs, pull_request: first_pr) } @@ -287,7 +287,7 @@ end context 'when you receive an comment' do - let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let!(:user) { create(:github_user, github_username: check_suite.pull_request.author) } let(:check_suite) { create(:check_suite, :with_running_ci_jobs) } let(:check_suite_rerun) { CheckSuite.find_by(commit_sha_ref: check_suite.commit_sha_ref, re_run: true) } @@ -515,7 +515,7 @@ end context 'when you receive an comment and does not exist a PR' do - let!(:user) { create(:user) } + let!(:user) { create(:github_user) } let(:commit_sha) { Faker::Internet.uuid } @@ -566,7 +566,7 @@ end context 'when can not save check_suite' do - let!(:user) { create(:user, github_username: check_suite.pull_request.author) } + let!(:user) { create(:github_user, github_username: check_suite.pull_request.author) } let(:commit_sha) { Faker::Internet.uuid } let(:check_suite) { create(:check_suite) } From 5c9871ad16f8255277e6cd514d591ed3f98d3c81 Mon Sep 17 00:00:00 2001 From: Rodrigo Nardi Date: Tue, 2 Apr 2024 17:00:16 -0300 Subject: [PATCH 10/10] Ci Features Updating unit tests Signed-off-by: Rodrigo Nardi --- lib/github/build_plan.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/github/build_plan.rb b/lib/github/build_plan.rb index de4b427..298717b 100644 --- a/lib/github/build_plan.rb +++ b/lib/github/build_plan.rb @@ -84,8 +84,7 @@ def github_pr end def create_pull_request - user_info = Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id')) - @user = user_info.user + @user = Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id')).user @pull_request = PullRequest.create( @@ -96,7 +95,7 @@ def create_pull_request plan: fetch_plan ) - user_info.add_pull_request(@pull_request) + Github::UserInfo.new(@payload.dig('pull_request', 'user', 'id'), pull_request: @pull_request) @pull_request end