odeon_uk 4.0.1 → 5.1.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.
- checksums.yaml +5 -5
- data/.github/workflows/release.yml +73 -0
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +36 -0
- data/README.md +100 -21
- data/Rakefile +14 -15
- data/lib/odeon_uk/cinema.rb +40 -22
- data/lib/odeon_uk/internal/api_response.rb +19 -39
- data/lib/odeon_uk/internal/guest_token.rb +23 -0
- data/lib/odeon_uk/internal/parser/api/showtime.rb +81 -0
- data/lib/odeon_uk/internal/title_sanitizer.rb +3 -8
- data/lib/odeon_uk/performance.rb +34 -41
- data/lib/odeon_uk/version.rb +3 -3
- data/lib/odeon_uk.rb +16 -2
- data/odeon_uk.gemspec +10 -6
- data/test/fixtures/api/showtimes-824.json +66 -0
- data/test/fixtures/api/sites.json +32 -0
- data/test/lib/odeon_uk/cinema_test.rb +66 -215
- data/test/lib/odeon_uk/internal/api_response_test.rb +48 -29
- data/test/lib/odeon_uk/internal/fixture_shape_test.rb +14 -0
- data/test/lib/odeon_uk/internal/guest_token_test.rb +84 -0
- data/test/lib/odeon_uk/internal/parser/api/showtime_test.rb +77 -0
- data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +109 -89
- data/test/lib/odeon_uk/performance_test.rb +129 -146
- data/test/lib/odeon_uk/version_test.rb +3 -3
- data/test/live/api_shape_test.rb +19 -0
- data/test/live/integration_test.rb +21 -0
- data/test/live/live_helper.rb +11 -0
- data/test/package_test.rb +56 -0
- data/test/support/api_fixtures_helper.rb +5 -30
- data/test/support/api_shapes.rb +46 -0
- data/test/support/fake_api_response.rb +9 -6
- data/test/test_helper.rb +6 -3
- metadata +67 -89
- data/.travis.yml +0 -7
- data/lib/odeon_uk/internal/parser/api/film_lookup.rb +0 -34
- data/lib/odeon_uk/internal/parser/api/performance_day.rb +0 -92
- data/rake/fixture_creator.rb +0 -46
- data/test/fixtures/api/all_cinemas.plist +0 -0
- data/test/fixtures/api/app_init.plist +0 -0
- data/test/fixtures/api/film_times/71-14876.plist +0 -0
- data/test/fixtures/api/film_times/71-15408.plist +0 -0
- data/test/fixtures/api/film_times/71-15684.plist +0 -0
- data/test/fixtures/api/film_times/71-15866.plist +0 -0
- data/test/fixtures/api/film_times/71-15868.plist +0 -0
- data/test/fixtures/api/film_times/71-15961.plist +0 -0
- data/test/fixtures/api/film_times/71-15963.plist +0 -0
- data/test/fixtures/api/film_times/71-15965.plist +0 -0
- data/test/fixtures/api/film_times/71-15967.plist +0 -0
- data/test/fixtures/api/film_times/71-15969.plist +0 -0
- data/test/fixtures/api/film_times/71-15977.plist +0 -0
- data/test/fixtures/api/film_times/71-15980.plist +0 -0
- data/test/fixtures/api/film_times/71-16037.plist +0 -0
- data/test/fixtures/api/film_times/71-16136.plist +0 -0
- data/test/fixtures/api/film_times/71-16142.plist +0 -0
- data/test/fixtures/api/film_times/71-16143.plist +0 -0
- data/test/fixtures/api/film_times/71-16187.plist +0 -0
- data/test/fixtures/api/film_times/71-16369.plist +0 -0
- data/test/fixtures/api/film_times/71-16370.plist +0 -0
- data/test/fixtures/api/film_times/71-16374.plist +0 -0
- data/test/fixtures/api/film_times/71-16386.plist +0 -0
- data/test/fixtures/api/film_times/71-16387.plist +0 -0
- data/test/fixtures/api/film_times/71-16388.plist +0 -0
- data/test/fixtures/api/film_times/71-16438.plist +0 -0
- data/test/fixtures/api/film_times/71-16454.plist +0 -0
- data/test/fixtures/api/film_times/71-16457.plist +0 -0
- data/test/fixtures/api/film_times/71-16477.plist +0 -0
- data/test/fixtures/api/film_times/71-16478.plist +0 -0
- data/test/fixtures/api/film_times/71-16480.plist +0 -0
- data/test/fixtures/api/film_times/71-16482.plist +0 -0
- data/test/fixtures/api/film_times/71-16489.plist +0 -0
|
@@ -1,262 +1,113 @@
|
|
|
1
1
|
require_relative '../../test_helper'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let(:described_class) { OdeonUk::Cinema }
|
|
7
|
-
let(:api_response) { FakeApiResponse.new }
|
|
8
|
-
|
|
9
|
-
before { WebMock.disable_net_connect! }
|
|
10
|
-
after { WebMock.allow_net_connect! }
|
|
11
|
-
|
|
12
|
-
describe '.new(id)' do
|
|
13
|
-
subject { described_class.new(id) }
|
|
14
|
-
|
|
15
|
-
describe 'Brighton' do
|
|
16
|
-
let(:id) { 71 }
|
|
17
|
-
|
|
18
|
-
it 'returns a cinema with a string id' do
|
|
19
|
-
subject.must_be_instance_of(described_class)
|
|
20
|
-
subject.id.must_equal(id.to_s)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
3
|
+
class OdeonUk::CinemaTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@api_response = FakeApiResponse.new
|
|
23
6
|
end
|
|
24
7
|
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
def test_new_returns_cinema_with_string_id
|
|
9
|
+
cinema = OdeonUk::Cinema.new(824)
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
subject.must_be_instance_of(Array)
|
|
31
|
-
subject.each do |element|
|
|
32
|
-
element.must_be_instance_of(described_class)
|
|
33
|
-
end
|
|
34
|
-
subject.count.must_be :>, 100
|
|
35
|
-
end
|
|
36
|
-
end
|
|
11
|
+
assert_instance_of OdeonUk::Cinema, cinema
|
|
12
|
+
assert_equal '824', cinema.id
|
|
37
13
|
end
|
|
38
14
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
describe 'short address' do
|
|
43
|
-
let(:id) { 71 }
|
|
44
|
-
|
|
45
|
-
it 'returns the postcode' do
|
|
46
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
47
|
-
subject.must_equal(street_address: 'Kingswest',
|
|
48
|
-
extended_address: nil,
|
|
49
|
-
locality: 'Brighton',
|
|
50
|
-
region: nil,
|
|
51
|
-
postal_code: 'BN1 2RE',
|
|
52
|
-
country_name: 'United Kingdom')
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
15
|
+
def test_new_zero_pads_id
|
|
16
|
+
assert_equal '017', OdeonUk::Cinema.new(17).id
|
|
56
17
|
end
|
|
57
18
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
describe 'London address' do
|
|
62
|
-
let(:id) { 105 }
|
|
19
|
+
def test_all_returns_cinemas
|
|
20
|
+
with_fake_api do
|
|
21
|
+
cinemas = OdeonUk::Cinema.all
|
|
63
22
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
subject.must_equal(street_address: '24-26 Leicester Square',
|
|
67
|
-
extended_address: nil,
|
|
68
|
-
locality: 'London',
|
|
69
|
-
region: nil,
|
|
70
|
-
postal_code: 'WC2H 7JY',
|
|
71
|
-
country_name: 'United Kingdom')
|
|
72
|
-
end
|
|
73
|
-
end
|
|
23
|
+
assert(cinemas.all? { |cinema| cinema.instance_of?(OdeonUk::Cinema) })
|
|
24
|
+
assert_equal %w(017 153 824 955), cinemas.map(&:id)
|
|
74
25
|
end
|
|
75
26
|
end
|
|
76
27
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
28
|
+
def test_adr_has_no_postcode
|
|
29
|
+
expected = {
|
|
30
|
+
street_address: '3 Example Parade',
|
|
31
|
+
extended_address: 'Seafront',
|
|
32
|
+
locality: 'Brighton',
|
|
33
|
+
region: nil,
|
|
34
|
+
postal_code: nil,
|
|
35
|
+
country_name: 'United Kingdom'
|
|
36
|
+
}
|
|
81
37
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
subject.must_equal 'Odeon'
|
|
85
|
-
end
|
|
86
|
-
end
|
|
38
|
+
with_fake_api do
|
|
39
|
+
assert_equal expected, OdeonUk::Cinema.new(824).adr
|
|
87
40
|
end
|
|
88
41
|
end
|
|
89
42
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
43
|
+
def test_address_for_london
|
|
44
|
+
expected = {
|
|
45
|
+
street_address: '2 Example Square',
|
|
46
|
+
extended_address: '',
|
|
47
|
+
locality: 'London',
|
|
48
|
+
region: nil,
|
|
49
|
+
postal_code: nil,
|
|
50
|
+
country_name: 'United Kingdom'
|
|
51
|
+
}
|
|
94
52
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
subject.must_equal 'United Kingdom'
|
|
98
|
-
end
|
|
99
|
-
end
|
|
53
|
+
with_fake_api do
|
|
54
|
+
assert_equal expected, OdeonUk::Cinema.new(153).address
|
|
100
55
|
end
|
|
101
56
|
end
|
|
102
57
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
describe 'short address' do
|
|
106
|
-
let(:id) { 71 }
|
|
107
|
-
|
|
108
|
-
it 'returns the postcode' do
|
|
109
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
110
|
-
subject.must_equal ''
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
58
|
+
def test_brand
|
|
59
|
+
assert_equal 'Odeon', OdeonUk::Cinema.new(824).brand
|
|
114
60
|
end
|
|
115
61
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
it 'returns cinema name' do
|
|
122
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
123
|
-
subject.must_equal 'Odeon Brighton'
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
describe 'Leicester Square' do
|
|
129
|
-
let(:id) { 105 }
|
|
130
|
-
|
|
131
|
-
it 'returns cinema name' do
|
|
132
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
133
|
-
subject.must_equal 'Odeon Leicester Square'
|
|
134
|
-
end
|
|
135
|
-
end
|
|
62
|
+
def test_full_name
|
|
63
|
+
with_fake_api do
|
|
64
|
+
assert_equal 'Odeon Brighton', OdeonUk::Cinema.new(824).full_name
|
|
65
|
+
assert_equal 'Odeon Leicester Square', OdeonUk::Cinema.new(153).full_name
|
|
136
66
|
end
|
|
137
67
|
end
|
|
138
68
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
let(:id) { 71 }
|
|
143
|
-
|
|
144
|
-
it 'returns town name' do
|
|
145
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
146
|
-
subject.must_equal 'Brighton'
|
|
147
|
-
end
|
|
148
|
-
end
|
|
69
|
+
def test_name_removes_london_prefix
|
|
70
|
+
with_fake_api do
|
|
71
|
+
assert_equal 'Leicester Square', OdeonUk::Cinema.new(153).name
|
|
149
72
|
end
|
|
150
73
|
end
|
|
151
74
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
let(:id) { 71 }
|
|
156
|
-
|
|
157
|
-
it 'returns cinema name' do
|
|
158
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
159
|
-
subject.must_equal 'Brighton'
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
describe 'Leicester Square' do
|
|
165
|
-
let(:id) { 105 }
|
|
166
|
-
|
|
167
|
-
it 'returns cinema name' do
|
|
168
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
169
|
-
subject.must_equal 'Leicester Square'
|
|
170
|
-
end
|
|
171
|
-
end
|
|
75
|
+
def test_postal_code_is_empty
|
|
76
|
+
with_fake_api do
|
|
77
|
+
assert_equal '', OdeonUk::Cinema.new(824).postal_code
|
|
172
78
|
end
|
|
173
79
|
end
|
|
174
80
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
let(:id) { 71 }
|
|
179
|
-
|
|
180
|
-
it 'returns the postcode' do
|
|
181
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
182
|
-
subject.must_equal 'BN1 2RE'
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
describe 'short address (London)' do
|
|
188
|
-
let(:id) { 211 }
|
|
189
|
-
|
|
190
|
-
it 'returns the postcode' do
|
|
191
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
192
|
-
subject.must_equal 'SE1 8XR'
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
describe 'short address (extra London Postcode)' do
|
|
198
|
-
let(:id) { 105 }
|
|
199
|
-
|
|
200
|
-
it 'returns the postcode' do
|
|
201
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
202
|
-
subject.must_equal 'WC2H 7JY'
|
|
203
|
-
end
|
|
204
|
-
end
|
|
81
|
+
def test_slug
|
|
82
|
+
with_fake_api do
|
|
83
|
+
assert_equal 'odeon-brighton', OdeonUk::Cinema.new(824).slug
|
|
205
84
|
end
|
|
206
85
|
end
|
|
207
86
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
it 'returns the postcode' do
|
|
214
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
215
|
-
subject.must_equal ''
|
|
216
|
-
end
|
|
217
|
-
end
|
|
87
|
+
def test_url
|
|
88
|
+
with_fake_api do
|
|
89
|
+
assert_equal 'https://www.odeon.co.uk/cinemas/brighton/',
|
|
90
|
+
OdeonUk::Cinema.new(824).url
|
|
218
91
|
end
|
|
219
92
|
end
|
|
220
93
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let(:id) { 71 }
|
|
226
|
-
|
|
227
|
-
it 'returns first line of address' do
|
|
228
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
229
|
-
subject.must_equal 'odeon-brighton'
|
|
230
|
-
end
|
|
231
|
-
end
|
|
94
|
+
def test_url_keeps_london_prefix
|
|
95
|
+
with_fake_api do
|
|
96
|
+
assert_equal 'https://www.odeon.co.uk/cinemas/london-leicester-square/',
|
|
97
|
+
OdeonUk::Cinema.new(153).url
|
|
232
98
|
end
|
|
233
99
|
end
|
|
234
100
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
let(:id) { 71 }
|
|
240
|
-
|
|
241
|
-
it 'returns first line of address' do
|
|
242
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
243
|
-
subject.must_equal 'Kingswest'
|
|
244
|
-
end
|
|
245
|
-
end
|
|
101
|
+
def test_url_uses_slug_that_differs_from_name
|
|
102
|
+
with_fake_api do
|
|
103
|
+
assert_equal 'https://www.odeon.co.uk/cinemas/manchester-trafford-centre/',
|
|
104
|
+
OdeonUk::Cinema.new(955).url
|
|
246
105
|
end
|
|
247
106
|
end
|
|
248
107
|
|
|
249
|
-
|
|
250
|
-
subject { described_class.new(id).url }
|
|
108
|
+
private
|
|
251
109
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
it 'returns first line of address' do
|
|
256
|
-
OdeonUk::Internal::ApiResponse.stub :new, api_response do
|
|
257
|
-
subject.must_equal 'http://www.odeon.co.uk/cinemas/brighton/71/'
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
end
|
|
110
|
+
def with_fake_api(&block)
|
|
111
|
+
OdeonUk::Internal::ApiResponse.stub(:new, @api_response, &block)
|
|
261
112
|
end
|
|
262
113
|
end
|
|
@@ -1,47 +1,66 @@
|
|
|
1
1
|
require_relative '../../../test_helper'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
class OdeonUk::Internal::ApiResponseTest < Minitest::Test
|
|
4
4
|
include ApiFixturesHelper
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
BASE_URL = 'https://vwc.odeon.co.uk/WSVistaWebClient/ocapi/v1'.freeze
|
|
7
|
+
HEADERS = {
|
|
8
|
+
'Authorization' => 'Bearer guest-jwt', 'Connect-Region-Code' => 'UK'
|
|
9
|
+
}.freeze
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
def setup
|
|
12
|
+
OdeonUk.token = 'guest-jwt'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
OdeonUk.token = nil
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_sites
|
|
21
|
+
stub_request(:get, "#{BASE_URL}/sites")
|
|
22
|
+
.with(headers: HEADERS)
|
|
23
|
+
.to_return(status: 200, body: sites_json)
|
|
24
|
+
|
|
25
|
+
sites = api.sites
|
|
26
|
+
|
|
27
|
+
assert_kind_of Array, sites
|
|
28
|
+
assert_includes sites.first.keys, 'id'
|
|
29
|
+
end
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
def test_showtimes
|
|
32
|
+
stub_request(:get, "#{BASE_URL}/showtimes/by-business-date/2026-10-24")
|
|
33
|
+
.with(query: { 'siteIds' => '824' }, headers: HEADERS)
|
|
34
|
+
.to_return(status: 200, body: showtimes_json('824'))
|
|
13
35
|
|
|
14
|
-
|
|
36
|
+
showtimes = api.showtimes('824', Date.new(2026, 10, 24))
|
|
15
37
|
|
|
16
|
-
|
|
17
|
-
subject.class.must_equal Hash
|
|
18
|
-
subject.keys.must_equal(%w(films offers))
|
|
19
|
-
end
|
|
38
|
+
assert_equal %w(businessDate relatedData showtimes), showtimes.keys.sort
|
|
20
39
|
end
|
|
21
40
|
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
def test_missing_token_raises_before_request
|
|
42
|
+
OdeonUk.token = nil
|
|
43
|
+
|
|
44
|
+
assert_raises(OdeonUk::AuthenticationError) { api.sites }
|
|
45
|
+
end
|
|
24
46
|
|
|
25
|
-
|
|
47
|
+
def test_rejected_token_raises
|
|
48
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 401)
|
|
26
49
|
|
|
27
|
-
|
|
28
|
-
subject.class.must_equal Hash
|
|
29
|
-
subject.keys.must_equal(%w(sites))
|
|
30
|
-
end
|
|
50
|
+
assert_raises(OdeonUk::AuthenticationError) { api.sites }
|
|
31
51
|
end
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
def test_server_error_omits_body
|
|
54
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 500, body: 'secret')
|
|
55
|
+
|
|
56
|
+
error = assert_raises(RuntimeError) { api.sites }
|
|
57
|
+
|
|
58
|
+
assert_equal 'Vista HTTP error 500', error.message
|
|
59
|
+
end
|
|
35
60
|
|
|
36
|
-
|
|
37
|
-
stub_post('film-times',
|
|
38
|
-
{ s: '71', m: '15130' },
|
|
39
|
-
film_times_plist_random)
|
|
40
|
-
end
|
|
61
|
+
private
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
subject.each { |group| group.keys.must_equal(%w(date attributes)) }
|
|
45
|
-
end
|
|
63
|
+
def api
|
|
64
|
+
OdeonUk::Internal::ApiResponse.new
|
|
46
65
|
end
|
|
47
66
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
|
|
3
|
+
class FixtureShapeTest < Minitest::Test
|
|
4
|
+
include ApiFixturesHelper
|
|
5
|
+
include ApiShapes
|
|
6
|
+
|
|
7
|
+
def test_sites_fixture_matches_shape
|
|
8
|
+
assert_sites_shape(JSON.parse(sites_json))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_showtimes_fixture_matches_shape
|
|
12
|
+
assert_showtimes_shape(JSON.parse(showtimes_json('824')))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
class OdeonUk::Internal::GuestTokenTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@directory = Dir.mktmpdir('odeon-guest-token')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def teardown
|
|
11
|
+
FileUtils.remove_entry(@directory)
|
|
12
|
+
OdeonUk.token = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_fetch_returns_token_from_page_data
|
|
16
|
+
token = token_expiring_at(Time.now + 3600)
|
|
17
|
+
curl = fake_curl("#{page_with(token)}\n200")
|
|
18
|
+
|
|
19
|
+
assert_equal token, guest_token(curl).fetch
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_fetch_rejects_expired_token
|
|
23
|
+
curl = fake_curl("#{page_with(token_expiring_at(Time.now - 60))}\n200")
|
|
24
|
+
|
|
25
|
+
assert_raises(OdeonUk::AuthenticationError) { guest_token(curl).fetch }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_fetch_rejects_page_without_page_data
|
|
29
|
+
curl = fake_curl("<html></html>\n200")
|
|
30
|
+
|
|
31
|
+
assert_raises(OdeonUk::AuthenticationError) { guest_token(curl).fetch }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_fetch_rejects_redirect_to_queue
|
|
35
|
+
curl = fake_curl("\n302")
|
|
36
|
+
|
|
37
|
+
assert_raises(OdeonUk::AuthenticationError) { guest_token(curl).fetch }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_fetch_rejects_failed_command
|
|
41
|
+
curl = fake_curl('', exit_status: 7)
|
|
42
|
+
|
|
43
|
+
assert_raises(OdeonUk::AuthenticationError) { guest_token(curl).fetch }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_fetch_explains_missing_executable
|
|
47
|
+
error = assert_raises(OdeonUk::AuthenticationError) do
|
|
48
|
+
guest_token(File.join(@directory, 'missing')).fetch
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert_match(/curl-impersonate/, error.message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_acquire_token_sets_token
|
|
55
|
+
token = token_expiring_at(Time.now + 3600)
|
|
56
|
+
|
|
57
|
+
OdeonUk.acquire_token!(curl: fake_curl("#{page_with(token)}\n200"))
|
|
58
|
+
|
|
59
|
+
assert_equal token, OdeonUk.token
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def guest_token(curl)
|
|
65
|
+
OdeonUk::Internal::GuestToken.new(curl: curl)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def token_expiring_at(time)
|
|
69
|
+
payload = Base64.urlsafe_encode64(JSON.generate('exp' => time.to_i), padding: false)
|
|
70
|
+
"header.#{payload}.signature"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def page_with(token)
|
|
74
|
+
data = { 'api' => { 'apiUrl' => 'https://vwc.odeon.co.uk/WSVistaWebClient', 'authToken' => token } }
|
|
75
|
+
"<script>\n window.initialData = #{JSON.generate(data)};\n window.pageData = [];\n</script>"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fake_curl(output, exit_status: 0)
|
|
79
|
+
path = File.join(@directory, 'curl-impersonate')
|
|
80
|
+
File.write(path, "#!#{RbConfig.ruby}\nSTDOUT.write(#{output.dump})\nexit #{exit_status}\n")
|
|
81
|
+
File.chmod(0o755, path)
|
|
82
|
+
path
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require_relative '../../../../../test_helper'
|
|
2
|
+
|
|
3
|
+
class OdeonUk::Internal::Parser::Api::ShowtimeTest < Minitest::Test
|
|
4
|
+
FILMS = {
|
|
5
|
+
'HO1' => { 'title' => { 'text' => 'Jailer 2 (Tamil)' } },
|
|
6
|
+
'HO2' => { 'title' => { 'text' => 'Cine-Excess 2026: Scum Of The Earth' } },
|
|
7
|
+
'HO3' => { 'title' => { 'text' => 'From Dusk Till Dawn (1996)' } }
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
ATTRIBUTES = {
|
|
11
|
+
'1' => { 'shortName' => { 'text' => 'TamilA' }, 'name' => { 'text' => 'Tamil (Audio)' } },
|
|
12
|
+
'2' => { 'shortName' => { 'text' => 'EnglishSub' }, 'name' => { 'text' => 'English (Sub)' } },
|
|
13
|
+
'3' => { 'shortName' => { 'text' => 'IMAX' }, 'name' => { 'text' => 'IMAX' } },
|
|
14
|
+
'4' => { 'shortName' => { 'text' => 'OpenCapt' }, 'name' => { 'text' => 'Open Captioned' } },
|
|
15
|
+
'5' => { 'shortName' => { 'text' => 'Wheelchair' }, 'name' => { 'text' => 'Wheelchair Accessible' } }
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def test_to_hash
|
|
19
|
+
expected = {
|
|
20
|
+
booking_url: 'https://www.odeon.co.uk/ticketing/seat-picker/?showtimeId=824-1',
|
|
21
|
+
dimension: '2d',
|
|
22
|
+
festival: nil,
|
|
23
|
+
film_name: 'Jailer 2',
|
|
24
|
+
film_year: nil,
|
|
25
|
+
language: nil,
|
|
26
|
+
starting_at: Time.utc(2026, 10, 24, 17, 30),
|
|
27
|
+
variant: []
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
assert_equal expected, parse
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_3d_glasses
|
|
34
|
+
assert_equal '3d', parse(requires3d: true)[:dimension]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_audio_language
|
|
38
|
+
assert_equal 'Tamil', parse(attribute_ids: %w(1 2 3 5))[:language]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_maps_known_attributes_to_variants
|
|
42
|
+
assert_equal %w(subtitled imax), parse(attribute_ids: %w(1 2 3 5))[:variant]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_festival_moves_from_title_to_attribute
|
|
46
|
+
showtime = parse(film_id: 'HO2')
|
|
47
|
+
|
|
48
|
+
assert_equal Cinebase::Festival.new(name: 'Cine-Excess', year: 2026), showtime[:festival]
|
|
49
|
+
assert_equal 'Scum Of The Earth', showtime[:film_name]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_film_year_moves_from_title_to_attribute
|
|
53
|
+
showtime = parse(film_id: 'HO3')
|
|
54
|
+
|
|
55
|
+
assert_equal 1996, showtime[:film_year]
|
|
56
|
+
assert_equal 'From Dusk Till Dawn', showtime[:film_name]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_open_captions_and_subtitles_are_one_variant
|
|
60
|
+
assert_equal %w(subtitled), parse(attribute_ids: %w(2 4))[:variant]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def parse(attribute_ids: [], requires3d: false, film_id: 'HO1')
|
|
66
|
+
data = {
|
|
67
|
+
'id' => '824-1',
|
|
68
|
+
'filmId' => film_id,
|
|
69
|
+
'schedule' => { 'startsAt' => '2026-10-24T18:30:00+01:00' },
|
|
70
|
+
'attributeIds' => attribute_ids,
|
|
71
|
+
'requires3dGlasses' => requires3d
|
|
72
|
+
}
|
|
73
|
+
OdeonUk::Internal::Parser::Api::Showtime.new(
|
|
74
|
+
data, films: FILMS, attributes: ATTRIBUTES
|
|
75
|
+
).to_hash
|
|
76
|
+
end
|
|
77
|
+
end
|