cineworld_uk 4.1.0-x86_64-darwin
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 +7 -0
- data/.agents/setup +51 -0
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +18 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +225 -0
- data/COMM-LICENSE +22 -0
- data/Gemfile +4 -0
- data/LICENSE +617 -0
- data/README.md +225 -0
- data/Rakefile +59 -0
- data/cineworld_uk.gemspec +30 -0
- data/lib/cineworld_uk/cinema.rb +168 -0
- data/lib/cineworld_uk/internal/api_response.rb +116 -0
- data/lib/cineworld_uk/internal/guest_token.rb +73 -0
- data/lib/cineworld_uk/internal/parser/api/cinema_address.rb +81 -0
- data/lib/cineworld_uk/internal/parser/api/film.rb +48 -0
- data/lib/cineworld_uk/internal/parser/api/performance.rb +41 -0
- data/lib/cineworld_uk/internal/title_sanitizer.rb +68 -0
- data/lib/cineworld_uk/performance.rb +104 -0
- data/lib/cineworld_uk/version.rb +6 -0
- data/lib/cineworld_uk.rb +31 -0
- data/tasks/package.rake +84 -0
- data/test/fixtures/api/cinema-list.json +1 -0
- data/test/fixtures/api/dates-14.json +1 -0
- data/test/fixtures/api/performances-tomorrow-14.json +1 -0
- data/test/fixtures/api/vista-showtimes-brighton.json +1 -0
- data/test/lib/cineworld_uk/cinema_test.rb +249 -0
- data/test/lib/cineworld_uk/internal/api_response_test.rb +207 -0
- data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +94 -0
- data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +160 -0
- data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +62 -0
- data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +153 -0
- data/test/lib/cineworld_uk/performance_test.rb +183 -0
- data/test/lib/cineworld_uk/version_test.rb +7 -0
- data/test/live/integration_test.rb +51 -0
- data/test/package_test.rb +49 -0
- data/test/support/fixture_reader.rb +32 -0
- data/test/test_helper.rb +12 -0
- data/vendor/curl-impersonate/SOURCE +5 -0
- data/vendor/curl-impersonate/bin/curl-impersonate +0 -0
- data/vendor/curl-impersonate/licenses/LICENSE +21 -0
- data/vendor/curl-impersonate/licenses/LICENSE_BORINGSSL +238 -0
- data/vendor/curl-impersonate/licenses/LICENSE_BROTLI +19 -0
- data/vendor/curl-impersonate/licenses/LICENSE_CARES +24 -0
- data/vendor/curl-impersonate/licenses/LICENSE_CURL +22 -0
- data/vendor/curl-impersonate/licenses/LICENSE_NGHTTP2 +23 -0
- data/vendor/curl-impersonate/licenses/LICENSE_NGHTTP3 +22 -0
- data/vendor/curl-impersonate/licenses/LICENSE_NGTCP2 +22 -0
- data/vendor/curl-impersonate/licenses/LICENSE_ZLIB +22 -0
- data/vendor/curl-impersonate/licenses/LICENSE_ZSTD +30 -0
- metadata +233 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
# This block groups independent Minitest cases, not application logic.
|
|
7
|
+
describe "Vista retrieval" do # rubocop:disable Metrics/BlockLength
|
|
8
|
+
before do
|
|
9
|
+
CineworldUk.token = "test-guest-token"
|
|
10
|
+
[CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
|
|
11
|
+
klass.instance_variables.each do |name|
|
|
12
|
+
klass.remove_instance_variable(name)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
fixture = "../../../fixtures/api/vista-showtimes-brighton.json"
|
|
16
|
+
@capture = JSON.parse(File.read(File.expand_path(fixture, __dir__)))
|
|
17
|
+
@base = "https://digital-api.cineworld.co.uk/ocapi/v1/"
|
|
18
|
+
sites = @capture.fetch("relatedData").fetch("sites")
|
|
19
|
+
stub_request(:get, @base + "sites").with(
|
|
20
|
+
headers: {
|
|
21
|
+
"Authorization" => "Bearer test-guest-token",
|
|
22
|
+
"Connect-Region-Code" => "GB"
|
|
23
|
+
}
|
|
24
|
+
).to_return(body: JSON.generate("sites" => sites))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after do
|
|
28
|
+
CineworldUk.token = nil
|
|
29
|
+
[CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
|
|
30
|
+
klass.instance_variables.each do |name|
|
|
31
|
+
klass.remove_instance_variable(name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stub_day(day, body)
|
|
37
|
+
stub_request(:get, @base + "showtimes/by-business-date/#{day}").with(
|
|
38
|
+
query: { "siteIds" => "014" },
|
|
39
|
+
headers: { "Authorization" => "Bearer test-guest-token",
|
|
40
|
+
"Connect-Region-Code" => "GB" }
|
|
41
|
+
).to_return(body: JSON.generate(body))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "retrieves cinemas and advertised session times from Vista" do
|
|
45
|
+
stub_day("2026-10-14", @capture)
|
|
46
|
+
cinema = CineworldUk::Cinema.all.first
|
|
47
|
+
_(cinema.id).must_equal 14
|
|
48
|
+
_(cinema.name).must_equal "Brighton"
|
|
49
|
+
_(cinema.postal_code).must_equal "BN2 5UF"
|
|
50
|
+
_(cinema.street_address).must_equal "Brighton Marina Village"
|
|
51
|
+
assert_requested :get, @base + "sites", times: 1
|
|
52
|
+
performances = CineworldUk::Performance.at(
|
|
53
|
+
14, from: Date.new(2026, 10, 14), to: Date.new(2026, 10, 14)
|
|
54
|
+
)
|
|
55
|
+
_(performances.size).must_equal 17
|
|
56
|
+
first = performances.first
|
|
57
|
+
_(first.film_name).must_equal "Other Mommy"
|
|
58
|
+
_(first.starting_at).must_equal Time.utc(2026, 10, 14, 14, 40)
|
|
59
|
+
_(first.booking_url).must_equal(
|
|
60
|
+
"https://web.cineworld.co.uk/order/showtimes/014-165507/seats"
|
|
61
|
+
)
|
|
62
|
+
_(first.variant).must_include "audio_described"
|
|
63
|
+
_(first.dimension).must_equal "2d"
|
|
64
|
+
assert_requested :get, @base + "sites", times: 1
|
|
65
|
+
assert_requested :get, @base + "showtimes/by-business-date/2026-10-14",
|
|
66
|
+
query: { "siteIds" => "014" }, times: 1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "keeps format session-specific and includes the last requested day" do
|
|
70
|
+
@capture["showtimes"] =
|
|
71
|
+
[@capture["showtimes"].first,
|
|
72
|
+
Marshal.load(Marshal.dump(@capture["showtimes"].first))]
|
|
73
|
+
@capture["showtimes"][1]["id"] = "014-second"
|
|
74
|
+
@capture["showtimes"][1]["requires3dGlasses"] = true
|
|
75
|
+
@capture["showtimes"][1]["schedule"]["startsAt"] =
|
|
76
|
+
"2026-10-25T16:40:00+00:00"
|
|
77
|
+
stub_day("2026-10-24",
|
|
78
|
+
"showtimes" => [],
|
|
79
|
+
"relatedData" => { "films" => [], "attributes" => [],
|
|
80
|
+
"sites" => [] })
|
|
81
|
+
stub_day("2026-10-25", @capture)
|
|
82
|
+
results = CineworldUk::Performance.at("014", from: Date.new(2026, 10, 24),
|
|
83
|
+
to: Date.new(2026, 10, 25))
|
|
84
|
+
_(results.map(&:dimension)).must_equal %w[2d 3d]
|
|
85
|
+
_(results.last.starting_at).must_equal Time.utc(2026, 10, 25, 16, 40)
|
|
86
|
+
_(results.first.cinema_name).must_equal "Brighton"
|
|
87
|
+
assert_not_requested :get, @base + "sites"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "rejects missing and rejected tokens without exposing credentials" do
|
|
91
|
+
CineworldUk.token = nil
|
|
92
|
+
assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
|
|
93
|
+
CineworldUk.token = "expired-secret"
|
|
94
|
+
stub_request(:get, @base + "sites").to_return(status: 401,
|
|
95
|
+
body: "expired-secret")
|
|
96
|
+
error = assert_raises(CineworldUk::AuthenticationError) do
|
|
97
|
+
CineworldUk::Cinema.all
|
|
98
|
+
end
|
|
99
|
+
refute_includes error.message, "expired-secret"
|
|
100
|
+
CineworldUk.token = "replacement"
|
|
101
|
+
stub_request(:get, @base + "sites").with(
|
|
102
|
+
headers: { "Authorization" => "Bearer replacement" }
|
|
103
|
+
).to_return(body: '{"sites":[]}')
|
|
104
|
+
_(CineworldUk::Cinema.all).must_equal []
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "defaults to fourteen days and rejects reversed ranges" do
|
|
108
|
+
day = Date.new(2026, 10, 14)
|
|
109
|
+
(day..day + 13).each do |date|
|
|
110
|
+
stub_day(date.iso8601,
|
|
111
|
+
"showtimes" => [],
|
|
112
|
+
"relatedData" => { "films" => [], "attributes" => [],
|
|
113
|
+
"sites" => [] })
|
|
114
|
+
end
|
|
115
|
+
_(CineworldUk::Performance.at(14, from: day)).must_equal []
|
|
116
|
+
assert_requested :get, %r{showtimes/by-business-date/}, times: 14
|
|
117
|
+
assert_raises(ArgumentError) do
|
|
118
|
+
CineworldUk::Performance.at(14, from: day, to: day - 1)
|
|
119
|
+
end
|
|
120
|
+
assert_not_requested :get, @base + "sites"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "does not follow redirects or hide forbidden responses" do
|
|
124
|
+
[302, 403].each do |status|
|
|
125
|
+
stub_request(:get, @base + "sites").to_return(
|
|
126
|
+
status: status, headers: { "Location" => "https://example.com/" }
|
|
127
|
+
)
|
|
128
|
+
error = assert_raises(RuntimeError) { CineworldUk::Cinema.all }
|
|
129
|
+
assert_includes error.message, status.to_s
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe "Guest token acquisition" do
|
|
135
|
+
before do
|
|
136
|
+
@directory = Dir.mktmpdir("cineworld token ")
|
|
137
|
+
@curl = File.join(@directory, "curl-impersonate")
|
|
138
|
+
CineworldUk.token = "existing-token"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
after do
|
|
142
|
+
FileUtils.remove_entry(@directory)
|
|
143
|
+
CineworldUk.token = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def serve_token_page(html, status: 200, exit_code: 0)
|
|
147
|
+
output = "#{html}\n#{status}"
|
|
148
|
+
File.write(@curl, "#!#{RbConfig.ruby}\nSTDOUT.write(#{output.dump})\nexit #{exit_code}\n")
|
|
149
|
+
File.chmod(0o700, @curl)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def token_page(expires)
|
|
153
|
+
payload = Base64.urlsafe_encode64(JSON.generate("exp" => expires), padding: false)
|
|
154
|
+
token = "header.#{payload}.signature"
|
|
155
|
+
data = { "props" => { "pageProps" => { "environment" => { "gasToken" => token } } } }
|
|
156
|
+
["<script type='application/json' id='__NEXT_DATA__'>#{JSON.generate(data)}</script>", token]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "sets and returns the guest JWT from either a 200 or 404 page" do
|
|
160
|
+
html, token = token_page(Time.now.to_i + 3600)
|
|
161
|
+
[200, 404].each do |status|
|
|
162
|
+
serve_token_page(html, status: status)
|
|
163
|
+
_(CineworldUk.acquire_token!(curl: @curl)).must_equal token
|
|
164
|
+
_(CineworldUk.token).must_equal token
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "rejects expired tokens including the expiry boundary without replacing the current token" do
|
|
169
|
+
now = Time.at(1_800_000_000)
|
|
170
|
+
Time.stub(:now, now) do
|
|
171
|
+
[-1, 0, 1].each do |offset|
|
|
172
|
+
html, token = token_page(now.to_i + offset)
|
|
173
|
+
serve_token_page(html)
|
|
174
|
+
if offset.positive?
|
|
175
|
+
_(CineworldUk.acquire_token!(curl: @curl)).must_equal token
|
|
176
|
+
else
|
|
177
|
+
assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
|
|
178
|
+
_(CineworldUk.token).must_equal "existing-token"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "reports blocked, redirected, missing and malformed page data without exposing the response" do
|
|
185
|
+
html, token = token_page(Time.now.to_i + 3600)
|
|
186
|
+
invalid_payload = Base64.urlsafe_encode64("broken-secret", padding: false)
|
|
187
|
+
[[html, 403], [html, 302], ["Cloudflare block", 200],
|
|
188
|
+
["<script id='__NEXT_DATA__'>{</script>", 200],
|
|
189
|
+
["<script id='__NEXT_DATA__'>{}</script>", 200],
|
|
190
|
+
[html.sub(token, "broken-secret"), 200],
|
|
191
|
+
[html.sub(token, "header.#{invalid_payload}.signature"), 200]].each do |body, status|
|
|
192
|
+
serve_token_page(body, status: status)
|
|
193
|
+
error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
|
|
194
|
+
refute_includes error.full_message, token
|
|
195
|
+
refute_includes error.full_message, "broken-secret"
|
|
196
|
+
_(CineworldUk.token).must_equal "existing-token"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "reports a missing executable or curl failure as an authentication error" do
|
|
201
|
+
error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
|
|
202
|
+
assert_includes error.message, "Install curl-impersonate"
|
|
203
|
+
serve_token_page("", status: 0, exit_code: 28)
|
|
204
|
+
error = assert_raises(CineworldUk::AuthenticationError) { CineworldUk.acquire_token!(curl: @curl) }
|
|
205
|
+
assert_includes error.message, "curl exit 28"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative '../../../../../test_helper'
|
|
2
|
+
require_relative '../../../../../support/fixture_reader'
|
|
3
|
+
|
|
4
|
+
describe CineworldUk::Internal::Parser::Api::CinemaAddress do
|
|
5
|
+
include Support::FixtureReader
|
|
6
|
+
|
|
7
|
+
let(:described_class) { CineworldUk::Internal::Parser::Api::CinemaAddress }
|
|
8
|
+
let(:api_response) { Minitest::Mock.new }
|
|
9
|
+
|
|
10
|
+
before { WebMock.disable_net_connect! }
|
|
11
|
+
after { WebMock.allow_net_connect! }
|
|
12
|
+
|
|
13
|
+
describe '#to_hash' do
|
|
14
|
+
subject { described_class.new(id).to_hash }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
api_response.expect(:cinema_list, cinema_list_json)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe 'passed simple (Brighton)' do
|
|
21
|
+
let(:id) { 14 }
|
|
22
|
+
|
|
23
|
+
it 'returns address hash' do
|
|
24
|
+
CineworldUk::Internal::ApiResponse.stub :new, api_response do
|
|
25
|
+
_(subject).must_equal(street_address: 'Brighton Marina Village',
|
|
26
|
+
extended_address: nil,
|
|
27
|
+
locality: 'Brighton',
|
|
28
|
+
region: nil,
|
|
29
|
+
postal_code: 'BN2 5UF',
|
|
30
|
+
country: 'United Kingdom')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe 'passed three line (NEC)' do
|
|
36
|
+
let(:id) { 90 }
|
|
37
|
+
|
|
38
|
+
it 'returns address hash' do
|
|
39
|
+
CineworldUk::Internal::ApiResponse.stub :new, api_response do
|
|
40
|
+
_(subject).must_equal(street_address: 'Resorts World',
|
|
41
|
+
extended_address: 'Pendigo Way',
|
|
42
|
+
locality: 'Birmingham',
|
|
43
|
+
region: nil,
|
|
44
|
+
postal_code: 'B40 1PU',
|
|
45
|
+
country: 'United Kingdom')
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe 'passed three line (Edinburgh)' do
|
|
51
|
+
let(:id) { 37 }
|
|
52
|
+
|
|
53
|
+
it 'returns address hash' do
|
|
54
|
+
CineworldUk::Internal::ApiResponse.stub :new, api_response do
|
|
55
|
+
_(subject).must_equal(street_address: 'Fountain Park',
|
|
56
|
+
extended_address: '130/3 Dundee Street',
|
|
57
|
+
locality: 'Edinburgh',
|
|
58
|
+
region: nil,
|
|
59
|
+
postal_code: 'EH11 1AF',
|
|
60
|
+
country: 'United Kingdom')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'passed two line (Leicester Square)' do
|
|
66
|
+
let(:id) { 103 }
|
|
67
|
+
|
|
68
|
+
it 'returns address hash' do
|
|
69
|
+
CineworldUk::Internal::ApiResponse.stub :new, api_response do
|
|
70
|
+
_(subject).must_equal(street_address: '5-6 Leicester Square',
|
|
71
|
+
extended_address: nil,
|
|
72
|
+
locality: 'London',
|
|
73
|
+
region: 'London',
|
|
74
|
+
postal_code: 'WC2H 7NA',
|
|
75
|
+
country: 'United Kingdom')
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# describe 'passed non-existant api' do
|
|
81
|
+
# let(:id) { 0 }
|
|
82
|
+
#
|
|
83
|
+
# it 'returns hash of nils' do
|
|
84
|
+
# _(subject).must_be_instance_of(Hash)
|
|
85
|
+
# _(subject).must_equal(street_address: nil,
|
|
86
|
+
# extended_address: nil,
|
|
87
|
+
# locality: "not an address",
|
|
88
|
+
# region: nil,
|
|
89
|
+
# postal_code: "not an address",
|
|
90
|
+
# country: "United Kingdom")
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require_relative '../../../../../test_helper'
|
|
2
|
+
require_relative '../../../../../support/fixture_reader'
|
|
3
|
+
|
|
4
|
+
describe CineworldUk::Internal::Parser::Api::Film do
|
|
5
|
+
include Support::FixtureReader
|
|
6
|
+
|
|
7
|
+
let(:described_class) { CineworldUk::Internal::Parser::Api::Film }
|
|
8
|
+
let(:data) { random_film }
|
|
9
|
+
|
|
10
|
+
before { WebMock.disable_net_connect! }
|
|
11
|
+
after { WebMock.allow_net_connect! }
|
|
12
|
+
|
|
13
|
+
describe '#id' do
|
|
14
|
+
subject { described_class.new(data).id }
|
|
15
|
+
|
|
16
|
+
it 'is a string' do
|
|
17
|
+
_(subject).must_be_instance_of(String)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'is the film id from the data' do
|
|
21
|
+
_(subject).must_equal(data['id'])
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe '#dimension' do
|
|
26
|
+
subject { described_class.new(data).dimension }
|
|
27
|
+
|
|
28
|
+
it 'is a string' do
|
|
29
|
+
_(subject).must_be_instance_of(String)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'is 2d or 3d' do
|
|
33
|
+
_(subject).must_match(/[23]d/)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe 'attributeIds includes "2d"' do
|
|
37
|
+
let(:data) { random_film.merge('attributeIds' => ['2d']) }
|
|
38
|
+
|
|
39
|
+
it 'is 2d' do
|
|
40
|
+
_(subject).must_equal('2d')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe 'attributeIds includes "3d"' do
|
|
45
|
+
let(:data) { random_film.merge('attributeIds' => ['3d']) }
|
|
46
|
+
|
|
47
|
+
it 'is 3d' do
|
|
48
|
+
_(subject).must_equal('3d')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe 'attributeIds with imax but not 3d' do
|
|
53
|
+
let(:data) { random_film.merge('attributeIds' => ['imax', '2d']) }
|
|
54
|
+
|
|
55
|
+
it 'is 2d' do
|
|
56
|
+
_(subject).must_equal('2d')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe 'attributeIds with imax and 3d' do
|
|
61
|
+
let(:data) { random_film.merge('attributeIds' => ['imax', '3d']) }
|
|
62
|
+
|
|
63
|
+
it 'is 3d' do
|
|
64
|
+
_(subject).must_equal('3d')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#name' do
|
|
70
|
+
subject { described_class.new(data).name }
|
|
71
|
+
|
|
72
|
+
it 'is a string' do
|
|
73
|
+
_(subject).must_be_instance_of(String)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe 'film name' do
|
|
77
|
+
let(:data) do
|
|
78
|
+
random_film.merge('name' => 'Test Film Name')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'is the film name' do
|
|
82
|
+
_(subject).must_equal('Test Film Name')
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe 'film name is unsanitized' do
|
|
87
|
+
let(:data) { random_film.merge('name' => 'ROH: Something') }
|
|
88
|
+
|
|
89
|
+
it 'is sanitized' do
|
|
90
|
+
_(subject).must_equal('Royal Opera House: Something')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe '#variant' do
|
|
96
|
+
subject { described_class.new(data).variant }
|
|
97
|
+
|
|
98
|
+
it 'is a sorted array of strings' do
|
|
99
|
+
_(subject).must_be_instance_of(Array)
|
|
100
|
+
subject.each { |element| _(element).must_be_instance_of(String) }
|
|
101
|
+
_(subject.sort).must_equal(subject)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe 'attributeIds includes "junior"' do
|
|
105
|
+
let(:data) do
|
|
106
|
+
random_film.merge('attributeIds' => ['junior'])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'includes "kids"' do
|
|
110
|
+
_(subject).must_include('kids')
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe 'attributeIds includes "imax"' do
|
|
115
|
+
let(:data) do
|
|
116
|
+
random_film.merge('attributeIds' => ['imax', '3d'])
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'includes "imax"' do
|
|
120
|
+
_(subject).must_include('imax')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe 'attributeIds includes "unlimited-screening"' do
|
|
125
|
+
let(:data) do
|
|
126
|
+
random_film.merge('attributeIds' => ['unlimited-screening'])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'includes "members"' do
|
|
130
|
+
_(subject).must_include('members')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe 'attributeIds includes "qa"' do
|
|
135
|
+
let(:data) do
|
|
136
|
+
random_film.merge('attributeIds' => ['qa'])
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'includes "q&a"' do
|
|
140
|
+
_(subject).must_include('q&a')
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe 'combination' do
|
|
145
|
+
let(:data) do
|
|
146
|
+
random_film.merge('attributeIds' => ['imax', 'unlimited-screening'])
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'includes "members"' do
|
|
150
|
+
_(subject).must_include('imax', 'members')
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
def random_film
|
|
158
|
+
JSON.parse(performances_tomorrow_json(14))['body']['films'].sample
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require_relative '../../../../../test_helper'
|
|
2
|
+
require_relative '../../../../../support/fixture_reader'
|
|
3
|
+
|
|
4
|
+
describe CineworldUk::Internal::Parser::Api::Performance do
|
|
5
|
+
include Support::FixtureReader
|
|
6
|
+
|
|
7
|
+
let(:described_class) { CineworldUk::Internal::Parser::Api::Performance }
|
|
8
|
+
let(:data) { random_performance }
|
|
9
|
+
|
|
10
|
+
before { WebMock.disable_net_connect! }
|
|
11
|
+
after { WebMock.allow_net_connect! }
|
|
12
|
+
|
|
13
|
+
describe '#booking_url' do
|
|
14
|
+
subject { described_class.new(data).booking_url }
|
|
15
|
+
|
|
16
|
+
it 'should be a url on the cineworld website' do
|
|
17
|
+
_(subject).must_match(%r{cineworld.co.uk})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#film_id' do
|
|
22
|
+
subject { described_class.new(data).film_id }
|
|
23
|
+
|
|
24
|
+
it 'should be a string' do
|
|
25
|
+
_(subject).must_be_instance_of(String)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#starting_at' do
|
|
30
|
+
subject { described_class.new(data).starting_at }
|
|
31
|
+
|
|
32
|
+
it 'should be a time' do
|
|
33
|
+
_(subject).must_be_instance_of(Time)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#variant' do
|
|
38
|
+
subject { described_class.new(data).variant }
|
|
39
|
+
|
|
40
|
+
describe 'has "audio-described" in attributeIds' do
|
|
41
|
+
let(:data) { random_performance.merge('attributeIds' => ['audio-described']) }
|
|
42
|
+
|
|
43
|
+
it 'includes "audio_described"' do
|
|
44
|
+
_(subject).must_include('audio_described')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'has "subbed" in attributeIds' do
|
|
49
|
+
let(:data) { random_performance.merge('attributeIds' => ['subbed']) }
|
|
50
|
+
|
|
51
|
+
it 'includes "subtitled"' do
|
|
52
|
+
_(subject).must_include('subtitled')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def random_performance
|
|
60
|
+
JSON.parse(performances_tomorrow_json(14))['body']['events'].sample
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
|
|
3
|
+
describe CineworldUk::Internal::TitleSanitizer do
|
|
4
|
+
let(:described_class) { CineworldUk::Internal::TitleSanitizer }
|
|
5
|
+
|
|
6
|
+
describe '#sanitized' do
|
|
7
|
+
subject { described_class.new(title).sanitized }
|
|
8
|
+
|
|
9
|
+
describe 'with 2d in title' do
|
|
10
|
+
let(:title) { 'Iron Man 3 2D' }
|
|
11
|
+
|
|
12
|
+
it 'removes dimension' do
|
|
13
|
+
_(subject).must_equal('Iron Man 3')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe 'with 3d in title' do
|
|
18
|
+
let(:title) { 'Iron Man 3 3d' }
|
|
19
|
+
|
|
20
|
+
it 'removes dimension' do
|
|
21
|
+
_(subject).must_equal('Iron Man 3')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe 'in hindi' do
|
|
26
|
+
let(:title) { 'Bang Bang! (Hindi)' }
|
|
27
|
+
|
|
28
|
+
it 'removes language' do
|
|
29
|
+
_(subject).must_equal('Bang Bang!')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe 'Unlimited Screening' do
|
|
34
|
+
let(:title) { 'Nightcrawler - Unlimited Screening' }
|
|
35
|
+
|
|
36
|
+
it 'removes prefix' do
|
|
37
|
+
_(subject).must_equal('Nightcrawler')
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe 'Austism screening' do
|
|
42
|
+
let(:title) { 'Autism Friendly Screening: Dolphin Tale 2' }
|
|
43
|
+
|
|
44
|
+
it 'removes prefix' do
|
|
45
|
+
_(subject).must_equal('Dolphin Tale 2')
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe 'Bolshoi screening' do
|
|
50
|
+
let(:title) { 'Bolshoi Ballet: The Legend of Love' }
|
|
51
|
+
|
|
52
|
+
it 'removes prefix' do
|
|
53
|
+
_(subject).must_equal('Bolshoi Ballet: The Legend of Love')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe 'English National Opera screening' do
|
|
58
|
+
let(:title) { 'ENO: La Traviata ' }
|
|
59
|
+
|
|
60
|
+
it 'removes prefix' do
|
|
61
|
+
_(subject).must_equal('English National Opera: La Traviata')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe 'with IMAX' do
|
|
66
|
+
let(:title) { '(IMAX) Fury' }
|
|
67
|
+
|
|
68
|
+
it 'removes prefix' do
|
|
69
|
+
_(subject).must_equal('Fury')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'Met Opera screening' do
|
|
74
|
+
let(:title) { 'MET Opera - Le Nozze Di Figaro' }
|
|
75
|
+
|
|
76
|
+
it 'removes prefix' do
|
|
77
|
+
_(subject).must_equal('Met Opera: Le Nozze Di Figaro')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe 'NT Live screening' do
|
|
82
|
+
let(:title) { 'NT Live Encore: Frankenstein (starring Benedict Cumberbatch)' }
|
|
83
|
+
|
|
84
|
+
it 'removes prefix' do
|
|
85
|
+
_(subject).must_equal('National Theatre: Frankenstein (starring Benedict Cumberbatch)')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe 'National Theatre screening' do
|
|
90
|
+
let(:title) { 'NT Live: Treasure Island' }
|
|
91
|
+
|
|
92
|
+
it 'removes prefix' do
|
|
93
|
+
_(subject).must_equal('National Theatre: Treasure Island')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe 'National Theatre Encore screening' do
|
|
98
|
+
let(:title) { 'NT Live: A Streetcar Named Desire (Young Vic) - Encore' }
|
|
99
|
+
|
|
100
|
+
it 'removes prefix' do
|
|
101
|
+
_(subject).must_equal('National Theatre: A Streetcar Named Desire (Young Vic)')
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe 'ROH screening' do
|
|
106
|
+
let(:title) { 'ROH - I Due Foscari' }
|
|
107
|
+
|
|
108
|
+
it 'removes prefix' do
|
|
109
|
+
_(subject).must_equal('Royal Opera House: I Due Foscari')
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe 'RSC screening' do
|
|
114
|
+
let(:title) { "RSC Live: Love's Labour's Lost" }
|
|
115
|
+
|
|
116
|
+
it 'removes prefix' do
|
|
117
|
+
_(subject).must_equal("Royal Shakespeare Company: Love's Labour's Lost")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe 'SciScreen screening' do
|
|
122
|
+
let(:title) { 'SciScreen: Dallas Buyers Club' }
|
|
123
|
+
|
|
124
|
+
it 'removes prefix' do
|
|
125
|
+
_(subject).must_equal('Dallas Buyers Club')
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
describe 'special screening' do
|
|
130
|
+
let(:title) { 'Billy Elliot The Musical Live - Special Performance' }
|
|
131
|
+
|
|
132
|
+
it 'removes prefix' do
|
|
133
|
+
_(subject).must_equal('Billy Elliot The Musical Live')
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe 'Take 2 screening' do
|
|
138
|
+
let(:title) { 'Take 2 - Boyhood' }
|
|
139
|
+
|
|
140
|
+
it 'removes prefix' do
|
|
141
|
+
_(subject).must_equal('Boyhood')
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe 'singalong and movies for juniors' do
|
|
146
|
+
let(:title) { 'Frozen Sing-Along - Movies For Juniors' }
|
|
147
|
+
|
|
148
|
+
it 'removes prefix' do
|
|
149
|
+
_(subject).must_equal('Frozen')
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|