ai_rspec_writer 1.0.5 → 1.0.6

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: 5a56acb3b0f314948afc0cf7619369116cf7c6759ae3015af150e6476cc7ac46
4
- data.tar.gz: 4981ef4684fe723b591ccdfed938e5684e62180e88157bbcfb5cd484706be8c6
3
+ metadata.gz: be389107c5f70774c3581dbf04170412e8a042adfca34873d9e7d3f73f6b1e33
4
+ data.tar.gz: 7164b14789a8de01a0e27487f0e66e6c14de572638b956f444bcb262e60a4464
5
5
  SHA512:
6
- metadata.gz: a4de160d97ab90474b3062d8b9865bdb459e722420fc52b2fd5085349512000310bc01eef750cb9c4a6d65c256e0d4fbf63900aca9898d41f71702813b94bc3f
7
- data.tar.gz: 8cc60641cd834cdd101de19863bd1281a21d001d3d3d85880dbefbfbc3feb84edc141a0deac4ddd6955ca627c7ecac257cea5a640ef95b2538d662320e3b5223
6
+ metadata.gz: b38dd7f0a8d2a04e3176a6add0bce16ca0b64daae60305f930f8851129e89ed316c1fbe03164cc980f38be2b38ca32d2ebe2ce583d2df5fb21fc56bfd03bce8e
7
+ data.tar.gz: d197257e867ae0cdeb5b06f2ec262415140575c1c9d77a16b54dd1e93a25d2182644bb71314afb89d595068564d3e8c49132e51d5ecfbb1af561ae723da2535a
data/bin/ai_rspec_writer CHANGED
@@ -6,7 +6,7 @@ require "optparse"
6
6
  require_relative "../lib/ai_rspec_writer"
7
7
  require_relative "../lib/ai_rspec_writer/schema_extractor"
8
8
 
9
- if AiRspecWriter.configuration.chatgpt_api_key.nil? || AiRspecWriter.configuration.gemini_api_key.nil?
9
+ if AiRspecWriter.configuration.chatgpt_api_key.nil? && AiRspecWriter.configuration.gemini_api_key.nil?
10
10
  puts "❌ API key is missing.".red
11
11
  puts "ℹ️ Set it in `.env`, or use `export CHATGPT_API_KEY=your-key` `export GEMINI_API_KEY=your-key`.".yellow
12
12
  exit 1
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gemini-ai'
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
4
6
  require 'colorize'
5
7
  require 'tty-spinner'
6
8
 
@@ -8,20 +10,19 @@ module AIRspecWriter
8
10
  module AIProviders
9
11
  # Generates RSpec tests using Google's Gemini AI API
10
12
  class GeminiGenerator
13
+ BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models'
14
+
11
15
  def initialize(ec: nil, schema: nil)
12
16
  @extra_comment = ec
13
17
  @schema = schema
14
- @client = Gemini.new(
15
- credentials: {
16
- service: 'generative-language-api',
17
- api_key: AiRspecWriter.configuration.gemini_api_key
18
- },
19
- options: { model: AiRspecWriter.configuration.gemini_model, server_sent_events: true }
20
- )
18
+ @api_key = AiRspecWriter.configuration.gemini_api_key
19
+ @model = AiRspecWriter.configuration.gemini_model
20
+
21
+ validate_configuration
21
22
  end
22
23
 
23
24
  def generate_spec(file_content)
24
- fixed_promt = "Write RSpec tests for this Ruby code with all factory files and rubocop standards:"
25
+ fixed_prompt = "Write RSpec tests for this Ruby code with all factory files and rubocop standards:"
25
26
  my_schema = "Check my table definition details: #{@schema}" if @schema
26
27
 
27
28
  # Define loading spinner
@@ -31,19 +32,13 @@ module AIRspecWriter
31
32
  spinner.auto_spin
32
33
 
33
34
  begin
34
- result = @client.generate_content({
35
- contents: {
36
- role: "user",
37
- parts: {
38
- text: "#{fixed_promt}\n\n#{file_content}\n\n#{@extra_comment}\n\n#{my_schema}"
39
- }
40
- }
41
- })
42
-
43
- if result["candidates"].first["content"]["parts"].first["text"].present?
35
+ content_text = build_content_text(fixed_prompt, file_content, my_schema)
36
+ response_text = call_gemini_api(content_text)
37
+
38
+ if response_text&.strip&.length&.positive?
44
39
  # Stop the spinner when AI response is ready
45
40
  spinner.success("✅ Done!")
46
- return result["candidates"].first["content"]["parts"].first["text"]
41
+ return response_text
47
42
  else
48
43
  spinner.error('(Gemini AI returned an empty response.)')
