zendesk2 1.0.0 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -5
  3. data/Gemfile +2 -2
  4. data/Guardfile +1 -1
  5. data/LICENSE.txt +1 -1
  6. data/README.md +2 -9
  7. data/Rakefile +3 -1
  8. data/lib/zendesk2/client/{models → collections}/categories.rb +0 -0
  9. data/lib/zendesk2/client/{models → collections}/forums.rb +0 -0
  10. data/lib/zendesk2/client/{models → collections}/groups.rb +0 -0
  11. data/lib/zendesk2/client/{models → collections}/memberships.rb +0 -0
  12. data/lib/zendesk2/client/{models → collections}/organizations.rb +0 -0
  13. data/lib/zendesk2/client/{models → collections}/ticket_audits.rb +0 -0
  14. data/lib/zendesk2/client/{models → collections}/ticket_comments.rb +0 -0
  15. data/lib/zendesk2/client/{models → collections}/ticket_fields.rb +0 -0
  16. data/lib/zendesk2/client/{models → collections}/ticket_metrics.rb +0 -0
  17. data/lib/zendesk2/client/{models → collections}/tickets.rb +0 -0
  18. data/lib/zendesk2/client/{models → collections}/topic_comments.rb +0 -0
  19. data/lib/zendesk2/client/{models → collections}/topics.rb +0 -0
  20. data/lib/zendesk2/client/{models → collections}/user_fields.rb +0 -0
  21. data/lib/zendesk2/client/{models → collections}/user_identities.rb +0 -3
  22. data/lib/zendesk2/client/{models → collections}/users.rb +0 -0
  23. data/lib/zendesk2/client/mock.rb +164 -0
  24. data/lib/zendesk2/client/real.rb +60 -0
  25. data/lib/zendesk2/client/requests/create_organization.rb +1 -1
  26. data/lib/zendesk2/client/requests/create_user.rb +1 -1
  27. data/lib/zendesk2/client/requests/create_user_identity.rb +7 -1
  28. data/lib/zendesk2/client/requests/search.rb +4 -4
  29. data/lib/zendesk2/client/requests/search_user.rb +3 -2
  30. data/lib/zendesk2/client.rb +17 -341
  31. data/lib/zendesk2/searchable.rb +31 -2
  32. data/lib/zendesk2/version.rb +1 -1
  33. data/lib/zendesk2.rb +10 -10
  34. data/spec/organizations_spec.rb +1 -1
  35. data/spec/shared/zendesk_resource.rb +8 -2
  36. data/spec/support/client_helper.rb +6 -1
  37. data/spec/tickets_spec.rb +13 -8
  38. data/spec/user_identities_spec.rb +23 -6
  39. data/spec/users_spec.rb +1 -1
  40. data/zendesk2.gemspec +1 -1
  41. metadata +27 -21
  42. data/LICENSE +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fb950fdc0ae118b3bba5edfb7a2729bec53561d
4
- data.tar.gz: 2d427b0a21b5b0fdc8551013a2c76f1a36bf7587
3
+ metadata.gz: 270b2d17ca7f778a7021f68b64e4cbe4451f48bd
4
+ data.tar.gz: bd9144b1cc0adb733437334f62b60ed38daa6558
5
5
  SHA512:
6
- metadata.gz: 4094b670f1a2f9e40a391ad70db20f3e5699b8eb1661d643d5a1e62ad80e0e81238470c67d7fa1d64e38f958b331ff4164bbe06f476b3329a8d0b144a087d6f4
7
- data.tar.gz: 5f42f0d0fc4fe7948baf89c14c424ee31a4b3c0794fbf31302c1af80b37b25416b58488607d3afa369ba0ccb4e763b7574b92694570a5fad1d057ab95b5e1641
6
+ metadata.gz: 84675208061a34c5b10bffecc1f06b78f6842aa6c2503bfac0a916a565d6e080dbdba095906ff27d9cd00cbb41df663099ef1a86bd3d86ae3cbe5aaeb60c67e8
7
+ data.tar.gz: c3c62ea6e1ed67e048f2cefe75fec4f142846776063461730f2f89110b88d9fe8b081c49a785ba10f54a10147d27a796e4281fe188dd87c2024b523de92e2c0d
data/.travis.yml CHANGED
@@ -1,11 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.1.1"
3
+ - "2.1.2"
4
4
  - "1.9.3"
5
- - "jruby-19mode"
6
- bundler_args: "--without development"
7
- before_install:
8
- - "gem install bundler -v 1.5.2"
9
5
  script: "bundle exec rake --trace"
10
6
  notifications:
11
7
  email:
data/Gemfile CHANGED
@@ -14,6 +14,6 @@ end
14
14
  group :test do
15
15
  gem 'awesome_print'
16
16
  gem 'guard-bundler', require: false
17
- gem 'guard-rspec', '~> 4.2', require: false
18
- gem 'rspec', '~> 2.99'
17
+ gem 'guard-rspec', '~> 4.3', require: false
18
+ gem 'rspec', '~> 3.0'
19
19
  end
data/Guardfile CHANGED
@@ -3,7 +3,7 @@ guard 'bundler' do
3
3
  watch(/^.+\.gemspec/)
4
4
  end
5
5
 
6
- guard 'rspec', cmd: 'bundle exec rspec', all_on_start: true, all_after_pass: true do
6
+ guard 'rspec', cmd: 'bundle exec rspec', all_on_start: true, all_after_pass: true, failed_mode: :focus do
7
7
  watch(%r{^spec/.+_spec\.rb$})
8
8
  watch(%r{^lib/(.+)\.rb$}) { "spec" }
9
9
  watch('spec/spec_helper.rb') { "spec" }
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Josh Lane
1
+ Copyright (c) 2014 Josh Lane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -37,7 +37,7 @@ Default credentials will be read in from `~/.zendesk2` file in YAML format.
37
37
 
