From 4b6e18ed7a6727b849eac7a63c6feedc0b53f0b6 Mon Sep 17 00:00:00 2001 From: Jeff McFadden Date: Tue, 21 May 2024 15:17:00 -0700 Subject: [PATCH 1/5] Add documentation all over and remove unused testing methods. --- lib/ghx.rb | 19 +++++++ lib/ghx/dependabot.rb | 10 ++++ lib/ghx/dependabot/alert.rb | 3 ++ lib/ghx/dependabot/package.rb | 2 + lib/ghx/dependabot/security_vulnerability.rb | 2 + lib/ghx/graphql_client.rb | 56 +++----------------- lib/ghx/issue.rb | 20 +++++++ lib/ghx/project_item.rb | 46 ++++++++++------ lib/ghx/rest_client.rb | 19 +++++-- lib/version.rb | 2 +- 10 files changed, 111 insertions(+), 68 deletions(-) diff --git a/lib/ghx.rb b/lib/ghx.rb index 15ed9df..1a01f7e 100644 --- a/lib/ghx.rb +++ b/lib/ghx.rb @@ -25,34 +25,53 @@ def symbolize_keys! # Extra classes to support more OO interfaces to the GitHub API. Wraps both the REST API and GraphQL API. Currently # incomplete. Functionality has been built for our existing use-cases, but nothing else. module GHX + + # Defaults to $stdout + # @return [Logger] def self.logger @logger ||= Logger.new($stdout) end + # @param logger [Logger] def self.logger=(logger) @logger = logger end + # Internal octokit client. + # API Key defaults to ENV["GITHUB_TOKEN"] + # @return [Octokit::Client] def self.octokit @octokit ||= Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) end + # @param octokit [Octokit::Client] + # @return [Octokit::Client] def self.octokit=(octokit) @octokit = octokit end + # Internal graphql client. + # API Key defaults to ENV["GITHUB_TOKEN"] + # @return [GHX::GraphqlClient] def self.graphql @graphql ||= GHX::GraphqlClient.new(ENV["GITHUB_GRAPHQL_TOKEN"]) end + # @param graphql [GHX::GraphqlClient] + # @return [GHX::GraphqlClient] def self.graphql=(graphql) @graphql = graphql end + # Internal graphql client. + # API Key defaults to ENV["GITHUB_TOKEN"] + # @return [GHX::RestClient] def self.rest_client @rest_client ||= GHX::RestClient.new(ENV["GITHUB_TOKEN"]) end + # @param rest_client [GHX::RestClient] + # @return [GHX::RestClient] def self.rest_client=(rest_client) @rest_client = rest_client end diff --git a/lib/ghx/dependabot.rb b/lib/ghx/dependabot.rb index eba3708..25a50a3 100644 --- a/lib/ghx/dependabot.rb +++ b/lib/ghx/dependabot.rb @@ -1,6 +1,16 @@ module GHX + # Module for Dependabot-related classes and methods module Dependabot + + # Get Dependabot alerts for a given repository + # + # @note ONLY RETURNS THE FIRST 100 ALERTS + # @param owner [String] the owner of the repository + # @param repo [String] the repository name + # @return [Array] the alerts def self.get_alerts(owner:, repo:) + # TODO: Add pagination to get all alerts in one go + GHX.rest_client.get("repos/#{owner}/#{repo}/dependabot/alerts?state=open&per_page=100").map do |alert| GHX::Dependabot::Alert.new(alert) end diff --git a/lib/ghx/dependabot/alert.rb b/lib/ghx/dependabot/alert.rb index 7df3572..7e13f4b 100644 --- a/lib/ghx/dependabot/alert.rb +++ b/lib/ghx/dependabot/alert.rb @@ -115,9 +115,12 @@ module GHX module Dependabot + + # A Dependabot Alert class Alert attr_reader :number, :state, :dependency, :security_advisory, :security_vulnerability, :url, :html_url, :created_at, :updated_at + # @param json_data [Hash] The JSON data for the alert, from the API def initialize(json_data) @number = json_data["number"] @state = json_data["state"] diff --git a/lib/ghx/dependabot/package.rb b/lib/ghx/dependabot/package.rb index a4f6368..6c52faa 100644 --- a/lib/ghx/dependabot/package.rb +++ b/lib/ghx/dependabot/package.rb @@ -1,5 +1,7 @@ module GHX module Dependabot + + # A package is a dependency that is managed by a package manager. Referenced by a SecurityVulnerability. class Package attr_reader :ecosystem, :name diff --git a/lib/ghx/dependabot/security_vulnerability.rb b/lib/ghx/dependabot/security_vulnerability.rb index e59df6d..1f42c9a 100644 --- a/lib/ghx/dependabot/security_vulnerability.rb +++ b/lib/ghx/dependabot/security_vulnerability.rb @@ -1,5 +1,7 @@ module GHX module Dependabot + + # A SecurityVulnerability is referenced by an Alert class SecurityVulnerability attr_reader :package, :severity, :vulnerable_version_range, :first_patched_version diff --git a/lib/ghx/graphql_client.rb b/lib/ghx/graphql_client.rb index de80c62..9e50557 100644 --- a/lib/ghx/graphql_client.rb +++ b/lib/ghx/graphql_client.rb @@ -1,9 +1,16 @@ module GHX + + # Internal class to interact with the GitHub GraphQL API class GraphqlClient + + # @param api_key [String] def initialize(api_key) @api_key = api_key end + # Perform a GraphQL Query and return the result + # @param query [String] GraphQL Query + # @return [Net::HTTPResponse] def query(query) uri = URI("https://api.github.com/graphql") req = Net::HTTP::Post.new(uri) @@ -15,54 +22,5 @@ def query(query) http.request(req) end end - - # @param [String] project_id - # @param [GithubProjectItem] project_item - # @param [DateTime] reported_at - def update_project_item_reported_at(project_item_id:, reported_at:, project_id: GithubProject::MAIN_GH_PROJECT_ID) - field_id = "PVTF_lADOALH_aM4Ac-_zzgSzAZs" # project_item.field_map["Reported At"] - - gql_query = <<~GQL - mutation { - updateProjectV2ItemFieldValue(input: { - fieldId: "#{field_id}", - itemId: "#{project_item_id}", - projectId: "#{project_id}", - value: { - date: "#{reported_at.to_date}" - } - }) { - projectV2Item { - id - } - } - } - GQL - - query(gql_query) - end - - def update_project_item_reported_by(project_item_id:, reported_by:, project_id: GithubProject::MAIN_GH_PROJECT_ID) - field_id = "PVTF_lADOALH_aM4Ac-_zzgSzBcc" # project_item.field_map["Reporter"] - - gql_query = <<~GQL - mutation { - updateProjectV2ItemFieldValue(input: { - fieldId: "#{field_id}", - itemId: "#{project_item_id}", - projectId: "#{project_id}", - value: { - text: "#{reported_by}" - } - }) { - projectV2Item { - id - } - } - } - GQL - - query(gql_query) - end end end diff --git a/lib/ghx/issue.rb b/lib/ghx/issue.rb index 5647be9..328703d 100644 --- a/lib/ghx/issue.rb +++ b/lib/ghx/issue.rb @@ -1,7 +1,13 @@ module GHX + # A GitHub Issue class Issue attr_accessor :owner, :repo, :number, :title, :body, :state, :state_reason, :author, :assignees, :labels, :milestone, :created_at, :updated_at, :closed_at + # Search for issues in a repository + # @param owner [String] the owner of the repository + # @param repo [String] the repository name + # @param query [String] the search query, using GitHub's search syntax + # @return [Array] the issues found def self.search(owner:, repo:, query:) data = GHX.rest_client.get("search/issues?q=#{URI.encode_www_form_component(query)}+is:issue+repo:#{owner}/#{repo}") data.fetch("items").to_a.map do |issue_data| @@ -13,17 +19,31 @@ def self.search(owner:, repo:, query:) [] end + # Find an issue by its number + # @param owner [String] the owner of the repository + # @param repo [String] the repository name + # @param number [Integer] the issue number + # @return [Issue] the issue found def self.find(owner:, repo:, number:) response_data = GHX.rest_client.get("repos/#{owner}/#{repo}/issues/#{number}") new(owner: owner, repo: repo, **response_data) end + # @param owner [String] the owner of the repository + # @param repo [String] the repository name + # @param **args [Hash] the attributes of the issue you wish to assign + # @return [Issue] the new issue def initialize(owner:, repo:, **args) @owner = owner @repo = repo update_attributes(args) end + # Save the issue to GitHub. Handles both creating and updating. + # + # If the issue has a number, it will be updated. Otherwise, it will be created. + # + # @return [Issue] the saved issue def save @number.nil? ? create : update end diff --git a/lib/ghx/project_item.rb b/lib/ghx/project_item.rb index 4da1a14..7c4410b 100644 --- a/lib/ghx/project_item.rb +++ b/lib/ghx/project_item.rb @@ -1,7 +1,15 @@ module GHX + + # A GitHub Project Item. This is a single item in a GitHub Project Board. + # + # ProjectsV2 are only available via the GraphQL API. This class wraps access to the API and provides a more OO interface. + # + # @note Access to ProjectItems should be done largely (if not always) through the Project class. class ProjectItem attr_accessor :id, :project_id, :issue_number, :issue_title, :issue_url, :issue_state, :field_values, :field_map + # @param field_configuration [Array] An array of field configurations. These are the fields that are available to the Project Item. These are provided via the Project itself. It's much easier to access ProjectItems through the project because of this. + # @param data [Hash] The data from the GraphQL API. def initialize(field_configuration:, data:) _setup_field_configuration(field_configuration) @@ -33,7 +41,11 @@ def initialize(field_configuration:, data:) end end - # Updates the given fields to the given values. Makes a GraphQL call per field to do the update. + # Updates the given fields to the given values. Makes a GraphQL call *per field* to do the update. + # + # The implementation wraps access to the various types for each field. Since GraphQL requires us to type match on all + # requests, this gives us convenience, especially for things like a select field. We can pass in the value, and the + # method will find the ID of the option and update the field for us. # # @param fields [Hash] A hash of field names to values. def update(**fields) @@ -138,23 +150,27 @@ def update_single_select_field(field_id, value) private + # Parse the field_configuration and set up the instance variables and accessors. + # + # Example field_configuration: + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCno", :name=>"Title", :data_type=>"TITLE", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCns", :name=>"Assignees", :data_type=>"ASSIGNEES", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzAZs", :name=>"Reported At", :data_type=>"DATE", :options=>nil} + # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCnw", :name=>"Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"f971fb55", :name=>"To triage"}, {:id=>"856cdede", :name=>"Ready to Assign"}, {:id=>"f75ad846", :name=>"Assigned"}, {:id=>"47fc9ee4", :name=>"Fix In progress"}, {:id=>"5ef0dc97", :name=>"Additional Info Requested"}, {:id=>"98236657", :name=>"Done - Fixed"}, {:id=>"98aea6ad", :name=>"Done - Won't Fix"}, {:id=>"a3b4fc3a", :name=>"Duplicate"}, {:id=>"81377549", :name=>"Not a Vulnerability"}]} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn0", :name=>"Labels", :data_type=>"LABELS", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn4", :name=>"Linked pull requests", :data_type=>"LINKED_PULL_REQUESTS", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn8", :name=>"Milestone", :data_type=>"MILESTONE", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoA", :name=>"Repository", :data_type=>"REPOSITORY", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoM", :name=>"Reviewers", :data_type=>"REVIEWERS", :options=>nil} + # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCuA", :name=>"Severity", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"79628723", :name=>"Informational"}, {:id=>"153889c6", :name=>"Low"}, {:id=>"093709ee", :name=>"Medium"}, {:id=>"5a00bbe7", :name=>"High"}, {:id=>"00e0bbaf", :name=>"Critical"}, {:id=>"fd986bd9", :name=>"Duplicate"}]} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBcc", :name=>"Reporter", :data_type=>"TEXT", :options=>nil} + # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBho", :name=>"Resolve By", :data_type=>"DATE", :options=>nil} + # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgTKjOw", :name=>"Payout Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"53c47c02", :name=>"Ready for Invoice"}, {:id=>"0b8a4629", :name=>"Payout in Process"}, {:id=>"5f356a58", :name=>"Payout Complete"}, {:id=>"368048ac", :name=>"Ineligible for Payout"}]} + # + # def _setup_field_configuration(field_configuration) @field_configuration = field_configuration.map { |fc| fc.merge({normalized_name: normalized_field_value_name(fc[:name])}) } - # Example field_configuration: - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCno", :name=>"Title", :data_type=>"TITLE", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCns", :name=>"Assignees", :data_type=>"ASSIGNEES", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzAZs", :name=>"Reported At", :data_type=>"DATE", :options=>nil} - # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCnw", :name=>"Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"f971fb55", :name=>"To triage"}, {:id=>"856cdede", :name=>"Ready to Assign"}, {:id=>"f75ad846", :name=>"Assigned"}, {:id=>"47fc9ee4", :name=>"Fix In progress"}, {:id=>"5ef0dc97", :name=>"Additional Info Requested"}, {:id=>"98236657", :name=>"Done - Fixed"}, {:id=>"98aea6ad", :name=>"Done - Won't Fix"}, {:id=>"a3b4fc3a", :name=>"Duplicate"}, {:id=>"81377549", :name=>"Not a Vulnerability"}]} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn0", :name=>"Labels", :data_type=>"LABELS", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn4", :name=>"Linked pull requests", :data_type=>"LINKED_PULL_REQUESTS", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn8", :name=>"Milestone", :data_type=>"MILESTONE", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoA", :name=>"Repository", :data_type=>"REPOSITORY", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoM", :name=>"Reviewers", :data_type=>"REVIEWERS", :options=>nil} - # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCuA", :name=>"Severity", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"79628723", :name=>"Informational"}, {:id=>"153889c6", :name=>"Low"}, {:id=>"093709ee", :name=>"Medium"}, {:id=>"5a00bbe7", :name=>"High"}, {:id=>"00e0bbaf", :name=>"Critical"}, {:id=>"fd986bd9", :name=>"Duplicate"}]} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBcc", :name=>"Reporter", :data_type=>"TEXT", :options=>nil} - # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBho", :name=>"Resolve By", :data_type=>"DATE", :options=>nil} - # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgTKjOw", :name=>"Payout Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"53c47c02", :name=>"Ready for Invoice"}, {:id=>"0b8a4629", :name=>"Payout in Process"}, {:id=>"5f356a58", :name=>"Payout Complete"}, {:id=>"368048ac", :name=>"Ineligible for Payout"}]} @field_configuration.each do |field| next unless field[:name] next if field[:name].to_s.empty? diff --git a/lib/ghx/rest_client.rb b/lib/ghx/rest_client.rb index 694e4c0..0accc4c 100644 --- a/lib/ghx/rest_client.rb +++ b/lib/ghx/rest_client.rb @@ -1,14 +1,21 @@ require "net/http" module GHX + + # RestClient is a simple wrapper around Net::HTTP to make it easier to make API calls to the GitHub REST API. + # + # This is necessary because not all GitHub API endpoints are covered by Octokit. class RestClient attr_reader :api_key + # @param api_key [String] the GitHub API key def initialize(api_key) @api_key = api_key end - # @return [Hash] the JSON response + # Make a GET request to the given path + # @param path [String] the path to the API endpoint + # @return [Hash] the parsed JSON response def get(path) uri = URI.parse("https://api.github.com/#{path}") request = Net::HTTP::Get.new(uri) @@ -27,7 +34,10 @@ def get(path) JSON.parse(response.body) end - # @return [Hash] the JSON response + # Make a POST request to the given path with the given params + # @param path [String] the path to the API endpoint + # @param params [Hash] the request body + # @return [Hash] the parsed JSON response def post(path, params) uri = URI.parse("https://api.github.com/#{path}") request = Net::HTTP::Post.new(uri) @@ -48,7 +58,10 @@ def post(path, params) JSON.parse(response.body) end - # @return [Hash] the JSON response + # Make a PATCH request to the given path with the given params + # @param path [String] the path to the API endpoint + # @param params [Hash] the request body + # @return [Hash] the parsed JSON response def patch(path, params) uri = URI.parse("https://api.github.com/#{path}") request = Net::HTTP::Patch.new(uri) diff --git a/lib/version.rb b/lib/version.rb index c11cda7..e0902bb 100644 --- a/lib/version.rb +++ b/lib/version.rb @@ -1,3 +1,3 @@ module GHX - VERSION = "0.2.0" + VERSION = "0.2.1" end From f8e9f4ecbbf7e88f67b1bcae118df20ae54707cb Mon Sep 17 00:00:00 2001 From: Jeff McFadden Date: Tue, 21 May 2024 15:28:00 -0700 Subject: [PATCH 2/5] Standard-ize, add tests action. --- .github/pull_request_template.md | 9 ++++++ .github/workflows/standardrb.yml | 5 --- .github/workflows/tests.yml | 32 ++++++++++++++++++++ Gemfile.lock | 2 +- lib/ghx.rb | 1 - lib/ghx/dependabot.rb | 1 - lib/ghx/dependabot/alert.rb | 1 - lib/ghx/dependabot/package.rb | 1 - lib/ghx/dependabot/security_vulnerability.rb | 1 - lib/ghx/graphql_client.rb | 2 -- lib/ghx/project_item.rb | 1 - lib/ghx/rest_client.rb | 1 - script/test | 5 +++ test/core_test.rb | 29 ++++++++++++++++++ 14 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/tests.yml create mode 100755 script/test create mode 100644 test/core_test.rb diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..62a4eb5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ +### Description + + +### Reason/Reference + \ No newline at end of file diff --git a/.github/workflows/standardrb.yml b/.github/workflows/standardrb.yml index d5d61c5..9058430 100644 --- a/.github/workflows/standardrb.yml +++ b/.github/workflows/standardrb.yml @@ -4,11 +4,6 @@ name: Standardrb # Controls when the action will run. on: - push: - paths-ignore: - - '.github/**' - - 'nginx/**' - branches: [main] pull_request: types: [opened, reopened, synchronize] merge_group: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5bd1b4b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +# This is a basic workflow to help you get started with Actions + +name: Tests + +# Controls when the action will run. +on: + pull_request: + types: [opened, reopened, synchronize] + merge_group: + types: [checks_requested] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + env: + BUNDLE_DEPLOYMENT: false + BUNDLE_FROZEN: false + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + # Disabled for now because of an issue installing ruby-debug-ide + # with: + # bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - run: | + bundle install + script/test diff --git a/Gemfile.lock b/Gemfile.lock index 3b62534..0386c9c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ghx (0.0.4) + ghx (0.2.1) faraday (~> 2.9.0) faraday-retry (~> 2.2.1) octokit diff --git a/lib/ghx.rb b/lib/ghx.rb index 1a01f7e..cf533ba 100644 --- a/lib/ghx.rb +++ b/lib/ghx.rb @@ -25,7 +25,6 @@ def symbolize_keys! # Extra classes to support more OO interfaces to the GitHub API. Wraps both the REST API and GraphQL API. Currently # incomplete. Functionality has been built for our existing use-cases, but nothing else. module GHX - # Defaults to $stdout # @return [Logger] def self.logger diff --git a/lib/ghx/dependabot.rb b/lib/ghx/dependabot.rb index 25a50a3..81447c6 100644 --- a/lib/ghx/dependabot.rb +++ b/lib/ghx/dependabot.rb @@ -1,7 +1,6 @@ module GHX # Module for Dependabot-related classes and methods module Dependabot - # Get Dependabot alerts for a given repository # # @note ONLY RETURNS THE FIRST 100 ALERTS diff --git a/lib/ghx/dependabot/alert.rb b/lib/ghx/dependabot/alert.rb index 7e13f4b..caa5c69 100644 --- a/lib/ghx/dependabot/alert.rb +++ b/lib/ghx/dependabot/alert.rb @@ -115,7 +115,6 @@ module GHX module Dependabot - # A Dependabot Alert class Alert attr_reader :number, :state, :dependency, :security_advisory, :security_vulnerability, :url, :html_url, :created_at, :updated_at diff --git a/lib/ghx/dependabot/package.rb b/lib/ghx/dependabot/package.rb index 6c52faa..ca2cd08 100644 --- a/lib/ghx/dependabot/package.rb +++ b/lib/ghx/dependabot/package.rb @@ -1,6 +1,5 @@ module GHX module Dependabot - # A package is a dependency that is managed by a package manager. Referenced by a SecurityVulnerability. class Package attr_reader :ecosystem, :name diff --git a/lib/ghx/dependabot/security_vulnerability.rb b/lib/ghx/dependabot/security_vulnerability.rb index 1f42c9a..60464c7 100644 --- a/lib/ghx/dependabot/security_vulnerability.rb +++ b/lib/ghx/dependabot/security_vulnerability.rb @@ -1,6 +1,5 @@ module GHX module Dependabot - # A SecurityVulnerability is referenced by an Alert class SecurityVulnerability attr_reader :package, :severity, :vulnerable_version_range, :first_patched_version diff --git a/lib/ghx/graphql_client.rb b/lib/ghx/graphql_client.rb index 9e50557..399f93f 100644 --- a/lib/ghx/graphql_client.rb +++ b/lib/ghx/graphql_client.rb @@ -1,8 +1,6 @@ module GHX - # Internal class to interact with the GitHub GraphQL API class GraphqlClient - # @param api_key [String] def initialize(api_key) @api_key = api_key diff --git a/lib/ghx/project_item.rb b/lib/ghx/project_item.rb index 7c4410b..0e8e75d 100644 --- a/lib/ghx/project_item.rb +++ b/lib/ghx/project_item.rb @@ -1,5 +1,4 @@ module GHX - # A GitHub Project Item. This is a single item in a GitHub Project Board. # # ProjectsV2 are only available via the GraphQL API. This class wraps access to the API and provides a more OO interface. diff --git a/lib/ghx/rest_client.rb b/lib/ghx/rest_client.rb index 0accc4c..b92195a 100644 --- a/lib/ghx/rest_client.rb +++ b/lib/ghx/rest_client.rb @@ -1,7 +1,6 @@ require "net/http" module GHX - # RestClient is a simple wrapper around Net::HTTP to make it easier to make API calls to the GitHub REST API. # # This is necessary because not all GitHub API endpoints are covered by Octokit. diff --git a/script/test b/script/test new file mode 100755 index 0000000..1a33e1a --- /dev/null +++ b/script/test @@ -0,0 +1,5 @@ +#!/bin/bash + +# Run minitest against all the files in the test dir +# This script is intended to be run from the root of the project +bundle exec ruby -Ilib:test test/*_test.rb \ No newline at end of file diff --git a/test/core_test.rb b/test/core_test.rb new file mode 100644 index 0000000..9792158 --- /dev/null +++ b/test/core_test.rb @@ -0,0 +1,29 @@ +require "minitest/autorun" +require "ghx" + +class CoreTest < Minitest::Test + def test_that_code_loads + GHX::Issue.new(owner: "test", repo: "test", title: "test") + GHX::Project.new("asdf1234") + + project_item_data = { + "content" => { + "id" => "asdf1234", + "number" => "1234", + "title" => "test", + "url" => "http://example.com", + "state" => "open" + }, + "project" => { + "id" => "asdf1234" + }, + "fieldValues" => { + "nodes" => [] + } + } + + GHX::ProjectItem.new(field_configuration: [], data: project_item_data) + + assert true + end +end From 0095ff43996e11b51b71bbc4a2d62f68e8599530 Mon Sep 17 00:00:00 2001 From: Jeff McFadden Date: Tue, 21 May 2024 15:29:47 -0700 Subject: [PATCH 3/5] Fix actions. --- .github/workflows/standardrb.yml | 6 +++--- .github/workflows/tests.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/standardrb.yml b/.github/workflows/standardrb.yml index 9058430..1ff8175 100644 --- a/.github/workflows/standardrb.yml +++ b/.github/workflows/standardrb.yml @@ -24,9 +24,9 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - # Disabled for now because of an issue installing ruby-debug-ide - # with: - # bundler-cache: true # runs 'bundle install' and caches installed gems automatically + with: + ruby-version: '3.3' # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - run: | bundle install bundle exec standardrb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5bd1b4b..aeedb05 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,9 +24,9 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 - # Disabled for now because of an issue installing ruby-debug-ide - # with: - # bundler-cache: true # runs 'bundle install' and caches installed gems automatically + with: + ruby-version: '3.3' # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - run: | bundle install script/test From 5a8e5aa40d44363e7257774c6aa7d311aa7258d9 Mon Sep 17 00:00:00 2001 From: Jeff McFadden Date: Tue, 21 May 2024 15:44:38 -0700 Subject: [PATCH 4/5] Test a bit more code loading, and use rake task to do it. --- .github/workflows/tests.yml | 2 +- Rakefile | 4 ++ script/test | 5 -- test/core_test.rb | 2 + test/dependabot_core_test.rb | 128 +++++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 6 deletions(-) delete mode 100755 script/test create mode 100644 test/dependabot_core_test.rb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aeedb05..434527e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,4 +29,4 @@ jobs: bundler-cache: true # runs 'bundle install' and caches installed gems automatically - run: | bundle install - script/test + bundle exec rake test diff --git a/Rakefile b/Rakefile index 2480d0e..fb5c708 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,6 @@ require "bundler" Bundler::GemHelper.install_tasks + +task :test do + Dir.glob('./test/*_test.rb').each { |file| require file} +end \ No newline at end of file diff --git a/script/test b/script/test deleted file mode 100755 index 1a33e1a..0000000 --- a/script/test +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Run minitest against all the files in the test dir -# This script is intended to be run from the root of the project -bundle exec ruby -Ilib:test test/*_test.rb \ No newline at end of file diff --git a/test/core_test.rb b/test/core_test.rb index 9792158..ed204ab 100644 --- a/test/core_test.rb +++ b/test/core_test.rb @@ -2,6 +2,8 @@ require "ghx" class CoreTest < Minitest::Test + + # The most basic test to ensure that the code loads def test_that_code_loads GHX::Issue.new(owner: "test", repo: "test", title: "test") GHX::Project.new("asdf1234") diff --git a/test/dependabot_core_test.rb b/test/dependabot_core_test.rb new file mode 100644 index 0000000..700742a --- /dev/null +++ b/test/dependabot_core_test.rb @@ -0,0 +1,128 @@ +require "minitest/autorun" +require "ghx" + +class DependabotCoreTest < Minitest::Test + + # The most basic test to ensure that the code loads + def test_that_code_loads + GHX::Dependabot::Alert.new(sample_dependabot_response) + + assert true + end + + def sample_dependabot_response + { + "number": 321, + "state": "open", + "dependency": { + "package": { + "ecosystem": "npm", + "name": "react-pdf" + }, + "manifest_path": "yarn.lock", + "scope": "runtime" + }, + "security_advisory": { + "ghsa_id": "GHSA-87hq-q4gp-9wr4", + "cve_id": "CVE-2024-34342", + "summary": "react-pdf vulnerable to arbitrary JavaScript execution upon opening a malicious PDF with PDF.js", + "description": "### Summary\n\nIf PDF.js is used to load a malicious PDF, and PDF.js is configured with `isEvalSupported` set to `true` (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.\n\n### Patches\n[This patch](https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad) forces `isEvalSupported` to `false`, removing the attack vector.\n\n### Workarounds\nSet `options.isEvalSupported` to `false`, where `options` is `Document` component prop.\n\n### References\n- [GHSA-wgrm-67xf-hhpq](https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq)\n- https://github.com/mozilla/pdf.js/pull/18015\n- https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6\n- https://bugzilla.mozilla.org/show_bug.cgi?id=1893645", + "severity": "high", + "identifiers": [ + { + "value": "GHSA-87hq-q4gp-9wr4", + "type": "GHSA" + }, + { + "value": "CVE-2024-34342", + "type": "CVE" + } + ], + "references": [ + { + "url": "https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq" + }, + { + "url": "https://github.com/wojtekmaj/react-pdf/security/advisories/GHSA-87hq-q4gp-9wr4" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-34342" + }, + { + "url": "https://github.com/mozilla/pdf.js/pull/18015" + }, + { + "url": "https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6" + }, + { + "url": "https://github.com/wojtekmaj/react-pdf/commit/208f28dd47fe38c33ce4bac4205b2b0a0bb207fe" + }, + { + "url": "https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad" + }, + { + "url": "https://github.com/advisories/GHSA-87hq-q4gp-9wr4" + } + ], + "published_at": "2024-05-07T16:48:59Z", + "updated_at": "2024-05-08T10:10:23Z", + "withdrawn_at": nil, + "vulnerabilities": [ + { + "package": { + "ecosystem": "npm", + "name": "react-pdf" + }, + "severity": "high", + "vulnerable_version_range": "< 7.7.3", + "first_patched_version": { + "identifier": "7.7.3" + } + }, + { + "package": { + "ecosystem": "npm", + "name": "react-pdf" + }, + "severity": "high", + "vulnerable_version_range": ">= 8.0.0, < 8.0.2", + "first_patched_version": { + "identifier": "8.0.2" + } + } + ], + "cvss": { + "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:L", + "score": 7.1 + }, + "cwes": [ + { + "cwe_id": "CWE-79", + "name": "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + } + ] + }, + "security_vulnerability" => { + "package" => { + "ecosystem": "npm", + "name": "react-pdf" + }, + "severity": "high", + "vulnerable_version_range": "< 7.7.3", + "first_patched_version": { + "identifier": "7.7.3" + } + }, + "url": "https://api.github.com/repos/CompanyCam/Company-Cam-API/dependabot/alerts/321", + "html_url": "https://github.com/CompanyCam/Company-Cam-API/security/dependabot/321", + "created_at": "2024-05-07T16:54:48Z", + "updated_at": "2024-05-07T16:54:48Z", + "dismissed_at": nil, + "dismissed_by": nil, + "dismissed_reason": nil, + "dismissed_comment": nil, + "fixed_at": nil, + "auto_dismissed_at": nil + } + end +end \ No newline at end of file From c7d7ca4b6acb9037f3932b5920029293f920e7e4 Mon Sep 17 00:00:00 2001 From: Jeff McFadden Date: Tue, 21 May 2024 15:45:52 -0700 Subject: [PATCH 5/5] Standard. --- Rakefile | 4 +- test/core_test.rb | 1 - test/dependabot_core_test.rb | 139 +++++++++++++++++------------------ 3 files changed, 71 insertions(+), 73 deletions(-) diff --git a/Rakefile b/Rakefile index fb5c708..cd4eb8e 100644 --- a/Rakefile +++ b/Rakefile @@ -2,5 +2,5 @@ require "bundler" Bundler::GemHelper.install_tasks task :test do - Dir.glob('./test/*_test.rb').each { |file| require file} -end \ No newline at end of file + Dir.glob("./test/*_test.rb").each { |file| require file } +end diff --git a/test/core_test.rb b/test/core_test.rb index ed204ab..df17adb 100644 --- a/test/core_test.rb +++ b/test/core_test.rb @@ -2,7 +2,6 @@ require "ghx" class CoreTest < Minitest::Test - # The most basic test to ensure that the code loads def test_that_code_loads GHX::Issue.new(owner: "test", repo: "test", title: "test") diff --git a/test/dependabot_core_test.rb b/test/dependabot_core_test.rb index 700742a..e857a9d 100644 --- a/test/dependabot_core_test.rb +++ b/test/dependabot_core_test.rb @@ -2,7 +2,6 @@ require "ghx" class DependabotCoreTest < Minitest::Test - # The most basic test to ensure that the code loads def test_that_code_loads GHX::Dependabot::Alert.new(sample_dependabot_response) @@ -12,117 +11,117 @@ def test_that_code_loads def sample_dependabot_response { - "number": 321, - "state": "open", - "dependency": { - "package": { - "ecosystem": "npm", - "name": "react-pdf" + :number => 321, + :state => "open", + :dependency => { + package: { + ecosystem: "npm", + name: "react-pdf" }, - "manifest_path": "yarn.lock", - "scope": "runtime" + manifest_path: "yarn.lock", + scope: "runtime" }, - "security_advisory": { - "ghsa_id": "GHSA-87hq-q4gp-9wr4", - "cve_id": "CVE-2024-34342", - "summary": "react-pdf vulnerable to arbitrary JavaScript execution upon opening a malicious PDF with PDF.js", - "description": "### Summary\n\nIf PDF.js is used to load a malicious PDF, and PDF.js is configured with `isEvalSupported` set to `true` (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.\n\n### Patches\n[This patch](https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad) forces `isEvalSupported` to `false`, removing the attack vector.\n\n### Workarounds\nSet `options.isEvalSupported` to `false`, where `options` is `Document` component prop.\n\n### References\n- [GHSA-wgrm-67xf-hhpq](https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq)\n- https://github.com/mozilla/pdf.js/pull/18015\n- https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6\n- https://bugzilla.mozilla.org/show_bug.cgi?id=1893645", - "severity": "high", - "identifiers": [ + :security_advisory => { + ghsa_id: "GHSA-87hq-q4gp-9wr4", + cve_id: "CVE-2024-34342", + summary: "react-pdf vulnerable to arbitrary JavaScript execution upon opening a malicious PDF with PDF.js", + description: "### Summary\n\nIf PDF.js is used to load a malicious PDF, and PDF.js is configured with `isEvalSupported` set to `true` (which is the default value), unrestricted attacker-controlled JavaScript will be executed in the context of the hosting domain.\n\n### Patches\n[This patch](https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad) forces `isEvalSupported` to `false`, removing the attack vector.\n\n### Workarounds\nSet `options.isEvalSupported` to `false`, where `options` is `Document` component prop.\n\n### References\n- [GHSA-wgrm-67xf-hhpq](https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq)\n- https://github.com/mozilla/pdf.js/pull/18015\n- https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6\n- https://bugzilla.mozilla.org/show_bug.cgi?id=1893645", + severity: "high", + identifiers: [ { - "value": "GHSA-87hq-q4gp-9wr4", - "type": "GHSA" + value: "GHSA-87hq-q4gp-9wr4", + type: "GHSA" }, { - "value": "CVE-2024-34342", - "type": "CVE" + value: "CVE-2024-34342", + type: "CVE" } ], - "references": [ + references: [ { - "url": "https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq" + url: "https://github.com/mozilla/pdf.js/security/advisories/GHSA-wgrm-67xf-hhpq" }, { - "url": "https://github.com/wojtekmaj/react-pdf/security/advisories/GHSA-87hq-q4gp-9wr4" + url: "https://github.com/wojtekmaj/react-pdf/security/advisories/GHSA-87hq-q4gp-9wr4" }, { - "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-34342" + url: "https://nvd.nist.gov/vuln/detail/CVE-2024-34342" }, { - "url": "https://github.com/mozilla/pdf.js/pull/18015" + url: "https://github.com/mozilla/pdf.js/pull/18015" }, { - "url": "https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6" + url: "https://github.com/mozilla/pdf.js/commit/85e64b5c16c9aaef738f421733c12911a441cec6" }, { - "url": "https://github.com/wojtekmaj/react-pdf/commit/208f28dd47fe38c33ce4bac4205b2b0a0bb207fe" + url: "https://github.com/wojtekmaj/react-pdf/commit/208f28dd47fe38c33ce4bac4205b2b0a0bb207fe" }, { - "url": "https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad" + url: "https://github.com/wojtekmaj/react-pdf/commit/671e6eaa2e373e404040c13cc6b668fe39839cad" }, { - "url": "https://github.com/advisories/GHSA-87hq-q4gp-9wr4" + url: "https://github.com/advisories/GHSA-87hq-q4gp-9wr4" } ], - "published_at": "2024-05-07T16:48:59Z", - "updated_at": "2024-05-08T10:10:23Z", - "withdrawn_at": nil, - "vulnerabilities": [ + published_at: "2024-05-07T16:48:59Z", + updated_at: "2024-05-08T10:10:23Z", + withdrawn_at: nil, + vulnerabilities: [ { - "package": { - "ecosystem": "npm", - "name": "react-pdf" + package: { + ecosystem: "npm", + name: "react-pdf" }, - "severity": "high", - "vulnerable_version_range": "< 7.7.3", - "first_patched_version": { - "identifier": "7.7.3" + severity: "high", + vulnerable_version_range: "< 7.7.3", + first_patched_version: { + identifier: "7.7.3" } }, { - "package": { - "ecosystem": "npm", - "name": "react-pdf" + package: { + ecosystem: "npm", + name: "react-pdf" }, - "severity": "high", - "vulnerable_version_range": ">= 8.0.0, < 8.0.2", - "first_patched_version": { - "identifier": "8.0.2" + severity: "high", + vulnerable_version_range: ">= 8.0.0, < 8.0.2", + first_patched_version: { + identifier: "8.0.2" } } ], - "cvss": { - "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:L", - "score": 7.1 + cvss: { + vector_string: "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:L", + score: 7.1 }, - "cwes": [ + cwes: [ { - "cwe_id": "CWE-79", - "name": "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" + cwe_id: "CWE-79", + name: "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" } ] }, "security_vulnerability" => { "package" => { - "ecosystem": "npm", - "name": "react-pdf" + ecosystem: "npm", + name: "react-pdf" }, - "severity": "high", - "vulnerable_version_range": "< 7.7.3", - "first_patched_version": { - "identifier": "7.7.3" + :severity => "high", + :vulnerable_version_range => "< 7.7.3", + :first_patched_version => { + identifier: "7.7.3" } }, - "url": "https://api.github.com/repos/CompanyCam/Company-Cam-API/dependabot/alerts/321", - "html_url": "https://github.com/CompanyCam/Company-Cam-API/security/dependabot/321", - "created_at": "2024-05-07T16:54:48Z", - "updated_at": "2024-05-07T16:54:48Z", - "dismissed_at": nil, - "dismissed_by": nil, - "dismissed_reason": nil, - "dismissed_comment": nil, - "fixed_at": nil, - "auto_dismissed_at": nil + :url => "https://api.github.com/repos/CompanyCam/Company-Cam-API/dependabot/alerts/321", + :html_url => "https://github.com/CompanyCam/Company-Cam-API/security/dependabot/321", + :created_at => "2024-05-07T16:54:48Z", + :updated_at => "2024-05-07T16:54:48Z", + :dismissed_at => nil, + :dismissed_by => nil, + :dismissed_reason => nil, + :dismissed_comment => nil, + :fixed_at => nil, + :auto_dismissed_at => nil } end -end \ No newline at end of file +end