zendesk2 1.0.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.