code_teams 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16b7aa715a2d1234dfff0c938a28e33631141e4fadf5489af3a872fc22f8c0ed
4
- data.tar.gz: e6457f4a124c8f654fea97059de74814bb0e21859b95408eb4b2028de30508bc
3
+ metadata.gz: c4e5cbd18c2a3348571f8b1a2c4cb556aff50b4e87322fefce0b50406fee8818
4
+ data.tar.gz: 3e8d66d171ce6a98b5eb9371b2edc96ad1f4ad6d9a75c5b324774ad173168363
5
5
  SHA512:
6
- metadata.gz: dec7b2ed15177d10c82a16009179c76f571bf28f4bd1a56d0b9c4a132989f2e95ebddb11b3e0eb429567317e76bb33ace537f0cb3b447061519d77ca39efddf9
7
- data.tar.gz: 4fa9956fd388b92ca1d5c4b3a7cbf2a439813c57e793edea0883c9e327c56eb8ae139898ed93b06b03bd346665567c37335e7d508afbb11eea05a85181cbe232
6
+ metadata.gz: c28e11c45ab2d51b0dedf49f5ff1b33591ab68904a7e7728e74bb7bfa44bca2a6b5795195f95be1c37728cab5b4ca0f7faa6d0827f64866987bb79c682e31ab0
7
+ data.tar.gz: c98171075d5059ad935e00bef6c4d0e79a391f7329e078677732b53f0465511a8e862399845b3c062e446b40b2ae123bc1f06c64c3950bcaa9b72fb91293445d
data/README.md CHANGED
@@ -111,6 +111,32 @@ if errors.any?
111
111
  end
