candy_check 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -12
  3. data/Guardfile +42 -0
  4. data/MIGRATION_GUIDE_0_2_0.md +141 -0
  5. data/README.md +49 -26
  6. data/candy_check.gemspec +32 -26
  7. data/lib/candy_check/cli/app.rb +16 -33
  8. data/lib/candy_check/cli/commands/play_store.rb +12 -13
  9. data/lib/candy_check/play_store.rb +17 -10
  10. data/lib/candy_check/play_store/android_publisher_service.rb +6 -0
  11. data/lib/candy_check/play_store/product_purchases/product_purchase.rb +90 -0
  12. data/lib/candy_check/play_store/product_purchases/product_verification.rb +53 -0
  13. data/lib/candy_check/play_store/subscription_purchases/subscription_purchase.rb +141 -0
  14. data/lib/candy_check/play_store/subscription_purchases/subscription_verification.rb +55 -0
  15. data/lib/candy_check/play_store/verification_failure.rb +8 -6
  16. data/lib/candy_check/play_store/verifier.rb +24 -49
  17. data/lib/candy_check/version.rb +1 -1
  18. data/spec/candy_check_spec.rb +2 -2
  19. data/spec/cli/commands/play_store_spec.rb +10 -43
  20. data/spec/fixtures/play_store/random_dummy_key.json +12 -0
  21. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/permission_denied.yml +196 -0
  22. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/response_with_empty_body.yml +183 -0
  23. data/spec/fixtures/vcr_cassettes/play_store/product_purchases/valid_but_not_consumed.yml +122 -0
  24. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/permission_denied.yml +196 -0
  25. data/spec/fixtures/vcr_cassettes/play_store/subscription_purchases/valid_but_expired.yml +127 -0
  26. data/spec/play_store/product_purchases/product_purchase_spec.rb +110 -0
  27. data/spec/play_store/product_purchases/product_verification_spec.rb +49 -0
  28. data/spec/play_store/subscription_purchases/subscription_purchase_spec.rb +181 -0
  29. data/spec/play_store/subscription_purchases/subscription_verification_spec.rb +65 -0
  30. data/spec/play_store/verification_failure_spec.rb +18 -18
  31. data/spec/play_store/verifier_spec.rb +32 -96
  32. data/spec/spec_helper.rb +24 -11
  33. metadata +120 -47
  34. data/lib/candy_check/play_store/client.rb +0 -139
  35. data/lib/candy_check/play_store/config.rb +0 -51
  36. data/lib/candy_check/play_store/discovery_repository.rb +0 -33
  37. data/lib/candy_check/play_store/receipt.rb +0 -81
  38. data/lib/candy_check/play_store/subscription.rb +0 -139
  39. data/lib/candy_check/play_store/subscription_verification.rb +0 -30
  40. data/lib/candy_check/play_store/verification.rb +0 -48
  41. data/spec/fixtures/api_cache.dump +0 -1
  42. data/spec/fixtures/play_store/api_cache.dump +0 -1
  43. data/spec/fixtures/play_store/auth_failure.txt +0 -18
  44. data/spec/fixtures/play_store/auth_success.txt +0 -20
  45. data/spec/fixtures/play_store/discovery.txt +0 -2841
  46. data/spec/fixtures/play_store/dummy.p12 +0 -0
  47. data/spec/fixtures/play_store/empty.txt +0 -17
  48. data/spec/fixtures/play_store/products_failure.txt +0 -29
  49. data/spec/fixtures/play_store/products_success.txt +0 -22
  50. data/spec/play_store/client_spec.rb +0 -125
  51. data/spec/play_store/config_spec.rb +0 -96
  52. data/spec/play_store/discovery_respository_spec.rb +0 -31
  53. data/spec/play_store/receipt_spec.rb +0 -88
  54. data/spec/play_store/subscription_spec.rb +0 -138
  55. data/spec/play_store/subscription_verification_spec.rb +0 -97
  56. data/spec/play_store/verification_spec.rb +0 -81
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::ProductPurchases::ProductVerification do
4
+ subject do
5
+ CandyCheck::PlayStore::ProductPurchases::ProductVerification.new(
6
+ package_name: package_name,
7
+ product_id: product_id,
8
+ token: token,
9
+ authorization: authorization,
10
+ )
11
+ end
12
+ let(:package_name) { "my_package_name" }
13
+ let(:product_id) { "my_product_id" }
14
+ let(:token) { "my_token" }
15
+ let(:json_key_file) { File.expand_path("../../fixtures/play_store/random_dummy_key.json", __dir__) }
16
+
17
+ let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) }
18
+
19
+ describe "valid" do
20
+ it "returns a product purchase" do
21
+ VCR.use_cassette("play_store/product_purchases/valid_but_not_consumed") do
22
+ result = subject.call!
23
+ result.must_be_instance_of CandyCheck::PlayStore::ProductPurchases::ProductPurchase
24
+ result.valid?.must_be_true
25
+ result.consumed?.must_be_false
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "failure" do
31
+ it "returns a verification failure" do
32
+ VCR.use_cassette("play_store/product_purchases/permission_denied") do
33
+ result = subject.call!
34
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
35
+ result.code.must_equal 401
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "empty" do
41
+ it "returns a verification failure" do
42
+ VCR.use_cassette("play_store/product_purchases/response_with_empty_body") do
43
+ result = subject.call!
44
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
45
+ result.code.must_equal(-1)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,181 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase do
4
+ subject { CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase.new(fake_subscription_purchase) }
5
+
6
+ describe "expired and canceled subscription" do
7
+ let(:fake_subscription_purchase) do
8
+ FakeSubscriptionPurchase.new(
9
+ kind: "androidpublisher#subscriptionPurchase",
10
+ start_time_millis: 1459540113244,
11
+ expiry_time_millis: 1462132088610,
12
+ auto_renewing: false,
13
+ developer_payload: "payload that gets stored and returned",
14
+ cancel_reason: 0,
15
+ payment_state: 1,
16
+ )
17
+ end
18
+
19
+ it "is expired?" do
20
+ subject.expired?.must_be_true
21
+ end
22
+
23
+ it "is canceled by user" do
24
+ subject.canceled_by_user?.must_be_true
25
+ end
26
+
27
+ it "returns the payment_state" do
28
+ subject.payment_state.must_equal 1
29
+ end
30
+
31
+ it "considers a payment as valid" do
32
+ subject.payment_received?.must_be_true
33
+ end
34
+
35
+ it "checks that auto renewal status is false" do
36
+ subject.auto_renewing?.must_be_false
37
+ end
38
+
39
+ it "returns the developer_payload" do
40
+ subject.developer_payload.must_equal \
41
+ "payload that gets stored and returned"
42
+ end
43
+
44
+ it "returns the kind" do
45
+ subject.kind.must_equal "androidpublisher#subscriptionPurchase"
46
+ end
47
+
48
+ it "returns the start_time_millis" do
49
+ subject.start_time_millis.must_equal 145_954_011_324_4
50
+ end
51
+
52
+ it "returns the expiry_time_millis" do
53
+ subject.expiry_time_millis.must_equal 146_213_208_861_0
54
+ end
55
+
56
+ it "returns the starts_at" do
57
+ expected = DateTime.new(2016, 4, 1, 19, 48, 33)
58
+ subject.starts_at.must_equal expected
59
+ end
60
+
61
+ it "returns the expires_at" do
62
+ expected = DateTime.new(2016, 5, 1, 19, 48, 8)
63
+ subject.expires_at.must_equal expected
64
+ end
65
+ end
66
+
67
+ describe "unexpired and renewing subscription" do
68
+ two_days_from_now = DateTime.now + 2
69
+ let(:fake_subscription_purchase) do
70
+ FakeSubscriptionPurchase.new(
71
+ kind: "androidpublisher#subscriptionPurchase",
72
+ start_time_millis: 1459540113244,
73
+ expiry_time_millis: (two_days_from_now.to_time.to_i * 1000),
74
+ auto_renewing: true,
75
+ developer_payload: "payload that gets stored and returned",
76
+ cancel_reason: 0,
77
+ payment_state: 1,
78
+ )
79
+ end
80
+
81
+ it "is expired?" do
82
+ subject.expired?.must_be_false
83
+ end
84
+
85
+ it "is two days left until it is overdue" do
86
+ subject.overdue_days.must_equal(-2)
87
+ end
88
+ end
89
+
90
+ describe "expired due to payment failure" do
91
+ let(:fake_subscription_purchase) do
92
+ FakeSubscriptionPurchase.new(
93
+ kind: "androidpublisher#subscriptionPurchase",
94
+ start_time_millis: 1459540113244,
95
+ expiry_time_millis: 1462132088610,
96
+ auto_renewing: true,
97
+ developer_payload: "payload that gets stored and returned",
98
+ cancel_reason: 1,
99
+ payment_state: 1,
100
+ )
101
+ end
102
+
103
+ it "is expired?" do
104
+ subject.expired?.must_be_true
105
+ end
106
+
107
+ it "is payment_failed?" do
108
+ subject.payment_failed?.must_be_true
109
+ end
110
+ end
111
+
112
+ describe "expired with pending payment" do
113
+ let(:fake_subscription_purchase) do
114
+ FakeSubscriptionPurchase.new(
115
+ kind: "androidpublisher#subscriptionPurchase",
116
+ start_time_millis: 1459540113244,
117
+ expiry_time_millis: 1462132088610,
118
+ auto_renewing: true,
119
+ developer_payload: "payload that gets stored and returned",
120
+ cancel_reason: 0,
121
+ payment_state: 0,
122
+ )
123
+ end
124
+
125
+ it "is expired?" do
126
+ subject.expired?.must_be_true
127
+ end
128
+
129
+ it "is payment_pending?" do
130
+ subject.payment_pending?.must_be_true
131
+ end
132
+ end
133
+
134
+ describe "trial" do
135
+ let(:fake_subscription_purchase) do
136
+ FakeSubscriptionPurchase.new(
137
+ kind: "androidpublisher#subscriptionPurchase",
138
+ start_time_millis: 1459540113244,
139
+ expiry_time_millis: 1462132088610,
140
+ auto_renewing: false,
141
+ developer_payload: "payload that gets stored and returned",
142
+ cancel_reason: 0,
143
+ payment_state: 1,
144
+ price_currency_code: "SOMECODE",
145
+ price_amount_micros: 0,
146
+ )
147
+ end
148
+
149
+ it "is trial?" do
150
+ subject.trial?.must_be_true
151
+ end
152
+
153
+ it "returns the price_currency_code" do
154
+ subject.price_currency_code.must_equal "SOMECODE"
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ class FakeSubscriptionPurchase
161
+ FIELDS = [
162
+ :kind,
163
+ :start_time_millis,
164
+ :expiry_time_millis,
165
+ :auto_renewing,
166
+ :developer_payload,
167
+ :cancel_reason,
168
+ :payment_state,
169
+ :price_amount_micros,
170
+ :price_currency_code,
171
+ ].freeze
172
+
173
+ attr_accessor *FIELDS
174
+
175
+ def initialize(hash)
176
+ FIELDS.each do |key|
177
+ self.public_send("#{key}=", hash[key])
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionVerification do
4
+ subject do
5
+ CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionVerification.new(
6
+ package_name: package_name,
7
+ subscription_id: subscription_id,
8
+ token: token,
9
+ authorization: authorization,
10
+ )
11
+ end
12
+
13
+ let(:json_key_file) { File.expand_path("../../fixtures/play_store/random_dummy_key.json", __dir__) }
14
+ let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) }
15
+
16
+ let(:package_name) { "my_package_name" }
17
+ let(:subscription_id) { "my_subscription_id" }
18
+ let(:token) { "my_token" }
19
+
20
+ describe "valid" do
21
+ it "returns a subscription" do
22
+ VCR.use_cassette("play_store/subscription_purchases/valid_but_expired") do
23
+ result = subject.call!
24
+
25
+ result.must_be_instance_of CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase
26
+ result.expired?.must_be_true
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "failure" do
32
+ it "returns a verification failure" do
33
+ VCR.use_cassette("play_store/subscription_purchases/permission_denied") do
34
+ result = subject.call!
35
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
36
+ result.code.must_equal 401
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "empty" do
42
+ let(:response) do
43
+ {}
44
+ end
45
+
46
+ it "returns a verification failure" do
47
+ result = subject.call!
48
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
49
+ result.code.must_equal(-1)
50
+ end
51
+ end
52
+
53
+ describe "invalid response kind" do
54
+ let(:response) do
55
+ {
56
+ "kind" => "something weird",
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
+ end
64
+ end
65
+ end
@@ -1,35 +1,35 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
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
- }
4
+ subject { CandyCheck::PlayStore::VerificationFailure.new(fake_error) }
5
+
6
+ describe "denied" do
7
+ let(:fake_error) do
8
+ FakeError.new("401", "The current user has insufficient permissions")
13
9
  end
