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