karafka-testing 2.5.3 → 2.5.5

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: ec0f7907a4fc370e0ee093df0b7759537977de1e54e4873149f8a784f6126e04
4
- data.tar.gz: 8ca3cb6784c6d2def64c4495d2f9318426c023499c401a01e33e295d27583c7e
3
+ metadata.gz: addea79d51dc5c967ee59a86e455b701b76961fbe5262d59af5749c57c9a0099
4
+ data.tar.gz: f1844e8a511ee458365b39ba36f4f200e6338c03742ba3350f7f66c085bf2433
5
5
  SHA512:
6
- metadata.gz: c61db348f3acb74d3eb3f7a9b807974ae9af1dc00d10e1dcd6f2e3c9dd006bf0f6937ebfd857e9dd51499968cf2e3c075cb9b073226acd0b19449755169c6271
7
- data.tar.gz: ed1482d93ea2532a5d9123fe05d1ef62157e33d8a8ff9be70fd8434c9fcd30031652e7a0d3ae093398942d183e97a4577bb8a06e7481aa05f39e9073b0b1ccf9
6
+ metadata.gz: cc98c964bbbea8e5e31e0ba65726b130e81e00bfe392fd6b75a30be534c7f923d4da00f5d96c4688cf04b3faaa1af4e7c4048da71205d345f94a9ceb90a46fb3
7
+ data.tar.gz: a63c804f23232ce992a9f9e013ea47560ed39317e47d1bf4d929a0b30390e78a29047fb2261e19ee040d37f42bc7486e90cf1476cedbfae88286ba27d3f37eca
@@ -6,9 +6,7 @@ concurrency:
6
6
 
7
7
  on:
8
8
  pull_request:
9
- branches: [ main, master ]
10
- push:
11
- branches: [ main, master ]
9
+ branches: [ master ]
12
10
  schedule:
13
11
  - cron: '0 1 * * *'
14
12
 
@@ -19,21 +17,19 @@ jobs:
19
17
  specs:
20
18
  timeout-minutes: 30
21
19
  runs-on: ubuntu-latest
22
- needs: diffend
23
20
  strategy:
24
21
  fail-fast: false
25
22
  matrix:
26
23
  ruby:
27
- - '3.5.0-preview1'
24
+ - '4.0.0-preview2'
28
25
  - '3.4'
29
26
  - '3.3'
30
27
  - '3.2'
31
- - '3.1'
32
28
  include:
33
29
  - ruby: '3.3'
34
30
  coverage: 'true'
35
31
  steps:
36
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
32
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
37
33
  with:
38
34
  fetch-depth: 0
39
35
 
@@ -41,7 +37,7 @@ jobs:
41
37
  run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
42
38
 
43
39
  - name: Set up Ruby
44
- uses: ruby/setup-ruby@2a7b30092b0caf9c046252510f9273b4875f3db9 # v1.254.0
40
+ uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
45
41
  with:
46
42
  ruby-version: ${{matrix.ruby}}
47
43
  bundler: 'latest'
@@ -56,29 +52,10 @@ jobs:
56
52
  run: |
57
53
  bundle install --jobs 4 --retry 3
58
54
 
59
- diffend:
60
- timeout-minutes: 5
61
- runs-on: ubuntu-latest
62
- strategy:
63
- fail-fast: false
64
- steps:
65
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
66
- with:
67
- fetch-depth: 0
68
-
69
- - name: Set up Ruby
70
- uses: ruby/setup-ruby@2a7b30092b0caf9c046252510f9273b4875f3db9 # v1.254.0
71
- with:
72
- ruby-version: 3.3
73
-
74
- - name: Install latest bundler
75
- run: gem install bundler --no-document
76
-
77
- - name: Install Diffend plugin
78
- run: bundle plugin install diffend
79
-
80
- - name: Bundle Secure
81
- run: bundle secure
55
+ - name: Run all tests
56
+ env:
57
+ GITHUB_COVERAGE: ${{matrix.coverage}}
58
+ run: bundle exec rspec
82
59
 
83
60
  coditsu:
84
61
  timeout-minutes: 5
@@ -86,7 +63,7 @@ jobs:
86
63
  strategy:
87
64
  fail-fast: false
88
65
  steps:
89
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
66
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
90
67
  with:
91
68
  fetch-depth: 0
92
69
 
@@ -106,3 +83,32 @@ jobs:
106
83
 
107
84
  - name: Run Coditsu
108
85
  run: ./coditsu_script.sh