14
10
 
15
- it 'returns the code' do
11
+ it "returns the code" do
16
12
  subject.code.must_equal 401
17
13
  end
18
14
 
19
- it 'returns the message' do
20
- subject.message.must_equal 'The current user has insufficient permissions'
15
+ it "returns the message" do
16
+ subject.message.must_equal "The current user has insufficient permissions"
21
17
  end
22
18
  end
23
19
 
24
- describe 'empty' do
25
- let(:attributes) { nil }
20
+ describe "empty" do
21
+ let(:fake_error) do
22
+ FakeError.new(nil, nil)
23
+ end
26
24
 
27
- it 'returns an unknown code' do
25
+ it "returns an unknown code" do
28
26
  subject.code.must_equal(-1)
29
27
  end
30
28
 
31
- it 'returns an unknown message' do
32
- subject.message.must_equal 'Unknown error'
29
+ it "returns an unknown message" do
30
+ subject.message.must_equal "Unknown error"
33
31
  end
34
32
  end
33
+
34
+ FakeError = Struct.new(:status_code, :message)
35
35
  end
@@ -1,111 +1,47 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  describe CandyCheck::PlayStore::Verifier do
4
- subject { CandyCheck::PlayStore::Verifier.new(config) }
5
- let(:config) do
6
- CandyCheck::PlayStore::Config
7
- .new(
8
- application_name: 'YourApplication',
9
- application_version: '1.0',
10
- issuer: 'abcdefg@developer.gserviceaccount.com',
11
- key_file: 'local/google.p12',
12
- key_secret: 'notasecret'
13
- )
14
- end
15
- let(:package) { 'the_package' }
16
- let(:product_id) { 'the_product' }
17
- let(:token) { 'the_token' }
18
-
19
- it 'holds the config' do
20
- subject.config.must_be_same_as config
21
- end
22
-
23
- it 'requires a boot before verification' do
24
- proc do
25
- subject.verify(package, product_id, token)
26
- end.must_raise CandyCheck::PlayStore::Verifier::BootRequiredError
27
- end
28
-
29
- it 'it configures and boots a client but raises on second boot!' do
30
- with_mocked_client do
31
- subject.boot!
32
- @client.config.must_be_same_as config
33
- @client.booted.must_be_true
34
- end
35
-
36
- proc do
37
- subject.boot!
38
- end.must_raise CandyCheck::PlayStore::Verifier::BootRequiredError
39
- end
40
-
41
- it 'uses a verifier when booted' do
42
- result = :stubbed
43
- with_mocked_client do
44
- subject.boot!
45
- end
46
- with_mocked_verifier(result) do
47
- subject.verify(package, product_id, token).must_be_same_as result
48
-
49
- assert_recorded(
50
- [@client, package, product_id, token]
51
- )
52
- end
53
- end
54
-
55
- it 'uses a subscription verifier when booted' do
56
- result = :stubbed
57
- with_mocked_client do
58
- subject.boot!
59
- end
60
- with_mocked_verifier(result) do
61
- subject.verify_subscription(
62
- package, product_id, token
63
- ).must_be_same_as result
4
+ subject { CandyCheck::PlayStore::Verifier.new(authorization: authorization) }
64
5
 
