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.
Files changed (61) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +21 -0
  6. data/README.md +96 -0
  7. data/lib/rtrail.rb +19 -0
  8. data/lib/rtrail/api.rb +42 -0
  9. data/lib/rtrail/case.rb +8 -0
  10. data/lib/rtrail/client.rb +113 -0
  11. data/lib/rtrail/entity.rb +65 -0
  12. data/lib/rtrail/exceptions.rb +4 -0
  13. data/lib/rtrail/helpers.rb +32 -0
  14. data/lib/rtrail/plan.rb +7 -0
  15. data/lib/rtrail/project.rb +83 -0
  16. data/lib/rtrail/result.rb +9 -0
  17. data/lib/rtrail/run.rb +39 -0
  18. data/lib/rtrail/section.rb +7 -0
  19. data/lib/rtrail/suite.rb +26 -0
  20. data/lib/rtrail/test.rb +22 -0
  21. data/mock/app.rb +55 -0
  22. data/mock/views/add_result/1.yajl +4 -0
  23. data/mock/views/add_run/1.yajl +7 -0
  24. data/mock/views/error.yajl +3 -0
  25. data/mock/views/get_case/1.yajl +4 -0
  26. data/mock/views/get_cases/1.yajl +3 -0
  27. data/mock/views/get_plan/1.yajl +4 -0
  28. data/mock/views/get_plans/1.yajl +3 -0
  29. data/mock/views/get_project/1.yajl +4 -0
  30. data/mock/views/get_project/2.yajl +4 -0
  31. data/mock/views/get_projects.yajl +4 -0
  32. data/mock/views/get_result/1.yajl +4 -0
  33. data/mock/views/get_results/1.yajl +3 -0
  34. data/mock/views/get_run/1.yajl +7 -0
  35. data/mock/views/get_run/2.yajl +7 -0
  36. data/mock/views/get_runs/1.yajl +4 -0
  37. data/mock/views/get_section/1.yajl +9 -0
  38. data/mock/views/get_section/2.yajl +9 -0
  39. data/mock/views/get_sections/1.yajl +4 -0
  40. data/mock/views/get_suite/1.yajl +6 -0
  41. data/mock/views/get_suite/2.yajl +5 -0
  42. data/mock/views/get_suite/3.yajl +6 -0
  43. data/mock/views/get_suites/1.yajl +4 -0
  44. data/mock/views/get_suites/2.yajl +4 -0
  45. data/mock/views/get_test/1.yajl +4 -0
  46. data/mock/views/get_test/2.yajl +4 -0
  47. data/mock/views/get_tests/1.yajl +5 -0
  48. data/rtrail.gemspec +41 -0
  49. data/spec/api_spec.rb +46 -0
  50. data/spec/case_spec.rb +12 -0
  51. data/spec/client_spec.rb +43 -0
  52. data/spec/entity_spec.rb +12 -0
  53. data/spec/helpers_spec.rb +52 -0
  54. data/spec/plan_spec.rb +12 -0
  55. data/spec/project_spec.rb +132 -0
  56. data/spec/result_spec.rb +12 -0
  57. data/spec/run_spec.rb +61 -0
  58. data/spec/spec_helper.rb +19 -0
  59. data/spec/suite_spec.rb +29 -0
  60. data/spec/test_spec.rb +34 -0
  61. metadata +258 -0
@@ -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=
@@ -0,0 +1,4 @@
1
+ .ruby-version
2
+ .ruby-gemset
3
+ Gemfile.lock
4
+ coverage/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format doc --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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.
@@ -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
+
@@ -0,0 +1,19 @@
1
+ [
2
+ 'api',
3
+ 'case',
4
+ 'client',
5
+ 'entity',
6
+ 'exceptions',
7
+ 'plan',
8
+ 'project',
9
+ 'result',
10
+ 'run',
11
+ 'suite',
12
+ 'test'
13
+ ].each do |file|
14
+ require_relative "rtrail/#{file}"
15
+ end
16
+
17
+ module RTrail
18
+ end
19
+
@@ -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
+
@@ -0,0 +1,8 @@
1
+ require_relative 'entity'
2
+
3
+ module RTrail
4
+ class Case < Entity
5
+ include HasCreateTime
6
+ end
7
+ end # module RTrail
8
+
@@ -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,4 @@
1
+ module RTrail
2
+ class Error < StandardError; end
3
+ class NotFound < Error; end
4
+ end # module RTrail
@@ -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
+
@@ -0,0 +1,7 @@
1
+ require_relative 'entity'
2
+
3
+ module RTrail
4
+ class Plan < Entity
5
+ end
6
+ end # module RTrail
7
+