86
+
87
+ yard-lint:
88
+ timeout-minutes: 5
89
+ runs-on: ubuntu-latest
90
+ steps:
91
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
92
+ - name: Set up Ruby
93
+ uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
94
+ with:
95
+ ruby-version: '3.4.7'
96
+ bundler-cache: true
97
+ - run: bundle exec yard-lint lib/
98
+
99
+ ci-success:
100
+ name: CI Success
101
+ runs-on: ubuntu-latest
102
+ if: always()
103
+ needs:
104
+ - coditsu
105
+ - specs
106
+ - yard-lint
107
+ steps:
108
+ - name: Check all jobs passed
109
+ if: |
110
+ contains(needs.*.result, 'failure') ||
111
+ contains(needs.*.result, 'cancelled') ||
112
+ contains(needs.*.result, 'skipped')
113
+ run: exit 1
114
+ - run: echo "All CI checks passed!"
@@ -19,12 +19,12 @@ jobs:
19
19
  id-token: write
20
20
 
21
21
  steps:
22
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
23
23
  with:
24
24
  fetch-depth: 0
25
25
 
26
26
  - name: Set up Ruby
27
- uses: ruby/setup-ruby@2a7b30092b0caf9c046252510f9273b4875f3db9 # v1.254.0
27
+ uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
28
28
  with:
29
29
  bundler-cache: false
30
30
 
@@ -32,4 +32,4 @@ jobs:
32
32
  run: |
33
33
  bundle install --jobs 4 --retry 3
34
34
 
35
- - uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1
35
+ - uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2
@@ -13,7 +13,7 @@ jobs:
13
13
  if: github.repository_owner == 'karafka'
14
14
  steps:
15
15
  - name: Trigger wiki refresh
16
- uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
16
+ uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
17
17
  with:
18
18
  token: ${{ secrets.WIKI_REPO_TOKEN }}
19
19
  repository: karafka/wiki
@@ -7,7 +7,7 @@ jobs:
7
7
  verify_action_pins:
8
8
  runs-on: ubuntu-latest
9
9
  steps:
10
- - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
10
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
11
11
  - name: Check SHA pins
12
12
  run: |
