tng 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.

Potentially problematic release.


This version of tng might be problematic. Click here for more details.

@@ -0,0 +1,102 @@
1
+ require "httpx"
2
+
3
+ module Tng
4
+ class HttpClient
5
+ def initialize(api_endpoint, api_key)
6
+ @api_endpoint = api_endpoint
7
+ @api_key = api_key
8
+ @timeout = {
9
+ connect_timeout: 300,
10
+ read_timeout: 300,
11
+ write_timeout: 300,
12
+ request_timeout: 300
13
+ }
14
+ end
15
+
16
+ def post(path, payload: {}, headers: {})
17
+ default_headers = {
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)
24
+
25
+ response = HTTPX.with(timeout: @timeout).post(
26
+ "#{@api_endpoint}/#{path}",
27
+ json: payload,
28
+ headers: merged_headers
29
+ )
30
+
31
+ debug_response("POST #{path}", response) if debug_enabled?
32
+ response
33
+ end
34
+
35
+ def post_binary(path, data, headers: {})
36
+ default_headers = {
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)
43
+
44
+ response = HTTPX.with(timeout: @timeout).post(
45
+ "#{@api_endpoint}/#{path}",
46
+ body: data,
47
+ headers: merged_headers
48
+ )
49
+
50
+ debug_response("POST #{path} (binary)", response) if debug_enabled?
51
+ response
52
+ end
53
+
54
+ def get(path, headers: {})
55
+ default_headers = {
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)
62
+
63
+ response = HTTPX.with(timeout: @timeout).get(
64
+ "#{@api_endpoint}/#{path}",
65
+ headers: merged_headers
66
+ )
67
+
68
+ debug_response("GET #{path}", response) if debug_enabled?
69
+ response
70
+ end
71
+
72
+ def ping
73
+ response = HTTPX.with(timeout: @timeout).get("#{@api_endpoint}/ping")
74
+ debug_response("GET /ping", response) if debug_enabled?
75
+ response
76
+ end
77
+
78
+ private
79
+
80
+ def debug_enabled?
81
+ ENV["DEBUG"] == "1"
82
+ end
83
+
84
+ def debug_response(request_info, response)
85
+ puts "\n -> DEBUG: #{request_info}"
86
+ puts " Status: #{response.status}"
87
+ puts " Headers: #{response.headers.to_h}"
88
+
89
+ if response.is_a?(HTTPX::ErrorResponse)
90
+ puts " Error: #{response.error&.message}"
91
+ else
92
+ body = response.body.to_s
93
+ if body.length > 500
94
+ puts " Body: #{body[0..500]}... (truncated, total length: #{body.length})"
95
+ else
96
+ puts " Body: #{body}"
97
+ end
98
+ end
99
+ puts " " + "─" * 50
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module Tng
6
+ class Railtie < Rails::Railtie
7
+ generators do
8
+ require_relative "../generators/tng/install_generator"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+ require "tng/utils"
5
+ require "tng/services/user_app_config"
6
+
7
+ module Tng
8
+ module Services
9
+ class TestGenerator
10
+ GENERATE_TESTS_PATH = "cli/tng_rails/contents/generate_tests"
11
+
12
+ def initialize(http_client)
13
+ @http_client = http_client
14
+ @machine_info = Tng.machine_info
15
+ end
16
+
17
+ def run_for_controller(controller)
18
+ payload = {
19
+ controller_test: Tng::Analyzers::Controller.read_test_file_for_controller(controller[:path]),
20
+ object_fixture_data: Tng::Utils.fixture_content,
21
+ machine_info: @machine_info,
22
+ ast: Tng::Analyzers::Controller.value_for_controller(controller[:path]),
23
+ test_type: "controller",
24
+ controller: controller,
25
+ routes: Tng::Analyzers::Controller.routes_for_controller(controller[:path]),
26
+ auth_patterns: Tng::Services::UserAppConfig.config_with_source,
27
+ model_info: Tng::Analyzers::Controller.model_info_for_controller(controller[:path]),
28
+ parents: Tng::Analyzers::Controller.parents_for_controller(controller)
29
+ }
30
+
31
+ send_request_and_save_test(payload)
32
+ end
33
+
34
+ def run_for_model(model)
35
+ payload = {
36
+ model_test: Tng::Analyzers::Model.read_test_file_for_model(model[:path]),
37
+ machine_info: @machine_info,
38
+ object_fixture_data: Tng::Utils.fixture_content,
39
+ ast: Tng::Analyzers::Model.value_for_model(model[:path]),
40
+ test_type: "model",
41
+ model: model,
42
+ auth_patterns: Tng::Services::UserAppConfig.config_with_source,
43
+ model_connections: Tng::Analyzers::Model.model_connections(model)
44
+ }
45
+
46
+ send_request_and_save_test(payload)
47
+ end
48
+
49
+ def run_for_service(service)
50
+ payload = {
51
+ service_test: Tng::Analyzers::Service.read_test_file_for_service(service[:path]),
52
+ machine_info: @machine_info,
53
+ object_fixture_data: Tng::Utils.fixture_content,
54
+ ast: Tng::Analyzers::Service.value_for_service(service[:path]),
55
+ test_type: "service",
56
+ service: service
57
+ }
58
+ send_request_and_save_test(payload)
59
+ end
60
+
61
+ def run_for_controller_method(controller, method_info)
62
+ payload = {
63
+ controller_test: Tng::Analyzers::Controller.read_test_file_for_controller(controller[:path]),
64
+ object_fixture_data: Tng::Utils.fixture_content,
65
+ machine_info: @machine_info,
66
+ ast: Tng::Analyzers::Controller.value_for_controller(controller[:path]),
67
+ test_type: "controller_method",
68
+ controller: controller,
69
+ method: method_info,
70
+ routes: Tng::Analyzers::Controller.routes_for_controller(controller[:path]),
71
+ auth_patterns: Tng::Services::UserAppConfig.config_with_source,
72
+ model_info: Tng::Analyzers::Controller.model_info_for_controller(controller[:path]),
73
+ parents: Tng::Analyzers::Controller.parents_for_controller(controller)
74
+ }
75
+
76
+ send_request_and_save_test(payload)
77
+ end
78
+
79
+ def run_for_model_method(model, method_info)
80
+ payload = {
81
+ model_test: Tng::Analyzers::Model.read_test_file_for_model(model[:path]),
82
+ machine_info: @machine_info,
83
+ object_fixture_data: Tng::Utils.fixture_content,
84
+ ast: Tng::Analyzers::Model.value_for_model(model[:path]),
85
+ test_type: "model_method",
86
+ model: model,
87
+ method: method_info,
88
+ auth_patterns: Tng::Services::UserAppConfig.config_with_source,
89
+ model_connections: Tng::Analyzers::Model.model_connections(model)
90
+ }
91
+
92
+ send_request_and_save_test(payload)
93
+ end
94
+
95
+ def run_for_service_method(service, method_info)
96
+ payload = {
97
+ service_test: Tng::Analyzers::Service.read_test_file_for_service(service[:path]),
98
+ machine_info: @machine_info,
99
+ object_fixture_data: Tng::Utils.fixture_content,
100
+ ast: Tng::Analyzers::Service.value_for_service(service[:path]),
101
+ test_type: "service_method",
102
+ service: service,
103
+ method: method_info,
104
+ auth_patterns: Tng::Services::UserAppConfig.config_with_source
105
+ }
106
+
107
+ send_request_and_save_test(payload)
108
+ end
109
+
110
+ private
111
+
112
+ def send_request_and_save_test(payload)
113
+ marshaled = Marshal.dump(payload)
114
+ compressed = Zlib::Deflate.deflate(marshaled)
115
+ response = @http_client.post_binary(GENERATE_TESTS_PATH, compressed)
116
+
117
+ if response.is_a?(HTTPX::ErrorResponse)
118
+ debug_log("Request failed with error response") if debug_enabled?
119
+ return handle_request_error(response)
120
+ end
121
+
122
+ debug_log("Request successful, status: #{response.status}") if debug_enabled?
123
+
124
+ test_content = response.body.to_s
125
+ if test_content.nil? || test_content.empty?
126
+ debug_log("Empty response body received") if debug_enabled?
127
+ return handle_empty_response
128
+ end
129
+
130
+ debug_log("Received test content, length: #{test_content.length}") if debug_enabled?
131
+ Tng::Utils.save_test_file(test_content)
132
+ rescue JSON::ParserError => e
133
+ debug_log("JSON parsing failed: #{e.message}") if debug_enabled?
134
+ puts "❌ Failed to parse generated tests via API: #{e.message}"
135
+ nil
136
+ end
137
+
138
+ def handle_request_error(response)
139
+ debug_log("Handling request error: #{response.error&.message}") if debug_enabled?
140
+ puts "❌ Request failed: #{response.error&.message}"
141
+ nil
142
+ end
143
+
144
+ def handle_empty_response
145
+ debug_log("Handling empty response") if debug_enabled?
146
+ puts "❌ Failed to generate test via API"
147
+ nil
148
+ end
149
+
150
+ def debug_enabled?
151
+ ENV["DEBUG"] == "1"
152
+ end
153
+
154
+ def debug_log(message)
155
+ puts "-> DEBUG [TestGenerator]: #{message}"
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,100 @@
1
+ module Services
2
+ class Testng
3
+ STATS_PATH = "cli/tng_rails/stats"
4
+
5
+ def initialize(http_client)
6
+ @http_client = http_client
7
+ end
8
+
9
+ def check_system_status
10
+ response = @http_client.ping
11
+
12
+ if response.is_a?(HTTPX::ErrorResponse)
13
+ return {
14
+ status: :error,
15
+ message: "Unable to connect to TNG service: #{response.error&.message}",
16
+ error_type: :connection_error
17
+ }
18
+ end
19
+
20
+ case response.status
21
+ when 200
22
+ begin
23
+ data = JSON.parse(response.body)
24
+ current_version = data["current_version"]
25
+ server_base_url = data["base_url"]
26
+ user_base_url = Tng::Services::UserAppConfig.base_url
27
+
28
+ if current_version > Tng::VERSION
29
+ return {
30
+ status: :version_mismatch,
31
+ message: "Version mismatch detected",
32
+ current_version: current_version,
33
+ gem_version: Tng::VERSION,
34
+ error_type: :version_mismatch
35
+ }
36
+ end
37
+
38
+ # Check for base URL mismatch
39
+ if server_base_url && user_base_url && server_base_url != user_base_url
40
+ return {
41
+ status: :base_url_mismatch,
42
+ message: "Base URL mismatch detected",
43
+ server_base_url: server_base_url,
44
+ user_base_url: user_base_url,
45
+ current_version: current_version,
46
+ gem_version: Tng::VERSION,
47
+ error_type: :base_url_mismatch
48
+ }
49
+ end
50
+
51
+ {
52
+ status: :ok,
53
+ message: data["message"] || "System is operational",
54
+ current_version: current_version,
55
+ gem_version: Tng::VERSION,
56
+ server_base_url: server_base_url
57
+ }
58
+ rescue JSON::ParserError => e
59
+ {
60
+ status: :error,
61
+ message: "Invalid response from server: #{e.message}",
62
+ error_type: :parse_error
63
+ }
64
+ end
65
+ else
66
+ {
67
+ status: :service_down,
68
+ message: "TestNG service is currently unavailable (HTTP #{response.status})",
69
+ error_type: :service_unavailable
70
+ }
71
+ end
72
+ end
73
+
74
+ def get_user_stats
75
+ headers = {
76
+ "Authorization" => "Bearer #{Tng::Services::UserAppConfig.api_key}",
77
+ "Content-Type" => "application/json",
78
+ "User-Agent" => "Tng/#{Tng::VERSION} Ruby/#{RUBY_VERSION}"
79
+ }
80
+
81
+ response = @http_client.get(STATS_PATH, headers: headers)
82
+
83
+ return nil if response.is_a?(HTTPX::ErrorResponse)
84
+
85
+ if response.status == 200
86
+ begin
87
+ stats_data = JSON.parse(response.body)
88
+ puts "✅ Statistics fetched successfully!"
89
+ stats_data
90
+ rescue JSON::ParserError => e
91
+ puts "❌ Failed to parse statistics response: #{e.message}"
92
+ nil
93
+ end
94
+ else
95
+ puts "❌ Failed to fetch statistics (#{response.status})"
96
+ nil
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tng
4
+ module Services
5
+ class UserAppConfig
6
+ def self.api_key
7
+ Tng.api_key
8
+ end
9
+
10
+ def self.base_url
11
+ Tng.base_url
12
+ end
13
+
14
+ def self.configured?
15
+ Tng.api_key && Tng.base_url
16
+ end
17
+
18
+ def self.missing_config
19
+ missing = []
20
+ missing << "API key" unless api_key
21
+ missing << "Base URL" unless base_url
22
+ missing
23
+ end
24
+
25
+ def self.config_with_source
26
+ auth_entry_points_with_source = []
27
+ methods = Tng.authentication_methods
28
+ methods.each do |method|
29
+ auth_entry_points_with_source << {
30
+ method: method[:method],
31
+ file: method[:file_location],
32
+ auth_type: method[:auth_type],
33
+ source: extract_method_source(method[:file_location], method[:method])
34
+ }
35
+ end
36
+ Tng.config[:authentication_entry_points_with_source] = auth_entry_points_with_source
37
+ Tng.config
38
+ end
39
+
40
+ def self.find_method_in_ast(node, method_name)
41
+ return nil unless node.is_a?(Prism::Node)
42
+
43
+ return node if node.is_a?(Prism::DefNode) && node.name == method_name.to_sym
44
+
45
+ node.child_nodes.each do |child|
46
+ result = find_method_in_ast(child, method_name)
47
+ return result if result
48
+ end
49
+
50
+ nil
51
+ end
52
+
53
+ def self.extract_method_source(file_path, method_name)
54
+ return nil unless file_path && method_name
55
+
56
+ full_path = Rails.root.join(file_path)
57
+ return nil unless File.exist?(full_path)
58
+
59
+ file_content = File.read(full_path)
60
+ result = Prism.parse(file_content)
61
+
62
+ method_node = find_method_in_ast(result.value, method_name)
63
+ return nil unless method_node
64
+
65
+ # Extract the method source from the original file content
66
+ start_line = method_node.location.start_line - 1 # Convert to 0-based index
67
+ end_line = method_node.location.end_line - 1
68
+
69
+ lines = file_content.lines
70
+ method_source = lines[start_line..end_line].join
71
+ method_source.strip
72
+ rescue StandardError
73
+ nil
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-box"
4
+ require "pastel"
5
+ require "tty-screen"
6
+ require_relative "theme"
7
+
8
+ class AboutDisplay
9
+ def initialize(pastel, prompt, version)
10
+ @pastel = pastel
11
+ @prompt = prompt
12
+ @version = version
13
+ @terminal_width = begin
14
+ TTY::Screen.width
15
+ rescue StandardError
16
+ 80
17
+ end
18
+ end
19
+
20
+ def display
21
+ about_content = [
22
+ @pastel.public_send(Tng::UI::Theme.color(:secondary)).bold("About Tng"),
23
+ "",
24
+ @pastel.public_send(Tng::UI::Theme.color(:primary),
25
+ "Tng is an LLM-powered test generation tool for Rails applications."),
26
+ "",
27
+ @pastel.public_send(Tng::UI::Theme.color(:accent), "Features:"),
28
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Controller test generation"),
29
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Model test generation"),
30
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Service test generation"),
31
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} LLM-powered test suggestions"),
32
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Test coverage analysis"),
33
+ "",
34
+ @pastel.public_send(Tng::UI::Theme.color(:accent), "Technology:"),
35
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
36
+ "#{Tng::UI::Theme.icon(:bullet)} Static code analysis with Prism"),
37
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
38
+ "#{Tng::UI::Theme.icon(:bullet)} Intelligent pattern recognition"),
39
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Rails-specific optimizations"),
40
+ "",
41
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "Version: #{@version}"),
42
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
43
+ "Built with #{Tng::UI::Theme.icon(:heart)} for Rails developers")
44
+ ].join("\n")
45
+
46
+ box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
47
+ style = Tng::UI::Theme.box_style(:default)
48
+
49
+ about_box = TTY::Box.frame(
50
+ title: { top_left: " About Tng " },
51
+ style: style[:style],
52
+ padding: style[:padding],
53
+ width: box_width
54
+ ) do
55
+ about_content
56
+ end
57
+
58
+ puts Tng::UI::Theme.center_box(about_box, box_width, @terminal_width)
59
+ @prompt.keypress(Tng::UI::Theme.center_text(
60
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
61
+ "Press any key to continue..."), @terminal_width
62
+ ))
63
+ end
64
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-box"
4
+ require "pastel"
5
+ require "tty-screen"
6
+ require_relative "theme"
7
+
8
+ class AuthenticationWarningDisplay
9
+ def initialize(pastel)
10
+ @pastel = pastel
11
+ @terminal_width = begin
12
+ TTY::Screen.width
13
+ rescue StandardError
14
+ 80
15
+ end
16
+ end
17
+
18
+ def display_if_missing
19
+ return false unless authentication_config_missing?
20
+
21
+ display_warning
22
+ handle_user_choice
23
+ end
24
+
25
+ def display_warning
26
+ warning_content = [
27
+ @pastel.public_send(Tng::UI::Theme.color(:error)).bold("Authentication Configuration Missing"),
28
+ "",
29
+ @pastel.public_send(Tng::UI::Theme.color(:primary), "TNG needs authentication setup to generate proper tests."),
30
+ "",
31
+ @pastel.public_send(Tng::UI::Theme.color(:accent), "Missing: ") + build_missing_items_summary,
32
+ "",
33
+ @pastel.public_send(Tng::UI::Theme.color(:secondary), "Quick fix:"),
34
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
35
+ "#{Tng::UI::Theme.icon(:bullet)} Edit config/initializers/tng.rb"),
36
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
37
+ "#{Tng::UI::Theme.icon(:bullet)} Uncomment authentication_methods array"),
38
+ @pastel.public_send(Tng::UI::Theme.color(:muted), "#{Tng::UI::Theme.icon(:bullet)} Add your auth method details"),
39
+ "",
40
+ @pastel.public_send(Tng::UI::Theme.color(:success), "Press Enter to continue anyway"),
41
+ @pastel.public_send(Tng::UI::Theme.color(:error), "Press Esc to cancel and fix configuration")
42
+ ].join("\n")
43
+
44
+ box_width = Tng::UI::Theme.calculate_box_width(@terminal_width)
45
+ style = Tng::UI::Theme.box_style(:warning)
46
+
47
+ warning_box = TTY::Box.frame(
48
+ title: { top_left: " Auth Warning " },
49
+ style: style[:style],
50
+ padding: style[:padding],
51
+ width: box_width
52
+ ) do
53
+ warning_content
54
+ end
55
+
56
+ puts Tng::UI::Theme.center_box(warning_box, box_width, @terminal_width)
57
+ puts
58
+ end
59
+
60
+ private
61
+
62
+ def handle_user_choice
63
+ require "io/console"
64
+
65
+ loop do
66
+ key = $stdin.getch
67
+ case key
68
+ when "\r", "\n" # Enter key
69
+ return false # Continue anyway
70
+ when "\e" # Escape key
71
+ return true # Cancel (authentication missing)
72
+ when "\u0003" # Ctrl+C
73
+ puts "\n#{@pastel.public_send(Tng::UI::Theme.color(:warning), "Operation cancelled.")}"
74
+ exit(0)
75
+ end
76
+ end
77
+ rescue StandardError
78
+ # Fallback if getch is not available
79
+ puts @pastel.public_send(Tng::UI::Theme.color(:muted), "Press Enter to continue or Ctrl+C to cancel...")
80
+ gets
81
+ false
82
+ end
83
+
84
+ def build_missing_items_summary
85
+ issues = []
86
+
87
+ issues << "empty auth methods" if Tng.authentication_enabled && authentication_methods_empty?
88
+
89
+ issues << "invalid auth config" if !authentication_methods_empty? && !authentication_methods_valid?
90
+
91
+ issues << "auth disabled" unless Tng.authentication_enabled
92
+
93
+ issues.empty? ? "unknown issue" : issues.join(", ")
94
+ end
95
+
96
+ def authentication_config_missing?
97
+ # Check if authentication is enabled but methods are not configured
98
+ return true if Tng.authentication_enabled && authentication_methods_empty?
99
+
100
+ # Check if authentication methods exist but are invalid
101
+ return true if !authentication_methods_empty? && !authentication_methods_valid?
102
+
103
+ false
104
+ end
105
+
106
+ def authentication_methods_empty?
107
+ methods = Tng.authentication_methods
108
+ methods.nil? || methods.empty?
109
+ end
110
+
111
+ def authentication_methods_valid?
112
+ methods = Tng.authentication_methods
113
+ return false if methods.nil? || methods.empty?
114
+
115
+ methods.all? do |method|
116
+ method.is_a?(Hash) &&
117
+ method.key?(:method) && !method[:method].to_s.strip.empty? &&
118
+ method.key?(:file_location) && !method[:file_location].to_s.strip.empty? &&
119
+ method.key?(:auth_type) && !method[:auth_type].to_s.strip.empty?
120
+ end
121
+ end
122
+
123
+ def build_missing_items_list
124
+ issues = []
125
+
126
+ if Tng.authentication_enabled && authentication_methods_empty?
127
+ issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods array is empty")
128
+ end
129
+
130
+ if !authentication_methods_empty? && !authentication_methods_valid?
131
+ issues << @pastel.public_send(Tng::UI::Theme.color(:error), " • authentication_methods contains invalid entries")
132
+
133
+ Tng.authentication_methods.each_with_index do |method, index|
134
+ next if method.is_a?(Hash)
135
+
136
+ issues << @pastel.public_send(Tng::UI::Theme.color(:muted), " - Entry #{index + 1}: not a valid hash")
137
+ next
138
+ end
139
+
140
+ Tng.authentication_methods.each_with_index do |method, index|
141
+ next unless method.is_a?(Hash)
142
+
143
+ if !method.key?(:method) || method[:method].to_s.strip.empty?
144
+ issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
145
+ " - Entry #{index + 1}: missing or empty 'method'")
146
+ end
147
+
148
+ if !method.key?(:file_location) || method[:file_location].to_s.strip.empty?
149
+ issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
150
+ " - Entry #{index + 1}: missing or empty 'file_location'")
151
+ end
152
+
153
+ if !method.key?(:auth_type) || method[:auth_type].to_s.strip.empty?
154
+ issues << @pastel.public_send(Tng::UI::Theme.color(:muted),
155
+ " - Entry #{index + 1}: missing or empty 'auth_type'")
156
+ end
157
+ end
158
+ end
159
+
160
+ unless Tng.authentication_enabled
161
+ issues << @pastel.public_send(Tng::UI::Theme.color(:warning),
162
+ " • authentication_enabled is set to false")
163
+ end
164
+
165
+ if issues.empty?
166
+ @pastel.public_send(Tng::UI::Theme.color(:muted),
167
+ " • Configuration appears valid")
168
+ else
169
+ issues.join("\n")
170
+ end
171
+ end
172
+ end