pisoni 1.23.1

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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile +16 -0
  4. data/LICENSE +202 -0
  5. data/Makefile +73 -0
  6. data/NOTICE +15 -0
  7. data/README.md +47 -0
  8. data/Rakefile +27 -0
  9. data/lib/3scale/core/alert_limit.rb +32 -0
  10. data/lib/3scale/core/api_client/attributes.rb +56 -0
  11. data/lib/3scale/core/api_client/collection.rb +19 -0
  12. data/lib/3scale/core/api_client/operations.rb +233 -0
  13. data/lib/3scale/core/api_client/resource.rb +52 -0
  14. data/lib/3scale/core/api_client/support.rb +55 -0
  15. data/lib/3scale/core/api_client.rb +5 -0
  16. data/lib/3scale/core/application.rb +67 -0
  17. data/lib/3scale/core/application_key.rb +34 -0
  18. data/lib/3scale/core/application_referrer_filter.rb +33 -0
  19. data/lib/3scale/core/errors.rb +130 -0
  20. data/lib/3scale/core/event.rb +26 -0
  21. data/lib/3scale/core/logger.rb +12 -0
  22. data/lib/3scale/core/metric.rb +32 -0
  23. data/lib/3scale/core/service.rb +82 -0
  24. data/lib/3scale/core/service_error.rb +34 -0
  25. data/lib/3scale/core/service_token.rb +39 -0
  26. data/lib/3scale/core/transaction.rb +26 -0
  27. data/lib/3scale/core/usage_limit.rb +40 -0
  28. data/lib/3scale/core/user.rb +47 -0
  29. data/lib/3scale/core/utilization.rb +28 -0
  30. data/lib/3scale/core/version.rb +5 -0
  31. data/lib/3scale/core.rb +63 -0
  32. data/lib/3scale_core.rb +1 -0
  33. data/lib/pisoni.rb +5 -0
  34. data/pisoni.gemspec +43 -0
  35. data/spec/alert_limit_spec.rb +72 -0
  36. data/spec/application_key_spec.rb +97 -0
  37. data/spec/application_referrer_filter_spec.rb +57 -0
  38. data/spec/application_spec.rb +188 -0
  39. data/spec/event_spec.rb +82 -0
  40. data/spec/metric_spec.rb +115 -0
  41. data/spec/private_endpoints/event.rb +9 -0
  42. data/spec/private_endpoints/service_error.rb +10 -0
  43. data/spec/private_endpoints/transaction.rb +16 -0
  44. data/spec/service_error_spec.rb +128 -0
  45. data/spec/service_spec.rb +159 -0
  46. data/spec/service_token_spec.rb +121 -0
  47. data/spec/spec_helper.rb +15 -0
  48. data/spec/transaction_spec.rb +71 -0
  49. data/spec/usagelimit_spec.rb +52 -0
  50. data/spec/user_spec.rb +164 -0
  51. data/spec/utilization_spec.rb +113 -0
  52. metadata +182 -0
