spec_guardian 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 48673c89d4e9875932318e835524bf63b4dc03eb779dc1f6a603b3cca2ac2eef
4
+ data.tar.gz: 78200eb6a421aec33340e58ae22674068b5ab7d05aaac03f5d2b83f5eb176af1
5
+ SHA512:
6
+ metadata.gz: 6ab6111033bbfe79842df12c1a526ea93785080e627d2bfda161e295b9e24b7d618899bdbdbfbb273b05f47f4efa975e038affa83e1002d276a442126b660c12
7
+ data.tar.gz: af439dc047b822ecf3ce303114ed7405923eda8bfa4e6b9ccf8b3b07af5c0cf7a42ae0fd718f84583c8f0efb285221647bb35850a06c0e059598acd46057b26a
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /vendor/bundle/
10
+ spec_guardian-*.gem
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.prettierrc ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "semi": true,
5
+ "singleQuote": true,
6
+ "jsxSingleQuote": false,
7
+ "trailingComma": "all",
8
+ "bracketSpacing": true,
9
+ "bracketSameLine": false,
10
+ "arrowParens": "avoid",
11
+ "printWidth": 120,
12
+ "rubyPlugins": "plugin/trailing_comma",
13
+ "overrides": [
14
+ {
15
+ "files": ["babel.config.js"],
16
+ "options": {
17
+ "trailingComma": "es5"
18
+ }
19
+ },
20
+ {
21
+ "files": ["*.css", "*.scss"],
22
+ "options": {
23
+ "singleQuote": false
24
+ }
25
+ }
26
+ ]
27
+ }
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,21 @@
1
+ # Overwrite or add rules to create your own house style
2
+ #
3
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
4
+ # Layout/SpaceInsideArrayLiteralBrackets:
5
+ # Enabled: false
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: single_quotes
9
+ Style/TrailingCommaInHashLiteral:
10
+ Enabled: true
11
+ EnforcedStyle: no_comma
12
+ Style/EmptyMethod:
13
+ Enabled: true
14
+ EnforcedStyle: expanded
15
+ Style/Documentation:
16
+ Enabled: false
17
+ Style/FrozenStringLiteralComment:
18
+ Enabled: false
19
+ Metrics/MethodLength:
20
+ Enabled: true
21
+ Max: 25
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-02-27
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ ruby '3.3.6'
6
+
7
+ # Specify your gem's dependencies in spec_guardian.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,305 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ spec_guardian (0.1.2)
5
+ anthropic (>= 0.3.2)
6
+ rails (>= 6.0)
7
+ rake (>= 12.0)
8
+ thor (>= 1.2)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ actioncable (8.0.1)
14
+ actionpack (= 8.0.1)
15
+ activesupport (= 8.0.1)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ zeitwerk (~> 2.6)
19
+ actionmailbox (8.0.1)
20
+ actionpack (= 8.0.1)
21
+ activejob (= 8.0.1)
22
+ activerecord (= 8.0.1)
23
+ activestorage (= 8.0.1)
24
+ activesupport (= 8.0.1)
25
+ mail (>= 2.8.0)
26
+ actionmailer (8.0.1)
27
+ actionpack (= 8.0.1)
28
+ actionview (= 8.0.1)
29
+ activejob (= 8.0.1)
30
+ activesupport (= 8.0.1)
31
+ mail (>= 2.8.0)
32
+ rails-dom-testing (~> 2.2)
33
+ actionpack (8.0.1)
34
+ actionview (= 8.0.1)
35
+ activesupport (= 8.0.1)
36
+ nokogiri (>= 1.8.5)
37
+ rack (>= 2.2.4)
38
+ rack-session (>= 1.0.1)
39
+ rack-test (>= 0.6.3)
40
+ rails-dom-testing (~> 2.2)
41
+ rails-html-sanitizer (~> 1.6)
42
+ useragent (~> 0.16)
43
+ actiontext (8.0.1)
44
+ actionpack (= 8.0.1)
45
+ activerecord (= 8.0.1)
46
+ activestorage (= 8.0.1)
47
+ activesupport (= 8.0.1)
48
+ globalid (>= 0.6.0)
49
+ nokogiri (>= 1.8.5)
50
+ actionview (8.0.1)
51
+ activesupport (= 8.0.1)
52
+ builder (~> 3.1)
53
+ erubi (~> 1.11)
54
+ rails-dom-testing (~> 2.2)
55
+ rails-html-sanitizer (~> 1.6)
56
+ activejob (8.0.1)
57
+ activesupport (= 8.0.1)
58
+ globalid (>= 0.3.6)
59
+ activemodel (8.0.1)
60
+ activesupport (= 8.0.1)
61
+ activerecord (8.0.1)
62
+ activemodel (= 8.0.1)
63
+ activesupport (= 8.0.1)
64
+ timeout (>= 0.4.0)
65
+ activestorage (8.0.1)
66
+ actionpack (= 8.0.1)
67
+ activejob (= 8.0.1)
68
+ activerecord (= 8.0.1)
69
+ activesupport (= 8.0.1)
70
+ marcel (~> 1.0)
71
+ activesupport (8.0.1)
72
+ base64
73
+ benchmark (>= 0.3)
74
+ bigdecimal
75
+ concurrent-ruby (~> 1.0, >= 1.3.1)
76
+ connection_pool (>= 2.2.5)
77
+ drb
78
+ i18n (>= 1.6, < 2)
79
+ logger (>= 1.4.2)
80
+ minitest (>= 5.1)
81
+ securerandom (>= 0.3)
82
+ tzinfo (~> 2.0, >= 2.0.5)
83
+ uri (>= 0.13.1)
84
+ anthropic (0.3.2)
85
+ event_stream_parser (>= 0.3.0, < 2.0.0)
86
+ faraday (>= 1)
87
+ faraday-multipart (>= 1)
88
+ ast (2.4.2)
89
+ base64 (0.2.0)
90
+ benchmark (0.4.0)
91
+ bigdecimal (3.1.9)
92
+ builder (3.3.0)
93
+ coderay (1.1.3)
94
+ concurrent-ruby (1.3.5)
95
+ connection_pool (2.5.0)
96
+ crass (1.0.6)
97
+ date (3.4.1)
98
+ diff-lcs (1.6.0)
99
+ drb (2.2.1)
100
+ erubi (1.13.1)
101
+ event_stream_parser (1.0.0)
102
+ faraday (2.12.2)
103
+ faraday-net_http (>= 2.0, < 3.5)
104
+ json
105
+ logger
106
+ faraday-multipart (1.1.0)
107
+ multipart-post (~> 2.0)
108
+ faraday-net_http (3.4.0)
109
+ net-http (>= 0.5.0)
110
+ globalid (1.2.1)
111
+ activesupport (>= 6.1)
112
+ i18n (1.14.7)
113
+ concurrent-ruby (~> 1.0)
114
+ io-console (0.8.0)
115
+ irb (1.15.1)
116
+ pp (>= 0.6.0)
117
+ rdoc (>= 4.0.0)
118
+ reline (>= 0.4.2)
119
+ json (2.10.1)
120
+ language_server-protocol (3.17.0.4)
121
+ lint_roller (1.1.0)
122
+ logger (1.6.6)
123
+ loofah (2.24.0)
124
+ crass (~> 1.0.2)
125
+ nokogiri (>= 1.12.0)
126
+ mail (2.8.1)
127
+ mini_mime (>= 0.1.1)
128
+ net-imap
129
+ net-pop
130
+ net-smtp
131
+ marcel (1.0.4)
132
+ method_source (1.1.0)
133
+ mini_mime (1.1.5)
134
+ minitest (5.25.4)
135
+ multipart-post (2.4.1)
136
+ net-http (0.6.0)
137
+ uri
138
+ net-imap (0.5.6)
139
+ date
140
+ net-protocol
141
+ net-pop (0.1.2)
142
+ net-protocol
143
+ net-protocol (0.2.2)
144
+ timeout
145
+ net-smtp (0.5.1)
146
+ net-protocol
147
+ nio4r (2.7.4)
148
+ nokogiri (1.18.3-aarch64-linux-gnu)
149
+ racc (~> 1.4)
150
+ nokogiri (1.18.3-aarch64-linux-musl)
151
+ racc (~> 1.4)
152
+ nokogiri (1.18.3-arm-linux-gnu)
153
+ racc (~> 1.4)
154
+ nokogiri (1.18.3-arm-linux-musl)
155
+ racc (~> 1.4)
156
+ nokogiri (1.18.3-arm64-darwin)
157
+ racc (~> 1.4)
158
+ nokogiri (1.18.3-x86_64-darwin)
159
+ racc (~> 1.4)
160
+ nokogiri (1.18.3-x86_64-linux-gnu)
161
+ racc (~> 1.4)
162
+ nokogiri (1.18.3-x86_64-linux-musl)
163
+ racc (~> 1.4)
164
+ parallel (1.26.3)
165
+ parser (3.3.7.1)
166
+ ast (~> 2.4.1)
167
+ racc
168
+ pp (0.6.2)
169
+ prettyprint
170
+ prettyprint (0.2.0)
171
+ pry (0.15.2)
172
+ coderay (~> 1.1)
173
+ method_source (~> 1.0)
174
+ psych (5.2.3)
175
+ date
176
+ stringio
177
+ racc (1.8.1)
178
+ rack (3.1.10)
179
+ rack-session (2.1.0)
180
+ base64 (>= 0.1.0)
181
+ rack (>= 3.0.0)
182
+ rack-test (2.2.0)
183
+ rack (>= 1.3)
184
+ rackup (2.2.1)
185
+ rack (>= 3)
186
+ rails (8.0.1)
187
+ actioncable (= 8.0.1)
188
+ actionmailbox (= 8.0.1)
189
+ actionmailer (= 8.0.1)
190
+ actionpack (= 8.0.1)
191
+ actiontext (= 8.0.1)
192
+ actionview (= 8.0.1)
193
+ activejob (= 8.0.1)
194
+ activemodel (= 8.0.1)
195
+ activerecord (= 8.0.1)
196
+ activestorage (= 8.0.1)
197
+ activesupport (= 8.0.1)
198
+ bundler (>= 1.15.0)
199
+ railties (= 8.0.1)
200
+ rails-dom-testing (2.2.0)
201
+ activesupport (>= 5.0.0)
202
+ minitest
203
+ nokogiri (>= 1.6)
204
+ rails-html-sanitizer (1.6.2)
205
+ loofah (~> 2.21)
206
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
207
+ railties (8.0.1)
208
+ actionpack (= 8.0.1)
209
+ activesupport (= 8.0.1)
210
+ irb (~> 1.13)
211
+ rackup (>= 1.0.0)
212
+ rake (>= 12.2)
213
+ thor (~> 1.0, >= 1.2.2)
214
+ zeitwerk (~> 2.6)
215
+ rainbow (3.1.1)
216
+ rake (13.2.1)
217
+ rdoc (6.12.0)
218
+ psych (>= 4.0.0)
219
+ regexp_parser (2.10.0)
220
+ reline (0.6.0)
221
+ io-console (~> 0.5)
222
+ rspec (3.13.0)
223
+ rspec-core (~> 3.13.0)
224
+ rspec-expectations (~> 3.13.0)
225
+ rspec-mocks (~> 3.13.0)
226
+ rspec-core (3.13.3)
227
+ rspec-support (~> 3.13.0)
228
+ rspec-expectations (3.13.3)
229
+ diff-lcs (>= 1.2.0, < 2.0)
230
+ rspec-support (~> 3.13.0)
231
+ rspec-mocks (3.13.2)
232
+ diff-lcs (>= 1.2.0, < 2.0)
233
+ rspec-support (~> 3.13.0)
234
+ rspec-support (3.13.2)
235
+ rubocop (1.73.1)
236
+ json (~> 2.3)
237
+ language_server-protocol (~> 3.17.0.2)
238
+ lint_roller (~> 1.1.0)
239
+ parallel (~> 1.10)
240
+ parser (>= 3.3.0.2)
241
+ rainbow (>= 2.2.2, < 4.0)
242
+ regexp_parser (>= 2.9.3, < 3.0)
243
+ rubocop-ast (>= 1.38.0, < 2.0)
244
+ ruby-progressbar (~> 1.7)
245
+ unicode-display_width (>= 2.4.0, < 4.0)
246
+ rubocop-ast (1.38.1)
247
+ parser (>= 3.3.1.0)
248
+ rubocop-packaging (0.5.2)
249
+ rubocop (>= 1.33, < 2.0)
250
+ rubocop-performance (1.24.0)
251
+ lint_roller (~> 1.1)
252
+ rubocop (>= 1.72.1, < 2.0)
253
+ rubocop-ast (>= 1.38.0, < 2.0)
254
+ rubocop-rspec (3.5.0)
255
+ lint_roller (~> 1.1)
256
+ rubocop (~> 1.72, >= 1.72.1)
257
+ rubocop-shopify (2.15.1)
258
+ rubocop (~> 1.51)
259
+ rubocop-thread_safety (0.7.0)
260
+ lint_roller (~> 1.1)
261
+ rubocop (~> 1.72, >= 1.72.1)
262
+ ruby-progressbar (1.13.0)
263
+ securerandom (0.4.1)
264
+ stringio (3.1.5)
265
+ thor (1.3.2)
266
+ timeout (0.4.3)
267
+ tzinfo (2.0.6)
268
+ concurrent-ruby (~> 1.0)
269
+ unicode-display_width (3.1.4)
270
+ unicode-emoji (~> 4.0, >= 4.0.4)
271
+ unicode-emoji (4.0.4)
272
+ uri (1.0.3)
273
+ useragent (0.16.11)
274
+ websocket-driver (0.7.7)
275
+ base64
276
+ websocket-extensions (>= 0.1.0)
277
+ websocket-extensions (0.1.5)
278
+ zeitwerk (2.7.2)
279
+
280
+ PLATFORMS
281
+ aarch64-linux-gnu
282
+ aarch64-linux-musl
283
+ arm-linux-gnu
284
+ arm-linux-musl
285
+ arm64-darwin
286
+ x86_64-darwin
287
+ x86_64-linux-gnu
288
+ x86_64-linux-musl
289
+
290
+ DEPENDENCIES
291
+ pry (~> 0.15.2)
292
+ rspec (~> 3.0)
293
+ rubocop (~> 1.73, >= 1.73.1)
294
+ rubocop-packaging (~> 0.5.2)
295
+ rubocop-performance (~> 1.24)
296
+ rubocop-rspec (~> 3.5)
297
+ rubocop-shopify (~> 2.15, >= 2.15.1)
298
+ rubocop-thread_safety (~> 0.7.0)
299
+ spec_guardian!
300
+
301
+ RUBY VERSION
302
+ ruby 3.3.6p108
303
+
304
+ BUNDLED WITH
305
+ 2.6.3
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Darren Terhune
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # SpecGuardian
2
+
3
+ A Ruby gem that uses AI (Claude) to automatically generate test files for your Rails application code.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'spec_guardian'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```ruby
16
+ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```ruby
22
+ gem install spec_guardian
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ Create an initializer file at `config/initializers/spec_guardian.rb` by running the install command to create this file `rails g spec_guardian:install`
28
+
29
+ ```ruby
30
+ SpecGuardian.configure do |config|
31
+ config.api_key = ENV['ANTHROPIC_API_KEY'] # Your Anthropic API key
32
+ config.ai_model = "claude-3-7-sonnet-20250219" # The Claude model to use
33
+ config.max_tokens = 20_000 # The max tokens to use per request defaults to 20_000
34
+ config.test_style = "thorough" # Options: minimal, thorough, edge_cases
35
+ end
36
+ ```
37
+
38
+ NOTE: The `max_tokens` determines the length of the response. If you are seeing files cut off, then you need to increase this value.
39
+
40
+ Ensure you have your Anthropic API key set as an environment variable or directly in the configuration. You can signup here https://console.anthropic.com to generate an account and get an API key.
41
+
42
+ ## Usage
43
+
44
+ ### Command Line
45
+
46
+ You can use the executable to generate a test for any file in your Rails application:
47
+
48
+ ```ruby
49
+ bundle exec spec_guardian app/models/user.rb
50
+ ```
51
+
52
+ During testing, models files with around 200-300 lines of code cost around $0.02. This will vary of course depending on how complex your code is. If you have rubocop installed you may need to adjust specs. It also isn't 100% accurate with generating perfect code so expect to look over the files it generates and be ready to fix things.
53
+
54
+ ### Rake Task
55
+
56
+ Alternatively, you can use the provided Rake task:
57
+
58
+ ```ruby
59
+ bundle exec rake spec_guardian:generate[app/models/user.rb]
60
+ ```
61
+
62
+ If you are using zsh you may need to quote your input:
63
+
64
+ ```ruby
65
+ bundle exec rake spec_guardian:generate['app/models/user.rb']
66
+ ```
67
+
68
+ ## Features
69
+
70
+ 1. Automatically detects whether your application uses RSpec or Minitest
71
+ 2. Places generated test files in the appropriate location (spec/ or test/ directory)
72
+ 3. Customizes test generation based on file type (model, controller, etc.)
73
+ 4. Supports different test styles (minimal, thorough, or edge_cases)
74
+
75
+ ## How It Works
76
+
77
+ 1. The gem detects your project's test framework (RSpec, Minitest, or Rails default)
78
+ 2. It analyzes the source file to determine its type (model, controller, etc.)
79
+ 3. It sends the source code to Claude's API with a specialized prompt
80
+ 4. It creates the test file in the correct location with the AI-generated test code
81
+
82
+ ## Contributing
83
+
84
+ Bug reports and pull requests are welcome on GitHub at https://github.com/darrenterhune/spec_guardian.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'spec_guardian'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require 'irb'
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/spec_guardian ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'spec_guardian'
5
+
6
+ # Parse command line arguments
7
+ if ARGV.empty? || ARGV[0] == '--help' || ARGV[0] == '-h'
8
+ puts 'Usage: spec_guardian PATH_TO_FILE'
9
+ puts 'Generates a test file for the given source file using AI.'
10
+ exit 0
11
+ end
12
+
13
+ begin
14
+ # Initialize Rails environment
15
+ require File.expand_path('config/environment', Dir.pwd)
16
+
17
+ # Generate the test
18
+ SpecGuardian.generate_test(ARGV[0])
19
+ rescue LoadError => e
20
+ puts 'Error: This command must be run from a Rails application root directory.'
21
+ exit 1
22
+ rescue SpecGuardian::Error => e
23
+ puts "Error: #{e.message}"
24
+ exit 1
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecGuardian
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ def copy_config
9
+ template('config/initializers/spec_guardian.rb')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # config/initializers/spec_guardian.rb
2
+ SpecGuardian.configure do |config|
3
+ config.api_key = ENV['ANTHROPIC_API_KEY'] # Your Anthropic API key
4
+ config.ai_model = 'claude-3-7-sonnet-20250219' # The Claude model to use
5
+ config.max_tokens = 20_000 # The max tokens to use per request defaults to 20_000
6
+ config.test_style = 'thorough' # Options: minimal, thorough, edge_cases
7
+ end
@@ -0,0 +1,74 @@
1
+ require 'anthropic'
2
+
3
+ module SpecGuardian
4
+ class AiClient
5
+ def self.generate_test(source_code, framework, file_path)
6
+ client = Anthropic::Client.new(
7
+ access_token: SpecGuardian.configuration.api_key
8
+ )
9
+
10
+ # File type detection
11
+ file_type = detect_file_type(file_path)
12
+
13
+ # Create the prompt
14
+ prompt = create_prompt(source_code, framework, file_type)
15
+ model = SpecGuardian.configuration.ai_model
16
+ max_tokens = SpecGuardian.configuration.max_tokens || 20_000
17
+
18
+ # Make API call
19
+ response = client.messages(
20
+ parameters: {
21
+ model: model,
22
+ max_tokens: max_tokens,
23
+ messages: [
24
+ {
25
+ role: 'user',
26
+ content: prompt
27
+ }
28
+ ]
29
+ }
30
+ )
31
+
32
+ # Extract and return the test code
33
+ response['content'][0]['text']
34
+ end
35
+
36
+ def self.detect_file_type(file_path)
37
+ if file_path.include?('/models/')
38
+ :model
39
+ elsif file_path.include?('/controllers/')
40
+ :controller
41
+ elsif file_path.include?('/views/')
42
+ :view
43
+ elsif file_path.include?('/helpers/')
44
+ :helper
45
+ elsif file_path.include?('/mailers/')
46
+ :mailer
47
+ elsif file_path.include?('/jobs/')
48
+ :job
49
+ else
50
+ :other
51
+ end
52
+ end
53
+
54
+ def self.create_prompt(source_code, framework, file_type)
55
+ framework_name = framework == :rspec ? 'RSpec' : 'Minitest'
56
+ style = SpecGuardian.configuration.test_style
57
+
58
+ <<~PROMPT
59
+ Generate a comprehensive Rails #{framework_name} test for the following #{file_type} code.
60
+ The test should follow best practices for Rails testing with #{framework_name}.
61
+
62
+ Test style preference: #{style}
63
+
64
+ Here's the source code to test:
65
+
66
+ ```ruby
67
+ #{source_code}
68
+ ```
69
+
70
+ Please generate only the test code without any explanations. The output should be valid Ruby that can be saved directly to a test file. Remove comments. Do not include code formatting markdown like '```ruby'. Do not test rails internals like associations, scopes or table columns.
71
+ PROMPT
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,12 @@
1
+ module SpecGuardian
2
+ class Configuration
3
+ attr_accessor :api_key, :ai_model, :test_style, :max_tokens
4
+
5
+ def initialize
6
+ @api_key = ENV['ANTHROPIC_API_KEY']
7
+ @ai_model = 'claude-3-7-sonnet-20250219'
8
+ @max_tokens = 20_000
9
+ @test_style = 'thorough' # Options: minimal, thorough, edge_cases
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ module SpecGuardian
4
+ class FilePathResolver
5
+ def self.resolve(file_path, framework)
6
+ # Remove leading ./ if present
7
+ file_path = file_path.sub(%r{^\./}, '')
8
+
9
+ # Extract the relative path from app/
10
+ relative_path = if file_path.start_with?('app/')
11
+ file_path.sub(%r{^app/}, '')
12
+ else
13
+ file_path
14
+ end
15
+
16
+ # Determine test directory based on framework
17
+ test_dir = framework == :rspec ? 'spec' : 'test'
18
+
19
+ # Handle different file types
20
+ if relative_path.start_with?('models/')
21
+ "#{test_dir}/#{relative_path.sub(/\.rb$/, '_spec.rb')}"
22
+ elsif relative_path.start_with?('controllers/')
23
+ "#{test_dir}/#{relative_path.sub(/\.rb$/, '_spec.rb')}"
24
+ elsif relative_path.start_with?('views/')
25
+ view_path = relative_path.sub(%r{^views/}, '')
26
+ "#{test_dir}/views/#{view_path.sub(/\.\w+\.erb$/, '.html_spec.rb')}"
27
+ else
28
+ # For other files, maintain the same structure
29
+ "#{test_dir}/#{relative_path.sub(/\.rb$/, '_spec.rb')}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ require 'rails'
2
+
3
+ module SpecGuardian
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load 'tasks/generate_tests.rake'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ module SpecGuardian
2
+ class TestFrameworkDetector
3
+ def self.detect
4
+ return :rspec if File.exist?('spec') && gemfile_includes?('rspec')
5
+
6
+ return :minitest if File.exist?('test') && gemfile_includes?('minitest')
7
+
8
+ if Rails.version.to_i >= 5
9
+ :minitest
10
+ else
11
+ :test_unit
12
+ end
13
+ end
14
+
15
+ def self.gemfile_includes?(gem_name)
16
+ return false unless File.exist?('Gemfile')
17
+
18
+ File.read('Gemfile').include?(gem_name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecGuardian
4
+ VERSION = '0.1.2'
5
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_guardian/version'
4
+ require_relative 'spec_guardian/configuration'
5
+ require_relative 'spec_guardian/test_framework_detector'
6
+ require_relative 'spec_guardian/file_path_resolver'
7
+ require_relative 'spec_guardian/ai_client'
8
+
9
+ module SpecGuardian
10
+ class Error < StandardError; end
11
+
12
+ class << self
13
+ attr_accessor :configuration
14
+
15
+ def configure
16
+ self.configuration ||= Configuration.new
17
+ yield(configuration) if block_given?
18
+ end
19
+
20
+ def generate_test(file_path)
21
+ # Validate the file exists
22
+ raise Error, "File not found: #{file_path}" unless File.exist?(file_path)
23
+
24
+ # Detect the test framework
25
+ framework = TestFrameworkDetector.detect
26
+
27
+ # Resolve the test file path
28
+ test_file_path = FilePathResolver.resolve(file_path, framework)
29
+
30
+ # Generate the test content
31
+ source_code = File.read(file_path)
32
+ test_content = AiClient.generate_test(source_code, framework, file_path)
33
+
34
+ # Create the test file
35
+ FileUtils.mkdir_p(File.dirname(test_file_path))
36
+ File.write(test_file_path, test_content)
37
+
38
+ puts "Test file generated: #{test_file_path}"
39
+ test_file_path
40
+ end
41
+ end
42
+ end
43
+
44
+ require 'spec_guardian/railtie' if defined?(Rails)
@@ -0,0 +1,13 @@
1
+ namespace :spec_guardian do
2
+ desc 'Generate a test file for a given source file using AI'
3
+ task :generate, [:file_path] => :environment do |t, args|
4
+ if args[:file_path].nil?
5
+ puts 'Error: Please provide a file path.'
6
+ puts 'Usage: rake spec_guardian:generate[app/models/user.rb]'
7
+ puts "You may need to use quotes if using zsh e.g. ['app/models/user.rb']"
8
+ exit 1
9
+ end
10
+
11
+ SpecGuardian.generate_test(args[:file_path])
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module SpecGuardian
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/spec_guardian/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'spec_guardian'
7
+ spec.version = SpecGuardian::VERSION
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.authors = ['Darren Terhune']
10
+ spec.email = ['darrenterhune@gmail.com']
11
+
12
+ spec.summary = 'Generate test files for Rails apps using AI'
13
+ spec.description = 'A gem that uses AI to automatically generate test files for your Rails application code'
14
+ spec.homepage = 'https://github.com/darrenterhune/spec_guardian'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.1.6')
17
+
18
+ spec.metadata = {
19
+ 'bug_tracker_uri' => 'https://github.com/darrenterhune/spec_guardian/issues',
20
+ 'changelog_uri' => "#{spec.homepage}/blob/main/CHANGELOG.md",
21
+ 'homepage_uri' => 'https://github.com/darrenterhune/spec_guardian',
22
+ 'source_code_uri' => 'https://github.com/darrenterhune/spec_guardian'
23
+ }
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = 'bin'
29
+ spec.executables = ['spec_guardian']
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.add_dependency 'anthropic', '>= 0.3.2'
33
+ spec.add_dependency 'rails', '>= 6.0'
34
+ spec.add_dependency 'rake', '>= 12.0'
35
+ spec.add_dependency 'thor', '>= 1.2'
36
+ spec.add_development_dependency 'pry', '~> 0.15.2'
37
+ spec.add_development_dependency 'rspec', '~> 3.0'
38
+ spec.add_development_dependency 'rubocop', '~> 1.73', '>= 1.73.1'
39
+ spec.add_development_dependency 'rubocop-packaging', '~> 0.5.2'
40
+ spec.add_development_dependency 'rubocop-performance', '~> 1.24'
41
+ spec.add_development_dependency 'rubocop-rspec', '~> 3.5'
42
+ spec.add_development_dependency 'rubocop-shopify', '~> 2.15', '>= 2.15.1'
43
+ spec.add_development_dependency 'rubocop-thread_safety', '~> 0.7.0'
44
+ end
metadata ADDED
@@ -0,0 +1,256 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spec_guardian
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Darren Terhune
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-03-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: anthropic
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.15.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.15.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.73'
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 1.73.1
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '1.73'
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.73.1
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop-packaging
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 0.5.2
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.5.2
131
+ - !ruby/object:Gem::Dependency
132
+ name: rubocop-performance
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '1.24'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.24'
145
+ - !ruby/object:Gem::Dependency
146
+ name: rubocop-rspec
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.5'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '3.5'
159
+ - !ruby/object:Gem::Dependency
160
+ name: rubocop-shopify
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '2.15'
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: 2.15.1
169
+ type: :development
170
+ prerelease: false
171
+ version_requirements: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - "~>"
174
+ - !ruby/object:Gem::Version
175
+ version: '2.15'
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: 2.15.1
179
+ - !ruby/object:Gem::Dependency
180
+ name: rubocop-thread_safety
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: 0.7.0
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - "~>"
191
+ - !ruby/object:Gem::Version
192
+ version: 0.7.0
193
+ description: A gem that uses AI to automatically generate test files for your Rails
194
+ application code
195
+ email:
196
+ - darrenterhune@gmail.com
197
+ executables:
198
+ - spec_guardian
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".DS_Store"
203
+ - ".gitignore"
204
+ - ".prettierrc"
205
+ - ".rspec"
206
+ - ".rubocop.yml"
207
+ - ".ruby-version"
208
+ - CHANGELOG.md
209
+ - Gemfile
210
+ - Gemfile.lock
211
+ - LICENSE.md
212
+ - README.md
213
+ - Rakefile
214
+ - bin/console
215
+ - bin/setup
216
+ - bin/spec_guardian
217
+ - lib/generators/spec_guardian/install_generator.rb
218
+ - lib/generators/spec_guardian/templates/config/initializers/spec_guardian.rb
219
+ - lib/spec_guardian.rb
220
+ - lib/spec_guardian/ai_client.rb
221
+ - lib/spec_guardian/configuration.rb
222
+ - lib/spec_guardian/file_path_resolver.rb
223
+ - lib/spec_guardian/railtie.rb
224
+ - lib/spec_guardian/test_framework_detector.rb
225
+ - lib/spec_guardian/version.rb
226
+ - lib/tasks/generate_tests.rake
227
+ - sig/spec_guardian.rbs
228
+ - spec_guardian.gemspec
229
+ homepage: https://github.com/darrenterhune/spec_guardian
230
+ licenses:
231
+ - MIT
232
+ metadata:
233
+ bug_tracker_uri: https://github.com/darrenterhune/spec_guardian/issues
234
+ changelog_uri: https://github.com/darrenterhune/spec_guardian/blob/main/CHANGELOG.md
235
+ homepage_uri: https://github.com/darrenterhune/spec_guardian
236
+ source_code_uri: https://github.com/darrenterhune/spec_guardian
237
+ post_install_message:
238
+ rdoc_options: []
239
+ require_paths:
240
+ - lib
241
+ required_ruby_version: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: 3.1.6
246
+ required_rubygems_version: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ requirements: []
252
+ rubygems_version: 3.5.22
253
+ signing_key:
254
+ specification_version: 4
255
+ summary: Generate test files for Rails apps using AI
256
+ test_files: []