From 80d85b42384b54c5a45eec36bc01563c3a3d3b91 Mon Sep 17 00:00:00 2001 From: Alex Evanczuk Date: Wed, 8 Mar 2023 16:24:38 -0500 Subject: [PATCH] Allow mappers and validators to be injected by the client (#38) * Allow mappers and validators to be injected into CodeOwnership * Bump version --- Gemfile.lock | 4 +- README.md | 11 ++ code_ownership.gemspec | 2 +- lib/code_ownership.rb | 16 ++- lib/code_ownership/configuration.rb | 44 +++++++ lib/code_ownership/mapper.rb | 62 ++++++++++ lib/code_ownership/private.rb | 14 +-- lib/code_ownership/private/configuration.rb | 37 ------ .../private/extension_loader.rb | 24 ++++ .../ownership_mappers/file_annotations.rb | 2 +- .../private/ownership_mappers/interface.rb | 66 ---------- .../ownership_mappers/js_package_ownership.rb | 2 +- .../ownership_mappers/package_ownership.rb | 2 +- .../private/ownership_mappers/team_globs.rb | 4 +- .../ownership_mappers/team_yml_ownership.rb | 2 +- .../private/validations/files_have_owners.rb | 2 +- .../validations/files_have_unique_owners.rb | 2 +- .../github_codeowners_up_to_date.rb | 4 +- .../private/validations/interface.rb | 34 ------ lib/code_ownership/validator.rb | 30 +++++ .../private/extension_loader_spec.rb | 115 ++++++++++++++++++ .../file_annotations_spec.rb | 1 + spec/lib/code_ownership_spec.rb | 33 +---- spec/spec_helper.rb | 10 +- 24 files changed, 325 insertions(+), 198 deletions(-) create mode 100644 lib/code_ownership/configuration.rb create mode 100644 lib/code_ownership/mapper.rb delete mode 100644 lib/code_ownership/private/configuration.rb create mode 100644 lib/code_ownership/private/extension_loader.rb delete mode 100644 lib/code_ownership/private/ownership_mappers/interface.rb delete mode 100644 lib/code_ownership/private/validations/interface.rb create mode 100644 lib/code_ownership/validator.rb create mode 100644 spec/lib/code_ownership/private/extension_loader_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 16ec621..4487db1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - code_ownership (1.31.1) + code_ownership (1.32.0) code_teams (~> 1.0) packs sorbet-runtime @@ -10,7 +10,7 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) - code_teams (1.0.0) + code_teams (1.0.1) sorbet-runtime coderay (1.1.3) diff-lcs (1.4.4) diff --git a/README.md b/README.md index 6407246..5c89276 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,17 @@ js_package_paths: This defaults `**/`, which makes it look for `package.json` files across your application. +### Custom Ownership +To enable custom ownership, you can inject your own custom classes into `code_ownership`. +To do this, first create a class that adheres to the `CodeOwnership::Mapper` and/or `CodeOwnership::Validator` interface. +Then, in `config/code_ownership.yml`, you can require that file: +```yml +require: + - ./lib/my_extension.rb +``` + +Now, `bin/codeownership validate` will automatically include your new mapper and/or validator. See [`spec/lib/code_ownership/private/extension_loader_spec.rb](spec/lib/code_ownership/private/extension_loader_spec.rb) for an example of what this looks like. + ## Usage: Reading CodeOwnership ### `for_file` `CodeOwnership.for_file`, given a relative path to a file returns a `CodeTeams::Team` if there is a team that owns the file, `nil` otherwise. diff --git a/code_ownership.gemspec b/code_ownership.gemspec index 6515e24..aff5ace 100644 --- a/code_ownership.gemspec +++ b/code_ownership.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "code_ownership" - spec.version = '1.31.1' + spec.version = '1.32.0' spec.authors = ['Gusto Engineers'] spec.email = ['dev@gusto.com'] spec.summary = 'A gem to help engineering teams declare ownership of code' diff --git a/lib/code_ownership.rb b/lib/code_ownership.rb index 73e0926..71fa423 100644 --- a/lib/code_ownership.rb +++ b/lib/code_ownership.rb @@ -7,8 +7,11 @@ require 'sorbet-runtime' require 'json' require 'packs' -require 'code_ownership/cli' +require 'code_ownership/mapper' +require 'code_ownership/validator' require 'code_ownership/private' +require 'code_ownership/cli' +require 'code_ownership/configuration' module CodeOwnership extend self @@ -27,7 +30,7 @@ def for_file(file) owner = T.let(nil, T.nilable(CodeTeams::Team)) - Private::OwnershipMappers::Interface.all.each do |mapper| + Mapper.all.each do |mapper| owner = mapper.map_file_to_owner(file) break if owner end @@ -41,7 +44,7 @@ def for_team(team) ownership_information = T.let([], T::Array[String]) ownership_information << "# Code Ownership Report for `#{team.name}` Team" - Private::OwnershipMappers::Interface.all.each do |mapper| + Mapper.all.each do |mapper| ownership_information << "## #{mapper.description}" codeowners_lines = mapper.codeowners_lines_to_owners ownership_for_mapper = [] @@ -172,6 +175,11 @@ def self.bust_caches! @for_file = nil @memoized_values = nil Private.bust_caches! - Private::OwnershipMappers::Interface.all.each(&:bust_caches!) + Mapper.all.each(&:bust_caches!) + end + + sig { returns(Configuration) } + def self.configuration + Private.configuration end end diff --git a/lib/code_ownership/configuration.rb b/lib/code_ownership/configuration.rb new file mode 100644 index 0000000..57cb847 --- /dev/null +++ b/lib/code_ownership/configuration.rb @@ -0,0 +1,44 @@ +# typed: strict + +module CodeOwnership + class Configuration < T::Struct + extend T::Sig + DEFAULT_JS_PACKAGE_PATHS = T.let(['**/'], T::Array[String]) + + const :owned_globs, T::Array[String] + const :unowned_globs, T::Array[String] + const :js_package_paths, T::Array[String] + const :unbuilt_gems_path, T.nilable(String) + const :skip_codeowners_validation, T::Boolean + const :raw_hash, T::Hash[T.untyped, T.untyped] + + sig { returns(Configuration) } + def self.fetch + config_hash = YAML.load_file('config/code_ownership.yml') + + if config_hash.key?("require") + config_hash["require"].each do |require_directive| + Private::ExtensionLoader.load(require_directive) + end + end + + new( + owned_globs: config_hash.fetch('owned_globs', []), + unowned_globs: config_hash.fetch('unowned_globs', []), + js_package_paths: js_package_paths(config_hash), + skip_codeowners_validation: config_hash.fetch('skip_codeowners_validation', false), + raw_hash: config_hash + ) + end + + sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) } + def self.js_package_paths(config_hash) + specified_package_paths = config_hash['js_package_paths'] + if specified_package_paths.nil? + DEFAULT_JS_PACKAGE_PATHS.dup + else + Array(specified_package_paths) + end + end + end +end diff --git a/lib/code_ownership/mapper.rb b/lib/code_ownership/mapper.rb new file mode 100644 index 0000000..bf7506a --- /dev/null +++ b/lib/code_ownership/mapper.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# typed: strict + +module CodeOwnership + module Mapper + extend T::Sig + extend T::Helpers + + interface! + + class << self + extend T::Sig + + sig { params(base: Class).void } + def included(base) + @mappers ||= T.let(@mappers, T.nilable(T::Array[Class])) + @mappers ||= [] + @mappers << base + end + + sig { returns(T::Array[Mapper]) } + def all + T.unsafe(@mappers).map(&:new) + end + end + + # + # This should be fast when run with ONE file + # + sig do + abstract.params(file: String). + returns(T.nilable(::CodeTeams::Team)) + end + def map_file_to_owner(file) + end + + # + # This should be fast when run with MANY files + # + sig do + abstract.params(files: T::Array[String]). + returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) + end + def map_files_to_owners(files) + end + + sig do + abstract.returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) + end + def codeowners_lines_to_owners + end + + sig { abstract.returns(String) } + def description + end + + sig { abstract.void } + def bust_caches! + end + end +end diff --git a/lib/code_ownership/private.rb b/lib/code_ownership/private.rb index fa60fa1..cb62ea6 100644 --- a/lib/code_ownership/private.rb +++ b/lib/code_ownership/private.rb @@ -2,15 +2,13 @@ # typed: strict -require 'code_ownership/private/configuration' +require 'code_ownership/private/extension_loader' require 'code_ownership/private/team_plugins/ownership' require 'code_ownership/private/team_plugins/github' require 'code_ownership/private/parse_js_packages' -require 'code_ownership/private/validations/interface' require 'code_ownership/private/validations/files_have_owners' require 'code_ownership/private/validations/github_codeowners_up_to_date' require 'code_ownership/private/validations/files_have_unique_owners' -require 'code_ownership/private/ownership_mappers/interface' require 'code_ownership/private/ownership_mappers/file_annotations' require 'code_ownership/private/ownership_mappers/team_globs' require 'code_ownership/private/ownership_mappers/package_ownership' @@ -21,10 +19,10 @@ module CodeOwnership module Private extend T::Sig - sig { returns(Private::Configuration) } + sig { returns(Configuration) } def self.configuration - @configuration ||= T.let(@configuration, T.nilable(Private::Configuration)) - @configuration ||= Private::Configuration.fetch + @configuration ||= T.let(@configuration, T.nilable(Configuration)) + @configuration ||= Configuration.fetch end sig { void } @@ -36,7 +34,7 @@ def self.bust_caches! sig { params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).void } def self.validate!(files:, autocorrect: true, stage_changes: true) - errors = Validations::Interface.all.flat_map do |validator| + errors = Validator.all.flat_map do |validator| validator.validation_errors( files: files, autocorrect: autocorrect, @@ -87,7 +85,7 @@ def self.files_by_mapper(files) @files_by_mapper ||= begin files_by_mapper = files.map { |file| [file, []] }.to_h - Private::OwnershipMappers::Interface.all.each do |mapper| + Mapper.all.each do |mapper| mapper.map_files_to_owners(files).each do |file, _team| files_by_mapper[file] ||= [] T.must(files_by_mapper[file]) << mapper.description diff --git a/lib/code_ownership/private/configuration.rb b/lib/code_ownership/private/configuration.rb deleted file mode 100644 index 6fa03a7..0000000 --- a/lib/code_ownership/private/configuration.rb +++ /dev/null @@ -1,37 +0,0 @@ -# typed: strict - -module CodeOwnership - module Private - class Configuration < T::Struct - extend T::Sig - DEFAULT_JS_PACKAGE_PATHS = T.let(['**/'], T::Array[String]) - - const :owned_globs, T::Array[String] - const :unowned_globs, T::Array[String] - const :js_package_paths, T::Array[String] - const :skip_codeowners_validation, T::Boolean - - sig { returns(Configuration) } - def self.fetch - config_hash = YAML.load_file('config/code_ownership.yml') - - new( - owned_globs: config_hash.fetch('owned_globs', []), - unowned_globs: config_hash.fetch('unowned_globs', []), - js_package_paths: js_package_paths(config_hash), - skip_codeowners_validation: config_hash.fetch('skip_codeowners_validation', false) - ) - end - - sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) } - def self.js_package_paths(config_hash) - specified_package_paths = config_hash['js_package_paths'] - if specified_package_paths.nil? - DEFAULT_JS_PACKAGE_PATHS.dup - else - Array(specified_package_paths) - end - end - end - end -end diff --git a/lib/code_ownership/private/extension_loader.rb b/lib/code_ownership/private/extension_loader.rb new file mode 100644 index 0000000..39e5c14 --- /dev/null +++ b/lib/code_ownership/private/extension_loader.rb @@ -0,0 +1,24 @@ +# typed: strict +# frozen_string_literal: true + +module CodeOwnership + module Private + # This class handles loading extensions to code_ownership using the `require` directive + # in the `code_ownership.yml` configuration. + module ExtensionLoader + class << self + extend T::Sig + sig { params(require_directive: String).void } + def load(require_directive) + # We want to transform the require directive to behave differently + # if it's a specific local file being required versus a gem + if require_directive.start_with?(".") + require File.join(Pathname.pwd, require_directive) + else + require require_directive + end + end + end + end + end +end diff --git a/lib/code_ownership/private/ownership_mappers/file_annotations.rb b/lib/code_ownership/private/ownership_mappers/file_annotations.rb index b4c6c14..1c93799 100644 --- a/lib/code_ownership/private/ownership_mappers/file_annotations.rb +++ b/lib/code_ownership/private/ownership_mappers/file_annotations.rb @@ -16,7 +16,7 @@ module OwnershipMappers # } class FileAnnotations extend T::Sig - include Interface + include Mapper @@map_files_to_owners = T.let({}, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars diff --git a/lib/code_ownership/private/ownership_mappers/interface.rb b/lib/code_ownership/private/ownership_mappers/interface.rb deleted file mode 100644 index 8a6b52d..0000000 --- a/lib/code_ownership/private/ownership_mappers/interface.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -# typed: strict - -module CodeOwnership - module Private - module OwnershipMappers - module Interface - extend T::Sig - extend T::Helpers - - interface! - - class << self - extend T::Sig - - sig { params(base: Class).void } - def included(base) - @mappers ||= T.let(@mappers, T.nilable(T::Array[Class])) - @mappers ||= [] - @mappers << base - end - - sig { returns(T::Array[Interface]) } - def all - T.unsafe(@mappers).map(&:new) - end - end - - # - # This should be fast when run with ONE file - # - sig do - abstract.params(file: String). - returns(T.nilable(::CodeTeams::Team)) - end - def map_file_to_owner(file) - end - - # - # This should be fast when run with MANY files - # - sig do - abstract.params(files: T::Array[String]). - returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) - end - def map_files_to_owners(files) - end - - sig do - abstract.returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) - end - def codeowners_lines_to_owners - end - - sig { abstract.returns(String) } - def description - end - - sig { abstract.void } - def bust_caches! - end - end - end - end -end diff --git a/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb b/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb index 2465a55..b471a9a 100644 --- a/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb +++ b/lib/code_ownership/private/ownership_mappers/js_package_ownership.rb @@ -7,7 +7,7 @@ module Private module OwnershipMappers class JsPackageOwnership extend T::Sig - include Interface + include Mapper @@package_json_cache = T.let({}, T::Hash[String, T.nilable(ParseJsPackages::Package)]) # rubocop:disable Style/ClassVars diff --git a/lib/code_ownership/private/ownership_mappers/package_ownership.rb b/lib/code_ownership/private/ownership_mappers/package_ownership.rb index 3690f8f..ef3b7ee 100644 --- a/lib/code_ownership/private/ownership_mappers/package_ownership.rb +++ b/lib/code_ownership/private/ownership_mappers/package_ownership.rb @@ -7,7 +7,7 @@ module Private module OwnershipMappers class PackageOwnership extend T::Sig - include Interface + include Mapper @@package_yml_cache = T.let({}, T::Hash[String, T.nilable(Packs::Pack)]) # rubocop:disable Style/ClassVars diff --git a/lib/code_ownership/private/ownership_mappers/team_globs.rb b/lib/code_ownership/private/ownership_mappers/team_globs.rb index ac8bec0..fa40e34 100644 --- a/lib/code_ownership/private/ownership_mappers/team_globs.rb +++ b/lib/code_ownership/private/ownership_mappers/team_globs.rb @@ -7,8 +7,8 @@ module Private module OwnershipMappers class TeamGlobs extend T::Sig - include Interface - include Validations::Interface + include Mapper + include Validator @@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars @@map_files_to_owners = {} # rubocop:disable Style/ClassVars diff --git a/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb b/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb index f9e1bd4..870e1cf 100644 --- a/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb +++ b/lib/code_ownership/private/ownership_mappers/team_yml_ownership.rb @@ -7,7 +7,7 @@ module Private module OwnershipMappers class TeamYmlOwnership extend T::Sig - include Interface + include Mapper @@map_files_to_owners = T.let(@map_files_to_owners, T.nilable(T::Hash[String, T.nilable(::CodeTeams::Team)])) # rubocop:disable Style/ClassVars @@map_files_to_owners = {} # rubocop:disable Style/ClassVars diff --git a/lib/code_ownership/private/validations/files_have_owners.rb b/lib/code_ownership/private/validations/files_have_owners.rb index 67f8d77..2674c32 100644 --- a/lib/code_ownership/private/validations/files_have_owners.rb +++ b/lib/code_ownership/private/validations/files_have_owners.rb @@ -6,7 +6,7 @@ module Validations class FilesHaveOwners extend T::Sig extend T::Helpers - include Interface + include Validator sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } def validation_errors(files:, autocorrect: true, stage_changes: true) diff --git a/lib/code_ownership/private/validations/files_have_unique_owners.rb b/lib/code_ownership/private/validations/files_have_unique_owners.rb index 46f616e..6025b43 100644 --- a/lib/code_ownership/private/validations/files_have_unique_owners.rb +++ b/lib/code_ownership/private/validations/files_have_unique_owners.rb @@ -6,7 +6,7 @@ module Validations class FilesHaveUniqueOwners extend T::Sig extend T::Helpers - include Interface + include Validator sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } def validation_errors(files:, autocorrect: true, stage_changes: true) diff --git a/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb b/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb index d2b5523..ed53e91 100644 --- a/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb +++ b/lib/code_ownership/private/validations/github_codeowners_up_to_date.rb @@ -6,7 +6,7 @@ module Validations class GithubCodeownersUpToDate extend T::Sig extend T::Helpers - include Interface + include Validator sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } def validation_errors(files:, autocorrect: true, stage_changes: true) @@ -105,7 +105,7 @@ def codeowners_file_lines map[team.name] = team_github.team end - Private::OwnershipMappers::Interface.all.flat_map do |mapper| + Mapper.all.flat_map do |mapper| codeowners_lines = mapper.codeowners_lines_to_owners.filter_map do |line, team| team_mapping = github_team_map[team&.name] next unless team_mapping diff --git a/lib/code_ownership/private/validations/interface.rb b/lib/code_ownership/private/validations/interface.rb deleted file mode 100644 index ff5a202..0000000 --- a/lib/code_ownership/private/validations/interface.rb +++ /dev/null @@ -1,34 +0,0 @@ -# typed: strict - -module CodeOwnership - module Private - module Validations - module Interface - extend T::Sig - extend T::Helpers - - interface! - - sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } - def validation_errors(files:, autocorrect: true, stage_changes: true) - end - - class << self - extend T::Sig - - sig { params(base: Class).void } - def included(base) - @validators ||= T.let(@validators, T.nilable(T::Array[Class])) - @validators ||= [] - @validators << base - end - - sig { returns(T::Array[Interface]) } - def all - T.unsafe(@validators).map(&:new) - end - end - end - end - end -end diff --git a/lib/code_ownership/validator.rb b/lib/code_ownership/validator.rb new file mode 100644 index 0000000..11206e1 --- /dev/null +++ b/lib/code_ownership/validator.rb @@ -0,0 +1,30 @@ +# typed: strict + +module CodeOwnership + module Validator + extend T::Sig + extend T::Helpers + + interface! + + sig { abstract.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } + def validation_errors(files:, autocorrect: true, stage_changes: true) + end + + class << self + extend T::Sig + + sig { params(base: Class).void } + def included(base) + @validators ||= T.let(@validators, T.nilable(T::Array[Class])) + @validators ||= [] + @validators << base + end + + sig { returns(T::Array[Validator]) } + def all + T.unsafe(@validators).map(&:new) + end + end + end +end diff --git a/spec/lib/code_ownership/private/extension_loader_spec.rb b/spec/lib/code_ownership/private/extension_loader_spec.rb new file mode 100644 index 0000000..216ec2e --- /dev/null +++ b/spec/lib/code_ownership/private/extension_loader_spec.rb @@ -0,0 +1,115 @@ +module CodeOwnership + # We do not bust the cache here so that we only load the extension once! + RSpec.describe Private::ExtensionLoader, :do_not_bust_cache do + let(:codeowners_validation) { Private::Validations::GithubCodeownersUpToDate } + + before do + write_file('config/code_ownership.yml', <<~YML) + owned_globs: + - app/**/*.rb + require: + - ./lib/my_extension.rb + YML + + write_file('config/teams/bar.yml', <<~CONTENTS) + name: Bar + github: + team: '@org/my-team' + CONTENTS + + write_file('app/services/my_ownable_file.rb') + + write_file('lib/my_extension.rb', <<~RUBY) + class MyExtension + extend T::Sig + include CodeOwnership::Mapper + include CodeOwnership::Validator + + sig do + override. + params(files: T::Array[String]). + returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) + end + def map_files_to_owners(files) # rubocop:disable Lint/UnusedMethodArgument + files.map{|f| [f, CodeTeams.all.last]}.to_h + end + + sig do + override.params(file: String). + returns(T.nilable(::CodeTeams::Team)) + end + def map_file_to_owner(file) + CodeTeams.all.last + end + + sig do + override.returns(T::Hash[String, T.nilable(::CodeTeams::Team)]) + end + def codeowners_lines_to_owners + Dir.glob('**/*.*').map{|f| [f, CodeTeams.all.last]}.to_h + end + + sig { override.returns(String) } + def description + 'My special extension' + end + + sig { override.params(files: T::Array[String], autocorrect: T::Boolean, stage_changes: T::Boolean).returns(T::Array[String]) } + def validation_errors(files:, autocorrect: true, stage_changes: true) + ['my validation errors'] + end + + sig { override.void } + def bust_caches! + nil + end + end + RUBY + + expect_any_instance_of(codeowners_validation).to receive(:`).with("git add #{Pathname.pwd.join('.github/CODEOWNERS')}") # rubocop:disable RSpec/AnyInstance + end + + after(:all) do + validators_without_extension = Validator.instance_variable_get(:@validators).reject{|v| v == MyExtension } + Validator.instance_variable_set(:@validators, validators_without_extension) + mappers_without_extension = Mapper.instance_variable_get(:@mappers).reject{|v| v == MyExtension } + Mapper.instance_variable_set(:@mappers, mappers_without_extension) + end + + describe 'CodeOwnership.validate!' do + it 'allows third party validations to be injected' do + expect { CodeOwnership.validate! }.to raise_error do |e| + expect(e).to be_a CodeOwnership::InvalidCodeOwnershipConfigurationError + puts e.message + expect(e.message).to eq <<~EXPECTED.chomp + my validation errors + See https://github.com/rubyatscale/code_ownership#README.md for more details + EXPECTED + end + end + + it 'allows extensions to add to codeowners list' do + expect { CodeOwnership.validate! }.to raise_error(CodeOwnership::InvalidCodeOwnershipConfigurationError) + expect(Pathname.new('.github/CODEOWNERS').read).to eq <<~EXPECTED + # STOP! - DO NOT EDIT THIS FILE MANUALLY + # This file was automatically generated by "bin/codeownership validate". + # + # CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub + # teams. This is useful when developers create Pull Requests since the + # code/file owner is notified. Reference GitHub docs for more details: + # https://help.github.com/en/articles/about-code-owners + + + # Team YML ownership + /config/teams/bar.yml @org/my-team + + # My special extension + /app/services/my_ownable_file.rb @org/my-team + /config/code_ownership.yml @org/my-team + /config/teams/bar.yml @org/my-team + /lib/my_extension.rb @org/my-team + EXPECTED + end + end + end +end diff --git a/spec/lib/code_ownership/private/ownership_mappers/file_annotations_spec.rb b/spec/lib/code_ownership/private/ownership_mappers/file_annotations_spec.rb index d0696fe..6eecccf 100644 --- a/spec/lib/code_ownership/private/ownership_mappers/file_annotations_spec.rb +++ b/spec/lib/code_ownership/private/ownership_mappers/file_annotations_spec.rb @@ -80,6 +80,7 @@ module CodeOwnership write_file('config/teams/foo.yml', <<~CONTENTS) name: Foo CONTENTS + create_minimal_configuration end context 'ruby file has no annotation' do diff --git a/spec/lib/code_ownership_spec.rb b/spec/lib/code_ownership_spec.rb index b0b8c5c..268f587 100644 --- a/spec/lib/code_ownership_spec.rb +++ b/spec/lib/code_ownership_spec.rb @@ -110,6 +110,7 @@ describe '.first_owned_file_for_backtrace' do before do + create_minimal_configuration create_files_with_defined_classe end @@ -183,37 +184,5 @@ - config/teams/bar.yml OWNERSHIP end - - context 'team does not own any packs or files using annotations' do - before do - write_file('config/teams/foo.yml', <<~CONTENTS) - name: Foo - github: - team: '@MyOrg/foo-team' - owned_globs: - - app/services/foo_stuff/** - CONTENTS - end - - it 'prints out ownership information for the given team' do - expect(CodeOwnership.for_team('Foo')).to eq <<~OWNERSHIP - # Code Ownership Report for `Foo` Team - ## Annotations at the top of file - This team owns nothing in this category. - - ## Team-specific owned globs - - app/services/foo_stuff/** - - ## Owner metadata key in package.yml - This team owns nothing in this category. - - ## Owner metadata key in package.json - This team owns nothing in this category. - - ## Team YML ownership - - config/teams/foo.yml - OWNERSHIP - end - end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6e785ac..d193aac 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,9 +18,11 @@ config.include_context 'application fixtures' - config.before do - CodeOwnership.bust_caches! - CodeTeams.bust_caches! - allow(CodeTeams::Plugin).to receive(:registry).and_return({}) + config.before do |c| + unless c.metadata[:do_not_bust_cache] + CodeOwnership.bust_caches! + CodeTeams.bust_caches! + allow(CodeTeams::Plugin).to receive(:registry).and_return({}) + end end end