65
- assert_recorded(
66
- [@client, package, product_id, token]
67
- )
68
- end
69
- end
6
+ let(:package_name) { "my_package_name" }
7
+ let(:product_id) { "my_product_id" }
8
+ let(:subscription_id) { "my_subscription_id" }
9
+ let(:token) { "my_token" }
70
10
 
71
- private
11
+ let(:json_key_file) { File.expand_path("../fixtures/play_store/random_dummy_key.json", __dir__) }
12
+ let(:authorization) { CandyCheck::PlayStore.authorization(json_key_file) }
72
13
 
73
- def with_mocked_verifier(*results)
74
- @recorded ||= []
75
- stub = proc do |*args|
76
- @recorded << args
77
- DummyPlayStoreVerification.new(*args).tap { |v| v.results = results }
14
+ describe "product purchases" do
15
+ it "verifies a product purchase" do
16
+ VCR.use_cassette("play_store/product_purchases/valid_but_not_consumed") do
17
+ result = subject.verify_product_purchase(package_name: package_name, product_id: product_id, token: token)
18
+ result.must_be_instance_of CandyCheck::PlayStore::ProductPurchases::ProductPurchase
19
+ result.valid?.must_be_true
20
+ result.consumed?.must_be_false
21
+ end
78
22
  end