112
112
  ```
113
113
 
114
+ ## Testing
115
+
116
+ `code_teams` provides test helpers for creating temporary teams in your specs without writing YML files to disk. Add the following to your `spec_helper.rb` (or `rails_helper.rb`):
117
+
118
+ ```ruby
119
+ require 'code_teams/testing'
120
+
121
+ CodeTeams::Testing.enable!
122
+ ```
123
+
124
+ This gives you:
125
+ - A `code_team_with_config` helper method available in all specs
126
+ - Automatic cleanup of testing teams between examples
127
+
128
+ Example usage in a spec:
129
+
130
+ ```ruby
131
+ RSpec.describe 'my feature' do
132
+ it 'works with a team' do
133
+ team = code_team_with_config(name: 'Test Team')
134
+
135
+ expect(CodeTeams.find('Test Team')).to eq(team)
136
+ end
137
+ end
138
+ ```
139
+
114
140
  ## Contributing
115
141
 
116
142
  Bug reports and pull requests are welcome!
@@ -25,12 +25,12 @@ module CodeTeams
25
25
  sig { returns(String) }
26
26
  def self.default_data_accessor_name
27
27
  # e.g., MyNamespace::MyPlugin -> my_plugin
28
- Utils.underscore(Utils.demodulize(name))
28
+ Utils.underscore(Utils.demodulize(T.must(name)))
29
29
  end
30
30
 
31
- sig { params(base: T.untyped).void }
31
+ sig { params(base: T.class_of(Plugin)).void }
32
32
  def self.inherited(base) # rubocop:disable Lint/MissingSuper
33
- all_plugins << T.cast(base, T.class_of(Plugin))
33
+ all_plugins << base
34
34
  end
35
35
 
36
36
  sig { returns(T::Array[T.class_of(Plugin)]) }
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # typed: false
4
+
5
+ require 'securerandom'
6
+ require 'code_teams/testing'
7
+
8
+ module CodeTeams
9
+ module Testing
10
+ module RSpecHelpers
11
+ def code_team_with_config(team_config = {})
12
+ team_config = team_config.dup
13
+ team_config[:name] ||= "Fake Team #{SecureRandom.hex(4)}"
14
+ CodeTeams::Testing.create_code_team(team_config)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # typed: strict
4
+
5
+ require 'securerandom'
6
+ require 'code_teams'
7
+ require 'code_teams/testing/rspec_helpers'
8
+
9
+ module CodeTeams
10
+ # Utilities for tests that need a controlled set of teams without writing YML
11
+ # files to disk.
12
+ #
13
+ # Opt-in by requiring `code_teams/testing`.
14
+ module Testing
15
+ extend T::Sig
16
+
17
+ THREAD_KEY = T.let(:__code_teams_collection, Symbol)
18
+ @enabled = T.let(false, T::Boolean)
19
+
20
+ sig { void }
21
+ def self.enable!
22
+ return if @enabled
23
+
24
+ CodeTeams.prepend(CodeTeamsExtension)
25
+ @enabled = true
26
+
27
+ return unless defined?(RSpec)
28
+
29
+ T.unsafe(RSpec).configure do |config|
30
+ config.include CodeTeams::Testing::RSpecHelpers
31
+
32
+ config.around do |example|
33
+ example.run
34
+ # Bust caches because plugins may hang onto stale data between examples.
35
+ if CodeTeams::Testing.code_teams.any?
36
+ CodeTeams.bust_caches!
37
+ CodeTeams::Testing.reset!
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(CodeTeams::Team) }
44
+ def self.create_code_team(attributes)
45
+ attributes = attributes.dup
46
+ attributes[:name] ||= "Fake Team #{SecureRandom.hex(4)}"
47
+
48
+ code_team = CodeTeams::Team.new(
49
+ config_yml: 'tmp/fake_config.yml',
50
+ raw_hash: Utils.deep_stringify_keys(attributes)
51
+ )
52
+
53
+ code_teams << code_team
54
+ code_team
55
+ end
56
+
57
+ sig { returns(T::Array[CodeTeams::Team]) }
58
+ def self.code_teams
59
+ existing = Thread.current[THREAD_KEY]
60
+ return existing if existing.is_a?(Array)
61
+
62
+ Thread.current[THREAD_KEY] = []
63
+ T.cast(Thread.current[THREAD_KEY], T::Array[CodeTeams::Team])
64
+ end
65
+
66
+ sig { void }
67
+ def self.reset!
68
+ Thread.current[THREAD_KEY] = []
69
+ end
70
+
71
+ module CodeTeamsExtension
72
+ extend T::Sig
73
+
74
+ sig { params(base: Module).void }
75
+ def self.prepended(base)
76
+ base.singleton_class.prepend(ClassMethods)
77
+ end
78
+
79
+ module ClassMethods
80
+ extend T::Sig
81
+
82
+ sig { returns(T::Array[CodeTeams::Team]) }
83
+ def all
84
+ CodeTeams::Testing.code_teams + super
85
+ end
86
+
87
+ sig { params(name: String).returns(T.nilable(CodeTeams::Team)) }
88
+ def find(name)
89
+ CodeTeams::Testing.code_teams.find { |t| t.name == name } || super
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,7 +1,14 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # typed: strict
4
+
1
5
  module CodeTeams
2
6
  module Utils
7
+ extend T::Sig
8
+
3
9
  module_function
4
10
 
11
+ sig { params(string: String).returns(String) }
5
12
  def underscore(string)
6
13
  string.gsub('::', '/')
7
14
  .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
@@ -10,8 +17,24 @@ module CodeTeams
10
17
  .downcase
11
18
  end
12
19
 
20
+ sig { params(string: String).returns(String) }
13
21
  def demodulize(string)
14
- string.split('::').last
22
+ T.must(string.split('::').last)
23
+ end
24
+
25
+ # Recursively converts symbol keys to strings. Top-level input should be a Hash.
26
+ sig { params(value: T.untyped).returns(T.untyped) }
27
+ def deep_stringify_keys(value)
28
+ case value
29
+ when Hash
30
+ value.each_with_object({}) do |(k, v), acc|
31
+ acc[k.to_s] = deep_stringify_keys(v)
32
+ end
33
+ when Array
34
+ value.map { |v| deep_stringify_keys(v) }
35
+ else
36
+ value
37
+ end
15
38
  end
16
39
  end
17
40
  end
data/lib/code_teams.rb CHANGED
@@ -84,7 +84,7 @@ module CodeTeams
84
84
  )
85
85
  end
86
86
 
87
- sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(Team) }
87
+ sig { params(raw_hash: T::Hash[String, T.untyped]).returns(Team) }
88
88
  def self.from_hash(raw_hash)
89
89
  new(
90
90
  config_yml: nil,
@@ -103,7 +103,7 @@ module CodeTeams
103
103
  end
104
104
  end
105
105
 
106
- sig { returns(T::Hash[T.untyped, T.untyped]) }
106
+ sig { returns(T::Hash[String, T.untyped]) }
107
107
  attr_reader :raw_hash
108
108
 
109
109
  sig { returns(T.nilable(String)) }
@@ -112,7 +112,7 @@ module CodeTeams
112
112
  sig do
113
113
  params(
114
114
  config_yml: T.nilable(String),
115
- raw_hash: T::Hash[T.untyped, T.untyped]
115
+ raw_hash: T::Hash[String, T.untyped]
116
116
  ).void
117
117
  end
118
118
  def initialize(config_yml:, raw_hash:)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code_teams
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
@@ -34,6 +34,8 @@ files:
34
34
  - lib/code_teams.rb
35
35
  - lib/code_teams/plugin.rb
36
36
  - lib/code_teams/plugins/identity.rb
37
+ - lib/code_teams/testing.rb
38
+ - lib/code_teams/testing/rspec_helpers.rb
37
39
  - lib/code_teams/utils.rb
38
40
  homepage: https://github.com/rubyatscale/code_teams
39
41
  licenses: