adcloud 0.7.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.
- data/.document +5 -0
- data/.gitignore +69 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +20 -0
- data/README.md +221 -0
- data/RELEASE_NOTES.md +14 -0
- data/Rakefile +8 -0
- data/adcloud.gemspec +27 -0
- data/lib/adcloud.rb +62 -0
- data/lib/adcloud/advertisement.rb +28 -0
- data/lib/adcloud/api_error.rb +33 -0
- data/lib/adcloud/authentication.rb +24 -0
- data/lib/adcloud/campaign.rb +69 -0
- data/lib/adcloud/connection.rb +46 -0
- data/lib/adcloud/customer.rb +9 -0
- data/lib/adcloud/entity.rb +84 -0
- data/lib/adcloud/exception_raiser.rb +22 -0
- data/lib/adcloud/media_file.rb +10 -0
- data/lib/adcloud/product.rb +10 -0
- data/lib/adcloud/report.rb +28 -0
- data/lib/adcloud/report_entry.rb +48 -0
- data/lib/adcloud/response_error_handler.rb +23 -0
- data/lib/adcloud/topic.rb +19 -0
- data/lib/adcloud/topic_discount.rb +16 -0
- data/lib/adcloud/version.rb +3 -0
- data/lib/adcloud/webhook.rb +35 -0
- data/lib/adcloud/webhook_config.rb +18 -0
- data/lib/adcloud/webhook_event.rb +18 -0
- data/test/adcloud/advertisement_test.rb +11 -0
- data/test/adcloud/authentication_test.rb +58 -0
- data/test/adcloud/campaign_test.rb +54 -0
- data/test/adcloud/connection_test.rb +78 -0
- data/test/adcloud/customer_test.rb +6 -0
- data/test/adcloud/entity_test.rb +159 -0
- data/test/adcloud/media_file_test.rb +7 -0
- data/test/adcloud/product_test.rb +5 -0
- data/test/adcloud/report_test.rb +34 -0
- data/test/adcloud/topic_test.rb +14 -0
- data/test/adcloud/webhook_event_test.rb +19 -0
- data/test/adcloud/webhook_test.rb +62 -0
- data/test/adcloud_test.rb +47 -0
- data/test/test_helper.rb +23 -0
- metadata +251 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class Topic < Entity
|
3
|
+
attribute :id, Integer
|
4
|
+
attribute :start_prio, Integer
|
5
|
+
attribute :modified, DateTime
|
6
|
+
attribute :created, DateTime
|
7
|
+
attribute :discounts, Hash
|
8
|
+
attribute :names, Hash
|
9
|
+
|
10
|
+
def discounts=(data)
|
11
|
+
@discounts = data.reduce({}) do |hash, raw_discount|
|
12
|
+
discount = TopicDiscount.new(raw_discount)
|
13
|
+
hash[discount.country_code] = discount
|
14
|
+
hash
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class TopicDiscount
|
3
|
+
include Virtus
|
4
|
+
|
5
|
+
attribute :country_id, Integer
|
6
|
+
attribute :country_code, String
|
7
|
+
attribute :discount, Float
|
8
|
+
attribute :reach, Float
|
9
|
+
attribute :min_price_cpc, Float
|
10
|
+
attribute :price_cpc, Float
|
11
|
+
attribute :max_price_cpc, Float
|
12
|
+
attribute :min_price_cpm, Float
|
13
|
+
attribute :price_cpm, Float
|
14
|
+
attribute :max_price_cpm, Float
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class Webhook
|
3
|
+
ALL = [:on_topic_price_update, :on_campaign_update]
|
4
|
+
|
5
|
+
attr_accessor :events
|
6
|
+
|
7
|
+
# @param events [Array<Hash>, String] Events
|
8
|
+
def initialize(events)
|
9
|
+
self.events = events.kind_of?(Array) ? events : JSON.parse(events.to_s)
|
10
|
+
rescue
|
11
|
+
raise ArgumentError.new("Invalid webhook event data!")
|
12
|
+
end
|
13
|
+
|
14
|
+
def events=(events)
|
15
|
+
@events = [events].flatten.map { |event| Adcloud::WebhookEvent.new(event) }
|
16
|
+
if Adcloud.config.webhooks.filter_tests
|
17
|
+
@events.reject! { |event| event.test_data? }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def process!
|
22
|
+
self.events.each do |event|
|
23
|
+
proc = case event.type
|
24
|
+
when 'TopicPrice.update'
|
25
|
+
:on_topic_price_update
|
26
|
+
when 'Booking.update'
|
27
|
+
:on_campaign_update
|
28
|
+
else
|
29
|
+
:on_unknown_webhook
|
30
|
+
end
|
31
|
+
Adcloud.config.webhooks.send(proc).call(event)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class WebhookConfig < ActiveSupport::Configurable::Configuration
|
3
|
+
def initialize
|
4
|
+
Adcloud::Webhook::ALL.each do |hook|
|
5
|
+
self.send("#{hook}=", WebhookConfig.method(:unused_webhook))
|
6
|
+
end
|
7
|
+
self.on_unknown_webhook = WebhookConfig.method(:unknown_webhook)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.unused_webhook(event)
|
11
|
+
Adcloud.logger.warn { "Webhook behaviour missing for #{event.inspect}" }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.unknown_webhook(event)
|
15
|
+
Adcloud.logger.warn { "Unknown webhook event #{event.inspect}. Please contact gem maintainer!" }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Adcloud
|
2
|
+
class WebhookEvent
|
3
|
+
attr_reader :data, :meta
|
4
|
+
|
5
|
+
def initialize(raw_response)
|
6
|
+
@meta = raw_response.delete('_meta') || {}
|
7
|
+
@data = raw_response || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
@meta['event']
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_data?
|
15
|
+
@meta.has_key?('is_test_data') && @meta['is_test_data']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
describe Adcloud::Advertisement do
|
5
|
+
|
6
|
+
it 'should set the correct api endpoint' do
|
7
|
+
Adcloud::Advertisement.api_endpoint.must_equal 'advertisements'
|
8
|
+
end
|
9
|
+
|
10
|
+
# for more tests checkout entity tests
|
11
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Adcloud::Authentication do
|
4
|
+
|
5
|
+
describe "initialization" do
|
6
|
+
|
7
|
+
describe "with a valid hash" do
|
8
|
+
before do
|
9
|
+
@adcloud_auth = Adcloud::Authentication.new(:client_id => "0987654321", :client_secret => "1234567890")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have a client_id 0987654321" do
|
13
|
+
@adcloud_auth.client_id.must_equal "0987654321"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have a client_secret 1234567890" do
|
17
|
+
@adcloud_auth.client_secret.must_equal "1234567890"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "with an empty hash" do
|
23
|
+
|
24
|
+
before do
|
25
|
+
@adcloud_auth = Adcloud::Authentication.new({})
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should have a client_id and client_secret being nil" do
|
29
|
+
@adcloud_auth.client_secret.must_be_nil
|
30
|
+
@adcloud_auth.client_id.must_be_nil
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "is successful" do
|
38
|
+
|
39
|
+
it "should return a token" do
|
40
|
+
stub_request(:post, "https://api.adcloud.com:443/v2/oauth/access_token").with(:body => {"client_id"=>"0987654321", "client_secret"=>"1234567890", "grant_type"=>"none"}, :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Ruby'}).to_return(:status => 200, :body => {"_meta" => {"access_token" => "bab0e5c477f211c4612345678907498b6e55600"}, "access_token" => "bab0e5c477f211c4612345678907498b6e55600","scope" => ""}, :headers => {})
|
41
|
+
adcloud_auth = Adcloud::Authentication.new(:client_id => "0987654321", :client_secret => "1234567890")
|
42
|
+
adcloud_auth.authenticate!
|
43
|
+
adcloud_auth.token.must_equal "bab0e5c477f211c4612345678907498b6e55600"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# describe "is unsuccessful" do
|
49
|
+
|
50
|
+
# it "should raise an authentication error" do
|
51
|
+
# stub_request(:post, "https://api.adcloud.com:443/v2/oauth/access_token").with(:body => {"client_id"=>"0987654321", "client_secret"=>"1234567890", "grant_type"=>"none"}, :headers => {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Ruby'}).to_return(:status => 401, :body => {"status" => 401, "errors" => [{"message" => "Bad credentials"}]}, :headers => {})
|
52
|
+
# adcloud_auth = Adcloud::Authentication.new(:client_id => "0987654321", :client_secret => "1234567890")
|
53
|
+
# -> { adcloud_auth.authenticate! }.must_raise(Adcloud::AuthenticationError)
|
54
|
+
# end
|
55
|
+
|
56
|
+
# end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
describe Adcloud::Campaign do
|
5
|
+
subject { Adcloud::Campaign }
|
6
|
+
|
7
|
+
let(:campaign) { subject.new }
|
8
|
+
let(:connection) { stub() }
|
9
|
+
|
10
|
+
before do
|
11
|
+
subject.stubs(:connection => connection)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "validate" do
|
15
|
+
let(:request_attributes) { campaign.attributes.select { |k, v| ![:_meta, :id].include?(k) } }
|
16
|
+
|
17
|
+
it 'should validate against the api' do
|
18
|
+
connection.expects(:get).with('campaigns/validate', { campaign: request_attributes }).returns({'_meta' => { 'status' => 226, 'details' => {}}})
|
19
|
+
campaign.validate
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should populate the errors hash' do
|
23
|
+
response_data = {'_meta' => { 'status' => 226, 'details' => { 'company_id' => ['must be present'] } } }
|
24
|
+
connection.stubs(:get).returns(response_data)
|
25
|
+
campaign.validate
|
26
|
+
campaign.errors['company_id'].must_equal ['must be present']
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raises an exception when the response is not well formatted' do
|
30
|
+
connection.expects(:get).with('campaigns/validate', { campaign: request_attributes })
|
31
|
+
-> { campaign.validate }.must_raise(Adcloud::AdcloudSucks::InvalidApiResponse)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#valid?' do
|
36
|
+
it 'validates the model' do
|
37
|
+
campaign.expects(:validate)
|
38
|
+
campaign.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns true when object is valid' do
|
42
|
+
campaign.stubs(:validate)
|
43
|
+
campaign.errors = {}
|
44
|
+
campaign.valid?.must_equal true
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns false when the object is invalid' do
|
48
|
+
campaign.stubs(:validate)
|
49
|
+
campaign.errors = { company_id: ['must be present'] }
|
50
|
+
campaign.valid?.must_equal false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Adcloud::Connection do
|
4
|
+
|
5
|
+
subject { Adcloud::Connection.new }
|
6
|
+
|
7
|
+
let(:authentication) { stub(:authenticate! => true, :token => "0987654321") }
|
8
|
+
|
9
|
+
describe "url" do
|
10
|
+
|
11
|
+
it "should be https://api.adcloud.com:443/" do
|
12
|
+
subject.url.must_equal "https://api.adcloud.com:443/v2/"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "authentication token" do
|
18
|
+
|
19
|
+
it "should be 0987654321" do
|
20
|
+
subject.expects(:authentication).returns(authentication)
|
21
|
+
subject.authentication_token.must_equal "0987654321"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "authentication" do
|
27
|
+
it "should return an authentication object" do
|
28
|
+
Adcloud::Authentication.expects(:new).returns(authentication)
|
29
|
+
subject.authentication.must_equal authentication
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "an authenticated connection" do
|
34
|
+
let(:url) { "https://api.adcloud.com:443/v2/whatever" }
|
35
|
+
|
36
|
+
before do
|
37
|
+
subject.stubs(:authentication_token).returns("0987654321")
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "post" do
|
41
|
+
|
42
|
+
it "should fire a post request" do
|
43
|
+
stub_request(:post, url).to_return(:status => 200, :body => {"_meta" => {}, "hello" => "world"}, :headers => {})
|
44
|
+
subject.post('whatever').must_equal({"_meta" => {}, "hello" => "world" })
|
45
|
+
end
|
46
|
+
|
47
|
+
# it "should raise an InvalidRequest Exception" do
|
48
|
+
# stub_request(:post, url).to_return(:status => 500, :body => "{}", :headers => {})
|
49
|
+
# -> { subject.post('whatever') }.must_raise(Adcloud::InvalidRequest)
|
50
|
+
# end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "get" do
|
55
|
+
|
56
|
+
it "should fire a get request" do
|
57
|
+
stub_request(:get, url).to_return(:status => 200, :body => {"_meta" => {}, "hello" => "world"}, :headers => {})
|
58
|
+
subject.get('whatever').must_equal({"_meta" => {}, "hello" => "world" })
|
59
|
+
end
|
60
|
+
|
61
|
+
# it "should raise an InvalidFilter Exception" do
|
62
|
+
# stub_request(:get, url).to_return(:status => 400, :body => "{}", :headers => {})
|
63
|
+
# -> { subject.get('whatever') }.must_raise(Adcloud::InvalidFilter)
|
64
|
+
# end
|
65
|
+
|
66
|
+
# it "should raise a NotFound Exception" do
|
67
|
+
# stub_request(:get, url).to_return(:status => 404, :body => "{}", :headers => {})
|
68
|
+
# -> { subject.get('whatever') }.must_raise(Adcloud::NotFound)
|
69
|
+
# end
|
70
|
+
|
71
|
+
# it "should raise an InvalidRequest Exception" do
|
72
|
+
# stub_request(:get, url).to_return(:status => 500, :body => "{}", :headers => {})
|
73
|
+
# -> { subject.get('whatever') }.must_raise(Adcloud::InvalidRequest)
|
74
|
+
# end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
describe Adcloud::Entity do
|
5
|
+
|
6
|
+
class Car < Adcloud::Entity
|
7
|
+
#self.api_endpoint = 'cars'
|
8
|
+
attribute :id, Integer
|
9
|
+
attribute :name, String
|
10
|
+
end
|
11
|
+
|
12
|
+
subject { Car }
|
13
|
+
|
14
|
+
let(:connection) { stub }
|
15
|
+
|
16
|
+
describe ".connection" do
|
17
|
+
it "should return a connection object" do
|
18
|
+
Adcloud::Connection.expects(:new).returns(connection)
|
19
|
+
subject.connection.must_equal(connection)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
describe "#connection" do
|
25
|
+
it "should return the class level connection object" do
|
26
|
+
subject.expects(:connection).returns(connection)
|
27
|
+
subject.new.connection.must_equal(connection)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.api_endpoint' do
|
32
|
+
it 'its api endpoint is cars' do
|
33
|
+
subject.api_endpoint.must_equal 'cars'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "find" do
|
38
|
+
let(:response_data) { { 'id' => 42, 'name' => 'Porsche' } }
|
39
|
+
|
40
|
+
it "should return a car" do
|
41
|
+
subject.connection.expects(:get).with('cars/42').returns(response_data)
|
42
|
+
subject.find(42).must_be_instance_of(Car)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "all" do
|
47
|
+
let(:empty_response_data) { {"_meta"=>{"type"=>"Array<>", "page"=>1, "size"=>0, "per_page"=>50, "total_count"=>0, "total_pages"=>0, "sort"=>{}, "uuid"=>"aabe4f5f31691f049ea1942e6a6d1793"}, "items"=>[]} }
|
48
|
+
let(:response_data) { { "_meta" => { "type" => "Array<Car>", "page" => 1, "size" => 2, "per_page" => 50, "total_count" => 2, "total_pages" => 1, "sort" => {}, "uuid" => "aabe4f5f31691f049ea1942e6a6d1793" }, "items" => [{ "id" => 52654, "name" => 'Mercedes', "_meta" => { "type" => "Car" } }, { "id" => 52655, "name" => 'Audi', "_meta" => { "type" => "Car" } }]} }
|
49
|
+
|
50
|
+
it "should return an empty array if response is empty" do
|
51
|
+
subject.connection.expects(:get).with('cars', :filter => {}, :page => 1, :per_page => 50).returns(empty_response_data)
|
52
|
+
subject.all.must_be_instance_of(Array)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should return an array if response contains objects' do
|
56
|
+
subject.connection.expects(:get).with('cars', :filter => {}, :page => 1, :per_page => 50).returns(response_data)
|
57
|
+
subject.all.must_be_instance_of(Array)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should return an array of cars' do
|
61
|
+
subject.connection.expects(:get).with('cars', :filter => {}, :page => 1, :per_page => 50).returns(response_data)
|
62
|
+
subject.all.each { |item| item.must_be_instance_of(subject) }
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should return 2 cars' do
|
66
|
+
subject.connection.expects(:get).with('cars', :filter => {}, :page => 1, :per_page => 50).returns(response_data)
|
67
|
+
subject.all.size.must_equal 2
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should pass the filter param' do
|
71
|
+
subject.connection.expects(:get).with('cars', :filter => { :oliver => "ist chef" }, :page => 1, :per_page => 50).returns(response_data)
|
72
|
+
subject.all(:oliver => "ist chef")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#create' do
|
77
|
+
before { subject.stubs(:connection => connection) }
|
78
|
+
|
79
|
+
describe 'when submitting a valid object' do
|
80
|
+
let(:response) { { '_meta' => { 'status' => 200 }, 'id' => 1 } }
|
81
|
+
|
82
|
+
it 'sends a request to the api' do
|
83
|
+
car = subject.new
|
84
|
+
attributes = car.attributes
|
85
|
+
attributes.delete(:id)
|
86
|
+
attributes.delete(:_meta)
|
87
|
+
connection.expects(:post).with('cars', { 'car' => attributes }).returns(response)
|
88
|
+
car.create
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'sets the car id' do
|
92
|
+
connection.stubs(:post).returns(response)
|
93
|
+
car = subject.new
|
94
|
+
car.create
|
95
|
+
car.id.must_equal 1
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns true' do
|
99
|
+
connection.stubs(:post).returns(response)
|
100
|
+
subject.new.create.must_equal true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'when submitting an invalid car' do
|
105
|
+
let(:error) { Adcloud::BadRequestError.new(stub(:body => {'_meta' => { 'details' => { :name => ['cannot be empty'] }}})) }
|
106
|
+
before do
|
107
|
+
subject.stubs(:connection => connection)
|
108
|
+
connection.stubs(:post).raises(error)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns false' do
|
112
|
+
subject.new.create.must_equal false
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'sets the errors hash' do
|
116
|
+
car = subject.new
|
117
|
+
car.create
|
118
|
+
car.errors.must_equal({ :name => ['cannot be empty'] })
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '.create' do
|
124
|
+
it 'creates a new car' do
|
125
|
+
car = subject.new
|
126
|
+
subject.expects(:new).returns(car)
|
127
|
+
car.expects(:create)
|
128
|
+
subject.create.must_equal car
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "errors" do
|
133
|
+
it "should be empty" do
|
134
|
+
subject.new.errors.must_be_empty
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '.api_name' do
|
139
|
+
it 'returns the class name in lowercase' do
|
140
|
+
Car.api_name.must_equal 'car'
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'removes the namespace' do
|
144
|
+
module MyTest
|
145
|
+
class Truck < Adcloud::Entity
|
146
|
+
end
|
147
|
+
end
|
148
|
+
MyTest::Truck.api_name.must_equal 'truck'
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'transforms camel case to underscore' do
|
152
|
+
class AirPlane < Adcloud::Entity
|
153
|
+
end
|
154
|
+
AirPlane.api_name.must_equal 'air_plane'
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|