thumbtack 1.0.0 → 1.1.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/thumbtack.rb +2 -1
  3. data/lib/thumbtack/adapters/basic_adapter.rb +83 -0
  4. data/lib/thumbtack/client.rb +26 -19
  5. data/lib/thumbtack/hash_to_digest.rb +12 -7
  6. data/lib/thumbtack/note.rb +1 -1
  7. data/lib/thumbtack/note_summary.rb +1 -1
  8. data/lib/thumbtack/post.rb +1 -1
  9. data/lib/thumbtack/posts.rb +13 -5
  10. data/lib/thumbtack/suggestion.rb +18 -4
  11. data/lib/thumbtack/symbolize_keys.rb +22 -0
  12. data/lib/thumbtack/types/boolean.rb +1 -1
  13. data/lib/thumbtack/types/date.rb +1 -1
  14. data/lib/thumbtack/types/date_time.rb +1 -1
  15. data/lib/thumbtack/types/identity.rb +1 -1
  16. data/lib/thumbtack/types/integer.rb +1 -1
  17. data/lib/thumbtack/types/length_validation.rb +10 -8
  18. data/lib/thumbtack/types/md5.rb +16 -3
  19. data/lib/thumbtack/types/range_validation.rb +5 -3
  20. data/lib/thumbtack/types/tags.rb +16 -3
  21. data/lib/thumbtack/types/text.rb +1 -1
  22. data/lib/thumbtack/types/title.rb +1 -1
  23. data/lib/thumbtack/types/url.rb +1 -1
  24. data/lib/thumbtack/version.rb +1 -1
  25. data/test/test_helper.rb +2 -2
  26. data/test/thumbtack/client_test.rb +29 -1
  27. data/test/thumbtack/integration/basic_adapter_test.rb +24 -0
  28. data/test/thumbtack/note_summary_test.rb +1 -1
  29. data/test/thumbtack/notes_test.rb +34 -34
  30. data/test/thumbtack/post_test.rb +1 -1
  31. data/test/thumbtack/posts_test.rb +113 -95
  32. data/test/thumbtack/specification_test.rb +2 -1
  33. data/test/thumbtack/suggestion_test.rb +4 -4
  34. data/test/thumbtack/tags_test.rb +30 -24
  35. data/test/thumbtack/types/date_test.rb +2 -2
  36. data/test/thumbtack/types/date_time_test.rb +4 -3
  37. data/test/thumbtack/types/identity_test.rb +1 -1
  38. data/test/thumbtack/types/md5_test.rb +3 -3
  39. data/test/thumbtack/types/tags_test.rb +2 -2
  40. data/test/thumbtack/types/text_test.rb +2 -2
  41. data/test/thumbtack/user_test.rb +10 -6
  42. metadata +26 -11
  43. data/lib/thumbtack/types.rb +0 -9
  44. data/test/thumbtack/integration/client_test.rb +0 -29
@@ -1,7 +1,8 @@
1
+ # encoding: utf-8
2
+
1
3
  module Thumbtack
2
4
  module Types
3
5
  # Handles validation of values within a certain range
4
- # Pinboard
5
6
  #
6
7
  # @api private
7
8
  class RangeValidation
@@ -15,15 +16,16 @@ module Thumbtack
15
16
  # @param [Range] range
16
17
  # the range of valid values
17
18
  #
18
- # @return [undefined]
19
+ # @return [self]
19
20
  #
20
21
  # @raise [Types::ValidationError]
21
- # if the value is not between 0001-01-01 and 2100-01-01
22
+ # if the value is not within the range
22
23
  def self.validate(value, range)
23
24
  unless range.cover?(value)
24
25
  fail ValidationError,
25
26
  "#{value} must be between #{range.begin} and #{range.end}"
26
27
  end
28
+ self
27
29
  end
28
30
  end
29
31
  end
@@ -19,13 +19,13 @@ module Thumbtack
19
19
  # @param [String, Array<String>] value
20
20
  # a single tag or an array of many tags
21
21
  #
