diff --git a/.gitignore b/.gitignore
index f2c1360..a30a7ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@
/test/version_tmp/
/tmp/
+.DS_Store
+
## Specific to RubyMotion:
.dat*
.repl_history
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..0d786ba
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,3 @@
+--color
+--warnings
+--require spec_helper
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..e707d0b
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,8 @@
+gem 'multi_json'
+gem 'oj'
+gem 'faraday'
+
+group :test do
+ gem 'rspec'
+ gem 'webmock'
+end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..e9612f3
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,37 @@
+GEM
+ specs:
+ addressable (2.3.6)
+ crack (0.4.2)
+ safe_yaml (~> 1.0.0)
+ diff-lcs (1.2.5)
+ faraday (0.9.0)
+ multipart-post (>= 1.2, < 3)
+ multi_json (1.10.1)
+ multipart-post (2.0.0)
+ oj (2.9.8)
+ rspec (3.0.0)
+ rspec-core (~> 3.0.0)
+ rspec-expectations (~> 3.0.0)
+ rspec-mocks (~> 3.0.0)
+ rspec-core (3.0.4)
+ rspec-support (~> 3.0.0)
+ rspec-expectations (3.0.4)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.0.0)
+ rspec-mocks (3.0.4)
+ rspec-support (~> 3.0.0)
+ rspec-support (3.0.4)
+ safe_yaml (1.0.3)
+ webmock (1.18.0)
+ addressable (>= 2.3.6)
+ crack (>= 0.3.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ faraday
+ multi_json
+ oj
+ rspec
+ webmock
diff --git a/lib/wsapi/models/object.rb b/lib/wsapi/models/object.rb
new file mode 100644
index 0000000..65e7344
--- /dev/null
+++ b/lib/wsapi/models/object.rb
@@ -0,0 +1,33 @@
+module Wsapi
+ class Object
+ attr_reader :raw_data
+
+ def initialize(raw_data)
+ @raw_data = raw_data
+ end
+
+ def name
+ @raw_data['_refObjectName']
+ end
+
+ def id
+ @raw_data['ObjectID']
+ end
+
+ def url
+ @raw_data['_ref']
+ end
+
+ def workspace
+ @raw_data["Workspace"]["_refObjectName"]
+ end
+
+ def self.from_data(type, raw_data)
+ if type && Wsapi.const_defined?(type)
+ Wsapi.const_get(type).new(raw_data)
+ else
+ Object.new(raw_data)
+ end
+ end
+ end
+end
diff --git a/lib/wsapi/models/project.rb b/lib/wsapi/models/project.rb
new file mode 100644
index 0000000..73a9d70
--- /dev/null
+++ b/lib/wsapi/models/project.rb
@@ -0,0 +1,7 @@
+module Wsapi
+ class Project < Wsapi::Object
+ def subscription
+ @subscription ||= Wsapi::Subscription.new(@raw_data["Subscription"])
+ end
+ end
+end
diff --git a/lib/wsapi/models/subscription.rb b/lib/wsapi/models/subscription.rb
new file mode 100644
index 0000000..9a0a6af
--- /dev/null
+++ b/lib/wsapi/models/subscription.rb
@@ -0,0 +1,7 @@
+module Wsapi
+ class Subscription < Wsapi::Object
+ def subscription_id
+ @raw_data["SubscriptionID"].to_s
+ end
+ end
+end
diff --git a/lib/wsapi/models/user.rb b/lib/wsapi/models/user.rb
new file mode 100644
index 0000000..4329d68
--- /dev/null
+++ b/lib/wsapi/models/user.rb
@@ -0,0 +1,27 @@
+module Wsapi
+ class User < Wsapi::Object
+ def username
+ @raw_data["UserName"]
+ end
+
+ def first_name
+ @raw_data["FirstName"]
+ end
+
+ def last_name
+ @raw_data["LastName"]
+ end
+
+ def name
+ "#{@raw_data['FirstName']} #{@raw_data['LastName']}"
+ end
+
+ def email
+ @raw_data["EmailAddress"]
+ end
+
+ def admin?
+ @raw_data["SubscriptionAdmin"]
+ end
+ end
+end
diff --git a/lib/wsapi/session.rb b/lib/wsapi/session.rb
new file mode 100644
index 0000000..d23a190
--- /dev/null
+++ b/lib/wsapi/session.rb
@@ -0,0 +1,192 @@
+require 'oj'
+require 'multi_json'
+require 'faraday'
+
+require 'wsapi/models/object'
+require 'wsapi/models/subscription'
+require 'wsapi/models/user'
+require 'wsapi/models/project'
+
+module Wsapi
+ class StandardErrorWithResponse < StandardError
+ attr_reader :response
+ def initialize(msg, response = nil)
+ @response = response
+ super(msg)
+ end
+ end
+ class AuthorizationError < StandardErrorWithResponse; end
+ class ApiError < StandardErrorWithResponse; end
+ class ObjectNotFoundError < StandardErrorWithResponse; end
+ class IpAddressLimited < StandardErrorWithResponse; end
+
+ WSAPI_URL = ENV['WSAPI_URL'] || ''
+
+ class ZuulAuthentication < Faraday::Middleware
+ def initialize(logger, zuul_session_id)
+ @zuul_session_id = zuul_session_id
+ super(logger)
+ end
+
+ def call(env)
+ env[:request_headers]['ZSESSIONID'] = @zuul_session_id
+ @app.call(env)
+ end
+ end
+
+ class Mapper
+ def self.get_errors(json)
+ if result = json["QueryResult"]
+ result["Errors"]
+ elsif result = json["OperationResult"]
+ result["Errors"]
+ else
+ []
+ end
+ end
+
+ def self.get_object(response)
+ json = MultiJson.load(response.body)
+ if get_errors(json).empty? && json.size == 1
+ Wsapi::Object.from_data(json.keys.first, json.values.first)
+ else
+ raise ApiError.new("Errors: #{get_errors(json).inspect}", response)
+ end
+ rescue MultiJson::LoadError, Oj::ParseError
+ raise ApiError.new("Invalid JSON response from WSAPI: #{response.body}", response)
+ end
+
+ def self.get_objects(response)
+ json = MultiJson.load(response.body)
+ if get_errors(json).empty? && query_result = json["QueryResult"]
+ query_result["Results"].map { |object| Wsapi::Object.from_data(object["_type"], object) }
+ else
+ raise ApiError.new("Errors: #{get_errors(json).inspect}", response)
+ end
+ rescue MultiJson::LoadError, Oj::ParseError
+ raise ApiError.new("Invalid JSON response from WSAPI: #{response.body}", response)
+ end
+ end
+
+ class Session
+ def initialize(session_id, opts = {})
+ @api_version = opts[:version] || "3.0"
+ @session_id = session_id
+ @workspace_id = opts[:workspace_id]
+ @conn = Faraday.new(ssl: { verify: false} ) do |faraday|
+ faraday.request :url_encoded # form-encode POST params
+ faraday.use ZuulAuthentication, @session_id
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
+ end
+ end
+
+ def get_user_subscription
+ response = wsapi_request(wsapi_resource_url("Subscription"))
+ Mapper.get_object(response)
+ end
+
+ def get_subscription(id)
+ response = wsapi_request(wsapi_resource_url("Subscription/#{id}"))
+ Mapper.get_object(response)
+ end
+
+ def get_projects(opts = {})
+ fetch_with_pages(opts) do |page_query|
+ wsapi_request(wsapi_resource_url("Project"), opts.merge(page_query))
+ end
+ end
+
+ def get_project(id)
+ response = wsapi_request(wsapi_resource_url("Project/#{id}"))
+ Mapper.get_object(response)
+ end
+
+ def get_current_user
+ response = wsapi_request(wsapi_resource_url("User"))
+ Mapper.get_object(response)
+ end
+
+ def get_user(id)
+ response = wsapi_request(wsapi_resource_url("User/#{id}"))
+ Mapper.get_object(response)
+ end
+
+ def get_user_by_username(username)
+ response = wsapi_request(wsapi_resource_url("User"), query: "(UserName = \"#{username}\")", pagesize: 1)
+ (Mapper.get_objects(response) ||[]).first
+ end
+
+ def get_team_members(project_id, opts = {})
+ fetch_with_pages(opts) do |page_query|
+ wsapi_request(wsapi_resource_url("Project/#{project_id}/TeamMembers"), opts.merge(page_query))
+ end
+ end
+
+ def get_editors(project_id, opts = {})
+ fetch_with_pages(opts) do |page_query|
+ wsapi_request(wsapi_resource_url("Project/#{project_id}/Editors"), opts.merge(page_query))
+ end
+ end
+
+ private
+
+ def workspace_url
+ wsapi_resource_url("Workspace/#{@workspace_id}")
+ end
+
+ def wsapi_resource_url(resource)
+ File.join(WSAPI_URL, "v#{@api_version}", resource)
+ end
+
+ def wsapi_request(url, opts = {})
+ response = @conn.get do |req|
+ req.url url
+ req.params['workspace'] = workspace_url if @workspace_id
+ req.params['query'] = opts[:query] if opts[:query]
+ req.params['start'] = opts[:start] || 1
+ req.params['pagesize'] = opts[:pagesize] || 200
+ req.params['fetch'] = opts[:fetch] || true # by default, fetch full objects
+ end
+ raise AuthorizationError.new("Unauthorized", response) if response.status == 401
+ raise ApiError.new("Internal server error", response) if response.status == 500
+ raise ObjectNotFoundError.new("Object not found") if object_not_found?(response)
+ raise IpAddressLimited.new("IP Address limited", response) if ip_address_limited?(response)
+ response
+ end
+
+ def ip_address_limited?(response)
+ limit_message = /Your IP address, (?:\d+\.?)+, is not within the allowed range that your subscription administrator has configured./
+ response.status > 401 && response.body.match(limit_message)
+ end
+
+ def object_not_found?(response)
+ if response.status == 200
+ result = MultiJson.load(response.body)["OperationResult"]
+ if result && error = result["Errors"].first
+ error.match("Cannot find object to read")
+ else
+ false
+ end
+ else
+ false
+ end
+ end
+
+ def fetch_with_pages(opts = {}, &block)
+ page_query = {
+ start: opts[:start] || 1,
+ pagesize: opts[:pagesize] || 100
+ }
+ resultCount = nil
+ objects = []
+ while(!resultCount || resultCount > objects.size) do
+ response = yield(page_query)
+ resultCount = MultiJson.load(response.body)["QueryResult"]["TotalResultCount"]
+ objects += Mapper.get_objects(response)
+ page_query[:start] += page_query[:pagesize]
+ end
+ objects
+ end
+ end
+end
+
diff --git a/spec/fixtures/wsapi/editors.json b/spec/fixtures/wsapi/editors.json
new file mode 100644
index 0000000..b0df4a3
--- /dev/null
+++ b/spec/fixtures/wsapi/editors.json
@@ -0,0 +1,266 @@
+{
+ "QueryResult": {
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 5,
+ "StartIndex": 1,
+ "PageSize": 20,
+ "Results": [
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/b9afb03d-e86b-418b-8438-eff565cb9e32",
+ "_objectVersion": "76",
+ "_refObjectName": "Greg Johnson",
+ "CreationDate": "2009-06-22T17:09:43.000Z",
+ "_CreatedAt": "Jun 22, 2009",
+ "ObjectID": "b9afb03d-e86b-418b-8438-eff565cb9e32",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Product",
+ "Disabled": false,
+ "DisplayName": "Greg Johnson",
+ "EmailAddress": "gjohnson@rallydev.com",
+ "FirstName": "Greg",
+ "LandingPage": "/oiterationstatus",
+ "LastLoginDate": "2013-12-02T18:04:48.616Z",
+ "LastName": "Johnson",
+ "LastPasswordUpdateDate": "2009-09-08T15:29:45.000Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/1f4a6137-23b3-4034-be34-18a59318d4e1",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/b9afb03d-e86b-418b-8438-eff565cb9e32/TeamMemberships",
+ "_type": "Project",
+ "Count": 12
+ },
+ "UserName": "Greg",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/b9afb03d-e86b-418b-8438-eff565cb9e32/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/7d45cd5b-4426-4b99-aa4f-a019a41e40f5",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/a8c79cd4-8d47-44eb-9f06-be1a776a3a02",
+ "_objectVersion": "201",
+ "_refObjectName": "Ann Konkler",
+ "CreationDate": "2009-07-13T17:55:39.000Z",
+ "_CreatedAt": "Jul 13, 2009",
+ "ObjectID": "a8c79cd4-8d47-44eb-9f06-be1a776a3a02",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Services",
+ "Disabled": false,
+ "DisplayName": "Ann Konkler",
+ "EmailAddress": "akonkler@rallydev.com",
+ "FirstName": "Ann",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-12-02T20:25:18.754Z",
+ "LastName": "Konkler",
+ "LastPasswordUpdateDate": "2012-08-29T20:56:52.761Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/71d6a348-bb03-4753-9322-e88cb0687ebc",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/a8c79cd4-8d47-44eb-9f06-be1a776a3a02/TeamMemberships",
+ "_type": "Project",
+ "Count": 42
+ },
+ "UserName": "akonkler@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/a8c79cd4-8d47-44eb-9f06-be1a776a3a02/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/c63d39e8-94ff-4a78-8bfc-983be5717588",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/56039be2-4776-4c97-96da-79d027340ef2",
+ "_objectVersion": "246",
+ "_refObjectName": "Kyle",
+ "CreationDate": "2009-07-21T20:48:26.000Z",
+ "_CreatedAt": "Jul 21, 2009",
+ "ObjectID": "56039be2-4776-4c97-96da-79d027340ef2",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Product",
+ "Disabled": false,
+ "DisplayName": "Kyle",
+ "EmailAddress": "kyle.clark@rallydev.com",
+ "FirstName": null,
+ "LandingPage": "/backlog",
+ "LastLoginDate": "2013-12-02T16:15:38.727Z",
+ "LastName": null,
+ "LastPasswordUpdateDate": "2011-10-04T15:09:24.113Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/777c996c-a770-4e05-af3e-6a19c7953279",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/56039be2-4776-4c97-96da-79d027340ef2/TeamMemberships",
+ "_type": "Project",
+ "Count": 15
+ },
+ "UserName": "kyle.clark@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/56039be2-4776-4c97-96da-79d027340ef2/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/6d3b45ca-04ce-40e2-9cf0-5420d9e85f1b",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a",
+ "_objectVersion": "772",
+ "_refObjectName": "...John M",
+ "CreationDate": "2009-08-17T06:46:09.000Z",
+ "_CreatedAt": "Aug 17, 2009",
+ "ObjectID": "2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Services",
+ "Disabled": false,
+ "DisplayName": "...John M",
+ "EmailAddress": "john.martin@rallydev.com",
+ "FirstName": "John",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-12-02T15:08:36.805Z",
+ "LastName": "Martin",
+ "LastPasswordUpdateDate": "2013-04-25T16:11:14.290Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/9c4fbe8e-685c-4aa4-b711-5d18b432e2f5",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a/TeamMemberships",
+ "_type": "Project",
+ "Count": 162
+ },
+ "UserName": "john.martin@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/c48645ab-1a99-43a9-8895-4ee994fded39",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/dd160a48-4dab-4c3f-a0ea-c94d77ac5735",
+ "_objectVersion": "291",
+ "_refObjectName": "Nick Musaelian",
+ "CreationDate": "2009-10-12T21:14:02.000Z",
+ "_CreatedAt": "Oct 13, 2009",
+ "ObjectID": "dd160a48-4dab-4c3f-a0ea-c94d77ac5735",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Support",
+ "Disabled": false,
+ "DisplayName": "Nick Musaelian",
+ "EmailAddress": "nick.musaelian@rallydev.com",
+ "FirstName": "Nick",
+ "LandingPage": "/backlog",
+ "LastLoginDate": "2013-12-02T16:17:46.333Z",
+ "LastName": "Musaelian",
+ "LastPasswordUpdateDate": "2013-04-15T23:25:16.978Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/70550cdd-5363-4c42-82c4-241db7384d45",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/dd160a48-4dab-4c3f-a0ea-c94d77ac5735/TeamMemberships",
+ "_type": "Project",
+ "Count": 4
+ },
+ "UserName": "nick.musaelian@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/dd160a48-4dab-4c3f-a0ea-c94d77ac5735/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/ba64a6a4-782c-401a-bfda-b8a22ecb3e40",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/ip_address_restriction.html b/spec/fixtures/wsapi/ip_address_restriction.html
new file mode 100644
index 0000000..cc57a2c
--- /dev/null
+++ b/spec/fixtures/wsapi/ip_address_restriction.html
@@ -0,0 +1 @@
+
Error 404 Your IP address, 188.40.137.146, is not within the allowed range that your subscription administrator has configured. HTTP ERROR 404
Problem accessing /slm/webservice/v2.0/User. Reason:
Your IP address, 188.40.137.146, is not within the allowed range that your subscription administrator has configured.
Powered by Jetty://
diff --git a/spec/fixtures/wsapi/not_found_error.json b/spec/fixtures/wsapi/not_found_error.json
new file mode 100644
index 0000000..6ab0fed
--- /dev/null
+++ b/spec/fixtures/wsapi/not_found_error.json
@@ -0,0 +1,8 @@
+{
+ "OperationResult": {
+ "Errors": [
+ "Cannot find object to read"
+ ],
+ "Warnings": []
+ }
+}
diff --git a/spec/fixtures/wsapi/page1.json b/spec/fixtures/wsapi/page1.json
new file mode 100644
index 0000000..1f57f5e
--- /dev/null
+++ b/spec/fixtures/wsapi/page1.json
@@ -0,0 +1,76 @@
+{
+ "QueryResult": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 3,
+ "StartIndex": 1,
+ "PageSize": 1,
+ "Results": [
+ {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/user/2487914",
+ "_objectVersion": "89",
+ "_refObjectName": "Richard",
+ "CreationDate": "2004-07-16T22:12:12.000Z",
+ "_CreatedAt": "Jul 16, 2004",
+ "ObjectID": 2487914,
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "None",
+ "Disabled": false,
+ "DisplayName": "Richard",
+ "EmailAddress": "bogus@rallydev.com",
+ "FirstName": "Richard",
+ "LandingPage": "/oiterationstatus",
+ "LastLoginDate": "2013-02-02T17:44:01.347Z",
+ "LastName": "Leavitt",
+ "LastPasswordUpdateDate": "2011-10-17T22:28:43.686Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/revisionhistory/122127368",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/2487914/TeamMemberships",
+ "_type": "Project",
+ "Count": 6
+ },
+ "UserName": "richard",
+ "UserPermissions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/2487914/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 5
+ },
+ "UserProfile": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/userprofile/28881803",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/page2.json b/spec/fixtures/wsapi/page2.json
new file mode 100644
index 0000000..2edcf1b
--- /dev/null
+++ b/spec/fixtures/wsapi/page2.json
@@ -0,0 +1,76 @@
+{
+ "QueryResult": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 3,
+ "StartIndex": 2,
+ "PageSize": 1,
+ "Results": [
+ {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/user/425841602",
+ "_objectVersion": "760",
+ "_refObjectName": "Garston",
+ "CreationDate": "2009-06-22T15:50:08.000Z",
+ "_CreatedAt": "Jun 22, 2009",
+ "ObjectID": 425841602,
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "None",
+ "Disabled": false,
+ "DisplayName": "Garston",
+ "EmailAddress": "bogus@rallydev.com",
+ "FirstName": "Garston",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-04-02T23:30:51.634Z",
+ "LastName": "Tremblay",
+ "LastPasswordUpdateDate": "2009-06-22T15:56:05.000Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/revisionhistory/425841606",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/425841602/TeamMemberships",
+ "_type": "Project",
+ "Count": 82
+ },
+ "UserName": "g",
+ "UserPermissions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/425841602/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/userprofile/425841604",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/page3.json b/spec/fixtures/wsapi/page3.json
new file mode 100644
index 0000000..3ab7e3c
--- /dev/null
+++ b/spec/fixtures/wsapi/page3.json
@@ -0,0 +1,76 @@
+{
+ "QueryResult": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 3,
+ "StartIndex": 3,
+ "PageSize": 1,
+ "Results": [
+ {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/user/2487921",
+ "_objectVersion": "122",
+ "_refObjectName": "Brad",
+ "CreationDate": "2004-07-16T22:12:12.000Z",
+ "_CreatedAt": "Jul 16, 2004",
+ "ObjectID": 2487921,
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "None",
+ "Disabled": false,
+ "DisplayName": "Brad",
+ "EmailAddress": "bogus@rallydev.com",
+ "FirstName": "brad",
+ "LandingPage": "/oiterationstatus",
+ "LastLoginDate": "2013-03-25T22:17:13.705Z",
+ "LastName": "norris",
+ "LastPasswordUpdateDate": "2011-06-09T17:17:40.400Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/revisionhistory/122127376",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/2487921/TeamMemberships",
+ "_type": "Project",
+ "Count": 8
+ },
+ "UserName": "brad",
+ "UserPermissions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/User/2487921/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 7
+ },
+ "UserProfile": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://test16cluster.f4tech.com/slm/webservice/v2.0/userprofile/28881809",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/project.json b/spec/fixtures/wsapi/project.json
new file mode 100644
index 0000000..d1d65c1
--- /dev/null
+++ b/spec/fixtures/wsapi/project.json
@@ -0,0 +1,88 @@
+{
+ "Project": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/project/79013919",
+ "_objectVersion": "24",
+ "_refObjectName": "The Chuck Norris",
+ "CreationDate": "2007-10-09T14:49:16.000Z",
+ "_CreatedAt": "Oct 9, 2007",
+ "ObjectID": "79013919",
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/workspace/41529001",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Children",
+ "_type": "Project",
+ "Count": 1
+ },
+ "Description": "When Chuck Norris was born, the only person who cried was the doctor. Never slap Chuck Norris.",
+ "Editors": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Editors",
+ "_type": "User",
+ "Count": 288
+ },
+ "Iterations": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Iterations",
+ "_type": "Iteration",
+ "Count": 124
+ },
+ "Name": "The Chuck Norris",
+ "Notes": "",
+ "Owner": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/user/162562330",
+ "_refObjectName": "Steve Stolt",
+ "_type": "User"
+ },
+ "Parent": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/project/2921510967",
+ "_refObjectName": "z Closed Projects",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Releases",
+ "_type": "Release",
+ "Count": 220
+ },
+ "State": "Open",
+ "TeamMembers": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/TeamMembers",
+ "_type": "User",
+ "Count": 40
+ },
+ "Errors": [],
+ "Warnings": []
+ }
+}
diff --git a/spec/fixtures/wsapi/project_weird_name.json b/spec/fixtures/wsapi/project_weird_name.json
new file mode 100644
index 0000000..f2b0965
--- /dev/null
+++ b/spec/fixtures/wsapi/project_weird_name.json
@@ -0,0 +1,88 @@
+{
+ "Project": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/project/79013919",
+ "_objectVersion": "24",
+ "_refObjectName": "!€",
+ "CreationDate": "2007-10-09T14:49:16.000Z",
+ "_CreatedAt": "Oct 9, 2007",
+ "ObjectID": "79013919",
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/workspace/41529001",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Children",
+ "_type": "Project",
+ "Count": 1
+ },
+ "Description": "When Chuck Norris was born, the only person who cried was the doctor. Never slap Chuck Norris.",
+ "Editors": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Editors",
+ "_type": "User",
+ "Count": 288
+ },
+ "Iterations": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Iterations",
+ "_type": "Iteration",
+ "Count": 124
+ },
+ "Name": "!€",
+ "Notes": "",
+ "Owner": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/user/162562330",
+ "_refObjectName": "Steve Stolt",
+ "_type": "User"
+ },
+ "Parent": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/project/2921510967",
+ "_refObjectName": "z Closed Projects",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/Releases",
+ "_type": "Release",
+ "Count": 220
+ },
+ "State": "Open",
+ "TeamMembers": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/Project/79013919/TeamMembers",
+ "_type": "User",
+ "Count": 40
+ },
+ "Errors": [],
+ "Warnings": []
+ }
+}
diff --git a/spec/fixtures/wsapi/projects.json b/spec/fixtures/wsapi/projects.json
new file mode 100644
index 0000000..74bf2e8
--- /dev/null
+++ b/spec/fixtures/wsapi/projects.json
@@ -0,0 +1,267 @@
+{
+ "QueryResult": {
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 4,
+ "StartIndex": 1,
+ "PageSize": 20,
+ "Results": [
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/686d6448-00b1-4173-b130-9214f13d3913",
+ "_objectVersion": "6",
+ "_refObjectName": "Speeding Up ALM Development",
+ "CreationDate": "2013-09-26T16:18:02.444Z",
+ "_CreatedAt": "Sep 26",
+ "ObjectID": "686d6448-00b1-4173-b130-9214f13d3913",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/workspace/3497d043-3ea7-4c8a-bf78-069847936c13",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/Children",
+ "_type": "Project",
+ "Count": 0
+ },
+ "Description": "",
+ "Editors": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/Editors",
+ "_type": "User",
+ "Count": 252
+ },
+ "Iterations": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/Iterations",
+ "_type": "Iteration",
+ "Count": 0
+ },
+ "Name": "Speeding Up ALM Development",
+ "Notes": "",
+ "Owner": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/4f1d5c6b-7ec1-4ab5-a32e-b2701a78d2a1",
+ "_refObjectName": "The Goze-father",
+ "_type": "User"
+ },
+ "Parent": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/a24f6339-7bb6-4638-bde0-3c6132354444",
+ "_refObjectName": "Rally",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/Releases",
+ "_type": "Release",
+ "Count": 0
+ },
+ "SchemaVersion": "2f8411ea60b42a93bab639d8c79d5b57",
+ "State": "Open",
+ "TeamMembers": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/686d6448-00b1-4173-b130-9214f13d3913/TeamMembers",
+ "_type": "User",
+ "Count": 0
+ },
+ "_type": "Project"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/bb34ef10-2fb8-4327-995a-4efc2498a6d2",
+ "_objectVersion": "1",
+ "_refObjectName": "Jeffrie Penrod",
+ "CreationDate": "2013-10-14T15:43:22.153Z",
+ "_CreatedAt": "Oct 14",
+ "ObjectID": "bb34ef10-2fb8-4327-995a-4efc2498a6d2",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/workspace/3497d043-3ea7-4c8a-bf78-069847936c13",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/Children",
+ "_type": "Project",
+ "Count": 0
+ },
+ "Description": "",
+ "Editors": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/Editors",
+ "_type": "User",
+ "Count": 253
+ },
+ "Iterations": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/Iterations",
+ "_type": "Iteration",
+ "Count": 6
+ },
+ "Name": "Jeffrie Penrod",
+ "Notes": "",
+ "Owner": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/bb7a7229-9929-42a8-a1f4-3ebf9f77561a",
+ "_refObjectName": "Brent",
+ "_type": "User"
+ },
+ "Parent": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/66d3637e-a3d8-46b1-b166-86c732e3c008",
+ "_refObjectName": "TAM Onboarding",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/Releases",
+ "_type": "Release",
+ "Count": 0
+ },
+ "SchemaVersion": "74713866d8d6c4bc1247fc24a8a4effa",
+ "State": "Open",
+ "TeamMembers": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/bb34ef10-2fb8-4327-995a-4efc2498a6d2/TeamMembers",
+ "_type": "User",
+ "Count": 0
+ },
+ "_type": "Project"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b",
+ "_objectVersion": "6",
+ "_refObjectName": "User Experience",
+ "CreationDate": "2013-10-14T16:08:57.496Z",
+ "_CreatedAt": "Oct 14",
+ "ObjectID": "3f5e6151-f502-4da1-aaa3-1d0cab7c853b",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/workspace/3497d043-3ea7-4c8a-bf78-069847936c13",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/Children",
+ "_type": "Project",
+ "Count": 0
+ },
+ "Description": "",
+ "Editors": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/Editors",
+ "_type": "User",
+ "Count": 253
+ },
+ "Iterations": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/Iterations",
+ "_type": "Iteration",
+ "Count": 0
+ },
+ "Name": "User Experience",
+ "Notes": "",
+ "Owner": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/c0d735d4-d0c0-4141-b23a-da3f8d39a8a6",
+ "_refObjectName": "Katrina",
+ "_type": "User"
+ },
+ "Parent": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/3adb00ef-32bc-49ac-b5e6-0aa30962f160",
+ "_refObjectName": "R&D",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/Releases",
+ "_type": "Release",
+ "Count": 2
+ },
+ "SchemaVersion": "c91db61066e1f3b6a96b9298694f3834",
+ "State": "Open",
+ "TeamMembers": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/3f5e6151-f502-4da1-aaa3-1d0cab7c853b/TeamMembers",
+ "_type": "User",
+ "Count": 5
+ },
+ "_type": "Project"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/6a0c6bcf-7cd8-4b37-895d-154b88023459",
+ "_objectVersion": "8",
+ "_refObjectName": "OCTO",
+ "CreationDate": "2013-10-16T19:08:31.680Z",
+ "_CreatedAt": "Oct 16",
+ "ObjectID": "6a0c6bcf-7cd8-4b37-895d-154b88023459",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "Workspace": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/workspace/3497d043-3ea7-4c8a-bf78-069847936c13",
+ "_refObjectName": "Rally",
+ "_type": "Workspace"
+ },
+ "BuildDefinitions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/BuildDefinitions",
+ "_type": "BuildDefinition",
+ "Count": 1
+ },
+ "Children": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/Children",
+ "_type": "Project",
+ "Count": 0
+ },
+ "Description": "",
+ "Editors": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/Editors",
+ "_type": "User",
+ "Count": 255
+ },
+ "Iterations": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/Iterations",
+ "_type": "Iteration",
+ "Count": 0
+ },
+ "Name": "OCTO",
+ "Notes": "",
+ "Owner": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/8d3069cc-a2d1-40ec-a45f-b2046112c88f",
+ "_refObjectName": "Rachel",
+ "_type": "User"
+ },
+ "Parent": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/project/a24f6339-7bb6-4638-bde0-3c6132354444",
+ "_refObjectName": "Rally",
+ "_type": "Project"
+ },
+ "Releases": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/Releases",
+ "_type": "Release",
+ "Count": 0
+ },
+ "SchemaVersion": "e664fb3c576cdbf61be4e4450282ef9d",
+ "State": "Open",
+ "TeamMembers": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Project/6a0c6bcf-7cd8-4b37-895d-154b88023459/TeamMembers",
+ "_type": "User",
+ "Count": 7
+ },
+ "_type": "Project"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/query_error.json b/spec/fixtures/wsapi/query_error.json
new file mode 100644
index 0000000..5a77657
--- /dev/null
+++ b/spec/fixtures/wsapi/query_error.json
@@ -0,0 +1,10 @@
+{
+ "QueryResult": {
+ "Errors": ["Could not parse: Unknown operator \")\""],
+ "Warnings": [],
+ "TotalResultCount": 0,
+ "StartIndex": 0,
+ "PageSize": 0,
+ "Results": []
+ }
+}
diff --git a/spec/fixtures/wsapi/subscription.json b/spec/fixtures/wsapi/subscription.json
new file mode 100644
index 0000000..dbc984e
--- /dev/null
+++ b/spec/fixtures/wsapi/subscription.json
@@ -0,0 +1,34 @@
+{
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_objectVersion": "56",
+ "_refObjectName": "Rally Development",
+ "CreationDate": "2004-07-16T22:12:12.000Z",
+ "_CreatedAt": "Jul 17, 2004",
+ "ObjectID": "76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "ExpirationDate": null,
+ "MaximumCustomUserFields": -1,
+ "MaximumProjects": -1,
+ "Modules": "Integration Hub,Rally Portfolio Manager,HP Integration,IBM Integration,Rally Idea Manager,Rally Quality Manager,Rally Support Manager,Rally Time Tracker,SSO,IP Restriction,Extension Whitelist",
+ "Name": "Rally Development",
+ "PasswordExpirationDays": 0,
+ "PreviousPasswordCount": 0,
+ "ProjectHierarchyEnabled": true,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/0f1a111e-c895-408d-9994-71b94f38b794",
+ "_type": "RevisionHistory"
+ },
+ "SessionTimeoutSeconds": null,
+ "StoryHierarchyEnabled": true,
+ "StoryHierarchyType": "Unlimited",
+ "SubscriptionID": 100,
+ "SubscriptionType": "Unlimited",
+ "Workspaces": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079/Workspaces",
+ "_type": "Workspace",
+ "Count": 1
+ },
+ "Errors": [],
+ "Warnings": []
+ }
+}
diff --git a/spec/fixtures/wsapi/subscriptions.json b/spec/fixtures/wsapi/subscriptions.json
new file mode 100644
index 0000000..a02942d
--- /dev/null
+++ b/spec/fixtures/wsapi/subscriptions.json
@@ -0,0 +1,43 @@
+{
+ "QueryResult": {
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 1,
+ "StartIndex": 1,
+ "PageSize": 20,
+ "Results": [
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_objectVersion": "56",
+ "_refObjectName": "Rally Development",
+ "CreationDate": "2004-07-16T22:12:12.000Z",
+ "_CreatedAt": "Jul 17, 2004",
+ "ObjectID": "76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "ExpirationDate": null,
+ "MaximumCustomUserFields": -1,
+ "MaximumProjects": -1,
+ "Modules": "Integration Hub,Rally Portfolio Manager,HP Integration,IBM Integration,Rally Idea Manager,Rally Quality Manager,Rally Support Manager,Rally Time Tracker,SSO,IP Restriction,Extension Whitelist",
+ "Name": "Rally Development",
+ "PasswordExpirationDays": 0,
+ "PreviousPasswordCount": 0,
+ "ProjectHierarchyEnabled": true,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/0f1a111e-c895-408d-9994-71b94f38b794",
+ "_type": "RevisionHistory"
+ },
+ "SessionTimeoutSeconds": null,
+ "StoryHierarchyEnabled": true,
+ "StoryHierarchyType": "Unlimited",
+ "SubscriptionID": 100,
+ "SubscriptionType": "Unlimited",
+ "Workspaces": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/Subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079/Workspaces",
+ "_type": "Workspace",
+ "Count": 1
+ },
+ "Errors": [],
+ "Warnings": []
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/team_members.json b/spec/fixtures/wsapi/team_members.json
new file mode 100644
index 0000000..73815a2
--- /dev/null
+++ b/spec/fixtures/wsapi/team_members.json
@@ -0,0 +1,266 @@
+{
+ "QueryResult": {
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 5,
+ "StartIndex": 1,
+ "PageSize": 20,
+ "Results": [
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/b9afb03d-e86b-418b-8438-eff565cb9e32",
+ "_objectVersion": "76",
+ "_refObjectName": "Greg Johnson",
+ "CreationDate": "2009-06-22T17:09:43.000Z",
+ "_CreatedAt": "Jun 22, 2009",
+ "ObjectID": "b9afb03d-e86b-418b-8438-eff565cb9e32",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Product",
+ "Disabled": false,
+ "DisplayName": "Greg Johnson",
+ "EmailAddress": "gjohnson@rallydev.com",
+ "FirstName": "Greg",
+ "LandingPage": "/oiterationstatus",
+ "LastLoginDate": "2013-12-02T18:04:48.616Z",
+ "LastName": "Johnson",
+ "LastPasswordUpdateDate": "2009-09-08T15:29:45.000Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/1f4a6137-23b3-4034-be34-18a59318d4e1",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/b9afb03d-e86b-418b-8438-eff565cb9e32/TeamMemberships",
+ "_type": "Project",
+ "Count": 12
+ },
+ "UserName": "gjohnson@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/b9afb03d-e86b-418b-8438-eff565cb9e32/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/7d45cd5b-4426-4b99-aa4f-a019a41e40f5",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/a8c79cd4-8d47-44eb-9f06-be1a776a3a02",
+ "_objectVersion": "201",
+ "_refObjectName": "Ann Konkler",
+ "CreationDate": "2009-07-13T17:55:39.000Z",
+ "_CreatedAt": "Jul 13, 2009",
+ "ObjectID": "a8c79cd4-8d47-44eb-9f06-be1a776a3a02",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Services",
+ "Disabled": false,
+ "DisplayName": "Ann Konkler",
+ "EmailAddress": "akonkler@rallydev.com",
+ "FirstName": "Ann",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-12-02T20:25:18.754Z",
+ "LastName": "Konkler",
+ "LastPasswordUpdateDate": "2012-08-29T20:56:52.761Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/71d6a348-bb03-4753-9322-e88cb0687ebc",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/a8c79cd4-8d47-44eb-9f06-be1a776a3a02/TeamMemberships",
+ "_type": "Project",
+ "Count": 42
+ },
+ "UserName": "akonkler@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/a8c79cd4-8d47-44eb-9f06-be1a776a3a02/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/c63d39e8-94ff-4a78-8bfc-983be5717588",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/56039be2-4776-4c97-96da-79d027340ef2",
+ "_objectVersion": "246",
+ "_refObjectName": "Kyle",
+ "CreationDate": "2009-07-21T20:48:26.000Z",
+ "_CreatedAt": "Jul 21, 2009",
+ "ObjectID": "56039be2-4776-4c97-96da-79d027340ef2",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Product",
+ "Disabled": false,
+ "DisplayName": "Kyle",
+ "EmailAddress": "kyle.clark@rallydev.com",
+ "FirstName": null,
+ "LandingPage": "/backlog",
+ "LastLoginDate": "2013-12-02T16:15:38.727Z",
+ "LastName": null,
+ "LastPasswordUpdateDate": "2011-10-04T15:09:24.113Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/777c996c-a770-4e05-af3e-6a19c7953279",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/56039be2-4776-4c97-96da-79d027340ef2/TeamMemberships",
+ "_type": "Project",
+ "Count": 15
+ },
+ "UserName": "kyle.clark@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/56039be2-4776-4c97-96da-79d027340ef2/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/6d3b45ca-04ce-40e2-9cf0-5420d9e85f1b",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a",
+ "_objectVersion": "772",
+ "_refObjectName": "...John M",
+ "CreationDate": "2009-08-17T06:46:09.000Z",
+ "_CreatedAt": "Aug 17, 2009",
+ "ObjectID": "2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Services",
+ "Disabled": false,
+ "DisplayName": "...John M",
+ "EmailAddress": "john.martin@rallydev.com",
+ "FirstName": "John",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-12-02T15:08:36.805Z",
+ "LastName": "Martin",
+ "LastPasswordUpdateDate": "2013-04-25T16:11:14.290Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/9c4fbe8e-685c-4aa4-b711-5d18b432e2f5",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a/TeamMemberships",
+ "_type": "Project",
+ "Count": 162
+ },
+ "UserName": "john.martin@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/2f4636eb-4717-48cb-82fe-c7d2a5f5fd7a/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/c48645ab-1a99-43a9-8895-4ee994fded39",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ },
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/dd160a48-4dab-4c3f-a0ea-c94d77ac5735",
+ "_objectVersion": "291",
+ "_refObjectName": "Nick Musaelian",
+ "CreationDate": "2009-10-12T21:14:02.000Z",
+ "_CreatedAt": "Oct 13, 2009",
+ "ObjectID": "dd160a48-4dab-4c3f-a0ea-c94d77ac5735",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "Support",
+ "Disabled": false,
+ "DisplayName": "Nick Musaelian",
+ "EmailAddress": "nick.musaelian@rallydev.com",
+ "FirstName": "Nick",
+ "LandingPage": "/backlog",
+ "LastLoginDate": "2013-12-02T16:17:46.333Z",
+ "LastName": "Musaelian",
+ "LastPasswordUpdateDate": "2013-04-15T23:25:16.978Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/70550cdd-5363-4c42-82c4-241db7384d45",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/dd160a48-4dab-4c3f-a0ea-c94d77ac5735/TeamMemberships",
+ "_type": "Project",
+ "Count": 4
+ },
+ "UserName": "nick.musaelian@rallydev.com",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/dd160a48-4dab-4c3f-a0ea-c94d77ac5735/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/ba64a6a4-782c-401a-bfda-b8a22ecb3e40",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/wsapi/user.json b/spec/fixtures/wsapi/user.json
new file mode 100644
index 0000000..493979e
--- /dev/null
+++ b/spec/fixtures/wsapi/user.json
@@ -0,0 +1,66 @@
+{
+ "User": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/user/11220674082",
+ "_objectVersion": "12",
+ "_refObjectName": "Antti",
+ "CreationDate": "2013-04-02T17:04:55.938Z",
+ "_CreatedAt": "Apr 2",
+ "ObjectID": "11220674082",
+ "Subscription": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/subscription/400059",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "None",
+ "Disabled": false,
+ "DisplayName": "Antti",
+ "EmailAddress": "apitkanen@rallydev.com",
+ "FirstName": "Antti",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-04-17T07:37:06.856Z",
+ "LastName": "Pitkanen",
+ "LastPasswordUpdateDate": "2013-04-02T17:18:13.216Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/revisionhistory/11220674083",
+ "_type": "RevisionHistory"
+ },
+ "Role": "None",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": true,
+ "TeamMemberships": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/User/11220674082/TeamMemberships",
+ "_type": "Project",
+ "Count": 0
+ },
+ "UserName": "antti",
+ "UserPermissions": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/User/11220674082/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_rallyAPIMajor": "2",
+ "_rallyAPIMinor": "0",
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v2.0/userprofile/11220674084",
+ "_type": "UserProfile"
+ },
+ "Errors": [],
+ "Warnings": []
+ }
+}
diff --git a/spec/fixtures/wsapi/users_by_username.json b/spec/fixtures/wsapi/users_by_username.json
new file mode 100644
index 0000000..38c7564
--- /dev/null
+++ b/spec/fixtures/wsapi/users_by_username.json
@@ -0,0 +1,62 @@
+{
+ "QueryResult": {
+ "Errors": [],
+ "Warnings": [],
+ "TotalResultCount": 1,
+ "StartIndex": 1,
+ "PageSize": 1,
+ "Results": [
+ {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/user/fb96bb72-e8ed-4f17-bcf4-4d8293ae88e8",
+ "_objectVersion": "22",
+ "_refObjectName": "Antti",
+ "CreationDate": "2013-04-02T17:04:55.938Z",
+ "_CreatedAt": "Apr 2",
+ "ObjectID": "fb96bb72-e8ed-4f17-bcf4-4d8293ae88e8",
+ "Subscription": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/subscription/76de2093-5f8d-403d-a2db-fcec9c5c7079",
+ "_refObjectName": "Rally Development",
+ "_type": "Subscription"
+ },
+ "CostCenter": "None",
+ "Department": "None",
+ "Disabled": false,
+ "DisplayName": "Antti",
+ "EmailAddress": "apitkanen@rallydev.com",
+ "FirstName": "Antti",
+ "LandingPage": "/dashboard",
+ "LastLoginDate": "2013-10-17T05:36:06.263Z",
+ "LastName": "Pitkanen",
+ "LastPasswordUpdateDate": "2013-04-02T17:18:13.216Z",
+ "MiddleName": null,
+ "NetworkID": null,
+ "OfficeLocation": "None",
+ "OnpremLdapUsername": null,
+ "Phone": null,
+ "RevisionHistory": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/revisionhistory/9cf45e44-6eda-4608-8074-2d58c4384a44",
+ "_type": "RevisionHistory"
+ },
+ "Role": "Developer",
+ "ShortDisplayName": null,
+ "SubscriptionAdmin": false,
+ "TeamMemberships": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/fb96bb72-e8ed-4f17-bcf4-4d8293ae88e8/TeamMemberships",
+ "_type": "Project",
+ "Count": 1
+ },
+ "UserName": "antti",
+ "UserPermissions": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/User/fb96bb72-e8ed-4f17-bcf4-4d8293ae88e8/UserPermissions",
+ "_type": "UserPermission",
+ "Count": 1
+ },
+ "UserProfile": {
+ "_ref": "https://rally1.rallydev.com/slm/webservice/v3.0/userprofile/2cf297fe-48ec-49a6-8bb9-57dae8c09c28",
+ "_type": "UserProfile"
+ },
+ "_type": "User"
+ }
+ ]
+ }
+}
diff --git a/spec/lib/session_spec.rb b/spec/lib/session_spec.rb
new file mode 100644
index 0000000..d6802dd
--- /dev/null
+++ b/spec/lib/session_spec.rb
@@ -0,0 +1,188 @@
+require 'wsapi/session'
+require 'uri'
+
+describe Wsapi::Session do
+ before :all do
+ @token = "deadbeefdeadbeef"
+ end
+
+ before :each do
+ @wsapi = Wsapi::Session.new(@token)
+ end
+
+ describe "API version" do
+ let(:user_data) { File.read(File.join("spec", "fixtures", "wsapi", "user.json")) }
+
+ it "defaults to API v3" do
+ stub_request(:get, wsapi_url_regexp("/User/1", "v3.0")).to_return(status: 200, body: user_data)
+ Wsapi::Session.new(SecureRandom.hex).get_user(1)
+ expect(WebMock).to have_requested(:get, wsapi_url_regexp("/User/1", "v3.0")).once
+ end
+
+ it "allows API version to be overwritten" do
+ stub_request(:get, wsapi_url_regexp("/User/1", "v2.0")).to_return(status: 200, body: user_data)
+ Wsapi::Session.new(SecureRandom.hex, version: "2.0").get_user(1)
+ expect(WebMock).to have_requested(:get, wsapi_url_regexp("/User/1", "v2.0")).once
+ end
+ end
+
+ describe "with users" do
+ before :each do
+ @user_data = File.read(File.join("spec", "fixtures", "wsapi", "user.json"))
+ @users_query_data = File.read(File.join("spec", "fixtures", "wsapi", "users_by_username.json"))
+ end
+
+ it "fetches user information" do
+ stub_request(:get, wsapi_url_regexp('/User/1')).to_return(status: 200, body: @user_data)
+ user = @wsapi.get_user(1)
+ expect(user.name).to eq("Antti Pitkanen")
+ expect(user.email).to eq("apitkanen@rallydev.com")
+ end
+
+ it "fetches current user" do
+ stub_request(:get, wsapi_url_regexp('/User')).with(
+ query: {"pagesize" => "200", "start" => "1", "fetch" => "true"} # must not include 'query', otherwise all users are returned
+ ).to_return(status: 200, body: @user_data)
+ user = @wsapi.get_current_user
+ expect(user.name).to eq("Antti Pitkanen")
+ expect(user.email).to eq("apitkanen@rallydev.com")
+ end
+
+ it "fetches user by username" do
+ stub_request(:get, wsapi_url_regexp('/User')).with(
+ query: hash_including({"pagesize" => "1", "query" => "(UserName = \"antti\")"})
+ ).to_return(status: 200, body: @users_query_data)
+ user = @wsapi.get_user_by_username("antti")
+ expect(user.username).to eq("antti")
+ expect(user.email).to eq("apitkanen@rallydev.com")
+ end
+ end
+
+ describe "with projects" do
+ before :each do
+ @project_data = File.read(File.join("spec", "fixtures", "wsapi", "project.json"))
+ @projects_data = File.read(File.join("spec", "fixtures", "wsapi", "projects.json"))
+ @editors_data = File.read(File.join("spec", "fixtures", "wsapi", "editors.json"))
+ stub_request(:get, wsapi_url_regexp('/Project/1')).to_return(status: 200, body: @project_data)
+ stub_request(:get, wsapi_url_regexp('/Project')).to_return(status: 200, body: @projects_data)
+ stub_request(:get, wsapi_url_regexp('/Project/1/Editors')).to_return(status: 200, body: @editors_data)
+ end
+
+ it "fetches given project" do
+ project = @wsapi.get_project(1)
+ expect(project.name).to eq("The Chuck Norris")
+ end
+
+ it "fetches all projects" do
+ projects = @wsapi.get_projects
+ expect(projects.size).to eq(4)
+ projects.each do |project|
+ expect(project.name).not_to be_nil
+ end
+ end
+
+ it "fetches editors for given project" do
+ editors = @wsapi.get_editors(1)
+ expect(editors.length).to eq(5)
+ expect(editors.first.email).to eq("gjohnson@rallydev.com")
+ expect(editors.first.username).to eq("Greg")
+ end
+ end
+
+ describe "with subscriptions" do
+ before :each do
+ @subscription_data = File.read(File.join("spec", "fixtures", "wsapi", "subscription.json"))
+ stub_request(:get, wsapi_url_regexp('/Subscription/1')).to_return(status: 200, body: @subscription_data)
+ stub_request(:get, wsapi_url_regexp('/Subscription')).to_return(status: 200, body: @subscription_data)
+ end
+
+ it "fetches user subscription" do
+ subscription = @wsapi.get_user_subscription
+ expect(subscription.name).to eq("Rally Development")
+ end
+
+ it "fetches given subscription" do
+ subscription = @wsapi.get_subscription(1)
+ expect(subscription.name).to eq("Rally Development")
+ end
+ end
+
+ describe "with paging" do
+ before :each do
+ @page1 = File.read(File.join("spec", "fixtures", "wsapi", "page1.json"))
+ @page2 = File.read(File.join("spec", "fixtures", "wsapi", "page2.json"))
+ @page3 = File.read(File.join("spec", "fixtures", "wsapi", "page3.json"))
+ stub_request(:get, wsapi_url_regexp('/Project/1/Editors')).with(
+ query: {"pagesize" => "1", "start" => "1", "fetch" => "true"}
+ ).to_return(status: 200, body: @page1)
+ stub_request(:get, wsapi_url_regexp('/Project/1/Editors')).with(
+ query: {"pagesize" => "1", "start" => "2", "fetch" => "true"}
+ ).to_return(status: 200, body: @page2)
+ stub_request(:get, wsapi_url_regexp('/Project/1/Editors')).with(
+ query: {"pagesize" => "1", "start" => "3", "fetch" => "true"}
+ ).to_return(status: 200, body: @page3)
+ end
+
+ it "fetches all the pages" do
+ editors = @wsapi.get_editors(1, {start: 1, pagesize: 1})
+ expect(editors.length).to eq(3)
+ end
+ end
+
+ describe "with errors" do
+ it "raises exception with query error" do
+ @error_data = File.read(File.join("spec", "fixtures", "wsapi", "query_error.json"))
+ stub_request(:get, /.*/).to_return(status: 200, body: @error_data)
+
+ expect {
+ @wsapi.get_projects
+ }.to raise_error(Wsapi::ApiError)
+ end
+
+ it "raises exception when object is not found" do
+ @error_data = File.read(File.join("spec", "fixtures", "wsapi", "not_found_error.json"))
+ stub_request(:get, /.*/).to_return(status: 200, body: @error_data)
+
+ expect {
+ @wsapi.get_user_subscription
+ }.to raise_error(Wsapi::ObjectNotFoundError)
+ end
+
+ it "raises exception when response body can't be parsed" do
+ stub_request(:get, /.*/).to_return(status: 423, body: "{invalid_json: ")
+
+ expect {
+ @wsapi.get_user_subscription
+ }.to raise_error(Wsapi::ApiError)
+ end
+
+ it "raises exception when response is 401" do
+ stub_request(:get, /.*/).to_return(status: 401)
+
+ expect {
+ @wsapi.get_user_subscription
+ }.to raise_error(Wsapi::AuthorizationError)
+ end
+
+ it "raises exception when response is 500" do
+ stub_request(:get, /.*/).to_return(status: 500)
+
+ expect {
+ @wsapi.get_user_subscription
+ }.to raise_error(Wsapi::ApiError)
+ end
+
+ it "raises a specific exception if IP address is blocked" do
+ stub_request(:get, /.*/)
+ .to_return(status: 404, body: File.read(File.join("spec", "fixtures", "wsapi", "ip_address_restriction.html")))
+ expect {
+ @wsapi.get_user_subscription
+ }.to raise_error(Wsapi::IpAddressLimited)
+ end
+ end
+
+ def wsapi_url_regexp(path, version = "v3.0")
+ base = File.join(Wsapi::WSAPI_URL, version, path).to_s
+ /#{Regexp.escape(base)}(?:\?.*|\Z)/
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..51998e5
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,86 @@
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause this
+# file to always be loaded, without a need to explicitly require it in any files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, make a
+# separate helper file that requires this one and then use it only in the specs
+# that actually need it.
+#
+# The `.rspec` file also contains a few flags that are not defaults but that
+# users commonly want.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+
+require_relative '../lib/wsapi/session'
+Dir[File.join("../lib/wsapi/models", "**/*.rb")].each do |f|
+ require f
+end
+
+require 'webmock/rspec'
+
+RSpec.configure do |config|
+# The settings below are suggested to provide a good initial experience
+# with RSpec, but feel free to customize to your heart's content.
+=begin
+ # These two settings work together to allow you to limit a spec run
+ # to individual examples or groups you care about by tagging them with
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
+ # get run.
+ config.filter_run :focus
+ config.run_all_when_everything_filtered = true
+
+ # Many RSpec users commonly either run the entire suite or an individual
+ # file, and it's useful to allow more verbose output when running an
+ # individual spec file.
+ if config.files_to_run.one?
+ # Use the documentation formatter for detailed output,
+ # unless a formatter has already been configured
+ # (e.g. via a command-line flag).
+ config.default_formatter = 'doc'
+ end
+
+ # Print the 10 slowest examples and example groups at the
+ # end of the spec run, to help surface which specs are running
+ # particularly slow.
+ config.profile_examples = 10
+
+ # Run specs in random order to surface order dependencies. If you find an
+ # order dependency and want to debug it, you can fix the order by providing
+ # the seed, which is printed after each run.
+ # --seed 1234
+ config.order = :random
+
+ # Seed global randomization in this process using the `--seed` CLI option.
+ # Setting this allows you to use `--seed` to deterministically reproduce
+ # test failures related to randomization by passing the same `--seed` value
+ # as the one that triggered the failure.
+ Kernel.srand config.seed
+
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # Enable only the newer, non-monkey-patching expect syntax.
+ # For more details, see:
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
+ expectations.syntax = :expect
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Enable only the newer, non-monkey-patching expect syntax.
+ # For more details, see:
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ mocks.syntax = :expect
+
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended.
+ mocks.verify_partial_doubles = true
+ end
+=end
+end