13
13
  if grep -E -r "uses: .*/.*@(v[0-9]+|main|master)($|[[:space:]]|$)" --include="*.yml" --include="*.yaml" .github/workflows/ | grep -v "#"; then
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --require spec_helper
2
+ --format documentation
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.5
1
+ 3.4.7
data/.yard-lint.yml ADDED
@@ -0,0 +1,174 @@
1
+ # YARD-Lint Configuration
2
+ # See https://github.com/mensfeld/yard-lint for documentation
3
+
4
+ # Global settings for all validators
5
+ AllValidators:
6
+ # YARD command-line options (applied to all validators by default)
7
+ YardOptions:
8
+ - --private
9
+ - --protected
10
+
11
+ # Global file exclusion patterns
12
+ Exclude:
13
+ - '\.git'
14
+ - 'vendor/**/*'
15
+ - 'node_modules/**/*'
16
+ - 'spec/**/*'
17
+ - 'test/**/*'
18
+
19
+ # Exit code behavior (error, warning, convention, never)
20
+ FailOnSeverity: convention
21
+
22
+ # Minimum documentation coverage percentage (0-100)
23
+ # Fails if coverage is below this threshold
24
+ MinCoverage: 99.0
25
+
26
+ # Diff mode settings
27
+ DiffMode:
28
+ # Default base ref for --diff (auto-detects main/master if not specified)
29
+ DefaultBaseRef: ~
30
+
31
+ # Documentation validators
32
+ Documentation/UndocumentedObjects:
33
+ Description: 'Checks for classes, modules, and methods without documentation.'
34
+ Enabled: true
35
+ Severity: error
36
+ ExcludedMethods:
37
+ - 'initialize/0' # Exclude parameter-less initialize
38
+ - '/^_/' # Exclude private methods (by convention)
39
+
40
+ Documentation/UndocumentedMethodArguments:
41
+ Description: 'Checks for method parameters without @param tags.'
42
+ Enabled: true
43
+ Severity: error
44
+
45
+ Documentation/UndocumentedBooleanMethods:
46
+ Description: 'Checks that question mark methods document their boolean return.'
47
+ Enabled: true
48
+ Severity: error
49
+
50
+ Documentation/UndocumentedOptions:
51
+ Description: 'Detects methods with options hash parameters but no @option tags.'
52
+ Enabled: true
53
+ Severity: error
54
+
55
+ Documentation/MarkdownSyntax:
56
+ Description: 'Detects common markdown syntax errors in documentation.'
57
+ Enabled: true
58
+ Severity: error
59
+
60
+ # Tags validators
61
+ Tags/Order:
62
+ Description: 'Enforces consistent ordering of YARD tags.'
63
+ Enabled: true
64
+ Severity: error
65
+ EnforcedOrder:
66
+ - param
67
+ - option
68
+ - return
69
+ - raise
70
+ - example
71
+
72
+ Tags/InvalidTypes:
73
+ Description: 'Validates type definitions in @param, @return, @option tags.'
74
+ Enabled: true
75
+ Severity: error
76
+ ValidatedTags:
77
+ - param
78
+ - option
79
+ - return
80
+
81
+ Tags/TypeSyntax:
82
+ Description: 'Validates YARD type syntax using YARD parser.'
83
+ Enabled: true
84
+ Severity: error
85
+ ValidatedTags:
86
+ - param
87
+ - option
88
+ - return
89
+ - yieldreturn
90
+
91
+ Tags/MeaninglessTag:
92
+ Description: 'Detects @param/@option tags on classes, modules, or constants.'
93
+ Enabled: true
94
+ Severity: error
95
+ CheckedTags:
96
+ - param
97
+ - option
98
+ InvalidObjectTypes:
99
+ - class
100
+ - module
101
+ - constant
102
+
103
+ Tags/CollectionType:
104
+ Description: 'Validates Hash collection syntax consistency.'
105
+ Enabled: true
106
+ Severity: error
107
+ EnforcedStyle: long # 'long' for Hash{K => V} (YARD standard), 'short' for {K => V}
108
+ ValidatedTags:
109
+ - param
110
+ - option
111
+ - return
112
+ - yieldreturn
113
+
114
+ Tags/TagTypePosition:
115
+ Description: 'Validates type annotation position in tags.'
116
+ Enabled: true
117
+ Severity: error
118
+ CheckedTags:
119
+ - param
120
+ - option
121
+ # EnforcedStyle: 'type_after_name' (YARD standard: @param name [Type])
122
+ # or 'type_first' (@param [Type] name)
123
+ EnforcedStyle: type_after_name
124
+
125
+ Tags/ApiTags:
126
+ Description: 'Enforces @api tags on public objects.'
127
+ Enabled: false # Opt-in validator
128
+ Severity: error
129
+ AllowedApis:
130
+ - public
131
+ - private
132
+ - internal
133
+
134
+ Tags/OptionTags:
135
+ Description: 'Requires @option tags for methods with options parameters.'
136
+ Enabled: true
137
+ Severity: error
138
+
139
+ # Warnings validators - catches YARD parser errors
140
+ Warnings/UnknownTag:
141
+ Description: 'Detects unknown YARD tags.'
142
+ Enabled: true
143
+ Severity: error
144
+
145
+ Warnings/UnknownDirective:
146
+ Description: 'Detects unknown YARD directives.'
147
+ Enabled: true
148
+ Severity: error
149
+
150
+ Warnings/InvalidTagFormat:
151
+ Description: 'Detects malformed tag syntax.'
152
+ Enabled: true
153
+ Severity: error
154
+
155
+ Warnings/InvalidDirectiveFormat:
156
+ Description: 'Detects malformed directive syntax.'
157
+ Enabled: true
158
+ Severity: error
159
+
160
+ Warnings/DuplicatedParameterName:
161
+ Description: 'Detects duplicate @param tags.'
162
+ Enabled: true
163
+ Severity: error
164
+
165
+ Warnings/UnknownParameterName:
166
+ Description: 'Detects @param tags for non-existent parameters.'
167
+ Enabled: true
168
+ Severity: error
169
+
170
+ # Semantic validators
171
+ Semantic/AbstractMethods:
172
+ Description: 'Ensures @abstract methods do not have real implementations.'
173
+ Enabled: true
174
+ Severity: error
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Karafka Testing Changelog
2
2
 