22
- # @return [undefined]
22
+ # @return [self]
23
23
  #
24
24
  # @raise [Types::ValidationError]
25
- # if any tag contains commas or are longer than 255 characters
25
+ # if any tags contain commas or are longer than 255 characters
26
26
  def self.validate(value)
27
27
  Array(value).each do |tag|
28
- unless tag.length < MAXIMUM_LENGTH && !tag.include?(INVALID_CHARACTER)
28
+ unless tag_valid?(tag)
29
29
  fail ValidationError,
30
30
  "#{tag} cannot contain commas or be longer than 255 characters"
31
31
  end
@@ -53,6 +53,19 @@ module Thumbtack
53
53
  def self.from_parameter(parameter)
54
54
  parameter.split(SEPARATOR)
55
55
  end
56
+
57
+ # If true, the tag is valid
58
+ #
59
+ # @param [String] tag
60
+ # a tag to validate
61
+ #
62
+ # @return [Boolean]
63
+ #
64
+ # @api private
65
+ def self.tag_valid?(tag)
66
+ tag.length < MAXIMUM_LENGTH && !tag.include?(INVALID_CHARACTER)
67
+ end
68
+ private_class_method :tag_valid?
56
69
  end
57
70
  end
58
71
  end
@@ -14,7 +14,7 @@ module Thumbtack
14
14
  # @param [String] value
15
15
  # text to validate
16
16
  #
17
- # @return [undefined]
17
+ # @return [self]
18
18
  #
19
19
  # @raise [Types::ValidationError]
20
20
  # if the value is longer than 65536 characters
@@ -14,7 +14,7 @@ module Thumbtack
14
14
  # @param [String] value
15
15
  # the title to validate
16
16
  #
17
- # @return [undefined]
17
+ # @return [self]
18
18
  #
19
19
  # @raise [Types::ValidationError]
20
20
  # if the value is longer than 255 characters
@@ -14,7 +14,7 @@ module Thumbtack
14
14
  # @param [String] value
15
15
  # the URL to validate
16
16
  #
17
- # @return [undefined]
17
+ # @return [self]
18
18
  #
19
19
  # @raise [Types::ValidationError]
20
20
  # if the URL's scheme isn't one of http, https, javascript, mailto, ftp,
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Thumbtack
4
4
  # Gem version
5
- VERSION = '1.0.0'.freeze
5
+ VERSION = '1.1.0'.freeze
6
6
  end
data/test/test_helper.rb CHANGED
@@ -11,9 +11,9 @@ def mock_client_get(url, params, response)
11
11
  client
12
12
  end
13
13
 
14
- def mock_client_action(url, params, response)
14
+ def mock_client_action(url, params)
15
15
  client = Minitest::Mock.new
16
- client.expect(:action, response, [url, params].compact)
16
+ client.expect(:action, { 'result' => 'done' }, [url, params].compact)
17
17
  client
18
18
  end
19
19
 
@@ -4,7 +4,8 @@ require 'test_helper'
4
4
 
5
5
  class ClientTest < Minitest::Test
6
6
  def setup
7
- @client = Client.new(nil, nil)
7
+ @adapter = Minitest::Mock.new
8
+ @client = Client.new(nil, nil, adapter: @adapter)
8
9
  end
9
10
 
10
11
  def test_posts
@@ -22,4 +23,31 @@ class ClientTest < Minitest::Test
22
23
  def test_notes
23
24
  assert_instance_of Notes, @client.notes
24
25
  end
