tina4ruby 3.11.13 → 3.11.15
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/CHANGELOG.md +80 -80
- data/LICENSE.txt +21 -21
- data/README.md +137 -137
- data/exe/tina4ruby +5 -5
- data/lib/tina4/ai.rb +696 -696
- data/lib/tina4/api.rb +189 -189
- data/lib/tina4/auth.rb +305 -305
- data/lib/tina4/auto_crud.rb +244 -244
- data/lib/tina4/cache.rb +154 -154
- data/lib/tina4/cli.rb +1449 -1449
- data/lib/tina4/constants.rb +46 -46
- data/lib/tina4/container.rb +74 -74
- data/lib/tina4/cors.rb +74 -74
- data/lib/tina4/crud.rb +692 -692
- data/lib/tina4/database/sqlite3_adapter.rb +165 -165
- data/lib/tina4/database.rb +625 -625
- data/lib/tina4/database_result.rb +208 -208
- data/lib/tina4/debug.rb +8 -8
- data/lib/tina4/dev.rb +14 -14
- data/lib/tina4/dev_admin.rb +935 -935
- data/lib/tina4/dev_mailbox.rb +191 -191
- data/lib/tina4/drivers/firebird_driver.rb +124 -110
- data/lib/tina4/drivers/mongodb_driver.rb +561 -561
- data/lib/tina4/drivers/mssql_driver.rb +112 -112
- data/lib/tina4/drivers/mysql_driver.rb +90 -90
- data/lib/tina4/drivers/odbc_driver.rb +191 -191
- data/lib/tina4/drivers/postgres_driver.rb +116 -106
- data/lib/tina4/drivers/sqlite_driver.rb +122 -122
- data/lib/tina4/env.rb +95 -95
- data/lib/tina4/error_overlay.rb +252 -252
- data/lib/tina4/events.rb +109 -109
- data/lib/tina4/field_types.rb +154 -154
- data/lib/tina4/frond.rb +2025 -2025
- data/lib/tina4/gallery/auth/meta.json +1 -1
- data/lib/tina4/gallery/auth/src/routes/api/gallery_auth.rb +114 -114
- data/lib/tina4/gallery/database/meta.json +1 -1
- data/lib/tina4/gallery/database/src/routes/api/gallery_db.rb +43 -43
- data/lib/tina4/gallery/error-overlay/meta.json +1 -1
- data/lib/tina4/gallery/error-overlay/src/routes/api/gallery_crash.rb +17 -17
- data/lib/tina4/gallery/orm/meta.json +1 -1
- data/lib/tina4/gallery/orm/src/routes/api/gallery_products.rb +16 -16
- data/lib/tina4/gallery/queue/meta.json +1 -1
- data/lib/tina4/gallery/queue/src/routes/api/gallery_queue.rb +325 -325
- data/lib/tina4/gallery/rest-api/meta.json +1 -1
- data/lib/tina4/gallery/rest-api/src/routes/api/gallery_hello.rb +14 -14
- data/lib/tina4/gallery/templates/meta.json +1 -1
- data/lib/tina4/gallery/templates/src/routes/gallery_page.rb +12 -12
- data/lib/tina4/gallery/templates/src/templates/gallery_page.twig +257 -257
- data/lib/tina4/graphql.rb +966 -966
- data/lib/tina4/health.rb +39 -39
- data/lib/tina4/html_element.rb +170 -170
- data/lib/tina4/job.rb +80 -80
- data/lib/tina4/localization.rb +168 -168
- data/lib/tina4/log.rb +203 -203
- data/lib/tina4/mcp.rb +696 -696
- data/lib/tina4/messenger.rb +587 -587
- data/lib/tina4/metrics.rb +793 -793
- data/lib/tina4/middleware.rb +445 -445
- data/lib/tina4/migration.rb +451 -451
- data/lib/tina4/orm.rb +790 -790
- data/lib/tina4/public/css/tina4.css +2463 -2463
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/public/images/logo.svg +5 -5
- data/lib/tina4/public/js/frond.min.js +2 -2
- data/lib/tina4/public/js/tina4-dev-admin.js +565 -565
- data/lib/tina4/public/js/tina4-dev-admin.min.js +480 -480
- data/lib/tina4/public/js/tina4.min.js +92 -92
- data/lib/tina4/public/js/tina4js.min.js +48 -48
- data/lib/tina4/public/swagger/index.html +90 -90
- data/lib/tina4/public/swagger/oauth2-redirect.html +63 -63
- data/lib/tina4/query_builder.rb +380 -380
- data/lib/tina4/queue.rb +366 -366
- data/lib/tina4/queue_backends/kafka_backend.rb +80 -80
- data/lib/tina4/queue_backends/lite_backend.rb +298 -298
- data/lib/tina4/queue_backends/mongo_backend.rb +126 -126
- data/lib/tina4/queue_backends/rabbitmq_backend.rb +73 -73
- data/lib/tina4/rack_app.rb +817 -817
- data/lib/tina4/rate_limiter.rb +130 -130
- data/lib/tina4/request.rb +268 -255
- data/lib/tina4/response.rb +346 -346
- data/lib/tina4/response_cache.rb +551 -551
- data/lib/tina4/router.rb +406 -406
- data/lib/tina4/scss/tina4css/_alerts.scss +34 -34
- data/lib/tina4/scss/tina4css/_badges.scss +22 -22
- data/lib/tina4/scss/tina4css/_buttons.scss +69 -69
- data/lib/tina4/scss/tina4css/_cards.scss +49 -49
- data/lib/tina4/scss/tina4css/_forms.scss +156 -156
- data/lib/tina4/scss/tina4css/_grid.scss +81 -81
- data/lib/tina4/scss/tina4css/_modals.scss +84 -84
- data/lib/tina4/scss/tina4css/_nav.scss +149 -149
- data/lib/tina4/scss/tina4css/_reset.scss +94 -94
- data/lib/tina4/scss/tina4css/_tables.scss +54 -54
- data/lib/tina4/scss/tina4css/_typography.scss +55 -55
- data/lib/tina4/scss/tina4css/_utilities.scss +197 -197
- data/lib/tina4/scss/tina4css/_variables.scss +117 -117
- data/lib/tina4/scss/tina4css/base.scss +1 -1
- data/lib/tina4/scss/tina4css/colors.scss +48 -48
- data/lib/tina4/scss/tina4css/tina4.scss +17 -17
- data/lib/tina4/scss_compiler.rb +178 -178
- data/lib/tina4/seeder.rb +567 -567
- data/lib/tina4/service_runner.rb +303 -303
- data/lib/tina4/session.rb +297 -297
- data/lib/tina4/session_handlers/database_handler.rb +72 -72
- data/lib/tina4/session_handlers/file_handler.rb +67 -67
- data/lib/tina4/session_handlers/mongo_handler.rb +49 -49
- data/lib/tina4/session_handlers/redis_handler.rb +43 -43
- data/lib/tina4/session_handlers/valkey_handler.rb +43 -43
- data/lib/tina4/shutdown.rb +84 -84
- data/lib/tina4/sql_translation.rb +158 -158
- data/lib/tina4/swagger.rb +124 -124
- data/lib/tina4/template.rb +894 -894
- data/lib/tina4/templates/base.twig +26 -26
- data/lib/tina4/templates/errors/302.twig +14 -14
- data/lib/tina4/templates/errors/401.twig +9 -9
- data/lib/tina4/templates/errors/403.twig +29 -29
- data/lib/tina4/templates/errors/404.twig +29 -29
- data/lib/tina4/templates/errors/500.twig +38 -38
- data/lib/tina4/templates/errors/502.twig +9 -9
- data/lib/tina4/templates/errors/503.twig +12 -12
- data/lib/tina4/templates/errors/base.twig +37 -37
- data/lib/tina4/test_client.rb +159 -159
- data/lib/tina4/testing.rb +340 -340
- data/lib/tina4/validator.rb +174 -174
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4/webserver.rb +312 -312
- data/lib/tina4/websocket.rb +343 -343
- data/lib/tina4/websocket_backplane.rb +190 -190
- data/lib/tina4/wsdl.rb +564 -564
- data/lib/tina4.rb +458 -458
- data/lib/tina4ruby.rb +4 -4
- metadata +3 -3
data/lib/tina4/test_client.rb
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
# Tina4 Test Client — Test routes without starting a server.
|
|
2
|
-
#
|
|
3
|
-
# Usage:
|
|
4
|
-
#
|
|
5
|
-
# client = Tina4::TestClient.new
|
|
6
|
-
#
|
|
7
|
-
# response = client.get("/api/users")
|
|
8
|
-
# assert_equal 200, response.status
|
|
9
|
-
# assert response.json["users"]
|
|
10
|
-
#
|
|
11
|
-
# response = client.post("/api/users", json: { name: "Alice" })
|
|
12
|
-
# assert_equal 201, response.status
|
|
13
|
-
#
|
|
14
|
-
# response = client.get("/api/users/1", headers: { "Authorization" => "Bearer token123" })
|
|
15
|
-
#
|
|
16
|
-
module Tina4
|
|
17
|
-
class TestResponse
|
|
18
|
-
attr_reader :status, :body, :headers, :content_type
|
|
19
|
-
|
|
20
|
-
# Build from a Rack response tuple [status, headers, body_array]
|
|
21
|
-
def initialize(rack_response)
|
|
22
|
-
@status = rack_response[0]
|
|
23
|
-
@headers = rack_response[1] || {}
|
|
24
|
-
@content_type = @headers["content-type"] || ""
|
|
25
|
-
raw_body = rack_response[2]
|
|
26
|
-
@body = raw_body.is_a?(Array) ? raw_body.join : raw_body.to_s
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Parse body as JSON.
|
|
30
|
-
def json
|
|
31
|
-
return nil if @body.nil? || @body.empty?
|
|
32
|
-
JSON.parse(@body)
|
|
33
|
-
rescue JSON::ParserError
|
|
34
|
-
nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Return body as a string.
|
|
38
|
-
def text
|
|
39
|
-
@body.to_s
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def inspect
|
|
43
|
-
"<TestResponse status=#{@status} content_type=#{@content_type.inspect}>"
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
class TestClient
|
|
48
|
-
# Send a GET request.
|
|
49
|
-
def get(path, headers: nil)
|
|
50
|
-
request("GET", path, headers: headers)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Send a POST request.
|
|
54
|
-
def post(path, json: nil, body: nil, headers: nil)
|
|
55
|
-
request("POST", path, json: json, body: body, headers: headers)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Send a PUT request.
|
|
59
|
-
def put(path, json: nil, body: nil, headers: nil)
|
|
60
|
-
request("PUT", path, json: json, body: body, headers: headers)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Send a PATCH request.
|
|
64
|
-
def patch(path, json: nil, body: nil, headers: nil)
|
|
65
|
-
request("PATCH", path, json: json, body: body, headers: headers)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Send a DELETE request.
|
|
69
|
-
def delete(path, headers: nil)
|
|
70
|
-
request("DELETE", path, headers: headers)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
private
|
|
74
|
-
|
|
75
|
-
# Build a mock Rack env, match the route, execute the handler.
|
|
76
|
-
def request(method, path, json: nil, body: nil, headers: nil)
|
|
77
|
-
# Build raw body
|
|
78
|
-
raw_body = ""
|
|
79
|
-
content_type = ""
|
|
80
|
-
|
|
81
|
-
if json
|
|
82
|
-
raw_body = JSON.generate(json)
|
|
83
|
-
content_type = "application/json"
|
|
84
|
-
elsif body
|
|
85
|
-
raw_body = body.to_s
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Split path and query string
|
|
89
|
-
clean_path, query_string = path.include?("?") ? path.split("?", 2) : [path, ""]
|
|
90
|
-
|
|
91
|
-
# Build Rack env hash
|
|
92
|
-
env = {
|
|
93
|
-
"REQUEST_METHOD" => method.upcase,
|
|
94
|
-
"PATH_INFO" => clean_path,
|
|
95
|
-
"QUERY_STRING" => query_string || "",
|
|
96
|
-
"SERVER_NAME" => "localhost",
|
|
97
|
-
"SERVER_PORT" => "7145",
|
|
98
|
-
"HTTP_HOST" => "localhost:7145",
|
|
99
|
-
"REMOTE_ADDR" => "127.0.0.1",
|
|
100
|
-
"rack.input" => StringIO.new(raw_body),
|
|
101
|
-
"rack.url_scheme" => "http"
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
# Add content type
|
|
105
|
-
env["CONTENT_TYPE"] = content_type unless content_type.empty?
|
|
106
|
-
env["CONTENT_LENGTH"] = raw_body.bytesize.to_s unless raw_body.empty?
|
|
107
|
-
|
|
108
|
-
# Add custom headers (convert to Rack format: X-Custom → HTTP_X_CUSTOM)
|
|
109
|
-
if headers
|
|
110
|
-
headers.each do |key, value|
|
|
111
|
-
rack_key = "HTTP_#{key.upcase.tr('-', '_')}"
|
|
112
|
-
env[rack_key] = value
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Match route
|
|
117
|
-
result = Tina4::Router.match(method.upcase, clean_path)
|
|
118
|
-
|
|
119
|
-
unless result
|
|
120
|
-
return TestResponse.new([404, { "content-type" => "application/json" }, ['{"error":"Not found"}']])
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
route, path_params = result
|
|
124
|
-
|
|
125
|
-
# Create request and response
|
|
126
|
-
req = Tina4::Request.new(env, path_params || {})
|
|
127
|
-
res = Tina4::Response.new
|
|
128
|
-
|
|
129
|
-
# Build handler args (same logic as RackApp.handle_route)
|
|
130
|
-
handler_params = route.handler.parameters.map(&:last)
|
|
131
|
-
route_params = path_params || {}
|
|
132
|
-
args = handler_params.map do |name|
|
|
133
|
-
if route_params.key?(name)
|
|
134
|
-
route_params[name]
|
|
135
|
-
elsif name == :request || name == :req
|
|
136
|
-
req
|
|
137
|
-
else
|
|
138
|
-
res
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Execute handler
|
|
143
|
-
handler_result = args.empty? ? route.handler.call : route.handler.call(*args)
|
|
144
|
-
|
|
145
|
-
# Auto-detect response type
|
|
146
|
-
if handler_result.is_a?(Tina4::Response)
|
|
147
|
-
final = handler_result
|
|
148
|
-
elsif route.respond_to?(:template) && route.template && handler_result.is_a?(Hash)
|
|
149
|
-
html = Tina4::Template.render(route.template, handler_result)
|
|
150
|
-
res.html(html)
|
|
151
|
-
final = res
|
|
152
|
-
else
|
|
153
|
-
final = Tina4::Response.auto_detect(handler_result, res)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
TestResponse.new(final.to_rack)
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
end
|
|
1
|
+
# Tina4 Test Client — Test routes without starting a server.
|
|
2
|
+
#
|
|
3
|
+
# Usage:
|
|
4
|
+
#
|
|
5
|
+
# client = Tina4::TestClient.new
|
|
6
|
+
#
|
|
7
|
+
# response = client.get("/api/users")
|
|
8
|
+
# assert_equal 200, response.status
|
|
9
|
+
# assert response.json["users"]
|
|
10
|
+
#
|
|
11
|
+
# response = client.post("/api/users", json: { name: "Alice" })
|
|
12
|
+
# assert_equal 201, response.status
|
|
13
|
+
#
|
|
14
|
+
# response = client.get("/api/users/1", headers: { "Authorization" => "Bearer token123" })
|
|
15
|
+
#
|
|
16
|
+
module Tina4
|
|
17
|
+
class TestResponse
|
|
18
|
+
attr_reader :status, :body, :headers, :content_type
|
|
19
|
+
|
|
20
|
+
# Build from a Rack response tuple [status, headers, body_array]
|
|
21
|
+
def initialize(rack_response)
|
|
22
|
+
@status = rack_response[0]
|
|
23
|
+
@headers = rack_response[1] || {}
|
|
24
|
+
@content_type = @headers["content-type"] || ""
|
|
25
|
+
raw_body = rack_response[2]
|
|
26
|
+
@body = raw_body.is_a?(Array) ? raw_body.join : raw_body.to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Parse body as JSON.
|
|
30
|
+
def json
|
|
31
|
+
return nil if @body.nil? || @body.empty?
|
|
32
|
+
JSON.parse(@body)
|
|
33
|
+
rescue JSON::ParserError
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Return body as a string.
|
|
38
|
+
def text
|
|
39
|
+
@body.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
"<TestResponse status=#{@status} content_type=#{@content_type.inspect}>"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class TestClient
|
|
48
|
+
# Send a GET request.
|
|
49
|
+
def get(path, headers: nil)
|
|
50
|
+
request("GET", path, headers: headers)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Send a POST request.
|
|
54
|
+
def post(path, json: nil, body: nil, headers: nil)
|
|
55
|
+
request("POST", path, json: json, body: body, headers: headers)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Send a PUT request.
|
|
59
|
+
def put(path, json: nil, body: nil, headers: nil)
|
|
60
|
+
request("PUT", path, json: json, body: body, headers: headers)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Send a PATCH request.
|
|
64
|
+
def patch(path, json: nil, body: nil, headers: nil)
|
|
65
|
+
request("PATCH", path, json: json, body: body, headers: headers)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Send a DELETE request.
|
|
69
|
+
def delete(path, headers: nil)
|
|
70
|
+
request("DELETE", path, headers: headers)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Build a mock Rack env, match the route, execute the handler.
|
|
76
|
+
def request(method, path, json: nil, body: nil, headers: nil)
|
|
77
|
+
# Build raw body
|
|
78
|
+
raw_body = ""
|
|
79
|
+
content_type = ""
|
|
80
|
+
|
|
81
|
+
if json
|
|
82
|
+
raw_body = JSON.generate(json)
|
|
83
|
+
content_type = "application/json"
|
|
84
|
+
elsif body
|
|
85
|
+
raw_body = body.to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Split path and query string
|
|
89
|
+
clean_path, query_string = path.include?("?") ? path.split("?", 2) : [path, ""]
|
|
90
|
+
|
|
91
|
+
# Build Rack env hash
|
|
92
|
+
env = {
|
|
93
|
+
"REQUEST_METHOD" => method.upcase,
|
|
94
|
+
"PATH_INFO" => clean_path,
|
|
95
|
+
"QUERY_STRING" => query_string || "",
|
|
96
|
+
"SERVER_NAME" => "localhost",
|
|
97
|
+
"SERVER_PORT" => "7145",
|
|
98
|
+
"HTTP_HOST" => "localhost:7145",
|
|
99
|
+
"REMOTE_ADDR" => "127.0.0.1",
|
|
100
|
+
"rack.input" => StringIO.new(raw_body),
|
|
101
|
+
"rack.url_scheme" => "http"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Add content type
|
|
105
|
+
env["CONTENT_TYPE"] = content_type unless content_type.empty?
|
|
106
|
+
env["CONTENT_LENGTH"] = raw_body.bytesize.to_s unless raw_body.empty?
|
|
107
|
+
|
|
108
|
+
# Add custom headers (convert to Rack format: X-Custom → HTTP_X_CUSTOM)
|
|
109
|
+
if headers
|
|
110
|
+
headers.each do |key, value|
|
|
111
|
+
rack_key = "HTTP_#{key.upcase.tr('-', '_')}"
|
|
112
|
+
env[rack_key] = value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Match route
|
|
117
|
+
result = Tina4::Router.match(method.upcase, clean_path)
|
|
118
|
+
|
|
119
|
+
unless result
|
|
120
|
+
return TestResponse.new([404, { "content-type" => "application/json" }, ['{"error":"Not found"}']])
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
route, path_params = result
|
|
124
|
+
|
|
125
|
+
# Create request and response
|
|
126
|
+
req = Tina4::Request.new(env, path_params || {})
|
|
127
|
+
res = Tina4::Response.new
|
|
128
|
+
|
|
129
|
+
# Build handler args (same logic as RackApp.handle_route)
|
|
130
|
+
handler_params = route.handler.parameters.map(&:last)
|
|
131
|
+
route_params = path_params || {}
|
|
132
|
+
args = handler_params.map do |name|
|
|
133
|
+
if route_params.key?(name)
|
|
134
|
+
route_params[name]
|
|
135
|
+
elsif name == :request || name == :req
|
|
136
|
+
req
|
|
137
|
+
else
|
|
138
|
+
res
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Execute handler
|
|
143
|
+
handler_result = args.empty? ? route.handler.call : route.handler.call(*args)
|
|
144
|
+
|
|
145
|
+
# Auto-detect response type
|
|
146
|
+
if handler_result.is_a?(Tina4::Response)
|
|
147
|
+
final = handler_result
|
|
148
|
+
elsif route.respond_to?(:template) && route.template && handler_result.is_a?(Hash)
|
|
149
|
+
html = Tina4::Template.render(route.template, handler_result)
|
|
150
|
+
res.html(html)
|
|
151
|
+
final = res
|
|
152
|
+
else
|
|
153
|
+
final = Tina4::Response.auto_detect(handler_result, res)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
TestResponse.new(final.to_rack)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|