battlenet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in battlenet.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2011 Brandon Tilley
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ __ ) | | | |
2
+ __ \ _` | __| __| | _ \ __ \ _ \ __|
3
+ | | ( | | | | __/ | | __/ |
4
+ ____/ \__,_| \__| \__| _| \___| _| _| \___| \__|
5
+
6
+ A Ruby library for the Battle.net Community Platform API
7
+
8
+ Battlenet is a Ruby library that exposes Blizzard's [Community Platform API](http://us.battle.net/wow/en/forum/topic/2369881371).
9
+
10
+ Installing
11
+ ==========
12
+
13
+ Battlenet is available as a Ruby gem. Install it via
14
+
15
+ [sudo] gem install battlenet
16
+
17
+ Use
18
+ ===
19
+
20
+ In general, the API is split into several sub-modules, each corresponding to an entity in Blizzard's API. For example, methods for using the Realm Status API are located in the module `Battlenet::API::Realm`. Methods on the model allow you to fetch certain information about the given entity. Arguments passed to the methods allow you to specify query string parameters. As an example, here are some sample API calls and the URL they translate into.
21
+
22
+ Battlenet::API::Realm.status
23
+ # => "http://us.battle.net/api/wow/realm/status"
24
+ Battlenet::API::Realm.status :realm => "Nazjatar"
25
+ # => "http://us.battle.net/api/wow/realm/status?realm=Nazjatar"
26
+ Battlenet::API::Realm.status :realm => ["Nazjatar", "Shadowsong"]
27
+ # => "http://us.battle.net/api/wow/realm/status?realm=Nazjatar&realm=Shadowsong"
28
+
29
+ Calls to the methods return an array of Hashes, and each hash contains the data for the queried resources. The attributes can be accessed via Strings or Symbols (you can set this to Strings only via `Battlenet::API.set_option`, see below).
30
+
31
+ **Note**: This is all subject to change depending on how Blizzard architects the rest of their API.
32
+
33
+ Configuration
34
+ =============
35
+
36
+ You may pass multiple options to Battlenet to change its behavior.
37
+
38
+ Region
39
+ ------
40
+
41
+ By default, the region is set to `:us`, which corresponds to the US Battle.net API. You may set this to any symbol to force the library to use that region's API.
42
+
43
+ Battlenet::API.set_option(:region, :eu)
44
+
45
+ HTTP Adapter
46
+ ------------
47
+
48
+ Battlenet supports multiple adapters for fetching API data over the Internet. By default, it uses Ruby's built-in `Net::HTTP` library. If you wish to use a different adapter, specify it like this:
49
+
50
+ Battlenet::API.set_option(:http_adapter, :typhoeus)
51
+
52
+ The following adapters are currently supported (more may be added later):
53
+
54
+ * `:net_http` - Ruby's `Net::HTTP` library
55
+ * `:typhoeus` - [Typhoeus](https://github.com/dbalatero/typhoeus)
56
+
57
+ Note that the adapter must be set before any API calls are made.
58
+
59
+ Currently Supported APIs
60
+ ========================
61
+
62
+ Currently, the following APIs are supported. More will be added as Blizzard expands their API library.
63
+
64
+ * [Realm Status API](http://us.battle.net/wow/en/forum/topic/2369741469)
65
+
66
+ Realm Status API
67
+ ----------------
68
+
69
+ api = Battlenet::API::Realm
70
+
71
+ # Getting data for all realms
72
+ all_realms = api.status
73
+ # Getting data for specific realms
74
+ realms = api.status :realm => ["Nazjatar", "Shadowsong"]
75
+ # Getting data for one realm
76
+ realm = api.status :realm => "Nazjatar"
77
+
78
+ # Getting data about a realm
79
+ realms.first["population"]
80
+ # => "low"
81
+ realms.first["queue"]
82
+ # => false
83
+
84
+ Contributing
85
+ ============
86
+
87
+ If you would like to contribute to the project, please feel free to do so. Just fork the project, commit your changes (preferably to a new branch), and then send me a pull request via GitHub. Be sure to add tests for your feature or fix.
88
+
89
+ Please do not change the contents of the `VERSION` file, or if you do, do so in a separate commit so that I can cherry-pick around it.
90
+
91
+ License
92
+ =======
93
+
94
+ Battlenet is released under the MIT license.
95
+
96
+ Copyright (c) 2011 Brandon Tilley
97
+
98
+ Permission is hereby granted, free of charge, to any person
99
+ obtaining a copy of this software and associated documentation
100
+ files (the "Software"), to deal in the Software without
101
+ restriction, including without limitation the rights to use,
102
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
103
+ copies of the Software, and to permit persons to whom the
104
+ Software is furnished to do so, subject to the following
105
+ conditions:
106
+
107
+ The above copyright notice and this permission notice shall be
108
+ included in all copies or substantial portions of the Software.
109
+
110
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
111
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
112
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
113
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
114
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
115
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
116
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
117
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = ['--color', '-f progress', '-r ./spec/spec_helper.rb']
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ t.fail_on_error = false
9
+ end
data/battlenet.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'battlenet/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "battlenet"
7
+ s.version = Battlenet::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Brandon Tilley"]
10
+ s.email = ["brandon@brandontilley.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Easily consume Blizzard's Community Platform API.}
13
+ s.description = %q{Easily consume Blizzard's Community Platform API.}
14
+
15
+ if s.respond_to?(:add_development_dependency)
16
+ s.add_development_dependency "rspec"
17
+ end
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
data/lib/battlenet.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'battlenet/api'
2
+ require 'battlenet/api/realm'
@@ -0,0 +1,11 @@
1
+ module Battlenet
2
+ module Adapter
3
+ class NotImplementedException < Exception; end
4
+
5
+ class AbstractAdapter
6
+ def get(url)
7
+ raise NotImplementedException.new("Please implement #get in your adapter")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require 'battlenet/adapter/abstract_adapter'
2
+ require 'net/http'
3
+
4
+ module Battlenet
5
+ module Adapter
6
+ class NetHTTP < AbstractAdapter
7
+ def get(url)
8
+ uri = URI.parse url
9
+ response = Net::HTTP.get_response uri
10
+ [response.code.to_i, response.body]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require 'battlenet/adapter/abstract_adapter'
2
+ require 'typhoeus'
3
+
4
+ module Battlenet
5
+ module Adapter
6
+ class Typhoeus < AbstractAdapter
7
+ def get(url)
8
+ response = ::Typhoeus::Request.get url
9
+ [response.code.to_i, response.body]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ module Battlenet
2
+ module Adapter; end
3
+
4
+ module AdapterManager
5
+ class InvalidAdapter < Exception; end
6
+
7
+ extend self
8
+
9
+ @adapters = {
10
+ :net_http => "NetHTTP",
11
+ :typhoeus => "Typhoeus"
12
+ }
13
+
14
+ def adapters
15
+ @adapters
16
+ end
17
+
18
+ def fetch(adapter_name)
19
+ unless adapters.include? adapter_name
20
+ raise InvalidAdapter.new("#{adapter_name.to_s} is not a valid adapter")
21
+ end
22
+
23
+ adapter_class = adapters[adapter_name]
24
+ adapter = load_adapter adapter_name, adapter_class
25
+ end
26
+
27
+ private
28
+
29
+ def load_adapter(adapter_name, klass_name)
30
+ begin
31
+ klass = Battlenet::Adapter.const_get("#{klass_name}", false)
32
+ rescue NameError
33
+ begin
34
+ adapter_file = "battlenet/adapter/#{adapter_name.to_s}"
35
+ require adapter_file
36
+ klass = Battlenet::Adapter.const_get("#{klass_name}", false)
37
+ rescue LoadError
38
+ raise InvalidAdapter.new("adapter #{klass_name} does not exist, and file #{adapter_file} does not exist")
39
+ rescue NameError
40
+ raise InvalidAdapter.new("expected #{adapter_file} to define Battlenet::Adapter::#{klass_name}")
41
+ end
42
+ end
43
+
44
+ return klass.new
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+ require 'battlenet/adapter_manager'
2
+ require 'cgi'
3
+ require 'json'
4
+
5
+ module Battlenet
6
+ module API
7
+ class APIError < Exception
8
+ attr_reader :code, :body
9
+ def initialize(code, body)
10
+ @code = code
11
+ @body = body
12
+ end
13
+
14
+ def to_s
15
+ "Status: #{code} Body: #{body}"
16
+ end
17
+ end
18
+
19
+ extend self
20
+
21
+ @config = {
22
+ :indifferent_hashes => true,
23
+ :http_adapter => :net_http,
24
+ :region => :us
25
+ }
26
+
27
+ def set_option(setting, value)
28
+ @config[setting] = value
29
+ end
30
+
31
+ def get_option(setting, default = nil)
32
+ @config[setting] || default
33
+ end
34
+
35
+ def adapter
36
+ @adapter ||= Battlenet::AdapterManager.fetch get_option(:http_adapter)
37
+ end
38
+
39
+ def base_url
40
+ region = get_option(:region, :us).to_s
41
+ "http://#{region}.battle.net/api/wow"
42
+ end
43
+
44
+ def make_api_call(path, query = {})
45
+ query_string = query.empty? ? '' : make_query_string(query)
46
+ url = base_url
47
+ url << (path.start_with?('/') ? '' : '/')
48
+ url << path
49
+ url << query_string
50
+ code, body = get url
51
+ raise APIError.new(code, body) unless code == 200
52
+ JSON::parse body
53
+ end
54
+
55
+ def get(url)
56
+ adapter.get(url)
57
+ end
58
+
59
+ def make_query_string(query)
60
+ query_string = "?"
61
+ query.each do |key, value|
62
+ case value
63
+ when String
64
+ query_string << "#{key}=#{CGI.escape value}&"
65
+ when Array
66
+ value.each { |v| query_string << "#{key}=#{CGI.escape v}&" }
67
+ end
68
+ end
69
+
70
+ query_string.chomp("&").chomp("?")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,14 @@
1
+ module Battlenet
2
+ module API
3
+ module Realm
4
+ extend self
5
+
6
+ @api = Battlenet::API
7
+
8
+ def status(options = {})
9
+ data = @api.make_api_call 'realm/status', options
10
+ data["realms"]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Battlenet
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,15 @@
1
+ require 'battlenet/adapter/abstract_adapter'
2
+
3
+ describe Battlenet::Adapter::AbstractAdapter do
4
+ it "has a method 'get'" do
5
+ subject.should respond_to :get
6
+ end
7
+
8
+ context "#get" do
9
+ it "is abstract" do
10
+ lambda {
11
+ subject.get('fake_url')
12
+ }.should raise_error Battlenet::Adapter::NotImplementedException
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ require 'battlenet/adapter_manager'
2
+
3
+ describe Battlenet::AdapterManager do
4
+ it "has a list of adapters" do
5
+ subject.adapters.should be_a Hash
6
+ end
7
+
8
+ context "#fetch" do
9
+ it "raises an exception if an invalid adapter is passed" do
10
+ lambda {
11
+ subject.fetch(:some_fake_adapter)
12
+ }.should raise_error Battlenet::AdapterManager::InvalidAdapter
13
+ end
14
+
15
+ it "requires a library file based on the adapter chosen if the class doesn't exist" do
16
+ subject.should_receive(:require).once.with('battlenet/adapter/net_http')
17
+ begin
18
+ subject.fetch(:net_http)
19
+ rescue Battlenet::AdapterManager::InvalidAdapter
20
+ # expected
21
+ end
22
+ end
23
+
24
+ it "raises an error if the adapter class doesn't exist and the library can't be loaded" do
25
+ old_adapters = subject.adapters
26
+ subject.instance_variable_set(:@adapters, {:adapter => "SuperAdapter"})
27
+
28
+ lambda {
29
+ subject.fetch(:adapter)
30
+ }.should raise_error Battlenet::AdapterManager::InvalidAdapter, /does not exist/
31
+
32
+ subject.instance_variable_set(:@adapters, old_adapters)
33
+ end
34
+
35
+ it "raises an error if the adapter library is loaded but doesn't define the adapter class" do
36
+ subject.should_receive(:require).once.with('battlenet/adapter/net_http').and_return(nil)
37
+ lambda {
38
+ subject.fetch(:net_http)
39
+ }.should raise_error Battlenet::AdapterManager::InvalidAdapter, /expected (.*) to define/
40
+ end
41
+
42
+ it "returns an instance of the adapter" do
43
+ adapter = subject.fetch(:net_http)
44
+ adapter.should be_a Battlenet::Adapter::NetHTTP
45
+ end
46
+ end
47
+ end
data/spec/api_spec.rb ADDED
@@ -0,0 +1,115 @@
1
+ require 'battlenet/api'
2
+
3
+ describe Battlenet::API do
4
+ context "configuration management" do
5
+ it "defauls to nil when getting an unset option" do
6
+ subject.get_option(:a_fake_setting_i_made_up).should be_nil
7
+ end
8
+
9
+ it "returns a passed value if an option is not set" do
10
+ subject.get_option(:a_fake_setting_i_made_up, "value").should == "value"
11
+ end
12
+
13
+ it "allows setting and retrieving a value" do
14
+ subject.set_option(:a_fake_setting_i_made_up, "my_value")
15
+ subject.get_option(:a_fake_setting_i_made_up).should == "my_value"
16
+ end
17
+ end
18
+
19
+ context "default configuration" do
20
+ it "defaults to the US region" do
21
+ subject.get_option(:region).should == :us
22
+ end
23
+
24
+ it "defaults to indifferent hashes" do
25
+ subject.get_option(:indifferent_hashes).should == true
26
+ end
27
+
28
+ it "defaults to the Net::HTTP library" do
29
+ subject.get_option(:http_adapter).should == :net_http
30
+ end
31
+ end
32
+
33
+ context "HTTP adapter" do
34
+ it "retrieval is via the Adapter module" do
35
+ adapter = subject.get_option(:http_adapter)
36
+ Battlenet::AdapterManager.should_receive(:fetch).with(adapter)
37
+ subject.send(:adapter)
38
+ end
39
+
40
+ it "caches its adapter" do
41
+ mock_adapter = mock("adapter")
42
+ Battlenet::AdapterManager.should_receive(:fetch).with(any_args()).and_return(mock_adapter)
43
+ adapter = subject.send(:adapter)
44
+ subject.set_option(:http_adapter, :fake_adapter)
45
+ subject.send(:adapter).should == mock_adapter
46
+ end
47
+ end
48
+
49
+ it "returns a base URL dependant on the region" do
50
+ old_region = subject.get_option(:region)
51
+ subject.set_option(:region, :eu)
52
+ subject.send(:base_url).should == "http://eu.battle.net/api/wow"
53
+ subject.set_option(:region, old_region)
54
+ end
55
+
56
+ context "#make_query_string" do
57
+ it "produces an empty string with an empty hash" do
58
+ str = subject.send(:make_query_string, {})
59
+ str.should be_empty
60
+ end
61
+
62
+ it "starts with a question mark" do
63
+ str = subject.send(:make_query_string, {:first => "value1", :second => "value2"})
64
+ str.start_with?("?").should == true
65
+ end
66
+
67
+ it "parses a basic hash" do
68
+ str = subject.send(:make_query_string, {:first => "value1", :second => "value2"})
69
+ str.should == "?first=value1&second=value2"
70
+ end
71
+
72
+ it "parses a hash with array values" do
73
+ str = subject.send(:make_query_string, {:first => ["value1", "value2"], :second => "value3"})
74
+ str.should == "?first=value1&first=value2&second=value3"
75
+ end
76
+
77
+ it "escapes values" do
78
+ str = subject.send(:make_query_string, {:first => ["value1", "va&lue2"], :second => "val&ue3"})
79
+ str.should == "?first=value1&first=va%26lue2&second=val%26ue3"
80
+ end
81
+ end
82
+
83
+ context "#make_api_call" do
84
+ it "makes a get call with the correct URL" do
85
+ subject.should_receive(:get).once.with("http://us.battle.net/api/wow/my/path").and_return([200, '{"testing": "value"}'])
86
+ subject.send(:make_api_call, '/my/path')
87
+ end
88
+
89
+ it "adds a slash to the path if necessary" do
90
+ subject.should_receive(:get).once.with("http://us.battle.net/api/wow/my/path").and_return([200, '{"testing": "value"}'])
91
+ subject.send(:make_api_call, 'my/path')
92
+ end
93
+
94
+ it "converts the second parameter into a query string" do
95
+ options = {:first => ["value1", "va&lue2"], :second => "val&ue3"}
96
+ query_string = subject.send(:make_query_string, options)
97
+ subject.should_receive(:get).once.with("http://us.battle.net/api/wow/my/path#{query_string}").and_return([200, '{"testing": "value"}'])
98
+ subject.send(:make_api_call, 'my/path', options)
99
+ end
100
+
101
+ it "raises an exception on an error" do
102
+ subject.adapter.should_receive(:get).and_return([404, "not found"])
103
+ lambda {
104
+ subject.make_api_call('my/path')
105
+ }.should raise_error Battlenet::API::APIError, /404 (.*) not found/
106
+ end
107
+
108
+ it "returns the data as JSON" do
109
+ subject.adapter.should_receive(:get).and_return([200, '{"testing": "value"}'])
110
+ data = subject.make_api_call('my/path')
111
+ data.should be_a Hash
112
+ data["testing"].should == "value"
113
+ end
114
+ end
115
+ end
File without changes
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: battlenet
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Brandon Tilley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-15 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ description: Easily consume Blizzard's Community Platform API.
28
+ email:
29
+ - brandon@brandontilley.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - battlenet.gemspec
43
+ - lib/battlenet.rb
44
+ - lib/battlenet/adapter/abstract_adapter.rb
45
+ - lib/battlenet/adapter/net_http.rb
46
+ - lib/battlenet/adapter/typhoeus.rb
47
+ - lib/battlenet/adapter_manager.rb
48
+ - lib/battlenet/api.rb
49
+ - lib/battlenet/api/realm.rb
50
+ - lib/battlenet/version.rb
51
+ - spec/abstract_adapter_spec.rb
52
+ - spec/adapter_manager_spec.rb
53
+ - spec/api_spec.rb
54
+ - spec/spec_helper.rb
55
+ has_rdoc: true
56
+ homepage: ""
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.5.0
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Easily consume Blizzard's Community Platform API.
83
+ test_files:
84
+ - spec/abstract_adapter_spec.rb
85
+ - spec/adapter_manager_spec.rb
86
+ - spec/api_spec.rb
87
+ - spec/spec_helper.rb