26
+
27
+ def test_default_adapter
28
+ assert_kind_of Adapters::BasicAdapter, Client.new(nil, nil).adapter
29
+ end
30
+
31
+ def test_get
32
+ @adapter.expect(:get,
33
+ { 'posts' => [] },
34
+ ['/posts/recent', { tag: 'thumbtack' }])
35
+ @client.get('/posts/recent', tag: 'thumbtack')
36
+ @adapter.verify
37
+ end
38
+
39
+ def test_action
40
+ @adapter.expect(:get, { 'result_code' => 'done' }, ['/posts/update', {}])
41
+ @client.action('/posts/update', {})
42
+ @adapter.verify
43
+ end
44
+
45
+ def test_action_result_error
46
+ assert_raises ResultError do
47
+ @adapter.expect(:get,
48
+ { 'result_code' => 'notdone' },
49
+ ['/posts/update', {}])
50
+ @client.action('/posts/update', {})
51
+ end
52
+ end
25
53
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'test_helper'
4
+
5
+ module Integration
6
+ class BasicAdapterTest < Minitest::Test
7
+ def setup
8
+ path = File.expand_path('../../../auth_token.txt', __FILE__)
9
+ auth_token = File.open(path).read.strip
10
+ @username, @token = auth_token.split(':')
11
+ @adapter = Adapters::BasicAdapter.new(@username, @token)
12
+ end
13
+
14
+ def test_requests_return_parsed_json
15
+ response = @adapter.get('/user/api_token')
16
+ assert_equal({ 'result' => @token }, response)
17
+ end
18
+
19
+ def test_request_query_parameters
20
+ response = @adapter.get('/posts/recent', tag: 'thumbtack-test-xyz123')
21
+ assert_equal([], response['posts'])
22
+ end
23
+ end
24
+ end
@@ -10,7 +10,7 @@ class NoteSummaryTest < Minitest::Test
10
10
  'hash' => '0c9c30f60cadabd31415',
11
11
  'created_at' => '2010-02-11 03:46:56',
12
12
  'updated_at' => '2010-02-11 03:47:47',
13
- 'length' => 19,
13
+ 'length' => 19
14
14
  }
15
15
  note = NoteSummary.from_hash(hash)
16
16
 
@@ -4,29 +4,29 @@ require 'test_helper'
4
4
 
5
5
  class NotesTest < Minitest::Test
6
6
  def test_list
7
- client = mock_client_get('/notes/list',
8
- nil,
9
- {
10
- 'count' => 2,
11
- 'notes' => [
12
- {
13
- 'id' => 'cf73bfc02e00edaa1e2b',
14
- 'title' => 'Paul Graham on Hirin\' The Ladies',
15
- 'hash' => '0bbca3cba9246bbbda2c',
16
- 'created_at' => '2011-10-28 13:37:23',
17
- 'updated_at' => '2011-10-28 13:37:23',
18
- 'length' => '890',
19
- },
20
- {
21
- 'id' => '8e5d6964bb810e0050b0',
22
- 'title' => 'StarCraft beta coming this week!',
23
- 'hash' => '0c9c30f60cadabd31415',
24
- 'created_at' => '2010-02-11 03:46:56',
25
- 'updated_at' => '2010-02-11 03:47:47',
26
- 'length' => '153',
27
- },
28
- ]
29
- })
7
+ client = mock_client_get(
8
+ '/notes/list',
9
+ nil,
10
+ 'count' => 2,
11
+ 'notes' => [
12
+ {
13
+ 'id' => 'cf73bfc02e00edaa1e2b',
14
+ 'title' => 'Paul Graham on Hirin\' The Ladies',
15
+ 'hash' => '0bbca3cba9246bbbda2c',
16
+ 'created_at' => '2011-10-28 13:37:23',
17
+ 'updated_at' => '2011-10-28 13:37:23',
18
+ 'length' => '890'
19
+ },
20
+ {
21
+ 'id' => '8e5d6964bb810e0050b0',
22
+ 'title' => 'StarCraft beta coming this week!',
23
+ 'hash' => '0c9c30f60cadabd31415',
24
+ 'created_at' => '2010-02-11 03:46:56',
25
+ 'updated_at' => '2010-02-11 03:47:47',
26
+ 'length' => '153'
27
+ }
28
+ ]
29
+ )
30
30
  notes = Notes.new(client)
31
31
  result = notes.list
32
32
 
@@ -36,17 +36,17 @@ class NotesTest < Minitest::Test
36
36
  end
37
37
 
38
38
  def test_get
