air_test 0.1.2 β†’ 0.1.4

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: 26f6255268e954f779db9fd1f517565afea3a4c8e6a64c34796b13df95dedb62
4
- data.tar.gz: 0d5c580cd9ea6997e2c7388e1e9c9dce7ffc863e5e713650e94204971a03b330
3
+ metadata.gz: 2de3a0976d4d5aa40c6178ca21af1d429b059106f0184d797c5a3dbf36c546f1
4
+ data.tar.gz: 4587d75cecbdabd22e760e6fcfa28b6c1cd9ebb12d2eb156414b11740666e40c
5
5
  SHA512:
6
- metadata.gz: ff3f31e9e880ae47460d8f4ed3f07ebc7c333533aa85cc6d40541729f65346c8a9d9052534d82f52a1c5e371b173feca5d73e886feb6ac3e716f350ce2a8e0f6
7
- data.tar.gz: efa4d2033943a6b309a19b39aebd743f92e8e43c84df9cb0ae5d3e44d5f0a8aef4e946481ec97c996f36116c96e0f7d6cb40f4de926566be5d45208dc9833f90
6
+ metadata.gz: 0e60e1d94293c77fe912a7017145c3331dec7b18c0aae9ba65c4b71436f662d96f43c46185fc3acdbf4981c023b7949db0cae61bbf0a469753b8d6c11f2178e4
7
+ data.tar.gz: 79b532e0af0ed9923a571fb5013c13171174eabee415f0a7123a60ebcd4388873259f436b7954cc0518ddba1e3af688a2d0b7c0ad5b6e38859154da3914ae536
data/.rubocop.yml CHANGED
@@ -6,3 +6,282 @@ Style/StringLiterals:
6
6
 
7
7
  Style/StringLiteralsInInterpolation:
8
8
  EnforcedStyle: double_quotes