3
+ ## 2.5.5 (2025-11-28)
4
+ - [Feature] Add `produce_to` method for explicit consumer targeting in multi-consumer scenarios.
5
+ - [Fix] Fix consumer mapping collision when multiple consumer groups listen to the same topic.
6
+
7
+ ## 2.5.4 (2025-10-03)
8
+ - [Fix] Topic present in second dynamically built subscription group not available for dispatch (#291).
9
+ - **[EOL]** Remove Ruby `3.1` according to the EOL schedule.
10
+
3
11
  ## 2.5.3 (2025-08-12)
4
12
  - [Fix] Allow testing consumers with Pro expansions (i.e. Piping).
5
13
 
@@ -15,7 +23,7 @@
15
23
  - [Maintenance] Release matching Karafka `2.5.0` release.
16
24
 
17
25
  ## 2.4.7 (2025-04-01)
18
- - **[Breaking]** Drop Ruby `3.0` support according to the EOL schedule.
26
+ - **[EOL]** Drop Ruby `3.0` support according to the EOL schedule.
19
27
  - **[Fix]** Check not only that `Mocha` is loaded but also that its stubs are used.
20
28
 
21
29
  ## 2.4.6 (2024-07-31)
@@ -40,7 +48,7 @@
40
48
  - [Fix] Fix instance variable in minitest helper (tldn0718)
41
49
 
42
50
  ## 2.4.0 (2024-04-26)
43
- - **[Breaking]** Drop Ruby `2.7` support.
51
+ - **[EOL]** Drop Ruby `2.7` support.
44
52
  - [Refactor] Extract common components for Minitest and RSpec.
45
53
  - [Fix] Support again `require: false` on gem loading.
46
54
  - [Fix] Fix a case where multiplexed SG would fail with `TopicInManyConsumerGroupsError`
@@ -149,7 +157,7 @@
149
157
  - Change the API to be more comprehensive
150
158
  - Update to work with Karafka 2.0
151
159
  - Support for Ruby 3.1
152
- - Drop support for ruby 2.6
160
+ - **[EOL]** Drop support for ruby 2.6
153
161
 
154
162
  ## 1.4.*
155
163
 
data/Gemfile CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- plugin 'diffend'
6
-
7
5
  gemspec
6
+
7
+ group :test do
8
+ gem 'ostruct'
9
+ gem 'rspec'
10
+ gem 'simplecov'
11
+ gem 'warning'
12
+ gem 'yard-lint'
13
+ end
data/Gemfile.lock CHANGED
@@ -1,37 +1,73 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka-testing (2.5.3)
5
- karafka (>= 2.5.0.beta1, < 2.6.0)
4
+ karafka-testing (2.5.5)
5
+ karafka (>= 2.5.0, < 2.6.0)
6
6
  waterdrop (>= 2.8.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- base64 (0.2.0)
12
- ffi (1.17.0)
13
- ffi (1.17.0-x86_64-linux-gnu)
14
- karafka (2.5.0.beta1)
11
+ base64 (0.3.0)
12
+ diff-lcs (1.6.2)
13
+ docile (1.4.1)
14
+ ffi (1.17.2)
15
+ ffi (1.17.2-x86_64-linux-gnu)
16
+ json (2.15.1)
17
+ karafka (2.5.1)
15
18
  base64 (~> 0.2)
16
- karafka-core (>= 2.5.0, < 2.6.0)
17
- karafka-rdkafka (>= 0.19.2)
18
- waterdrop (>= 2.8.3, < 3.0.0)
19
+ karafka-core (>= 2.5.6, < 2.6.0)
20
+ karafka-rdkafka (>= 0.22.0)
21
+ waterdrop (>= 2.8.9, < 3.0.0)
19
22
  zeitwerk (~> 2.3)
20
- karafka-core (2.5.0)
21
- karafka-rdkafka (>= 0.19.2, < 0.21.0)
23
+ karafka-core (2.5.7)
24
+ karafka-rdkafka (>= 0.20.0)
22
25
  logger (>= 1.6.0)
23
- karafka-rdkafka (0.19.2)
26
+ karafka-rdkafka (0.22.2)
24
27
  ffi (~> 1.15)
28
+ json (> 2.0)
29
+ logger
30
+ mini_portile2 (~> 2.6)
31
+ rake (> 12)
32
+ karafka-rdkafka (0.22.2-x86_64-linux-gnu)
33
+ ffi (~> 1.15)
34
+ json (> 2.0)
35
+ logger
25
36
  mini_portile2 (~> 2.6)
26
37
  rake (> 12)
27
38
  logger (1.7.0)
28
- mini_portile2 (2.8.7)
29
- rake (13.2.1)
30
- waterdrop (2.8.3)
39
+ mini_portile2 (2.8.9)
40
+ ostruct (0.6.3)
41
+ rake (13.3.0)
42
+ rspec (3.13.2)
43
+ rspec-core (~> 3.13.0)
44
+ rspec-expectations (~> 3.13.0)
45
+ rspec-mocks (~> 3.13.0)
46
+ rspec-core (3.13.6)
47
+ rspec-support (~> 3.13.0)
48
+ rspec-expectations (3.13.5)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.13.0)
51
+ rspec-mocks (3.13.7)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.13.0)
54
+ rspec-support (3.13.6)
55
+ simplecov (0.22.0)
56
+ docile (~> 1.1)
57
+ simplecov-html (~> 0.11)
58
+ simplecov_json_formatter (~> 0.1)
59
+ simplecov-html (0.13.2)
60
+ simplecov_json_formatter (0.1.4)
61
+ warning (1.5.0)
62
+ waterdrop (2.8.12)
31
63
  karafka-core (>= 2.4.9, < 3.0.0)
