alm-rest-api 0.0.2
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.
- data/.gitattributes +22 -0
- data/.gitignore +163 -0
- data/Rakefile +8 -0
- data/alm-rest-api-0.0.1.gem +0 -0
- data/alm-rest-api.gemspec +14 -0
- data/lib/alm-rest-api.rb +154 -0
- data/lib/alm-rest-api/constants.rb +12 -0
- data/lib/alm-rest-api/defect-fields.rb +33 -0
- data/lib/alm-rest-api/entity.rb +30 -0
- data/lib/alm-rest-api/response.rb +11 -0
- data/lib/alm-rest-api/rest-connector.rb +147 -0
- data/lib/alm-rest-api/value-lists.rb +43 -0
- data/test/test_alm_rest_api.rb +118 -0
- metadata +77 -0
data/.gitattributes
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Auto detect text files and perform LF normalization
|
2
|
+
* text=auto
|
3
|
+
|
4
|
+
# Custom for Visual Studio
|
5
|
+
*.cs diff=csharp
|
6
|
+
*.sln merge=union
|
7
|
+
*.csproj merge=union
|
8
|
+
*.vbproj merge=union
|
9
|
+
*.fsproj merge=union
|
10
|
+
*.dbproj merge=union
|
11
|
+
|
12
|
+
# Standard to msysgit
|
13
|
+
*.doc diff=astextplain
|
14
|
+
*.DOC diff=astextplain
|
15
|
+
*.docx diff=astextplain
|
16
|
+
*.DOCX diff=astextplain
|
17
|
+
*.dot diff=astextplain
|
18
|
+
*.DOT diff=astextplain
|
19
|
+
*.pdf diff=astextplain
|
20
|
+
*.PDF diff=astextplain
|
21
|
+
*.rtf diff=astextplain
|
22
|
+
*.RTF diff=astextplain
|
data/.gitignore
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#################
|
2
|
+
## Eclipse
|
3
|
+
#################
|
4
|
+
|
5
|
+
*.pydevproject
|
6
|
+
.project
|
7
|
+
.metadata
|
8
|
+
bin/
|
9
|
+
tmp/
|
10
|
+
*.tmp
|
11
|
+
*.bak
|
12
|
+
*.swp
|
13
|
+
*~.nib
|
14
|
+
local.properties
|
15
|
+
.classpath
|
16
|
+
.settings/
|
17
|
+
.loadpath
|
18
|
+
|
19
|
+
# External tool builders
|
20
|
+
.externalToolBuilders/
|
21
|
+
|
22
|
+
# Locally stored "Eclipse launch configurations"
|
23
|
+
*.launch
|
24
|
+
|
25
|
+
# CDT-specific
|
26
|
+
.cproject
|
27
|
+
|
28
|
+
# PDT-specific
|
29
|
+
.buildpath
|
30
|
+
|
31
|
+
|
32
|
+
#################
|
33
|
+
## Visual Studio
|
34
|
+
#################
|
35
|
+
|
36
|
+
## Ignore Visual Studio temporary files, build results, and
|
37
|
+
## files generated by popular Visual Studio add-ons.
|
38
|
+
|
39
|
+
# User-specific files
|
40
|
+
*.suo
|
41
|
+
*.user
|
42
|
+
*.sln.docstates
|
43
|
+
|
44
|
+
# Build results
|
45
|
+
[Dd]ebug/
|
46
|
+
[Rr]elease/
|
47
|
+
*_i.c
|
48
|
+
*_p.c
|
49
|
+
*.ilk
|
50
|
+
*.meta
|
51
|
+
*.obj
|
52
|
+
*.pch
|
53
|
+
*.pdb
|
54
|
+
*.pgc
|
55
|
+
*.pgd
|
56
|
+
*.rsp
|
57
|
+
*.sbr
|
58
|
+
*.tlb
|
59
|
+
*.tli
|
60
|
+
*.tlh
|
61
|
+
*.tmp
|
62
|
+
*.vspscc
|
63
|
+
.builds
|
64
|
+
*.dotCover
|
65
|
+
|
66
|
+
## TODO: If you have NuGet Package Restore enabled, uncomment this
|
67
|
+
#packages/
|
68
|
+
|
69
|
+
# Visual C++ cache files
|
70
|
+
ipch/
|
71
|
+
*.aps
|
72
|
+
*.ncb
|
73
|
+
*.opensdf
|
74
|
+
*.sdf
|
75
|
+
|
76
|
+
# Visual Studio profiler
|
77
|
+
*.psess
|
78
|
+
*.vsp
|
79
|
+
|
80
|
+
# ReSharper is a .NET coding add-in
|
81
|
+
_ReSharper*
|
82
|
+
|
83
|
+
# Installshield output folder
|
84
|
+
[Ee]xpress
|
85
|
+
|
86
|
+
# DocProject is a documentation generator add-in
|
87
|
+
DocProject/buildhelp/
|
88
|
+
DocProject/Help/*.HxT
|
89
|
+
DocProject/Help/*.HxC
|
90
|
+
DocProject/Help/*.hhc
|
91
|
+
DocProject/Help/*.hhk
|
92
|
+
DocProject/Help/*.hhp
|
93
|
+
DocProject/Help/Html2
|
94
|
+
DocProject/Help/html
|
95
|
+
|
96
|
+
# Click-Once directory
|
97
|
+
publish
|
98
|
+
|
99
|
+
# Others
|
100
|
+
[Bb]in
|
101
|
+
[Oo]bj
|
102
|
+
sql
|
103
|
+
TestResults
|
104
|
+
*.Cache
|
105
|
+
ClientBin
|
106
|
+
stylecop.*
|
107
|
+
~$*
|
108
|
+
*.dbmdl
|
109
|
+
Generated_Code #added for RIA/Silverlight projects
|
110
|
+
|
111
|
+
# Backup & report files from converting an old project file to a newer
|
112
|
+
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
113
|
+
_UpgradeReport_Files/
|
114
|
+
Backup*/
|
115
|
+
UpgradeLog*.XML
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
############
|
120
|
+
## Windows
|
121
|
+
############
|
122
|
+
|
123
|
+
# Windows image file caches
|
124
|
+
Thumbs.db
|
125
|
+
|
126
|
+
# Folder config file
|
127
|
+
Desktop.ini
|
128
|
+
|
129
|
+
|
130
|
+
#############
|
131
|
+
## Python
|
132
|
+
#############
|
133
|
+
|
134
|
+
*.py[co]
|
135
|
+
|
136
|
+
# Packages
|
137
|
+
*.egg
|
138
|
+
*.egg-info
|
139
|
+
dist
|
140
|
+
build
|
141
|
+
eggs
|
142
|
+
parts
|
143
|
+
bin
|
144
|
+
var
|
145
|
+
sdist
|
146
|
+
develop-eggs
|
147
|
+
.installed.cfg
|
148
|
+
|
149
|
+
# Installer logs
|
150
|
+
pip-log.txt
|
151
|
+
|
152
|
+
# Unit test / coverage reports
|
153
|
+
.coverage
|
154
|
+
.tox
|
155
|
+
|
156
|
+
#Translations
|
157
|
+
*.mo
|
158
|
+
|
159
|
+
#Mr Developer
|
160
|
+
.mr.developer.cfg
|
161
|
+
|
162
|
+
# Mac crap
|
163
|
+
.DS_Store
|
data/Rakefile
ADDED
Binary file
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'alm-rest-api'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.date = '2012-12-14'
|
5
|
+
s.summary = "ALM!"
|
6
|
+
s.description = "ALM REST API Integration"
|
7
|
+
s.authors = ["Simon Zheng"]
|
8
|
+
s.email = 'xiaomengzheng@gmail.com'
|
9
|
+
s.require_paths = ["lib"]
|
10
|
+
s.files = `git ls-files`.split("\n")
|
11
|
+
#s.files = ["lib/alm-rest-api.rb"]
|
12
|
+
s.homepage =
|
13
|
+
'http://rubygems.org/gems/alm-rest-api'
|
14
|
+
end
|
data/lib/alm-rest-api.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# alm-rest-api.rb
|
2
|
+
|
3
|
+
module ALM
|
4
|
+
|
5
|
+
# Logging in to our system is standard http login (basic authentication),
|
6
|
+
# where one must store the returned cookies for further use.
|
7
|
+
def self.login(loginUrl, username, password)
|
8
|
+
response = RestConnector.instance.httpBasicAuth(loginUrl, username, password)
|
9
|
+
|
10
|
+
return response.statusCode == '200'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logout()
|
14
|
+
# note the get operation logs us out by setting authentication cookies to:
|
15
|
+
# LWSSO_COOKIE_KEY="" via server response header Set-Cookie
|
16
|
+
logoutUrl = RestConnector.instance.buildUrl("qcbin/authentication-point/logout")
|
17
|
+
response = RestConnector.instance.httpGet(logoutUrl, nil, nil)
|
18
|
+
|
19
|
+
return response.statusCode == '200'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.isAuthenticated()
|
23
|
+
isAuthenticateUrl = RestConnector.instance.buildUrl("qcbin/rest/is-authenticated")
|
24
|
+
response = RestConnector.instance.httpGet(isAuthenticateUrl, nil, nil)
|
25
|
+
responseCode = response.statusCode
|
26
|
+
|
27
|
+
# if already authenticated
|
28
|
+
# if not authenticated - get the address where to authenticate
|
29
|
+
# via WWW-Authenticate
|
30
|
+
if responseCode == "200"
|
31
|
+
ret = nil
|
32
|
+
elsif responseCode == "401"
|
33
|
+
authenticationHeader = response.responseHeaders["WWW-Authenticate"]
|
34
|
+
newUrl = authenticationHeader.split("=").at(1)
|
35
|
+
newUrl = newUrl.delete("\"")
|
36
|
+
newUrl = newUrl + "/authenticate"
|
37
|
+
ret = newUrl
|
38
|
+
end
|
39
|
+
|
40
|
+
return ret
|
41
|
+
end
|
42
|
+
|
43
|
+
# convenience method to do user login
|
44
|
+
def self.isLoggedIn(username, password)
|
45
|
+
authenticationPoint = isAuthenticated();
|
46
|
+
if (authenticationPoint != nil)
|
47
|
+
return login(authenticationPoint, username, password)
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
|
52
|
+
# read all defects fields
|
53
|
+
def self.getDefectFields(required = false)
|
54
|
+
defectFieldsUrl = RestConnector.instance.buildEntityCollectionUrl("customization/entities/defect/field")
|
55
|
+
queryString = nil
|
56
|
+
if required
|
57
|
+
queryString = "required=true"
|
58
|
+
end
|
59
|
+
requestHeaders = Hash.new
|
60
|
+
requestHeaders["Accept"] = "application/xml"
|
61
|
+
response = RestConnector.instance.httpGet(defectFieldsUrl, queryString, requestHeaders)
|
62
|
+
|
63
|
+
defectFields = nil
|
64
|
+
if response.statusCode == '200'
|
65
|
+
defectFields = DefectFields::Fields.parse(response.toString())
|
66
|
+
end
|
67
|
+
|
68
|
+
return defectFields
|
69
|
+
end
|
70
|
+
|
71
|
+
# read pre-defined defects fields values
|
72
|
+
def self.getValueLists(defectFields = nil)
|
73
|
+
# ALM 11.0 the url is "customization/list"
|
74
|
+
valueListsUrl = RestConnector.instance.buildEntityCollectionUrl("customization/list")
|
75
|
+
queryString = nil
|
76
|
+
if defectFields
|
77
|
+
defectFields.fields.each do |field|
|
78
|
+
if field.list_id
|
79
|
+
if queryString == nil
|
80
|
+
queryString = "id=" + field.list_id.to_s
|
81
|
+
else
|
82
|
+
queryString = queryString + "," + field.list_id.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
requestHeaders = Hash.new
|
89
|
+
requestHeaders["Accept"] = "application/xml"
|
90
|
+
response = RestConnector.instance.httpGet(valueListsUrl, queryString, requestHeaders)
|
91
|
+
|
92
|
+
valueLists = nil
|
93
|
+
if response.statusCode == '200'
|
94
|
+
valueLists = ValueLists::Lists.parse(response.toString())
|
95
|
+
end
|
96
|
+
|
97
|
+
return valueLists
|
98
|
+
end
|
99
|
+
|
100
|
+
# create new defect
|
101
|
+
def self.createDefect(defect)
|
102
|
+
defectsUrl = RestConnector.instance.buildEntityCollectionUrl("defect")
|
103
|
+
requestHeaders = Hash.new
|
104
|
+
requestHeaders["Content-Type"] = "application/xml"
|
105
|
+
requestHeaders["Accept"] = "application/xml"
|
106
|
+
|
107
|
+
response = RestConnector.instance.httpPost(defectsUrl, defect.to_xml, requestHeaders)
|
108
|
+
|
109
|
+
defectId = nil
|
110
|
+
if response.statusCode == '201'
|
111
|
+
defectUrl = response.responseHeaders["Location"]
|
112
|
+
defectId = defectUrl.split('/').last
|
113
|
+
end
|
114
|
+
|
115
|
+
return defectId
|
116
|
+
end
|
117
|
+
|
118
|
+
# delete a defect
|
119
|
+
def self.deleteDefect(defectId)
|
120
|
+
defectUrl = RestConnector.instance.buildDefectUrl(defectId)
|
121
|
+
requestHeaders = Hash.new
|
122
|
+
requestHeaders["Accept"] = "application/xml"
|
123
|
+
|
124
|
+
response = RestConnector.instance.httpDelete(defectUrl, requestHeaders)
|
125
|
+
|
126
|
+
return response.statusCode == '200'
|
127
|
+
end
|
128
|
+
|
129
|
+
# attach a file
|
130
|
+
def self.attachWithMultipart(defectId, filePath)
|
131
|
+
attachmentUrl = RestConnector.instance.buildEntityCollectionUrl("attachment")
|
132
|
+
boundary = "AaB03x"
|
133
|
+
requestHeaders = Hash.new
|
134
|
+
requestHeaders["Content-Type"] = "multipart/form-data, boundary=#{boundary}"
|
135
|
+
|
136
|
+
#post_body = []
|
137
|
+
#post_body < < "--#{boundary}rn"
|
138
|
+
#post_body < < "Content-Disposition: form-data; name="datafile"; filename="#{File.basename(file)}"rn"
|
139
|
+
#post_body < < "Content-Type: text/plainrn"
|
140
|
+
#post_body < < "rn"
|
141
|
+
#post_body < < File.read(file)
|
142
|
+
#post_body < < "rn--#{boundary}--rn"
|
143
|
+
|
144
|
+
#Response response = RestConnector.instance.httpPost(attachmentUrl, post_body, requestHeaders)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
require 'alm-rest-api/entity'
|
150
|
+
require 'alm-rest-api/defect-fields'
|
151
|
+
require 'alm-rest-api/value-lists'
|
152
|
+
require 'alm-rest-api/constants'
|
153
|
+
require 'alm-rest-api/response'
|
154
|
+
require 'alm-rest-api/rest-connector'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# These constants are used throughout the code to set the server to work with.
|
2
|
+
# To execute this code, change these settings to fit
|
3
|
+
# those of your server.
|
4
|
+
class ALM::Constants
|
5
|
+
HOST = "localhost"
|
6
|
+
PORT = "8080"
|
7
|
+
USERNAME = "sa"
|
8
|
+
PASSWORD = "C71a04t23"
|
9
|
+
DOMAIN = "DEFAULT"
|
10
|
+
PROJECT = "Simon"
|
11
|
+
VERSIONED = true
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
module DefectFields
|
4
|
+
|
5
|
+
class Field
|
6
|
+
include HappyMapper
|
7
|
+
|
8
|
+
tag 'Field'
|
9
|
+
attribute :physical_name, String, :tag => 'PhysicalName'
|
10
|
+
attribute :name, String, :tag => 'Name'
|
11
|
+
attribute :label, String, :tag => 'Label'
|
12
|
+
element :active, Boolean, :tag => 'Active'
|
13
|
+
element :editable, Boolean, :tag => 'Editable'
|
14
|
+
element :size, Integer, :tag => 'Size'
|
15
|
+
element :filterable, Boolean, :tag => 'Filterable'
|
16
|
+
element :groupable, Boolean, :tag => 'Groupable'
|
17
|
+
element :history, Boolean, :tag => 'History'
|
18
|
+
element :list_id, Integer, :tag => 'List-Id'
|
19
|
+
element :type, String, :tag => 'Type'
|
20
|
+
element :supports_multivalue, Boolean, :tag => 'SupportsMultivalue'
|
21
|
+
element :required, Boolean, :tag => 'Required'
|
22
|
+
element :system, Boolean, :tag => 'System'
|
23
|
+
element :verify, Boolean, :tag => 'Verify'
|
24
|
+
element :virtual, Boolean, :tag => 'Virtual'
|
25
|
+
end
|
26
|
+
|
27
|
+
class Fields
|
28
|
+
include HappyMapper
|
29
|
+
|
30
|
+
tag 'Fields'
|
31
|
+
has_many :fields, Field
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
class Field
|
4
|
+
include HappyMapper
|
5
|
+
|
6
|
+
tag 'Field'
|
7
|
+
attribute :name, String, :tag => 'Name'
|
8
|
+
element :value, String, :tag => 'Value'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Fields
|
12
|
+
include HappyMapper
|
13
|
+
|
14
|
+
tag 'Fields'
|
15
|
+
has_many :fields, Field
|
16
|
+
end
|
17
|
+
|
18
|
+
class Entity
|
19
|
+
include HappyMapper
|
20
|
+
|
21
|
+
def initialize(type)
|
22
|
+
@type = type
|
23
|
+
@fields = Fields.new
|
24
|
+
@fields.fields = Array.new
|
25
|
+
end
|
26
|
+
|
27
|
+
tag 'Entity'
|
28
|
+
attribute :type, String, :tag => 'Type'
|
29
|
+
element :fields, Fields
|
30
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# rest-connector.rb
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'stringio'
|
6
|
+
require 'singleton'
|
7
|
+
|
8
|
+
class ALM::RestConnector
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_accessor :cookies
|
12
|
+
attr_accessor :host
|
13
|
+
attr_accessor :port
|
14
|
+
attr_accessor :domain
|
15
|
+
attr_accessor :project
|
16
|
+
|
17
|
+
def init(cookies, host, port, domain, project)
|
18
|
+
@cookies = cookies
|
19
|
+
@host = host
|
20
|
+
@port = port
|
21
|
+
@domain = domain
|
22
|
+
@project = project
|
23
|
+
end
|
24
|
+
|
25
|
+
def buildEntityCollectionUrl(entityType)
|
26
|
+
return buildUrl('qcbin/rest/domains/' + domain + '/projects/' + project + '/' + entityType + 's')
|
27
|
+
end
|
28
|
+
|
29
|
+
def buildDefectUrl(defectId)
|
30
|
+
return buildUrl('qcbin/rest/domains/' + domain + '/projects/' + project + '/defects/' + defectId)
|
31
|
+
end
|
32
|
+
|
33
|
+
def buildUrl(path)
|
34
|
+
return "http://#{host}:#{port}/#{path}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def httpPut(url, data, headers)
|
38
|
+
return doHttp('PUT', url, nil, data, headers, cookies)
|
39
|
+
end
|
40
|
+
|
41
|
+
def httpPost(url, data, headers)
|
42
|
+
return doHttp('POST', url, nil, data, headers, cookies)
|
43
|
+
end
|
44
|
+
|
45
|
+
def httpDelete(url, headers)
|
46
|
+
return doHttp('DELETE', url, nil, nil, headers, cookies)
|
47
|
+
end
|
48
|
+
|
49
|
+
def httpGet(url, queryString, headers)
|
50
|
+
return doHttp('GET', url, queryString, nil, headers, cookies)
|
51
|
+
end
|
52
|
+
|
53
|
+
def httpBasicAuth(url, username, password)
|
54
|
+
headers = {"username" => username, "password" => password}
|
55
|
+
return doHttp('AUTH', url, nil, nil, headers, cookies)
|
56
|
+
end
|
57
|
+
|
58
|
+
def getCookieString
|
59
|
+
s = StringIO.new
|
60
|
+
if (!cookies.empty?)
|
61
|
+
cookies.each{|key,value|
|
62
|
+
s << key << '=' << value << ';'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
return s.string
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def doHttp(type, url, queryString, data, headers, cookies)
|
72
|
+
if (queryString != nil && !queryString.empty?)
|
73
|
+
url.concat('?' + queryString)
|
74
|
+
end
|
75
|
+
|
76
|
+
uri = URI.parse(url)
|
77
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
78
|
+
|
79
|
+
case type
|
80
|
+
when "POST"
|
81
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
82
|
+
when "GET"
|
83
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
84
|
+
when "PUT"
|
85
|
+
request = Net::HTTP::Put.new(uri.request_uri)
|
86
|
+
when "DELETE"
|
87
|
+
request = Net::HTTP::Delete.new(uri.request_uri)
|
88
|
+
when "AUTH"
|
89
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
90
|
+
request.basic_auth(headers["username"], headers["password"])
|
91
|
+
end
|
92
|
+
|
93
|
+
cookieString = getCookieString()
|
94
|
+
prepareHttpRequest(request, headers, data, cookieString)
|
95
|
+
|
96
|
+
response = http.request(request)
|
97
|
+
|
98
|
+
res = retrieveHtmlResponse(response)
|
99
|
+
updateCookies(res)
|
100
|
+
|
101
|
+
return res
|
102
|
+
end
|
103
|
+
|
104
|
+
def prepareHttpRequest(request, headers, data, cookieString)
|
105
|
+
contentType = nil
|
106
|
+
if (cookieString != nil && !cookieString.empty?)
|
107
|
+
request["Cookie"] = cookieString
|
108
|
+
end
|
109
|
+
|
110
|
+
if (headers != nil)
|
111
|
+
contentType = headers.delete("Content-Type")
|
112
|
+
headers.each{|key, value|
|
113
|
+
request[key] = value
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
if (data != nil)
|
118
|
+
if (contentType != nil)
|
119
|
+
request["Content-Type"] = contentType
|
120
|
+
request.body = data
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def retrieveHtmlResponse(response)
|
126
|
+
res = ALM::Response.new
|
127
|
+
res.statusCode = response.code
|
128
|
+
res.responseHeaders = response
|
129
|
+
res.responseData = response.body
|
130
|
+
|
131
|
+
return res
|
132
|
+
end
|
133
|
+
|
134
|
+
def updateCookies(response)
|
135
|
+
newCookies = response.responseHeaders.get_fields('Set-Cookie')
|
136
|
+
if (newCookies != nil)
|
137
|
+
newCookies.each{|cookie|
|
138
|
+
c1 = cookie.split(';')[0]
|
139
|
+
c2 = c1.split('=')
|
140
|
+
key = c2[0]
|
141
|
+
value = c2[1]
|
142
|
+
cookies[key] = value
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'happymapper'
|
2
|
+
|
3
|
+
module ValueLists
|
4
|
+
|
5
|
+
class Item
|
6
|
+
include HappyMapper
|
7
|
+
|
8
|
+
tag 'Item'
|
9
|
+
attribute :value, String, :tag => 'value'
|
10
|
+
end
|
11
|
+
|
12
|
+
class Items
|
13
|
+
include HappyMapper
|
14
|
+
|
15
|
+
tag 'Items'
|
16
|
+
has_many :items, Item
|
17
|
+
end
|
18
|
+
|
19
|
+
class List
|
20
|
+
include HappyMapper
|
21
|
+
|
22
|
+
tag 'List'
|
23
|
+
element :name, String, :tag => 'Name'
|
24
|
+
element :id, Integer, :tag => 'Id'
|
25
|
+
has_one :items, Items
|
26
|
+
end
|
27
|
+
|
28
|
+
class Lists
|
29
|
+
include HappyMapper
|
30
|
+
|
31
|
+
tag 'Lists'
|
32
|
+
has_many :lists, List
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.getValuesById(id, valueLists)
|
36
|
+
valueLists.lists.each do |list|
|
37
|
+
if (list.id == id)
|
38
|
+
return list.items.items
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'alm-rest-api'
|
4
|
+
|
5
|
+
class TestALMRestAPI < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
ALM::RestConnector.instance.init(Hash.new,
|
9
|
+
ALM::Constants::HOST,
|
10
|
+
ALM::Constants::PORT,
|
11
|
+
ALM::Constants::DOMAIN,
|
12
|
+
ALM::Constants::PROJECT)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_AuthenticateLoginLogout
|
16
|
+
if false # change to true if you want run the test case
|
17
|
+
# Returns nil if authenticated. If not authenticated, returns
|
18
|
+
# a URL indicating where to login.
|
19
|
+
# We are not logged in, so call returns a URL
|
20
|
+
authenticationPoint = ALM.isAuthenticated()
|
21
|
+
assert_not_nil(authenticationPoint, "response from isAuthenticated means we're authenticated. that can't be.")
|
22
|
+
|
23
|
+
# now we login to previously returned URL.
|
24
|
+
loginResponse = ALM.login(authenticationPoint, ALM::Constants::USERNAME, ALM::Constants::PASSWORD)
|
25
|
+
assert(loginResponse, "failed to login.")
|
26
|
+
assert((ALM::RestConnector.instance.getCookieString.include? "LWSSO_COOKIE_KEY"), "login did not cause creation of Light Weight Single Sign On(LWSSO) cookie.")
|
27
|
+
|
28
|
+
# proof that we are indeed logged in
|
29
|
+
assert_nil(ALM.isAuthenticated(), "isAuthenticated returned not authenticated after login.")
|
30
|
+
|
31
|
+
# and now we logout
|
32
|
+
ALM.logout()
|
33
|
+
|
34
|
+
# And now we can see that we are indeed logged out
|
35
|
+
# because isAuthenticated once again returns a url, and not null.
|
36
|
+
assert_not_nil(ALM.isAuthenticated(), "isAuthenticated returned authenticated after logout.")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_GetDefectFields
|
41
|
+
if false # change to true if you want run the test case
|
42
|
+
loginResponse = ALM.isLoggedIn(ALM::Constants::USERNAME, ALM::Constants::PASSWORD)
|
43
|
+
assert(loginResponse, "failed to login.")
|
44
|
+
|
45
|
+
defectFields = ALM.getDefectFields(true)
|
46
|
+
valueLists = ALM.getValueLists(defectFields)
|
47
|
+
if defectFields
|
48
|
+
defectFields.fields.each do |field|
|
49
|
+
puts "Name = #{field.name}"
|
50
|
+
puts "Label = #{field.label}"
|
51
|
+
puts "Size = #{field.size}"
|
52
|
+
puts "Type = #{field.type}"
|
53
|
+
puts "Required = #{field.required}"
|
54
|
+
if (field.list_id && valueLists)
|
55
|
+
items = ValueLists.getValuesById(field.list_id, valueLists)
|
56
|
+
items.each do |item|
|
57
|
+
puts "Value = #{item.value}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
puts "--------------------------"
|
61
|
+
end
|
62
|
+
|
63
|
+
puts defectFields.to_xml
|
64
|
+
if valueLists
|
65
|
+
puts valueLists.to_xml
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
ALM.logout()
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_CreateDeleteDefect
|
74
|
+
if true # change to true if you want run the test case
|
75
|
+
loginResponse = ALM.isLoggedIn(ALM::Constants::USERNAME, ALM::Constants::PASSWORD)
|
76
|
+
assert(loginResponse, "failed to login.")
|
77
|
+
|
78
|
+
defect = Entity.new("defect")
|
79
|
+
|
80
|
+
defectFields = ALM.getDefectFields(true)
|
81
|
+
valueLists = ALM.getValueLists(defectFields)
|
82
|
+
if defectFields
|
83
|
+
defectFields.fields.each do |field|
|
84
|
+
defectField = Field.new
|
85
|
+
defectField.name = field.name
|
86
|
+
type = field.type
|
87
|
+
case type
|
88
|
+
when "LookupList"
|
89
|
+
if valueLists
|
90
|
+
items = ValueLists.getValuesById(field.list_id, valueLists)
|
91
|
+
defectField.value = items.first.value
|
92
|
+
end
|
93
|
+
when "Date"
|
94
|
+
defectField.value = Time.now.strftime("%Y-%m-%d")
|
95
|
+
when "UsersList"
|
96
|
+
defectField.value = ALM::Constants::USERNAME
|
97
|
+
else
|
98
|
+
defectField.value = "0"
|
99
|
+
end
|
100
|
+
defect.fields.fields<<defectField
|
101
|
+
end
|
102
|
+
|
103
|
+
puts defect.to_xml
|
104
|
+
|
105
|
+
defectId = ALM.createDefect(defect)
|
106
|
+
assert_not_nil(defectId, "fail to create defect.")
|
107
|
+
|
108
|
+
if defectId
|
109
|
+
deleteResponse = ALM.deleteDefect(defectId)
|
110
|
+
assert(deleteResponse, "failed to delete defect.")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
ALM.logout()
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alm-rest-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Simon Zheng
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-12-14 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: ALM REST API Integration
|
22
|
+
email: xiaomengzheng@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- .gitattributes
|
31
|
+
- .gitignore
|
32
|
+
- Rakefile
|
33
|
+
- alm-rest-api-0.0.1.gem
|
34
|
+
- alm-rest-api.gemspec
|
35
|
+
- lib/alm-rest-api.rb
|
36
|
+
- lib/alm-rest-api/constants.rb
|
37
|
+
- lib/alm-rest-api/defect-fields.rb
|
38
|
+
- lib/alm-rest-api/entity.rb
|
39
|
+
- lib/alm-rest-api/response.rb
|
40
|
+
- lib/alm-rest-api/rest-connector.rb
|
41
|
+
- lib/alm-rest-api/value-lists.rb
|
42
|
+
- test/test_alm_rest_api.rb
|
43
|
+
homepage: http://rubygems.org/gems/alm-rest-api
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.10
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: ALM!
|
76
|
+
test_files: []
|
77
|
+
|