79
- CandyCheck::PlayStore::Verification.stub :new, stub do
80
- yield
81
- end
82
- end
83
23
 
84
- def with_mocked_client
85
- stub = proc do |*args|
86
- @client = DummyPlayStoreClient.new(*args)
87
- end
88
- CandyCheck::PlayStore::Client.stub :new, stub do
89
- yield
24
+ it "can return a product purchase verification failure" do
25
+ VCR.use_cassette("play_store/product_purchases/permission_denied") do
26
+ result = subject.verify_product_purchase(package_name: package_name, product_id: product_id, token: token)
27
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
28
+ end
90
29
  end
91
30
  end
92
31
 
93
- def assert_recorded(*calls)
94
- @recorded.must_equal calls
95
- end
96
-
97
- DummyPlayStoreVerification = Struct.new(:client, :package,
98
- :product_id, :token) do
99
- attr_accessor :results
100
- def call!
101
- results.shift
32
+ describe "subscription purchases" do
33
+ it "verifies a subscription purchase" do
34
+ VCR.use_cassette("play_store/subscription_purchases/valid_but_expired") do
35
+ result = subject.verify_subscription_purchase(package_name: package_name, subscription_id: subscription_id, token: token)
36
+ result.must_be_instance_of CandyCheck::PlayStore::SubscriptionPurchases::SubscriptionPurchase
37
+ end
102
38
  end
103
- end
104
39
 
105
- DummyPlayStoreClient = Struct.new(:config) do
106
- attr_reader :booted
107
- def boot!
108
- @booted = true
40
+ it "can return a subscription purchase verification failure" do
41
+ VCR.use_cassette("play_store/subscription_purchases/permission_denied") do
42
+ result = subject.verify_subscription_purchase(package_name: package_name, subscription_id: subscription_id, token: token)
43
+ result.must_be_instance_of CandyCheck::PlayStore::VerificationFailure
44
+ end
109
45
  end
110
46
  end
111
47
  end