candy_check 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +15 -0
- data/bin/cc_appstore +90 -0
- data/bin/cc_playstore +119 -0
- data/candy_check.gemspec +31 -0
- data/lib/candy_check/app_store/client.rb +57 -0
- data/lib/candy_check/app_store/config.rb +30 -0
- data/lib/candy_check/app_store/receipt.rb +83 -0
- data/lib/candy_check/app_store/verification.rb +49 -0
- data/lib/candy_check/app_store/verification_failure.rb +60 -0
- data/lib/candy_check/app_store/verifier.rb +69 -0
- data/lib/candy_check/app_store.rb +12 -0
- data/lib/candy_check/play_store/client.rb +102 -0
- data/lib/candy_check/play_store/config.rb +51 -0
- data/lib/candy_check/play_store/discovery_repository.rb +33 -0
- data/lib/candy_check/play_store/receipt.rb +81 -0
- data/lib/candy_check/play_store/verification.rb +46 -0
- data/lib/candy_check/play_store/verification_failure.rb +30 -0
- data/lib/candy_check/play_store/verifier.rb +52 -0
- data/lib/candy_check/play_store.rb +15 -0
- data/lib/candy_check/utils/attribute_reader.rb +30 -0
- data/lib/candy_check/utils/config.rb +40 -0
- data/lib/candy_check/utils.rb +2 -0
- data/lib/candy_check/version.rb +4 -0
- data/lib/candy_check.rb +8 -0
- data/spec/app_store/client_spec.rb +55 -0
- data/spec/app_store/config_spec.rb +41 -0
- data/spec/app_store/receipt_spec.rb +92 -0
- data/spec/app_store/verifcation_failure_spec.rb +28 -0
- data/spec/app_store/verification_spec.rb +66 -0
- data/spec/app_store/verifier_spec.rb +110 -0
- data/spec/candy_check_spec.rb +9 -0
- data/spec/fixtures/api_cache.dump +1 -0
- data/spec/fixtures/play_store/api_cache.dump +1 -0
- data/spec/fixtures/play_store/auth_failure.txt +18 -0
- data/spec/fixtures/play_store/auth_success.txt +20 -0
- data/spec/fixtures/play_store/discovery.txt +2841 -0
- data/spec/fixtures/play_store/dummy.p12 +0 -0
- data/spec/fixtures/play_store/empty.txt +17 -0
- data/spec/fixtures/play_store/products_failure.txt +29 -0
- data/spec/fixtures/play_store/products_success.txt +22 -0
- data/spec/play_store/client_spec.rb +104 -0
- data/spec/play_store/config_spec.rb +96 -0
- data/spec/play_store/discovery_respository_spec.rb +31 -0
- data/spec/play_store/receipt_spec.rb +88 -0
- data/spec/play_store/verification_failure_spec.rb +35 -0
- data/spec/play_store/verification_spec.rb +80 -0
- data/spec/play_store/verifier_spec.rb +95 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/support/with_fixtures.rb +9 -0
- data/spec/support/with_temp_file.rb +23 -0
- metadata +270 -0
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Expires: Mon, 19 Jan 2015 12:14:41 GMT
|
3
|
+
Date: Mon, 19 Jan 2015 12:09:41 GMT
|
4
|
+
Cache-Control: public, max-age=300, must-revalidate, no-transform
|
5
|
+
ETag: "ye6orv2F-1npMW3u9suM3a7C5Bo/xy4W57xFkYdtUdm4rpPIU-7grtg"
|
6
|
+
Vary: Origin
|
7
|
+
Vary: X-Origin
|
8
|
+
Content-Type: application/json; charset=UTF-8
|
9
|
+
X-Content-Type-Options: nosniff
|
10
|
+
X-Frame-Options: SAMEORIGIN
|
11
|
+
X-XSS-Protection: 1; mode=block
|
12
|
+
Content-Length: 83495
|
13
|
+
Server: GSE
|
14
|
+
Alternate-Protocol: 443:quic,p=0.02
|
15
|
+
|
16
|
+
{
|
17
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
HTTP/1.1 401 Unauthorized
|
2
|
+
Expires: Mon, 19 Jan 2015 12:14:41 GMT
|
3
|
+
Date: Mon, 19 Jan 2015 12:09:41 GMT
|
4
|
+
Cache-Control: public, max-age=300, must-revalidate, no-transform
|
5
|
+
ETag: "ye6orv2F-1npMW3u9suM3a7C5Bo/xy4W57xFkYdtUdm4rpPIU-7grtg"
|
6
|
+
Vary: Origin
|
7
|
+
Vary: X-Origin
|
8
|
+
WWW-Authenticate: Bearer realm="https://accounts.google.com/AuthSubRequest"
|
9
|
+
Content-Type: application/json; charset=UTF-8
|
10
|
+
X-Content-Type-Options: nosniff
|
11
|
+
X-Frame-Options: SAMEORIGIN
|
12
|
+
X-XSS-Protection: 1; mode=block
|
13
|
+
Content-Length: 83495
|
14
|
+
Server: GSE
|
15
|
+
Alternate-Protocol: 443:quic,p=0.02
|
16
|
+
|
17
|
+
{
|
18
|
+
"error": {
|
19
|
+
"errors": [
|
20
|
+
{
|
21
|
+
"domain": "androidpublisher",
|
22
|
+
"reason": "permissionDenied",
|
23
|
+
"message": "The current user has insufficient permissions to perform the requested operation."
|
24
|
+
}
|
25
|
+
],
|
26
|
+
"code": 401,
|
27
|
+
"message": "The current user has insufficient permissions to perform the requested operation."
|
28
|
+
}
|
29
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
HTTP/1.1 200 OK
|
2
|
+
Expires: Mon, 19 Jan 2015 12:14:41 GMT
|
3
|
+
Date: Mon, 19 Jan 2015 12:09:41 GMT
|
4
|
+
Cache-Control: public, max-age=300, must-revalidate, no-transform
|
5
|
+
ETag: "ye6orv2F-1npMW3u9suM3a7C5Bo/xy4W57xFkYdtUdm4rpPIU-7grtg"
|
6
|
+
Vary: Origin
|
7
|
+
Vary: X-Origin
|
8
|
+
Content-Type: application/json; charset=UTF-8
|
9
|
+
X-Content-Type-Options: nosniff
|
10
|
+
X-Frame-Options: SAMEORIGIN
|
11
|
+
X-XSS-Protection: 1; mode=block
|
12
|
+
Content-Length: 83495
|
13
|
+
Server: GSE
|
14
|
+
Alternate-Protocol: 443:quic,p=0.02
|
15
|
+
|
16
|
+
{
|
17
|
+
"kind": "androidpublisher#productPurchase",
|
18
|
+
"purchaseTimeMillis": "1421676237413",
|
19
|
+
"purchaseState": 0,
|
20
|
+
"consumptionState": 0,
|
21
|
+
"developerPayload": "payload that gets stored and returned"
|
22
|
+
}
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::Client do
|
4
|
+
include WithTempFile
|
5
|
+
include WithFixtures
|
6
|
+
|
7
|
+
with_temp_file :cache_file
|
8
|
+
|
9
|
+
subject { CandyCheck::PlayStore::Client.new(config) }
|
10
|
+
|
11
|
+
let(:config) do
|
12
|
+
CandyCheck::PlayStore::Config.new(
|
13
|
+
application_name: 'demo_app',
|
14
|
+
application_version: '1.0',
|
15
|
+
issuer: 'test_issuer',
|
16
|
+
key_file: fixture_path('play_store', 'dummy.p12'),
|
17
|
+
cache_file: cache_file_path,
|
18
|
+
key_secret: 'notasecret'
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'discovery' do
|
23
|
+
describe 'w/o cache file' do
|
24
|
+
it 'boot loads and dumps discovery file' do
|
25
|
+
mock_discovery!('discovery.txt')
|
26
|
+
mock_authorize!('auth_success.txt')
|
27
|
+
subject.boot!
|
28
|
+
File.exist?(cache_file_path).must_be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'fails if discovery fails' do
|
32
|
+
mock_discovery!('empty.txt')
|
33
|
+
proc { subject.boot! }.must_raise \
|
34
|
+
CandyCheck::PlayStore::Client::DiscoveryError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'with cache file' do
|
39
|
+
let(:cache_file_path) { fixture_path('play_store', 'api_cache.dump') }
|
40
|
+
|
41
|
+
it 'loads the discovery from cache file' do
|
42
|
+
mock_authorize!('auth_success.txt')
|
43
|
+
subject.boot!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'fails if authentication fails' do
|
49
|
+
mock_discovery!('discovery.txt')
|
50
|
+
mock_authorize!('auth_failure.txt')
|
51
|
+
proc { subject.boot! }.must_raise Signet::AuthorizationError
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns the products call result\'s data even if it is a failure' do
|
55
|
+
bootup!
|
56
|
+
|
57
|
+
mock_request!('products_failure.txt')
|
58
|
+
result = subject.verify('the_package', 'the_id', 'the_token')
|
59
|
+
result.must_be_instance_of Hash
|
60
|
+
|
61
|
+
result['error']['code'].must_equal 401
|
62
|
+
result['error']['message'].must_equal 'The current user has insufficient' \
|
63
|
+
' permissions to perform the requested operation.'
|
64
|
+
result['error']['errors'].size.must_equal 1
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns the products call result\'s data for a successful call' do
|
68
|
+
bootup!
|
69
|
+
mock_request!('products_success.txt')
|
70
|
+
result = subject.verify('the_package', 'the_id', 'the_token')
|
71
|
+
result.must_be_instance_of Hash
|
72
|
+
result['purchaseState'].must_equal 0
|
73
|
+
result['consumptionState'].must_equal 0
|
74
|
+
result['developerPayload'].must_equal \
|
75
|
+
'payload that gets stored and returned'
|
76
|
+
result['purchaseTimeMillis'].must_equal '1421676237413'
|
77
|
+
result['kind'].must_equal 'androidpublisher#productPurchase'
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def bootup!
|
83
|
+
mock_discovery!('discovery.txt')
|
84
|
+
mock_authorize!('auth_success.txt')
|
85
|
+
subject.boot!
|
86
|
+
end
|
87
|
+
|
88
|
+
def mock_discovery!(file)
|
89
|
+
stub_request(:get, 'https://www.googleapis.com/discovery/' \
|
90
|
+
'v1/apis/androidpublisher/v2/rest')
|
91
|
+
.to_return(fixture_content('play_store', file))
|
92
|
+
end
|
93
|
+
|
94
|
+
def mock_authorize!(file)
|
95
|
+
stub_request(:post, 'https://accounts.google.com/o/oauth2/token')
|
96
|
+
.to_return(fixture_content('play_store', file))
|
97
|
+
end
|
98
|
+
|
99
|
+
def mock_request!(file)
|
100
|
+
stub_request(:get, 'https://www.googleapis.com/androidpublisher/v2/' \
|
101
|
+
'applications/the_package/purchases/products/the_id/tokens/the_token')
|
102
|
+
.to_return(fixture_content('play_store', file))
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::Config do
|
4
|
+
subject { CandyCheck::PlayStore::Config.new(attributes) }
|
5
|
+
|
6
|
+
let(:attributes) do
|
7
|
+
{
|
8
|
+
application_name: 'the_name',
|
9
|
+
application_version: 'the_version',
|
10
|
+
issuer: 'the_issuer',
|
11
|
+
key_file: 'the_key_file',
|
12
|
+
key_secret: 'the_key_secret'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'minimal attributes' do
|
17
|
+
it 'initializes and validates correctly' do
|
18
|
+
subject.application_name.must_equal 'the_name'
|
19
|
+
subject.application_version.must_equal 'the_version'
|
20
|
+
subject.issuer.must_equal 'the_issuer'
|
21
|
+
subject.key_file.must_equal 'the_key_file'
|
22
|
+
subject.key_secret.must_equal 'the_key_secret'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'maximal attributes' do
|
27
|
+
let(:attributes) do
|
28
|
+
{
|
29
|
+
application_name: 'the_name',
|
30
|
+
application_version: 'the_version',
|
31
|
+
issuer: 'the_issuer',
|
32
|
+
key_file: 'the_key_file',
|
33
|
+
key_secret: 'the_key_secret',
|
34
|
+
cache_file: 'the_cache_file'
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'initializes and validates correctly' do
|
39
|
+
subject.application_name.must_equal 'the_name'
|
40
|
+
subject.application_version.must_equal 'the_version'
|
41
|
+
subject.issuer.must_equal 'the_issuer'
|
42
|
+
subject.key_file.must_equal 'the_key_file'
|
43
|
+
subject.key_secret.must_equal 'the_key_secret'
|
44
|
+
subject.cache_file.must_equal 'the_cache_file'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'invalid attributes' do
|
49
|
+
it 'needs application_name' do
|
50
|
+
assert_raises_missing :application_name
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'needs application_version' do
|
54
|
+
assert_raises_missing :application_version
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'needs issuer' do
|
58
|
+
assert_raises_missing :issuer
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'needs key_file' do
|
62
|
+
assert_raises_missing :key_file
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'needs key_secret' do
|
66
|
+
assert_raises_missing :key_secret
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def assert_raises_missing(name)
|
72
|
+
attributes.delete(name)
|
73
|
+
proc do
|
74
|
+
subject
|
75
|
+
end.must_raise ArgumentError
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'p12 certificate' do
|
80
|
+
include WithFixtures
|
81
|
+
|
82
|
+
let(:attributes) do
|
83
|
+
{
|
84
|
+
application_name: 'the_name',
|
85
|
+
application_version: 'the_version',
|
86
|
+
issuer: 'the_issuer',
|
87
|
+
key_file: fixture_path('play_store', 'dummy.p12'),
|
88
|
+
key_secret: 'notasecret'
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'load the api_key from a file' do
|
93
|
+
subject.api_key.wont_be_nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::DiscoveryRepository do
|
4
|
+
subject { CandyCheck::PlayStore::DiscoveryRepository.new(discovery_path) }
|
5
|
+
|
6
|
+
let(:data) do
|
7
|
+
{ 'demo' => 1 }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'empty file path' do
|
11
|
+
let(:discovery_path) { nil }
|
12
|
+
|
13
|
+
it 'returns nil for nil path' do
|
14
|
+
subject.load.must_be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not save' do
|
18
|
+
subject.save(data)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'valid file path' do
|
23
|
+
include WithTempFile
|
24
|
+
with_temp_file :discovery
|
25
|
+
|
26
|
+
it 'saves and loads the file content' do
|
27
|
+
subject.save(data)
|
28
|
+
subject.load.must_equal data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::Receipt do
|
4
|
+
subject { CandyCheck::PlayStore::Receipt.new(attributes) }
|
5
|
+
|
6
|
+
describe 'valid and non-consumed product' do
|
7
|
+
let(:attributes) do
|
8
|
+
{
|
9
|
+
'kind' => 'androidpublisher#productPurchase',
|
10
|
+
'purchaseTimeMillis' => '1421676237413',
|
11
|
+
'purchaseState' => 0,
|
12
|
+
'consumptionState' => 0,
|
13
|
+
'developerPayload' => 'payload that gets stored and returned'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is valid?' do
|
18
|
+
subject.valid?.must_be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is not consumed' do
|
22
|
+
subject.consumed?.must_be_false
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the purchase_state' do
|
26
|
+
subject.purchase_state.must_equal 0
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns the consumption_state' do
|
30
|
+
subject.consumption_state.must_equal 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns the developer_payload' do
|
34
|
+
subject.developer_payload.must_equal \
|
35
|
+
'payload that gets stored and returned'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns the kind' do
|
39
|
+
subject.kind.must_equal \
|
40
|
+
'androidpublisher#productPurchase'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns the purchase_time_millis' do
|
44
|
+
subject.purchase_time_millis.must_equal 1_421_676_237_413
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns the purchased_at' do
|
48
|
+
expected = DateTime.new(2015, 1, 19, 14, 03, 57)
|
49
|
+
subject.purchased_at.must_equal expected
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'valid and consumed product' do
|
54
|
+
let(:attributes) do
|
55
|
+
{
|
56
|
+
'kind' => 'androidpublisher#productPurchase',
|
57
|
+
'purchaseTimeMillis' => '1421676237413',
|
58
|
+
'purchaseState' => 0,
|
59
|
+
'consumptionState' => 1,
|
60
|
+
'developerPayload' => 'payload that gets stored and returned'
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'is valid?' do
|
65
|
+
subject.valid?.must_be_true
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'is consumed?' do
|
69
|
+
subject.consumed?.must_be_true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'non-valid product' do
|
74
|
+
let(:attributes) do
|
75
|
+
{
|
76
|
+
'kind' => 'androidpublisher#productPurchase',
|
77
|
+
'purchaseTimeMillis' => '1421676237413',
|
78
|
+
'purchaseState' => 1,
|
79
|
+
'consumptionState' => 0,
|
80
|
+
'developerPayload' => 'payload that gets stored and returned'
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'is valid?' do
|
85
|
+
subject.valid?.must_be_false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::VerificationFailure do
|
4
|
+
subject { CandyCheck::PlayStore::VerificationFailure.new(attributes) }
|
5
|
+
|
6
|
+
describe 'denied' do
|
7
|
+
let(:attributes) do
|
8
|
+
{
|
9
|
+
'errors' => [],
|
10
|
+
'code' => 401,
|
11
|
+
'message' => 'The current user has insufficient permissions'
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns the code' do
|
16
|
+
subject.code.must_equal 401
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns the message' do
|
20
|
+
subject.message.must_equal 'The current user has insufficient permissions'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'empty' do
|
25
|
+
let(:attributes) { nil }
|
26
|
+
|
27
|
+
it 'returns an unknown code' do
|
28
|
+
subject.code.must_equal(-1)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns an unknown message' do
|
32
|
+
subject.message.must_equal 'Unknown error'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CandyCheck::PlayStore::Verification do
|
4
|
+
subject do
|
5
|
+
CandyCheck::PlayStore::Verification.new(client, package, product_id, token)
|
6
|
+
end
|
7
|
+
let(:client) { DummyGoogleClient.new(response) }
|
8
|
+
let(:package) { 'the_package' }
|
9
|
+
let(:product_id) { 'the_product' }
|
10
|
+
let(:token) { 'the_token' }
|
11
|
+
|
12
|
+
describe 'valid' do
|
13
|
+
let(:response) do
|
14
|
+
{
|
15
|
+
'kind' => 'androidpublisher#productPurchase',
|
16
|
+
'purchaseTimeMillis' => '1421676237413',
|
17
|
+
'purchaseState' => 0,
|
18
|
+
'consumptionState' => 0,
|
19
|
+
'developerPayload' => 'payload that gets stored and returned'
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'calls the client with the correct paramters' do
|
24
|
+
subject.call!
|
25
|
+
client.package.must_equal package
|
26
|
+
client.product_id.must_equal product_id
|
27
|
+
client.token.must_equal token
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns a receipt' do
|
31
|
+
result = subject.call!
|
32
|
+
result.must_be_instance_of CandyCheck::PlayStore::Receipt
|
33
|
+
result.valid?.must_be_true
|
34
|
+
result.consumed?.must_be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'failure' do
|
39
|
+
let(:response) do
|
40
|
+
{
|
41
|
+
'error' => {
|
42
|
+
'code' => 401,
|
43
|
+
'message' => 'The current user has insufficient permissions'
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns a verification failure' do
|
49
|
+
result = subject.call!
|
50
|
+
result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
|
51
|
+
result.code.must_equal 401
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'empty' do
|
56
|
+
let(:response) do
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns a verification failure' do
|
61
|
+
result = subject.call!
|
62
|
+
result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
|
63
|
+
result.code.must_equal(-1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
class DummyGoogleClient < Struct.new(:response)
|
70
|
+
attr_reader :package, :product_id, :token
|
71
|
+
|
72
|
+
def boot!
|
73
|
+
end
|
74
|
+
|
75
|
+
def verify(package, product_id, token)
|
76
|
+
@package, @product_id, @token = package, product_id, token
|
77
|
+
response
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|