38
38
  ```yaml
39
39
  ---
40
- :subdomain: zendeskdev
40
+ :url: https://www.zendesk.com
41
41
  :username: zendeskedge@example.com
42
42
  :password: wickedsecurepassword
43
43
  :token: reallylongrandomstringprovidedbyzendesk
@@ -45,14 +45,7 @@ Default credentials will be read in from `~/.zendesk2` file in YAML format.
45
45
 
46
46
  ### Creating the client
47
47
 
48
- Either the absolute url or the subdomain is required. Username and either password or token are always required.
49
-
50
- ```ruby
51
- Zendesk2::Client.new(subdomain: "engineyard", username: "orchestra", password: "gwoo")
52
- => #<Zendesk2::Client::Real:0x007f99da1f9430 @url="https://engineyard.zendesk.com/api/v2", @username="orchestra", @password="gwoo", …>
53
- ```
54
-
55
- or
48
+ Url is always required. Username and either password or token are always required.
56
49
 
57
50
  ```ruby
58
51
  Zendesk2::Client.new(url: "http://support.cloud.engineyard.com", username: "mate", token: "asdfghjkl1qwertyuiop5zxcvbnm3")
data/Rakefile CHANGED
@@ -2,6 +2,8 @@
2
2
  require "bundler/gem_tasks"
3
3
  require 'rspec/core/rake_task'
4
4
 
5
- RSpec::Core::RakeTask.new(:spec)
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.rspec_opts = "-cfd"
7
+ end
6
8
 
7
9
  task :default => :spec
@@ -1,6 +1,4 @@
1
1
  class Zendesk2::Client::UserIdentities < Zendesk2::Collection
2
- include Zendesk2::Searchable
3
-
4
2
  model Zendesk2::Client::UserIdentity
5
3
 
6
4
  attribute :user_id, type: :integer
@@ -9,7 +7,6 @@ class Zendesk2::Client::UserIdentities < Zendesk2::Collection
9
7
  self.collection_root = "identities"
10
8
  self.model_method = :get_user_identity
11
9
  self.model_root = "identity"
12
- self.search_type = "identity"
13
10
 
14
11
  scopes << :user_id
15
12
  end
@@ -0,0 +1,164 @@
1
+ class Zendesk2::Client < Cistern::Service
2
+ class Mock
3
+ include Shared
4
+
5
+ attr_reader :username, :url, :token, :jwt_token
6
+
7
+ def self.data
8
+ @data ||= {
9
+ :categories => {},
10
+ :forums => {},
11
+ :groups => {},
12
+ :identities => {},
13
+ :memberships => {},
14
+ :organizations => {},
15
+ :ticket_audits => {},
16
+ :ticket_comments => {},
17
+ :ticket_fields => {},
18
+ :ticket_metrics => {},
19
+ :tickets => {},
20
+ :topic_comments => {},
21
+ :topics => {},
22
+ :user_fields => {},
23
+ :users => {},
24
+ }
25
+ end
26
+
27
+ def self.new_id
28
+ @current_id ||= 0
29
+ @current_id += 1
30
+ end
31
+
32
+ def data
33
+ self.class.data
34
+ end
35
+
36
+ def self.reset!
37
+ @data = nil
38
+ end
39
+
40
+ def initialize(options={})
41
+ @url = options[:url]
42
+ @path = ::URI.parse(url).path
43
+ @username, @password = options[:username], options[:password]
44
+ @token = options[:token]
45
+ @jwt_token = options[:jwt_token]
46
+
47
+ @current_user ||= self.create_user("email" => @username, "name" => "Mock Agent").body["user"]
48
+ @current_user_identity ||= self.data[:identities].values.first
49
+ end
50
+
51
+ # Lazily re-seeds data after reset
52
+ # @return [Hash] current user response
53
+ def current_user
54
+ self.data[:users][@current_user["id"]] ||= @current_user
55
+ self.data[:identities][@current_user_identity["id"]] ||= @current_user_identity
56
+
57
+ @current_user
58
+ end
59
+
60
+ def url_for(path)
61
+ File.join(@url, "/api/v2", path.to_s)
62
+ end
63
+
64
+ def resources(collection, path, collection_root, options={})
65
+ filter = options[:filter]
66
+ resources = self.data[collection].values
67
+ resources = filter.call(resources) if filter
68
+ count = resources.size
69
+
70
+ response(
71
+ :body => {
72
+ collection_root => resources,
73
+ "count" => count,
74
+ },
75
+ :path => path
76
+ )
77
+ end
78
+
79
+ def page(params, collection, path, collection_root, options={})
80
+ page_params = Zendesk2.paging_parameters(params)
81
+ page_size = (page_params["per_page"] || 50).to_i
82
+ page_index = (page_params["page"] || 1).to_i
83
+ offset = (page_index - 1) * page_size
84
+ filter = options[:filter]
85
+ resources = self.data[collection].values
86
+ resources = filter.call(resources) if filter
87
+ count = resources.size
88
+ total_pages = (count / page_size) + 1
89
+
90
+ next_page = if page_index < total_pages
91
+ url_for("#{path}?page=#{page_index + 1}&per_page=#{page_size}")
92
+ end
93
+ previous_page = if page_index > 1
94
+ url_for("#{path}?page=#{page_index - 1}&per_page=#{page_size}")
95
+ end
96
+
97
+ resource_page = resources.slice(offset, page_size)
98
+
99
+ body = {
100
+ collection_root => resource_page,
101
+ "count" => count,
102
+ "next_page" => next_page,
103
+ "previous_page" => previous_page,
104
+ }
105
+
106
+ response(
107
+ :body => body,
108
+ :path => path
109
+ )
110
+ end
111
+
112
+ def pluralize(word)
113
+ pluralized = word.dup
114
+ [[/y$/, 'ies'], [/$/, 's']].find{|regex, replace| pluralized.gsub!(regex, replace) if pluralized.match(regex)}
115
+ pluralized
116
+ end
117
+
118
+ def self.error_map
119
+ @@error_map ||= {
120
+ :invalid => [422, {
121
+ "error" => "RecordInvalid",
122
+ "description" => "Record validation errors",
123
+ }],
124
+ :not_found => [404, {
125
+ "error" => "RecordNotFound",
126
+ "description" => "Not found",
127
+ }],
128
+ }
129
+ end
130
+
131
+ def find!(collection, identity, options={})
132
+ if resource = self.data[collection][identity]
133
+ resource
134
+ else
135
+ error!(options[:error] || :not_found)
136
+ end
137
+ end
138
+
139
+ def error!(type, options={})
140
+ status, body = self.class.error_map[type]
141
+ body.merge!("details" => options[:details]) if options[:details]
142
+
143
+ response(
144
+ :status => status,
145
+ :body => body,
146
+ )
147
+ end
148
+
149
+ def response(options={})
150
+ method = options[:method] || :get
151
+ status = options[:status] || 200
152
+ path = options[:path]
153
+ body = options[:body]
154
+
155
+ url = options[:url] || url_for(path)
156
+
157
+ env = Faraday::Env.new(method, body, url, nil, {}, nil, nil, {}, nil, {"Content-Type" => "application/json; charset=utf-8"}, status)
158
+
159
+ Faraday::Response::RaiseError.new.on_complete(env) || Faraday::Response.new(env)
160
+ rescue Faraday::Error::ClientError => e
161
+ raise Zendesk2::Error.new(e)
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,60 @@
1
+ class Zendesk2::Client < Cistern::Service
2
+ class Real
3
+ include Zendesk2::Client::Shared
4
+
5
+ attr_accessor :username, :url, :token, :logger, :jwt_token
6
+
7
+ def initialize(options={})
8
+ @url = if url = options[:url] || Zendesk2.defaults[:url]
9
+ ::URI.parse(url).to_s
10
+ end
11
+
12
+ @logger = options[:logger] || Logger.new(nil)
13
+ adapter = options[:adapter] || Faraday.default_adapter
14
+ @username = options[:username] || Zendesk2.defaults[:username]
15
+ @token = options[:token] || Zendesk2.defaults[:token]
16
+ password = options[:password] || Zendesk2.defaults[:password]
17
+
18
+ connection_options = options[:connection_options] || {}
19
+
20
+ @auth_token = password || @token
21
+ @username += "/token" if @auth_token == @token
22
+ @jwt_token = options[:jwt_token]
23
+
24
+ raise "Missing required options: :url" unless @url
25
+ raise "Missing required options: :username" unless @username
26
+ raise "Missing required options: :password or :token" unless password || @token
27
+
28
+ @connection = Faraday.new({url: @url}.merge(connection_options)) do |builder|
29
+ # response
30
+ builder.use Faraday::Request::BasicAuthentication, @username, @auth_token
31
+ builder.use Faraday::Response::RaiseError
32
+ builder.response :json
33
+
34
+ # request
35
+ builder.request :multipart
36
+ builder.request :json
37
+
38
+ builder.use Zendesk2::Logger, @logger
39
+ builder.adapter adapter
40
+ end
41
+ end
42
+
43
+ def request(options={})
44
+ method = options[:method] || :get
45
+ url = options[:url] || File.join(@url, "/api/v2", options[:path])
46
+ params = options[:params] || {}
47
+ body = options[:body]
48
+ headers = {"User-Agent" => USER_AGENT}.merge(options[:headers] || {})
49
+
50
+ @connection.send(method) do |req|
51
+ req.url url
52
+ req.headers.merge!(headers)
53
+ req.params.merge!(params)
54
+ req.body = body
55
+ end
56
+ rescue Faraday::Error::ClientError => e
57
+ raise Zendesk2::Error.new(e)
58
+ end
59
+ end
60
+ end
@@ -52,7 +52,7 @@ class Zendesk2::Client
52
52
  "details" => {
53
53
  "name" => [
54
54
  {
55
- "description" => "Name has already been taken"
55
+ "description" => "Name: has already been taken"
56
56
  }
57
57
  ]
58
58
  }
@@ -43,7 +43,7 @@ class Zendesk2::Client
43
43
  }
44
44
 
45
45
  self.data[:identities][user_identity_id] = user_identity
46
- self.data[:users][user_id] = record.reject{|k,v| k == "email"}
46
+ self.data[:users][user_id] = record.reject { |k,v| k == "email" }
47
47
 
48
48
  response(
49
49
  :method => :post,
@@ -13,6 +13,8 @@ class Zendesk2::Client
13
13
 
14
14
  class Mock
15
15
  def create_user_identity(params={})
16
+ params = Cistern::Hash.stringify_keys(params)
17
+
16
18
  identity = self.class.new_id
17
19
  user_id = params["user_id"]
18
20
 
@@ -25,7 +27,11 @@ class Zendesk2::Client
25
27
  "primary" => false,
26
28
  }.merge(params)
27
29
 
28
- record.merge("primary" => true) if self.data[:identities].values.find{|ui| ui["user_id"] == user_id}.nil?
30
+ record.merge("primary" => true) if self.data[:identities].values.find { |ui| ui["user_id"] == user_id }.nil?
31
+
32
+ if self.data[:identities].values.find { |i| i["value"] == record["value"] }
33
+ error!(:invalid, details: { "value"=> [ { "description"=>"Value: #{record["value"]} is already being used by another user" } ] })
34
+ end
29
35
 
30
36
  self.data[:identities][identity] = record
31
37
 
@@ -1,10 +1,9 @@
1
1
  class Zendesk2::Client