9
+
10
+ Gemspec/AddRuntimeDependency:
11
+ Enabled: true
12
+ Gemspec/AttributeAssignment:
13
+ Enabled: true
14
+ Gemspec/DeprecatedAttributeAssignment:
15
+ Enabled: true
16
+ Gemspec/DevelopmentDependencies:
17
+ Enabled: true
18
+ Gemspec/RequireMFA:
19
+ Enabled: true
20
+ Layout/LineContinuationLeadingSpace:
21
+ Enabled: true
22
+ Layout/LineContinuationSpacing:
23
+ Enabled: true
24
+ Layout/LineEndStringConcatenationIndentation:
25
+ Enabled: true
26
+ Layout/SpaceBeforeBrackets:
27
+ Enabled: true
28
+ Lint/AmbiguousAssignment:
29
+ Enabled: true
30
+ Lint/AmbiguousOperatorPrecedence:
31
+ Enabled: true
32
+ Lint/AmbiguousRange:
33
+ Enabled: true
34
+ Lint/ArrayLiteralInRegexp:
35
+ Enabled: true
36
+ Lint/ConstantOverwrittenInRescue:
37
+ Enabled: true
38
+ Lint/ConstantReassignment:
39
+ Enabled: true
40
+ Lint/CopDirectiveSyntax:
41
+ Enabled: true
42
+ Lint/DeprecatedConstants:
43
+ Enabled: true
44
+ Lint/DuplicateBranch:
45
+ Enabled: true
46
+ Lint/DuplicateMagicComment:
47
+ Enabled: true
48
+ Lint/DuplicateMatchPattern:
49
+ Enabled: true
50
+ Lint/DuplicateRegexpCharacterClassElement:
51
+ Enabled: true
52
+ Lint/DuplicateSetElement:
53
+ Enabled: true
54
+ Lint/EmptyBlock:
55
+ Enabled: true
56
+ Lint/EmptyClass:
57
+ Enabled: true
58
+ Lint/EmptyInPattern:
59
+ Enabled: true
60
+ Lint/HashNewWithKeywordArgumentsAsDefault:
61
+ Enabled: true
62
+ Lint/IncompatibleIoSelectWithFiberScheduler:
63
+ Enabled: true
64
+ Lint/ItWithoutArgumentsInBlock:
65
+ Enabled: true
66
+ Lint/LambdaWithoutLiteralBlock:
67
+ Enabled: true
68
+ Lint/LiteralAssignmentInCondition:
69
+ Enabled: true
70
+ Lint/MixedCaseRange:
71
+ Enabled: true
72
+ Lint/NoReturnInBeginEndBlocks:
73
+ Enabled: true
74
+ Lint/NonAtomicFileOperation:
75
+ Enabled: true
76
+ Lint/NumberedParameterAssignment:
77
+ Enabled: true
78
+ Lint/NumericOperationWithConstantResult:
79
+ Enabled: true
80
+ Lint/OrAssignmentToConstant:
81
+ Enabled: true
82
+ Lint/RedundantDirGlobSort:
83
+ Enabled: true
84
+ Lint/RedundantRegexpQuantifiers:
85
+ Enabled: true
86
+ Lint/RedundantTypeConversion:
87
+ Enabled: true
88
+ Lint/RefinementImportMethods:
89
+ Enabled: true
90
+ Lint/RequireRangeParentheses:
91
+ Enabled: true
92
+ Lint/RequireRelativeSelfPath:
93
+ Enabled: true
94
+ Lint/SharedMutableDefault:
95
+ Enabled: true
96
+ Lint/SuppressedExceptionInNumberConversion:
97
+ Enabled: true
98
+ Lint/SymbolConversion:
99
+ Enabled: true
100
+ Lint/ToEnumArguments:
101
+ Enabled: true
102
+ Lint/TripleQuotes:
103
+ Enabled: true
104
+ Lint/UnescapedBracketInRegexp:
105
+ Enabled: true
106
+ Lint/UnexpectedBlockArity:
107
+ Enabled: true
108
+ Lint/UnmodifiedReduceAccumulator:
109
+ Enabled: true
110
+ Lint/UselessConstantScoping:
111
+ Enabled: true
112
+ Lint/UselessDefaultValueArgument:
113
+ Enabled: true
114
+ Lint/UselessDefined:
115
+ Enabled: true
116
+ Lint/UselessNumericOperation:
117
+ Enabled: true
118
+ Lint/UselessOr:
119
+ Enabled: true
120
+ Lint/UselessRescue:
121
+ Enabled: true
122
+ Lint/UselessRuby2Keywords:
123
+ Enabled: true
124
+ Metrics/CollectionLiteralLength:
125
+ Enabled: true
126
+ Naming/BlockForwarding:
127
+ Enabled: true
128
+ Naming/PredicateMethod:
129
+ Enabled: true
130
+ Security/CompoundHash:
131
+ Enabled: true
132
+ Security/IoMethods:
133
+ Enabled: true
134
+ Style/AmbiguousEndlessMethodDefinition:
135
+ Enabled: true
136
+ Style/ArgumentsForwarding:
137
+ Enabled: true
138
+ Style/ArrayIntersect:
139
+ Enabled: true
140
+ Style/BitwisePredicate:
141
+ Enabled: true
142
+ Style/CollectionCompact:
143
+ Enabled: true
144
+ Style/CollectionQuerying:
145
+ Enabled: true
146
+ Style/CombinableDefined:
147
+ Enabled: true
148
+ Style/ComparableBetween:
149
+ Enabled: true
150
+ Style/ComparableClamp:
151
+ Enabled: true
152
+ Style/ConcatArrayLiterals:
153
+ Enabled: true
154
+ Style/DataInheritance:
155
+ Enabled: true
156
+ Style/DigChain:
157
+ Enabled: true
158
+ Style/DirEmpty:
159
+ Enabled: true
160
+ Style/DocumentDynamicEvalDefinition:
161
+ Enabled: true
162
+ Style/EmptyHeredoc:
163
+ Enabled: true
164
+ Style/EmptyStringInsideInterpolation:
165
+ Enabled: true
166
+ Style/EndlessMethod:
167
+ Enabled: true
168
+ Style/EnvHome:
169
+ Enabled: true
170
+ Style/ExactRegexpMatch:
171
+ Enabled: true
172
+ Style/FetchEnvVar:
173
+ Enabled: true
174
+ Style/FileEmpty:
175
+ Enabled: true
176
+ Style/FileNull:
177
+ Enabled: true
178
+ Style/FileRead:
179
+ Enabled: true
180
+ Style/FileTouch:
181
+ Enabled: true
182
+ Style/FileWrite:
183
+ Enabled: true
184
+ Style/HashConversion:
185
+ Enabled: true
186
+ Style/HashExcept:
187
+ Enabled: true
188
+ Style/HashFetchChain:
189
+ Enabled: true
190
+ Style/HashSlice:
191
+ Enabled: true
192
+ Style/IfWithBooleanLiteralBranches:
193
+ Enabled: true
194
+ Style/InPatternThen:
195
+ Enabled: true
196
+ Style/ItAssignment:
197
+ Enabled: true
198
+ Style/ItBlockParameter:
199
+ Enabled: true
200
+ Style/KeywordArgumentsMerging:
201
+ Enabled: true
202
+ Style/MagicCommentFormat:
203
+ Enabled: true
204
+ Style/MapCompactWithConditionalBlock:
205
+ Enabled: true
206
+ Style/MapIntoArray:
207
+ Enabled: true
208
+ Style/MapToHash:
209
+ Enabled: true
210
+ Style/MapToSet:
211
+ Enabled: true
212
+ Style/MinMaxComparison:
213
+ Enabled: true
214
+ Style/MultilineInPatternThen:
215
+ Enabled: true
216
+ Style/NegatedIfElseCondition:
217
+ Enabled: true
218
+ Style/NestedFileDirname:
219
+ Enabled: true
220
+ Style/NilLambda:
221
+ Enabled: true
222
+ Style/NumberedParameters:
223
+ Enabled: true
224
+ Style/NumberedParametersLimit:
225
+ Enabled: true
226
+ Style/ObjectThen:
227
+ Enabled: true
228
+ Style/OpenStructUse:
229
+ Enabled: true
230
+ Style/OperatorMethodCall:
231
+ Enabled: true
232
+ Style/QuotedSymbols:
233
+ Enabled: true
234
+ Style/RedundantArgument:
235
+ Enabled: true
236
+ Style/RedundantArrayConstructor:
237
+ Enabled: true
238
+ Style/RedundantArrayFlatten:
239
+ Enabled: true
240
+ Style/RedundantConstantBase:
241
+ Enabled: true
242
+ Style/RedundantCurrentDirectoryInPath:
243
+ Enabled: true
244
+ Style/RedundantDoubleSplatHashBraces:
245
+ Enabled: true
246
+ Style/RedundantEach:
247
+ Enabled: true
248
+ Style/RedundantFilterChain:
249
+ Enabled: true
250
+ Style/RedundantFormat:
251
+ Enabled: true
252
+ Style/RedundantHeredocDelimiterQuotes:
253
+ Enabled: true
254
+ Style/RedundantInitialize:
255
+ Enabled: true
256
+ Style/RedundantInterpolationUnfreeze:
257
+ Enabled: true
258
+ Style/RedundantLineContinuation:
259
+ Enabled: true
260
+ Style/RedundantRegexpArgument:
261
+ Enabled: true
262
+ Style/RedundantRegexpConstructor:
263
+ Enabled: true
264
+ Style/RedundantSelfAssignmentBranch:
265
+ Enabled: true
266
+ Style/RedundantStringEscape:
267
+ Enabled: true
268
+ Style/ReturnNilInPredicateMethodDefinition:
269
+ Enabled: true
270
+ Style/SafeNavigationChainLength:
271
+ Enabled: true
272
+ Style/SelectByRegexp:
273
+ Enabled: true
274
+ Style/SendWithLiteralMethodName:
275
+ Enabled: true
276
+ Style/SingleLineDoEndBlock:
277
+ Enabled: true
278
+ Style/StringChars:
279
+ Enabled: true
280
+ Style/SuperArguments:
281
+ Enabled: true
282
+ Style/SuperWithArgsParentheses:
283
+ Enabled: true
284
+ Style/SwapValues:
285
+ Enabled: true
286
+ Style/YAMLFileRead:
287
+ Enabled: true
data/README.md CHANGED
@@ -95,12 +95,6 @@ GITHUB_BOT_TOKEN=ghp_xxx
95
95
 
