riot-gear 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.yardopts +6 -0
- data/CHANGELOG +39 -0
- data/Gemfile +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +79 -6
- data/Rakefile +4 -24
- data/lib/riot/gear/context/asserts_header.rb +6 -0
- data/lib/riot/gear/context/asserts_json.rb +21 -7
- data/lib/riot/gear/context/asserts_status.rb +6 -0
- data/lib/riot/gear/context/persist_cookie.rb +17 -0
- data/lib/riot/gear/middleware/riotparty.rb +81 -24
- data/lib/riot/gear/version.rb +6 -0
- data/riot-gear.gemspec +14 -71
- data/test/actions/delete_test.rb +54 -45
- data/test/actions/get_test.rb +95 -36
- data/test/actions/post_test.rb +55 -45
- data/test/actions/put_test.rb +54 -45
- data/test/assertions/asserts_json_test.rb +2 -2
- data/test/helpers/cookie_values_test.rb +10 -6
- data/test/riotparty_proxy_methods_test.rb +1 -1
- data/test/setting_up_gear_context_test.rb +4 -3
- data/test/teststrap.rb +2 -0
- metadata +55 -81
- data/VERSION +0 -1
data/.gitignore
ADDED
data/.yardopts
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# @markup markdown
|
2
|
+
|
3
|
+
# 0.0.9
|
4
|
+
|
5
|
+
* get, post, put, delete can have responses saved for use in other requests within the same (or nested) context [jaknowlden]
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
context GoNuts do
|
9
|
+
post(:the_new_nut) do
|
10
|
+
{ :path => "/make/some", :body => "..." }
|
11
|
+
end
|
12
|
+
|
13
|
+
get do
|
14
|
+
{ :path => "/get/some/#{response(:the_new_nut)["id"]}" }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
The last get, post, put, or delete in the context will still be the one used for assertions.
|
20
|
+
|
21
|
+
* get, post, put, delete can take a block that expects a settings hash to be returned (with a path) [jaknowlden]
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
context GoNuts do
|
25
|
+
post do
|
26
|
+
{ :path => "/make/some", :body => "..." }
|
27
|
+
end
|
28
|
+
|
29
|
+
get do
|
30
|
+
{ :path => "/get/some", :query => { ... } }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
* tons of documentation [jaknowlden]
|
36
|
+
|
37
|
+
# 0.0.8 and before
|
38
|
+
|
39
|
+
See the commit log: http://github.com/thumblemonks/riot-gear/commits/master
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Justin Knowlden, Thumble Monks
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,17 +1,90 @@
|
|
1
1
|
# Riot Gear
|
2
2
|
|
3
|
-
|
3
|
+
Riot Gear is a framework for HTTP-based smoke testing using a real testing framework; [Riot](http://thumblemonks.github.com/riot) + [HTTParty](http://github.com/jnunemaker/httparty). The principle impetus for creating Riot Gear came from a desire to easily develop a suite of smoke tests for a few, JSON-based web services. On one hand you could use Riot Gear to develop in-depth integration tests hitting a local testing environment that are constantly run through your continuous integration system. On the other hand, you could use Riot Gear to implement real smoke tests that hit your production environment frequently and/or maybe after a release. From this you can derive that Riot Gear does not intend to replace Selenium (Se) or any of Se's "competitors". Nope ... Riot Gear just wants to make the real-world testing of web-enabled APIs easier.
|
4
4
|
|
5
|
-
|
5
|
+
How Riot Gear does this is by combining two things I enjoy; Riot for its lightweight, contextual, and flexible testing framework; and HTTParty for its simplistic and powerful approach to providing HTTP enabled APIs (so to speak). The resulting DSL essentially allows one to mix HTTParty behavior directly with Riot behavior; i.e. build up and make an HTTP request and then test its response.
|
6
6
|
|
7
|
-
|
7
|
+
Here's an example involving a hypothetical login and login failure:
|
8
|
+
|
9
|
+
require 'riot/gear'
|
10
|
+
|
11
|
+
context "Logging into Example.com as good.user" do
|
12
|
+
base_uri "http://example.com"
|
13
|
+
post "/session", :body => {"email" => "good.user@example.com", "password" => "p4$sw0R)"}
|
14
|
+
|
15
|
+
asserts_status.equals(200)
|
16
|
+
asserts_header("Content-Type").equals("application/json;charset=utf-8")
|
17
|
+
asserts_json("user.name").equals("Slick Rick")
|
18
|
+
end
|
19
|
+
|
20
|
+
context "Failing to log into Example.com as good.user" do
|
21
|
+
base_uri "http://example.com"
|
22
|
+
post "/session", :body => {"email" => "good.user@example.com", "password" => "y0uF41l"}
|
23
|
+
|
24
|
+
asserts_status.equals(403)
|
25
|
+
asserts_header("Content-Type").equals("application/json;charset=utf-8")
|
26
|
+
asserts_json("error.message").matches(/email or password is invalid/i)
|
27
|
+
end
|
28
|
+
|
29
|
+
If you're familiar with Riot and/or HTTParty you'll recognize their natures immediately. To begin, there are two Riot `context` blocks. Each context (and any of the sub-contexts) are independent from each other; this is in keeping with the nature of Riot. Within each context there will be setup code and then validation code. Setup code will most likely be reflected as HTTParty calls - though you should feel free to use Riot's helpers, setups, and hookups; whereas validation code will always be Riot (until this statement is wrong).
|
30
|
+
|
31
|
+
Thus, in the two example contexts, the setup code involves telling HTTParty what the `base_uri` is that any HTTP requests for that context will be made to; followed by the actual HTTP request - a `post` to `/session` with login credentials. After the `post` is sent, the `HTTParty::Response` object is made available for validation as the Riot helper named `response`.
|
32
|
+
|
33
|
+
Riot Gear provides a few built-in assertions for validating common response information, which you see above: `asserts_status`, `asserts_header`, and `asserts_json`. Each of these generate normal Riot assertions that can have assertion macros applied to them (eg. `equals`, `kind_of`, `matches`, `nil`, `exists`, etc.). You could easily replace what `asserts_status` does in the example above with this:
|
34
|
+
|
35
|
+
asserts("status code") do
|
36
|
+
response.code
|
37
|
+
end.equals(200)
|
38
|
+
|
39
|
+
## Priming a request
|
40
|
+
|
41
|
+
A common problem when testing services is that you need to perform a few activities before you can perform the one you want to test. For instance, you may need to login and create some resource before you can test an update to that resource. This is simple enough in Riot Gear since the last request made through `get`, `post`, `put`, `delete`, or `head` is the one whose response will be validated. For instance:
|
42
|
+
|
43
|
+
require 'riot/gear'
|
44
|
+
|
45
|
+
context "Updating a playlist" do
|
8
46
|
base_uri "http://example.com"
|
9
|
-
|
47
|
+
|
48
|
+
post "/session", :body => {"email" => "good.user@example.com", "password" => "p4$sw0R)"}
|
49
|
+
persist_cookie("example_session")
|
50
|
+
|
51
|
+
post "/user/playlists", :body => {"name" => "Dubsteppin to the oldies"}
|
52
|
+
put "/user/playlists/dubsteppin-to-the-oldies", :body => {"name" => "Dubsteppin to the newbies"}
|
10
53
|
|
11
54
|
asserts_status.equals(200)
|
12
55
|
asserts_header("Content-Type").equals("application/json;charset=utf-8")
|
56
|
+
asserts_json("data.message").equals("Playlist updated successfully")
|
57
|
+
end
|
58
|
+
|
59
|
+
The `post` commands and the `put` will execute in the order they are defined. However, only the response from the `put` will be used for validation. It also would not matter where in the context you put the assertions because they always run last. For instance, this context is effectually the same as the one above:
|
60
|
+
|
61
|
+
require 'riot/gear'
|
62
|
+
|
63
|
+
context "Updating a playlist" do
|
64
|
+
base_uri "http://example.com"
|
65
|
+
|
66
|
+
post "/session", :body => {"email" => "good.user@example.com", "password" => "p4$sw0R)"}
|
67
|
+
persist_cookie("example_session")
|
68
|
+
|
69
|
+
asserts_json("data.message").equals("Playlist updated successfully")
|
13
70
|
|
14
|
-
|
71
|
+
post "/user/playlists", :body => {"name" => "Dubsteppin to the oldies"}
|
72
|
+
|
73
|
+
asserts_header("Content-Type").equals("application/json;charset=utf-8")
|
74
|
+
|
75
|
+
put "/user/playlists/dubsteppin-to-the-oldies", :body => {"name" => "Dubsteppin to the newbies"}
|
76
|
+
|
77
|
+
asserts_status.equals(200)
|
15
78
|
end
|
16
79
|
|
17
|
-
|
80
|
+
*This fact is likely to change soon.*
|
81
|
+
|
82
|
+
## Coming soon
|
83
|
+
|
84
|
+
* Reusable macro-like blocks
|
85
|
+
* Priming a test against another host via `at_host`
|
86
|
+
* Sequential command processing
|
87
|
+
|
88
|
+
## License
|
89
|
+
|
90
|
+
Riot Gear is released under the MIT license. See {file:MIT-LICENSE}.
|
data/Rakefile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
3
|
-
|
2
|
+
require 'bundler'
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
4
|
require 'rake/testtask'
|
5
5
|
|
6
6
|
#
|
@@ -15,34 +15,14 @@ task(:console) { exec "irb -r boot" }
|
|
15
15
|
|
16
16
|
#
|
17
17
|
# Testing
|
18
|
+
task :default => ["test"]
|
18
19
|
|
19
20
|
Rake::TestTask.new("test") do |t|
|
20
21
|
t.libs << "test"
|
21
22
|
t.pattern = "test/**/*_test.rb"
|
23
|
+
#t.warning = true
|
22
24
|
t.verbose = false
|
23
25
|
end
|
24
|
-
Rake::Task["test"].instance_variable_set(:@full_comment, nil) # Dumb dumb dumb
|
25
|
-
Rake::Task["test"].comment = "Run the tests!"
|
26
26
|
|
27
27
|
task :default => :test
|
28
28
|
|
29
|
-
#
|
30
|
-
# Some monks like diamonds. I like gems.
|
31
|
-
|
32
|
-
begin
|
33
|
-
require 'jeweler'
|
34
|
-
Jeweler::Tasks.new do |gem|
|
35
|
-
gem.name = "riot-gear"
|
36
|
-
gem.summary = "Riot + HTTParty smoke testing framework"
|
37
|
-
gem.description = "Riot + HTTParty smoke testing framework. You'd use it for integration testing with real HTTP requests and responses"
|
38
|
-
gem.email = "gus@gusg.us"
|
39
|
-
gem.homepage = "http://github.com/thumblemonks/riot-gear"
|
40
|
-
gem.authors = ["Justin 'Gus' Knowlden"]
|
41
|
-
gem.add_dependency 'riot'
|
42
|
-
gem.add_dependency 'httparty'
|
43
|
-
gem.add_development_dependency 'webmock', ">=1.6.1"
|
44
|
-
end
|
45
|
-
Jeweler::GemcutterTasks.new
|
46
|
-
rescue LoadError
|
47
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
48
|
-
end
|
@@ -4,6 +4,12 @@ module Riot
|
|
4
4
|
module Gear
|
5
5
|
module AssertsHeader
|
6
6
|
|
7
|
+
# Generates an assertion that retrieves the value of some header from the last response.
|
8
|
+
#
|
9
|
+
# asserts_header("Content-Length").equals("125")
|
10
|
+
#
|
11
|
+
# @param [String] header_key the name of the header
|
12
|
+
# @return [Riot::Assertion] an assertion block that macros can be applied to
|
7
13
|
def asserts_header(header_key)
|
8
14
|
asserts("header variable #{header_key}") { response.headers[header_key] }
|
9
15
|
end
|
@@ -4,15 +4,29 @@ module Riot
|
|
4
4
|
module Gear
|
5
5
|
module AssertsJson
|
6
6
|
|
7
|
-
#
|
8
|
-
# that block will be called with the value and the response
|
9
|
-
# in the assertion test.
|
7
|
+
# Generates an assertion that based on the value returned from passing the JSON path to the json_path
|
8
|
+
# helper. If a handler block is provided, that block will be called with the value and the response
|
9
|
+
# from the block will be used as the actual in the assertion test.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# context "testing a hash" do
|
12
|
+
# setup do
|
13
|
+
# {"a" => {"b" => {"c" => {"d" => "foo"}}}}
|
14
|
+
# end
|
12
15
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
+
# asserts_json("a.b.c.d").equals("foo")
|
17
|
+
# asserts_json("a['b'].c['d']").equals("foo")
|
18
|
+
#
|
19
|
+
# asserts_json("a.b") do |value|
|
20
|
+
# value["c"]
|
21
|
+
# end.equals({"d" => "foo"})
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# This is useful for testing actual JSON responses from some service that are converted to a hash by
|
25
|
+
# HTTParty.
|
26
|
+
#
|
27
|
+
# @param [String] json_string a JSON looking path
|
28
|
+
# @param [lambda] &handler an optional block for filtering the actual value
|
29
|
+
# @return [Riot::Assertion] an assertion block that macros can be applied to
|
16
30
|
def asserts_json(json_string, &handler)
|
17
31
|
asserts("value from body as json:#{json_string}") do
|
18
32
|
value = json_path(response, json_string)
|
@@ -4,6 +4,12 @@ module Riot
|
|
4
4
|
module Gear
|
5
5
|
module AssertsStatus
|
6
6
|
|
7
|
+
# Generates an assertion that retrieves the status code from the last response. The statuc code will
|
8
|
+
# be an integer.
|
9
|
+
#
|
10
|
+
# asserts_status.equals(200)
|
11
|
+
#
|
12
|
+
# @return [Riot::Assertion] an assertion block that macros can be applied to
|
7
13
|
def asserts_status
|
8
14
|
asserts("status code") { response.code }
|
9
15
|
end
|
@@ -5,6 +5,23 @@ module Riot
|
|
5
5
|
module Gear
|
6
6
|
module PersistCookie
|
7
7
|
|
8
|
+
# Graft a cookie (name and value) from the last response onto the next and subsequent requests. Only
|
9
|
+
# applies to multiple requests within a {Riot::Context}. For instance, if you log into your service,
|
10
|
+
# you will probably want to pass along whatever the session cookie was to the next request in the test.
|
11
|
+
#
|
12
|
+
# context "Get new messages" do
|
13
|
+
# base_uri "http://example.com"
|
14
|
+
# post "/session?email=foo@bar.baz&password=beepboopbop"
|
15
|
+
#
|
16
|
+
# persist_cookie("example_session")
|
17
|
+
#
|
18
|
+
# get "/user/messages.json"
|
19
|
+
# asserts_status.equals(200)
|
20
|
+
# asserts_json("messages").length(8)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @param [String] cookie_name the name of a cookie to persist
|
24
|
+
# @return [Riot::Assertion] an assertion block that macros can be applied to
|
8
25
|
def persist_cookie(cookie_name)
|
9
26
|
hookup do
|
10
27
|
if cookie_value = cookie_values[cookie_name]
|
@@ -2,9 +2,18 @@ require 'httparty'
|
|
2
2
|
|
3
3
|
module Riot
|
4
4
|
module Gear
|
5
|
+
# Here we prepare a {Riot::Context} to have HTTParty bound to it. Basically, this means that you can
|
6
|
+
# use HTTParty within a context the same way you would inside any class or you would normally use it in.
|
7
|
+
# Anything you can do with HTTParty, you can do within a context ... and then you can test it :)
|
8
|
+
#
|
9
|
+
# Great pains are made to ensure that the HTTParty setup bound to one context does not interfere setup
|
10
|
+
# bound to another context.
|
5
11
|
class RiotPartyMiddleware < ::Riot::ContextMiddleware
|
6
12
|
register
|
7
13
|
|
14
|
+
# Prepares the context for HTTParty support.
|
15
|
+
#
|
16
|
+
# @param [Riot::Context] context the context to prepare
|
8
17
|
def call(context)
|
9
18
|
setup_faux_class(context)
|
10
19
|
setup_helpers(context)
|
@@ -16,49 +25,93 @@ module Riot
|
|
16
25
|
private
|
17
26
|
|
18
27
|
# Only cluttering anonymous classes with HTTParty stuff. Keeps each context safe from collision ... in
|
19
|
-
# theory
|
28
|
+
# theory.
|
29
|
+
#
|
30
|
+
# @param [Riot::Context] context the context to create the setup for
|
31
|
+
# @todo Fix this so that settings like +base_uri+ can be inherited
|
20
32
|
def setup_faux_class(context)
|
21
33
|
context.setup(true) do
|
34
|
+
@saved_responses = {}
|
35
|
+
|
22
36
|
Class.new do
|
23
37
|
include HTTParty
|
24
38
|
# debug_output $stderr
|
25
39
|
end
|
26
40
|
end
|
27
41
|
|
28
|
-
context.helper(:response)
|
42
|
+
context.helper(:response) do |name=nil|
|
43
|
+
@saved_responses[name] || @smoke_response
|
44
|
+
end
|
29
45
|
end # setup_faux_class
|
30
46
|
|
47
|
+
# Returns the list of methods that do something; like make a network call.
|
31
48
|
#
|
32
|
-
#
|
33
|
-
|
49
|
+
# @return [Array<Symbol>]
|
34
50
|
def actionable_methods; [:get, :post, :put, :delete, :head]; end
|
35
51
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
52
|
+
# Returns the list of methods that configure actionable HTTParty methods. The {HTTParty.options} and
|
53
|
+
# {HTTParty.default_options} methods are explicitly excluded from this list
|
54
|
+
#
|
55
|
+
# @return [Array<Symbol>]
|
42
56
|
def proxiable_methods
|
43
57
|
methods = HTTParty::ClassMethods.instance_methods.map { |m| m.to_s.to_sym }
|
44
58
|
methods - actionable_methods - [:options, :default_options]
|
45
59
|
end
|
46
60
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
61
|
+
# Bind the set of actionable methods to a given context.
|
62
|
+
#
|
63
|
+
# Basically, we're just passing standard HTTParty setup methods onto situation via hookups. These
|
64
|
+
# hookups - so long as the topic hasn't changed yet - are bound to an anonymous class that has
|
65
|
+
# HTTParty included to it. Meaning, this is how you call get, post, put, delete from within a
|
66
|
+
# test.
|
67
|
+
#
|
68
|
+
# There are couple of different forms for these actions. As you would expect, there's:
|
69
|
+
#
|
70
|
+
# get "/path", :query => {...}, ...
|
71
|
+
#
|
72
|
+
# But you can also record the response for use later in the test:
|
73
|
+
#
|
74
|
+
# post(:new_thing) do
|
75
|
+
# { :path => "/things", :body => ... }
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# get do # this response will be used for assertions since it's last
|
79
|
+
# { :path => "/things/#{response(:new_thing).id}/settings" }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# @param [Riot::Context] context the context to add the helper to
|
83
|
+
def proxy_action_methods(context)
|
84
|
+
context_eigen = (class << context; self; end)
|
85
|
+
actionable_methods.each do |method_name|
|
86
|
+
context_eigen.__send__(:define_method, method_name) do |*args, &settings_block|
|
55
87
|
hookup do
|
56
|
-
|
57
|
-
|
88
|
+
if settings_block
|
89
|
+
name = args.first
|
90
|
+
options = instance_eval(&settings_block)
|
91
|
+
path = options.delete(:path) || "/"
|
92
|
+
else
|
93
|
+
name = nil
|
94
|
+
path, options = *args
|
95
|
+
end
|
96
|
+
result = topic.__send__(method_name, path, options || {})
|
97
|
+
@saved_responses[name] = result
|
98
|
+
@smoke_response = result # TODO remove this after it's certain no usages in the wild
|
58
99
|
end
|
59
|
-
end
|
100
|
+
end
|
60
101
|
end # methods.each
|
61
|
-
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Bind the set of proxiable (non-action) methods to a given context.
|
105
|
+
#
|
106
|
+
# @param [Riot::Context] context the context to add the helper to
|
107
|
+
def proxy_httparty_hookups(context)
|
108
|
+
context_eigen = (class << context; self; end)
|
109
|
+
proxiable_methods.each do |method_name|
|
110
|
+
context_eigen.__send__(:define_method, method_name) do |*args|
|
111
|
+
hookup { topic.__send__(method_name, *args) }
|
112
|
+
end
|
113
|
+
end # methods.each
|
114
|
+
end
|
62
115
|
|
63
116
|
#
|
64
117
|
# Helpful helpers
|
@@ -70,13 +123,13 @@ module Riot
|
|
70
123
|
|
71
124
|
# Maps a JSON string to a Hash tree. For instance, give this hash:
|
72
125
|
#
|
73
|
-
# json_object = {"a" => {"b" => "c" => {"d" => "foo"}}}
|
126
|
+
# json_object = {"a" => {"b" => {"c" => {"d" => "foo"}}}}
|
74
127
|
#
|
75
128
|
# You could retrieve the value of 'd' via JSON notation in any of the following ways:
|
76
129
|
#
|
77
130
|
# json_path(json_object, "a.b.c.d")
|
78
131
|
# => "foo"
|
79
|
-
# json_path(json_object, "a['b'].c[d]")
|
132
|
+
# json_path(json_object, "a['b'].c['d']")
|
80
133
|
# => "foo"
|
81
134
|
#
|
82
135
|
# You can even work with array indexes
|
@@ -84,6 +137,8 @@ module Riot
|
|
84
137
|
# json_object = {"a" => {"b" => "c" => ["foo", {"d" => "bar"}]}}
|
85
138
|
# json_path(json_object, "a[b].c[1].d")
|
86
139
|
# => "bar"
|
140
|
+
#
|
141
|
+
# @param [Riot::Context] context the context to add the helper to
|
87
142
|
def helper_json_path(context)
|
88
143
|
context.helper(:json_path) do |dictionary, path|
|
89
144
|
return nil if path.to_s.empty?
|
@@ -102,6 +157,8 @@ module Riot
|
|
102
157
|
# "stupid_marketing_tricks" => {"value" => "personal-information", ...},
|
103
158
|
# ...
|
104
159
|
# }
|
160
|
+
#
|
161
|
+
# @param [Riot::Context] context the context to add the helper to
|
105
162
|
def helper_cookie_value(context)
|
106
163
|
context.helper(:cookie_values) do
|
107
164
|
response.header["set-cookie"].split("\n").inject({}) do |jar, cookie_str|
|