39
- client = mock_client_get('/notes/8e5d6964bb810e0050b0',
40
- nil,
41
- {
42
- 'id' => '8e5d6964bb810e0050b0',
43
- 'title' => 'StarCraft beta coming this week!',
44
- 'text' => 'This is a test note',
45
- 'hash' => '0c9c30f60cadabd31415',
46
- 'created_at' => '2010-02-11 03:46:56',
47
- 'updated_at' => '2010-02-11 03:47:47',
48
- 'length' => 19
49
- })
39
+ client = mock_client_get(
40
+ '/notes/8e5d6964bb810e0050b0',
41
+ nil,
42
+ 'id' => '8e5d6964bb810e0050b0',
43
+ 'title' => 'StarCraft beta coming this week!',
44
+ 'text' => 'This is a test note',
45
+ 'hash' => '0c9c30f60cadabd31415',
46
+ 'created_at' => '2010-02-11 03:46:56',
47
+ 'updated_at' => '2010-02-11 03:47:47',
48
+ 'length' => 19
49
+ )
50
50
  notes = Notes.new(client)
51
51
  result = notes.get('8e5d6964bb810e0050b0')
52
52
  assert_equal '8e5d6964bb810e0050b0', result.id
@@ -25,6 +25,6 @@ class PostTest < Minitest::Test
25
25
  assert_equal DateTime.new(2014, 6, 29, 16, 57, 45), post.time
26
26
  assert_equal true, post.shared
27
27
  assert_equal false, post.toread
28
- assert_equal ['test123', 'another'], post.tags
28
+ assert_equal %w(test123 another), post.tags
29
29
  end
30
30
  end
@@ -4,9 +4,11 @@ require 'test_helper'
4
4
 
5
5
  class PostsTest < Minitest::Test
6
6
  def test_update
7
- client = mock_client_get('/posts/update',
8
- nil,
9
- { 'update_time' => '2014-06-26T19:01:33Z' })
7
+ client = mock_client_get(
8
+ '/posts/update',
9
+ nil,
10
+ 'update_time' => '2014-06-26T19:01:33Z'
11
+ )
10
12
  posts = Posts.new(client)
11
13
 
12
14
  assert_equal DateTime.new(2014, 6, 26, 19, 1, 33), posts.update
@@ -14,10 +16,11 @@ class PostsTest < Minitest::Test
14
16
  end
15
17
 
16
18
  def test_add
17
- client = mock_client_action('/posts/add',
18
- { url: 'http://example.org',
19
- description: 'example.org' },
20
- { 'result_code' => 'done' })
19
+ client = mock_client_action(
20
+ '/posts/add',
21
+ url: 'http://example.org',
22
+ description: 'example.org'
23
+ )
21
24
  posts = Posts.new(client)
22
25
 
23
26
  assert_equal posts, posts.add('http://example.org', 'example.org')
@@ -25,22 +28,28 @@ class PostsTest < Minitest::Test
25
28
  end
26
29
 
27
30
  def test_add_with_tags
28
- client = mock_client_action('/posts/add',
29
- { url: 'http://example.org',
30
- description: 'example.org',
31
- tags: 'thumbtack test' },
32
- { 'result_code' => 'done' })
31
+ client = mock_client_action(
32
+ '/posts/add',
33
+ url: 'http://example.org',
34
+ description: 'example.org',
35
+ tags: 'thumbtack test'
36
+ )
33
37
  posts = Posts.new(client)
34
38
 
35
39
  assert_equal posts,
36
- posts.add('http://example.org', 'example.org', tags: %w(thumbtack test))
40
+ posts.add(
41
+ 'http://example.org',
42
+ 'example.org',
43
+ tags: %w(thumbtack test)
44
+ )
37
45
  client.verify
38
46
  end
39
47
 
40
48
  def test_delete
41
- client = mock_client_action('/posts/delete',
42
- { url: 'http://example.org' },
43
- { 'result_code' => 'done' })
49
+ client = mock_client_action(
50
+ '/posts/delete',
51
+ url: 'http://example.org'
52
+ )
44
53
  posts = Posts.new(client)