96
96
  ---
97
97
 
98
- ## πŸ§ͺ Tests (optional)
99
-
100
- If you want to test the gem’s services, you can add specs in the `spec/` folder.
101
-
102
- ---
103
-
104
98
  ## πŸ†˜ Troubleshooting
105
99
 
106
100
  - **Notion or GitHub authentication error**: check your tokens.
data/exe/air_test CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
- require 'fileutils'
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
3
5
 
4
6
  GREEN = "\e[32m"
5
7
  YELLOW = "\e[33m"
@@ -25,7 +27,7 @@ else
25
27
  puts "#{GREEN}βœ… Created #{initializer_path}#{RESET}"
26
28
  end
27
29
 
28
- ['spec/features', 'spec/steps'].each do |dir|
30
+ ["spec/features", "spec/steps"].each do |dir|
29
31
  if Dir.exist?(dir)
30
32
  puts "#{YELLOW}⚠️ #{dir} already exists. Skipping.#{RESET}"
31
33
  else
@@ -61,4 +63,4 @@ puts "\n✨ All set! Next steps:"
61
63
  puts " 1. Fill in your config/initializers/air_test.rb"
62
64
  puts " 2. Add your tokens to .env or your environment"
63
65
  puts " 3. Run: bundle exec rake air_test:generate_specs_from_notion"