32
- karafka-rdkafka (>= 0.19.1)
64
+ karafka-rdkafka (>= 0.20.0)
33
65
  zeitwerk (~> 2.3)
34
- zeitwerk (2.6.16)
66
+ yard (0.9.37)
67
+ yard-lint (1.2.3)
68
+ yard (~> 0.9)
69
+ zeitwerk (~> 2.6)
70
+ zeitwerk (2.7.3)
35
71
 
36
72
  PLATFORMS
37
73
  ruby
@@ -39,6 +75,11 @@ PLATFORMS
39
75
 
40
76
  DEPENDENCIES
41
77
  karafka-testing!
78
+ ostruct
79
+ rspec
80
+ simplecov
81
+ warning
82
+ yard-lint
42
83
 
43
84
  BUNDLED WITH
44
85
  2.6.3
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.require_paths = %w[lib]
21
21
 
22
- spec.add_dependency 'karafka', '>= 2.5.0.beta1', '< 2.6.0'
22
+ spec.add_dependency 'karafka', '>= 2.5.0', '< 2.6.0'
23
23
  spec.add_dependency 'waterdrop', '>= 2.8.0'
24
24
 
25
- spec.required_ruby_version = '>= 3.1.0'
25
+ spec.required_ruby_version = '>= 3.2.0'
26
26
 