45
54
 
46
55
  assert_equal posts, posts.delete('http://example.org')
@@ -48,23 +57,23 @@ class PostsTest < Minitest::Test
48
57
  end
49
58
 
50
59
  def test_get
51
- client = mock_client_get('/posts/get',
52
- { url: 'http://example.org' },
53
- {
54
- 'date' => '2014-06-29T16:57:45Z',
55
- 'user' => 'nwjsmith',
56
- 'posts' => [{
57
- 'href' => 'http://example.org',
58
- 'description' => 'example.org',
59
- 'extended' => '',
60
- 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
61
- 'hash' => 'dab521de65f9250b4cca7383feef67dc',
62
- 'time' => '2014-06-29T16:57:45Z',
63
- 'shared' => 'yes',
64
- 'toread' => 'no',
65
- 'tags' => 'test123'
66
- }]
67
- })
60
+ client = mock_client_get(
61
+ '/posts/get',
62
+ { url: 'http://example.org' },
63
+ 'date' => '2014-06-29T16:57:45Z',
64
+ 'user' => 'nwjsmith',
65
+ 'posts' => [{
66
+ 'href' => 'http://example.org',
67
+ 'description' => 'example.org',
68
+ 'extended' => '',
69
+ 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
70
+ 'hash' => 'dab521de65f9250b4cca7383feef67dc',
71
+ 'time' => '2014-06-29T16:57:45Z',
72
+ 'shared' => 'yes',
73
+ 'toread' => 'no',
74
+ 'tags' => 'test123'
75
+ }]
76
+ )
68
77
  posts = Posts.new(client)
69
78
  response = posts.get(url: 'http://example.org')
70
79
 
@@ -74,23 +83,23 @@ class PostsTest < Minitest::Test
74
83
  end
75
84
 
76
85
  def test_recent
77
- client = mock_client_get('/posts/recent',
78
- {tag: 'webdev'},
79
- {
80
- 'date' => '2014-06-29T16:57:45Z',
81
- 'user' => 'nwjsmith',
82
- 'posts' => [{
83
- 'href' => 'http://example.org',
84
- 'description' => 'example.org',
85
- 'extended' => '',
86
- 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
87
- 'hash' => 'dab521de65f9250b4cca7383feef67dc',
88
- 'time' => '2014-06-29T16:57:45Z',
89
- 'shared' => 'yes',
90
- 'toread' => 'no',
91
- 'tags' => 'webdev'
92
- }]
93
- })
86
+ client = mock_client_get(
87
+ '/posts/recent',
88
+ { tag: 'webdev' },
89
+ 'date' => '2014-06-29T16:57:45Z',
90
+ 'user' => 'nwjsmith',
91
+ 'posts' => [{
92
+ 'href' => 'http://example.org',
93
+ 'description' => 'example.org',
94
+ 'extended' => '',
95
+ 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
96
+ 'hash' => 'dab521de65f9250b4cca7383feef67dc',
97
+ 'time' => '2014-06-29T16:57:45Z',
98
+ 'shared' => 'yes',
99
+ 'toread' => 'no',
100
+ 'tags' => 'webdev'
101
+ }]
102
+ )
94
103
  posts = Posts.new(client)
95
104
  response = posts.recent(tag: 'webdev')
96
105
 
@@ -100,19 +109,23 @@ class PostsTest < Minitest::Test
100
109
  end
101
110
 
102
111
  def test_all