64
- puts "\nHappy testing! πŸŽ‰"
66
+ puts "\nHappy testing! πŸŽ‰"
@@ -1,14 +1,16 @@
1
+ # Handles configuration for AirTest, including API tokens and environment variables.
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module AirTest
5
+ # Handles configuration for AirTest, including API tokens and environment variables.
4
6
  class Configuration
5
7
  attr_accessor :notion_token, :notion_database_id, :github_token, :repo
6
8
 
7
9
  def initialize
8
- @notion_token = ENV['NOTION_TOKEN']
9
- @notion_database_id = ENV['NOTION_DATABASE_ID']
10
- @github_token = ENV['GITHUB_BOT_TOKEN'] || ENV['GITHUB_TOKEN']
10
+ @notion_token = ENV.fetch("NOTION_TOKEN", nil)
11
+ @notion_database_id = ENV.fetch("NOTION_DATABASE_ID", nil)
12
+ @github_token = ENV["GITHUB_BOT_TOKEN"] || ENV.fetch("GITHUB_TOKEN", nil)
11
13
  @repo = nil
12
14
  end
13
15
  end
14
- end
16
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirTest
4
+ class Engine < ::Rails::Engine
5
+ # This ensures that the Rake tasks are automatically loaded when the gem is included in a Rails app
6
+ rake_tasks do
7
+ load File.expand_path("tasks/air_test.rake", __dir__)
8
+ end
9
+ end
10
+ end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'octokit'
2
+
3
+ require "octokit"
3
4
 
4
5
  module AirTest
6
+ # Handles GitHub API interactions for AirTest, such as commits and pull requests.
5
7
  class GithubClient
6
8
  def initialize(config = AirTest.configuration)
7
9
  @github_token = config.github_token
@@ -17,18 +19,18 @@ module AirTest
17
19
  end
18
20
  files.each { |f| system("git add -f #{f}") }
19
21
  has_changes = !system("git diff --cached --quiet")
20
- if has_changes
21
- system("git commit -m '#{commit_message}'")
22
- end
22
+ system("git commit -m '#{commit_message}'") if has_changes
23
23
  system("git push origin #{branch}")
24
24
  has_changes
25
25
  end
26
26
 
27
- def create_pull_request(branch, pr_title, pr_body, assignees: ['Notion'])
27
+ # rubocop:disable Metrics/MethodLength
28
+ def create_pull_request(branch, pr_title, pr_body, assignees: ["Notion"])
28
29
  return unless @client && @repo