@@ -0,0 +1,82 @@
1
+ module ThreeScale
2
+ module Core
3
+ class Service < APIClient::Resource
4
+ attributes :provider_key, :id, :backend_version, :referrer_filters_required,
5
+ :user_registration_required, :default_user_plan_id,
6
+ :default_user_plan_name, :default_service
7
+
8
+ class << self
9
+ def load_by_id(service_id)
10
+ api_read({}, uri: service_uri(service_id), rprefix: :service)
11
+ end
12
+
13
+ def delete_by_id!(service_id)
14
+ api_delete({}, uri: service_uri(service_id)) do |result|
15
+ if result[:response].status == 400
16
+ raise ServiceIsDefaultService, service_id
17
+ end
18
+ end
19
+ end
20
+
21
+ def save!(attributes)
22
+ id = attributes.fetch(:id)
23
+ api_update(attributes, uri: service_uri(id)) do |result|
24
+ if result[:response].status == 400
25
+ raise ServiceRequiresDefaultUserPlan
26
+ end
27
+ true
28
+ end
29
+ end
30
+
31
+ def change_provider_key!(old_key, new_key)
32
+ ret = api_do_put({ new_key: new_key },
33
+ uri: "#{default_uri}change_provider_key/#{old_key}",
34
+ prefix: '') do |result|
35
+ if result[:response].status == 400
36
+ exception = provider_key_exception(
37
+ result[:response_json][:error], old_key, new_key)
38
+ raise exception if exception
39
+ end
40
+ true
41
+ end
42
+ ret[:ok]
43
+ end
44
+
45
+ def make_default(service_id)
46
+ save! id: service_id, default_service: true
47
+ end
48
+
49
+ private
50
+
51
+ def service_uri(id)
52
+ "#{default_uri}#{id}"
53
+ end
54
+
55
+ def provider_key_exception(error, old_key, new_key)
56
+ case error
57
+ when /does not exist/
58
+ ProviderKeyNotFound.new old_key
59
+ when /already exists/
60
+ ProviderKeyExists.new new_key
61
+ when /are not valid/
62
+ InvalidProviderKeys.new
63
+ else
64
+ nil
65
+ end
66
+ end
67
+ end
68
+
69
+ def referrer_filters_required?
70
+ @referrer_filters_required
71
+ end
72
+
73
+ def user_registration_required?
74
+ @user_registration_required
75
+ end
76
+
77
+ def save!
78
+ self.class.save! attributes
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,34 @@
1
+ module ThreeScale
2
+ module Core
3
+ class ServiceError < APIClient::Resource
4
+ attributes :code, :message, :timestamp
5
+
6
+ default_uri '/internal/services/'
7
+
8
+ def self.service_errors_uri(service_id)
9
+ "#{default_uri}#{service_id}/errors/"
10
+ end
11
+ private_class_method :service_errors_uri
12
+
13
+ def self.load_all(service_id, options={})
14
+ result = api_do_get(options,
15
+ { uri: service_errors_uri(service_id),
16
+ prefix: '',
17
+ rprefix: :errors }) do |result|
18
+ if result[:response].status == 400 &&
19
+ result[:response_json][:error] == 'per_page needs to be > 0'
20
+ raise InvalidPerPage.new
21
+ end
22
+ true
23
+ end
24
+
25
+ APIClient::Collection.new(result[:attributes].map { |attrs| new attrs },
26
+ result[:response_json][:count])
27
+ end
28
+
29
+ def self.delete_all(service_id)
30
+ api_delete({}, uri: service_errors_uri(service_id))
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ module ThreeScale
2
+ module Core
3
+ class ServiceToken < APIClient::Resource
4
+
5
+ default_uri '/internal/service_tokens/'
6
+
7
+ class << self
8
+
9
+ def save!(attributes)
10
+ api_do_post(attributes, prefix: :service_tokens) do |result|
11
+
12
+ status = result[:response].status
13
+
14
+ if status == 400
15
+ raise ServiceTokenMissingParameter, result[:response_json][:error]
16
+ end
17
+
18
+ if status == 422
19
+ case result[:response_json][:error]
20
+ when /Service ID/
21
+ raise ServiceTokenRequiresServiceId
22
+ when /Service token/
23
+ raise ServiceTokenRequiresToken
24
+ end
25
+ end
26
+
27
+ true
28
+ end
29
+ end
30
+
31
+ def delete(attributes)
32
+ result = api_do_delete(attributes, uri: default_uri, prefix: :service_tokens)
33
+
34
+ result[:response_json][:count]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module ThreeScale
2
+ module Core
3
+ class Transaction < APIClient::Resource
4
+ attributes :application_id, :usage, :timestamp
5
+
6
+ default_uri '/internal/services/'
7
+
8
+ def self.transactions_uri(service_id)
9
+ "#{default_uri}#{service_id}/transactions/"
10
+ end
11
+ private_class_method :transactions_uri
12
+
13
+ def self.load_all(service_id)
14
+ result = api_do_get({}, { uri: transactions_uri(service_id),
15
+ rprefix: :transactions }) do |result|
16
+ return nil if result[:response].status == 404
17
+ true
18
+ end
19
+
20
+ return nil if result.nil?
21
+
22
+ APIClient::Collection.new(result[:attributes].map { |attrs| new attrs })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ module ThreeScale
2
+ module Core
3
+ class UsageLimit < APIClient::Resource
4
+ PERIODS = [:eternity, :year, :month, :week, :day, :hour, :minute].freeze
5
+
6
+ attributes :service_id, :plan_id, :metric_id, :value, *PERIODS
7
+
8
+ default_uri '/internal/services/'
9
+
10
+ def self.base_uri(service_id, plan_id, metric_id, period)
11
+ "#{default_uri}#{service_id}/plans/#{plan_id}/usagelimits/#{metric_id}/#{period}"
12
+ end
13
+ private_class_method :base_uri
14
+
15
+ def self.load_value(service_id, plan_id, metric_id, period)
16
+ obj = api_read({}, uri: base_uri(service_id, plan_id, metric_id, period))
17
+ obj and obj.public_send(period).to_i
18
+ end
19
+
20
+ def self.save(attributes)
21
+ # save currently DOES NOT support multiple periods at the same time,
22
+ # since it would mean multiple API calls per call to this method.
23
+ periodlst = PERIODS & attributes.keys
24
+ raise UsageLimitInvalidPeriods.new(periodlst) unless periodlst.one?
25
+
26
+ service_id, plan_id, metric_id = attributes.fetch(:service_id), attributes.fetch(:plan_id), attributes.fetch(:metric_id)
27
+ period = periodlst.shift
28
+ value = attributes[period]
29
+ fixed_fields = { service_id: service_id, plan_id: plan_id, metric_id: metric_id }.freeze
30
+
31
+ api_update(fixed_fields.merge({period => value}), uri: base_uri(service_id, plan_id, metric_id, period))
32
+ end
33
+
34
+ def self.delete(service_id, plan_id, metric_id, period)
35
+ api_delete({}, uri: base_uri(service_id, plan_id, metric_id, period))
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,47 @@
1
+ module ThreeScale
2
+ module Core
3
+ class User < APIClient::Resource
4
+ attributes :service_id, :username, :state, :plan_id, :plan_name
5
+
6
+ default_uri '/internal/services/'
7
+
8
+ def self.base_uri(service_id, username)
9
+ "#{default_uri}#{service_id}/users/#{username}"
10
+ end
11
+ private_class_method :base_uri
12
+
13
+ def self.check_params(service_id, username)
14
+ raise UserRequiresUsername if username.nil? || username == ''.freeze
15
+ raise UserRequiresServiceId if service_id.nil? || service_id == ''.freeze
16
+ end
17
+ private_class_method :check_params
18
+
19
+ def self.load(service_id, username)
20
+ check_params service_id, username
21
+ api_read({}, uri: base_uri(service_id, username))
22
+ end
23
+
24
+ def self.save!(attributes)
25
+ service_id, username = attributes[:service_id], attributes[:username]
26
+ check_params service_id, username
27
+ api_update(attributes,
28
+ uri: base_uri(service_id, username)) do |result|
29
+ if result[:response].status == 400
30
+ if result[:response_json][:error] =~ /requires a valid service/
31
+ raise UserRequiresValidServiceId.new(service_id)
32
+ elsif result[:response_json][:error] =~ /requires a defined plan/
33
+ raise UserRequiresDefinedPlan.new(attributes[:plan_id],
34
+ attributes[:plan_name])
35
+ end
36
+ end
37
+ true
38
+ end
39
+ end
40
+
41
+ def self.delete!(service_id, username)
42
+ check_params service_id, username
43
+ api_delete({}, uri: base_uri(service_id, username))
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,28 @@
1
+ module ThreeScale
2
+ module Core
3
+ class Utilization < APIClient::Resource
4
+ attributes :period, :metric_name, :max_value, :current_value
5
+
6
+ default_uri '/internal/services/'
7
+
8
+ def self.utilization_uri(service_id, app_id)
9
+ "#{default_uri}#{service_id}/applications/#{app_id}/utilization/"
10
+ end
11
+ private_class_method :utilization_uri
12
+
13
+ def self.load(service_id, app_id)
14
+ result = api_do_get({},
15
+ uri: utilization_uri(service_id, app_id),
16
+ rprefix: :utilization) do |result|
17
+ return nil if result[:response].status == 404
18
+ true
19
+ end
20
+
21
+ return nil if result.nil?
22
+
23
+ usage_reports = result[:attributes].map { |attrs| new attrs }
24
+ APIClient::Collection.new(usage_reports)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module ThreeScale
2
+ module Core
3
+ VERSION = '1.23.1'
4
+ end
5
+ end
@@ -0,0 +1,63 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'faraday'
4
+
5
+ require '3scale/core/version'
6
+ require '3scale/core/logger'
7
+
8
+ require '3scale/core/api_client'
9
+ require '3scale/core/application'
10
+ require '3scale/core/metric'
11
+ require '3scale/core/service'
12
+ require '3scale/core/usage_limit'
13
+ require '3scale/core/user'
14
+ require '3scale/core/event'
15
+ require '3scale/core/alert_limit'
16
+ require '3scale/core/errors'
17
+ require '3scale/core/application_key'
18
+ require '3scale/core/application_referrer_filter'
19
+ require '3scale/core/service_error'
20
+ require '3scale/core/transaction'
21
+ require '3scale/core/utilization'
22
+ require '3scale/core/service_token'
23
+
24
+ module ThreeScale
25
+ module Core
26
+ extend self
27
+
28
+ attr_accessor :username, :password
29
+ attr_writer :url
30
+
31
+ def faraday
32
+ return @faraday if @faraday
33
+
34
+ url = self.url
35
+ @faraday = Faraday.new(url: url) do |f|
36
+ f.adapter :net_http_persistent
37
+ end
38
+ @faraday.headers = {
39
+ 'User-Agent' => "pisoni v#{ThreeScale::Core::VERSION}",
40
+ 'Accept' => 'application/json',
41
+ 'Content-Type' => 'application/json'
42
+ }
43
+
44
+ if @username.nil? && @password.nil?
45
+ # even though the url may contain the user info, turns out Faraday is
46
+ # not really picking it up, so must fill it in if present in the URL and
47
+ # no previous setting was done (ie. assigning username or password).
48
+ uri = URI.parse url
49
+ @username = uri.user
50
+ @password = uri.password
51
+ end
52
+
53
+ @faraday.basic_auth(@username, @password) if @username || @password
54
+ @faraday
55
+ end
56
+
57
+ def url
58
+ ENV['THREESCALE_CORE_INTERNAL_API'] || @url ||
59
+ raise(UnknownAPIEndpoint)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1 @@
1
+ require '3scale/core'
data/lib/pisoni.rb ADDED
@@ -0,0 +1,5 @@
1
+ require '3scale/core'
2
+
3
+ module Pisoni
4
+ include ThreeScale::Core
5
+ end
data/pisoni.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require '3scale/core/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'pisoni'
8
+ s.version = ThreeScale::Core::VERSION
9
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
10
+
11
+ s.platform = Gem::Platform::RUBY
12
+
13
+ s.authors = ['Alejandro Martinez Ruiz']
14
+ s.email = %w[alex@3scale.net]
15
+
16
+ s.homepage = 'https://github.com/3scale/pisoni'
17
+ s.summary = 'Client for the Apisonator internal API for model data'
18
+ s.description = 'Client for the Apisonator internal API for model data.'
19
+ s.license = 'Apache-2.0'
20
+
21
+ s.add_dependency 'faraday', '~> 0.13.1'
22
+ s.add_dependency 'json', '~> 1.8.1'
23
+ s.add_dependency 'injectedlogger', '~> 0.0.13'
24
+ s.add_dependency 'net-http-persistent'
25
+
26
+ s.add_development_dependency 'rake'
27
+
28
+ s.files = `git ls-files`.split($/).reject do |f| [
29
+ %r{^\.[^\/]},
30
+ %r{^script/},
31
+ %r{^docker/},
32
+ %r{^mk/},
33
+ ].any? { |r| r.match f }
34
+ end
35
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
36
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
37
+
38
+ s.require_paths = ["lib"]
39
+
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+
42
+ s.required_ruby_version = '>= 2.2.4'
43
+ end
@@ -0,0 +1,72 @@
1
+ require_relative './spec_helper'
2
+ module ThreeScale
3
+ module Core
4
+ describe AlertLimit do
5
+ describe '.load_all' do
6
+ describe 'when there are alert limits' do
7
+ let(:service_id) { 100 }
8
+ let(:values) { [50, 100] }
9
+ before do
10
+ values.map { |value| AlertLimit.delete(service_id, value) }
11
+ values.map { |value| AlertLimit.save(service_id, value) }
12
+ end
13
+
14
+ it 'returns a list of alert limits' do
15
+ alert_limits = AlertLimit.load_all(service_id)
16
+
17
+ alert_limits.size.must_equal 2
18
+ alert_limits.map(&:value).must_equal values
19
+ end
20
+ end
21
+
22
+ describe 'when there are no alert limits' do
23
+ let(:service_id) { 200 }
24
+
25
+ it 'returns an empty list' do
26
+ AlertLimit.load_all(service_id).must_equal []
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '.save' do
32
+ let(:service_id) { 500 }
33
+ let(:value) { 100 }
34
+
35
+ before do
36
+ AlertLimit.delete(service_id, value)
37
+ end
38
+
39
+ it 'returns a AlertLimit object' do
40
+ alert_limit = AlertLimit.save(service_id, value)
41
+
42
+ alert_limit.must_be_kind_of AlertLimit
43
+ alert_limit.value.must_equal value
44
+ end
45
+ end
46
+
47
+ describe '.delete' do
48
+ describe 'with an existing alert limit' do
49
+ let(:service_id) { 300 }
50
+ let(:value) { 50 }
51
+
52
+ before do
53
+ AlertLimit.save(service_id, value)
54
+ end
55
+
56
+ it 'returns true' do
57
+ AlertLimit.delete(service_id, value).must_equal true
58
+ end
59
+ end
60
+
61
+ describe 'with a non-existing alert limit' do
62
+ let(:service_id) { 300 }
63
+ let(:value) { 75 }
64
+
65
+ it 'returns true' do
66
+ AlertLimit.delete(service_id, value).must_equal false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,97 @@
1
+ require_relative './spec_helper'
2
+ module ThreeScale
3
+ module Core
4
+ describe ApplicationKey do
5
+ describe '.load_all' do
6
+ describe 'when there are application keys' do
7
+ let(:service_id) { 100 }
8
+ let(:app_id) { 2001 }
9
+ let(:values) { ["foo", "bar"] }
10
+ before do
11
+ values.map { |value| ApplicationKey.delete(service_id, app_id, value) }
12
+
13
+ Application.save service_id: service_id, id: app_id, state: 'suspended',
14
+ plan_id: '3066', plan_name: 'crappy', redirect_url: 'blah'
15
+
16
+ values.map { |value| ApplicationKey.save(service_id, app_id, value) }
17
+ end
18
+
19
+ it 'returns a list of application keys' do
20
+ application_keys = ApplicationKey.load_all(service_id, app_id)
21
+
22
+ application_keys.size.must_equal 2
23
+ application_keys.map(&:value).sort.must_equal values.sort
24
+ end
25
+ end
26
+
27
+ describe 'when there are no application keys' do
28
+ let(:service_id) { 200 }
29
+ let(:app_id) { 300 }
30
+
31
+ before do
32
+ Application.save service_id: service_id, id: app_id, state: 'suspended',
33
+ plan_id: '3066', plan_name: 'crappy', redirect_url: 'blah'
34
+ end
35
+
36
+ it 'returns an empty list' do
37
+ ApplicationKey.load_all(service_id, app_id).must_equal []
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '.save' do
43
+ let(:service_id) { 500 }
44
+ let(:app_id) { 500 }
45
+ let(:value) { "foobar" }
46
+
47
+ before do
48
+ ApplicationKey.delete(service_id, app_id, value)
49
+
50
+ Application.save service_id: service_id, id: app_id, state: 'suspended',
51
+ plan_id: '3066', plan_name: 'crappy', redirect_url: 'blah'
52
+ end
53
+
54
+ it 'returns an ApplicationKey object' do
55
+ application_key = ApplicationKey.save(service_id, app_id, value)
56
+
57
+ application_key.must_be_kind_of ApplicationKey
58
+ application_key.value.must_equal value
59
+ end
60
+ end
61
+
62
+ describe '.delete' do
63
+ describe 'with an existing application key' do
64
+ let(:service_id) { 300 }
65
+ let(:app_id) { 200 }
66
+ let(:value) { "foo" }
67
+
68
+ before do
69
+ Application.save service_id: service_id, id: app_id, state: 'suspended',
70
+ plan_id: '3066', plan_name: 'crappy', redirect_url: 'blah'
71
+
72
+ ApplicationKey.save(service_id, app_id, value)
73
+ end
74
+
75
+ it 'returns true' do
76
+ ApplicationKey.delete(service_id, app_id, value).must_equal true
77
+ end
78
+ end
79
+
80
+ describe 'with a non-existing application key' do
81
+ let(:service_id) { 300 }
82
+ let(:app_id) { 500 }
83
+ let(:value) { "nonexistingkey" }
84
+
85
+ before do
86
+ Application.save service_id: service_id, id: app_id, state: 'suspended',
87
+ plan_id: '3066', plan_name: 'crappy', redirect_url: 'blah'
88
+ end
89
+
90
+ it 'returns true' do
91
+ ApplicationKey.delete(service_id, app_id, value).must_equal false
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,57 @@
1
+ require_relative './spec_helper'
2
+ module ThreeScale
3
+ module Core
4
+ describe ApplicationReferrerFilter do
5
+ let(:service_id) { 10 }
6
+ let(:app_id) { 100 }
7
+ let(:filters) { %w(foo bar doopah) }
8
+ let(:application) do
9
+ { service_id: service_id,
10
+ id: app_id,
11
+ state: 'suspended',
12
+ plan_id: '3066',
13
+ plan_name: 'crappy',
14
+ redirect_url: 'blah' }
15
+ end
16
+
17
+ before do
18
+ filters.map do |filter|
19
+ ApplicationReferrerFilter.delete(service_id, app_id, filter)
20
+ end
21
+
22
+ Application.delete(service_id, app_id)
23
+ Application.save(application)
24
+ end
25
+
26
+ describe '.load_all' do
27
+ describe 'Getting all referrer filters' do
28
+ let(:values) { %w(foo bar) }
29
+
30
+ before do
31
+ values.map { |value| ApplicationReferrerFilter.save(service_id, app_id, value) }
32
+ end
33
+
34
+ it 'returns a sorted list of filters' do
35
+ filters = ApplicationReferrerFilter.load_all(service_id, app_id)
36
+ filters.must_equal values.sort
37
+ end
38
+ end
39
+
40
+ describe 'when there are no referrer filters' do
41
+ it 'returns an empty list' do
42
+ ApplicationReferrerFilter.load_all(service_id, app_id).must_equal []
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '.save' do
48
+ let(:filter) { 'doopah' }
49
+
50
+ it 'saves the filter' do
51
+ ApplicationReferrerFilter.save(service_id, app_id, filter)
52
+ ApplicationReferrerFilter.load_all(service_id, app_id).must_equal([filter])
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end