tina4ruby 3.12.13 → 3.13.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: 65d79f819e3ac62c1e8d3447409b8c75530c239db2bb6a7df9066de4104495ed
4
- data.tar.gz: d0b0af5dd0e914b880cc64a7b9aca29e6f46de65dcec3104b18e01ad174509b8
3
+ metadata.gz: bf3bb8bf3275f76f25951fdb75de731c304323b64cb2a495e4060ad7fcafdde1
4
+ data.tar.gz: a616d714a2a9beecfeb8d42a5b4124540d2abe07be8d9d2cd83ec5996596eb44
5
5
  SHA512:
6
- metadata.gz: 2e59eac2e37d78d1f7752cd26680338ff404794f6ff23dbe9ab2a1570ce3f458200fd5f488fefb8ef6f5f0b1764aa355def94dbed13a50149df30b4ea798059a
7
- data.tar.gz: 7021e0596f5275b4daf0969f0690400e99222fe10f30547b31fc8ee747f266c88086fb577927953813ac53b52dad9d8ce95a7ecb7ae48bfd2c1aa50fa8ff5e47
6
+ metadata.gz: be8986d4743a886d442d267add5953bff00a24fe85388b1947e57b1c9fa68447db08d63811b1f13d25cacb64eeea80b8e06fe366ec53961290c3125dbea69a1a
7
+ data.tar.gz: 535158f269ed6964d764e010efb319ac343991d9737a41bad8acbefe6eb8e8ff191694fb662a2df4cec684499c4b708ca924b6d80a7b1cfea306323eb697706b
data/lib/tina4/auth.rb CHANGED
@@ -109,19 +109,25 @@ module Tina4
109
109
  end
110
110
 
111
111
 
112
- def valid_token(token) # -> bool
112
+ # Verify a JWT signature + expiry.
113
+ #
114
+ # 3.13.0: return type changed from `Boolean` to `Hash | nil`. The
115
+ # decoded payload is returned on success, nil on failure. Matches
116
+ # firebase/jwt-ruby and Python's Auth.valid_token in 3.13.0.
117
+ #
118
+ # Legacy `if Tina4::Auth.valid_token(t)` patterns keep working
119
+ # because a non-empty Hash is truthy and nil is falsy.
120
+ def valid_token(token)
113
121
  if use_hmac?
114
- !hmac_decode(token, hmac_secret).nil?
122
+ hmac_decode(token, hmac_secret) # returns Hash payload or nil
115
123
  else
116
124
  ensure_keys
117
125
  require "jwt"
118
- JWT.decode(token, public_key, true, algorithm: "RS256")
119
- true
126
+ decoded = JWT.decode(token, public_key, true, algorithm: "RS256")
127
+ decoded[0] # firebase/jwt-ruby returns [payload, header]
120
128
  end
121
- rescue JWT::ExpiredSignature
122
- false
123
- rescue JWT::DecodeError
124
- false
129
+ rescue JWT::ExpiredSignature, JWT::DecodeError
130
+ nil
125
131
  end
126
132
 
127
133
  def valid_token_detail(token)
