typekit-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +350 -0
  8. data/Rakefile +7 -0
  9. data/bin/typekit +146 -0
  10. data/lib/typekit.rb +13 -0
  11. data/lib/typekit/client.rb +38 -0
  12. data/lib/typekit/configuration.rb +16 -0
  13. data/lib/typekit/configuration/base.rb +21 -0
  14. data/lib/typekit/configuration/default.rb +34 -0
  15. data/lib/typekit/connection.rb +10 -0
  16. data/lib/typekit/connection/adaptor.rb +13 -0
  17. data/lib/typekit/connection/adaptor/standard.rb +32 -0
  18. data/lib/typekit/connection/dispatcher.rb +17 -0
  19. data/lib/typekit/connection/request.rb +23 -0
  20. data/lib/typekit/connection/response.rb +20 -0
  21. data/lib/typekit/core.rb +16 -0
  22. data/lib/typekit/helper.rb +43 -0
  23. data/lib/typekit/parser.rb +14 -0
  24. data/lib/typekit/parser/json.rb +13 -0
  25. data/lib/typekit/parser/yaml.rb +13 -0
  26. data/lib/typekit/processor.rb +38 -0
  27. data/lib/typekit/routing.rb +9 -0
  28. data/lib/typekit/routing/map.rb +43 -0
  29. data/lib/typekit/routing/node.rb +5 -0
  30. data/lib/typekit/routing/node/base.rb +45 -0
  31. data/lib/typekit/routing/node/collection.rb +34 -0
  32. data/lib/typekit/routing/node/operation.rb +32 -0
  33. data/lib/typekit/routing/node/root.rb +8 -0
  34. data/lib/typekit/routing/node/scope.rb +19 -0
  35. data/lib/typekit/routing/proxy.rb +17 -0
  36. data/lib/typekit/version.rb +3 -0
  37. data/spec/cassettes/index_kits_ok.yml +16 -0
  38. data/spec/cassettes/index_kits_unauthorized.yml +16 -0
  39. data/spec/cassettes/show_families_calluna_found.yml +16 -0
  40. data/spec/spec_helper.rb +20 -0
  41. data/spec/support/rest_helper.rb +22 -0
  42. data/spec/typekit/client_spec.rb +36 -0
  43. data/spec/typekit/configuration_spec.rb +34 -0
  44. data/spec/typekit/connection/adaptor_spec.rb +24 -0
  45. data/spec/typekit/connection/dispatcher_spec.rb +36 -0
  46. data/spec/typekit/connection/request_spec.rb +13 -0
  47. data/spec/typekit/helper_spec.rb +93 -0
  48. data/spec/typekit/processor_spec.rb +34 -0
  49. data/spec/typekit/routing/map_spec.rb +106 -0
  50. data/spec/typekit/routing/node_spec.rb +40 -0
  51. data/typekit-client.gemspec +33 -0
  52. metadata +208 -0
