rtrail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+