tep 0.11.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 +7 -0
- data/LICENSE +21 -0
- data/Makefile +134 -0
- data/README.md +247 -0
- data/SINATRA_COMPAT.md +376 -0
- data/bin/tep +2156 -0
- data/examples/agentic_chat/README.md +103 -0
- data/examples/agentic_chat/app.rb +310 -0
- data/examples/api_gateway/README.md +49 -0
- data/examples/api_gateway/app.rb +66 -0
- data/examples/blog/app.rb +367 -0
- data/examples/blog/views/index.erb +36 -0
- data/examples/blog/views/login.erb +28 -0
- data/examples/blog/views/new_post.erb +25 -0
- data/examples/blog/views/show.erb +16 -0
- data/examples/chat/app.rb +278 -0
- data/examples/chat/assets/logo.svg +13 -0
- data/examples/chat/assets/style.css +209 -0
- data/examples/chat/views/index.erb +142 -0
- data/examples/chatbot/README.md +111 -0
- data/examples/chatbot/app.rb +1024 -0
- data/examples/chatbot/assets/chat.js +249 -0
- data/examples/chatbot/assets/compare.js +93 -0
- data/examples/chatbot/assets/markdown.js +84 -0
- data/examples/chatbot/assets/style.css +215 -0
- data/examples/chatbot/schema.sql +25 -0
- data/examples/chatbot/views/compare.erb +43 -0
- data/examples/chatbot/views/index.erb +42 -0
- data/examples/chatbot/views/login.erb +22 -0
- data/examples/chatbot/views/setup.erb +23 -0
- data/examples/counter/README.md +68 -0
- data/examples/counter/app.rb +85 -0
- data/examples/experiments/AGENTS.md +91 -0
- data/examples/experiments/README.md +99 -0
- data/examples/experiments/app.rb +225 -0
- data/examples/geohash/Gemfile +11 -0
- data/examples/geohash/Gemfile.lock +17 -0
- data/examples/geohash/README.md +58 -0
- data/examples/geohash/app.rb +33 -0
- data/examples/hello.rb +120 -0
- data/examples/llm_gateway/README.md +73 -0
- data/examples/llm_gateway/app.rb +91 -0
- data/examples/maidenhead/Gemfile +7 -0
- data/examples/maidenhead/Gemfile.lock +17 -0
- data/examples/maidenhead/README.md +47 -0
- data/examples/maidenhead/app.rb +46 -0
- data/examples/pg_hello.rb +76 -0
- data/examples/qdrant/Gemfile +11 -0
- data/examples/qdrant/Gemfile.lock +29 -0
- data/examples/qdrant/README.md +54 -0
- data/examples/sinatra_style.rb +32 -0
- data/examples/websocket_echo.rb +37 -0
- data/lib/tep/agent_delegation.rb +35 -0
- data/lib/tep/app.rb +291 -0
- data/lib/tep/assets.rb +52 -0
- data/lib/tep/auth.rb +78 -0
- data/lib/tep/auth_bearer_token.rb +126 -0
- data/lib/tep/auth_oauth2.rb +189 -0
- data/lib/tep/auth_oauth2_client.rb +29 -0
- data/lib/tep/auth_oauth2_code.rb +40 -0
- data/lib/tep/auth_session_cookie.rb +132 -0
- data/lib/tep/broadcast.rb +265 -0
- data/lib/tep/broadcast_subscription.rb +42 -0
- data/lib/tep/cache.rb +49 -0
- data/lib/tep/events.rb +257 -0
- data/lib/tep/filter.rb +21 -0
- data/lib/tep/handler.rb +35 -0
- data/lib/tep/http.rb +599 -0
- data/lib/tep/identity.rb +67 -0
- data/lib/tep/job.rb +186 -0
- data/lib/tep/json.rb +572 -0
- data/lib/tep/jwt.rb +126 -0
- data/lib/tep/live_view.rb +219 -0
- data/lib/tep/llm.rb +505 -0
- data/lib/tep/logger.rb +85 -0
- data/lib/tep/mcp.rb +203 -0
- data/lib/tep/multipart.rb +98 -0
- data/lib/tep/net.rb +155 -0
- data/lib/tep/openai_server.rb +725 -0
- data/lib/tep/parallel.rb +168 -0
- data/lib/tep/parser.rb +81 -0
- data/lib/tep/password.rb +102 -0
- data/lib/tep/pg.rb +1128 -0
- data/lib/tep/presence.rb +589 -0
- data/lib/tep/presence_entry.rb +52 -0
- data/lib/tep/proxy.rb +801 -0
- data/lib/tep/request.rb +194 -0
- data/lib/tep/response.rb +134 -0
- data/lib/tep/router.rb +137 -0
- data/lib/tep/scheduler.rb +342 -0
- data/lib/tep/security.rb +140 -0
- data/lib/tep/server.rb +276 -0
- data/lib/tep/server_scheduled.rb +375 -0
- data/lib/tep/session.rb +98 -0
- data/lib/tep/shell.rb +62 -0
- data/lib/tep/sphttp.c +858 -0
- data/lib/tep/sqlite.rb +215 -0
- data/lib/tep/streamer.rb +31 -0
- data/lib/tep/tep_pg.c +769 -0
- data/lib/tep/tep_sqlite.c +320 -0
- data/lib/tep/url.rb +161 -0
- data/lib/tep/version.rb +3 -0
- data/lib/tep/websocket/connection.rb +171 -0
- data/lib/tep/websocket/driver.rb +169 -0
- data/lib/tep/websocket/frame.rb +238 -0
- data/lib/tep/websocket/handshake.rb +159 -0
- data/lib/tep/websocket.rb +68 -0
- data/lib/tep.rb +981 -0
- data/public/hello.txt +1 -0
- data/public/style.css +4 -0
- data/spinel-ext.json +33 -0
- data/test/helper.rb +248 -0
- data/test/real_world/01_simple.rb +5 -0
- data/test/real_world/02_lifecycle.rb +20 -0
- data/test/real_world/03_chat.rb +75 -0
- data/test/real_world/04_health_api.rb +25 -0
- data/test/real_world/05_todo_api.rb +57 -0
- data/test/real_world/06_basic_auth.rb +25 -0
- data/test/real_world/07_bbc_rest_api.rb +228 -0
- data/test/real_world/07_sklise_things.rb +109 -0
- data/test/real_world/08_jwd83_helloworld.rb +56 -0
- data/test/run_all.rb +7 -0
- data/test/run_parallel.rb +89 -0
- data/test/spinel_scheduled_burst_segv_repro.rb +33 -0
- data/test/test_api_gateway.rb +76 -0
- data/test/test_auth.rb +223 -0
- data/test/test_auth_oauth2.rb +208 -0
- data/test/test_auth_session_cookie.rb +198 -0
- data/test/test_broadcast.rb +197 -0
- data/test/test_broadcast_pg.rb +135 -0
- data/test/test_cache.rb +98 -0
- data/test/test_cache_static.rb +48 -0
- data/test/test_cookies.rb +52 -0
- data/test/test_erb.rb +53 -0
- data/test/test_erb_ivars.rb +58 -0
- data/test/test_events.rb +114 -0
- data/test/test_filters.rb +41 -0
- data/test/test_geohash_example.rb +89 -0
- data/test/test_http.rb +137 -0
- data/test/test_http_pool.rb +122 -0
- data/test/test_http_pool_send.rb +57 -0
- data/test/test_identity.rb +165 -0
- data/test/test_inbound_tls.rb +101 -0
- data/test/test_inbound_tls_scheduled.rb +101 -0
- data/test/test_job.rb +108 -0
- data/test/test_json.rb +168 -0
- data/test/test_jwt.rb +143 -0
- data/test/test_live_view.rb +324 -0
- data/test/test_llm.rb +250 -0
- data/test/test_llm_gateway.rb +95 -0
- data/test/test_logger.rb +101 -0
- data/test/test_maidenhead_example.rb +86 -0
- data/test/test_mcp.rb +264 -0
- data/test/test_misc_v02.rb +54 -0
- data/test/test_modular.rb +43 -0
- data/test/test_multi_filters.rb +40 -0
- data/test/test_mustache.rb +57 -0
- data/test/test_openai_server.rb +598 -0
- data/test/test_optional_segments.rb +45 -0
- data/test/test_parallel.rb +102 -0
- data/test/test_params.rb +99 -0
- data/test/test_pass.rb +42 -0
- data/test/test_password.rb +101 -0
- data/test/test_pg.rb +673 -0
- data/test/test_presence.rb +374 -0
- data/test/test_presence_pg.rb +309 -0
- data/test/test_proxy.rb +556 -0
- data/test/test_proxy_dsl.rb +119 -0
- data/test/test_proxy_streaming.rb +146 -0
- data/test/test_real_world.rb +397 -0
- data/test/test_regex_routes.rb +52 -0
- data/test/test_request_methods.rb +102 -0
- data/test/test_response.rb +123 -0
- data/test/test_routing.rb +109 -0
- data/test/test_scheduler.rb +153 -0
- data/test/test_security.rb +72 -0
- data/test/test_server_scheduled.rb +56 -0
- data/test/test_sessions.rb +59 -0
- data/test/test_shell.rb +54 -0
- data/test/test_sqlite.rb +148 -0
- data/test/test_sqlite_cached.rb +171 -0
- data/test/test_static.rb +57 -0
- data/test/test_streaming.rb +96 -0
- data/test/test_unsupported.rb +32 -0
- data/test/test_websocket.rb +152 -0
- data/test/test_websocket_echo.rb +138 -0
- data/test/views/greet.erb +5 -0
- data/test/views/hello.erb +5 -0
- data/test/views/list.erb +5 -0
- data/test/views/m_ivars.mustache +3 -0
- data/test/views/m_simple.mustache +4 -0
- data/test/views/mixed.erb +3 -0
- metadata +264 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# Tep::Parallel -- fork-based fan-out. Boots a tep app that runs a
|
|
4
|
+
# Worker against a small input list and checks both the
|
|
5
|
+
# result-collecting and fire-and-forget shapes.
|
|
6
|
+
class TestParallel < TepTest
|
|
7
|
+
|
|
8
|
+
app_source <<~RB
|
|
9
|
+
require 'sinatra'
|
|
10
|
+
|
|
11
|
+
class Doubler < Tep::ParallelWorker
|
|
12
|
+
def run(item)
|
|
13
|
+
# Item is a small integer-as-string; double it and emit the
|
|
14
|
+
# child's pid so the test can verify each result came from a
|
|
15
|
+
# distinct process.
|
|
16
|
+
n = item.to_i
|
|
17
|
+
(n * 2).to_s + ":" + Sock.sphttp_getpid.to_s
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Echoer < Tep::ParallelWorker
|
|
22
|
+
def run(item)
|
|
23
|
+
item
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Two workers so we can ensure the result order matches the
|
|
28
|
+
# input order even though forks complete out of order.
|
|
29
|
+
get '/map_doubled' do
|
|
30
|
+
items = ["1", "2", "3", "4"]
|
|
31
|
+
p = Tep::Parallel.new(Doubler.new)
|
|
32
|
+
results = p.map_processes(items)
|
|
33
|
+
out = ""
|
|
34
|
+
i = 0
|
|
35
|
+
while i < results.length
|
|
36
|
+
if out.length > 0
|
|
37
|
+
out = out + ","
|
|
38
|
+
end
|
|
39
|
+
out = out + results[i]
|
|
40
|
+
i += 1
|
|
41
|
+
end
|
|
42
|
+
out
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
get '/map_echo' do
|
|
46
|
+
items = ["alpha", "beta", "gamma"]
|
|
47
|
+
p = Tep::Parallel.new(Echoer.new)
|
|
48
|
+
results = p.map_processes(items)
|
|
49
|
+
results.join("|")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
get '/each' do
|
|
53
|
+
# Fire-and-forget: writes a sentinel file in each child;
|
|
54
|
+
# parent then asserts the files exist.
|
|
55
|
+
items = ["x", "y"]
|
|
56
|
+
p = Tep::Parallel.new(FileSentinel.new)
|
|
57
|
+
p.each_process(items)
|
|
58
|
+
ok = "yes"
|
|
59
|
+
if Tep::Shell.read("/tmp/tep_par_test_each_x").length == 0
|
|
60
|
+
ok = "missing_x"
|
|
61
|
+
end
|
|
62
|
+
if Tep::Shell.read("/tmp/tep_par_test_each_y").length == 0
|
|
63
|
+
ok = "missing_y"
|
|
64
|
+
end
|
|
65
|
+
Tep::Shell.run("rm -f /tmp/tep_par_test_each_x /tmp/tep_par_test_each_y")
|
|
66
|
+
ok
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class FileSentinel < Tep::ParallelWorker
|
|
70
|
+
def run(item)
|
|
71
|
+
File.write("/tmp/tep_par_test_each_" + item, "done")
|
|
72
|
+
""
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
RB
|
|
76
|
+
|
|
77
|
+
def test_map_processes_returns_ordered_results
|
|
78
|
+
res = get("/map_doubled")
|
|
79
|
+
assert_equal "200", res.code
|
|
80
|
+
body = res.body
|
|
81
|
+
parts = body.split(",")
|
|
82
|
+
assert_equal 4, parts.length
|
|
83
|
+
# Each entry: "doubled:pid". The doubled values must be 2,4,6,8.
|
|
84
|
+
doubled = parts.map { |s| s.split(":")[0] }
|
|
85
|
+
assert_equal %w[2 4 6 8], doubled
|
|
86
|
+
# The pids should all be distinct -- one process per item.
|
|
87
|
+
pids = parts.map { |s| s.split(":")[1] }
|
|
88
|
+
assert_equal pids.uniq.length, pids.length, "expected distinct child pids, got #{pids.inspect}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_map_processes_preserves_strings
|
|
92
|
+
res = get("/map_echo")
|
|
93
|
+
assert_equal "200", res.code
|
|
94
|
+
assert_equal "alpha|beta|gamma", res.body
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_each_process_runs_workers_for_side_effects
|
|
98
|
+
res = get("/each")
|
|
99
|
+
assert_equal "200", res.code
|
|
100
|
+
assert_equal "yes", res.body
|
|
101
|
+
end
|
|
102
|
+
end
|
data/test/test_params.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# Sinatra-style params: path captures, query string, form body merging.
|
|
4
|
+
class TestParams < TepTest
|
|
5
|
+
app_source <<~RB
|
|
6
|
+
get '/path/:a/:b' do
|
|
7
|
+
"" + params[:a] + "/" + params[:b]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
get '/q' do
|
|
11
|
+
"" + params[:foo]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
get '/multi' do
|
|
15
|
+
"" + params[:a] + "+" + params[:b]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
post '/form' do
|
|
19
|
+
"" + params[:name] + "=" + params[:age]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
post '/multipart' do
|
|
23
|
+
"" + params[:name] + "=" + params[:age]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
get '/encoded/:name' do
|
|
27
|
+
"" + params[:name]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
get '/missing' do
|
|
31
|
+
v = params[:nope]
|
|
32
|
+
v.length.to_s + ":" + v
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
get '/q-and-path/:id' do
|
|
36
|
+
"" + params[:id] + "+" + params[:tag]
|
|
37
|
+
end
|
|
38
|
+
RB
|
|
39
|
+
|
|
40
|
+
def test_path_capture_two
|
|
41
|
+
res = get("/path/foo/bar")
|
|
42
|
+
assert_equal "foo/bar", res.body
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_query_single
|
|
46
|
+
res = get("/q?foo=hello")
|
|
47
|
+
assert_equal "hello", res.body
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_query_multiple
|
|
51
|
+
res = get("/multi?a=1&b=2")
|
|
52
|
+
assert_equal "1+2", res.body
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_form_body
|
|
56
|
+
res = post("/form", "name=alice&age=30",
|
|
57
|
+
"Content-Type" => "application/x-www-form-urlencoded")
|
|
58
|
+
assert_equal "alice=30", res.body
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_multipart_body
|
|
62
|
+
# Browsers send multipart/form-data for any form using FormData
|
|
63
|
+
# or carrying a file input. The text fields land in req.params;
|
|
64
|
+
# file-upload parts are skipped in v1.
|
|
65
|
+
bnd = "----TepTestBoundary"
|
|
66
|
+
body = "--#{bnd}\r\n" \
|
|
67
|
+
"Content-Disposition: form-data; name=\"name\"\r\n" \
|
|
68
|
+
"\r\n" \
|
|
69
|
+
"alice\r\n" \
|
|
70
|
+
"--#{bnd}\r\n" \
|
|
71
|
+
"Content-Disposition: form-data; name=\"age\"\r\n" \
|
|
72
|
+
"\r\n" \
|
|
73
|
+
"30\r\n" \
|
|
74
|
+
"--#{bnd}--\r\n"
|
|
75
|
+
res = post("/multipart", body,
|
|
76
|
+
"Content-Type" => "multipart/form-data; boundary=#{bnd}")
|
|
77
|
+
assert_equal "alice=30", res.body
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_url_encoded_path
|
|
81
|
+
res = get("/encoded/hello%20world")
|
|
82
|
+
assert_equal "hello world", res.body
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_url_encoded_plus
|
|
86
|
+
res = get("/q?foo=hello+world")
|
|
87
|
+
assert_equal "hello world", res.body
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_missing_param_is_empty_string
|
|
91
|
+
res = get("/missing")
|
|
92
|
+
assert_equal "0:", res.body
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_query_overlays_path
|
|
96
|
+
res = get("/q-and-path/42?tag=ruby")
|
|
97
|
+
assert_equal "42+ruby", res.body
|
|
98
|
+
end
|
|
99
|
+
end
|
data/test/test_pass.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# `pass` skips to the next matching route. Sinatra raises
|
|
4
|
+
# Sinatra::Pass internally; tep just sets a flag and the dispatcher
|
|
5
|
+
# walks to the next match.
|
|
6
|
+
class TestPass < TepTest
|
|
7
|
+
app_source <<~RB
|
|
8
|
+
require 'sinatra'
|
|
9
|
+
|
|
10
|
+
# First definition wins, but it can pass for `/admin/special`.
|
|
11
|
+
get '/admin/:section' do
|
|
12
|
+
pass if params[:section] == "special"
|
|
13
|
+
"default admin: " + params[:section]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
get '/admin/special' do
|
|
17
|
+
"special admin handler"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# If every match passes, default 404 fires.
|
|
21
|
+
get '/skip' do
|
|
22
|
+
pass
|
|
23
|
+
end
|
|
24
|
+
RB
|
|
25
|
+
|
|
26
|
+
def test_pass_falls_through_to_next_match
|
|
27
|
+
res = get("/admin/special")
|
|
28
|
+
assert_equal "200", res.code
|
|
29
|
+
assert_equal "special admin handler", res.body
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_no_pass_returns_first_match
|
|
33
|
+
res = get("/admin/users")
|
|
34
|
+
assert_equal "200", res.code
|
|
35
|
+
assert_equal "default admin: users", res.body
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_pass_with_no_more_matches_404s
|
|
39
|
+
res = get("/skip")
|
|
40
|
+
assert_equal "404", res.code
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# Tep::Password -- PBKDF2-SHA256 password hashing.
|
|
4
|
+
class TestPassword < TepTest
|
|
5
|
+
app_source <<~RB
|
|
6
|
+
require 'sinatra'
|
|
7
|
+
|
|
8
|
+
post '/hash' do
|
|
9
|
+
res.headers["Content-Type"] = "text/plain"
|
|
10
|
+
pwd = Tep::Json.get_str(req.raw_body, "password")
|
|
11
|
+
Tep::Password.hash(pwd)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
post '/verify' do
|
|
15
|
+
res.headers["Content-Type"] = "text/plain"
|
|
16
|
+
pwd = Tep::Json.get_str(req.raw_body, "password")
|
|
17
|
+
hash = Tep::Json.get_str(req.raw_body, "hash")
|
|
18
|
+
Tep::Password.verify(pwd, hash) ? "ok" : "bad"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
post '/split' do
|
|
22
|
+
res.headers["Content-Type"] = "text/plain"
|
|
23
|
+
parts = Tep::Password.split4(req.raw_body)
|
|
24
|
+
parts[0] + "|" + parts[1] + "|" + parts[2] + "|" + parts[3]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
post '/random' do
|
|
28
|
+
res.headers["Content-Type"] = "text/plain"
|
|
29
|
+
Crypto.sp_crypto_random_b64url(16)
|
|
30
|
+
end
|
|
31
|
+
RB
|
|
32
|
+
|
|
33
|
+
def issue_hash(pwd)
|
|
34
|
+
post("/hash", %({"password":"#{pwd}"})).body.strip
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def verify_pwd(pwd, hash)
|
|
38
|
+
body = '{"password":"' + pwd + '","hash":"' + hash + '"}'
|
|
39
|
+
post("/verify", body).body.strip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_hash_format
|
|
43
|
+
h = issue_hash("hunter2")
|
|
44
|
+
# pbkdf2-sha256$<iters>$<salt>$<derived>
|
|
45
|
+
parts = h.split("$")
|
|
46
|
+
assert_equal 4, parts.length
|
|
47
|
+
assert_equal "pbkdf2-sha256", parts[0]
|
|
48
|
+
assert_equal "200000", parts[1]
|
|
49
|
+
assert parts[2].length > 0, "salt should be non-empty"
|
|
50
|
+
assert parts[3].length > 0, "derived should be non-empty"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_verify_good_password
|
|
54
|
+
h = issue_hash("hunter2")
|
|
55
|
+
assert_equal "ok", verify_pwd("hunter2", h)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_verify_wrong_password
|
|
59
|
+
h = issue_hash("hunter2")
|
|
60
|
+
assert_equal "bad", verify_pwd("not-the-password", h)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_random_salt_per_hash
|
|
64
|
+
h1 = issue_hash("same-password")
|
|
65
|
+
h2 = issue_hash("same-password")
|
|
66
|
+
refute_equal h1, h2, "two hashes of the same password should differ (random salt)"
|
|
67
|
+
# but BOTH must verify
|
|
68
|
+
assert_equal "ok", verify_pwd("same-password", h1)
|
|
69
|
+
assert_equal "ok", verify_pwd("same-password", h2)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_malformed_hash_returns_bad
|
|
73
|
+
assert_equal "bad", verify_pwd("anything", "not-a-real-hash")
|
|
74
|
+
assert_equal "bad", verify_pwd("anything", "pbkdf2-sha256$bad")
|
|
75
|
+
assert_equal "bad", verify_pwd("anything", "")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_random_b64url_distinct
|
|
79
|
+
r1 = post("/random", "").body.strip
|
|
80
|
+
r2 = post("/random", "").body.strip
|
|
81
|
+
refute_equal r1, r2
|
|
82
|
+
# 16 bytes -> 22 b64url chars (no padding).
|
|
83
|
+
assert_equal 22, r1.length
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_split4_basic
|
|
87
|
+
res = post("/split", "a$b$c$d")
|
|
88
|
+
assert_equal "a|b|c|d", res.body.strip
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_split4_with_empty_segments
|
|
92
|
+
res = post("/split", "$$c$d")
|
|
93
|
+
assert_equal "||c|d", res.body.strip
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_split4_short_input
|
|
97
|
+
# Fewer than 3 separators -- trailing slots stay "".
|
|
98
|
+
res = post("/split", "x$y")
|
|
99
|
+
assert_equal "x|y||", res.body.strip
|
|
100
|
+
end
|
|
101
|
+
end
|