49
44
  return nil
@@ -53,6 +48,94 @@ module AIRspecWriter
53
48
  exit 1
54
49
  end
55
50
  end
51
+
52
+ private
53
+
54
+ def validate_configuration
55
+ raise ConfigurationError, "Gemini API key is required" if @api_key.nil? || @api_key.empty?
56
+ raise ConfigurationError, "Gemini model is required" if @model.nil? || @model.empty?
57
+ end
58
+
59
+ def build_content_text(fixed_prompt, file_content, my_schema)
60
+ content_parts = [fixed_prompt, file_content, @extra_comment, my_schema].compact
61
+ content_parts.join("\n\n")
62
+ end
63
+
64
+ def call_gemini_api(content_text)
65
+ url = build_api_url
66
+ payload = build_payload(content_text)
67
+
68
+ response = make_http_request(url, payload)
69
+ parse_response(response)
70
+ end
71
+
72
+ def build_api_url
73
+ "#{BASE_URL}/#{@model}:generateContent?key=#{@api_key}"
74
+ end
75
+
76
+ def build_payload(content_text)
77
+ {
78
+ contents: [
79
+ {
80
+ role: "user",
81
+ parts: [
82
+ { text: content_text }
83
+ ]
84
+ }
85
+ ],
86
+ generationConfig: {
87
+ temperature: 0.7,
88
+ topK: 40,
89
+ topP: 0.95,
90
+ maxOutputTokens: 8192
91
+ }
92
+ }
93
+ end
94
+
95
+ def make_http_request(url, payload)
96
+ uri = URI(url)
97
+ http = Net::HTTP.new(uri.host, uri.port)
98
+ http.use_ssl = true
99
+ http.read_timeout = 60 # Set timeout for long responses
100
+
101
+ request = Net::HTTP::Post.new(uri)
102
+ request['Content-Type'] = 'application/json'
103
+ request.body = payload.to_json
104
+
105
+ response = http.request(request)
106
+
107
+ unless response.is_a?(Net::HTTPSuccess)
108
+ raise ApiError, "HTTP #{response.code}: #{response.body}"
109
+ end
110
+
111
+ response
112
+ end
113
+
114
+ def parse_response(response)
115
+ data = JSON.parse(response.body)
116
+
117
+ if data['error']
118
+ raise ApiError, "API error: #{data['error']['message']}"
119
+ end
120
+
121
+ # Extract the generated text following the same pattern as your original code
122
+ candidates = data['candidates']
123
+ return nil if candidates.nil? || candidates.empty?
124
+
125
+ first_candidate = candidates.first
126
+ content = first_candidate['content']
127
+ return nil if content.nil?
128
+
129
+ parts = content['parts']
130
+ return nil if parts.nil? || parts.empty?
131
+
132
+ parts.first['text']
133
+ rescue JSON::ParserError => e
134
+ raise ApiError, "Failed to parse response: #{e.message}"
135
+ end
136
+
137
+ class ApiError < StandardError; end
138
+ class ConfigurationError < StandardError; end
56
139
  end
57
140
  end
58
- end
141
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AiRspecWriter
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.6"
5
5
  end
@@ -4,6 +4,11 @@ require_relative "ai_rspec_writer/generator"
4
4
  require_relative "ai_rspec_writer/spec_file_handler"
5
5
  require "colorize"
6
6
 
7
+ # Load Rails configuration only if inside a Rails application
8
+ if defined?(Rails::Generators)
9
+ require_relative "generators/ai_rspec_writer/install_generator"
10
+ end
11
+
7
12
  module AiRspecWriter
8
13
  class Error < StandardError; end
9
14
  class << self
