code_teams 1.2.0 → 1.3.1

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: 9fd90de1ce7aec137a02a8ae637a0e07c7d77921af1842e1b9b7d53a0586f756
4
+ data.tar.gz: 7423dfedbda6246ef5b7fe923161719342541e39541f1eff5879d5ad43257764
5
5
  SHA512:
6
- metadata.gz: dec7b2ed15177d10c82a16009179c76f571bf28f4bd1a56d0b9c4a132989f2e95ebddb11b3e0eb429567317e76bb33ace537f0cb3b447061519d77ca39efddf9
7
- data.tar.gz: 4fa9956fd388b92ca1d5c4b3a7cbf2a439813c57e793edea0883c9e327c56eb8ae139898ed93b06b03bd346665567c37335e7d508afbb11eea05a85181cbe232
6
+ metadata.gz: 2547bcf7dca0a65cd7e12f7ffffad3ce583d03f78f199978372aea0f4818061bd5089668ec39c188821945ecd11ca61cc8ef0639f73da16f15f6b80625100902
7
+ data.tar.gz: a1a1559ccefc40fcfedb6ef78fad40d8cf21eccee582c4ced6894aa1f963059d747a24848bd4b15f931cd3dc2a1c732ec4854f2994d0e4cca7ad53e50edaa6fd
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,99 @@
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
+ # Plugin's registry is keyed by team name, and the hook above only busts it after an
55
+ # example that built a fake team. An example reading only real teams therefore leaves
56
+ # real-team plugin instances behind, and a fake team sharing a real team's name would
57
+ # inherit that cached data. Drop the registry so the fake registers first.
58
+ Plugin.bust_caches!
59
+ code_team
60
+ end
61
+
62
+ sig { returns(T::Array[CodeTeams::Team]) }
63
+ def self.code_teams
64
+ existing = Thread.current[THREAD_KEY]
65
+ return existing if existing.is_a?(Array)
66
+
67
+ Thread.current[THREAD_KEY] = []
68
+ T.cast(Thread.current[THREAD_KEY], T::Array[CodeTeams::Team])
69
+ end
70
+
71
+ sig { void }
72
+ def self.reset!
73
+ Thread.current[THREAD_KEY] = []
74
+ end
75
+
76
+ module CodeTeamsExtension
77
+ extend T::Sig
78
+
79
+ sig { params(base: T::Module[T.anything]).void }
80
+ def self.prepended(base)
81
+ base.singleton_class.prepend(ClassMethods)
82
+ end
83
+
84
+ module ClassMethods
85
+ extend T::Sig
86
+
87
+ sig { returns(T::Array[CodeTeams::Team]) }
88
+ def all
89
+ CodeTeams::Testing.code_teams + super
90
+ end
91
+
92
+ sig { params(name: String).returns(T.nilable(CodeTeams::Team)) }
93
+ def find(name)
94
+ CodeTeams::Testing.code_teams.find { |t| t.name == name } || super
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,17 +1,40 @@
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
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
14
+ .gsub(/(?<=[A-Z])(?=[A-Z][a-z])/, '_')
8
15
  .gsub(/([a-z\d])([A-Z])/, '\1_\2')
9
16
  .tr('-', '_')
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.1
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:
@@ -50,7 +52,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
50
52
  requirements:
51
53
  - - ">="
52
54
  - !ruby/object:Gem::Version
53
- version: '3.2'
55
+ version: '3.3'
54
56
  required_rubygems_version: !ruby/object:Gem::Requirement
55
57
  requirements:
56
58
  - - ">="