103
- client = mock_client_get('/posts/all',
104
- {tag: 'webdev'},
105
- [{
106
- 'href' => 'http://example.org',
107
- 'description' => 'example.org',
108
- 'extended' => '',
109
- 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
110
- 'hash' => 'dab521de65f9250b4cca7383feef67dc',
111
- 'time' => '2014-06-29T16:57:45Z',
112
- 'shared' => 'yes',
113
- 'toread' => 'no',
114
- 'tags' => 'webdev'
115
- }])
112
+ client = mock_client_get(
113
+ '/posts/all',
114
+ { tag: 'webdev' },
115
+ [
116
+ {
117
+ 'href' => 'http://example.org',
118
+ 'description' => 'example.org',
119
+ 'extended' => '',
120
+ 'meta' => '46ca40b9b92ee0ea1284785a5d2a9b38',
121
+ 'hash' => 'dab521de65f9250b4cca7383feef67dc',
122
+ 'time' => '2014-06-29T16:57:45Z',
123
+ 'shared' => 'yes',
124
+ 'toread' => 'no',
125
+ 'tags' => 'webdev'
126
+ }
127
+ ]
128
+ )
116
129
  posts = Posts.new(client)
117
130
  response = posts.all(tag: 'webdev')
118
131
 
@@ -122,12 +135,14 @@ class PostsTest < Minitest::Test
122
135
  end
123
136
 
124
137
  def test_suggest
125
- client = mock_client_get('/posts/suggest',
126
- {url: 'http://blog.com'},
127
- [
128
- { 'popular' => ['blog', 'blogs', 'people'] },
129
- { 'recommended' => ['blog', 'writing', 'weblog'] }
130
- ])
138
+ client = mock_client_get(
139
+ '/posts/suggest',
140
+ { url: 'http://blog.com' },
141
+ [
142
+ { 'popular' => %w(blog blogs people) },
143
+ { 'recommended' => %w(blog writing weblog) }
144
+ ]
145
+ )
131
146
  posts = Posts.new(client)
132
147
  response = posts.suggest('http://blog.com')
133
148
 
@@ -137,35 +152,38 @@ class PostsTest < Minitest::Test
137
152
  end
138
153
 
139
154
  def test_dates
140
- client = mock_client_get('/posts/dates',
141
- {tag: 'argentina'},
142
- {
143
- 'user' => 'user',
144
- 'tag' => 'argentina',
145
- 'dates' => {
146
- '2010-11-29' => '5',
147
- '2010-11-28' => '15',
148
- '2010-11-26' => '2',
149
- '2010-11-25' => '2',
150
- '2010-11-23' => '7',
151
- '2010-11-22' => '20',
152
- '2010-11-21' => '16',
153
- '2010-11-19' => '4'
154
- }
155
- })
155
+ client = mock_client_get(
156
+ '/posts/dates',
157
+ { tag: 'argentina' },
158
+ 'user' => 'user',
159
+ 'tag' => 'argentina',
160
+ 'dates' => {
161
+ '2010-11-29' => '5',
162
+ '2010-11-28' => '15',
163
+ '2010-11-26' => '2',
164
+ '2010-11-25' => '2',
165
+ '2010-11-23' => '7',
166
+ '2010-11-22' => '20',
167
+ '2010-11-21' => '16',
168
+ '2010-11-19' => '4'
169
+ }
170
+ )
156
171
  posts = Posts.new(client)
157
172
  response = posts.dates(tag: 'argentina')
158
173
 
159
- assert_equal({
160
- Date.new(2010, 11, 29) => 5,
161
- Date.new(2010, 11, 28) => 15,
162
- Date.new(2010, 11, 26) => 2,
163
- Date.new(2010, 11, 25) => 2,
164
- Date.new(2010, 11, 23) => 7,
165
- Date.new(2010, 11, 22) => 20,
166
- Date.new(2010, 11, 21) => 16,
167
- Date.new(2010, 11, 19) => 4
168
- }, response)
174
+ assert_equal(
175
+ {
176
+ Date.new(2010, 11, 29) => 5,
177
+ Date.new(2010, 11, 28) => 15,
178
+ Date.new(2010, 11, 26) => 2,
179
+ Date.new(2010, 11, 25) => 2,
180
+ Date.new(2010, 11, 23) => 7,
181
+ Date.new(2010, 11, 22) => 20,
182
+ Date.new(2010, 11, 21) => 16,
183
+ Date.new(2010, 11, 19) => 4
184
+ },
185
+ response
186
+ )
169
187
  client.verify
170
188
  end
171
189
  end