30
+
29
31
  @client.create_pull_request(
30
32
  @repo,
31
- 'main',
33
+ "main",
32
34
  branch,
33
35
  pr_title,
34
36
  pr_body,
@@ -38,6 +40,7 @@ module AirTest
38
40
  warn "❌ Erreur lors de la crΓ©ation de la PR : #{e.message}"
39
41
  nil
40
42
  end
43
+ # rubocop:enable Metrics/MethodLength
41
44
 
42
45
  private
43
46
 
@@ -47,7 +50,7 @@ module AirTest
47
50
 
48
51
  def detect_repo_from_git
49
52
  remote_url = `git config --get remote.origin.url`.strip
50
- remote_url.split(/[:\/]/).last(2).join('/').gsub(/\.git$/, '')
53
+ remote_url.split(%r{[:/]}).last(2).join("/").gsub(/\.git$/, "")
51
54
  end
52
55
  end
53
- end
56
+ end
@@ -1,85 +1,95 @@
1
+ # Parses Notion tickets and extracts relevant information for spec generation in AirTest.
2
+ # rubocop:disable Metrics/ClassLength
1
3
  # frozen_string_literal: true
2
- require 'net/http'
3
- require 'json'
4
- require 'uri'
4
+
5
+ require "net/http"
6
+ require "json"
7
+ require "uri"
5
8
 
6
9
  module AirTest
10
+ # Parses Notion tickets and extracts relevant information for spec generation in AirTest.
7
11
  class NotionParser
8
12
  def initialize(config = AirTest.configuration)
9
13
  @database_id = config.notion_database_id
10
14
  @notion_token = config.notion_token
11
- @base_url = 'https://api.notion.com/v1'
15
+ @base_url = "https://api.notion.com/v1"
12
16
  end
13
17
 
14
18
  def fetch_tickets(limit: 5)
15
19
  uri = URI("#{@base_url}/databases/#{@database_id}/query")
16
20
  response = make_api_request(uri, { page_size: 100 })
17
- return [] unless response.code == '200'
21
+ return [] unless response.code == "200"
22
+
18
23
  data = JSON.parse(response.body)
19
- data['results'].first(limit)
24
+ data["results"].first(limit)
20
25
  end
21
26
 
22
27
  def parse_ticket_content(page_id)
23
28
  blocks = get_page_content(page_id)
24
29
  return nil unless blocks
30
+
25
31
  parse_content(blocks)
26
32
  end
27
33
 
28
34
  def extract_ticket_title(ticket)
29
- ticket.dig('properties', 'Projects', 'title', 0, 'plain_text') || 'No title'
35
+ ticket.dig("properties", "Projects", "title", 0, "plain_text") || "No title"
30
36
  end
31
37
 
32
38
  def extract_ticket_id(ticket)
33
- ticket.dig('properties', 'ID', 'unique_id', 'number') || 'No ID'
39
+ ticket.dig("properties", "ID", "unique_id", "number") || "No ID"
34
40
  end
35
41
 
36
42
  def extract_ticket_url(ticket)
37
- ticket['url'] || "https://www.notion.so/#{ticket['id'].gsub('-', '')}"
43
+ ticket["url"] || "https://www.notion.so/#{ticket["id"].gsub("-", "")}"
38
44
  end
39
45
 
40
46
  private
41
47
 
42
48
  def get_page_content(page_id)
43
49
  uri = URI("#{@base_url}/blocks/#{page_id}/children")
44
- response = make_api_request(uri, nil, 'GET')
45
- return nil unless response.code == '200'
50
+ response = make_api_request(uri, nil, "GET")
51
+ return nil unless response.code == "200"
52
+
46
53
  data = JSON.parse(response.body)
47
- data['results']
54
+ data["results"]
48
55
  end
49
56
 
50
- def make_api_request(uri, request_body = nil, method = 'POST')
57
+ # rubocop:disable Metrics/MethodLength
58
+ def make_api_request(uri, request_body = nil, method = "POST")
51
59
  http = Net::HTTP.new(uri.host, uri.port)
52
60
  http.use_ssl = true
53
- if method == 'GET'
61
+ if method == "GET"
54
62
  request = Net::HTTP::Get.new(uri)
55
63
  else
56
64
  request = Net::HTTP::Post.new(uri)
57
65
  request.body = request_body.to_json if request_body
58
66
  end
59
- request['Authorization'] = "Bearer #{@notion_token}"
60
- request['Notion-Version'] = '2022-06-28'
61
- request['Content-Type'] = 'application/json'
67
+ request["Authorization"] = "Bearer #{@notion_token}"
68
+ request["Notion-Version"] = "2022-06-28"
69
+ request["Content-Type"] = "application/json"
62
70
  http.request(request)
63
71
  end
72
+ # rubocop:enable Metrics/MethodLength
64
73
 
74
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
65
75
  def parse_content(blocks)
66
76
  parsed_data = {
67
- feature: '',
77
+ feature: "",
68
78
  scenarios: [],
69
- meta: { tags: [], priority: '', estimate: nil, assignee: '' }
79
+ meta: { tags: [], priority: "", estimate: nil, assignee: "" }
70
80
  }
71
81
  current_scenario = nil
72
82
  in_feature_block = false
73
83
  in_scenario_block = false
74
84
  blocks.each do |block|
75
- case block['type']
76
- when 'heading_1', 'heading_2', 'heading_3'
77
- heading_text = extract_text(block[block['type']]['rich_text'])
78
- if heading_text.downcase.include?('feature')
85
+ case block["type"]
86
+ when "heading_1", "heading_2", "heading_3"
87
+ heading_text = extract_text(block[block["type"]]["rich_text"])
88
+ if heading_text.downcase.include?("feature")
79
89
  in_feature_block = true
80
90
  in_scenario_block = false
81
91
  parsed_data[:feature] = heading_text
82
- elsif heading_text.downcase.include?('scenario')
92
+ elsif heading_text.downcase.include?("scenario")
83
93
  in_scenario_block = true
84
94
  in_feature_block = false
85
95
  current_scenario = { title: heading_text, steps: [] }
@@ -88,33 +98,36 @@ module AirTest
88
98
  in_feature_block = false
89
99
  in_scenario_block = false
90
100
  end
91
- when 'paragraph'
92
- text = extract_text(block['paragraph']['rich_text'])
101
+ when "paragraph"
102
+ text = extract_text(block["paragraph"]["rich_text"])
93
103
  next if text.empty?
104
+
94
105
  if in_feature_block
95
- parsed_data[:feature] += "\n" + text
106
+ parsed_data[:feature] += "\n#{text}"
96
107
  elsif in_scenario_block && current_scenario
97
108
  current_scenario[:steps] << text
98
109
  end
99
- when 'bulleted_list_item', 'numbered_list_item'
100
- text = extract_text(block[block['type']]['rich_text'])
110
+ when "bulleted_list_item", "numbered_list_item"
111
+ text = extract_text(block[block["type"]]["rich_text"])
101
112
  next if text.empty?
113
+
102
114
  if in_feature_block
103
- parsed_data[:feature] += "\nβ€’ " + text
115
+ parsed_data[:feature] += "\nβ€’ #{text}"
104
116
  elsif in_scenario_block && current_scenario
105
117
  current_scenario[:steps] << text
106
118
  end
107
- when 'callout'
108
- text = extract_text(block['callout']['rich_text'])
119
+ when "callout"
120
+ text = extract_text(block["callout"]["rich_text"])
109
121
  next if text.empty?
110
- if text.downcase.include?('tag')
122
+
123
+ if text.downcase.include?("tag")
111
124
  tags = extract_tags(text)
112
125
  parsed_data[:meta][:tags].concat(tags)
113
- elsif text.downcase.include?('priority')
126
+ elsif text.downcase.include?("priority")
114
127
  parsed_data[:meta][:priority] = extract_priority(text)
115
- elsif text.downcase.include?('estimate')
128
+ elsif text.downcase.include?("estimate")
116
129
  parsed_data[:meta][:estimate] = extract_estimate(text)
117
- elsif text.downcase.include?('assignee')
130
+ elsif text.downcase.include?("assignee")
118
131
  parsed_data[:meta][:assignee] = extract_assignee(text)
119
132
  end
120
133
  end
@@ -123,16 +136,18 @@ module AirTest
123
136
  parsed_data[:meta][:tags] = parsed_data[:meta][:tags].uniq
124
137
  parsed_data
125
138
  end
139
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
126
140
 
127
141
  def extract_text(rich_text_array)
128
- return '' unless rich_text_array
129
- rich_text_array.map { |text| text['plain_text'] }.join(' ')
142
+ return "" unless rich_text_array
143
+
144
+ rich_text_array.map { |text| text["plain_text"] }.join(" ")
130
145
  end
131
146
 
132
147
  def extract_tags(text)
133
148
  if text =~ /tags?:\s*(.+)/i
134
- tags_text = $1.strip
135
- tags_text.split(/[\,\s]+/).map(&:strip).reject(&:empty?)
149
+ tags_text = ::Regexp.last_match(1).strip
150
+ tags_text.split(/[,\s]+/).map(&:strip).reject(&:empty?)
136
151
  else
137
152
  []
138
153
  end
@@ -140,26 +155,26 @@ module AirTest
140
155
 
141
156
  def extract_priority(text)
142
157
  if text =~ /priority:\s*(.+)/i
143
- $1.strip
158
+ ::Regexp.last_match(1).strip
144
159
  else
145
- ''
160
+ ""
146
161
  end
147
162
  end
148
163
 
149
164
  def extract_estimate(text)
150
- if text =~ /estimate:\s*(\d+)/i
151
- $1.to_i
152
- else
153
- nil
154
- end
165
+ return unless text =~ /estimate:\s*(\d+)/i
166
+
167
+ ::Regexp.last_match(1).to_i
155
168
  end
156
169
 
157
170
  def extract_assignee(text)
158
171
  if text =~ /assignee:\s*(.+)/i
159
- $1.strip
172
+ ::Regexp.last_match(1).strip
160
173
  else
161
- ''
174
+ ""
162
175
  end
163
176
  end
164
177
  end
165
- end
178
+ end
179
+
180
+ # rubocop:enable Metrics/ClassLength
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Runs the main automation workflow for AirTest, orchestrating Notion parsing and GitHub actions.
4
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
3
5
  module AirTest
6
+ # Runs the main automation workflow for AirTest, orchestrating Notion parsing and GitHub actions.
4
7
  class Runner
5
8
  def initialize(config = AirTest.configuration)
6
9
  @notion = NotionParser.new(config)
@@ -16,26 +19,28 @@ module AirTest
16
19
  title = @notion.extract_ticket_title(ticket)
17
20
  url = @notion.extract_ticket_url(ticket)
18
21
  puts "\nπŸ“‹ Processing: FDR#{ticket_id} - #{title}"
19
- parsed_data = @notion.parse_ticket_content(ticket['id'])
22
+ parsed_data = @notion.parse_ticket_content(ticket["id"])
20
23
  next unless parsed_data && parsed_data[:feature] && !parsed_data[:feature].empty?
21
- slug = parsed_data[:feature].downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-|-$/, '')
24
+
25
+ slug = parsed_data[:feature].downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
22
26
  branch = "air_test/#{ticket_id}-#{slug}"
