tng 0.1.6 → 0.2.1
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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +6 -20
- data/bin/tng +238 -472
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/tng/analyzers/model.rb +9 -0
- data/lib/tng/analyzers/other.rb +277 -0
- data/lib/tng/analyzers/service.rb +1 -0
- data/lib/tng/api/http_client.rb +21 -23
- data/lib/tng/services/direct_generation.rb +320 -0
- data/lib/tng/services/extract_methods.rb +39 -0
- data/lib/tng/services/test_generator.rb +232 -104
- data/lib/tng/services/testng.rb +2 -2
- data/lib/tng/services/user_app_config.rb +5 -6
- data/lib/tng/ui/about_display.rb +7 -5
- data/lib/tng/ui/controller_test_flow_display.rb +0 -17
- data/lib/tng/ui/model_test_flow_display.rb +0 -17
- data/lib/tng/ui/other_test_flow_display.rb +78 -0
- data/lib/tng/ui/post_install_box.rb +7 -9
- data/lib/tng/ui/service_test_flow_display.rb +0 -17
- data/lib/tng/ui/show_help.rb +11 -68
- data/lib/tng/utils.rb +67 -5
- data/lib/tng/version.rb +1 -1
- metadata +15 -11
data/binaries/tng.bundle
CHANGED
Binary file
|
data/binaries/tng.so
CHANGED
Binary file
|
data/lib/tng/analyzers/model.rb
CHANGED
@@ -106,6 +106,15 @@ module Tng
|
|
106
106
|
when Prism::DefNode
|
107
107
|
# Both instance and class methods (def self.method_name)
|
108
108
|
methods << node.name.to_s
|
109
|
+
when Prism::CallNode
|
110
|
+
# Handle scope definitions: scope :name, -> { ... }
|
111
|
+
if node.name == :scope && node.arguments&.arguments&.any?
|
112
|
+
first_arg = node.arguments.arguments.first
|
113
|
+
if first_arg.is_a?(Prism::SymbolNode)
|
114
|
+
scope_name = first_arg.value
|
115
|
+
methods << scope_name if scope_name
|
116
|
+
end
|
117
|
+
end
|
109
118
|
when Prism::SingletonClassNode
|
110
119
|
# Methods inside class << self blocks
|
111
120
|
node.body&.child_nodes&.each do |child|
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Tng
|
4
|
+
module Analyzers
|
5
|
+
class Other
|
6
|
+
# Directories to scan for "other" files
|
7
|
+
OTHER_DIRECTORIES = %w[
|
8
|
+
app/jobs
|
9
|
+
app/helpers
|
10
|
+
lib
|
11
|
+
libs
|
12
|
+
app/lib
|
13
|
+
app/libs
|
14
|
+
app/mailers
|
15
|
+
app/channels
|
16
|
+
app/decorators
|
17
|
+
app/presenters
|
18
|
+
app/serializers
|
19
|
+
app/policies
|
20
|
+
app/forms
|
21
|
+
app/queries
|
22
|
+
app/graphql
|
23
|
+
app/graphql/resolvers
|
24
|
+
app/graphql/types
|
25
|
+
app/graphql/mutations
|
26
|
+
app/graphql/loaders
|
27
|
+
app/graphql/schemas
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
def self.files_in_dir(dir = nil)
|
31
|
+
base_dirs = if dir.nil?
|
32
|
+
OTHER_DIRECTORIES.select { |d| Dir.exist?(File.join(Dir.pwd, d)) }
|
33
|
+
else
|
34
|
+
[dir]
|
35
|
+
end
|
36
|
+
|
37
|
+
return [] if base_dirs.empty?
|
38
|
+
|
39
|
+
base_dirs.flat_map do |base_dir|
|
40
|
+
full_path = File.join(Dir.pwd, base_dir)
|
41
|
+
find_other_files(full_path, base_dir)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.value_for_other(file_path)
|
46
|
+
raise "file_path is required" if file_path.nil?
|
47
|
+
|
48
|
+
# For now, use the same parsing logic as services
|
49
|
+
# We can enhance this later if needed for specific file types
|
50
|
+
Tng::Analyzer::Service.parse_service_file(file_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.read_test_file_for_other(other_path)
|
54
|
+
files = find_test_file_for_other(other_path)
|
55
|
+
|
56
|
+
files.map do |file_path|
|
57
|
+
{
|
58
|
+
path: file_path,
|
59
|
+
content: File.read(file_path)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.find_test_file_for_other(other_path)
|
65
|
+
# Extract the file type and name for test file discovery
|
66
|
+
file_type = determine_file_type(other_path)
|
67
|
+
file_name = File.basename(other_path, ".rb")
|
68
|
+
testing_framework = Tng.testing_framework
|
69
|
+
return [] if testing_framework.nil?
|
70
|
+
|
71
|
+
paths = if testing_framework.downcase == "rspec"
|
72
|
+
build_rspec_test_paths(file_type, file_name)
|
73
|
+
else
|
74
|
+
build_minitest_test_paths(file_type, file_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
paths.select { |path| File.exist?(path) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.methods_for_other(other_name, file_path)
|
81
|
+
raise "other_name is required" if other_name.nil?
|
82
|
+
raise "file_path is required" if file_path.nil?
|
83
|
+
|
84
|
+
begin
|
85
|
+
# Try to load the class/module if possible
|
86
|
+
if File.exist?(file_path)
|
87
|
+
source_code = File.read(file_path)
|
88
|
+
result = Prism.parse(source_code)
|
89
|
+
|
90
|
+
public_methods = []
|
91
|
+
extract_public_method_names(result.value, public_methods, :public)
|
92
|
+
|
93
|
+
# Return the public methods found in the source file
|
94
|
+
public_methods.map { |method_name| { name: method_name.to_s } }
|
95
|
+
else
|
96
|
+
[]
|
97
|
+
end
|
98
|
+
rescue StandardError => e
|
99
|
+
puts "❌ Error analyzing file #{other_name}: #{e.message}"
|
100
|
+
[]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.extract_public_method_names(node, methods, current_visibility = :public)
|
105
|
+
return unless node.is_a?(Prism::Node)
|
106
|
+
|
107
|
+
case node
|
108
|
+
when Prism::DefNode
|
109
|
+
# Only add methods that are public
|
110
|
+
methods << node.name.to_s if current_visibility == :public
|
111
|
+
when Prism::CallNode
|
112
|
+
# Handle visibility modifiers (private, protected, public)
|
113
|
+
if node.receiver.nil? && node.arguments.nil?
|
114
|
+
case node.name
|
115
|
+
when :private
|
116
|
+
current_visibility = :private
|
117
|
+
when :protected
|
118
|
+
current_visibility = :protected
|
119
|
+
when :public
|
120
|
+
current_visibility = :public
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
when Prism::SingletonClassNode
|
125
|
+
# Methods inside class << self blocks - always public by default
|
126
|
+
node.body&.child_nodes&.each do |child|
|
127
|
+
extract_public_method_names(child, methods, :public)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Recursively process child nodes with current visibility
|
132
|
+
node.child_nodes.each do |child|
|
133
|
+
extract_public_method_names(child, methods, current_visibility)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.find_other_files(dir, _base_dir)
|
138
|
+
return [] unless Dir.exist?(dir)
|
139
|
+
|
140
|
+
Dir.glob(File.join(dir, "**", "*.rb")).map do |file_path|
|
141
|
+
relative_path = file_path.gsub("#{Dir.pwd}/", "")
|
142
|
+
file_type = determine_file_type(file_path)
|
143
|
+
|
144
|
+
{
|
145
|
+
file: File.basename(file_path),
|
146
|
+
path: file_path,
|
147
|
+
relative_path: relative_path,
|
148
|
+
type: file_type
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.determine_file_type(file_path)
|
154
|
+
case file_path
|
155
|
+
when %r{app/jobs}
|
156
|
+
"job"
|
157
|
+
when %r{app/helpers}
|
158
|
+
"helper"
|
159
|
+
when %r{(?:app/)?libs?(?:/|$)}
|
160
|
+
"lib"
|
161
|
+
when %r{app/mailers}
|
162
|
+
"mailer"
|
163
|
+
when %r{app/channels}
|
164
|
+
"channel"
|
165
|
+
when %r{app/decorators}
|
166
|
+
"decorator"
|
167
|
+
when %r{app/presenters}
|
168
|
+
"presenter"
|
169
|
+
when %r{app/serializers}
|
170
|
+
"serializer"
|
171
|
+
when %r{app/policies}
|
172
|
+
"policy"
|
173
|
+
when %r{app/forms}
|
174
|
+
"form"
|
175
|
+
when %r{app/queries}
|
176
|
+
"query"
|
177
|
+
when %r{app/graphql/resolvers}
|
178
|
+
"resolver"
|
179
|
+
when %r{app/graphql/types}
|
180
|
+
"type"
|
181
|
+
when %r{app/graphql/mutations}
|
182
|
+
"mutation"
|
183
|
+
when %r{app/graphql/loaders}
|
184
|
+
"loader"
|
185
|
+
when %r{app/graphql/schemas}
|
186
|
+
"schema"
|
187
|
+
when %r{app/graphql}
|
188
|
+
"graphql"
|
189
|
+
else
|
190
|
+
"other"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.build_rspec_test_paths(file_type, file_name)
|
195
|
+
case file_type
|
196
|
+
when "job"
|
197
|
+
["spec/jobs/#{file_name}_spec.rb"]
|
198
|
+
when "helper"
|
199
|
+
["spec/helpers/#{file_name}_spec.rb"]
|
200
|
+
when "lib"
|
201
|
+
["spec/lib/#{file_name}_spec.rb"]
|
202
|
+
when "mailer"
|
203
|
+
["spec/mailers/#{file_name}_spec.rb"]
|
204
|
+
when "channel"
|
205
|
+
["spec/channels/#{file_name}_spec.rb"]
|
206
|
+
when "query"
|
207
|
+
["spec/queries/#{file_name}_spec.rb"]
|
208
|
+
when "decorator"
|
209
|
+
["spec/decorators/#{file_name}_spec.rb"]
|
210
|
+
when "presenter"
|
211
|
+
["spec/presenters/#{file_name}_spec.rb"]
|
212
|
+
when "serializer"
|
213
|
+
["spec/serializers/#{file_name}_spec.rb"]
|
214
|
+
when "policy"
|
215
|
+
["spec/policies/#{file_name}_spec.rb"]
|
216
|
+
when "form"
|
217
|
+
["spec/forms/#{file_name}_spec.rb"]
|
218
|
+
when "resolver"
|
219
|
+
["spec/graphql/resolvers/#{file_name}_spec.rb"]
|
220
|
+
when "type"
|
221
|
+
["spec/graphql/types/#{file_name}_spec.rb"]
|
222
|
+
when "mutation"
|
223
|
+
["spec/graphql/mutations/#{file_name}_spec.rb"]
|
224
|
+
when "loader"
|
225
|
+
["spec/graphql/loaders/#{file_name}_spec.rb"]
|
226
|
+
when "schema"
|
227
|
+
["spec/graphql/schemas/#{file_name}_spec.rb"]
|
228
|
+
when "graphql"
|
229
|
+
["spec/graphql/#{file_name}_spec.rb"]
|
230
|
+
else
|
231
|
+
["spec/#{file_type}s/#{file_name}_spec.rb"]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def self.build_minitest_test_paths(file_type, file_name)
|
236
|
+
case file_type
|
237
|
+
when "job"
|
238
|
+
["test/jobs/#{file_name}_test.rb"]
|
239
|
+
when "helper"
|
240
|
+
["test/helpers/#{file_name}_test.rb"]
|
241
|
+
when "lib"
|
242
|
+
["test/lib/#{file_name}_test.rb"]
|
243
|
+
when "mailer"
|
244
|
+
["test/mailers/#{file_name}_test.rb"]
|
245
|
+
when "channel"
|
246
|
+
["test/channels/#{file_name}_test.rb"]
|
247
|
+
when "query"
|
248
|
+
["test/queries/#{file_name}_test.rb"]
|
249
|
+
when "decorator"
|
250
|
+
["test/decorators/#{file_name}_test.rb"]
|
251
|
+
when "presenter"
|
252
|
+
["test/presenters/#{file_name}_test.rb"]
|
253
|
+
when "serializer"
|
254
|
+
["test/serializers/#{file_name}_test.rb"]
|
255
|
+
when "policy"
|
256
|
+
["test/policies/#{file_name}_test.rb"]
|
257
|
+
when "form"
|
258
|
+
["test/forms/#{file_name}_test.rb"]
|
259
|
+
when "resolver"
|
260
|
+
["test/graphql/resolvers/#{file_name}_test.rb"]
|
261
|
+
when "type"
|
262
|
+
["test/graphql/types/#{file_name}_test.rb"]
|
263
|
+
when "mutation"
|
264
|
+
["test/graphql/mutations/#{file_name}_test.rb"]
|
265
|
+
when "loader"
|
266
|
+
["test/graphql/loaders/#{file_name}_test.rb"]
|
267
|
+
when "schema"
|
268
|
+
["test/graphql/schemas/#{file_name}_test.rb"]
|
269
|
+
when "graphql"
|
270
|
+
["test/graphql/#{file_name}_test.rb"]
|
271
|
+
else
|
272
|
+
["test/#{file_type}s/#{file_name}_test.rb"]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
data/lib/tng/api/http_client.rb
CHANGED
@@ -14,13 +14,7 @@ module Tng
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def post(path, payload: {}, headers: {})
|
17
|
-
|
18
|
-
"Content-Type" => "application/json",
|
19
|
-
"Authorization" => "Bearer #{@api_key}",
|
20
|
-
"User-Agent" => "TestNG-Rails/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
21
|
-
}
|
22
|
-
|
23
|
-
merged_headers = default_headers.merge(headers)
|
17
|
+
merged_headers = json_default_headers.merge(headers)
|
24
18
|
|
25
19
|
response = HTTPX.with(timeout: @timeout).post(
|
26
20
|
"#{@api_endpoint}/#{path}",
|
@@ -33,13 +27,7 @@ module Tng
|
|
33
27
|
end
|
34
28
|
|
35
29
|
def post_binary(path, data, headers: {})
|
36
|
-
|
37
|
-
"Content-Type" => "application/octet-stream",
|
38
|
-
"Authorization" => "Bearer #{@api_key}",
|
39
|
-
"User-Agent" => "TestNG-Rails/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
40
|
-
}
|
41
|
-
|
42
|
-
merged_headers = default_headers.merge(headers)
|
30
|
+
merged_headers = stream_default_headers.merge(headers)
|
43
31
|
|
44
32
|
response = HTTPX.with(timeout: @timeout).post(
|
45
33
|
"#{@api_endpoint}/#{path}",
|
@@ -52,13 +40,7 @@ module Tng
|
|
52
40
|
end
|
53
41
|
|
54
42
|
def get(path, headers: {})
|
55
|
-
|
56
|
-
"Content-Type" => "application/json",
|
57
|
-
"Authorization" => "Bearer #{@api_key}",
|
58
|
-
"User-Agent" => "TestNG-Rails/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
59
|
-
}
|
60
|
-
|
61
|
-
merged_headers = default_headers.merge(headers)
|
43
|
+
merged_headers = json_default_headers.merge(headers)
|
62
44
|
|
63
45
|
response = HTTPX.with(timeout: @timeout).get(
|
64
46
|
"#{@api_endpoint}/#{path}",
|
@@ -77,18 +59,34 @@ module Tng
|
|
77
59
|
|
78
60
|
private
|
79
61
|
|
62
|
+
def stream_default_headers
|
63
|
+
{
|
64
|
+
"Content-Type" => "application/octet-stream",
|
65
|
+
"Authorization" => "Bearer #{@api_key}",
|
66
|
+
"User-Agent" => "TestNG-Rails/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def json_default_headers
|
71
|
+
{
|
72
|
+
"Content-Type" => "application/json",
|
73
|
+
"Authorization" => "Bearer #{@api_key}",
|
74
|
+
"User-Agent" => "TestNG-Rails/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
80
78
|
def debug_enabled?
|
81
79
|
ENV["DEBUG"] == "1"
|
82
80
|
end
|
83
81
|
|
84
82
|
def debug_response(request_info, response)
|
85
83
|
puts "\n -> DEBUG: #{request_info}"
|
86
|
-
puts " Status: #{response.status}"
|
87
|
-
puts " Headers: #{response.headers.to_h}"
|
88
84
|
|
89
85
|
if response.is_a?(HTTPX::ErrorResponse)
|
90
86
|
puts " Error: #{response.error&.message}"
|
91
87
|
else
|
88
|
+
puts " Status: #{response.status}"
|
89
|
+
puts " Headers: #{response.headers.to_h}"
|
92
90
|
body = response.body.to_s
|
93
91
|
if body.length > 500
|
94
92
|
puts " Body: #{body[0..500]}... (truncated, total length: #{body.length})"
|