@@ -0,0 +1,5 @@
1
+ require_relative 'node/base'
2
+ require_relative 'node/root'
3
+ require_relative 'node/scope'
4
+ require_relative 'node/collection'
5
+ require_relative 'node/operation'
@@ -0,0 +1,45 @@
1
+ module Typekit
2
+ module Routing
3
+ module Node
4
+ class Base
5
+ def append(child)
6
+ children << child
7
+ end
8
+
9
+ def assemble(request, path)
10
+ process(request, path)
11
+ return authorize(request) if path.empty?
12
+ lookup!(path.first).assemble(request, path)
13
+ end
14
+
15
+ def match(name)
16
+ end
17
+
18
+ def process(request, path)
19
+ end
20
+
21
+ def permitted?(request)
22
+ end
23
+
24
+ protected
25
+
26
+ def children
27
+ @children ||= []
28
+ end
29
+
30
+ def lookup(name)
31
+ children.find { |c| c.match(name) }
32
+ end
33
+
34
+ def lookup!(name)
35
+ lookup(name) or raise Error, 'Not found'
36
+ end
37
+
38
+ def authorize(request)
39
+ raise Error, 'Not permitted' unless permitted?(request)
40
+ request
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ module Typekit
2
+ module Routing
3
+ module Node
4
+ class Collection < Base
5
+ def initialize(name, only: nil)
6
+ @name = name
7
+ @actions = only && Array(only) || Typekit.actions
8
+ unless (@actions - Typekit.actions).empty?
9
+ raise Error, 'Not supported'
10
+ end
11
+ end
12
+
13
+ def match(name)
14
+ @name == name
15
+ end
16
+
17
+ def process(request, path)
18
+ request << path.shift # @name
19
+ return request if path.empty?
20
+ request << path.shift # id
21
+ end
22
+
23
+ def permitted?(request)
24
+ return false unless @actions.include?(request.action)
25
+
26
+ id_present = request.path.last != @name
27
+ member_action = Helper.member_action?(request.action)
28
+
29
+ id_present == member_action
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module Typekit
2
+ module Routing
3
+ module Node
4
+ class Operation < Base
5
+ def initialize(name, action:, on:, **options)
6
+ # TODO: how about on == :collection?
7
+ unless Typekit.actions.include?(action) && on == :member
8
+ raise Error, 'Not supported'
9
+ end
10
+ @name = name
11
+ @action = action
12
+ end
13
+
14
+ def match(name)
15
+ if @name.is_a?(String) && @name =~ /^:/
16
+ true
17
+ else
18
+ @name == name
19
+ end
20
+ end
21
+
22
+ def process(request, path)
23
+ request << path.shift # @name
24
+ end
25
+
26
+ def permitted?(request)
27
+ @action == request.action
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,8 @@
1
+ module Typekit
2
+ module Routing
3
+ module Node
4
+ class Root < Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module Typekit
2
+ module Routing
3
+ module Node
4
+ class Scope < Base
5
+ def initialize(path)
6
+ @path = Array(path)
7
+ end
8
+
9
+ def match(name)
10
+ !lookup(name).nil?
11
+ end
12
+
13
+ def process(request, path)
14
+ @path.each { |chunk| request << chunk }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Typekit
2
+ module Routing
3
+ class Proxy
4
+ def initialize(owner, **options)
5
+ @owner = owner
6
+ @options = options
7
+ end
8
+
9
+ def method_missing(name, *arguments, **options, &block)
10
+ name = :"define_#{ name }"
11
+ return super unless @owner.respond_to?(name)
12
+ # NOTE: https://bugs.ruby-lang.org/issues/9776
13
+ @owner.send(name, *arguments, **options, **@options, &block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Typekit
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,16 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://typekit.com/api/v1/json/kits
6
+ response:
7
+ status:
8
+ code: 200
9
+ message: OK
10
+ headers:
11
+ Status:
12
+ - 200 OK
13
+ body:
14
+ encoding: UTF-8
15
+ string: '{"kits":[{"id":"bas4cfe","link":"/api/v1/json/kits/bas4cfe"},{"id":"sfh6bkj","link":"/api/v1/json/kits/sfh6bkj"},{"id":"kof8zcn","link":"/api/v1/json/kits/kof8zcn"},{"id":"muq6mxx","link":"/api/v1/json/kits/muq6mxx"}]}'
16
+ recorded_at: Fri, 23 May 2014 00:00:00 GMT
@@ -0,0 +1,16 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://typekit.com/api/v1/json/kits
6
+ response:
7
+ status:
8
+ code: 401
9
+ message: Unauthorized
10
+ headers:
11
+ Status:
12
+ - 401 Unauthorized
13
+ body:
14
+ encoding: UTF-8
15
+ string: '{"errors":["Not authorized"]}'
16
+ recorded_at: Fri, 23 May 2014 00:00:00 GMT
@@ -0,0 +1,16 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://typekit.com/api/v1/json/families/calluna
6
+ response:
7
+ status:
8
+ code: 302
9
+ message: Found
10
+ headers:
11
+ Status:
12
+ - 302 Found
13
+ body:
14
+ encoding: UTF-8
15
+ string: '{"family":{"id":"vybr","link":"/api/v1/json/families/vybr"}}'
16
+ recorded_at: Tue, 27 May 2014 00:00:00 GMT
@@ -0,0 +1,20 @@
1
+ require_relative 'support/rest_helper'
2
+ require 'webmock/rspec'
3
+ require 'vcr'
4
+
5
+ RSpec.configure do |config|
6
+ config.treat_symbols_as_metadata_keys_with_true_values = true
7
+ config.run_all_when_everything_filtered = true
8
+ config.filter_run :focus
9
+ config.order = 'random'
10
+ end
11
+
12
+ VCR.configure do |config|
13
+ config.cassette_library_dir = 'spec/cassettes'
14
+ config.hook_into :webmock
15
+ config.configure_rspec_metadata!
16
+ config.default_cassette_options = {
17
+ allow_unused_http_interactions: false
18
+ }
19
+ config.allow_http_connections_when_no_cassette = false
20
+ end
@@ -0,0 +1,22 @@
1
+ module RESTHelper
2
+ def restful_actions
3
+ # TODO: keep in sync with Typekit.actions?
4
+ [ :index, :show, :create, :update, :delete ]
5
+ end
6
+
7
+ def restful_collection_actions
8
+ # TODO: keep in sync with Typekit.collection_actions?
9
+ [ :index, :create ]
10
+ end
11
+
12
+ def restful_member_actions
13
+ # TODO: keep in sync with Typekit.member_actions?
14
+ [ :show, :update, :delete ]
15
+ end
16
+
17
+ def rest_http_dictionary
18
+ # TODO: keep in sync with Typekit.action_dictionary?
19
+ { :index => :get, :show => :get, :create => :post,
20
+ :update => :post, :delete => :delete }
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Client do
5
+ let(:token) { 'arbitrary' }
6
+ let(:subject) { Typekit::Client.new(token: token) }
7
+
8
+ describe '#index' do
9
+ context 'when successful' do
10
+ options = { vcr: { cassette_name: 'index_kits_ok' } }
11
+
12
+ it 'returns hashes', options do
13
+ result = subject.index(:kits)
14
+ expect(result).to be_kind_of(Hash)
15
+ end
16
+ end
17
+
18
+ context 'when unauthorized' do
19
+ options = { vcr: { cassette_name: 'index_kits_unauthorized' } }
20
+
21
+ it 'raises exceptions', options do
22
+ expect { subject.index(:kits) }.to \
23
+ raise_error(Typekit::Error, /(authentication|authorized)/i)
24
+ end
25
+ end
26
+
27
+ context 'when found' do
28
+ options = { vcr: { cassette_name: 'show_families_calluna_found' } }
29
+
30
+ it 'returns the Response as is', options do
31
+ expect { subject.show(:families, 'calluna') }.not_to \
32
+ raise_error
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Configuration do
5
+ let(:subject_module) { Typekit::Configuration }
6
+
7
+ def build(name, **options)
8
+ subject_module.build(name, **options)
9
+ end
10
+
11
+ describe '.build' do
12
+ it 'requies an API token to be given' do
13
+ expect { build(:default) }.to \
14
+ raise_error(subject_module::Error, /Not enough arguments/i)
15
+ end
16
+
17
+ it 'returns registeries of settings' do
18
+ expect(build(:default, token: 'nekot')).to \
19
+ be_kind_of(subject_module::Base)
20
+ end
21
+
22
+ it 'raises exceptions when encouters unknown registeries' do
23
+ expect { build(:awesome, token: 'nekot') }.to \
24
+ raise_error(subject_module::Error, /Unknown configuration/i)
25
+ end
26
+ end
27
+
28
+ describe 'Base#map' do
29
+ it 'returns a Map' do
30
+ expect(build(:default, token: 'nekot').map).to \
31
+ be_kind_of(Typekit::Routing::Map)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Connection::Adaptor do
5
+ let(:address) { 'https://typekit.com/api/v1/json/kits' }
6
+
7
+ [ 'Standard' ].each do |adaptor|
8
+ subject { Typekit::Connection::Adaptor.const_get(adaptor).new }
9
+
10
+ describe "#{ adaptor }.process" do
11
+ it 'returns the code, headers, and body of the response' do
12
+ stub_http_request(:get, address).to_return(
13
+ code: '200', body: 'Hej!', headers: { 'a' => 'b' } )
14
+ response = subject.process(:get, address)
15
+ expect(response).to eq([ '200', { 'a' => [ 'b' ] }, 'Hej!' ])
16
+ end
17
+
18
+ it 'raises exceptions when encounters unknown methods' do
19
+ expect { subject.process(:smile, address) }.to \
20
+ raise_error(Typekit::Connection::Error, /Invalid method/i)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Connection::Dispatcher do
5
+ extend RESTHelper
6
+
7
+ let(:token) { 'arbitrary' }
8
+ let(:address) { 'https://typekit.com/api/v1/json/kits' }
9
+ let(:subject) { Typekit::Connection::Dispatcher.new(token: token) }
10
+
11
+ def create_request(action)
12
+ double('Request', action: action, address: address, parameters: {})
13
+ end
14
+
15
+ describe '#deliver' do
16
+ restful_actions.each do |action|
17
+ method = rest_http_dictionary[action]
18
+
19
+ context "when sending #{ action } Requests" do
20
+ it 'sets the token header' do
21
+ stub = stub_http_request(method, address)
22
+ response = subject.deliver(create_request(action))
23
+ expect(stub).to have_requested(method, address).
24
+ with(:headers => { 'X-Typekit-Token' => token })
25
+ end
26
+
27
+ it 'returns Responses' do
28
+ stub_http_request(method, address).
29
+ to_return(code: '200', body: 'Hej!')
30
+ response = subject.deliver(create_request(action))
31
+ expect([ response.code, response.content ]).to eq([ 200, 'Hej!' ])
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Connection::Request do
5
+ subject { Typekit::Connection::Request.new(action: :show) }
6
+
7
+ describe '#address' do
8
+ it 'assembles the address' do
9
+ [ :kits, 'xxx', :families, 'yyy' ].each { |chunk| subject << chunk }
10
+ expect(subject.address).to eq('kits/xxx/families/yyy')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Helper do
5
+ extend RESTHelper
6
+
7
+ let(:subject_module) { Typekit::Helper }
8
+
9
+ describe '.member_action?' do
10
+ restful_member_actions.each do |action|
11
+ it "returns true for the #{ action } member action" do
12
+ expect(subject_module.member_action?(action)).to be_true
13
+ end
14
+ end
15
+
16
+ restful_collection_actions.each do |action|
17
+ it "returns false for the #{ action } collection action" do
18
+ expect(subject_module.member_action?(action)).to be_false
19
+ end
20
+ end
21
+
22
+ it 'raises exceptions when encounters unknown actions' do
23
+ expect { subject_module.member_action?(:rock) }.to \
24
+ raise_error(Typekit::Helper::Error, /Unknown action/i)
25
+ end
26
+ end
27
+
28
+ describe '.translate_action' do
29
+ rest_http_dictionary.each do |action, method|
30
+ it "returns the #{ method } verb for the #{ action } action" do
31
+ expect(subject_module.translate_action(action)).to eq(method)
32
+ end
33
+ end
34
+
35
+ it 'raises exceptions when encounters unknown actions' do
36
+ expect { subject_module.translate_action(:rock) }.to \
37
+ raise_error(Typekit::Helper::Error, /Unknown action/i)
38
+ end
39
+ end
40
+
41
+ describe '.build_query' do
42
+ it 'handels ordinary parameters' do
43
+ queries = [
44
+ 'name=Megakit&domains=localhost',
45
+ 'domains=localhost&name=Megakit'
46
+ ]
47
+ query = subject_module.build_query(
48
+ name: 'Megakit', domains: 'localhost')
49
+ expect(queries).to include(query)
50
+ end
51
+
52
+ it 'handles parameters whose values are ordinary lists' do
53
+ query = subject_module.build_query(
54
+ domains: [ 'example.com', 'example.net' ])
55
+ expect(query).to eq('domains[]=example.com&domains[]=example.net')
56
+ end
57
+
58
+ it 'handles parameters whose values are object lists' do
59
+ queries = [
60
+ 'families[0][id]=gkmg&families[1][id]=asdf',
61
+ 'families[1][id]=asdf&families[0][id]=gkmg'
62
+ ]
63
+ query = subject_module.build_query(
64
+ families: { 0 => { id: 'gkmg' }, 1 => { id: 'asdf' } })
65
+ expect(queries).to include(query)
66
+ end
67
+
68
+ it 'converts integers to decimal strings' do
69
+ query = subject_module.build_query(page: 42)
70
+ expect(query).to eq('page=42')
71
+ end
72
+
73
+ it 'converts integers in object lists to decimal strings' do
74
+ queries = [
75
+ 'primes[0][value]=2&primes[1][value]=3',
76
+ 'primes[1][value]=3&primes[0][value]=2'
77
+ ]
78
+ query = subject_module.build_query(
79
+ primes: { 0 => { value: 2 }, 1 => { value: 3 } })
80
+ expect(queries).to include(query)
81
+ end
82
+
83
+ it 'converts the logical true to the string true' do
84
+ query = subject_module.build_query(badge: true)
85
+ expect(query).to eq('badge=true')
86
+ end
87
+
88
+ it 'converts the logical false to the string false' do
89
+ query = subject_module.build_query(badge: false)
90
+ expect(query).to eq('badge=false')
91
+ end
92
+ end
93
+ end