27
27
  spec.metadata = {
28
28
  'homepage_uri' => 'https://karafka.io',
@@ -15,17 +15,25 @@ module Karafka
15
15
  # @note Since we run the lookup on subscription groups, the search will automatically
16
16
  # expand with matching patterns
17
17
  def karafka_consumer_find_candidate_topics(requested_topic, requested_consumer_group)
18
- karafka_consumer_find_subscription_groups(requested_consumer_group)
19
- # multiplexed subscriptions of the same origin share name, thus we can reduced
20
- # multiplexing to the first one as during testing, there is no multiplexing parallel
21
- # execution anyhow
22
- .uniq(&:name)
23
- .map(&:topics)
24
- .filter_map do |topics|
25
- topics.find(requested_topic.to_s)
26
- rescue Karafka::Errors::TopicNotFoundError
27
- nil
28
- end
18
+ subscription_groups = karafka_consumer_find_subscription_groups(requested_consumer_group)
19
+
20
+ candidate_topics = []
21
+
22
+ subscription_groups.each do |group|
23
+ topic = group.topics.find(requested_topic.to_s)
24
+
25
+ next unless topic
26
+
27
+ candidate_topics << topic
28
+ rescue Karafka::Errors::TopicNotFoundError
29
+ # Skip groups that don't have the requested topic
30
+ next
31
+ end
32
+
33
+ # Remove duplicate topics from multiplexed subscriptions
34
+ # (Multiplexed subscriptions share the same name, and during testing
35
+ # there's no parallel execution anyway, so we only need the first one)
36
+ candidate_topics.uniq { |topic| topic.subscription_group.name }
29
37
  end
30
38
 
31
39
  # Finds subscription groups from the requested consumer group or selects all if no
@@ -92,7 +92,7 @@ module Karafka
92
92
  consumer_obj = if defined?(@consumer)
93
93
  @consumer
94
94
  else
95
- @_karafka_consumer_mappings&.dig(message[:topic])
95
+ _karafka_find_consumer_for_message(message)
96
96
  end
97
97
  # Consumer needs to be defined in order to pass messages to it
98
98
  return unless defined?(consumer_obj)
@@ -103,6 +103,9 @@ module Karafka
103
103
  # We target to the consumer only messages that were produced to it, since specs may also
104
104
  # produce other messages targeting other topics
105
105
  return unless message[:topic] == consumer_obj.topic.name
106
+ # If consumer_group is explicitly specified, verify it matches
107
+ return if message[:consumer_group] &&
108
+ message[:consumer_group].to_s != consumer_obj.topic.consumer_group.name
106
109
 
107
110
  # Build message metadata and copy any metadata that would come from the message
108
111
  metadata = _karafka_message_metadata_defaults(consumer_obj)
@@ -144,7 +147,8 @@ module Karafka
144
147
  elsif defined?(@consumer)
145
148
  @consumer.topic.name
146
149
  else
147
- @_karafka_consumer_mappings&.keys&.last
150
+ last_consumer = @_karafka_consumer_mappings&.values&.last
151
+ last_consumer&.topic&.name
148
152
  end
149
153
  Karafka.producer.produce_sync(
150
154
  {
@@ -165,8 +169,52 @@ module Karafka
165
169
  @_karafka_consumer_messages
166
170
  end
167
171
 
172
+ # Produces message to a specific consumer instance
173
+ # Use when testing multiple consumers for the same topic
174
+ #
175
+ # @param consumer_instance [Object] the consumer to produce to
176
+ # @param payload [String] message content (usually serialized JSON) to deliver to the
177
+ # consumer
178
+ # @param metadata [Hash] any metadata to dispatch alongside the payload
179
+ #
180
+ # @example Produce to specific consumer when multiple exist for same topic
181
+ # consumer1 = @karafka.consumer_for(:events, :analytics_group)
182
+ # consumer2 = @karafka.consumer_for(:events, :notifications_group)
183
+ # @karafka.produce_to(consumer1, { 'event' => 'click' }.to_json)
184
+ def _karafka_produce_to(consumer_instance, payload, metadata = {})
185
+ _karafka_produce(
186
+ payload,
187
+ metadata.merge(
188
+ topic: consumer_instance.topic.name,
189
+ consumer_group: consumer_instance.topic.consumer_group.name
190
+ )
191
+ )
192
+ end
193
+
168
194
  private
169
195
 
196
+ # Finds a consumer for the given message with backward-compatible fallback
197
+ # @param message [Hash] the message being routed
198
+ # @return [Object, nil] the consumer instance or nil
199
+ def _karafka_find_consumer_for_message(message)
200
+ return nil unless @_karafka_consumer_mappings
201
+
202
+ topic_name = message[:topic]
203
+ consumer_group = message[:consumer_group]
204
+
205
+ if consumer_group
206
+ # Explicit consumer group - find by composite key pattern
207
+ @_karafka_consumer_mappings.values.find do |c|
208
+ c.topic.name == topic_name && c.topic.consumer_group.name == consumer_group.to_s
209
+ end
210
+ else
211
+ # No consumer group specified - find all consumers for this topic
212
+ matching = @_karafka_consumer_mappings.values.select { |c| c.topic.name == topic_name }
213
+ # If exactly one consumer matches, use it (backward compatible)
214
+ matching.size == 1 ? matching.first : nil
215
+ end
216
+ end
217
+
170
218
  # @param consumer_obj [Karafka::BaseConsumer] consumer reference
171
219
  # @return [Hash] message default options
172
220
  def _karafka_message_metadata_defaults(consumer_obj)
@@ -200,10 +248,10 @@ module Karafka
200
248
  @consumer.coordinator = coordinators.find_or_create(topic.name, 0)
201
249
  @consumer.coordinator.seek_offset = 0
202
250
  # Indicate usage as for tests no direct enqueuing happens
203
- @consumer.instance_variable_set('@used', true)
251
+ @consumer.instance_variable_set(:@used, true)
204
252
  expansions = processing_cfg.expansions_selector.find(topic)
205
253
  expansions.each { |expansion| @consumer.singleton_class.include(expansion) }
206
- @_karafka_consumer_mappings[topic.name] = @consumer
254
+ @_karafka_consumer_mappings[topic.id] = @consumer
207
255
  @consumer
208
256
  end
209
257
  end
@@ -11,14 +11,19 @@ module Karafka
11
11
  @minitest_example = minitest_example
12
12
  end
13
13
 
14
- # @param args Anything that the `#_karafka_consumer_for` accepts
15
- def consumer_for(*args)
16
- @minitest_example._karafka_consumer_for(*args)
14
+ # Forwards all arguments to `#_karafka_consumer_for`
15
+ def consumer_for(*)
16
+ @minitest_example._karafka_consumer_for(*)
17
17
  end
18
18
 
19
- # @param args Anything that `#_karafka_produce` accepts
20
- def produce(*args)
21
- @minitest_example._karafka_produce(*args)
19
+ # Forwards all arguments to `#_karafka_produce`
20
+ def produce(*)
21
+ @minitest_example._karafka_produce(*)
22
+ end
23
+
24
+ # Forwards all arguments to `#_karafka_produce_to`
25
+ def produce_to(*)
26
+ @minitest_example._karafka_produce_to(*)
22
27
  end
23
28
 
24
29
  # @return [Array<Hash>] messages produced via `Karafka#producer`
@@ -42,8 +42,8 @@ module Karafka
42
42
  base.before(:context) do
43
43
  Karafka::Testing.ensure_karafka_initialized!
44
44
  @_karafka_shared_producer_client = WaterDrop::Clients::Dummy.new(-1)
45
- Karafka.producer.instance_variable_set(:'@client', @_karafka_shared_producer_client)
46
- Karafka.producer.instance_variable_set(:'@pid', ::Process.pid)
45
+ Karafka.producer.instance_variable_set(:@client, @_karafka_shared_producer_client)
46
+ Karafka.producer.instance_variable_set(:@pid, ::Process.pid)
47
47
  end
48
48
 
49
49
  base.prepend_before do
@@ -107,7 +107,7 @@ module Karafka
107
107
  consumer_obj = if defined?(consumer)
108
108
  consumer
109
109
  else
110
- @_karafka_consumer_mappings&.dig(message[:topic])
110
+ _karafka_find_consumer_for_message(message)
111
111
  end
112
112
  # Consumer needs to be defined in order to pass messages to it
113
113
  return unless consumer_obj
@@ -118,6 +118,9 @@ module Karafka
118
118
  # We target to the consumer only messages that were produced to it, since specs may also
119
119
  # produce other messages targeting other topics
120
120
  return unless message[:topic] == consumer_obj.topic.name
121
+ # If consumer_group is explicitly specified, verify it matches
122
+ return if message[:consumer_group] &&
123
+ message[:consumer_group].to_s != consumer_obj.topic.consumer_group.name
121
124
 
122
125
  # Build message metadata and copy any metadata that would come from the message
123
126
  metadata = _karafka_message_metadata_defaults(consumer_obj)
@@ -160,7 +163,8 @@ module Karafka
160
163
  elsif defined?(consumer)
161
164
  consumer.topic.name
162
165
  else
163
- @_karafka_consumer_mappings&.keys&.last
166
+ last_consumer = @_karafka_consumer_mappings&.values&.last
167
+ last_consumer&.topic&.name
164
168
  end
165
169
  Karafka.producer.produce_sync(
166
170
  {
@@ -175,8 +179,55 @@ module Karafka
175
179
  _karafka_producer_client.messages
176
180
  end
177
181
 
182
+ # Produces message to a specific consumer instance
183
+ # Use when testing multiple consumers for the same topic
184
+ #
185
+ # @param consumer_instance [Object] the consumer to produce to
186
+ # @param payload [String] message content (usually serialized JSON) to deliver to the
187
+ # consumer
188
+ # @param metadata [Hash] any metadata to dispatch alongside the payload
189
+ #
190
+ # @example Produce to specific consumer when multiple exist for same topic
191
+ # let(:consumer1) { karafka.consumer_for(:events, :analytics_group) }
192
+ # let(:consumer2) { karafka.consumer_for(:events, :notifications_group) }
193
+ #
194
+ # before do
195
+ # karafka.produce_to(consumer1, { 'event' => 'click' }.to_json)
196
+ # end
197
+ def _karafka_produce_to(consumer_instance, payload, metadata = {})
198
+ _karafka_produce(
199
+ payload,
200
+ metadata.merge(
201
+ topic: consumer_instance.topic.name,
202
+ consumer_group: consumer_instance.topic.consumer_group.name
203
+ )
204
+ )
205
+ end
206
+
178
207
  private
179
208
 
209
+ # Finds a consumer for the given message with backward-compatible fallback
210
+ # @param message [Hash] the message being routed
211
+ # @return [Object, nil] the consumer instance or nil
212
+ def _karafka_find_consumer_for_message(message)
213
+ return nil unless @_karafka_consumer_mappings
214
+
215
+ topic_name = message[:topic]
216
+ consumer_group = message[:consumer_group]
217
+
218
+ if consumer_group
219
+ # Explicit consumer group - find by composite key pattern
220
+ @_karafka_consumer_mappings.values.find do |c|
221
+ c.topic.name == topic_name && c.topic.consumer_group.name == consumer_group.to_s
222
+ end
223
+ else
224
+ # No consumer group specified - find all consumers for this topic
225
+ matching = @_karafka_consumer_mappings.values.select { |c| c.topic.name == topic_name }
226
+ # If exactly one consumer matches, use it (backward compatible)
227
+ matching.size == 1 ? matching.first : nil
228
+ end
229
+ end
230
+
180
231
  # @param consumer_obj [Karafka::BaseConsumer] consumer reference
181
232
  # @return [Hash] message default options
182
233
  def _karafka_message_metadata_defaults(consumer_obj)
@@ -211,11 +262,11 @@ module Karafka
211
262
  consumer.coordinator = coordinators.find_or_create(topic.name, 0)
212
263
  consumer.coordinator.seek_offset = 0
213
264
  # Indicate usage as for tests no direct enqueuing happens
214
- consumer.instance_variable_set('@used', true)
265
+ consumer.instance_variable_set(:@used, true)
215
266
  expansions = processing_cfg.expansions_selector.find(topic)
216
267
  expansions.each { |expansion| consumer.singleton_class.include(expansion) }
217
268
 
218
- @_karafka_consumer_mappings[topic.name] = consumer
269
+ @_karafka_consumer_mappings[topic.id] = consumer
219
270
  consumer
220
271
  end
221
272
  end
@@ -11,14 +11,19 @@ module Karafka
11
11
  @rspec_example = rspec_example
12
12
  end
13
13
 
14
- # @param args Anything that the `#_karafka_consumer_for` accepts
15
- def consumer_for(*args)
16
- @rspec_example._karafka_consumer_for(*args)
14
+ # Forwards all arguments to `#_karafka_consumer_for`
15
+ def consumer_for(*)
16
+ @rspec_example._karafka_consumer_for(*)
17
17
  end
18
18
 
19
- # @param args Anything that `#_karafka_produce` accepts
20
- def produce(*args)
21
- @rspec_example._karafka_produce(*args)
19
+ # Forwards all arguments to `#_karafka_produce`
20
+ def produce(*)
21
+ @rspec_example._karafka_produce(*)
22
+ end
23
+
24
+ # Forwards all arguments to `#_karafka_produce_to`
25
+ def produce_to(*)
26
+ @rspec_example._karafka_produce_to(*)
22
27
  end
23
28
 
24
29
  # @return [Array<Hash>] messages produced via `Karafka#producer`
@@ -4,6 +4,6 @@
4
4
  module Karafka
5
5
  module Testing
6
6
  # Current version of gem. It should match Karafka framework version
7
- VERSION = '2.5.3'
7
+ VERSION = '2.5.5'
8
8
  end
9
9
  end
data/renovate.json CHANGED
@@ -3,16 +3,9 @@
3
3
  "extends": [
4
4
  "config:recommended"
5
5
  ],
6
+ "minimumReleaseAge": "7 days",
6
7
  "github-actions": {
7
8
  "enabled": true,
8
9
  "pinDigests": true
9
- },
10
- "packageRules": [
11
- {
12
- "matchManagers": [
13
- "github-actions"
14
- ],
15
- "minimumReleaseAge": "7 days"
16
- }
17
- ]
10
+ }
18
11
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: karafka-testing
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.3
4
+ version: 2.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 2.5.0.beta1
18
+ version: 2.5.0
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: 2.6.0
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 2.5.0.beta1
28
+ version: 2.5.0
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: 2.6.0
@@ -51,7 +51,6 @@ extensions: []
51
51
  extra_rdoc_files: []
52
52
  files:
53
53
  - ".coditsu/ci.yml"
54
- - ".diffend.yml"
55
54
  - ".github/CODEOWNERS"
56
55
  - ".github/FUNDING.yml"
57
56
  - ".github/ISSUE_TEMPLATE/bug_report.md"
@@ -64,6 +63,7 @@ files:
64
63
  - ".rspec"
65
64
  - ".ruby-gemset"
66
65
  - ".ruby-version"
66
+ - ".yard-lint.yml"
67
67
  - 2.0-Upgrade.md
68
68
  - CHANGELOG.md
69
69
  - Gemfile
@@ -101,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
101
  requirements:
102
102
  - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 3.1.0
104
+ version: 3.2.0
105
105
  required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - ">="
data/.diffend.yml DELETED
@@ -1,3 +0,0 @@
1
- project_id: 'd19af75c-1296-45bd-950f-3072b0134497'
2
- shareable_id: '0bddcda8-7b70-4bf4-aa80-8bbc3b16106a'
3
- shareable_key: '6d808641-cde2-49c6-9fc3-fbc2fc86a733'