candy_check 0.1.2 → 0.2.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.
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