@@ -0,0 +1,104 @@
1
+ require "rails/generators"
2
+ require "tty-spinner"
3
+
4
+ module AiRspecWriter
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def install_ai_rspec_writer
9
+ puts "\n🚀 Starting `ai_rspec_writer` setup...\n".yellow
10
+ check_rspec_installed
11
+ configure_spec_helper
12
+ configure_rails_helper
13
+ puts "\n✅ `ai_rspec_writer` setup completed successfully!\n".green
14
+ end
15
+
16
+ private
17
+
18
+ # Check if RSpec is installed, otherwise install it
19
+ def check_rspec_installed
20
+ spinner = TTY::Spinner.new("🔍 Checking if RSpec is installed... :spinner")
21
+ spinner.auto_spin
22
+
23
+ if File.exist?("spec/rails_helper.rb") && File.exist?("spec/spec_helper.rb")
24
+ spinner.success("✅ RSpec is already installed.")
25
+ else
26
+ spinner.stop("⚠️ RSpec not found. Installing...".red)
27
+ generate "rspec:install"
28
+ puts "✅ RSpec installed successfully.".green
29
+ end
30
+ end
31
+
32
+ # Append content to spec_helper.rb
33
+ def configure_spec_helper
34
+ spec_helper_path = "spec/spec_helper.rb"
35
+ spinner = TTY::Spinner.new("🔧 Configuring `spec_helper.rb`... :spinner")
36
+ spinner.auto_spin
37
+
38
+ if File.exist?(spec_helper_path)
39
+ original_content = File.read(spec_helper_path)
40
+ new_content = "#{spec_file_content}\n\n" + original_content
41
+ File.write(spec_helper_path, new_content)
42
+ spinner.success("✅ Updated `spec_helper.rb` with AI RSpec settings.")
43
+ else
44
+ spinner.error("⚠️ `spec_helper.rb` not found. Skipping...".red)
45
+ end
46
+ end
47
+
48
+ # Append content to rails_helper.rb after `RSpec.configure do |config|`
49
+ def configure_rails_helper
50
+ rails_helper_path = "spec/rails_helper.rb"
51
+ target_line = "RSpec.configure do |config|"
52
+ spinner = TTY::Spinner.new("🔧 Configuring `rails_helper.rb`... :spinner")
53
+ spinner.auto_spin
54
+
55
+ if File.exist?(rails_helper_path)
56
+ content = File.readlines(rails_helper_path)
57
+ index = content.find_index { |line| line.strip == target_line.strip }
58
+
59
+ if index
60
+ content.insert(index + 1, "#{rails_helper_content}\n")
61
+ File.write(rails_helper_path, content.join)
62
+ spinner.success("✅ Successfully inserted configurations into `rails_helper.rb`.")
63
+ else
64
+ spinner.error("⚠️ `RSpec.configure` block not found in `rails_helper.rb`. Skipping...".red)
65
+ end
66
+ else
67
+ spinner.error("⚠️ `rails_helper.rb` not found. Skipping...".red)
68
+ end
69
+ end
70
+
71
+ # Content for spec_helper.rb
72
+ def spec_file_content
73
+ <<~RUBY
74
+ # Auto-generated by ai_rspec_writer
75
+ require 'simplecov'
76
+ SimpleCov.start 'rails' do
77
+ add_filter '/spec/'
78
+ add_filter '/config/'
79
+ add_filter '/vendor/'
80
+ add_filter '/db/'
81
+ add_filter '/bin/'
82
+ end
83
+ # Auto-generated by ai_rspec_writer
84
+ RUBY
85
+ end
86
+
87
+ # Content for rails_helper.rb with proper indentation
88
+ def rails_helper_content
89
+ <<~RUBY.indent(2)
90
+ # Auto-generated by ai_rspec_writer
91
+ config.include FactoryBot::Syntax::Methods
92
+
93
+ # Include Shoulda Matchers
94
+ Shoulda::Matchers.configure do |shoulda_config|
95
+ shoulda_config.integrate do |with|
96
+ with.test_framework :rspec
97
+ with.library :rails
98
+ end
99
+ end
100
+ # Auto-generated by ai_rspec_writer
101
+ RUBY
102
+ end
103
+ end
104
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai_rspec_writer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aniruddha Mirajkar
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-03-04 00:00:00.000000000 Z
10
+ date: 2025-07-07 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rspec-rails
@@ -157,6 +156,7 @@ files:
157
156
  - lib/ai_rspec_writer/schema_extractor.rb
158
157
  - lib/ai_rspec_writer/spec_file_handler.rb
159
158
  - lib/ai_rspec_writer/version.rb
159
+ - lib/generators/ai_rspec_writer/install_generator.rb
160
160
  homepage: https://github.com/aniruddhami/ai_rspec_writer
161
161
  licenses:
162
162
  - MIT
@@ -165,7 +165,6 @@ metadata:
165
165
  source_code_uri: https://github.com/aniruddhami/ai_rspec_writer
166
166
  changelog_uri: https://github.com/aniruddhami/ai_rspec_writer/blob/main/CHANGELOG.md
167
167
  github_repo: https://github.com/aniruddhami/ai_rspec_writer
168
- post_install_message:
169
168
  rdoc_options: []
170
169
  require_paths:
171
170
  - lib
@@ -180,8 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
179
  - !ruby/object:Gem::Version
181
180
  version: '0'
182
181
  requirements: []
183
- rubygems_version: 3.4.1
184
- signing_key:
182
+ rubygems_version: 3.6.6
185
183
  specification_version: 4
186
184
  summary: A gem to generate RSpec tests using AI (ChatGPT, GeminiAI).
187
185
  test_files: []