kiosk-test-support 0.5.20
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/CHANGELOG.md +149 -0
- data/LICENSE.txt +202 -0
- data/README.md +146 -0
- data/lib/kiosk/story_spec.rb +6 -0
- data/lib/kiosk/story_test.rb +16 -0
- data/lib/kiosk/test_helpers/assistant/events.rb +135 -0
- data/lib/kiosk/test_helpers/assistant.rb +151 -0
- data/lib/kiosk/test_helpers/conformance/checks/declared_shape.rb +91 -0
- data/lib/kiosk/test_helpers/conformance/checks/executes.rb +57 -0
- data/lib/kiosk/test_helpers/conformance/checks/helpers.rb +112 -0
- data/lib/kiosk/test_helpers/conformance/checks/principal_scope.rb +104 -0
- data/lib/kiosk/test_helpers/conformance/checks/routes.rb +94 -0
- data/lib/kiosk/test_helpers/conformance/checks.rb +45 -0
- data/lib/kiosk/test_helpers/conformance/in_process_origin.rb +121 -0
- data/lib/kiosk/test_helpers/conformance/minitest.rb +57 -0
- data/lib/kiosk/test_helpers/conformance/null_origin.rb +55 -0
- data/lib/kiosk/test_helpers/conformance/outcome.rb +25 -0
- data/lib/kiosk/test_helpers/conformance/rspec.rb +100 -0
- data/lib/kiosk/test_helpers/conformance/verb.rb +36 -0
- data/lib/kiosk/test_helpers/conformance.rb +46 -0
- data/lib/kiosk/test_helpers/customer.rb +154 -0
- data/lib/kiosk/test_helpers/descriptor_examples.rb +46 -0
- data/lib/kiosk/test_helpers/errors.rb +55 -0
- data/lib/kiosk/test_helpers/in_process_executor.rb +198 -0
- data/lib/kiosk/test_helpers/in_process_wire.rb +90 -0
- data/lib/kiosk/test_helpers/journey.rb +98 -0
- data/lib/kiosk/test_helpers/kyc.rb +98 -0
- data/lib/kiosk/test_helpers/live_server.rb +56 -0
- data/lib/kiosk/test_helpers/null_executor.rb +95 -0
- data/lib/kiosk/test_helpers/person.rb +52 -0
- data/lib/kiosk/test_helpers/small_equihash.rb +18 -0
- data/lib/kiosk/test_helpers/story.rb +36 -0
- data/lib/kiosk/test_helpers/stripe_mock.rb +146 -0
- data/lib/kiosk/test_helpers/version.rb +7 -0
- data/lib/kiosk/test_helpers/wire.rb +124 -0
- data/lib/kiosk/test_helpers.rb +28 -0
- metadata +205 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk/user_identity_providers/devise_session'
|
|
4
|
+
require 'kiosk/test_helpers/customer'
|
|
5
|
+
|
|
6
|
+
module Kiosk
|
|
7
|
+
module TestHelpers
|
|
8
|
+
# Someone signed in on the operator's own site: links assistants to their
|
|
9
|
+
# account, approves the code an assistant shows them, and unlinks one.
|
|
10
|
+
#
|
|
11
|
+
# alice = a_person(email: "alice@example.com", password: "…")
|
|
12
|
+
# diner = alice.links(a_newcomer(as: Diner))
|
|
13
|
+
class Person
|
|
14
|
+
# The signed-in browser session, for the operator's own pages.
|
|
15
|
+
attr_reader :site
|
|
16
|
+
|
|
17
|
+
def initialize(origin, email:, password:)
|
|
18
|
+
@origin = origin
|
|
19
|
+
@site = UserIdentityProviders::DeviseSession.new(origin).sign_in!(email:, password:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A one-time code for an assistant to redeem.
|
|
23
|
+
def link_code
|
|
24
|
+
status, link = site.post_json('/kiosk/auth/link', {}, { session: true })
|
|
25
|
+
raise "the site minted no link code: #{status} #{link}" unless status == 201
|
|
26
|
+
|
|
27
|
+
link.fetch('link_code')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# `customer` redeems a fresh link code, and comes back acting for this person.
|
|
31
|
+
def links(customer) = customer.redeems(link_code)
|
|
32
|
+
|
|
33
|
+
# A newly keyed assistant, linked to this person's account.
|
|
34
|
+
def links_a_newcomer = links(Customer.newcomer(Assistant.new(base_url: @origin)))
|
|
35
|
+
|
|
36
|
+
# Approves the code an assistant showed, on the site's verification page.
|
|
37
|
+
def approves(user_code)
|
|
38
|
+
page = site.get_html("/kiosk/oauth/device/verify?user_code=#{user_code}")
|
|
39
|
+
approved = site.post_form('/kiosk/oauth/device/verify', 'user_code' => user_code, 'decision' => 'approve',
|
|
40
|
+
'authenticity_token' => site.csrf_token(page.body))
|
|
41
|
+
raise "the site did not approve #{user_code}: #{page.code}, #{approved.code}" unless [page.code, approved.code] == %w[200 200]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unlinks(customer)
|
|
45
|
+
status, = site.post_json('/kiosk/auth/unlink', { agent_id: customer.principal.agent_id }, { session: true })
|
|
46
|
+
raise "the site did not unlink #{customer.principal.agent_id}: #{status}" unless status == 204
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def visits(path) = site.get_html(path)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk/pow/equihash'
|
|
4
|
+
|
|
5
|
+
module Kiosk
|
|
6
|
+
module TestHelpers
|
|
7
|
+
# Require this from test setup only. It makes the Equihash default params
|
|
8
|
+
# n=96 k=5, so a registration in a test solves in about 0.2 s, not 10 s.
|
|
9
|
+
# Params passed explicitly are unchanged.
|
|
10
|
+
module SmallEquihash
|
|
11
|
+
PARAMS = { n: 96, k: 5 }.freeze
|
|
12
|
+
|
|
13
|
+
def params(n: PARAMS[:n], k: PARAMS[:k]) = super # rubocop:disable Naming/MethodParameterName -- the Equihash parameter names.
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Kiosk::Pow::Equihash.singleton_class.prepend(Kiosk::TestHelpers::SmallEquihash)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk/test_helpers/customer'
|
|
4
|
+
require 'kiosk/test_helpers/live_server'
|
|
5
|
+
|
|
6
|
+
module Kiosk
|
|
7
|
+
module TestHelpers
|
|
8
|
+
autoload :Person, 'kiosk/test_helpers/person'
|
|
9
|
+
|
|
10
|
+
# A test that tells a business story: who comes to the origin, what they
|
|
11
|
+
# ask for, what the origin answers and what they learn afterwards.
|
|
12
|
+
module Story
|
|
13
|
+
def self.included(base)
|
|
14
|
+
base.include LiveServer
|
|
15
|
+
base.public_send(base.respond_to?(:teardown) ? :teardown : :after) { @assistant&.disconnect }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# A newly registered assistant, as `as` (a demo's own Customer subclass).
|
|
19
|
+
def a_customer(as: Customer) = as.new(assistant, register)
|
|
20
|
+
|
|
21
|
+
# An assistant holding a key and no account here yet.
|
|
22
|
+
def a_newcomer(as: Customer) = as.newcomer(assistant)
|
|
23
|
+
|
|
24
|
+
# Someone with an account on the operator's site, signed in there.
|
|
25
|
+
def a_person(email:, password:) = Person.new(live_url, email:, password:)
|
|
26
|
+
|
|
27
|
+
# What the origin publishes at `path` to anyone, with no credential.
|
|
28
|
+
def published(path)
|
|
29
|
+
status, body = assistant.wire.get_json(path)
|
|
30
|
+
raise "GET #{path} with no credential answered #{status}" unless status == 200
|
|
31
|
+
|
|
32
|
+
body
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'socket'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module Kiosk
|
|
9
|
+
module TestHelpers
|
|
10
|
+
# A local stripe-mock for demo suites. A front on {url} relays to it and
|
|
11
|
+
# answers two calls as Stripe does where stripe-mock does not: a confirmed
|
|
12
|
+
# PaymentIntent create with a test card is `succeeded`, fully received, and
|
|
13
|
+
# a refund that names no charge is a 400.
|
|
14
|
+
module StripeMock
|
|
15
|
+
LOG = '/tmp/stripe-mock.log'
|
|
16
|
+
DROPPED = %w[accept-encoding connection content-length host transfer-encoding].freeze
|
|
17
|
+
NO_CHARGE_BODY = JSON.generate(error: {
|
|
18
|
+
type: 'invalid_request_error',
|
|
19
|
+
message: 'One of the following params should be provided for this request: ' \
|
|
20
|
+
'payment_intent or charge.'
|
|
21
|
+
})
|
|
22
|
+
NO_CHARGE = "HTTP/1.1 400 Bad Request\r\ncontent-type: application/json\r\n" \
|
|
23
|
+
"content-length: #{NO_CHARGE_BODY.bytesize}\r\nconnection: close\r\n\r\n#{NO_CHARGE_BODY}".freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# The front's base URL: STRIPE_MOCK_URL when set, else a free port held
|
|
28
|
+
# for the life of this process. A suite that spawns its front stops it at
|
|
29
|
+
# exit, so each concurrent suite runs its own.
|
|
30
|
+
def url = @url ||= ENV.fetch('STRIPE_MOCK_URL') { "http://127.0.0.1:#{free_port}" }
|
|
31
|
+
|
|
32
|
+
# Reuses a front already listening on {url}; otherwise spawns stripe-mock
|
|
33
|
+
# and the front, stopped at exit.
|
|
34
|
+
#
|
|
35
|
+
# @return [String] the front's base URL
|
|
36
|
+
def start
|
|
37
|
+
return url if listening?(port)
|
|
38
|
+
|
|
39
|
+
abort 'stripe-mock not found. Install it: brew install stripe-mock' unless system('command -v stripe-mock >/dev/null 2>&1')
|
|
40
|
+
|
|
41
|
+
upstream = free_port
|
|
42
|
+
pids = spawn_processes(upstream)
|
|
43
|
+
at_exit { pids.each { stop(it) } }
|
|
44
|
+
await_ready(upstream)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def port = URI(url).port
|
|
48
|
+
|
|
49
|
+
def free_port = TCPServer.open('127.0.0.1', 0) { it.addr[1] }
|
|
50
|
+
|
|
51
|
+
def spawn_processes(upstream)
|
|
52
|
+
[spawn('stripe-mock', '-http-port', upstream.to_s, out: LOG, err: LOG),
|
|
53
|
+
spawn(RbConfig.ruby, '-I', File.expand_path('../..', __dir__), '-rkiosk/test_helpers/stripe_mock',
|
|
54
|
+
'-e', "Kiosk::TestHelpers::StripeMock.serve(#{port}, #{upstream})", out: LOG, err: LOG)]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def stop(pid)
|
|
58
|
+
Process.kill('TERM', pid)
|
|
59
|
+
Process.wait(pid)
|
|
60
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def await_ready(upstream)
|
|
65
|
+
30.times do
|
|
66
|
+
return url if listening?(port) && listening?(upstream)
|
|
67
|
+
|
|
68
|
+
sleep 0.3
|
|
69
|
+
end
|
|
70
|
+
abort "stripe-mock did not become ready on #{url} — see #{LOG}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def listening?(port)
|
|
74
|
+
TCPSocket.new('127.0.0.1', port).close
|
|
75
|
+
true
|
|
76
|
+
rescue StandardError
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Runs the front until killed.
|
|
81
|
+
def serve(port, upstream)
|
|
82
|
+
@upstream = upstream
|
|
83
|
+
server = TCPServer.new('127.0.0.1', port)
|
|
84
|
+
loop { Thread.new(server.accept) { |client| relay(client) } }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def relay(client)
|
|
88
|
+
method, path = client.gets.to_s.split
|
|
89
|
+
return unless path # a readiness check connects and hangs up
|
|
90
|
+
|
|
91
|
+
headers = read_headers(client)
|
|
92
|
+
body = client.read(headers['content-length'].to_i)
|
|
93
|
+
return client.write(NO_CHARGE) if chargeless_refund?(method, path, body)
|
|
94
|
+
|
|
95
|
+
relay_upstream(client, method, path, body, headers)
|
|
96
|
+
ensure
|
|
97
|
+
client.close
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def relay_upstream(client, method, path, body, headers)
|
|
101
|
+
upstream = forward(method, path, body, headers)
|
|
102
|
+
answer = upstream.body.to_s
|
|
103
|
+
answer = settled(answer) if confirmed_create?(method, path, body)
|
|
104
|
+
respond(client, upstream, answer)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def chargeless_refund?(method, path, body)
|
|
108
|
+
params = URI.decode_www_form(body.to_s).to_h
|
|
109
|
+
method == 'POST' && path == '/v1/refunds' && params['payment_intent'].to_s.empty? && params['charge'].to_s.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def forward(method, path, body, headers)
|
|
113
|
+
Net::HTTP.start('127.0.0.1', @upstream) do |http|
|
|
114
|
+
http.send_request(method, path, body, headers.except(*DROPPED))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def read_headers(client)
|
|
119
|
+
headers = {}
|
|
120
|
+
while (line = client.gets) && line != "\r\n"
|
|
121
|
+
name, value = line.split(':', 2)
|
|
122
|
+
headers[name.downcase] = value.strip
|
|
123
|
+
end
|
|
124
|
+
headers
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def confirmed_create?(method, path, body)
|
|
128
|
+
method == 'POST' && path == '/v1/payment_intents' &&
|
|
129
|
+
URI.decode_www_form(body.to_s).to_h['confirm'] == 'true'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def respond(client, upstream, answer)
|
|
133
|
+
client.write("HTTP/1.1 #{upstream.code} #{upstream.message}\r\n")
|
|
134
|
+
upstream.each_header { |k, v| client.write("#{k}: #{v}\r\n") unless DROPPED.include?(k) || k == 'content-encoding' }
|
|
135
|
+
client.write("content-length: #{answer.bytesize}\r\nconnection: close\r\n\r\n#{answer}")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def settled(answer)
|
|
139
|
+
intent = JSON.parse(answer)
|
|
140
|
+
return answer unless intent['object'] == 'payment_intent'
|
|
141
|
+
|
|
142
|
+
JSON.generate(intent.merge('status' => 'succeeded', 'amount_received' => intent['amount']))
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'kiosk/pow/equihash/solver'
|
|
7
|
+
|
|
8
|
+
module Kiosk
|
|
9
|
+
module TestHelpers
|
|
10
|
+
# Any method at any path of an origin, with the whole answer returned. With
|
|
11
|
+
# `pay_tolls: true` it solves a proof-of-work toll and sends the request once more.
|
|
12
|
+
#
|
|
13
|
+
# wire = Kiosk::TestHelpers::Wire.new(base_url: "http://127.0.0.1:3006")
|
|
14
|
+
# status, problem = wire.post_json("/kiosk/edit_listing", { listing_id: "junk" }, Wire.bearer(token))
|
|
15
|
+
class Wire
|
|
16
|
+
# One answer: a body that is not JSON reads as `{}`, an unreachable origin as status 0.
|
|
17
|
+
Response = Data.define(:status, :headers, :body, :raw_body, :proofs) do
|
|
18
|
+
def initialize(status:, body:, headers: {}, raw_body: '', proofs: 0) = super
|
|
19
|
+
|
|
20
|
+
def [](header) = headers[header.to_s.downcase]
|
|
21
|
+
|
|
22
|
+
# Whether the client paid a proof-of-work toll and sent the request again.
|
|
23
|
+
def pow_retried? = proofs.positive?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
METHODS = {
|
|
27
|
+
get: Net::HTTP::Get,
|
|
28
|
+
post: Net::HTTP::Post,
|
|
29
|
+
put: Net::HTTP::Put,
|
|
30
|
+
patch: Net::HTTP::Patch,
|
|
31
|
+
delete: Net::HTTP::Delete,
|
|
32
|
+
head: Net::HTTP::Head,
|
|
33
|
+
options: Net::HTTP::Options
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# The scheme decides TLS.
|
|
37
|
+
def self.http_for(uri, open_timeout: nil, read_timeout: nil)
|
|
38
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
|
39
|
+
http.use_ssl = uri.scheme == 'https'
|
|
40
|
+
http.open_timeout = open_timeout if open_timeout
|
|
41
|
+
http.read_timeout = read_timeout if read_timeout
|
|
42
|
+
http
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.bearer(token) = { 'Authorization' => "Bearer #{token}" }
|
|
46
|
+
|
|
47
|
+
# A solved proof of work for each challenge, as the `Kiosk-PoW` header carries them.
|
|
48
|
+
def self.solve(challenges) = challenges.map { { challenge: it, nonce: Kiosk::Pow::Equihash.solve(it) } }
|
|
49
|
+
|
|
50
|
+
attr_reader :base_url
|
|
51
|
+
|
|
52
|
+
def initialize(base_url:, pay_tolls: false, open_timeout: 10, read_timeout: 30)
|
|
53
|
+
@base_url = base_url.to_s.chomp('/')
|
|
54
|
+
@pay_tolls = pay_tolls
|
|
55
|
+
@open_timeout = open_timeout
|
|
56
|
+
@read_timeout = read_timeout
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def bearer(token) = self.class.bearer(token)
|
|
60
|
+
|
|
61
|
+
def post_json(path, body = {}, headers = {}) = post(path, body, headers).then { [it.status, it.body] }
|
|
62
|
+
|
|
63
|
+
def get_json(path, params = {}, headers = {}) = get(path, params, headers).then { [it.status, it.body] }
|
|
64
|
+
|
|
65
|
+
def post(path, body = {}, headers = {}) = request(:post, path, body:, headers:)
|
|
66
|
+
|
|
67
|
+
def get(path, params = {}, headers = {}) = request(:get, path, params:, headers:)
|
|
68
|
+
|
|
69
|
+
def post_form(path, form, headers = {})
|
|
70
|
+
request(:post, path, headers:) { it.set_form_data(form) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def request(method, path, body: nil, params: nil, headers: {}, &form)
|
|
74
|
+
klass = METHODS.fetch(method.to_s.downcase.to_sym) { raise ArgumentError, "unsupported HTTP method #{method.inspect}" }
|
|
75
|
+
uri = request_uri(path, params)
|
|
76
|
+
answer = deliver(klass, uri, body, headers, &form)
|
|
77
|
+
challenges = toll_challenges(answer)
|
|
78
|
+
return answer unless challenges
|
|
79
|
+
|
|
80
|
+
proofs = self.class.solve(challenges)
|
|
81
|
+
deliver(klass, uri, body, headers.merge('Kiosk-PoW' => JSON.generate(proofs)), &form).with(proofs: proofs.size)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def request_uri(path, params)
|
|
87
|
+
uri = URI("#{@base_url}#{path}")
|
|
88
|
+
uri.query = URI.encode_www_form(params) if params&.any?
|
|
89
|
+
uri
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The proof-of-work challenges of a toll this client pays, or nil.
|
|
93
|
+
def toll_challenges(answer)
|
|
94
|
+
return unless @pay_tolls && answer.status == 402 && answer.body.is_a?(Hash)
|
|
95
|
+
|
|
96
|
+
challenges = answer.body['challenges']
|
|
97
|
+
challenges if challenges.is_a?(Array) && challenges.any?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def deliver(klass, uri, body, headers)
|
|
101
|
+
request = klass.new(uri, body.nil? ? headers : { 'Content-Type' => 'application/json' }.merge(headers))
|
|
102
|
+
request.body = JSON.generate(body) unless body.nil?
|
|
103
|
+
yield request if block_given?
|
|
104
|
+
begin
|
|
105
|
+
answer = self.class.http_for(uri, open_timeout: @open_timeout, read_timeout: @read_timeout).request(request)
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
return Response.new(status: 0, body: { 'error' => e.class.name, 'detail' => e.message })
|
|
108
|
+
end
|
|
109
|
+
response(answer)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def response(answer)
|
|
113
|
+
raw_body = answer.body.to_s
|
|
114
|
+
body = begin
|
|
115
|
+
JSON.parse(raw_body)
|
|
116
|
+
rescue JSON::ParserError
|
|
117
|
+
{}
|
|
118
|
+
end
|
|
119
|
+
headers = answer.each_header.to_h { |name, value| [name.downcase, value] }
|
|
120
|
+
Response.new(status: answer.code.to_i, headers:, body:, raw_body:)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk'
|
|
4
|
+
|
|
5
|
+
require 'kiosk/test_helpers/version'
|
|
6
|
+
require 'kiosk/test_helpers/errors'
|
|
7
|
+
require 'kiosk/test_helpers/null_executor'
|
|
8
|
+
require 'kiosk/test_helpers/journey'
|
|
9
|
+
require 'kiosk/test_helpers/conformance'
|
|
10
|
+
|
|
11
|
+
module Kiosk
|
|
12
|
+
# Test support for Kiosk operators: the journey DSL over a pluggable executor,
|
|
13
|
+
# and the conformance checks an origin runs against itself.
|
|
14
|
+
module TestHelpers
|
|
15
|
+
class << self
|
|
16
|
+
# Anything answering the {NullExecutor} contract.
|
|
17
|
+
attr_accessor :executor
|
|
18
|
+
|
|
19
|
+
def require_executor!
|
|
20
|
+
executor || raise(Errors::ExecutorNotConfigured)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset!
|
|
24
|
+
@executor = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kiosk-test-support
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.20
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Phil Pirozhkov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json_schemer
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.3'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '3.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.3'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '3.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: jwt
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '2.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '4.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '2.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '4.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: kiosk-core
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 0.5.0
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: 0.5.0
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: kiosk-pow-equihash
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 0.5.0
|
|
73
|
+
type: :runtime
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - "~>"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 0.5.0
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: puma
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '5.0'
|
|
87
|
+
type: :runtime
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '5.0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: websocket-driver
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - "~>"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0.7'
|
|
101
|
+
type: :runtime
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - "~>"
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0.7'
|
|
108
|
+
description: |
|
|
109
|
+
kiosk-test-support is the framework-agnostic test surface a Kiosk
|
|
110
|
+
provider uses on their OWN application.
|
|
111
|
+
|
|
112
|
+
It carries two things. The CONFORMANCE CHECKS assert the four
|
|
113
|
+
properties the protocol makes normative of an origin: that its routes
|
|
114
|
+
resolve, that a verb executes, that a query answers the shape it
|
|
115
|
+
declared, and that data access is scoped to the authenticated
|
|
116
|
+
principal. They ship with a Minitest adapter and an RSpec adapter, both
|
|
117
|
+
loaded by explicit require, so the same fault reads identically in
|
|
118
|
+
either framework.
|
|
119
|
+
|
|
120
|
+
The JOURNEY DSL carries the identity-scoped call helpers
|
|
121
|
+
(`as_agent_of`, `as_user`, `as_agent`, `as_anonymous`, `query`,
|
|
122
|
+
`run_query`, `run_action`, `pay_action`, `kiosk_seed`), the pluggable
|
|
123
|
+
executor contract, a NullExecutor for self-tests, and the structured
|
|
124
|
+
error classes (`RLSDenied`, `QuotaExceeded`, `ExecutorNotConfigured`).
|
|
125
|
+
It is wired into RSpec by `kiosk-rls-rspec` and into Minitest by
|
|
126
|
+
`kiosk-rls-minitest`.
|
|
127
|
+
|
|
128
|
+
The ASSISTANT drives an origin over HTTP as an assistant does: it
|
|
129
|
+
registers, pays proof-of-work tolls, queries, runs, pays with signed
|
|
130
|
+
mandates and listens on the event stream. `Kyc` stands in for the
|
|
131
|
+
operator's KYC provider, and `StripeMock` fronts a local stripe-mock.
|
|
132
|
+
|
|
133
|
+
No Postgres, no Rails, no test-framework dependency.
|
|
134
|
+
`InProcessExecutor` and `Conformance::InProcessOrigin` run against
|
|
135
|
+
the host's kiosk-server and are required explicitly; this gem does
|
|
136
|
+
not depend on kiosk-server.
|
|
137
|
+
email:
|
|
138
|
+
- hello@fili.pp.ru
|
|
139
|
+
executables: []
|
|
140
|
+
extensions: []
|
|
141
|
+
extra_rdoc_files: []
|
|
142
|
+
files:
|
|
143
|
+
- CHANGELOG.md
|
|
144
|
+
- LICENSE.txt
|
|
145
|
+
- README.md
|
|
146
|
+
- lib/kiosk/story_spec.rb
|
|
147
|
+
- lib/kiosk/story_test.rb
|
|
148
|
+
- lib/kiosk/test_helpers.rb
|
|
149
|
+
- lib/kiosk/test_helpers/assistant.rb
|
|
150
|
+
- lib/kiosk/test_helpers/assistant/events.rb
|
|
151
|
+
- lib/kiosk/test_helpers/conformance.rb
|
|
152
|
+
- lib/kiosk/test_helpers/conformance/checks.rb
|
|
153
|
+
- lib/kiosk/test_helpers/conformance/checks/declared_shape.rb
|
|
154
|
+
- lib/kiosk/test_helpers/conformance/checks/executes.rb
|
|
155
|
+
- lib/kiosk/test_helpers/conformance/checks/helpers.rb
|
|
156
|
+
- lib/kiosk/test_helpers/conformance/checks/principal_scope.rb
|
|
157
|
+
- lib/kiosk/test_helpers/conformance/checks/routes.rb
|
|
158
|
+
- lib/kiosk/test_helpers/conformance/in_process_origin.rb
|
|
159
|
+
- lib/kiosk/test_helpers/conformance/minitest.rb
|
|
160
|
+
- lib/kiosk/test_helpers/conformance/null_origin.rb
|
|
161
|
+
- lib/kiosk/test_helpers/conformance/outcome.rb
|
|
162
|
+
- lib/kiosk/test_helpers/conformance/rspec.rb
|
|
163
|
+
- lib/kiosk/test_helpers/conformance/verb.rb
|
|
164
|
+
- lib/kiosk/test_helpers/customer.rb
|
|
165
|
+
- lib/kiosk/test_helpers/descriptor_examples.rb
|
|
166
|
+
- lib/kiosk/test_helpers/errors.rb
|
|
167
|
+
- lib/kiosk/test_helpers/in_process_executor.rb
|
|
168
|
+
- lib/kiosk/test_helpers/in_process_wire.rb
|
|
169
|
+
- lib/kiosk/test_helpers/journey.rb
|
|
170
|
+
- lib/kiosk/test_helpers/kyc.rb
|
|
171
|
+
- lib/kiosk/test_helpers/live_server.rb
|
|
172
|
+
- lib/kiosk/test_helpers/null_executor.rb
|
|
173
|
+
- lib/kiosk/test_helpers/person.rb
|
|
174
|
+
- lib/kiosk/test_helpers/small_equihash.rb
|
|
175
|
+
- lib/kiosk/test_helpers/story.rb
|
|
176
|
+
- lib/kiosk/test_helpers/stripe_mock.rb
|
|
177
|
+
- lib/kiosk/test_helpers/version.rb
|
|
178
|
+
- lib/kiosk/test_helpers/wire.rb
|
|
179
|
+
homepage: https://kiosk.tech
|
|
180
|
+
licenses:
|
|
181
|
+
- Apache-2.0
|
|
182
|
+
metadata:
|
|
183
|
+
homepage_uri: https://kiosk.tech
|
|
184
|
+
source_code_uri: https://github.com/kiosk-hq/kiosk
|
|
185
|
+
changelog_uri: https://github.com/kiosk-hq/kiosk/blob/main/kiosk-test-support/CHANGELOG.md
|
|
186
|
+
bug_tracker_uri: https://github.com/kiosk-hq/kiosk/issues
|
|
187
|
+
rubygems_mfa_required: 'true'
|
|
188
|
+
rdoc_options: []
|
|
189
|
+
require_paths:
|
|
190
|
+
- lib
|
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
|
+
requirements:
|
|
193
|
+
- - ">="
|
|
194
|
+
- !ruby/object:Gem::Version
|
|
195
|
+
version: '4.0'
|
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
197
|
+
requirements:
|
|
198
|
+
- - ">="
|
|
199
|
+
- !ruby/object:Gem::Version
|
|
200
|
+
version: '0'
|
|
201
|
+
requirements: []
|
|
202
|
+
rubygems_version: 4.0.3
|
|
203
|
+
specification_version: 4
|
|
204
|
+
summary: Kiosk conformance checks and journey-test DSL for RSpec and Minitest
|
|
205
|
+
test_files: []
|