tng 0.3.0 → 0.3.3
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/Rakefile +20 -43
- data/bin/tng +264 -558
- data/binaries/go-ui-darwin-amd64 +0 -0
- data/binaries/go-ui-darwin-arm64 +0 -0
- data/binaries/go-ui-linux-amd64 +0 -0
- data/binaries/go-ui-linux-arm64 +0 -0
- data/binaries/tng-darwin-arm64.bundle +0 -0
- data/binaries/tng-linux-arm64.so +0 -0
- data/binaries/tng-linux-x86_64.so +0 -0
- data/lib/tng/api/http_client.rb +19 -0
- data/lib/tng/services/test_generator.rb +114 -81
- data/lib/tng/services/testng.rb +2 -8
- data/lib/tng/ui/go_ui_session.rb +406 -0
- data/lib/tng/utils.rb +3 -30
- data/lib/tng/version.rb +1 -1
- data/lib/tng.rb +24 -11
- data/tng.gemspec +4 -9
- metadata +55 -126
- data/binaries/tng.bundle +0 -0
- data/binaries/tng.so +0 -0
- data/lib/tng/ui/about_display.rb +0 -66
- data/lib/tng/ui/authentication_warning_display.rb +0 -122
- data/lib/tng/ui/configuration_display.rb +0 -52
- data/lib/tng/ui/controller_test_flow_display.rb +0 -79
- data/lib/tng/ui/display_banner.rb +0 -44
- data/lib/tng/ui/goodbye_display.rb +0 -41
- data/lib/tng/ui/model_test_flow_display.rb +0 -80
- data/lib/tng/ui/other_test_flow_display.rb +0 -78
- data/lib/tng/ui/service_test_flow_display.rb +0 -78
- data/lib/tng/ui/show_help.rb +0 -78
- data/lib/tng/ui/system_status_display.rb +0 -128
- data/lib/tng/ui/user_stats_display.rb +0 -160
data/binaries/go-ui-darwin-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-darwin-arm64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-amd64
CHANGED
|
Binary file
|
data/binaries/go-ui-linux-arm64
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/tng/api/http_client.rb
CHANGED
|
@@ -70,6 +70,25 @@ module Tng
|
|
|
70
70
|
response
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
# Verify if the API key is valid
|
|
74
|
+
# Returns: { valid: true/false, error: nil/String }
|
|
75
|
+
def verify_auth
|
|
76
|
+
response = get("cli/tng_rails/stats")
|
|
77
|
+
|
|
78
|
+
return { valid: false, error: "Network error" } if response.is_a?(HTTPX::ErrorResponse)
|
|
79
|
+
|
|
80
|
+
case response.status
|
|
81
|
+
when 200, 201
|
|
82
|
+
{ valid: true, error: nil }
|
|
83
|
+
when 401
|
|
84
|
+
{ valid: false, error: "Invalid or missing API key" }
|
|
85
|
+
when 403
|
|
86
|
+
{ valid: false, error: "API key expired or usage limit reached" }
|
|
87
|
+
else
|
|
88
|
+
{ valid: false, error: "Unexpected error (#{response.status})" }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
73
92
|
private
|
|
74
93
|
|
|
75
94
|
def stream_default_headers
|
|
@@ -20,53 +20,49 @@ module Tng
|
|
|
20
20
|
@pastel = Pastel.new
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def run_for_controller_method(controller, method_info)
|
|
24
|
-
generate_test_for_type(controller, method_info, :controller)
|
|
23
|
+
def run_for_controller_method(controller, method_info, progress: nil)
|
|
24
|
+
generate_test_for_type(controller, method_info, :controller, progress: progress)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def run_for_model_method(model, method_info)
|
|
28
|
-
generate_test_for_type(model, method_info, :model)
|
|
27
|
+
def run_for_model_method(model, method_info, progress: nil)
|
|
28
|
+
generate_test_for_type(model, method_info, :model, progress: progress)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def run_for_service_method(service, method_info)
|
|
32
|
-
generate_test_for_type(service, method_info, :service)
|
|
31
|
+
def run_for_service_method(service, method_info, progress: nil)
|
|
32
|
+
generate_test_for_type(service, method_info, :service, progress: progress)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def run_for_other_method(other_file, method_info)
|
|
36
|
-
generate_test_for_type(other_file, method_info, :other)
|
|
35
|
+
def run_for_other_method(other_file, method_info, progress: nil)
|
|
36
|
+
generate_test_for_type(other_file, method_info, :other, progress: progress)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
private
|
|
40
39
|
|
|
41
|
-
def generate_test_for_type(file_object, method_info, type)
|
|
40
|
+
def generate_test_for_type(file_object, method_info, type, progress: nil)
|
|
42
41
|
start_time = Time.now
|
|
43
42
|
|
|
44
43
|
response = send_request_for_type(file_object, method_info, type)
|
|
45
44
|
return unless response
|
|
46
45
|
|
|
47
46
|
if response.is_a?(HTTPX::ErrorResponse)
|
|
48
|
-
|
|
49
|
-
error_color = Tng::UI::Theme.color(:error)
|
|
50
|
-
puts "#{error_icon} #{@pastel.decorate("Request failed:",
|
|
51
|
-
error_color)} #{response.error&.message || "Unknown error"}"
|
|
52
|
-
return
|
|
47
|
+
return { error: :network_error, message: response.error&.message || "Network error" }
|
|
53
48
|
end
|
|
54
49
|
|
|
55
50
|
job_data = JSON.parse(response.body)
|
|
56
51
|
|
|
52
|
+
# Check for authentication errors
|
|
53
|
+
if response.status == 401
|
|
54
|
+
return { error: :auth_failed, message: "Invalid or missing API key" }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
57
|
# Check for forbidden responses (usage limits or invalid keys)
|
|
58
58
|
if response.status == 403
|
|
59
|
-
|
|
60
|
-
error_color = Tng::UI::Theme.color(:error)
|
|
61
|
-
puts "#{error_icon} #{@pastel.decorate("Access denied:",
|
|
62
|
-
error_color)} Please check your API key and usage limits."
|
|
63
|
-
return
|
|
59
|
+
return { error: :auth_failed, message: "API key expired or usage limit reached" }
|
|
64
60
|
end
|
|
65
61
|
job_id = job_data["job_id"]
|
|
66
62
|
|
|
67
63
|
return unless job_id
|
|
68
64
|
|
|
69
|
-
result = poll_for_completion(job_id)
|
|
65
|
+
result = poll_for_completion(job_id, progress: progress)
|
|
70
66
|
|
|
71
67
|
return unless result
|
|
72
68
|
|
|
@@ -106,21 +102,57 @@ module Tng
|
|
|
106
102
|
]
|
|
107
103
|
end
|
|
108
104
|
|
|
109
|
-
def poll_for_completion(job_id)
|
|
105
|
+
def poll_for_completion(job_id, progress: nil)
|
|
110
106
|
start_time = Time.current
|
|
111
|
-
|
|
107
|
+
attempts = 0
|
|
108
|
+
max_attempts = MAX_POLL_DURATION_SECONDS / POLL_INTERVAL_SECONDS
|
|
109
|
+
|
|
110
|
+
# Initialize agent steps if progress tracking is enabled
|
|
111
|
+
agent_step_indices = {}
|
|
112
|
+
if progress
|
|
113
|
+
# Current step index in progress updater (assuming it follows previous steps)
|
|
114
|
+
# We create 4 generic steps for the agents
|
|
115
|
+
# Note: indices are relative to the current session, so we just append them.
|
|
116
|
+
# But we need their indices to update them later.
|
|
117
|
+
# Since we can't ask progress for current index easily without hacking,
|
|
118
|
+
# we rely on the fact that we call update 4 times.
|
|
119
|
+
|
|
120
|
+
# Use a base offset if we could know it, but we can't reliably.
|
|
121
|
+
# Actually, if we use explicit_step, we need absolute indices.
|
|
122
|
+
# Let's assume the previous steps were 0, 1, 2, 3 based on bin/tng.
|
|
123
|
+
# So we start at 4.
|
|
124
|
+
base_idx = 4
|
|
125
|
+
|
|
126
|
+
progress.update("Context Builder: Pending...", nil, step_increment: true)
|
|
127
|
+
agent_step_indices["context_agent_status"] = base_idx
|
|
128
|
+
|
|
129
|
+
progress.update("Style Analyzer: Pending...", nil, step_increment: true)
|
|
130
|
+
agent_step_indices["style_agent_status"] = base_idx + 1
|
|
131
|
+
|
|
132
|
+
progress.update("Logic Analyzer: Pending...", nil, step_increment: true)
|
|
133
|
+
agent_step_indices["logical_issue_status"] = base_idx + 2
|
|
134
|
+
|
|
135
|
+
progress.update("Logic Generator: Pending...", nil, step_increment: true)
|
|
136
|
+
agent_step_indices["behavior_expert_status"] = base_idx + 3
|
|
137
|
+
end
|
|
112
138
|
|
|
113
139
|
loop do
|
|
140
|
+
attempts += 1
|
|
114
141
|
seconds_elapsed = (Time.current - start_time).to_i
|
|
115
142
|
|
|
116
143
|
if seconds_elapsed > MAX_POLL_DURATION_SECONDS
|
|
117
|
-
|
|
118
|
-
puts "❌ Test generation timed out after #{MAX_POLL_DURATION_SECONDS} seconds (7 minutes)"
|
|
119
|
-
return
|
|
144
|
+
return { error: :timeout, message: "Test generation timed out after #{MAX_POLL_DURATION_SECONDS} seconds" }
|
|
120
145
|
end
|
|
121
146
|
|
|
122
|
-
|
|
123
|
-
|
|
147
|
+
# Calculate pseudo-percentage
|
|
148
|
+
if progress
|
|
149
|
+
pct = ((attempts.to_f / max_attempts.to_f) * 95.0).to_i
|
|
150
|
+
# Update global progress bar without adding a new step (explicit_step=4 updates Context Builder line, but we want to just update percentage?)
|
|
151
|
+
# The progress bar component takes the percent from the *last* update message.
|
|
152
|
+
# So we can pass specific percent when we update any agent step.
|
|
153
|
+
else
|
|
154
|
+
pct = 0
|
|
155
|
+
end
|
|
124
156
|
|
|
125
157
|
sleep(POLL_INTERVAL_SECONDS)
|
|
126
158
|
|
|
@@ -128,31 +160,73 @@ module Tng
|
|
|
128
160
|
|
|
129
161
|
if status_response.is_a?(HTTPX::ErrorResponse)
|
|
130
162
|
debug_log("Status check failed: #{status_response.error&.message}") if debug_enabled?
|
|
131
|
-
next
|
|
163
|
+
next
|
|
132
164
|
end
|
|
133
165
|
|
|
134
166
|
begin
|
|
135
167
|
status_data = JSON.parse(status_response.body.to_s)
|
|
136
168
|
status = status_data["status"]
|
|
169
|
+
|
|
170
|
+
# Update UI with granular info
|
|
171
|
+
if progress && status_data["info"].is_a?(Hash)
|
|
172
|
+
info = status_data["info"]
|
|
173
|
+
|
|
174
|
+
agent_step_indices.each do |key, step_idx|
|
|
175
|
+
item_data = info[key]
|
|
176
|
+
next unless item_data
|
|
177
|
+
|
|
178
|
+
agent_status = "pending"
|
|
179
|
+
values = []
|
|
180
|
+
|
|
181
|
+
if item_data.is_a?(Hash)
|
|
182
|
+
agent_status = item_data["status"] || "pending"
|
|
183
|
+
values = item_data["values"] || []
|
|
184
|
+
else
|
|
185
|
+
agent_status = item_data.to_s
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
label = case key
|
|
189
|
+
when "context_agent_status" then "Context Builder"
|
|
190
|
+
when "style_agent_status" then "Style Analyzer"
|
|
191
|
+
when "logical_issue_status" then "Logic Analyzer"
|
|
192
|
+
when "behavior_expert_status" then "Logic Generator"
|
|
193
|
+
else key
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
msg = if agent_status == "processing"
|
|
197
|
+
"#{label}: Processing..."
|
|
198
|
+
elsif agent_status == "completed"
|
|
199
|
+
"#{label}: Completed"
|
|
200
|
+
elsif agent_status == "failed"
|
|
201
|
+
"#{label}: Failed"
|
|
202
|
+
else
|
|
203
|
+
"#{label}: #{agent_status.capitalize}..."
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
if values.any?
|
|
207
|
+
# Clean values
|
|
208
|
+
clean_vals = values.map { |v| v.to_s.gsub("_", " ").gsub("'", "").gsub(":", "").strip }
|
|
209
|
+
display_str = clean_vals.first(3).join(", ")
|
|
210
|
+
display_str += ", ..." if clean_vals.size > 3
|
|
211
|
+
msg += " (#{display_str})"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Pass percentage only on the last step (Logic Generator) to keep main bar moving?
|
|
215
|
+
# Or pass it on all updates.
|
|
216
|
+
p = (step_idx == agent_step_indices["behavior_expert_status"]) ? pct : nil
|
|
217
|
+
|
|
218
|
+
progress.update(msg, p, step_increment: false, explicit_step: step_idx)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
137
221
|
|
|
138
222
|
case status
|
|
139
223
|
when "completed"
|
|
140
224
|
debug_log("Test generation completed!") if debug_enabled?
|
|
141
|
-
progress_bar.finish
|
|
142
|
-
success_icon = Tng::UI::Theme.icon(:success)
|
|
143
|
-
success_color = Tng::UI::Theme.color(:success)
|
|
144
|
-
puts "#{success_icon} #{@pastel.decorate("Test generation completed!", success_color)}"
|
|
145
225
|
return status_data["result"]
|
|
146
226
|
when "failed"
|
|
147
227
|
debug_log("Test generation failed: #{status_data["error"]}") if debug_enabled?
|
|
148
|
-
|
|
149
|
-
error_icon = Tng::UI::Theme.icon(:error)
|
|
150
|
-
error_color = Tng::UI::Theme.color(:error)
|
|
151
|
-
puts "#{error_icon} #{@pastel.decorate("Test generation failed:",
|
|
152
|
-
error_color)} An error occurred while generating tests. Please try again."
|
|
153
|
-
return
|
|
228
|
+
return { error: :generation_failed, message: status_data["error"] }
|
|
154
229
|
when "pending", "processing"
|
|
155
|
-
# Progress bar updates smoothly above
|
|
156
230
|
next
|
|
157
231
|
else
|
|
158
232
|
debug_log("Unknown test generation status: #{status}") if debug_enabled?
|
|
@@ -165,47 +239,6 @@ module Tng
|
|
|
165
239
|
end
|
|
166
240
|
end
|
|
167
241
|
|
|
168
|
-
def create_progress_bar
|
|
169
|
-
rocket_icon = Tng::UI::Theme.icon(:rocket)
|
|
170
|
-
success_color = Tng::UI::Theme.color(:success)
|
|
171
|
-
primary_color = Tng::UI::Theme.color(:primary)
|
|
172
|
-
muted_color = Tng::UI::Theme.color(:muted)
|
|
173
|
-
|
|
174
|
-
complete_char = @pastel.decorate("\u2593", success_color)
|
|
175
|
-
incomplete_char = @pastel.decorate("\u2591", muted_color)
|
|
176
|
-
head_char = @pastel.decorate("\u25B6", success_color)
|
|
177
|
-
|
|
178
|
-
TTY::ProgressBar.new(
|
|
179
|
-
"#{rocket_icon} #{@pastel.decorate("Generating tests",
|
|
180
|
-
primary_color)} #{@pastel.decorate("[:bar]",
|
|
181
|
-
:white)} #{@pastel.decorate(":status",
|
|
182
|
-
muted_color)} #{@pastel.decorate(
|
|
183
|
-
"(:elapsed)", muted_color
|
|
184
|
-
)}",
|
|
185
|
-
total: nil,
|
|
186
|
-
complete: complete_char,
|
|
187
|
-
incomplete: incomplete_char,
|
|
188
|
-
head: head_char,
|
|
189
|
-
width: 40,
|
|
190
|
-
clear: true,
|
|
191
|
-
frequency: 10, # Update 10 times per second for smooth animation
|
|
192
|
-
interval: 1 # Show elapsed time updates every second
|
|
193
|
-
)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def determine_status_text(seconds_elapsed)
|
|
197
|
-
case seconds_elapsed
|
|
198
|
-
when 0..15 then "initializing..."
|
|
199
|
-
when 16..45 then "analyzing code structure..."
|
|
200
|
-
when 46..90 then "generating test cases..."
|
|
201
|
-
when 91..150 then "optimizing test logic..."
|
|
202
|
-
when 151..210 then "refining assertions..."
|
|
203
|
-
when 211..270 then "formatting output..."
|
|
204
|
-
when 271..330 then "finalizing tests..."
|
|
205
|
-
else "completing generation..."
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
242
|
def trigger_cleanup(job_id)
|
|
210
243
|
@http_client.patch("#{CONTENT_RESPONSES_PATH}/#{job_id}/cleanup")
|
|
211
244
|
rescue StandardError => e
|
data/lib/tng/services/testng.rb
CHANGED
|
@@ -84,16 +84,10 @@ module Services
|
|
|
84
84
|
|
|
85
85
|
if response.status == 200
|
|
86
86
|
begin
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
stats_data
|
|
90
|
-
rescue JSON::ParserError => e
|
|
91
|
-
puts "❌ Failed to parse statistics response: #{e.message}"
|
|
87
|
+
JSON.parse(response.body)
|
|
88
|
+
rescue JSON::ParserError
|
|
92
89
|
nil
|
|
93
90
|
end
|
|
94
|
-
else
|
|
95
|
-
puts "❌ Failed to fetch statistics (#{response.status})"
|
|
96
|
-
nil
|
|
97
91
|
end
|
|
98
92
|
end
|
|
99
93
|
end
|