23
27
  spec_path = @spec.generate_spec_from_parsed_data(ticket_id, parsed_data)
24
28
  step_path = @spec.generate_step_definitions_for_spec(spec_path)
25
29
  files_to_commit = [spec_path]
26
30
  files_to_commit << step_path if step_path
27
- has_changes = @github.commit_and_push_branch(branch, files_to_commit, "Add specs for Notion ticket #{ticket_id}")
31
+ has_changes = @github.commit_and_push_branch(branch, files_to_commit,
32
+ "Add specs for Notion ticket #{ticket_id}")
28
33
  if has_changes
29
34
  pr_title = title
30
- scenarios_md = parsed_data[:scenarios].map.with_index(1) do |sc, i|
31
- steps = sc[:steps]&.join(' ')
35
+ scenarios_md = parsed_data[:scenarios].map.with_index(1) do |sc, _i|
36
+ steps = sc[:steps]&.join(" ")
32
37
  " - [ ] #{sc[:title]} – #{steps}"
33
38
  end.join("\n")
34
39
  pr_body = <<~MD
35
- - **Story Notion :** #{url}
36
- - **Feature** : #{parsed_data[:feature]}
37
- - **ScΓ©narios** :
38
- #{scenarios_md}
40
+ - **Story Notion :** #{url}
41
+ - **Feature** : #{parsed_data[:feature]}
42
+ - **ScΓ©narios** :
43
+ #{scenarios_md}
39
44
  MD
