air_test 0.1.5.5 → 0.1.6.0

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: e8814b0e5adb17e2636d7da5b58d8c2f472c78fa31325d1d415ef30de9993104
4
- data.tar.gz: d1e48aa3bb971eaecb093b97a247197e4138b02ed5d9cb41c00d3f202c22afcb
3
+ metadata.gz: ad53fd406fa863853dea5408851219dee4651ed0f36565eea406e0e1ac8218a2
4
+ data.tar.gz: 04c60aa2b2a9db1203440e26f4d95f166826dc887865d997f9e94fe7b132bf54
5
5
  SHA512:
6
- metadata.gz: 1e2fee67f17bb43db86a10d981f73d033429ce2da0d55481eb56356c1fe979777cccf72e59e6a24fc79e3468cc681f12081c8ec9fa125394b2ae35ff8e00cf7b
7
- data.tar.gz: 65138b6b62ad47c68fb0abc70ca9670687fd0d595c7de55b4e8e7a1817f54e8e17cc1159bfcdc34b3394f2bf125c670c42faaab3b360c868e496b96d51408999
6
+ metadata.gz: 6bfe5096f73ad09e186863a51e279ab5e415d2dd3b3e09be7441fdd581634097152d249d5fe51fc802115b80081a12e25b65a93245a14694f75b1e0ca6dcbfdf
7
+ data.tar.gz: 67b13d6c175daf0360e7987e4432cd5eeb09e417bb9d2f9c3cf832926eb652be77cd14daea8dc27dd62a7284bca50aea58ef07dacc7da2546f08bc2bac7be765
data/README.md CHANGED
@@ -49,6 +49,47 @@ Make sure your environment variables are set (in `.env`, your shell, or your CI/
49
49
 
50
50
  ---
51
51
 
52
+ ## 📝 Creating Notion Tickets with Gherkin Format
53
+
54
+ To ensure that your Notion tickets are compatible with the air_test automation, follow these guidelines when creating your tickets:
55
+
56
+ ### 1. Create a New Page in Notion
57
+
58
+ - Start by creating a new page in your Notion workspace for each ticket.
59
+
60
+ ### 2. Use the Gherkin Syntax
61
+
62
+ - Each ticket should follow the Gherkin syntax, which includes the following keywords:
63
+ - **Feature**: A high-level description of the feature being implemented.
64
+ - **Scenario**: A specific situation or case that describes how the feature should behave.
65
+ - **Given**: The initial context or state before the scenario starts.
66
+ - **When**: The action that triggers the scenario.
67
+ - **Then**: The expected outcome or result of the action.
68
+
69
+ ### 3. Example Structure
70
+
71
+ Here’s an example of how to structure a ticket in Notion:
72
+
73
+ Feature: User Login
74
+ Scenario: Successful login with valid credentials
75
+ Given the user is on the login page
76
+ When the user enters valid credentials
77
+ Then the user should be redirected to the dashboard
78
+ Scenario: Unsuccessful login with invalid credentials
79
+ Given the user is on the login page
80
+ When the user enters invalid credentials
81
+ Then an error message should be displayed
82
+
83
+ ### 4. Additional Tips
84
+
85
+ - Ensure that each ticket is clearly titled and contains all necessary scenarios.
86
+ - Use bullet points or toggle lists in Notion to organize multiple scenarios under a single feature.
87
+ - Make sure to keep the Gherkin syntax consistent across all tickets for better parsing.
88
+
89
+ By following these guidelines, you can create Notion tickets that are ready to be parsed by the air_test automation tool.
90
+
91
+ ---
92
+
52
93
  ## 🛠 Usage
53
94
 
54
95
  Run the automated workflow from your Rails project terminal:
@@ -74,17 +115,6 @@ bundle exec rake air_test:generate_specs_from_notion
74
115
 
75
116
  ---
76
117
 
77
- ## 🧩 Gem Structure
78
-
79
- - `lib/air_test/configuration.rb`: centralized configuration
80
- - `lib/air_test/notion_parser.rb`: Notion extraction and parsing
81
- - `lib/air_test/spec_generator.rb`: spec and step file generation
82
- - `lib/air_test/github_client.rb`: git and GitHub PR management
83
- - `lib/air_test/runner.rb`: workflow orchestrator
84
- - `lib/tasks/air_test.rake`: Rake task to launch the automation
85
-
86
- ---
87
-
88
118
  ## 📝 Example .env
89
119
 
90
120
  ```
@@ -103,17 +133,6 @@ GITHUB_BOT_TOKEN=ghp_xxx
103
133
 
104
134
  ---
105
135
 
106
- ## 📦 Publishing the Gem (optional)
107
-
108
- To publish the gem on RubyGems:
109
-
110
- ```sh
111
- gem build air_test.gemspec
112
- gem push air_test-x.y.z.gem
113
- ```
114
-
115
- ---
116
-
117
136
  ## 👨‍💻 Author & License
118
137
 
119
138
  - Author: [Airtest]
@@ -4,13 +4,46 @@
4
4
  module AirTest
5
5
  # Handles configuration for AirTest, including API tokens and environment variables.
6
6
  class Configuration
7
- attr_accessor :notion_token, :notion_database_id, :github_token, :repo
7
+ attr_accessor :tool, :notion, :jira, :monday, :github, :repo
8
8
 
9
9
  def initialize
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)
10
+ @tool = ENV.fetch("AIRTEST_TOOL", "notion")
11
+ @notion = {
12
+ token: ENV.fetch("NOTION_TOKEN", nil),
13
+ database_id: ENV.fetch("NOTION_DATABASE_ID", nil)
14
+ }
15
+ @jira = {
16
+ token: ENV.fetch("JIRA_TOKEN", nil),
17
+ project_id: ENV.fetch("JIRA_PROJECT_ID", nil),
18
+ domain: ENV.fetch("JIRA_DOMAIN", nil),
19
+ email: ENV.fetch("JIRA_EMAIL", nil)
20
+ }
21
+ @monday = {
22
+ token: ENV.fetch("MONDAY_TOKEN", nil),
23
+ board_id: ENV.fetch("MONDAY_BOARD_ID", nil),
24
+ domain: ENV.fetch("MONDAY_DOMAIN", nil)
25
+ }
26
+ @github = {
27
+ token: ENV["GITHUB_BOT_TOKEN"] || ENV.fetch("GITHUB_TOKEN", nil)
28
+ }
13
29
  @repo = ENV.fetch("REPO", nil)
14
30
  end
31
+
32
+ def validate!
33
+ case tool.to_s.downcase
34
+ when "notion"
35
+ raise "Missing NOTION_TOKEN" unless notion[:token]
36
+ raise "Missing NOTION_DATABASE_ID" unless notion[:database_id]
37
+ when "jira"
38
+ raise "Missing JIRA_TOKEN" unless jira[:token]
39
+ raise "Missing JIRA_PROJECT_ID" unless jira[:project_id]
40
+ raise "Missing JIRA_DOMAIN" unless jira[:domain]
41
+ raise "Missing JIRA_EMAIL" unless jira[:email]
42
+ when "monday"
43
+ raise "Missing MONDAY_TOKEN" unless monday[:token]
44
+ raise "Missing MONDAY_BOARD_ID" unless monday[:board_id]
45
+ raise "Missing MONDAY_DOMAIN" unless monday[:domain]
46
+ end
47
+ end
15
48
  end
16
49
  end
@@ -6,9 +6,9 @@ module AirTest
6
6
  # Handles GitHub API interactions for AirTest, such as commits and pull requests.
7
7
  class GithubClient
8
8
  def initialize(config = AirTest.configuration)
9
- @github_token = config.github_token
9
+ @token = config.github[:token]
10
10
  @repo = config.repo || detect_repo_from_git
11
- @client = Octokit::Client.new(access_token: @github_token) if @github_token
11
+ @client = Octokit::Client.new(access_token: @token) if @token
12
12
  end
13
13
 
14
14
  def commit_and_push_branch(branch, files, commit_message)
@@ -21,9 +21,9 @@ module AirTest
21
21
  system('git config user.name "air-test-bot"')
22
22
  system('git config user.email "airtest.bot@gmail.com"')
23
23
  # Set remote to use bot token if available
24
- if @github_token
24
+ if @token
25
25
  repo_url = "github.com/#{@repo}.git"
26
- system("git remote set-url origin https://#{@github_token}@#{repo_url}")
26
+ system("git remote set-url origin https://#{@token}@#{repo_url}")
27
27
  end
28
28
  files.each { |f| system("git add -f #{f}") }
29
29
  has_changes = !system("git diff --cached --quiet")
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+ require_relative "ticket_parser"
7
+
8
+ module AirTest
9
+ class JiraTicketParser
10
+ include TicketParser
11
+ def initialize(config = AirTest.configuration)
12
+ @domain = config.jira[:domain]
13
+ @api_key = config.jira[:token]
14
+ @project_key = config.jira[:project_id]
15
+ @email = config.jira[:email]
16
+ end
17
+
18
+ def fetch_tickets(limit: 5)
19
+ # Try different status names (English and French)
20
+ statuses = ["To Do", "À faire", "Open", "New"]
21
+ all_issues = []
22
+
23
+ statuses.each do |status|
24
+ jql = "project = #{@project_key} AND status = '#{status}' ORDER BY created DESC"
25
+ uri = URI("#{@domain}/rest/api/3/search?jql=#{URI.encode_www_form_component(jql)}&maxResults=#{limit}")
26
+ request = Net::HTTP::Get.new(uri)
27
+ request.basic_auth(@email, @api_key)
28
+ request["Accept"] = "application/json"
29
+ http = Net::HTTP.new(uri.host, uri.port)
30
+ http.use_ssl = true
31
+ response = http.request(request)
32
+
33
+ next unless response.code == "200"
34
+
35
+ data = JSON.parse(response.body)
36
+ issues = data["issues"] || []
37
+ all_issues.concat(issues)
38
+ puts "Found #{issues.length} issues with status '#{status}'" if issues.any?
39
+ end
40
+
41
+ all_issues.first(limit)
42
+ end
43
+
44
+ def parse_ticket_content(issue_id)
45
+ # Fetch issue details (description, etc.)
46
+ uri = URI("#{@domain}/rest/api/3/issue/#{issue_id}")
47
+ request = Net::HTTP::Get.new(uri)
48
+ request.basic_auth(@email, @api_key)
49
+ request["Accept"] = "application/json"
50
+ http = Net::HTTP.new(uri.host, uri.port)
51
+ http.use_ssl = true
52
+ response = http.request(request)
53
+ return nil unless response.code == "200"
54
+
55
+ issue = JSON.parse(response.body)
56
+ # Example: parse description as feature, steps, etc. (customize as needed)
57
+ {
58
+ feature: issue.dig("fields", "summary") || "",
59
+ scenarios: [
60
+ {
61
+ title: "Scenario",
62
+ steps: [issue.dig("fields", "description", "content")&.map do |c|
63
+ c["content"]&.map do |t|
64
+ t["text"]
65
+ end&.join(" ")
66
+ end&.join(" ") || ""]
67
+ }
68
+ ],
69
+ meta: { tags: [], priority: "", estimate: nil,
70
+ assignee: issue.dig("fields", "assignee", "displayName") || "" }
71
+ }
72
+ end
73
+
74
+ def extract_ticket_title(ticket)
75
+ ticket.dig("fields", "summary") || "No title"
76
+ end
77
+
78
+ def extract_ticket_id(ticket)
79
+ ticket["key"] || "No ID"
80
+ end
81
+
82
+ def extract_ticket_url(ticket)
83
+ "#{@domain}/browse/#{ticket["key"]}"
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "uri"
6
+ require_relative "ticket_parser"
7
+
8
+ module AirTest
9
+ class MondayTicketParser
10
+ include TicketParser
11
+ def initialize(config = AirTest.configuration)
12
+ @api_token = config.monday[:token]
13
+ @board_id = config.monday[:board_id]
14
+ @domain = config.monday[:domain]
15
+ @base_url = "https://api.monday.com/v2"
16
+ end
17
+
18
+ def fetch_tickets(limit: 5)
19
+ # First, get all items from the board
20
+ query = <<~GRAPHQL
21
+ query {
22
+ boards(ids: "#{@board_id}") {
23
+ items_page {
24
+ items {
25
+ id
26
+ name
27
+ column_values {
28
+ id
29
+ text
30
+ value
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ GRAPHQL
37
+
38
+ response = make_graphql_request(query)
39
+ return [] unless response["data"]
40
+
41
+ items = response.dig("data", "boards", 0, "items_page", "items") || []
42
+
43
+ # Filter for items with "Not Started" status
44
+ not_started_items = items.select do |item|
45
+ status_column = item["column_values"].find { |cv| cv["id"] == "project_status" }
46
+ status_column && status_column["text"] == "Not Started"
47
+ end
48
+
49
+ not_started_items.first(limit)
50
+ end
51
+
52
+ def parse_ticket_content(item_id)
53
+ # For Monday, we'll use the item name as feature and create a simple scenario
54
+ # In the future, you could add a description column to Monday and parse it like Notion
55
+ {
56
+ feature: "Feature: #{extract_ticket_title({ "id" => item_id, "name" => "Loading..." })}",
57
+ scenarios: [
58
+ {
59
+ title: "Scenario",
60
+ steps: ["Implement the feature"]
61
+ }
62
+ ],
63
+ meta: { tags: [], priority: "", estimate: nil, assignee: "" }
64
+ }
65
+ end
66
+
67
+ def extract_ticket_title(ticket)
68
+ ticket["name"] || "No title"
69
+ end
70
+
71
+ def extract_ticket_id(ticket)
72
+ ticket["id"] || "No ID"
73
+ end
74
+
75
+ def extract_ticket_url(ticket)
76
+ "https://#{@domain}/boards/#{@board_id}/pulses/#{ticket["id"]}"
77
+ end
78
+
79
+ private
80
+
81
+ def make_graphql_request(query)
82
+ uri = URI(@base_url)
83
+ request = Net::HTTP::Post.new(uri)
84
+ request["Authorization"] = @api_token
85
+ request["Content-Type"] = "application/json"
86
+ request.body = { query: query }.to_json
87
+
88
+ http = Net::HTTP.new(uri.host, uri.port)
89
+ http.use_ssl = true
90
+ response = http.request(request)
91
+
92
+ return {} unless response.code == "200"
93
+
94
+ JSON.parse(response.body)
95
+ rescue JSON::ParserError
96
+ {}
97
+ end
98
+ end
99
+ end
@@ -1,23 +1,33 @@
1
- # Parses Notion tickets and extracts relevant information for spec generation in AirTest.
2
- # rubocop:disable Metrics/ClassLength
3
1
  # frozen_string_literal: true
4
2
 
5
3
  require "net/http"
6
4
  require "json"
7
5
  require "uri"
6
+ require_relative "ticket_parser"
8
7
 
9
8
  module AirTest
10
- # Parses Notion tickets and extracts relevant information for spec generation in AirTest.
11
- class NotionParser
9
+ # Implements TicketParser for Notion integration.
10
+ class NotionTicketParser
11
+ include TicketParser
12
12
  def initialize(config = AirTest.configuration)
13
- @database_id = config.notion_database_id
14
- @notion_token = config.notion_token
13
+ @database_id = config.notion[:database_id]
14
+ @notion_token = config.notion[:token]
15
15
  @base_url = "https://api.notion.com/v1"
16
16
  end
17
17
 
18
18
  def fetch_tickets(limit: 5)
19
19
  uri = URI("#{@base_url}/databases/#{@database_id}/query")
20
- response = make_api_request(uri, { page_size: 100 })
20
+ # Add filter for 'Not started' status
21
+ request_body = {
22
+ page_size: 100,
23
+ filter: {
24
+ property: "Status",
25
+ select: {
26
+ equals: "Not started"
27
+ }
28
+ }
29
+ }
30
+ response = make_api_request(uri, request_body)
21
31
  return [] unless response.code == "200"
22
32
 
23
33
  data = JSON.parse(response.body)
@@ -26,16 +36,16 @@ module AirTest
26
36
 
27
37
  def parse_ticket_content(page_id)
28
38
  blocks = get_page_content(page_id)
29
- puts "\n===== RAW NOTION BLOCKS ====="
30
- puts JSON.pretty_generate(blocks)
39
+ # puts "\n===== RAW NOTION BLOCKS ====="
40
+ # puts JSON.pretty_generate(blocks)
31
41
  return nil unless blocks
42
+
32
43
  normalized_blocks = normalize_blocks(blocks)
33
- puts "\n===== NORMALIZED BLOCKS ====="
34
- puts JSON.pretty_generate(normalized_blocks)
35
- parsed_data = parse_content(normalized_blocks)
36
- puts "\n===== PARSED DATA ====="
37
- puts JSON.pretty_generate(parsed_data)
38
- parsed_data
44
+ # puts "\n===== NORMALIZED BLOCKS ====="
45
+ # puts JSON.pretty_generate(normalized_blocks)
46
+ parse_content(normalized_blocks)
47
+ # puts "\n===== PARSED DATA ====="
48
+ # puts JSON.pretty_generate(parsed_data)
39
49
  end
40
50
 
41
51
  def extract_ticket_title(ticket)
@@ -93,7 +103,11 @@ module AirTest
93
103
 
94
104
  blocks.each do |block|
95
105
  block_type = block["type"]
96
- text = extract_text(block[block_type]["rich_text"]) rescue ""
106
+ text = begin
107
+ extract_text(block[block_type]["rich_text"])
108
+ rescue StandardError
109
+ ""
110
+ end
97
111
 
98
112
  if %w[heading_1 heading_2 heading_3].include?(block_type)
99
113
  heading_text = text.strip
@@ -127,19 +141,19 @@ module AirTest
127
141
  parsed_data[:scenarios] << current_scenario
128
142
  elsif %w[paragraph bulleted_list_item numbered_list_item].include?(block_type)
129
143
  next if text.empty?
144
+
130
145
  if in_feature_block
131
146
  parsed_data[:feature] += "\n#{text}"
132
147
  elsif in_background_block
133
148
  background_steps << text
134
149
  elsif in_scenario_block && current_scenario
135
150
  # Only add as step if not a scenario heading
136
- unless text.strip.downcase.start_with?("scenario:")
137
- current_scenario[:steps] << text
138
- end
151
+ current_scenario[:steps] << text unless text.strip.downcase.start_with?("scenario:")
139
152
  end
140
153
  elsif block_type == "callout"
141
154
  text = extract_text(block["callout"]["rich_text"])
142
155
  next if text.empty?
156
+
143
157
  if text.downcase.include?("tag")
144
158
  tags = extract_tags(text)
145
159
  parsed_data[:meta][:tags].concat(tags)
@@ -166,23 +180,19 @@ module AirTest
166
180
  in_steps = false
167
181
  blocks.each do |block|
168
182
  block_type = block["type"]
169
- text = extract_text(block[block_type]["rich_text"] || block["paragraph"]["rich_text"]) rescue ""
183
+ text = begin
184
+ extract_text(block[block_type]["rich_text"] || block["paragraph"]["rich_text"])
185
+ rescue StandardError
186
+ ""
187
+ end
170
188
  if %w[heading_1 heading_2 heading_3].include?(block_type)
171
189
  heading_text = text.strip
172
- if heading_text.downcase.include?("feature")
173
- in_steps = true
174
- elsif heading_text.downcase.include?("scenario")
175
- in_steps = true
176
- else
177
- in_steps = false
178
- end
190
+ in_steps = heading_text.downcase.include?("feature")) || heading_text.downcase.include?("scenario")
179
191
  elsif %w[paragraph bulleted_list_item numbered_list_item].include?(block_type)
180
192
  steps << text if in_steps && !text.empty? && !text.strip.downcase.start_with?("scenario:")
181
193
  end
182
194
  end
183
- if steps.any?
184
- parsed_data[:scenarios] << { title: "Scenario", steps: steps }
185
- end
195
+ parsed_data[:scenarios] << { title: "Scenario", steps: steps } if steps.any?
186
196
  end
187
197
 
188
198
  parsed_data[:feature] = parsed_data[:feature].strip
@@ -234,10 +244,10 @@ module AirTest
234
244
  blocks.each do |block|
235
245
  block_type = block["type"]
236
246
  text = if block[block_type] && block[block_type]["rich_text"]
237
- block[block_type]["rich_text"].map { |rt| rt["plain_text"] }.join("")
238
- else
239
- ""
240
- end
247
+ block[block_type]["rich_text"].map { |rt| rt["plain_text"] }.join
248
+ else
249
+ ""
250
+ end
241
251
  lines = text.split("\n").map(&:strip).reject(&:empty?)
242
252
  if lines.size > 1
243
253
  lines.each do |line|
@@ -254,5 +264,3 @@ module AirTest
254
264
  end
255
265
  end
256
266
  end
257
-
258
- # rubocop:enable Metrics/ClassLength
@@ -6,20 +6,32 @@ module AirTest
6
6
  # Runs the main automation workflow for AirTest, orchestrating Notion parsing and GitHub actions.
7
7
  class Runner
8
8
  def initialize(config = AirTest.configuration)
9
- @notion = NotionParser.new(config)
9
+ @config = config
10
+ @parser = case config.tool.to_s.downcase
11
+ when "notion"
12
+ NotionTicketParser.new(config)
13
+ when "jira"
14
+ JiraTicketParser.new(config)
15
+ when "monday"
16
+ MondayTicketParser.new(config)
17
+ else
18
+ raise "Unknown tool: #{config.tool}"
19
+ end
10
20
  @spec = SpecGenerator.new
11
21
  @github = GithubClient.new(config)
12
22
  end
13
23
 
14
24
  def run(limit: 5)
15
- tickets = @notion.fetch_tickets(limit: limit)
25
+ @config.validate!
26
+ tickets = @parser.fetch_tickets(limit: limit)
27
+ # Filter for 'Not started' tickets (assuming each parser returns only those, or filter here if needed)
16
28
  puts "🔍 Found #{tickets.length} tickets"
17
29
  tickets.each do |ticket|
18
- ticket_id = @notion.extract_ticket_id(ticket)
19
- title = @notion.extract_ticket_title(ticket)
20
- url = @notion.extract_ticket_url(ticket)
30
+ ticket_id = @parser.extract_ticket_id(ticket)
31
+ title = @parser.extract_ticket_title(ticket)
32
+ url = @parser.extract_ticket_url(ticket)
21
33
  puts "\n📋 Processing: FDR#{ticket_id} - #{title}"
22
- parsed_data = @notion.parse_ticket_content(ticket["id"])
34
+ parsed_data = @parser.parse_ticket_content(ticket["id"])
23
35
  unless parsed_data && parsed_data[:feature] && !parsed_data[:feature].empty?
24
36
  puts "⚠️ Skipping ticket FDR#{ticket_id} due to missing or empty feature."
25
37
  next
@@ -43,6 +55,9 @@ module AirTest
43
55
  - **Feature** : #{parsed_data[:feature]}
44
56
  - **Scénarios** :
45
57
  #{scenarios_md}
58
+ - **Want to help us improve airtest?**
59
+ Leave feedback [here](http://bit.ly/4o5rinU)
60
+ or [join the community](https://discord.gg/ggnBvhtw7E)
46
61
  MD
47
62
  pr = @github.create_pull_request(branch, pr_title, pr_body)
48
63
  puts "✅ Pull Request créée : #{pr.html_url}" if pr
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AirTest
4
+ # Interface for ticket parsers (Notion, Jira, Monday, etc.)
5
+ module TicketParser
6
+ def fetch_tickets(limit: 5)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def parse_ticket_content(page_id)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def extract_ticket_title(ticket)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def extract_ticket_id(ticket)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def extract_ticket_url(ticket)
23
+ raise NotImplementedError
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AirTest
4
- VERSION = "0.1.5.5"
4
+ VERSION = "0.1.6.0"
5
5
  end
data/lib/air_test.rb CHANGED
@@ -14,7 +14,10 @@ end
14
14
 
15
15
  require_relative "air_test/version"
16
16
  require_relative "air_test/configuration"
17
- require_relative "air_test/notion_parser"
17
+ require_relative "air_test/ticket_parser"
18
+ require_relative "air_test/notion_ticket_parser"
19
+ require_relative "air_test/jira_ticket_parser"
20
+ require_relative "air_test/monday_ticket_parser"
18
21
  require_relative "air_test/spec_generator"
19
22
  require_relative "air_test/github_client"
20
23
  require_relative "air_test/runner"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: air_test
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5.5
4
+ version: 0.1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - julien bouland
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-07-23 00:00:00.000000000 Z
10
+ date: 2025-07-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -72,10 +72,13 @@ files:
72
72
  - lib/air_test/configuration.rb
73
73
  - lib/air_test/engine.rb
74
74
  - lib/air_test/github_client.rb
75
- - lib/air_test/notion_parser.rb
75
+ - lib/air_test/jira_ticket_parser.rb
76
+ - lib/air_test/monday_ticket_parser.rb
77
+ - lib/air_test/notion_ticket_parser.rb
76
78
  - lib/air_test/runner.rb
77
79
  - lib/air_test/spec_generator.rb
78
80
  - lib/air_test/tasks/air_test.rake
81
+ - lib/air_test/ticket_parser.rb
79
82
  - lib/air_test/version.rb
80
83
  - sig/air_test.rbs
81
84
  homepage: https://github.com/airtest-dev/airtest