data/lib/tina4/test.rb ADDED
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Tina4 — The Intelligent Native Application 4ramework
4
+ # Copyright 2007 - current Tina4
5
+ # License: MIT https://opensource.org/licenses/MIT
6
+
7
+ module Tina4
8
+ # Tina4 xUnit-style test base class — class-based test suites with HTTP
9
+ # helpers and positional assertions. Zero external dependencies.
10
+ #
11
+ # Documentation chapter 18 has long described:
12
+ #
13
+ # class UserApiTest < Tina4::Test
14
+ # def test_health
15
+ # resp = get("/health")
16
+ # assert_equal_value(resp.status, 200)
17
+ # end
18
+ # end
19
+ #
20
+ # Until 3.13.0 this class did not exist — examples crashed with
21
+ # "uninitialized constant Tina4::Test". Ruby parity of the
22
+ # Python `tina4_python.test.Test` and PHP `Tina4\Test` classes.
23
+ #
24
+ # The class has a built-in runner (no Minitest/RSpec required):
25
+ #
26
+ # results = Tina4::Test.run_all # discovers all subclasses
27
+ # # => { passed: 12, failed: 0, errors: 0, details: [...] }
28
+ #
29
+ # Or run a single suite class:
30
+ #
31
+ # UserApiTest.run!
32
+ #
33
+ # HTTP helpers (get/post/put/patch/delete) delegate to TestClient.
34
+ # Positional assertions match the Python (actual, expected, message)
35
+ # shape used throughout the cross-framework docs.
36
+ class Test
37
+ # Class-level registry so run_all can discover every subclass without
38
+ # filesystem scanning.
39
+ @subclasses = []
40
+
41
+ class << self
42
+ attr_reader :subclasses
43
+
44
+ def inherited(subclass)
45
+ super
46
+ Test.subclasses << subclass
47
+ end
48
+
49
+ # Run every test method (`test_*`) on the calling subclass. Returns
50
+ # a hash with passed/failed/errors counts and per-test details.
51
+ def run!
52
+ instance_methods(false).grep(/\Atest_/).sort.each_with_object(
53
+ { passed: 0, failed: 0, errors: 0, details: [] }
54
+ ) do |method, results|
55
+ suite = new
56
+ begin
57
+ suite.send(:set_up)
58
+ suite.send(method)
59
+ suite.send(:tear_down)
60
+ results[:passed] += 1
61
+ results[:details] << { suite: name, test: method, status: "passed" }
62
+ rescue AssertionError => e
63
+ results[:failed] += 1
64
+ results[:details] << { suite: name, test: method, status: "failed", message: e.message }
65
+ rescue StandardError => e
66
+ results[:errors] += 1
67
+ results[:details] << { suite: name, test: method, status: "error", message: "#{e.class}: #{e.message}" }
68
+ end
69
+ end
70
+ end
71
+
72
+ # Discover and run every Tina4::Test subclass.
73
+ def run_all(quiet: false)
74
+ results = { passed: 0, failed: 0, errors: 0, details: [] }
75
+ Test.subclasses.each do |klass|
76
+ out = klass.run!
77
+ results[:passed] += out[:passed]
78
+ results[:failed] += out[:failed]
79
+ results[:errors] += out[:errors]
80
+ results[:details].concat(out[:details])
81
+ end
82
+ unless quiet
83
+ puts "Tina4 Test results: #{results[:passed]} passed, #{results[:failed]} failed, #{results[:errors]} errors"
84
+ end
85
+ results
86
+ end
87
+ end
88
+
89
+ # ── Lifecycle hooks ──────────────────────────────────────────────
90
+ # snake_case Tina4 idiom; override in subclasses.
91
+
92
+ def set_up
93
+ end
94
+
95
+ def tear_down
96
+ end
97
+
98
+ # ── HTTP test client (lazy) ───────────────────────────────────────
99
+
100
+ def test_client
101
+ @test_client ||= Tina4::TestClient.new
102
+ end
103
+
104
+ def get(path, headers: nil)
105
+ test_client.get(path, headers: headers)
106
+ end
107
+
108
+ def post(path, json: nil, body: nil, headers: nil)
109
+ test_client.post(path, json: json, body: body, headers: headers)
110
+ end
111
+
112
+ def put(path, json: nil, body: nil, headers: nil)
113
+ test_client.put(path, json: json, body: body, headers: headers)
114
+ end
115
+
116
+ def patch(path, json: nil, body: nil, headers: nil)
117
+ test_client.patch(path, json: json, body: body, headers: headers)
118
+ end
119
+
120
+ def delete(path, headers: nil)
121
+ test_client.delete(path, headers: headers)
122
+ end
123
+
124
+ # ── Positional assertions — (actual, expected, message) shape ─────
125
+
126
+ def assert_equal_value(actual, expected, message = nil)
127
+ return if actual == expected
128
+
129
+ raise AssertionError, message || "Expected #{expected.inspect}, got #{actual.inspect}"
130
+ end
131
+
132
+ def assert_not_equal_value(actual, expected, message = nil)
133
+ return unless actual == expected
134
+
135
+ raise AssertionError, message || "Expected #{actual.inspect} != #{expected.inspect}, but they are equal"
136
+ end
137
+
138
+ def assert_true(value, message = nil)
139
+ return if value
140
+
141
+ raise AssertionError, message || "Expected truthy, got #{value.inspect}"
142
+ end
143
+
144
+ def assert_false(value, message = nil)
145
+ return unless value
146
+
147
+ raise AssertionError, message || "Expected falsy, got #{value.inspect}"
148
+ end
149
+
150
+ def assert_nil_value(value, message = nil)
151
+ return if value.nil?
152
+
153
+ raise AssertionError, message || "Expected nil, got #{value.inspect}"
154
+ end
155
+
156
+ def assert_not_nil_value(value, message = nil)
157
+ return unless value.nil?
158
+
159
+ raise AssertionError, message || "Expected non-nil, got nil"
160
+ end
161
+
162
+ def assert_raises(expected_class, message = nil)
163
+ raise ArgumentError, "Block required" unless block_given?
164
+
165
+ begin
166
+ yield
167
+ rescue StandardError => e
168
+ return if e.is_a?(expected_class)
169
+
170
+ raise AssertionError,
171
+ message || "Expected #{expected_class}, got #{e.class}: #{e.message}"
172
+ end
173
+ raise AssertionError, message || "Expected #{expected_class} to be raised, but nothing was"
174
+ end
175
+ end
176
+
177
+ # AssertionError raised by Tina4::Test assertion helpers on failure.
178
+ class AssertionError < StandardError; end
179
+ end
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.12.13"
4
+ VERSION = "3.13.0"
5
5
  end
data/lib/tina4.rb CHANGED
@@ -54,6 +54,7 @@ require_relative "tina4/response_cache"
54
54
  require_relative "tina4/html_element"
55
55
  require_relative "tina4/error_overlay"
56
56
  require_relative "tina4/test_client"
57
+ require_relative "tina4/test"
57
58
  require_relative "tina4/docs"
58
59
  require_relative "tina4/mcp"
59
60
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.12.13
4
+ version: 3.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-29 00:00:00.000000000 Z
11
+ date: 2026-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -402,6 +402,7 @@ files:
402
402
  - lib/tina4/templates/errors/502.twig
403
403
  - lib/tina4/templates/errors/503.twig
404
404
  - lib/tina4/templates/errors/base.twig
405
+ - lib/tina4/test.rb
405
406
  - lib/tina4/test_client.rb
406
407
  - lib/tina4/testing.rb
407
408
  - lib/tina4/validator.rb