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,122 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: params
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Fri, 04 Oct 2019 09:25:53 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "some token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Fri, 04 Oct 2019 09:25:53 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/products/my_product_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-29-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Fri, 04 Oct 2019 09:25:53 GMT
71
+ Authorization:
72
+ - Bearer some_bearer_token
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 200
78
+ message: OK
79
+ headers:
80
+ Expires:
81
+ - Fri, 04 Oct 2019 09:25:54 GMT
82
+ Date:
83
+ - Fri, 04 Oct 2019 09:25:54 GMT
84
+ Cache-Control:
85
+ - private, max-age=0, must-revalidate, no-transform
86
+ Etag:
87
+ - '""'
88
+ Vary:
89
+ - Origin
90
+ - X-Origin
91
+ Content-Type:
92
+ - application/json; charset=UTF-8
93
+ Content-Encoding:
94
+ - gzip
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "kind": "androidpublisher#productPurchase",
113
+ "purchaseTimeMillis": "123456789",
114
+ "purchaseState": 0,
115
+ "consumptionState": 0,
116
+ "developerPayload": "",
117
+ "orderId": "123",
118
+ "acknowledgementState": 1
119
+ }
120
+ http_version:
121
+ recorded_at: Fri, 04 Oct 2019 09:25:54 GMT
122
+ recorded_with: VCR 5.0.0
@@ -0,0 +1,196 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: params
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Thu, 10 Oct 2019 14:07:52 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "my_token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Thu, 10 Oct 2019 14:07:52 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-31-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Thu, 10 Oct 2019 14:07:52 GMT
71
+ Authorization:
72
+ - Bearer my_bearer
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 401
78
+ message: Unauthorized
79
+ headers:
80
+ Vary:
81
+ - Origin
82
+ - X-Origin
83
+ Www-Authenticate:
84
+ - Bearer realm="https://accounts.google.com/", error=invalid_token
85
+ Content-Type:
86
+ - application/json; charset=UTF-8
87
+ Content-Encoding:
88
+ - gzip
89
+ Date:
90
+ - Thu, 10 Oct 2019 14:07:52 GMT
91
+ Expires:
92
+ - Thu, 10 Oct 2019 14:07:52 GMT
93
+ Cache-Control:
94
+ - private, max-age=0
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "error": {
113
+ "errors": [
114
+ {
115
+ "domain": "androidpublisher",
116
+ "reason": "permissionDenied",
117
+ "message": "The current user has insufficient permissions to perform the requested operation."
118
+ }
119
+ ],
120
+ "code": 401,
121
+ "message": "The current user has insufficient permissions to perform the requested operation."
122
+ }
123
+ }
124
+ http_version:
125
+ recorded_at: Thu, 10 Oct 2019 14:07:52 GMT
126
+ - request:
127
+ method: get
128
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
129
+ body:
130
+ encoding: UTF-8
131
+ string: ''
132
+ headers:
133
+ User-Agent:
134
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-31-generic (gzip)
135
+ Accept:
136
+ - "*/*"
137
+ Accept-Encoding:
138
+ - gzip,deflate
139
+ Date:
140
+ - Thu, 10 Oct 2019 14:07:53 GMT
141
+ Authorization:
142
+ - Bearer my_bearer
143
+ Content-Type:
144
+ - application/x-www-form-urlencoded
145
+ response:
146
+ status:
147
+ code: 401
148
+ message: Unauthorized
149
+ headers:
150
+ Vary:
151
+ - Origin
152
+ - X-Origin
153
+ Www-Authenticate:
154
+ - Bearer realm="https://accounts.google.com/", error=invalid_token
155
+ Content-Type:
156
+ - application/json; charset=UTF-8
157
+ Content-Encoding:
158
+ - gzip
159
+ Date:
160
+ - Thu, 10 Oct 2019 14:07:53 GMT
161
+ Expires:
162
+ - Thu, 10 Oct 2019 14:07:53 GMT
163
+ Cache-Control:
164
+ - private, max-age=0
165
+ X-Content-Type-Options:
166
+ - nosniff
167
+ X-Frame-Options:
168
+ - SAMEORIGIN
169
+ X-Xss-Protection:
170
+ - 1; mode=block
171
+ Server:
172
+ - GSE
173
+ Alt-Svc:
174
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
175
+ ma=2592000,h3-Q043=":443"; ma=2592000
176
+ Transfer-Encoding:
177
+ - chunked
178
+ body:
179
+ encoding: UTF-8
180
+ string: |
181
+ {
182
+ "error": {
183
+ "errors": [
184
+ {
185
+ "domain": "androidpublisher",
186
+ "reason": "permissionDenied",
187
+ "message": "The current user has insufficient permissions to perform the requested operation."
188
+ }
189
+ ],
190
+ "code": 401,
191
+ "message": "The current user has insufficient permissions to perform the requested operation."
192
+ }
193
+ }
194
+ http_version:
195
+ recorded_at: Thu, 10 Oct 2019 14:07:53 GMT
196
+ recorded_with: VCR 5.0.0
@@ -0,0 +1,127 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://www.googleapis.com/oauth2/v4/token
6
+ body:
7
+ encoding: ASCII-8BIT
8
+ string: some_grant
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.15.4
12
+ Content-Type:
13
+ - application/x-www-form-urlencoded
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Content-Type:
24
+ - application/json; charset=utf-8
25
+ Vary:
26
+ - Origin
27
+ - Referer
28
+ - X-Origin
29
+ Date:
30
+ - Thu, 24 Oct 2019 10:43:13 GMT
31
+ Server:
32
+ - scaffolding on HTTPServer2
33
+ Cache-Control:
34
+ - private
35
+ X-Xss-Protection:
36
+ - '0'
37
+ X-Frame-Options:
38
+ - SAMEORIGIN
39
+ X-Content-Type-Options:
40
+ - nosniff
41
+ Alt-Svc:
42
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
43
+ ma=2592000,h3-Q043=":443"; ma=2592000
44
+ Transfer-Encoding:
45
+ - chunked
46
+ body:
47
+ encoding: ASCII-8BIT
48
+ string: |-
49
+ {
50
+ "access_token": "some token",
51
+ "expires_in": 3600,
52
+ "token_type": "Bearer"
53
+ }
54
+ http_version:
55
+ recorded_at: Thu, 24 Oct 2019 10:43:13 GMT
56
+ - request:
57
+ method: get
58
+ uri: https://www.googleapis.com/androidpublisher/v3/applications/my_package_name/purchases/subscriptions/my_subscription_id/tokens/my_token
59
+ body:
60
+ encoding: UTF-8
61
+ string: ''
62
+ headers:
63
+ User-Agent:
64
+ - unknown/0.0.0 google-api-ruby-client/0.28.7 Linux/5.0.0-32-generic (gzip)
65
+ Accept:
66
+ - "*/*"
67
+ Accept-Encoding:
68
+ - gzip,deflate
69
+ Date:
70
+ - Thu, 24 Oct 2019 10:43:13 GMT
71
+ Authorization:
72
+ - Bearer some_token
73
+ Content-Type:
74
+ - application/x-www-form-urlencoded
75
+ response:
76
+ status:
77
+ code: 200
78
+ message: OK
79
+ headers:
80
+ Expires:
81
+ - Thu, 24 Oct 2019 10:43:13 GMT
82
+ Date:
83
+ - Thu, 24 Oct 2019 10:43:13 GMT
84
+ Cache-Control:
85
+ - private, max-age=0, must-revalidate, no-transform
86
+ Etag:
87
+ - ''
88
+ Vary:
89
+ - Origin
90
+ - X-Origin
91
+ Content-Type:
92
+ - application/json; charset=UTF-8
93
+ Content-Encoding:
94
+ - gzip
95
+ X-Content-Type-Options:
96
+ - nosniff
97
+ X-Frame-Options:
98
+ - SAMEORIGIN
99
+ X-Xss-Protection:
100
+ - 1; mode=block
101
+ Server:
102
+ - GSE
103
+ Alt-Svc:
104
+ - quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443";
105
+ ma=2592000,h3-Q043=":443"; ma=2592000
106
+ Transfer-Encoding:
107
+ - chunked
108
+ body:
109
+ encoding: UTF-8
110
+ string: |
111
+ {
112
+ "kind": "androidpublisher#subscriptionPurchase",
113
+ "startTimeMillis": "123456789",
114
+ "expiryTimeMillis": "123456789",
115
+ "autoRenewing": false,
116
+ "priceCurrencyCode": "EUR",
117
+ "priceAmountMicros": "123",
118
+ "countryCode": "DE",
119
+ "developerPayload": "",
120
+ "cancelReason": 1,
121
+ "orderId": "123",
122
+ "purchaseType": 0,
123
+ "acknowledgementState": 1
124
+ }
125
+ http_version:
126
+ recorded_at: Thu, 24 Oct 2019 10:43:13 GMT
127
+ recorded_with: VCR 5.0.0
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+
3
+ describe CandyCheck::PlayStore::ProductPurchases::ProductPurchase do
4
+ subject { CandyCheck::PlayStore::ProductPurchases::ProductPurchase.new(fake_product_purchase) }
5
+
6
+ describe "valid and non-consumed product" do
7
+ let(:fake_product_purchase) do
8
+ FakeProductPurchase.new(
9
+ consumption_state: 0,
10
+ developer_payload: "payload that gets stored and returned",
11
+ kind: "androidpublisher#productPurchase",
12
+ order_id: "ABC123",
13
+ purchase_state: 0,
14
+ purchase_time_millis: 1421676237413,
15
+ )
16
+ end
17
+
18
+ it "is valid?" do
19
+ subject.valid?.must_be_true
20
+ end
21
+
22
+ it "is not consumed" do
23
+ subject.consumed?.must_be_false
24
+ end
25
+
26
+ it "returns the purchase_state" do
27
+ subject.purchase_state.must_equal 0
28
+ end
29
+
30
+ it "returns the consumption_state" do
31
+ subject.consumption_state.must_equal 0
32
+ end
33
+
34
+ it "returns the developer_payload" do
35
+ subject.developer_payload.must_equal "payload that gets stored and returned"
36
+ end
37
+
38
+ it "returns the kind" do
39
+ subject.kind.must_equal "androidpublisher#productPurchase"
40
+ end
41
+
42
+ it "returns the purchase_time_millis" do
43
+ subject.purchase_time_millis.must_equal 1_421_676_237_413
44
+ end
45
+
46
+ it "returns the purchased_at" do
47
+ expected = DateTime.new(2015, 1, 19, 14, 3, 57)
48
+ subject.purchased_at.must_equal expected
49
+ end
50
+ end
51
+
52
+ describe "valid and consumed product" do
53
+ let(:fake_product_purchase) do
54
+ FakeProductPurchase.new(
55
+ consumption_state: 1,
56
+ developer_payload: "payload that gets stored and returned",
57
+ kind: "androidpublisher#productPurchase",
58
+ order_id: "ABC123",
59
+ purchase_state: 0,
60
+ purchase_time_millis: 1421676237413,
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(:fake_product_purchase) do
75
+ FakeProductPurchase.new(
76
+ consumption_state: 0,
77
+ developer_payload: "payload that gets stored and returned",
78
+ kind: "androidpublisher#productPurchase",
79
+ order_id: "ABC123",
80
+ purchase_state: 1,
81
+ purchase_time_millis: 1421676237413,
82
+ )
83
+ end
84
+
85
+ it "is valid?" do
86
+ subject.valid?.must_be_false
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ class FakeProductPurchase
93
+ FIELDS = [
94
+ :consumption_state,
95
+ :developer_payload,
96
+ :kind,
97
+ :order_id,
98
+ :purchase_state,
99
+ :purchase_time_millis,
100
+ ].freeze
101
+
102
+ attr_accessor *FIELDS
103
+
104
+ def initialize(hash)
105
+ FIELDS.each do |key|
106
+ self.public_send("#{key}=", hash[key])
107
+ end
108
+ end
109
+ end
110
+ end