2
2
  class Real
3
3
  def search(query)
4
- term = query.map{|k,v| "#{k}:#{v}"}.join(" ")
5
4
  request(
6
5
  :method => :get,
7
- :params => {query: term},
6
+ :params => {query: query},
8
7
  :path => "/search.json",
9
8
  )
10
9
  end
@@ -12,10 +11,11 @@ class Zendesk2::Client
12
11
 
13
12
  class Mock
14
13
  def search(query)
15
- type = query.delete("type")
14
+ terms = Hash[query.split(" ").map { |t| t.split(":") }]
15
+ type = terms.delete("type")
16
16
  collection = type.nil? ? self.data.values : self.data[pluralize(type).to_sym]
17
17
 
18
- results = collection.select{|k,v| query.all?{|term, condition| v[term.to_s] == condition}}.values
18
+ results = collection.values.select { |v| terms.all?{ |term, condition| v[term.to_s].to_s == condition.to_s } }
19
19
 
20
20
  response(
21
21
  :path => "/search.json",
@@ -5,7 +5,8 @@ class Zendesk2::Client
5
5
 
6
6
  class Mock
7
7
  def search_user(query)
8
- query.delete("type") # context already provided
8
+ terms = Hash[query.split(" ").map { |t| t.split(":") }]
9
+ terms.delete("type") # context already provided
9
10
 
10
11
  collection = self.data[:users].values
11
12
  collection = collection.map do |user|
@@ -14,7 +15,7 @@ class Zendesk2::Client
14
15
  end
15
16
  end.flatten
16
17
 
17
- results = collection.select{|v| query.all?{|term, condition| v[term.to_s] == condition}}
18
+ results = collection.select { |v| terms.all?{ |term, condition| v[term.to_s].to_s == condition.to_s } }
18
19
 
19
20
  response(
20
21
  :path => "/search.json",
@@ -1,125 +1,20 @@
1
1
  class Zendesk2::Client < Cistern::Service
2
2
  USER_AGENT = "Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}) Zendesk2/#{Zendesk2::VERSION} Faraday/#{Faraday::VERSION}".freeze
3
3
 
4
- model_path "zendesk2/client/models"
5
- request_path "zendesk2/client/requests"
6
-
7
- require 'zendesk2/client/models/audit_event' # so special
8
-
9
- collection :categories
10
- collection :forums
11
- collection :groups
12
- collection :memberships
13
- collection :organizations
14
- collection :ticket_audits
15
- collection :ticket_fields
16
- collection :ticket_metrics
17
- collection :tickets
18
- collection :ticket_comments
19
- collection :topic_comments
20
- collection :topics
21
- collection :user_fields
22
- collection :user_identities
23
- collection :users
24
- model :category
25
- model :forum
26
- model :group
27
- model :membership
28
- model :organization
29
- model :ticket
30
- model :ticket_audit
31
- model :ticket_metric
32
- model :ticket_change
33
- model :ticket_comment
34
- model :ticket_comment_privacy_change
35
- model :ticket_create
36
- model :ticket_field
37
- model :ticket_notification
38
- model :ticket_voice_comment
39
- model :topic
40
- model :topic_comment
41
- model :user
42
- model :user_field
43
- model :user_identity
44
-
45
- request :create_category
46
- request :create_forum
47
- request :create_group
48
- request :create_membership
49
- request :create_organization
50
- request :create_ticket
51
- request :create_ticket_field
52
- request :create_topic
53
- request :create_topic_comment
54
- request :create_user
55
- request :create_user_field
56
- request :create_user_identity
57
- request :destroy_category
58
- request :destroy_forum
59
- request :destroy_group
60
- request :destroy_membership
61
- request :destroy_organization
62
- request :destroy_ticket
63
- request :destroy_ticket_field
64
- request :destroy_topic
65
- request :destroy_topic_comment
66
- request :destroy_user
67
- request :destroy_user_field
68
- request :destroy_user_identity
69
- request :get_assignable_groups
70
- request :get_audits
71
- request :get_categories
72
- request :get_category
73
- request :get_ccd_tickets
74
- request :get_current_user
75
- request :get_forum
76
- request :get_forums
77
- request :get_group
78
- request :get_groups
79
- request :get_membership
80
- request :get_organization
81
- request :get_organization_by_external_id
82
- request :get_organization_memberships
83
- request :get_organization_tickets
84
- request :get_organization_users
85
- request :get_organizations
86
- request :get_requested_tickets
87
- request :get_ticket
88
- request :get_ticket_audit
89
- request :get_ticket_audits
90
- request :get_ticket_comments
91
- request :get_ticket_field
92
- request :get_ticket_fields
93
- request :get_ticket_metric
94
- request :get_ticket_metrics
95
- request :get_tickets
96
- request :get_topic
97
- request :get_topic_comment
98
- request :get_topic_comments
99
- request :get_topics
100
- request :get_user
101
- request :get_user_field
102
- request :get_user_fields
103
- request :get_user_identities
104
- request :get_user_identity
105
- request :get_user_memberships
106
- request :get_users
107
- request :mark_membership_default
108
- request :mark_user_identity_primary
109
- request :search
110
- request :search_user
111
- request :update_category
112
- request :update_forum
113
- request :update_group
114
- request :update_organization
115
- request :update_request
116
- request :update_ticket
117
- request :update_ticket_field
118
- request :update_topic
119
- request :update_topic_comment
120
- request :update_user
121
- request :update_user_field
122
- request :update_user_identity
4
+ collection_path "zendesk2/client/collections"
5
+ model_path "zendesk2/client/models"
6
+ request_path "zendesk2/client/requests"
7
+
8
+ # might be nice if cistern took care of this
9
+ [
10
+ [:collection, collection_path],
11
+ [:model, model_path],
12
+ [:request, request_path],
13
+ ].each do |type, path|
14
+ Dir[File.expand_path(File.join("../..", path, "*.rb"), __FILE__)].sort.each do |file|
15
+ send(type, File.basename(file, ".rb"))
16
+ end
17
+ end
123
18
 
124
19
  recognizes :url, :logger, :adapter, :username, :password, :token, :jwt_token
125
20
 
@@ -130,226 +25,7 @@ class Zendesk2::Client < Cistern::Service
130
25
  end
131
26
  end
132
27
  end
133
-
134
- class Real
135
- include Shared
136
-
137
- attr_accessor :username, :url, :token, :logger, :jwt_token
138
-
139
- def initialize(options={})
140
- @url = if url = options[:url] || Zendesk2.defaults[:url]
141
- ::URI.parse(url).to_s
142
- end
143
-
144
- @logger = options[:logger] || Logger.new(nil)
145
- adapter = options[:adapter] || Faraday.default_adapter
146
- @username = options[:username] || Zendesk2.defaults[:username]
147
- @token = options[:token] || Zendesk2.defaults[:token]
148
- password = options[:password] || Zendesk2.defaults[:password]
149
-
150
- connection_options = options[:connection_options] || {}
151
-
152
- @auth_token = password || @token
153
- @username += "/token" if @auth_token == @token
154
- @jwt_token = options[:jwt_token]
155
-
156
- raise "Missing required options: :url" unless @url
157
- raise "Missing required options: :username" unless @username
158
- raise "Missing required options: :password or :token" unless password || @token
159
-
160
- @connection = Faraday.new({url: @url}.merge(connection_options)) do |builder|
161
- # response
162
- builder.use Faraday::Request::BasicAuthentication, @username, @auth_token
163
- builder.use Faraday::Response::RaiseError
164
- builder.response :json
165
-
166
- # request
167
- builder.request :multipart
168
- builder.request :json
169
-
170
- builder.use Zendesk2::Logger, @logger
171
- builder.adapter adapter
172
- end
173
- end
174
-
175
- def request(options={})
176
- method = options[:method] || :get
177
- url = options[:url] || File.join(@url, "/api/v2", options[:path])
178
- params = options[:params] || {}
179
- body = options[:body]
180
- headers = {"User-Agent" => USER_AGENT}.merge(options[:headers] || {})
181
-
182
- @connection.send(method) do |req|
183
- req.url url
184
- req.headers.merge!(headers)
185
- req.params.merge!(params)
186
- req.body = body
187
- end
188
- rescue Faraday::Error::ClientError => e
189
- raise Zendesk2::Error.new(e)
190
- end
191
- end
192
-
193
- class Mock
194
- include Shared
195
-
196
- attr_reader :username, :url, :token, :jwt_token
197
-
198
- def self.data
199
- @data ||= {
200
- :categories => {},
201
- :forums => {},
202
- :groups => {},
203
- :identities => {},
204
- :memberships => {},
205
- :organizations => {},
206
- :ticket_audits => {},
207
- :ticket_comments => {},
208
- :ticket_fields => {},
209
- :ticket_metrics => {},
210
- :tickets => {},
211
- :topic_comments => {},
212
- :topics => {},
213
- :user_fields => {},
214
- :users => {},
215
- }
216
- end
217
-
218
- def self.new_id
219
- @current_id ||= 0
220
- @current_id += 1
221
- end
222
-
223
- def data
224
- self.class.data
225
- end
226
-
227
- def self.reset!
228
- @data = nil
229
- end
230
-
231
- def initialize(options={})
232
- @url = options[:url]
233
- @path = ::URI.parse(url).path
234
- @username, @password = options[:username], options[:password]
235
- @token = options[:token]
236
- @jwt_token = options[:jwt_token]
237
-
238
- @current_user ||= self.create_user("email" => @username, "name" => "Mock Agent").body["user"]
239
- @current_user_identity ||= self.data[:identities].values.first
240
- end
241
-
242
- # Lazily re-seeds data after reset
243
- # @return [Hash] current user response
244
- def current_user
245
- self.data[:users][@current_user["id"]] ||= @current_user
246
- self.data[:identities][@current_user_identity["id"]] ||= @current_user_identity
247
-
248
- @current_user
249
- end
250
-
251
- def url_for(path)
252
- File.join(@url, "/api/v2", path.to_s)
253
- end
254
-
255
- def resources(collection, path, collection_root, options={})
256
- filter = options[:filter]
257
- resources = self.data[collection].values
258
- resources = filter.call(resources) if filter
259
- count = resources.size
260
-
261
- response(
262
- :body => {
263
- collection_root => resources,
264
- "count" => count,
265
- },
266
- :path => path
267
- )
268
- end
269
-
270
- def page(params, collection, path, collection_root, options={})
271
- page_params = Zendesk2.paging_parameters(params)
272
- page_size = (page_params["per_page"] || 50).to_i
273
- page_index = (page_params["page"] || 1).to_i
274
- offset = (page_index - 1) * page_size
275
- filter = options[:filter]
276
- resources = self.data[collection].values
277
- resources = filter.call(resources) if filter
278
- count = resources.size
279
- total_pages = (count / page_size) + 1
280
-
281
- next_page = if page_index < total_pages
282
- url_for("#{path}?page=#{page_index + 1}&per_page=#{page_size}")
283
- end
284
- previous_page = if page_index > 1
285
- url_for("#{path}?page=#{page_index - 1}&per_page=#{page_size}")
286
- end
287
-
288
- resource_page = resources.slice(offset, page_size)
289
-
290
- body = {
291
- collection_root => resource_page,
292
- "count" => count,
293
- "next_page" => next_page,
294
- "previous_page" => previous_page,
295
- }
296
-
297
- response(
298
- :body => body,
299
- :path => path
300
- )
301
- end
302
-
303
- def pluralize(word)
304
- pluralized = word.dup
305
- [[/y$/, 'ies'], [/$/, 's']].find{|regex, replace| pluralized.gsub!(regex, replace) if pluralized.match(regex)}
306
- pluralized
307
- end
308
-
309
- def self.error_map
310
- @@error_map ||= {
311
- :invalid => [422, {
312
- "error" => "RecordInvalid",
313
- "description" => "Record validation errors",
314
- }],
315
- :not_found => [404, {
316
- "error" => "RecordNotFound",
317
- "description" => "Not found",
318
- }],
319
- }
320
- end
321
-
322
- def find!(collection, identity, options={})
323
- if resource = self.data[collection][identity]
324
- resource
325
- else
326
- error!(options[:error] || :not_found)
327
- end
328
- end
329
-
330
- def error!(type, options={})
331
- status, body = self.class.error_map[type]
332
- body.merge!("details" => options[:details]) if options[:details]
333
-
334
- response(
335
- :status => status,
336
- :body => body,
337
- )
338
- end
339
-
340
- def response(options={})
341
- method = options[:method] || :get
342
- status = options[:status] || 200
343
- path = options[:path]
344
- body = options[:body]
345
-
346
- url = options[:url] || url_for(path)
347
-
348
- env = Faraday::Env.new(method, body, url, nil, {}, nil, nil, {}, nil, {"Content-Type" => "application/json; charset=utf-8"}, status)
349
-
350
- Faraday::Response::RaiseError.new.on_complete(env) || Faraday::Response.new(env)
351
- rescue Faraday::Error::ClientError => e
352
- raise Zendesk2::Error.new(e)
353
- end
354
- end
355
28
  end
29
+
30
+ require 'zendesk2/client/real'
31
+ require 'zendesk2/client/mock'
@@ -3,8 +3,37 @@ module Zendesk2::Searchable
3
3
  klass.send(:extend, Zendesk2::Searchable::Attributes)
4
4
  end
5
5
 
6
- def search(parameters)
7
- body = connection.send(self.class.search_request, parameters.merge("type" => self.class.search_type)).body
6
+ # Search for resources of a certain type.
7
+ #
8
+ # If you need more control over your search (see Zendesk2::Client::Real#search)
9
+ #
10
+ # @example search with a simple hash
11
+ # client.users.search("email" => "jlane@engineyard.com")
12
+ # @example search with fully qualified query
13
+ # client.tickets.search("created>2012-06-17+type:ticket+organization:'Mondocam Photo'")
14
+ #
15
+ # @param [String, Hash] seach terms. This will be converted to a qualified Zendesk search String
16
+ # @see http://developer.zendesk.com/documentation/rest_api/search.html
17
+ def search(terms)
18
+ query = if terms.is_a?(Hash)
19
+ terms.merge("type" => self.class.search_type).
20
+ merge(self.class.scopes.inject({}){|r,k| r.merge(k.to_s => public_send(k))}).
21
+ map { |k,v| "#{k}:#{v}" }.join(" ")
22
+ else
23
+ additional_terms = ["type:#{self.class.search_type}"]
24
+ additional_terms += self.class.scopes.inject([]) { |r,k| ["#{k}:#{public_send(k)}"] }
25
+
26
+ additional_terms.inject(terms.to_s) do |qualified_search, qualification|
27
+ if !qualified_search.include?(qualification)
28
+ "#{qualified_search} #{qualification}"
29
+ else
30
+ qualified_search
31
+ end
32
+ end
33
+ end
34
+
35
+ body = connection.send(self.class.search_request, query).body
36
+
8
37
  if data = body.delete("results")
9
38
  collection = self.clone.load(data)
10
39
  collection.merge_attributes(Cistern::Hash.slice(body, "count", "next_page", "previous_page"))
@@ -1,3 +1,3 @@
1
1
  module Zendesk2
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.2"
3
3
  end
data/lib/zendesk2.rb CHANGED
@@ -15,16 +15,6 @@ require 'yaml'
15
15
  require 'uri'
16
16
 
17
17
  module Zendesk2
18
- require 'zendesk2/attributes'
19
- require 'zendesk2/attributes'
20
- require 'zendesk2/error'
21
- require 'zendesk2/client'
22
- require 'zendesk2/searchable'
23
- require 'zendesk2/logger'
24
- require 'zendesk2/model'
25
- require 'zendesk2/collection'
26
- require 'zendesk2/paged_collection'
27
-
28
18
  def self.defaults
29
19
  @defaults ||= if File.exists?(File.expand_path("~/.zendesk2"))
30
20
  YAML.load_file(File.expand_path("~/.zendesk2"))
@@ -54,3 +44,13 @@ module Zendesk2
54
44
  string.nil? || string == ""
55
45
  end
56
46
  end
47
+
48
+ require 'zendesk2/attributes'
49
+ require 'zendesk2/error'
50
+ require 'zendesk2/searchable'
51
+ require 'zendesk2/logger'
52
+ require 'zendesk2/model'
53
+ require 'zendesk2/collection'
54
+ require 'zendesk2/paged_collection'
55
+
56
+ require 'zendesk2/client'
@@ -25,7 +25,7 @@ describe "organizations" do
25
25
  it "should hate non-unique names" do
26
26
  expect { client.organizations.create!(name: organization.name) }.to raise_exception(Zendesk2::Error)
27
27
  model = client.organizations.create(name: organization.name)
28
- expect(model.errors).to eq({"name" => ["Name has already been taken"]})
28
+ expect(model.errors).to eq({"name" => ["Name: has already been taken"]})
29
29
  end
30
30
 
31
31
  it "should be able to find organizations by external id" do
@@ -4,6 +4,7 @@ shared_examples "zendesk resource" do |options={}|
4
4
  let(:create_params) { instance_exec(&options[:create_params]) || {} }
5
5
  let(:update_params) { instance_exec(&options[:update_params]) }
6
6
  let(:fetch_params) { options[:fetch_params] || lambda { |r| r.identity } }
7
+ let(:search_params) { options[:search_params] ? instance_exec(&options[:search_params]) : create_params }
7
8
 
8
9
  let(:record) { @record }
9
10
  after(:each) { @record && @record.destroy }
@@ -73,9 +74,14 @@ shared_examples "zendesk resource" do |options={}|
73
74
 
74
75
  if options.fetch(:search, true) && Zendesk2::Client.mocking?
75
76
  # Search index takes 2-3 minutes according to the docs
76
- it "should search" do
77
+ it "should search by hash" do
77
78
  @record = collection.create!(create_params)
78
- expect(collection.search(create_params)).to include(record)
79
+ expect(collection.search(search_params).to_a).to eq([record])
80
+ end
81
+
82
+ it "should search by string" do
83
+ @record = collection.create!(create_params)
84
+ expect(collection.search(search_params.map { |k,v| [k,v].join(":") }.join(" ")).to_a).to eq([record])
79
85
  end
80
86
  end
81
87
  end
@@ -3,7 +3,12 @@ require 'logger'
3
3
  module ClientHelper
4
4
  def create_client(options={})
5
5
  options.merge!(logger: Logger.new(STDOUT)) if ENV['VERBOSE']
6
- options = {username: "zendesk2@example.org", password: "password"}.merge(Zendesk2.defaults).merge(options)
6
+ options = {
7
+ :username => "zendesk2@example.org",
8
+ :password => "password",
9
+ :url => "https://www.zendesk.com",
10
+ }.merge(Zendesk2.defaults).merge(options)
11
+
7
12
  Zendesk2::Client.new(options)
8
13
  end
9
14
  end
data/spec/tickets_spec.rb CHANGED
@@ -3,11 +3,13 @@ require 'spec_helper'
3
3
  describe "Zendesk2::Client" do
4
4
  let(:client) { create_client }
5
5
 
6
- include_examples "zendesk resource", {
7
- :collection => lambda { client.tickets },
8
- :create_params => lambda { {subject: Zendesk2.uuid, description: Zendesk2.uuid} },
9
- :update_params => lambda { {subject: Zendesk2.uuid} },
10
- }
6
+ describe "tickets" do
7
+ include_examples "zendesk resource", {
8
+ :collection => lambda { client.tickets },
9
+ :create_params => lambda { {subject: Zendesk2.uuid, description: Zendesk2.uuid} },
10
+ :update_params => lambda { {subject: Zendesk2.uuid} },
11
+ }
12
+ end
11
13
 
12
14
  describe "#create_ticket" do
13
15
  it "should require a description" do
@@ -74,7 +76,7 @@ describe "Zendesk2::Client" do
74
76
  end
75
77
  end
76
78
 
77
- describe "comments" do
79
+ describe "ticket comments" do
78
80
  let(:ticket) { client.tickets.create!(subject: Zendesk2.uuid, description: Zendesk2.uuid) }
79
81
 
80
82
  it "lists audits" do
@@ -85,7 +87,10 @@ describe "Zendesk2::Client" do
85
87
  expect(audit.ticket).to eq(ticket)
86
88
 
87
89
  events = audit.events
88
- expect(events.size).to eq(1)
90
+
91
+ if Zendesk2::Client.mocking?
92
+ expect(events.size).to eq(1)
93
+ end
89
94
 
90
95
  event = events.first
91
96
  expect(event.body).to eq(body)
@@ -101,7 +106,7 @@ describe "Zendesk2::Client" do
101
106
  end
102
107
  end
103
108
 
104
- describe "custom fields" do
109
+ describe "ticket custom fields" do
105
110
  let!(:ticket_field) { client.ticket_fields.create!(title: SecureRandom.hex(3), type: "text") }
106
111
 
107
112
  it "should be based on ticket_fields" do
@@ -5,10 +5,27 @@ describe "user_identities" do
5
5
  let(:user) { client.users.create(email: "zendesk2+#{Zendesk2.uuid}@example.org", name: Zendesk2.uuid, verified: true) }
6
6
 
7
7
  include_examples "zendesk resource", {
8
- :create_params => lambda { { value: "ey+#{Zendesk2.uuid}@example.org", type: "email", user_id: user.id } },
9
- :update_params => lambda { { verified: true } },
10
- :fetch_params => lambda { |uc| { "user_id" => uc.user_id, "id" => uc.id } },
11
- :collection => lambda { client.user_identities(user_id: user.id) },
12
- :paged => false,
13
- }
8
+ :create_params => lambda { { value: "ey+#{Zendesk2.uuid}@example.org", type: "email", user_id: user.id } },
9
+ :update_params => lambda { { verified: true } },
10
+ :fetch_params => lambda { |uc| { "user_id" => uc.user_id, "id" => uc.id } },
11
+ :collection => lambda { client.user_identities(user_id: user.id) },
12
+ :paged => false,
13
+ :search => false,
14
+ }
15
+
16
+ describe "#create_user_identity" do
17
+ let(:another_user) { client.users.create(email: "zendesk2+#{Zendesk2.uuid}@example.org", name: Zendesk2.uuid, verified: true) }
18
+
19
+ it "should prevent duplicate identities across users" do
20
+ expect {
21
+ client.create_user_identity("type" => "email", "value" => user.email, "user_id" => another_user.id)
22
+ }.to raise_exception(Zendesk2::Error, /is already being used by another user/)
23
+ end
24
+
25
+ it "should prevent duplicate identities on the same user" do
26
+ expect {
27
+ client.create_user_identity("type" => "email", "value" => user.email, "user_id" => user.id)
28
+ }.to raise_exception(Zendesk2::Error, /is already being used by another user/)
29
+ end
30
+ end
14
31
  end
data/spec/users_spec.rb CHANGED
@@ -34,7 +34,7 @@ describe "users" do
34
34
  expect(user.requested_tickets).to include ticket
35
35
  end
36
36
 
37
- it "should get ccd tickets" do
37
+ it "should get ccd tickets", mock_only: true do
38
38
  ticket = client.tickets.create(collaborators: [user], subject: Zendesk2.uuid, description: Zendesk2.uuid)
39
39
 
40
40
  expect(user.ccd_tickets).to include ticket
data/zendesk2.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.version = Zendesk2::VERSION
18
18
 
19
19
  gem.add_dependency "addressable", "~> 2.2"
20
- gem.add_dependency "cistern", "~> 0.6"
20
+ gem.add_dependency "cistern", "~> 0.9", ">= 0.9.2"
21
21
  gem.add_dependency "faraday", "~> 0.9"
22
22
  gem.add_dependency "faraday_middleware", "~> 0.9"
23
23
  gem.add_dependency "jwt", "~> 1.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zendesk2
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Lane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-26 00:00:00.000000000 Z
11
+ date: 2014-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -30,14 +30,20 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.6'
33
+ version: '0.9'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 0.9.2
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - "~>"
39
42
  - !ruby/object:Gem::Version
40
- version: '0.6'
43
+ version: '0.9'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.9.2
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: faraday
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -91,7 +97,6 @@ files:
91
97
  - ".travis.yml"
92
98
  - Gemfile
93
99
  - Guardfile
94
- - LICENSE
95
100
  - LICENSE.txt
96
101
  - README.md
97
102
  - Rakefile
@@ -99,42 +104,44 @@ files:
99
104
  - lib/zendesk2.rb
100
105
  - lib/zendesk2/attributes.rb
101
106
  - lib/zendesk2/client.rb
107
+ - lib/zendesk2/client/collections/categories.rb
108
+ - lib/zendesk2/client/collections/forums.rb
109
+ - lib/zendesk2/client/collections/groups.rb
110
+ - lib/zendesk2/client/collections/memberships.rb
111
+ - lib/zendesk2/client/collections/organizations.rb
112
+ - lib/zendesk2/client/collections/ticket_audits.rb
113
+ - lib/zendesk2/client/collections/ticket_comments.rb
114
+ - lib/zendesk2/client/collections/ticket_fields.rb
115
+ - lib/zendesk2/client/collections/ticket_metrics.rb
116
+ - lib/zendesk2/client/collections/tickets.rb
117
+ - lib/zendesk2/client/collections/topic_comments.rb
118
+ - lib/zendesk2/client/collections/topics.rb
119
+ - lib/zendesk2/client/collections/user_fields.rb
120
+ - lib/zendesk2/client/collections/user_identities.rb
121
+ - lib/zendesk2/client/collections/users.rb
122
+ - lib/zendesk2/client/mock.rb
102
123
  - lib/zendesk2/client/models/audit_event.rb
103
- - lib/zendesk2/client/models/categories.rb
104
124
  - lib/zendesk2/client/models/category.rb
105
125
  - lib/zendesk2/client/models/forum.rb
106
- - lib/zendesk2/client/models/forums.rb
107
126
  - lib/zendesk2/client/models/group.rb
108
- - lib/zendesk2/client/models/groups.rb
109
127
  - lib/zendesk2/client/models/membership.rb
110
- - lib/zendesk2/client/models/memberships.rb
111
128
  - lib/zendesk2/client/models/organization.rb
112
- - lib/zendesk2/client/models/organizations.rb
113
129
  - lib/zendesk2/client/models/ticket.rb
114
130
  - lib/zendesk2/client/models/ticket_audit.rb
115
- - lib/zendesk2/client/models/ticket_audits.rb
116
131
  - lib/zendesk2/client/models/ticket_change.rb
117
132
  - lib/zendesk2/client/models/ticket_comment.rb
118
133
  - lib/zendesk2/client/models/ticket_comment_privacy_change.rb
119
- - lib/zendesk2/client/models/ticket_comments.rb
120
134
  - lib/zendesk2/client/models/ticket_create.rb
121
135
  - lib/zendesk2/client/models/ticket_field.rb
122
- - lib/zendesk2/client/models/ticket_fields.rb
123
136
  - lib/zendesk2/client/models/ticket_metric.rb
124
- - lib/zendesk2/client/models/ticket_metrics.rb
125
137
  - lib/zendesk2/client/models/ticket_notification.rb
126
138
  - lib/zendesk2/client/models/ticket_voice_comment.rb
127
- - lib/zendesk2/client/models/tickets.rb
128
139
  - lib/zendesk2/client/models/topic.rb
129
140
  - lib/zendesk2/client/models/topic_comment.rb
130
- - lib/zendesk2/client/models/topic_comments.rb
131
- - lib/zendesk2/client/models/topics.rb
132
141
  - lib/zendesk2/client/models/user.rb
133
142
  - lib/zendesk2/client/models/user_field.rb
134
- - lib/zendesk2/client/models/user_fields.rb
135
- - lib/zendesk2/client/models/user_identities.rb
136
143
  - lib/zendesk2/client/models/user_identity.rb
137
- - lib/zendesk2/client/models/users.rb
144
+ - lib/zendesk2/client/real.rb
138
145
  - lib/zendesk2/client/requests/create_category.rb
139
146
  - lib/zendesk2/client/requests/create_forum.rb
140
147
  - lib/zendesk2/client/requests/create_group.rb
@@ -276,4 +283,3 @@ test_files:
276
283
  - spec/user_fields_spec.rb
277
284
  - spec/user_identities_spec.rb
278
285
  - spec/users_spec.rb
279
- has_rdoc:
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2012 Josh Lane
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.