40
45
  pr = @github.create_pull_request(branch, pr_title, pr_body)
41
46
  puts "βœ… Pull Request créée : #{pr.html_url}" if pr
@@ -45,4 +50,5 @@ module AirTest
45
50
  end
46
51
  end
47
52
  end
48
- end
53
+ end
54
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
@@ -1,14 +1,18 @@
1
+ # Generates RSpec and Turnip spec files from parsed Notion ticket data for AirTest.
1
2
  # frozen_string_literal: true
2
- require 'fileutils'
3
+
4
+ require "fileutils"
3
5
 
4
6
  module AirTest
7
+ # Generates RSpec and Turnip spec files from parsed Notion ticket data for AirTest.
5
8
  class SpecGenerator
6
- def generate_spec_from_parsed_data(ticket_id, parsed_data, output_dir: 'spec/features')
7
- feature_slug = parsed_data[:feature].downcase.gsub(/[^a-z0-9]+/, '-').gsub(/^-|-$/, '')
9
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
+ def generate_spec_from_parsed_data(ticket_id, parsed_data, output_dir: "spec/features")
11
+ feature_slug = parsed_data[:feature].downcase.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
8
12
  filename = "#{feature_slug}_fdr#{ticket_id}.rb"
9
13
  filepath = File.join(output_dir, filename)
10
14
  FileUtils.mkdir_p(output_dir)
11
- File.open(filepath, 'w') do |f|
15
+ File.open(filepath, "w") do |f|
12
16
  f.puts "# language: turnip"
13
17
  f.puts "# frozen_string_literal: true\n"
14
18
  f.puts "feature '#{parsed_data[:feature]}' do"
@@ -24,19 +28,18 @@ module AirTest
24
28
  filepath
25
29
  end
26
30
 
27
- def generate_step_definitions_for_spec(spec_filepath, output_dir: 'spec/steps')
31
+ def generate_step_definitions_for_spec(spec_filepath, output_dir: "spec/steps")
28
32
  step_texts = []
29
33
  File.readlines(spec_filepath).each do |line|
