rtrail 0.0.1
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 +15 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +96 -0
- data/lib/rtrail.rb +19 -0
- data/lib/rtrail/api.rb +42 -0
- data/lib/rtrail/case.rb +8 -0
- data/lib/rtrail/client.rb +113 -0
- data/lib/rtrail/entity.rb +65 -0
- data/lib/rtrail/exceptions.rb +4 -0
- data/lib/rtrail/helpers.rb +32 -0
- data/lib/rtrail/plan.rb +7 -0
- data/lib/rtrail/project.rb +83 -0
- data/lib/rtrail/result.rb +9 -0
- data/lib/rtrail/run.rb +39 -0
- data/lib/rtrail/section.rb +7 -0
- data/lib/rtrail/suite.rb +26 -0
- data/lib/rtrail/test.rb +22 -0
- data/mock/app.rb +55 -0
- data/mock/views/add_result/1.yajl +4 -0
- data/mock/views/add_run/1.yajl +7 -0
- data/mock/views/error.yajl +3 -0
- data/mock/views/get_case/1.yajl +4 -0
- data/mock/views/get_cases/1.yajl +3 -0
- data/mock/views/get_plan/1.yajl +4 -0
- data/mock/views/get_plans/1.yajl +3 -0
- data/mock/views/get_project/1.yajl +4 -0
- data/mock/views/get_project/2.yajl +4 -0
- data/mock/views/get_projects.yajl +4 -0
- data/mock/views/get_result/1.yajl +4 -0
- data/mock/views/get_results/1.yajl +3 -0
- data/mock/views/get_run/1.yajl +7 -0
- data/mock/views/get_run/2.yajl +7 -0
- data/mock/views/get_runs/1.yajl +4 -0
- data/mock/views/get_section/1.yajl +9 -0
- data/mock/views/get_section/2.yajl +9 -0
- data/mock/views/get_sections/1.yajl +4 -0
- data/mock/views/get_suite/1.yajl +6 -0
- data/mock/views/get_suite/2.yajl +5 -0
- data/mock/views/get_suite/3.yajl +6 -0
- data/mock/views/get_suites/1.yajl +4 -0
- data/mock/views/get_suites/2.yajl +4 -0
- data/mock/views/get_test/1.yajl +4 -0
- data/mock/views/get_test/2.yajl +4 -0
- data/mock/views/get_tests/1.yajl +5 -0
- data/rtrail.gemspec +41 -0
- data/spec/api_spec.rb +46 -0
- data/spec/case_spec.rb +12 -0
- data/spec/client_spec.rb +43 -0
- data/spec/entity_spec.rb +12 -0
- data/spec/helpers_spec.rb +52 -0
- data/spec/plan_spec.rb +12 -0
- data/spec/project_spec.rb +132 -0
- data/spec/result_spec.rb +12 -0
- data/spec/run_spec.rb +61 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/suite_spec.rb +29 -0
- data/spec/test_spec.rb +34 -0
- metadata +258 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZmEzZWQxZWViOTUwZWJhZjEwNDJmYmU3ODBlY2E3ODkxMjViM2Y3OQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZjhkODIwMmY3ZWE0MDE4ODk4ZWNjZGJhOTQwNDFmYTMwNWJkOTkwMA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
Yzg0NDgxZDA1NWM2ZTkzMTc4ZTkwOWFlYTAzMTRjMmUxYWZjOTU3YTBmZjVl
|
10
|
+
NDhlNDM2YmViMWUxMmI1NjM5ZTNmYTQ2NzEzNmM5YWFlZGU5MDQ1MTc5MjFk
|
11
|
+
YzYzOTkzNzM4MWRhYmRmZjcwNDBjYjk5ZDI0NmVlMDcwNWNkNWM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MzIzYWRkOWRiOTg4MjI2MTM0NzE3YWJjYmJiYWY2OGMwYTA5ZTVmNWUyN2I0
|
14
|
+
NmY5MjViOTcwZGUwZjkzMDAxNmM2MTIzY2M2YmQzMmM0ZTYxN2EwNTA1NDBl
|
15
|
+
M2RmMDE5NTRiYTBiYzc0OTEwODk2OTNjMDRlMWRhMzk3Zjc1NjY=
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format doc --color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Automation Excellence
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
RTrail
|
2
|
+
======
|
3
|
+
|
4
|
+
Ruby object wrapper for the TestRail API.
|
5
|
+
|
6
|
+
RTrail uses the [TestRail API v2](http://docs.gurock.com/testrail-api2/start),
|
7
|
+
which means you must use TestRail 3.x or later.
|
8
|
+
|
9
|
+
|
10
|
+
Installation
|
11
|
+
------------
|
12
|
+
|
13
|
+
Run from a console:
|
14
|
+
|
15
|
+
$ gem install rtrail
|
16
|
+
|
17
|
+
Or add to your `Gemfile`:
|
18
|
+
|
19
|
+
gem 'rtrail'
|
20
|
+
|
21
|
+
|
22
|
+
Overview
|
23
|
+
--------
|
24
|
+
|
25
|
+
RTrail provides a hierarchy of Ruby objects, mirroring the data structure
|
26
|
+
within TestRail. Here's a rough look:
|
27
|
+
|
28
|
+
- Project(s)
|
29
|
+
- Plan(s)
|
30
|
+
- Suite(s)
|
31
|
+
- Case(s)
|
32
|
+
- Section(s)
|
33
|
+
- Run(s)
|
34
|
+
- Test(s)
|
35
|
+
- Result(s)
|
36
|
+
|
37
|
+
Within RTrail, each of these things are modeled by a class, with a common base
|
38
|
+
class known as `Entity`.
|
39
|
+
|
40
|
+
|
41
|
+
Usage
|
42
|
+
-----
|
43
|
+
|
44
|
+
The top-level interface is through `RTrail::API`; simply provide your TestRail
|
45
|
+
server's hostname, username and password:
|
46
|
+
|
47
|
+
> api = RTrail::API.new("http://example.com/", "epierce", "myPa$$w0rd")
|
48
|
+
|
49
|
+
Some convenience methods are provided; for example:
|
50
|
+
|
51
|
+
# Get a list of all Projects
|
52
|
+
> api.projects
|
53
|
+
=> [#<RTrail::Project ...>, ..., #<RTrail::Project ...>]
|
54
|
+
|
55
|
+
# Get a single Project by name
|
56
|
+
> api.project("Web Applications")
|
57
|
+
=> #<RTrail::Project ...>
|
58
|
+
|
59
|
+
# Get a Suite within a Project
|
60
|
+
> api.suite("Web Applications", "Storefront")
|
61
|
+
=> #<RTrail::Suite ...>
|
62
|
+
|
63
|
+
Each Entity type provides wrappers for some of the API methods you're likely to
|
64
|
+
use; for instance:
|
65
|
+
|
66
|
+
> project = api.project("Web Applications")
|
67
|
+
> project.suites # GET /get_suites/<project_id>
|
68
|
+
> project.plans # GET /get_plans/<project_id>
|
69
|
+
> project.runs # GET /get_runs/<project_id>
|
70
|
+
|
71
|
+
> project.runs.count
|
72
|
+
=> 4
|
73
|
+
|
74
|
+
> project.runs.each { |run| run.id }
|
75
|
+
=> [103, 104, 119, 127]
|
76
|
+
|
77
|
+
> run = project.runs.first
|
78
|
+
|
79
|
+
> run.completed?
|
80
|
+
=> true
|
81
|
+
> run.summary
|
82
|
+
=> "103: Website Login [completed 2014-07-16 21:45:08 UTC]
|
83
|
+
|
84
|
+
|
85
|
+
Development
|
86
|
+
-----------
|
87
|
+
|
88
|
+
To contribute, please fork this repository, and submit a pull request with your
|
89
|
+
changes.
|
90
|
+
|
91
|
+
|
92
|
+
License
|
93
|
+
-------
|
94
|
+
|
95
|
+
MIT License.
|
96
|
+
|
data/lib/rtrail.rb
ADDED
data/lib/rtrail/api.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'client'
|
2
|
+
require_relative 'entity'
|
3
|
+
require_relative 'project'
|
4
|
+
|
5
|
+
module RTrail
|
6
|
+
# High-level API for TestRail
|
7
|
+
class API
|
8
|
+
def initialize(hostname, user, password)
|
9
|
+
url = hostname.gsub(/\/$/, '') + '/index.php?/api/v2/'
|
10
|
+
|
11
|
+
@client = RTrail::Client.new(url, user, password)
|
12
|
+
|
13
|
+
# FIXME: This is a bit hinky, but works well
|
14
|
+
RTrail::Entity.client = @client
|
15
|
+
end
|
16
|
+
attr_reader :client
|
17
|
+
|
18
|
+
# Convenience methods for commonly-accessed entities
|
19
|
+
|
20
|
+
def projects
|
21
|
+
return Project.all
|
22
|
+
end
|
23
|
+
|
24
|
+
def project(project_name)
|
25
|
+
return Project.by_name(project_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def suite(project_name, suite_name)
|
29
|
+
return Project.by_name(project_name).suite_by_name(suite_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def runs(project_name)
|
33
|
+
return project(project_name).runs
|
34
|
+
end
|
35
|
+
|
36
|
+
def cases(project_name, suite_name)
|
37
|
+
return suite(project_name, suite_name).cases
|
38
|
+
end
|
39
|
+
|
40
|
+
end # class API
|
41
|
+
end # module RTrail
|
42
|
+
|
data/lib/rtrail/case.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
require 'hashie'
|
6
|
+
|
7
|
+
require_relative 'exceptions'
|
8
|
+
|
9
|
+
module RTrail
|
10
|
+
class Client
|
11
|
+
def initialize(base_url, user, password)
|
12
|
+
@base_url = base_url
|
13
|
+
@user = user
|
14
|
+
@password = password
|
15
|
+
end
|
16
|
+
attr_reader :base_url, :user, :password
|
17
|
+
|
18
|
+
# Send a GET request to the API and return a Hashie::Mash (for individual
|
19
|
+
# object requests) or an Array of Hashie::Mash (for requests returning a
|
20
|
+
# list of objects).
|
21
|
+
#
|
22
|
+
# @param [String] path
|
23
|
+
# The API method to call including parameters (e.g. get_case/1)
|
24
|
+
#
|
25
|
+
def get(path)
|
26
|
+
_request(:get, path)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Send a POST request to the API and return a Hashie::Mash.
|
30
|
+
#
|
31
|
+
# @param [String] path
|
32
|
+
# The API method to call including parameters (e.g. add_case/1)
|
33
|
+
# @param [Hash] data
|
34
|
+
# The data to submit as part of the request. Strings must be UTF-8
|
35
|
+
# encoded.
|
36
|
+
#
|
37
|
+
def post(path, data)
|
38
|
+
_request(:post, path, data)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Send a GET or POST request to the given path, and return a
|
44
|
+
# Hashie::Mash or Array of Hashie::Mash.
|
45
|
+
#
|
46
|
+
# @param [Symbol] method
|
47
|
+
# :get or :post
|
48
|
+
#
|
49
|
+
def _request(method, path, data=nil)
|
50
|
+
url = URI.parse(@base_url + path)
|
51
|
+
url_with_query = "#{url.path}?#{url.query}"
|
52
|
+
|
53
|
+
if method == :post
|
54
|
+
request = Net::HTTP::Post.new(url_with_query)
|
55
|
+
request.body = JSON.dump(data)
|
56
|
+
else
|
57
|
+
request = Net::HTTP::Get.new(url_with_query)
|
58
|
+
end
|
59
|
+
|
60
|
+
request.basic_auth(@user, @password)
|
61
|
+
request.add_field("Content-Type", "application/json")
|
62
|
+
|
63
|
+
conn = Net::HTTP.new(url.host, url.port)
|
64
|
+
if url.scheme == "https"
|
65
|
+
conn.use_ssl = true
|
66
|
+
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
67
|
+
end
|
68
|
+
response = conn.request(request)
|
69
|
+
return _result(response)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Parse a Net::HTTPResponse as JSON, and return a Hashie::Mash,
|
73
|
+
# or an Array of Hashie::Mash.
|
74
|
+
#
|
75
|
+
# @raise [RTrail::Error]
|
76
|
+
# If the response code is anything but 200 OK, if the response cannot be
|
77
|
+
# parsed as JSON, or if the parsed JSON is neither a Hash nor Array.
|
78
|
+
#
|
79
|
+
def _result(response)
|
80
|
+
if response.body && !response.body.empty?
|
81
|
+
begin
|
82
|
+
result = JSON.parse(response.body)
|
83
|
+
rescue => ex
|
84
|
+
raise RTrail::Error.new(
|
85
|
+
"Error parsing JSON response: #{ex.message}")
|
86
|
+
end
|
87
|
+
else
|
88
|
+
result = {}
|
89
|
+
end
|
90
|
+
|
91
|
+
if response.code != "200"
|
92
|
+
if result && result.key?("error")
|
93
|
+
error = result["error"]
|
94
|
+
else
|
95
|
+
error = "Unknown error"
|
96
|
+
end
|
97
|
+
raise RTrail::Error.new(
|
98
|
+
"TestRail API returned HTTP #{response.code} (#{error})")
|
99
|
+
end
|
100
|
+
|
101
|
+
if result.is_a?(Hash)
|
102
|
+
return Hashie::Mash.new(result)
|
103
|
+
elsif result.is_a?(Array)
|
104
|
+
return result.map {|h| Hashie::Mash.new(h)}
|
105
|
+
else
|
106
|
+
raise RTrail::Error.new(
|
107
|
+
"Unexpected result type: #{result.class.name}")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end # class Client
|
112
|
+
end # module RTrail
|
113
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative 'helpers'
|
2
|
+
|
3
|
+
module RTrail
|
4
|
+
# Base class for RTrail objects
|
5
|
+
class Entity
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def client=(client)
|
10
|
+
@@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def client
|
14
|
+
@@client
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the plain lowercase name of the derived class,
|
18
|
+
# ex. "project", "suite", "case", suitable for passing
|
19
|
+
# to the `get_*` API methods.
|
20
|
+
def basename
|
21
|
+
return self.name.downcase.gsub(/^.*::/, '')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def client
|
26
|
+
return self.class.client
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(id_or_data)
|
30
|
+
if id_or_data.is_a?(Hash) || id_or_data.is_a?(Entity)
|
31
|
+
@data = Hashie::Mash.new(id_or_data)
|
32
|
+
else
|
33
|
+
@data = fetch(id_or_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
attr_accessor :data
|
37
|
+
|
38
|
+
# Fetch data for a derived class object.
|
39
|
+
def fetch(id)
|
40
|
+
return client.get("get_#{self.class.basename}/#{id}")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Pass-through to Hashie::Mash for attribute access
|
44
|
+
def method_missing(meth, *args, &block)
|
45
|
+
return data.send(meth, *args, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return a list of entities retrieved from `get_<thing>s/<id>`.
|
49
|
+
def get_entities(klass, parent_id, params={})
|
50
|
+
path = path_with_params(
|
51
|
+
"get_#{klass.basename}s/#{parent_id}", params)
|
52
|
+
|
53
|
+
return client.get(path).map do |data|
|
54
|
+
klass.new(data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add a new entity by invoking POST `add_<thing>/<id>`.
|
59
|
+
def add_entity(klass, parent_id, params={})
|
60
|
+
path = "add_#{klass.basename}/#{parent_id}"
|
61
|
+
return klass.new(client.post(path, params))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end # module RTrail
|
65
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RTrail
|
2
|
+
module Helpers
|
3
|
+
# Return true if `name_or_id` appears to be a numeric id,
|
4
|
+
# false otherwise.
|
5
|
+
def is_id?(name_or_id)
|
6
|
+
# Use !! to convert regex comparison to boolean
|
7
|
+
return !!(name_or_id.to_s =~ /^[0-9]+$/)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Return an HTTP path with parameters appended in GET syntax.
|
11
|
+
def path_with_params(path, params={})
|
12
|
+
if params.empty?
|
13
|
+
return path
|
14
|
+
else
|
15
|
+
# FIXME: What if value contains a space?
|
16
|
+
param_string = params.map do |k,v|
|
17
|
+
"#{k}=#{v}"
|
18
|
+
end.join("&")
|
19
|
+
return path + "&" + param_string
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Include this in any Entity with a `created_on` attribute
|
24
|
+
# to allow getting creation date as a Time instance.
|
25
|
+
module HasCreateTime
|
26
|
+
def create_time
|
27
|
+
return Time.at(self[:created_on].to_i).utc
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end # module Helpers
|
31
|
+
end # module RTrail
|
32
|
+
|