donedone 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +7 -0
- data/README.md +73 -0
- data/Rakefile +13 -0
- data/bin/donedone +25 -0
- data/donedone.gemspec +25 -0
- data/examples/lighthouse_csv_importer.rb +72 -0
- data/lib/donedone.rb +5 -0
- data/lib/donedone/constant.rb +33 -0
- data/lib/donedone/http.rb +131 -0
- data/lib/donedone/issue_tracker.rb +248 -0
- data/lib/donedone/multipart.rb +78 -0
- data/lib/donedone/version.rb +3 -0
- data/spec/donedone/constant_spec.rb +17 -0
- data/spec/donedone/http_spec.rb +101 -0
- data/spec/donedone/issue_tracker_spec.rb +164 -0
- data/spec/spec_helper.rb +12 -0
- metadata +95 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 85ea18b103a503e2eda28b1f26ef959019a20442
|
4
|
+
data.tar.gz: 4f1593a019af558f1f167d21691d6d01b367a106
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c458cef331617286b28a74dab64132178ea91e05ed25aa930787afd6c3da7d544ed2f82e49e5779a2d83220580f3a4f985a580742978aafeb5f30444dff5ec55
|
7
|
+
data.tar.gz: c0ae992671cecbf2750f4dd71c9e05d9bf928752ea26448f749f93d2bcbe3797cd7545822d11dba20d56eed0d016bbf46e62cd0f918055c53415901fd0d87441
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2013 Inquisitive Minds, Inc
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# DoneDone API Ruby Client Library (GEM)
|
2
|
+
|
3
|
+
## REQUIREMENT
|
4
|
+
Ruby
|
5
|
+
|
6
|
+
## USAGE
|
7
|
+
To use the Ruby library with a DoneDone project, you will need to enable the API option under the Project Settings page.
|
8
|
+
|
9
|
+
Please see http://www.getdonedone.com/api for more detailed documentation.
|
10
|
+
|
11
|
+
The examples below work for projects with the API enabled.
|
12
|
+
|
13
|
+
|
14
|
+
## EXAMPLES
|
15
|
+
```
|
16
|
+
|
17
|
+
# use it in your own code:
|
18
|
+
cmd-prompt> gem install 'donedone'
|
19
|
+
require 'donedone'
|
20
|
+
|
21
|
+
# or interact via donedone ruby-cmd
|
22
|
+
cmd-prompt> donedone -h
|
23
|
+
|
24
|
+
# import your LightHouse CSV:
|
25
|
+
./examples/lighthouse_csv_importer.rb <domain> <usr> <pwd> <project_id> /path/to/your/light_house.csv
|
26
|
+
|
27
|
+
# or via irb
|
28
|
+
cmd-prompt> irb
|
29
|
+
require 'donedone'
|
30
|
+
|
31
|
+
domain = "YOUR_COMPANY_DOMAIN" #e.g. wearemammoth
|
32
|
+
username = "YOUR_USERNAME"
|
33
|
+
password = "YOUR_PASSWORD"
|
34
|
+
|
35
|
+
issue_tracker = DoneDone::IssueTracker.new(domain, username, password)
|
36
|
+
|
37
|
+
# list all the api calls (plus the 'result' method):
|
38
|
+
issue_tracker.public_methods(false)
|
39
|
+
|
40
|
+
issue_tracker.projects
|
41
|
+
project_id = issue_tracker.result.first["ID"]
|
42
|
+
|
43
|
+
issue_tracker.priority_levels
|
44
|
+
|
45
|
+
issue_tracker.people_in_project(project_id)
|
46
|
+
tester_id = issue_tracker.result.first["ID"]
|
47
|
+
resolver_id = issue_tracker.result.last["ID"]
|
48
|
+
|
49
|
+
issue_tracker.issues_in_project(project_id)
|
50
|
+
priority_level_id = issue_tracker.result.first["PriorityLevelID"]
|
51
|
+
issue_id = issue_tracker.result.first["OrderNumber"]
|
52
|
+
|
53
|
+
issue_tracker.issue_exist?(project_id, issue_id)
|
54
|
+
issue_tracker.potential_statuses_for_issue(project_id, issue_id)
|
55
|
+
issue_tracker.issue(project_id, issue_id)
|
56
|
+
issue_tracker.people_for_issue_assignment(project_id, issue_id)
|
57
|
+
|
58
|
+
new_issue_id = issue_tracker.create_issue(project_id, title, priority_id,
|
59
|
+
resolver_id, tester_id, {:description => '', :tags=> nil, :watcher_id=>nil, :attachments=>nil})
|
60
|
+
|
61
|
+
comment = "blah blah"
|
62
|
+
file_name = "./file1.txt"
|
63
|
+
File.open(file_name, "w") {|f| f.puts "attachment one." }
|
64
|
+
comment_url = issue_tracker.create_comment(project_id, new_issue_id, comment, {:people_to_cc_ids=>nil :attachments=>[file_name]})
|
65
|
+
puts issue_tracker.result["SuccesfullyAttachedFiles"] ? "attachment uploaded successfully" : "failed to upload attachment"
|
66
|
+
File.unlink(file_name)
|
67
|
+
|
68
|
+
issue_url = issue_tracker.update_issue(project_id, new_issue_id, {:title=>nil, :priority_id=>nil, :resolver_id=>nil, :tester_id=nil, :description=>nil, :tags=>nil, :state_id=>nil, :attachments=>nil})
|
69
|
+
|
70
|
+
```
|
71
|
+
|
72
|
+
## TODO
|
73
|
+
improve design via specs
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
gemspec = eval(File.read("donedone.gemspec"))
|
7
|
+
|
8
|
+
task :build => "#{gemspec.full_name}.gem"
|
9
|
+
|
10
|
+
file "#{gemspec.full_name}.gem" => gemspec.files + ["donedone.gemspec"] do
|
11
|
+
system "gem build donedone.gemspec"
|
12
|
+
system "gem install donedone-#{DoneDone::VERSION}.gem"
|
13
|
+
end
|
data/bin/donedone
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'donedone'
|
6
|
+
rescue LoadError
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'donedone'
|
11
|
+
rescue LoadError
|
12
|
+
require_relative '../lib/donedone'
|
13
|
+
end
|
14
|
+
|
15
|
+
if ([] == ARGV) || ARGV.length < 4 || (ARGV[0].start_with?("-h"))
|
16
|
+
warn "USAGE: ${$0} <domain> <usr> <pwd> <api-method> [args...]"
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
init_args = []
|
20
|
+
# puts "ARGV start: #{ARGV.inspect}"
|
21
|
+
3.times { init_args << ARGV.shift }
|
22
|
+
# puts "ARGV now: #{ARGV.inspect}"
|
23
|
+
issue_tracker = DoneDone::IssueTracker.new(*init_args)
|
24
|
+
puts issue_tracker.send(ARGV.shift, *ARGV)
|
25
|
+
exit
|
data/donedone.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "donedone/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "donedone"
|
7
|
+
s.version = DoneDone::VERSION
|
8
|
+
s.authors = ["Jonathan Thomas"]
|
9
|
+
s.email = ["buyer+donedone@his-service.net"]
|
10
|
+
s.homepage = "https://github.com/zoodles/DoneDone-API-Ruby"
|
11
|
+
s.summary = %q{donedone.com api client}
|
12
|
+
s.description = %q{Check your existing todo items, add new ones, update old one}
|
13
|
+
s.rubyforge_project = "donedone"
|
14
|
+
s.license = 'MIT'
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.bindir = 'bin'
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_dependency "mime-types", "~> 2.0"
|
23
|
+
s.add_development_dependency "rspec", "~> 2.14.0"
|
24
|
+
#s.add_development_dependency "simplecov"
|
25
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Import projct issues from LightHouse CSV exports
|
3
|
+
#
|
4
|
+
# This is a simple example that migrates data from LightHouse to DoneDone using
|
5
|
+
# the Ruby client. It makes a couple of assumptions. First, the CSV file must
|
6
|
+
# contain columns listed below.
|
7
|
+
#
|
8
|
+
# number | state | title | milestone | assigned | created | updated | project | tags
|
9
|
+
#
|
10
|
+
# Second, it also assumes the accounts have already been created for the
|
11
|
+
# project, and throws an execption if not. Because LightHouse handles things a
|
12
|
+
# bit different from DoneDone, this script will also create the project with assignee
|
13
|
+
# as both resolver and tester.
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'rubygems'
|
17
|
+
gem 'donedone'
|
18
|
+
rescue LoadError
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require 'donedone'
|
23
|
+
rescue LoadError
|
24
|
+
require_relative '../lib/donedone'
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'csv'
|
28
|
+
|
29
|
+
DEFAULT_DOMAIN = "Your company domain"
|
30
|
+
domain = ARGV.shift || DEFAULT_DOMAIN
|
31
|
+
username = ARGV.shift || "Your donedone username"
|
32
|
+
password = ARGV.shift || "Your donedone password"
|
33
|
+
project_id = ARGV.shift.to_i || "Your donedone project ID"
|
34
|
+
|
35
|
+
CSVFilePath = ARGV.shift || "Path to the CSV file exported from lighthouseapp.com"
|
36
|
+
priority_id = ARGV.shift || 2 #Assuming medium priority
|
37
|
+
|
38
|
+
# puts "domain: #{domain.inspect}, username: #{username.inspect}, password: #{password.inspect}, project_id: #{project_id.inspect}, csv: #{CSVFilePath.inspect}"
|
39
|
+
fail( "unknown file: #{CSVFilePath.inspect}") unless File.exists?(CSVFilePath)
|
40
|
+
fail( "You must edit pass-in variables or edit the default values in #{$0}" ) if DEFAULT_DOMAIN == domain
|
41
|
+
|
42
|
+
issue_tracker = DoneDone::IssueTracker.new(domain, username, password)
|
43
|
+
project_peoples = issue_tracker.people_in_project(project_id)
|
44
|
+
|
45
|
+
|
46
|
+
# read file
|
47
|
+
csv_text = File.binread(CSVFilePath)
|
48
|
+
# slurp
|
49
|
+
csv = CSV.parse(csv_text, { :headers => true, :return_headers => false, :col_sep => ',', :quote_char => '"'})
|
50
|
+
|
51
|
+
# loop over issues
|
52
|
+
csv.each do |issue|
|
53
|
+
# puts "i: #{issue.inspect}"
|
54
|
+
person_id = nil
|
55
|
+
name = issue['assigned']
|
56
|
+
next if "" == project_peoples
|
57
|
+
# puts "pp: #{project_peoples.inspect}"
|
58
|
+
if person = project_peoples.detect{|people| people["Value"] == name}
|
59
|
+
person_id = person["ID"]
|
60
|
+
else
|
61
|
+
fail "Fail to find DoneDone account for #{name}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create issue with medium priority
|
65
|
+
print issue_tracker.create_issue(
|
66
|
+
project_id, issue['title'], priority_id,
|
67
|
+
person_id, person_id,
|
68
|
+
{:description => "Created by DoneDone API Ruby client.", :tags => issue['tags']})
|
69
|
+
|
70
|
+
# Pause execution for API request wait time.
|
71
|
+
sleep(5)
|
72
|
+
end
|
data/lib/donedone.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module DoneDone
|
2
|
+
class Constant
|
3
|
+
PROJECTS_WITH_ISSUES = 'Projects/true' unless const_defined?(:PROJECTS_WITH_ISSUES)
|
4
|
+
PROJECTS = 'Projects' unless const_defined?(:PROJECTS)
|
5
|
+
PRIORITY_LEVELS = 'PriorityLevels' unless const_defined?(:PRIORITY_LEVELS)
|
6
|
+
PEOPLE_IN_PROJECT = "PeopleInProject/%s" unless const_defined?(:PEOPLE_IN_PROJECT)
|
7
|
+
ISSUES_IN_PROJECT = "IssuesInProject/%s" unless const_defined?(:ISSUES_IN_PROJECT)
|
8
|
+
DOES_ISSUE_EXIST = "DoesIssueExist/%s/%s" unless const_defined?(:DOES_ISSUE_EXIST)
|
9
|
+
POTENTIAL_STATUSES_FOR_ISSUE = "PotentialStatusesForIssue/%s/%s" unless const_defined?(:POTENTIAL_STATUSES_FOR_ISSUE)
|
10
|
+
CREATE_ISSUE = "Issue/%s" unless const_defined?(:CREATE_ISSUE)
|
11
|
+
ISSUE = "#{CREATE_ISSUE}/%s" unless const_defined?(:ISSUE)
|
12
|
+
PEOPLE_FOR_ISSUE_ASSIGNMENT = "PeopleForIssueAssignment/%s/%s" unless const_defined?(:PEOPLE_FOR_ISSUE_ASSIGNMENT)
|
13
|
+
COMMENT = "Comment/%s/%s" unless const_defined?(:COMMENT)
|
14
|
+
|
15
|
+
HOST = "%s.mydonedone.com" unless const_defined?(:HOST)
|
16
|
+
PROTOCOL = "https" unless const_defined?(:PROTOCOL)
|
17
|
+
BASE_URL_PATH = "IssueTracker/API" unless const_defined?(:BASE_URL_PATH)
|
18
|
+
BASE_URL = "#{PROTOCOL}://%s/#{BASE_URL_PATH}/" unless const_defined?(:BASE_URL)
|
19
|
+
|
20
|
+
SSL_VERIFY_MODE = OpenSSL::SSL::VERIFY_NONE unless const_defined?(:SSL_VERIFY_MODE)
|
21
|
+
SSL_VERSION = :SSLv3 unless const_defined?(:SSL_VERSION)
|
22
|
+
|
23
|
+
def self.url_for(name, *args)
|
24
|
+
format_str = const_get(name)
|
25
|
+
if args.empty?
|
26
|
+
format_str
|
27
|
+
else
|
28
|
+
format_str % args
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require_relative 'multipart'
|
4
|
+
|
5
|
+
module DoneDone
|
6
|
+
class Http
|
7
|
+
attr_reader :domain, :data, :files, :method_url
|
8
|
+
attr_reader :_request, :_debug
|
9
|
+
private :_request
|
10
|
+
private :_debug
|
11
|
+
|
12
|
+
attr_reader :put_class, :get_class, :post_class, :multipart_post_class
|
13
|
+
attr_reader :http_class, :ssl_verify_mode, :ssl_version
|
14
|
+
def initialize(domain, username, password, options={})
|
15
|
+
@domain = domain
|
16
|
+
@_username = username
|
17
|
+
@_password = password
|
18
|
+
|
19
|
+
@put_class = options[:put_class] || Net::HTTP::Put
|
20
|
+
@get_class = options[:get_class] || Net::HTTP::Get
|
21
|
+
@post_class = options[:post_class] || Net::HTTP::Post
|
22
|
+
@multipart_post_class = options[:multipart_post_class] || Multipart::Post
|
23
|
+
@http_class = options[:http_class] || Net::HTTP
|
24
|
+
@ssl_verify_mode = options[:ssl_verify_mode] || Constant::SSL_VERIFY_MODE
|
25
|
+
@ssl_version = options[:ssl_version] || Constant::SSL_VERSION
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset(options = {})
|
29
|
+
@data = options[:data]
|
30
|
+
@files = options[:files]
|
31
|
+
@_request = nil
|
32
|
+
@uri = nil
|
33
|
+
@base_url = nil
|
34
|
+
@host = nil
|
35
|
+
@http = nil
|
36
|
+
@_debug = options.has_key?(:debug) ? options[:debug] : false
|
37
|
+
end
|
38
|
+
|
39
|
+
def host
|
40
|
+
unless @host
|
41
|
+
@host = Constant.url_for('HOST', domain)
|
42
|
+
end
|
43
|
+
@host
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(method_url, options={})
|
47
|
+
@method_url = method_url
|
48
|
+
reset(options)
|
49
|
+
request(get_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
def put(method_url, options={})
|
53
|
+
@method_url = method_url
|
54
|
+
reset(options)
|
55
|
+
request(put_class) do
|
56
|
+
append_any_form_data
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def post(method_url, options={})
|
61
|
+
@method_url = method_url
|
62
|
+
reset(options)
|
63
|
+
request(post_class) do
|
64
|
+
files ? append_multipart_data : append_any_form_data
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def http
|
72
|
+
unless @http
|
73
|
+
@http = http_class.new(uri.host, uri.port)
|
74
|
+
@http.use_ssl = true
|
75
|
+
@http.verify_mode = ssl_verify_mode
|
76
|
+
@http.ssl_version = ssl_version
|
77
|
+
end
|
78
|
+
@http
|
79
|
+
end
|
80
|
+
|
81
|
+
def base_url
|
82
|
+
unless @base_url
|
83
|
+
@base_url = Constant.url_for('BASE_URL', host)
|
84
|
+
end
|
85
|
+
@base_url
|
86
|
+
end
|
87
|
+
|
88
|
+
def uri
|
89
|
+
unless @uri
|
90
|
+
@uri = URI.parse("#{base_url}#{method_url}")
|
91
|
+
end
|
92
|
+
@uri
|
93
|
+
end
|
94
|
+
|
95
|
+
def request(method_class)
|
96
|
+
debug { "#{method_class.name}, #{uri.request_uri.inspect} - host: #{uri.host}, - port: #{uri.port}" }
|
97
|
+
@_request = method_class.new(uri.request_uri)
|
98
|
+
|
99
|
+
yield if block_given?
|
100
|
+
|
101
|
+
debug { "uri: #{uri.inspect}, files: #{files.inspect}" }
|
102
|
+
_request.basic_auth(@_username, @_password)
|
103
|
+
|
104
|
+
debug { "request: #{_request.to_hash.inspect}" }
|
105
|
+
http.request(_request)
|
106
|
+
end
|
107
|
+
|
108
|
+
def append_any_form_data #(data=data)
|
109
|
+
if data
|
110
|
+
debug { "form_data: #{data.inspect}" }
|
111
|
+
_request.set_form_data(data)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def append_multipart_data #(files=files, data=data)
|
116
|
+
body, content_type, useragent =
|
117
|
+
multipart_post_class.prepare_query( files.merge(data) )
|
118
|
+
|
119
|
+
debug { "params: #{params.inspect}; body: #{body.inspect}, content_type: #{content_type.inspect}, useragent: #{useragent.inspect}" }
|
120
|
+
@_request.content_type = content_type
|
121
|
+
@_request.content_length = body.size
|
122
|
+
@_request["User-Agent"] = useragent
|
123
|
+
|
124
|
+
@_request.body = body
|
125
|
+
end
|
126
|
+
|
127
|
+
def debug
|
128
|
+
puts(yield) if _debug && block_given?
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
require_relative "http"
|
2
|
+
require_relative "constant"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module DoneDone
|
6
|
+
class IssueTracker
|
7
|
+
HELPER_METHODS = [:response, :result] unless const_defined?(:HELPER_METHODS)
|
8
|
+
def self.api_methods
|
9
|
+
instance_methods(false) - HELPER_METHODS
|
10
|
+
end
|
11
|
+
|
12
|
+
#Provide access to the DoneDone IssueTracker API.
|
13
|
+
#See http://www.getdonedone.com/api for complete documentation for the
|
14
|
+
#API.
|
15
|
+
|
16
|
+
attr_reader :response
|
17
|
+
attr_reader :_debug
|
18
|
+
private :_debug
|
19
|
+
attr_reader :_http_helper
|
20
|
+
private :_http_helper
|
21
|
+
|
22
|
+
#_debug - print debug messages
|
23
|
+
#domain - company's DoneDone domain
|
24
|
+
#username - DoneDone username
|
25
|
+
#password - DoneDone password
|
26
|
+
|
27
|
+
def initialize(domain, username, password=nil, options = {})
|
28
|
+
@_debug = options.has_key?(:debug) ? options[:debug] : false
|
29
|
+
@_http_helper = options[:http_helper] || DoneDone::Http.new(domain, username, password)
|
30
|
+
@response = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def result
|
34
|
+
response ? JSON.parse( response.body ) : ""
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Get all Projects with the API enabled
|
39
|
+
# load_with_issues - Passing true will deep load all of the projects as
|
40
|
+
# well as all of their active issues.
|
41
|
+
def projects(load_with_issues=false)
|
42
|
+
url = load_with_issues ? Constant.url_for('PROJECTS_WITH_ISSUES') : Constant.url_for('PROJECTS')
|
43
|
+
api url
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get priority levels
|
47
|
+
def priority_levels
|
48
|
+
api Constant.url_for('PRIORITY_LEVELS')
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get all people in a project
|
52
|
+
# project_id - project id
|
53
|
+
def people_in_project project_id
|
54
|
+
api Constant.url_for('PEOPLE_IN_PROJECT', project_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get all issues in a project
|
58
|
+
# project_id - project id
|
59
|
+
def issues_in_project project_id
|
60
|
+
api Constant.url_for('ISSUES_IN_PROJECT', project_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check if an issue exists
|
64
|
+
# project_id - project id
|
65
|
+
# issue_id - issue id
|
66
|
+
def issue_exist?(project_id, issue_id)
|
67
|
+
api Constant.url_for('DOES_ISSUE_EXIST', project_id, issue_id)
|
68
|
+
!result.empty? ? result["IssueExists"] : false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get potential statuses for issue
|
72
|
+
# Note: If you are an admin, you'll get both all allowed statuses
|
73
|
+
# as well as ALL statuses back from the server
|
74
|
+
# project_id - project id
|
75
|
+
# issue_id - issue id
|
76
|
+
def potential_statuses_for_issue( project_id, issue_id)
|
77
|
+
api Constant.url_for('POTENTIAL_STATUSES_FOR_ISSUE', project_id, issue_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Note: You can use this to check if an issue exists as well,
|
81
|
+
# since it will return a 404 if the issue does not exist.
|
82
|
+
def issue(project_id, issue_id)
|
83
|
+
api Constant.url_for('ISSUE', project_id, issue_id)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get a list of people that cane be assigend to an issue
|
87
|
+
def people_for_issue_assignment(project_id, issue_id)
|
88
|
+
api Constant.url_for('PEOPLE_FOR_ISSUE_ASSIGNMENT', project_id, issue_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
#Create Issue
|
92
|
+
# project_id - project id
|
93
|
+
# title - required title.
|
94
|
+
# priority_id - priority levels
|
95
|
+
# resolver_id - person assigned to solve this issue
|
96
|
+
# tester_id - person assigned to test and verify if a issue is
|
97
|
+
# resolved
|
98
|
+
# description - optional description of the issue
|
99
|
+
# tags - a string of tags delimited by comma
|
100
|
+
# watcher_id - a string of people's id delimited by comma
|
101
|
+
# attachments - list of file paths
|
102
|
+
def create_issue( project_id, title, priority_id, resolver_id, tester_id, options={})
|
103
|
+
description=options[:description]
|
104
|
+
tags=options[:tags]
|
105
|
+
watcher_id=options[:watcher_id]
|
106
|
+
attachments=options[:attachments]
|
107
|
+
|
108
|
+
data = {
|
109
|
+
'title' => title,
|
110
|
+
'priority_level_id' => priority_id,
|
111
|
+
'resolver_id' => resolver_id,
|
112
|
+
'tester_id' => tester_id,
|
113
|
+
}
|
114
|
+
|
115
|
+
data['description'] = description if description
|
116
|
+
data['tags'] = tags if tags
|
117
|
+
data['watcher_ids'] = watcher_id if watcher_id
|
118
|
+
|
119
|
+
params = {:data => data, :update => false, :post => true}
|
120
|
+
params[:attachments] = attachments if attachments
|
121
|
+
api Constant.url_for('CREATE_ISSUE', project_id), params
|
122
|
+
!result.empty? ? result["IssueID"] : nil
|
123
|
+
end
|
124
|
+
|
125
|
+
#Create Comment on issue
|
126
|
+
#project_id - project id
|
127
|
+
#issue_id - issue id
|
128
|
+
#comment - comment string
|
129
|
+
#people_to_cc_ids - a string of people to be CCed on this comment,
|
130
|
+
#delimited by comma
|
131
|
+
#attachments - list of absolute file path.
|
132
|
+
def create_comment(project_id, order_number, comment, options={})
|
133
|
+
people_to_cc_ids=options[:people_to_cc_ids]
|
134
|
+
attachments=options[:attachments]
|
135
|
+
|
136
|
+
data = {'comment' => comment}
|
137
|
+
data['people_to_cc_ids']= people_to_cc_ids if people_to_cc_ids
|
138
|
+
|
139
|
+
params = {:data => data, :update => false, :post => true}
|
140
|
+
params[:attachments] = attachments if attachments
|
141
|
+
api Constant.url_for('COMMENT', project_id, order_number), params
|
142
|
+
!result.empty? ? result["CommentURL"] : nil
|
143
|
+
end
|
144
|
+
|
145
|
+
#Update Issue
|
146
|
+
# If you provide any parameters then the value you pass will be
|
147
|
+
# used to update the issue. If you wish to keep the value that's
|
148
|
+
# already on an issue, then do not provide the parameter in your
|
149
|
+
# PUT data. Any value you provide, including tags, will overwrite
|
150
|
+
# the existing values on the issue. If you wish to retain the
|
151
|
+
# tags for an issue and update it by adding one new tag, then
|
152
|
+
# you'll have to provide all of the existing tags as well as the
|
153
|
+
# new tag in your tags parameter, for example.
|
154
|
+
|
155
|
+
# project_id - project id
|
156
|
+
# order_number - issue id
|
157
|
+
# title - required title
|
158
|
+
# priority_id - priority levels
|
159
|
+
# resolver_id - person assigned to solve this issue
|
160
|
+
# tester_id - person assigned to test and verify if a issue is
|
161
|
+
# resolved
|
162
|
+
# description - optional description of the issue
|
163
|
+
# tags - a string of tags delimited by comma
|
164
|
+
# state_id - a valid state that this issue can transition to
|
165
|
+
# attachments - list of file paths
|
166
|
+
def update_issue(project_id, order_number, options={})
|
167
|
+
title=options[:title]
|
168
|
+
priority_id=options[:priority_id]
|
169
|
+
resolver_id=options[:resolver_id]
|
170
|
+
tester_id=options[:tester_id]
|
171
|
+
description=options[:description]
|
172
|
+
tags=options[:tags]
|
173
|
+
state_id=options[:state_id]
|
174
|
+
attachments=options[:attachments]
|
175
|
+
|
176
|
+
data = {}
|
177
|
+
data['title'] = title if title
|
178
|
+
data['priority_level_id'] = priority_id if priority_id
|
179
|
+
data['resolver_id'] = resolver_id if resolver_id
|
180
|
+
|
181
|
+
data['tester_id'] = tester_id if tester_id
|
182
|
+
data['description'] = description if description
|
183
|
+
data['tags'] = tags if tags
|
184
|
+
data['state_id'] = state_id if state_id
|
185
|
+
|
186
|
+
params = {:update => true}
|
187
|
+
params[:data] = data unless data.empty?
|
188
|
+
params[:attachments] = attachments if attachments
|
189
|
+
api Constant.url_for('ISSUE', project_id, order_number), params
|
190
|
+
!result.empty? ? result["IssueURL"] : nil
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Perform generic API calling
|
197
|
+
#This is the base method for all IssueTracker API calls.
|
198
|
+
# method_url - IssueTracker method URL
|
199
|
+
# data - optional POST form data
|
200
|
+
# attachemnts - optional list of file paths
|
201
|
+
# update - flag to indicate if this is a PUT operation
|
202
|
+
def api(method_url, options={})
|
203
|
+
data = options[:data]
|
204
|
+
attachments = options[:attachments]
|
205
|
+
update = options.has_key?(:update) ? options[:update] : false
|
206
|
+
post = options.has_key?(:post) ? options[:post] : false
|
207
|
+
|
208
|
+
@response = nil
|
209
|
+
files = {}
|
210
|
+
request_method = nil
|
211
|
+
|
212
|
+
if attachments
|
213
|
+
debug { "attachments; using post" }
|
214
|
+
request_method = :post
|
215
|
+
attachments.each_with_index do |attachment, index|
|
216
|
+
files["attachment-#{index}"] = attachment
|
217
|
+
end
|
218
|
+
else
|
219
|
+
request_method = :get
|
220
|
+
end
|
221
|
+
|
222
|
+
if update
|
223
|
+
debug { "using put" }
|
224
|
+
request_method = :put
|
225
|
+
end
|
226
|
+
|
227
|
+
if post
|
228
|
+
debug { "using post" }
|
229
|
+
request_method = :post
|
230
|
+
end
|
231
|
+
|
232
|
+
params = { :debug => _debug }
|
233
|
+
params[:data] = data if data
|
234
|
+
params[:files] = files unless files.empty?
|
235
|
+
|
236
|
+
@response = _http_helper.send(request_method, method_url, params)
|
237
|
+
return result
|
238
|
+
rescue Exception => e
|
239
|
+
warn e.message
|
240
|
+
warn e.backtrace.inspect
|
241
|
+
return ""
|
242
|
+
end
|
243
|
+
|
244
|
+
def debug
|
245
|
+
puts(yield) if _debug && block_given?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Takes a hash of file-key and file-name and returns a string of text
|
2
|
+
# formatted to be sent as a multipart form post.
|
3
|
+
|
4
|
+
require 'mime/types'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
|
8
|
+
module DoneDone
|
9
|
+
module Multipart
|
10
|
+
VERSION = "1.0.0" unless const_defined?(:VERSION)
|
11
|
+
|
12
|
+
# Formats a given hash as a multipart form post
|
13
|
+
# If determine if the params are Files or Strings and process
|
14
|
+
# appropriately
|
15
|
+
class Post
|
16
|
+
# We have to pretend like we're a web browser...
|
17
|
+
USERAGENT = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6" unless const_defined?(:USERAGENT)
|
18
|
+
BOUNDARY = '--JTMultiPart' + rand(1000000).to_s + 'ZZZZZ' unless const_defined?(:BOUNDARY)
|
19
|
+
CONTENT_TYPE = "multipart/form-data; boundary=#{ BOUNDARY }" unless const_defined?(:CONTENT_TYPE)
|
20
|
+
#HEADER = { "Content-Type" => CONTENT_TYPE, "User-Agent" => USERAGENT } unless const_defined?(:HEADER)
|
21
|
+
|
22
|
+
def self.prepare_query(params)
|
23
|
+
fp = []
|
24
|
+
|
25
|
+
params.each do |k, v|
|
26
|
+
if File.exists?(v) # must be a file
|
27
|
+
path = File.path(v)
|
28
|
+
content = File.binread(v)
|
29
|
+
fp.push(FileParam.new(k, path, content))
|
30
|
+
else # must be a string
|
31
|
+
fp.push(StringParam.new(k, v))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Assemble the request body using the special multipart format
|
36
|
+
query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
|
37
|
+
return query, CONTENT_TYPE, USERAGENT
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
class StringParam
|
45
|
+
attr_accessor :k, :v
|
46
|
+
|
47
|
+
def initialize(k, v)
|
48
|
+
@k = k
|
49
|
+
@v = v
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_multipart
|
53
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Formats the contents of a file for inclusion with a multipart
|
58
|
+
# form post
|
59
|
+
class FileParam
|
60
|
+
attr_accessor :k, :filename, :content
|
61
|
+
|
62
|
+
def initialize(k, filename, content)
|
63
|
+
@k = k
|
64
|
+
@filename = filename
|
65
|
+
@content = content
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_multipart
|
69
|
+
# If we can tell the possible mime-type from the filename, use the
|
70
|
+
# first in the list; otherwise, use "application/octet-stream"
|
71
|
+
mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
|
72
|
+
return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{ filename }\"\r\n" +
|
73
|
+
"Content-Transfer-Encoding: binary\r\n" +
|
74
|
+
"Content-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DoneDone::Constant do
|
4
|
+
describe ".url_for" do
|
5
|
+
context 'valid args' do
|
6
|
+
it "generates url with format string" do
|
7
|
+
expect(DoneDone::Constant.url_for('PEOPLE_IN_PROJECT')).to eq(DoneDone::Constant::PEOPLE_IN_PROJECT)
|
8
|
+
expect(DoneDone::Constant.url_for('PEOPLE_IN_PROJECT', '%s')).to eq(DoneDone::Constant::PEOPLE_IN_PROJECT)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "generates url without format string" do
|
12
|
+
expect(DoneDone::Constant.url_for('PROJECTS_WITH_ISSUES')).to eq(DoneDone::Constant::PROJECTS_WITH_ISSUES)
|
13
|
+
expect(DoneDone::Constant.url_for('PROJECTS_WITH_ISSUES', 'bogus_arg1')).to eq(DoneDone::Constant::PROJECTS_WITH_ISSUES)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DoneDone::Http do
|
4
|
+
class BogusNetHttp
|
5
|
+
attr_reader :host, :port, :use_ssl
|
6
|
+
attr_accessor :ssl_version, :verify_mode
|
7
|
+
def initialize(host, port)
|
8
|
+
@host = host
|
9
|
+
@port = port
|
10
|
+
clear
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear
|
14
|
+
@use_ssl = false
|
15
|
+
@verify_mode = nil
|
16
|
+
@ssl_version = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def use_ssl=(bool)
|
20
|
+
@use_ssl = !!bool
|
21
|
+
end
|
22
|
+
|
23
|
+
def request(request_object)
|
24
|
+
@request = request_object
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:net_http_class) { BogusNetHttp }
|
29
|
+
let(:domain) { "domain" }
|
30
|
+
let(:username) { "username" }
|
31
|
+
let(:password) { "password" }
|
32
|
+
let(:new_options) { {:http_class => net_http_class} }
|
33
|
+
let(:new_args) { [domain, username, password, new_options] }
|
34
|
+
let(:donedone_http_object) { DoneDone::Http.new(*new_args) }
|
35
|
+
let(:request_methods) { [:get, :put, :post] }
|
36
|
+
|
37
|
+
describe "init" do
|
38
|
+
context 'invalid args' do
|
39
|
+
it "raises an Exception for 0-2 args" do
|
40
|
+
expect { DoneDone::Http.new }.to raise_error()
|
41
|
+
expect { DoneDone::Http.new(domain) }.to raise_error()
|
42
|
+
expect { DoneDone::Http.new(domain, username) }.to raise_error()
|
43
|
+
end
|
44
|
+
it "raises an Exception for >4 args" do
|
45
|
+
expect { DoneDone::Http.new(domain, username, password, {}, :extra1) }.to raise_error()
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'valid args' do
|
50
|
+
it "requires 3-4 args" do
|
51
|
+
expect { DoneDone::Http.new(domain, username, password) }.to_not raise_error()
|
52
|
+
expect { DoneDone::Http.new(domain, username, password, {}) }.to_not raise_error()
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "request(s)" do
|
58
|
+
let(:method_url) { "method_url" }
|
59
|
+
|
60
|
+
context 'invalid args' do
|
61
|
+
it "will raise an Exception for 0 args" do
|
62
|
+
request_methods.each do |method|
|
63
|
+
expect { donedone_http_object.send(method) }.to raise_error
|
64
|
+
end
|
65
|
+
end
|
66
|
+
it "will raise an Exception for >2 args" do
|
67
|
+
request_methods.each do |method|
|
68
|
+
expect { donedone_http_object.send(method, method_url, {}, :extra1) }.to raise_error
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
context 'valid args' do
|
73
|
+
let(:ssl_port) { 443 }
|
74
|
+
it "will not raise an Exception for 1-2 args" do
|
75
|
+
request_methods.each do |method|
|
76
|
+
expect { donedone_http_object.send(method, method_url) }.to_not raise_error
|
77
|
+
expect { donedone_http_object.send(method, method_url, {}) }.to_not raise_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# a bit fragile: specific to Net::HTTP
|
82
|
+
it "sets-up a proper http object for :get" do
|
83
|
+
expect(donedone_http_object.host).to start_with(domain)
|
84
|
+
net_http_obj = net_http_class.new(donedone_http_object.host, ssl_port)
|
85
|
+
net_http_class.should_receive(:new).exactly(request_methods.length).times.with(donedone_http_object.host, ssl_port).and_return(net_http_obj)
|
86
|
+
|
87
|
+
request_methods.each do |method|
|
88
|
+
expect(net_http_obj.use_ssl).to eq(false)
|
89
|
+
expect(net_http_obj.verify_mode).to eq(nil)
|
90
|
+
expect(net_http_obj.ssl_version).to eq(nil)
|
91
|
+
donedone_http_object.send(method, method_url)
|
92
|
+
expect(net_http_obj.use_ssl).to eq(true)
|
93
|
+
expect(net_http_obj.verify_mode).to eq(DoneDone::Constant::SSL_VERIFY_MODE)
|
94
|
+
expect(net_http_obj.ssl_version).to eq(DoneDone::Constant::SSL_VERSION)
|
95
|
+
donedone_http_object.reset
|
96
|
+
net_http_obj.clear
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DoneDone::IssueTracker do
|
4
|
+
let(:bogus_http_helper) { Object.new }
|
5
|
+
let(:http_helper) { bogus_http_helper }
|
6
|
+
let(:domain) { :domain }
|
7
|
+
let(:username) { :username }
|
8
|
+
let(:password) { :password }
|
9
|
+
let(:http_helper_options) { {:debug=>false} }
|
10
|
+
let(:issue_tracker) { DoneDone::IssueTracker.new(domain, username, password, :http_helper => http_helper) }
|
11
|
+
|
12
|
+
describe "init" do
|
13
|
+
context 'invalid args' do
|
14
|
+
it "raises an Exception for 0-1 args" do
|
15
|
+
expect { DoneDone::IssueTracker.new }.to raise_error()
|
16
|
+
expect { DoneDone::IssueTracker.new(domain) }.to raise_error()
|
17
|
+
end
|
18
|
+
it "raises an Exception for >4 args" do
|
19
|
+
expect { DoneDone::IssueTracker.new(domain, username, password, {}, :extra1) }.to raise_error()
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'valid args' do
|
24
|
+
it "requires 2-4 args" do
|
25
|
+
expect { DoneDone::IssueTracker.new(domain, username) }.to_not raise_error()
|
26
|
+
expect { DoneDone::IssueTracker.new(domain, username, password) }.to_not raise_error()
|
27
|
+
expect { DoneDone::IssueTracker.new(domain, username, password, {}) }.to_not raise_error()
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "methods" do
|
33
|
+
let(:expected_api_methods) { [ :projects, :priority_levels, :people_in_project, :issues_in_project, :issue_exist?, :potential_statuses_for_issue, :issue, :people_for_issue_assignment, :create_issue, :create_comment, :update_issue ] }
|
34
|
+
|
35
|
+
let(:actual_helper_methods) { DoneDone::IssueTracker::HELPER_METHODS }
|
36
|
+
let(:actual_api_methods) { DoneDone::IssueTracker.api_methods }
|
37
|
+
|
38
|
+
it "distinguises its api- and helper-methods" do
|
39
|
+
expect(actual_api_methods).to_not include(actual_helper_methods)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "knows its api methods" do
|
43
|
+
expect(DoneDone::IssueTracker.api_methods).to eq(expected_api_methods)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "responds to its expected-helper and -api methods" do
|
47
|
+
(actual_helper_methods + actual_api_methods).each do |expected_method|
|
48
|
+
expect(issue_tracker).to respond_to(expected_method)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "api-requests" do
|
54
|
+
it "requests all projects" do
|
55
|
+
http_helper.should_receive(:get).with( DoneDone::Constant::PROJECTS, http_helper_options )
|
56
|
+
|
57
|
+
issue_tracker.projects
|
58
|
+
end
|
59
|
+
|
60
|
+
it "requests all projects with their issues" do
|
61
|
+
http_helper.should_receive(:get).with( DoneDone::Constant::PROJECTS_WITH_ISSUES, http_helper_options)
|
62
|
+
|
63
|
+
issue_tracker.projects(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "requests all priority_levels" do
|
67
|
+
http_helper.should_receive(:get).with( DoneDone::Constant::PRIORITY_LEVELS, http_helper_options)
|
68
|
+
|
69
|
+
issue_tracker.priority_levels
|
70
|
+
end
|
71
|
+
|
72
|
+
it "requests all people_in_project" do
|
73
|
+
project_id = 1
|
74
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('PEOPLE_IN_PROJECT', project_id), http_helper_options)
|
75
|
+
|
76
|
+
issue_tracker.people_in_project(project_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "requests all issues_in_project" do
|
80
|
+
project_id = 1
|
81
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('ISSUES_IN_PROJECT', project_id), http_helper_options)
|
82
|
+
|
83
|
+
issue_tracker.issues_in_project(project_id)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "requests if an issue exists for a project" do
|
87
|
+
project_id = 1
|
88
|
+
issue_order_number = 1
|
89
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('DOES_ISSUE_EXIST', project_id, issue_order_number), http_helper_options)
|
90
|
+
|
91
|
+
issue_tracker.issue_exist?(project_id, issue_order_number)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "requests status for a project's issue" do
|
95
|
+
project_id = 1
|
96
|
+
issue_order_number = 1
|
97
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('POTENTIAL_STATUSES_FOR_ISSUE', project_id, issue_order_number), http_helper_options)
|
98
|
+
|
99
|
+
issue_tracker.potential_statuses_for_issue(project_id, issue_order_number)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "requests a project's issue's details" do
|
103
|
+
project_id = 1
|
104
|
+
issue_order_number = 1
|
105
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('ISSUE', project_id, issue_order_number), http_helper_options)
|
106
|
+
|
107
|
+
issue_tracker.issue(project_id, issue_order_number)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "requests people for issue assignment" do
|
111
|
+
project_id = 1
|
112
|
+
issue_order_number = 1
|
113
|
+
http_helper.should_receive(:get).with( DoneDone::Constant.url_for('PEOPLE_FOR_ISSUE_ASSIGNMENT', project_id, issue_order_number), http_helper_options)
|
114
|
+
|
115
|
+
issue_tracker.people_for_issue_assignment(project_id, issue_order_number)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "requests to create an issue" do
|
119
|
+
project_id = 1
|
120
|
+
title = 'required title'
|
121
|
+
priority_level_id = 2 # required
|
122
|
+
resolver_id = 2 # required
|
123
|
+
tester_id = 2 # required
|
124
|
+
|
125
|
+
data = {
|
126
|
+
'title' => title,
|
127
|
+
'priority_level_id' => priority_level_id,
|
128
|
+
'resolver_id' => resolver_id,
|
129
|
+
'tester_id' => tester_id,
|
130
|
+
}
|
131
|
+
|
132
|
+
options = http_helper_options.merge(:data => data)
|
133
|
+
http_helper.should_receive(:post).with( DoneDone::Constant.url_for('CREATE_ISSUE', project_id), options)
|
134
|
+
|
135
|
+
issue_tracker.create_issue(project_id, title, priority_level_id, resolver_id, tester_id)
|
136
|
+
end
|
137
|
+
|
138
|
+
# todo: test for passing file upload(s)
|
139
|
+
it "requests to create a comment for a project's issue" do
|
140
|
+
project_id = 1
|
141
|
+
issue_order_number = 1
|
142
|
+
comment = 'required comment'
|
143
|
+
|
144
|
+
data = { 'comment' => comment }
|
145
|
+
|
146
|
+
options = http_helper_options.merge(:data => data)
|
147
|
+
http_helper.should_receive(:post).with( DoneDone::Constant.url_for('COMMENT', project_id, issue_order_number), options)
|
148
|
+
|
149
|
+
issue_tracker.create_comment(project_id, issue_order_number, comment)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "requests to update a project's issue" do
|
153
|
+
project_id = 1
|
154
|
+
issue_order_number = 1
|
155
|
+
|
156
|
+
options = http_helper_options
|
157
|
+
http_helper.should_receive(:put).with( DoneDone::Constant.url_for('ISSUE', project_id, issue_order_number), options)
|
158
|
+
|
159
|
+
issue_tracker.update_issue(project_id, issue_order_number, options)
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: donedone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Thomas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mime-types
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.14.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.14.0
|
41
|
+
description: Check your existing todo items, add new ones, update old one
|
42
|
+
email:
|
43
|
+
- buyer+donedone@his-service.net
|
44
|
+
executables:
|
45
|
+
- donedone
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- MIT-LICENSE
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/donedone
|
55
|
+
- donedone.gemspec
|
56
|
+
- examples/lighthouse_csv_importer.rb
|
57
|
+
- lib/donedone.rb
|
58
|
+
- lib/donedone/constant.rb
|
59
|
+
- lib/donedone/http.rb
|
60
|
+
- lib/donedone/issue_tracker.rb
|
61
|
+
- lib/donedone/multipart.rb
|
62
|
+
- lib/donedone/version.rb
|
63
|
+
- spec/donedone/constant_spec.rb
|
64
|
+
- spec/donedone/http_spec.rb
|
65
|
+
- spec/donedone/issue_tracker_spec.rb
|
66
|
+
- spec/spec_helper.rb
|
67
|
+
homepage: https://github.com/zoodles/DoneDone-API-Ruby
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata: {}
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project: donedone
|
87
|
+
rubygems_version: 2.0.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: donedone.com api client
|
91
|
+
test_files:
|
92
|
+
- spec/donedone/constant_spec.rb
|
93
|
+
- spec/donedone/http_spec.rb
|
94
|
+
- spec/donedone/issue_tracker_spec.rb
|
95
|
+
- spec/spec_helper.rb
|