30
- if line =~ /pending ['\"](.*)['\"]/
31
- step_texts << $1.strip
32
- end
34
+ step_texts << ::Regexp.last_match(1).strip if line =~ /pending ['\"](.*)['\"]/
33
35
  end
34
36
  return if step_texts.empty?
35
- spec_filename = File.basename(spec_filepath, '.rb')
37
+
38
+ spec_filename = File.basename(spec_filepath, ".rb")
36
39
  step_file = File.join(output_dir, "#{spec_filename}_steps.rb")
37
40
  FileUtils.mkdir_p(output_dir)
38
- File.open(step_file, 'w') do |f|
39
- f.puts "# Auto-generated step definitions for #{spec_filename.gsub('-', ' ')}"
41
+ File.open(step_file, "w") do |f|
42
+ f.puts "# Auto-generated step definitions for #{spec_filename.gsub("-", " ")}"
40
43
  step_texts.uniq.each do |step|
41
44
  f.puts "\nstep '#{step}' do"
42
45
  f.puts " pending 'Implement: #{step.gsub("'", "\\'")}'"
@@ -45,5 +48,6 @@ module AirTest
45
48
  end
46
49
  step_file
47
50
  end
51
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
48
52
  end
49
- end
53
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AirTest
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
data/lib/air_test.rb CHANGED
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "air_test/version"
4
- require_relative "air_test_automation/configuration"
5
- require_relative "air_test_automation/notion_parser"
6
- require_relative "air_test_automation/spec_generator"
7
- require_relative "air_test_automation/github_client"
8
- require_relative "air_test_automation/rake_tasks"
9
-
3
+ # Main namespace for the AirTest gem, which automates spec generation and GitHub integration.
10
4
  module AirTest
11
5
  class << self
12
6
  attr_accessor :configuration
@@ -17,3 +11,13 @@ module AirTest
17
11
  yield(configuration)
18
12
  end
19
13
  end
14
+
15
+ require_relative "air_test/version"
16
+ require_relative "air_test/configuration"
17
+ require_relative "air_test/notion_parser"
18
+ require_relative "air_test/spec_generator"
19
+ require_relative "air_test/github_client"
20
+ require_relative "air_test/runner"
21
+
22
+ # Load Rails engine if Rails is available (for Rake task support)
23
+ require_relative "air_test/engine" if defined?(Rails)
@@ -1,7 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :air_test do
2
- desc "Generate specs and PR from Notion"
3
- task generate_specs_from_notion: :environment do
4
- require 'air_test/runner'
5
- AirTest::Runner.new.run(limit: 5)
4
+ desc "Generate specs and PR from Notion tickets"
5
+ task :generate_specs_from_notion, [:limit] => :environment do |_task, args|
6
+ require "air_test/runner"
7
+
8
+ limit = (args[:limit] || 5).to_i
9
+ puts "πŸš€ Starting AirTest with limit: #{limit}"
10
+
11
+ begin
12
+ runner = AirTest::Runner.new
13
+ runner.run(limit: limit)
14
+ puts "βœ… AirTest completed successfully!"
15
+ rescue StandardError => e
16
+ puts "❌ AirTest failed: #{e.message}"
17
+ puts e.backtrace.first(5).join("\n")
18
+ exit 1
19
+ end
6
20
  end
7
- end
21
+
22
+ desc "Show AirTest configuration"
23
+ task config: :environment do
24
+ require "air_test/configuration"
25
+
26
+ config = AirTest.configuration
27
+ puts "πŸ”§ AirTest Configuration:"
28
+ puts " Notion Token: #{config.notion_token ? "Set" : "Not set"}"
29
+ puts " Notion Database ID: #{config.notion_database_id || "Not set"}"
30
+ puts " GitHub Token: #{config.github_token ? "Set" : "Not set"}"
31
+ puts " Repository: #{config.repo || "Not set"}"
32
+ end
33
+ end
metadata CHANGED
@@ -1,16 +1,58 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: air_test
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - julien bouland
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-07-18 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-07-19 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-retry
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: octokit
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '7.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '7.0'
12
54
  description: Automate the generation of Turnip/RSpec specs from Notion tickets, create
13
- branches, commits, pushes, and GitHub Pull Requestsβ€”all with a single Rake command.
55
+ branches, commits, pushes, and GitHub Pull Requests. All with a single Rake command.
14
56
  email:
15
57
  - bouland.julien@gmail.com
16
58
  executables:
@@ -28,6 +70,7 @@ files:
28
70
  - exe/air_test
29
71
  - lib/air_test.rb
30
72
  - lib/air_test/configuration.rb
73
+ - lib/air_test/engine.rb
31
74
  - lib/air_test/github_client.rb
32
75
  - lib/air_test/notion_parser.rb
33
76
  - lib/air_test/runner.rb
@@ -42,6 +85,7 @@ metadata:
42
85
  homepage_uri: https://github.com/airtest-dev/airtest
43
86
  source_code_uri: https://github.com/airtest-dev/airtest
44
87
  changelog_uri: https://github.com/airtest-io/air_test/blob/main/CHANGELOG.md
88
+ rubygems_mfa_required: 'true'
45
89
  rdoc_options: []
46
90
  require_paths:
47
91
  - lib