leantesting 1.0.0
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/Gemfile +9 -0
- data/LICENSE +10 -0
- data/README.md +348 -0
- data/Rakefile +9 -0
- data/leantesting.gemspec +20 -0
- data/lib/BaseClass/APIRequest.rb +195 -0
- data/lib/BaseClass/Entity.rb +33 -0
- data/lib/BaseClass/EntityHandler.rb +183 -0
- data/lib/BaseClass/EntityList.rb +188 -0
- data/lib/Entity/Bug/Bug.rb +11 -0
- data/lib/Entity/Bug/BugAttachment.rb +7 -0
- data/lib/Entity/Bug/BugComment.rb +7 -0
- data/lib/Entity/Platform/PlatformBrowser.rb +10 -0
- data/lib/Entity/Platform/PlatformBrowserVersion.rb +7 -0
- data/lib/Entity/Platform/PlatformDevice.rb +7 -0
- data/lib/Entity/Platform/PlatformOS.rb +10 -0
- data/lib/Entity/Platform/PlatformOSVersion.rb +7 -0
- data/lib/Entity/Platform/PlatformType.rb +10 -0
- data/lib/Entity/Project/Project.rb +27 -0
- data/lib/Entity/Project/ProjectBugScheme.rb +7 -0
- data/lib/Entity/Project/ProjectSection.rb +7 -0
- data/lib/Entity/Project/ProjectUser.rb +7 -0
- data/lib/Entity/Project/ProjectVersion.rb +7 -0
- data/lib/Entity/User/UserOrganization.rb +7 -0
- data/lib/Exception/BaseException/SDKException.rb +19 -0
- data/lib/Exception/SDKBadJSONResponseException.rb +15 -0
- data/lib/Exception/SDKDuplicateRequestException.rb +19 -0
- data/lib/Exception/SDKErrorResponseException.rb +13 -0
- data/lib/Exception/SDKIncompleteRequestException.rb +19 -0
- data/lib/Exception/SDKInvalidArgException.rb +15 -0
- data/lib/Exception/SDKMissingArgException.rb +15 -0
- data/lib/Exception/SDKUnexpectedResponseException.rb +15 -0
- data/lib/Exception/SDKUnsupportedRequestException.rb +19 -0
- data/lib/Handler/Attachment/AttachmentsHandler.rb +17 -0
- data/lib/Handler/Auth/OAuth2Handler.rb +118 -0
- data/lib/Handler/Bug/BugAttachmentsHandler.rb +51 -0
- data/lib/Handler/Bug/BugCommentsHandler.rb +20 -0
- data/lib/Handler/Bug/BugsHandler.rb +57 -0
- data/lib/Handler/Platform/PlatformBrowserVersionsHandler.rb +20 -0
- data/lib/Handler/Platform/PlatformBrowsersHandler.rb +23 -0
- data/lib/Handler/Platform/PlatformDevicesHandler.rb +10 -0
- data/lib/Handler/Platform/PlatformHandler.rb +22 -0
- data/lib/Handler/Platform/PlatformOSHandler.rb +23 -0
- data/lib/Handler/Platform/PlatformOSVersionsHandler.rb +20 -0
- data/lib/Handler/Platform/PlatformTypeDevicesHandler.rb +20 -0
- data/lib/Handler/Platform/PlatformTypesHandler.rb +21 -0
- data/lib/Handler/Project/ProjectBugReproducibilitySchemeHandler.rb +20 -0
- data/lib/Handler/Project/ProjectBugSeveritySchemeHandler.rb +20 -0
- data/lib/Handler/Project/ProjectBugStatusSchemeHandler.rb +20 -0
- data/lib/Handler/Project/ProjectBugTypeSchemeHandler.rb +20 -0
- data/lib/Handler/Project/ProjectBugsHandler.rb +67 -0
- data/lib/Handler/Project/ProjectSectionsHandler.rb +39 -0
- data/lib/Handler/Project/ProjectUsersHandler.rb +20 -0
- data/lib/Handler/Project/ProjectVersionsHandler.rb +39 -0
- data/lib/Handler/Project/ProjectsHandler.rb +46 -0
- data/lib/Handler/User/UserHandler.rb +14 -0
- data/lib/Handler/User/UserOrganizationsHandler.rb +14 -0
- data/lib/leantesting.rb +65 -0
- data/lib/loader.rb +18 -0
- data/tests/APIRequestTest.rb +152 -0
- data/tests/BaseClassesTest.rb +96 -0
- data/tests/ClientTest.rb +52 -0
- data/tests/EntitiesTest.rb +97 -0
- data/tests/EntityListTest.rb +51 -0
- data/tests/ExceptionsTest.rb +70 -0
- data/tests/HandlersRequestsTest.rb +215 -0
- data/tests/MockRequestsTest.rb +556 -0
- data/tests/OAuth2HandlerTest.rb +75 -0
- data/tests/res/upload_sample.jpg +0 -0
- metadata +169 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Represents a single Entity. All remote responses are decoded and parsed into one or more Entities.
|
3
|
+
#
|
4
|
+
|
5
|
+
class Entity
|
6
|
+
attr_accessor :data
|
7
|
+
|
8
|
+
@origin = nil # Reference to originating Client instance
|
9
|
+
@data = nil # Internal entity object data
|
10
|
+
|
11
|
+
#
|
12
|
+
# Constructs an Entity instance
|
13
|
+
#
|
14
|
+
# Arguments:
|
15
|
+
# origin Client -- Original client instance reference
|
16
|
+
# data Hash -- Data to be contained in the new Entity. Must be non-empty.
|
17
|
+
#
|
18
|
+
# Exceptions:
|
19
|
+
# SDKInvalidArgException if provided data param is not a hash.
|
20
|
+
# SDKInvalidArgException if provided data param is empty. Entities cannot be empty.
|
21
|
+
#
|
22
|
+
def initialize(origin, data)
|
23
|
+
if !data.is_a? Hash
|
24
|
+
raise SDKInvalidArgException, '`data` must be a hash'
|
25
|
+
elsif data.length.zero?
|
26
|
+
raise SDKInvalidArgException, '`data` must be non-empty'
|
27
|
+
end
|
28
|
+
|
29
|
+
@origin = origin
|
30
|
+
@data = data
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#
|
2
|
+
# An EntityHandler is the equivalent of a method centralizer for a corresponding endpoint (such as /v1/entities).
|
3
|
+
#
|
4
|
+
# Functional naming conventions and equivalents:
|
5
|
+
# create(fields) <=> `Create a new Entity`
|
6
|
+
# all(fields) <=> `List all Entities`
|
7
|
+
# find(id) <=> `Retrieve an existing Entity`
|
8
|
+
# delete(id) <=> `Delete an Entity`
|
9
|
+
# update(id, fields) <=> `Update an Entity`
|
10
|
+
#
|
11
|
+
class EntityHandler
|
12
|
+
|
13
|
+
#
|
14
|
+
# Constructs an EntityHandler instance
|
15
|
+
#
|
16
|
+
# Keyword arguments:
|
17
|
+
# origin Client -- Originating client reference
|
18
|
+
#
|
19
|
+
def initialize(origin)
|
20
|
+
@origin = origin # Reference to originating Client instance
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Function definition for creating a new entity. Base function checks for invalid parameters.
|
25
|
+
#
|
26
|
+
# Keyword arguments:
|
27
|
+
# fields Hash -- Non-empty hash consisting of entity data to send for adding
|
28
|
+
#
|
29
|
+
# Exceptions:
|
30
|
+
# SDKInvalidArgException if provided fields param is not a hash.
|
31
|
+
# SDKInvalidArgException if provided fields param is empty.
|
32
|
+
#
|
33
|
+
def create(fields)
|
34
|
+
if !fields.is_a? Hash
|
35
|
+
raise SDKInvalidArgException, '`fields` must be a hash'
|
36
|
+
elsif fields.length.zero?
|
37
|
+
raise SDKInvalidArgException, '`fields` must be non-empty'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Function definition for listing all entities. Base function checks for invalid parameters.
|
43
|
+
#
|
44
|
+
# Keyword arguments:
|
45
|
+
# filters Hash -- (optional) Filters to apply to restrict listing. Currently supported: limit, page
|
46
|
+
#
|
47
|
+
# Exceptions:
|
48
|
+
# SDKInvalidArgException if provided filters param is not a hash.
|
49
|
+
# SDKInvalidArgException if invalid filter value found in filters hash.
|
50
|
+
#
|
51
|
+
def all(filters = nil)
|
52
|
+
if !filters
|
53
|
+
filters = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
if !filters.is_a? Hash
|
57
|
+
raise SDKInvalidArgException, '`filters` must be a hash'
|
58
|
+
else
|
59
|
+
filters.each do |k,|
|
60
|
+
if !['include', 'limit', 'page'].include? k
|
61
|
+
raise SDKInvalidArgException, 'unsupported ' + k + ' for `filters`'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Function definition for retrieving an existing entity. Base function checks for invalid parameters.
|
70
|
+
#
|
71
|
+
# Keyword arguments:
|
72
|
+
# id Fixnum -- ID field to look for in the entity collection
|
73
|
+
#
|
74
|
+
# Exceptions:
|
75
|
+
# SDKInvalidArgException if provided id param is not an integer.
|
76
|
+
#
|
77
|
+
def find(id)
|
78
|
+
if !id.is_a? Fixnum
|
79
|
+
raise SDKInvalidArgException, '`id` must be of type Fixnum'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Function definition for deleting an existing entity. Base function checks for invalid parameters.
|
85
|
+
#
|
86
|
+
# Keyword arguments:
|
87
|
+
# id Fixnum -- ID field of entity to delete in the entity collection
|
88
|
+
#
|
89
|
+
# Exceptions:
|
90
|
+
# SDKInvalidArgException if provided id param is not an integer.
|
91
|
+
#
|
92
|
+
def delete(id)
|
93
|
+
if !id.is_a? Fixnum
|
94
|
+
raise SDKInvalidArgException, '`id` must be of type Fixnum'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Function definition for updating an existing entity. Base function checks for invalid parameters.
|
100
|
+
#
|
101
|
+
# Keyword arguments:
|
102
|
+
# id Fixnum -- ID field of entity to update in the entity collection
|
103
|
+
# fields Hash -- Non-empty dictionary consisting of entity data to send for update
|
104
|
+
#
|
105
|
+
# Exceptions:
|
106
|
+
# SDKInvalidArgException if provided id param is not an integer.
|
107
|
+
# SDKInvalidArgException if provided fields param is not a hash.
|
108
|
+
# SDKInvalidArgException if provided fields param is empty.
|
109
|
+
#
|
110
|
+
def update(id, fields)
|
111
|
+
if !id.is_a? Fixnum
|
112
|
+
raise SDKInvalidArgException, '`id` must be of type Fixnum'
|
113
|
+
elsif !fields.is_a? Hash
|
114
|
+
raise SDKInvalidArgException, '`fields` must be a hash'
|
115
|
+
elsif fields.length.zero?
|
116
|
+
raise SDKInvalidArgException, '`fields` must be non-empty'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
#
|
123
|
+
# Helper function that enforces a structure based on a supported table:
|
124
|
+
# - Forces use of REQUIRED fields
|
125
|
+
# - Detects duplicate fields
|
126
|
+
# - Detects unsupported fields
|
127
|
+
#
|
128
|
+
# Keyword arguments:
|
129
|
+
# obj Hash -- Hash to be enforced
|
130
|
+
# supports Hash -- Support table consisting of REQUIRED and OPTIONAL keys to be used in enforcing
|
131
|
+
#
|
132
|
+
# Exceptions:
|
133
|
+
# SDKUnsupportedRequestException if unsupported fields are found
|
134
|
+
# SDKIncompleteRequestException if any required field is missing
|
135
|
+
# SDKDuplicateRequestException if any duplicate field is found
|
136
|
+
#
|
137
|
+
def enforce(obj, supports)
|
138
|
+
sall = [] # All supported keys
|
139
|
+
sreq = [] # Mandatory supported keys
|
140
|
+
|
141
|
+
socc = supports.clone # Key use occurances
|
142
|
+
|
143
|
+
unsup = [] # Unsupported key list
|
144
|
+
dupl = [] # Duplicate key list
|
145
|
+
mreq = [] # Missing required keys
|
146
|
+
|
147
|
+
supports.each do |sk, sv|
|
148
|
+
if sv == true
|
149
|
+
sreq.push(sk)
|
150
|
+
end
|
151
|
+
|
152
|
+
sall.push(sk)
|
153
|
+
socc[sk] = 0
|
154
|
+
end
|
155
|
+
|
156
|
+
obj.each do |k,|
|
157
|
+
if sall.include? k
|
158
|
+
socc[k] += 1
|
159
|
+
else
|
160
|
+
unsup.push(k)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
socc.each do |ok, ov|
|
165
|
+
if ov > 1
|
166
|
+
dupl.push(ok)
|
167
|
+
elsif ov == 0 && sreq.include?(ok)
|
168
|
+
mreq.push(ok)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
if unsup.length.nonzero?
|
173
|
+
raise SDKUnsupportedRequestException, unsup
|
174
|
+
elsif mreq.length.nonzero?
|
175
|
+
raise SDKIncompleteRequestException, mreq
|
176
|
+
elsif dupl.length.nonzero?
|
177
|
+
raise SDKDuplicateRequestException, dupl
|
178
|
+
end
|
179
|
+
|
180
|
+
true
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
#
|
2
|
+
# An EntityList is a list of Entity objects, obtained from compiling the results of an all() call.
|
3
|
+
#
|
4
|
+
class EntityList
|
5
|
+
attr_reader :collection
|
6
|
+
|
7
|
+
@origin = nil # Reference to originating Client instance
|
8
|
+
|
9
|
+
@identifier = nil # Class definition identifier for the collection Entities
|
10
|
+
@collection = nil # Internal collection corresponding to current page
|
11
|
+
|
12
|
+
@request = nil # APIRequest definition to use for collection generation
|
13
|
+
@filters = nil # Filter list for generation (origins in Handler call)
|
14
|
+
|
15
|
+
@pagination = nil # Pagination object as per response (without links)
|
16
|
+
|
17
|
+
#
|
18
|
+
# Constructs an Entity List instance.
|
19
|
+
#
|
20
|
+
# Arguments:
|
21
|
+
# origin Client -- Original client instance reference
|
22
|
+
# request APIRequest -- An API Request definition given by the entity collection handler. This is used for any
|
23
|
+
# subsequent collection regeneration, as any data updates are dependant on external requests.
|
24
|
+
# identifier Class -- class definition to use for dynamic class instancing within array collection
|
25
|
+
# filters Hash -- original filters passed over from originating all() call
|
26
|
+
#
|
27
|
+
def initialize(origin, request, identifier, filters = nil)
|
28
|
+
if !filters
|
29
|
+
filters = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
@origin = origin
|
33
|
+
@request = request
|
34
|
+
@identifier = identifier
|
35
|
+
@filters = filters
|
36
|
+
|
37
|
+
generateCollectionData
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Sets iterator position to first page. Ignored if already on first page.
|
43
|
+
#
|
44
|
+
def first
|
45
|
+
if @pagination['current_page'] == 1
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
@filters['page'] = 1
|
50
|
+
generateCollectionData
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Sets iterator position to previous page. Ignored if on first page.
|
55
|
+
#
|
56
|
+
def previous
|
57
|
+
if @pagination['current_page'] == 1
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
|
61
|
+
@filters['page'] -=1
|
62
|
+
generateCollectionData
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Sets iterator position to next page. Ignored if on last page.
|
67
|
+
#
|
68
|
+
def next
|
69
|
+
if @pagination['current_page'] == @pagination['total_pages']
|
70
|
+
return false
|
71
|
+
end
|
72
|
+
|
73
|
+
if @filters.has_key? 'page'
|
74
|
+
@filters['page'] += 1
|
75
|
+
else
|
76
|
+
@filters['page'] = 2
|
77
|
+
end
|
78
|
+
|
79
|
+
generateCollectionData
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Sets iterator position to last page. Ignored if already on last page.
|
84
|
+
#
|
85
|
+
def last
|
86
|
+
if @pagination['current_page'] == @pagination['total_pages']
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
|
90
|
+
@filters['page'] = @pagination['total_pages']
|
91
|
+
generateCollectionData
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Internal loop handler for emulating enumerable functionality
|
96
|
+
#
|
97
|
+
def each
|
98
|
+
first
|
99
|
+
begin
|
100
|
+
yield toArray
|
101
|
+
end while self.next
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Outputs total number of Entities inside multi-page collection
|
106
|
+
#
|
107
|
+
# Returns:
|
108
|
+
# Fixnum -- Number of total Entities
|
109
|
+
#
|
110
|
+
def total
|
111
|
+
@pagination['total']
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Outputs total number of pages the multi-page collection has, regardful of limit/per_page
|
116
|
+
#
|
117
|
+
# Returns:
|
118
|
+
# Fixnum -- Number of total pages
|
119
|
+
#
|
120
|
+
def totalPages
|
121
|
+
@pagination['total_pages']
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Outputs number of Entities in current collection page. Will always be same as limmit/per_page if not on last page.
|
126
|
+
#
|
127
|
+
# Returns:
|
128
|
+
# Fixnum -- Number of Entities in page
|
129
|
+
#
|
130
|
+
def count
|
131
|
+
@pagination['count']
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Outputs internal collection in array format (converted from Entity objects)
|
136
|
+
#
|
137
|
+
# Returns:
|
138
|
+
# Array -- array of elements converted into hashes
|
139
|
+
#
|
140
|
+
def toArray
|
141
|
+
@collection.map{ |entity| entity.data }
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
#
|
147
|
+
# (Re)generates internal collection data based on current iteration position.
|
148
|
+
#
|
149
|
+
# Regeneration is done every time position changes (i.e. every time repositioning functions are used).
|
150
|
+
#
|
151
|
+
# Exceptions:
|
152
|
+
# SDKUnexpectedResponseException if no `meta` field is found
|
153
|
+
# SDKUnexpectedResponseException if no `pagination` field is found in `meta field`
|
154
|
+
# SDKUnexpectedResponseException if no collection set is found
|
155
|
+
# SDKUnexpectedResponseException if multiple collection sets are found
|
156
|
+
#
|
157
|
+
def generateCollectionData
|
158
|
+
@collection = [] # Clear previous collection data on fresh regeneration
|
159
|
+
@pagination = {} # Clear previous pagination data on fresh regeneration
|
160
|
+
|
161
|
+
@request.updateOpts({'params' => @filters})
|
162
|
+
raw = @request.exec
|
163
|
+
|
164
|
+
if !raw.has_key? 'meta'
|
165
|
+
raise SDKUnexpectedResponseException, 'missing `meta` field'
|
166
|
+
elsif !raw['meta'].has_key? 'pagination'
|
167
|
+
raise SDKUnexpectedResponseException, '`meta` missing `pagination` field'
|
168
|
+
end
|
169
|
+
|
170
|
+
if raw['meta']['pagination'].has_key? 'links'
|
171
|
+
raw['meta']['pagination'].delete('links') # Remove not needed links sub-data
|
172
|
+
end
|
173
|
+
|
174
|
+
@pagination = raw['meta']['pagination'] # Pass pagination data as per response meta key
|
175
|
+
raw.delete('meta')
|
176
|
+
|
177
|
+
if raw.length.zero?
|
178
|
+
raise SDKUnexpectedResponseException, 'collection object missing'
|
179
|
+
elsif raw.length > 1
|
180
|
+
cols = raw.map{ |k,| k }.join(', ')
|
181
|
+
raise SDKUnexpectedResponseException, 'expected one collection object, multiple received: ' + cols
|
182
|
+
end
|
183
|
+
|
184
|
+
classDef = @identifier # Definition to be used for dynamic Entity instancing
|
185
|
+
raw.values[0].each{ |entity| @collection.push(classDef.new(@origin, entity)) }
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|