picturehouse_uk 6.0.0-aarch64-linux-gnu
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/.github/workflows/ci.yml +47 -0
- data/.github/workflows/release.yml +73 -0
- data/.gitignore +18 -0
- data/.ruby-version +1 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +213 -0
- data/COMM-LICENSE +22 -0
- data/Gemfile +4 -0
- data/LICENSE +617 -0
- data/README.md +175 -0
- data/Rakefile +37 -0
- data/lib/picturehouse_uk/cinema.rb +127 -0
- data/lib/picturehouse_uk/internal/api_response.rb +35 -0
- data/lib/picturehouse_uk/internal/guest_token.rb +28 -0
- data/lib/picturehouse_uk/internal/parser/api/showtime.rb +70 -0
- data/lib/picturehouse_uk/internal/title_sanitizer.rb +78 -0
- data/lib/picturehouse_uk/performance.rb +80 -0
- data/lib/picturehouse_uk/version.rb +6 -0
- data/lib/picturehouse_uk.rb +30 -0
- data/picturehouse_uk.gemspec +29 -0
- data/test/fixtures/api/showtimes-019.json +62 -0
- data/test/fixtures/api/sites.json +40 -0
- data/test/lib/picturehouse_uk/cinema_test.rb +85 -0
- data/test/lib/picturehouse_uk/internal/api_response_test.rb +52 -0
- data/test/lib/picturehouse_uk/internal/fixture_shape_test.rb +14 -0
- data/test/lib/picturehouse_uk/internal/guest_token_test.rb +71 -0
- data/test/lib/picturehouse_uk/internal/parser/api/showtime_test.rb +59 -0
- data/test/lib/picturehouse_uk/internal/title_sanitizer_test.rb +243 -0
- data/test/lib/picturehouse_uk/performance_test.rb +111 -0
- data/test/lib/picturehouse_uk/version_test.rb +7 -0
- data/test/live/api_shape_test.rb +19 -0
- data/test/live/integration_test.rb +27 -0
- data/test/live/live_helper.rb +11 -0
- data/test/package_test.rb +56 -0
- data/test/support/api_fixtures_helper.rb +17 -0
- data/test/support/api_shapes.rb +48 -0
- data/test/support/fake_api_response.rb +20 -0
- data/test/test_helper.rb +20 -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_LIBIDN2_GPL2 +339 -0
- data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_GPL3 +35 -0
- data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_LGPL3 +165 -0
- data/vendor/curl-impersonate/licenses/LICENSE_LIBIDN2_UNICODE +91 -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 +225 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'picturehouse_uk/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'picturehouse_uk'
|
|
7
|
+
spec.version = PicturehouseUk::VERSION
|
|
8
|
+
spec.authors = ['Andy Croll']
|
|
9
|
+
spec.email = ['andy@goodscary.com']
|
|
10
|
+
spec.description = 'Pull movie & cinema information from the Picturehouse Vista API'
|
|
11
|
+
spec.summary = "It's a scraper, but a nice one"
|
|
12
|
+
spec.homepage = 'https://github.com/andycroll/picturehouse_uk'
|
|
13
|
+
spec.licenses = %w(AGPL MIT)
|
|
14
|
+
|
|
15
|
+
spec.files = `git ls-files`.split("\n")
|
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
spec.require_paths = ['lib']
|
|
19
|
+
|
|
20
|
+
spec.add_development_dependency 'base64'
|
|
21
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
22
|
+
spec.add_development_dependency 'minitest-mock'
|
|
23
|
+
spec.add_development_dependency 'minitest-reporters'
|
|
24
|
+
spec.add_development_dependency 'rake'
|
|
25
|
+
spec.add_development_dependency 'simplecov', '< 1'
|
|
26
|
+
spec.add_development_dependency 'webmock'
|
|
27
|
+
|
|
28
|
+
spec.add_runtime_dependency 'cinebase', '~> 5.4'
|
|
29
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"businessDate": "2026-10-24",
|
|
3
|
+
"showtimes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "019-1001",
|
|
6
|
+
"filmId": "FILM-1",
|
|
7
|
+
"schedule": { "startsAt": "2026-10-24T10:30:00+01:00" },
|
|
8
|
+
"attributeIds": ["A-2D", "A-AD"],
|
|
9
|
+
"requires3dGlasses": false
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "019-1002",
|
|
13
|
+
"filmId": "FILM-1",
|
|
14
|
+
"schedule": { "startsAt": "2026-10-24T14:00:00+01:00" },
|
|
15
|
+
"attributeIds": ["A-SILVER"],
|
|
16
|
+
"requires3dGlasses": true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "019-1003",
|
|
20
|
+
"filmId": "FILM-2",
|
|
21
|
+
"schedule": { "startsAt": "2026-10-24T18:00:00+01:00" },
|
|
22
|
+
"attributeIds": ["A-SUB", "A-QA"],
|
|
23
|
+
"requires3dGlasses": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "019-1004",
|
|
27
|
+
"filmId": "FILM-3",
|
|
28
|
+
"schedule": { "startsAt": "2026-10-24T11:00:00+01:00" },
|
|
29
|
+
"attributeIds": ["A-BABY", "A-TODDLER"],
|
|
30
|
+
"requires3dGlasses": false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "019-1005",
|
|
34
|
+
"filmId": "FILM-4",
|
|
35
|
+
"schedule": { "startsAt": "2026-10-25T00:15:00+01:00" },
|
|
36
|
+
"attributeIds": ["A-ARTS", "A-HOH"],
|
|
37
|
+
"requires3dGlasses": false
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"relatedData": {
|
|
41
|
+
"films": [
|
|
42
|
+
{ "id": "FILM-1", "title": { "text": "The Example Film" } },
|
|
43
|
+
{ "id": "FILM-2", "title": { "text": "Imaginary Picture (1996)" } },
|
|
44
|
+
{ "id": "FILM-3", "title": { "text": "Pretend Pups + director Q&A" } },
|
|
45
|
+
{ "id": "FILM-4", "title": { "text": "CFF: Made Up Matinee (70mm)" } }
|
|
46
|
+
],
|
|
47
|
+
"attributes": [
|
|
48
|
+
{ "id": "A-2D", "shortName": { "text": "2D" }, "name": { "text": "2D" } },
|
|
49
|
+
{ "id": "A-AD", "shortName": { "text": "Audio D" }, "name": { "text": "AD" } },
|
|
50
|
+
{ "id": "A-SILVER", "shortName": { "text": "Silver Scr" }, "name": { "text": "Silver Screen" } },
|
|
51
|
+
{ "id": "A-SUB", "shortName": { "text": "Sub Cinema" }, "name": { "text": "Subtitled" } },
|
|
52
|
+
{ "id": "A-QA", "shortName": { "text": "Q+A" }, "name": { "text": "+ Q&A " } },
|
|
53
|
+
{ "id": "A-BABY", "shortName": { "text": "Watch Baby" }, "name": { "text": "Watch with Baby" } },
|
|
54
|
+
{ "id": "A-TODDLER", "shortName": { "text": "Toddler Ti" }, "name": { "text": "Toddler Time" } },
|
|
55
|
+
{ "id": "A-ARTS", "shortName": { "text": "Screen Art" }, "name": { "text": "Screen Arts" } },
|
|
56
|
+
{ "id": "A-HOH", "shortName": { "text": "HOHSub" }, "name": { "text": "Subtitled (Captioned)" } }
|
|
57
|
+
],
|
|
58
|
+
"sites": [
|
|
59
|
+
{ "id": "019", "name": { "text": "Duke's at Komedia" } }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sites": [
|
|
3
|
+
{
|
|
4
|
+
"id": "002",
|
|
5
|
+
"name": { "text": "Arts Picturehouse Cambridge" },
|
|
6
|
+
"contactDetails": {
|
|
7
|
+
"addressV2": {
|
|
8
|
+
"line1": { "text": "1 Example Street" },
|
|
9
|
+
"line2": { "text": "Cambridge" },
|
|
10
|
+
"line3": { "text": "CB2 0ZZ" },
|
|
11
|
+
"line4": null
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "013",
|
|
17
|
+
"name": { "text": "Picturehouse at FACT Liverpool" },
|
|
18
|
+
"contactDetails": {
|
|
19
|
+
"addressV2": {
|
|
20
|
+
"line1": { "text": "2 Example Street" },
|
|
21
|
+
"line2": { "text": "Ropewalks" },
|
|
22
|
+
"line3": { "text": "Liverpool" },
|
|
23
|
+
"line4": { "text": "L1 0ZZ" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "019",
|
|
29
|
+
"name": { "text": "Duke's at Komedia" },
|
|
30
|
+
"contactDetails": {
|
|
31
|
+
"addressV2": {
|
|
32
|
+
"line1": { "text": "3 Example Street" },
|
|
33
|
+
"line2": { "text": "Brighton" },
|
|
34
|
+
"line3": { "text": "BN1 0ZZ" },
|
|
35
|
+
"line4": null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require_relative '../../test_helper'
|
|
2
|
+
|
|
3
|
+
class PicturehouseUk::CinemaTest < Minitest::Test
|
|
4
|
+
def setup
|
|
5
|
+
@api_response = FakeApiResponse.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_new_zero_pads_id
|
|
9
|
+
assert_equal '019', PicturehouseUk::Cinema.new(19).id
|
|
10
|
+
assert_equal '019', PicturehouseUk::Cinema.new('019').id
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_all_returns_cinemas
|
|
14
|
+
with_fake_api do
|
|
15
|
+
cinemas = PicturehouseUk::Cinema.all
|
|
16
|
+
|
|
17
|
+
assert(cinemas.all? { |cinema| cinema.instance_of?(PicturehouseUk::Cinema) })
|
|
18
|
+
assert_equal %w(002 013 019), cinemas.map(&:id)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_adr_with_postcode_on_third_line
|
|
23
|
+
expected = {
|
|
24
|
+
street_address: '3 Example Street',
|
|
25
|
+
extended_address: nil,
|
|
26
|
+
locality: 'Brighton',
|
|
27
|
+
region: nil,
|
|
28
|
+
postal_code: 'BN1 0ZZ',
|
|
29
|
+
country_name: 'United Kingdom'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
with_fake_api do
|
|
33
|
+
assert_equal expected, PicturehouseUk::Cinema.new(19).adr
|
|
34
|
+
assert_equal expected, PicturehouseUk::Cinema.new(19).address
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_adr_with_extended_address
|
|
39
|
+
with_fake_api do
|
|
40
|
+
adr = PicturehouseUk::Cinema.new(13).adr
|
|
41
|
+
|
|
42
|
+
assert_equal '2 Example Street', adr[:street_address]
|
|
43
|
+
assert_equal 'Ropewalks', adr[:extended_address]
|
|
44
|
+
assert_equal 'Liverpool', adr[:locality]
|
|
45
|
+
assert_equal 'L1 0ZZ', adr[:postal_code]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_brand
|
|
50
|
+
assert_equal 'Picturehouse', PicturehouseUk::Cinema.new(19).brand
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_name_and_full_name
|
|
54
|
+
with_fake_api do
|
|
55
|
+
assert_equal "Duke's at Komedia", PicturehouseUk::Cinema.new(19).name
|
|
56
|
+
assert_equal "Duke's at Komedia", PicturehouseUk::Cinema.new(19).full_name
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_slug
|
|
61
|
+
with_fake_api do
|
|
62
|
+
assert_equal 'dukes-at-komedia', PicturehouseUk::Cinema.new(19).slug
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_url
|
|
67
|
+
with_fake_api do
|
|
68
|
+
assert_equal 'https://www.picturehouses.com/cinema/duke-s-at-komedia',
|
|
69
|
+
PicturehouseUk::Cinema.new(19).url
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_url_uses_slug_that_differs_from_name
|
|
74
|
+
with_fake_api do
|
|
75
|
+
assert_equal 'https://www.picturehouses.com/cinema/picturehouse-at-fact',
|
|
76
|
+
PicturehouseUk::Cinema.new(13).url
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def with_fake_api(&block)
|
|
83
|
+
PicturehouseUk::Internal::ApiResponse.stub(:new, @api_response, &block)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
|
|
3
|
+
class PicturehouseUk::Internal::ApiResponseTest < Minitest::Test
|
|
4
|
+
include ApiFixturesHelper
|
|
5
|
+
|
|
6
|
+
BASE_URL = 'https://digital-api.picturehouses.com/ocapi/v1'.freeze
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
PicturehouseUk.token = 'guest-jwt'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
PicturehouseUk.token = nil
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_sites_sends_token_without_region
|
|
18
|
+
stub_request(:get, "#{BASE_URL}/sites")
|
|
19
|
+
.with { |request| request.headers['Authorization'] == 'Bearer guest-jwt' && !request.headers.key?('Connect-Region-Code') }
|
|
20
|
+
.to_return(status: 200, body: sites_json)
|
|
21
|
+
|
|
22
|
+
assert_equal %w(002 013 019), api.sites.map { |site| site['id'] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_showtimes
|
|
26
|
+
stub_request(:get, "#{BASE_URL}/showtimes/by-business-date/2026-10-24")
|
|
27
|
+
.with(query: { 'siteIds' => '019' })
|
|
28
|
+
.to_return(status: 200, body: showtimes_json('019'))
|
|
29
|
+
|
|
30
|
+
showtimes = api.showtimes('019', Date.new(2026, 10, 24))
|
|
31
|
+
|
|
32
|
+
assert_equal %w(businessDate relatedData showtimes), showtimes.keys.sort
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_missing_token_raises_before_request
|
|
36
|
+
PicturehouseUk.token = nil
|
|
37
|
+
|
|
38
|
+
assert_raises(PicturehouseUk::AuthenticationError) { api.sites }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_rejected_token_raises
|
|
42
|
+
stub_request(:get, "#{BASE_URL}/sites").to_return(status: 401)
|
|
43
|
+
|
|
44
|
+
assert_raises(PicturehouseUk::AuthenticationError) { api.sites }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def api
|
|
50
|
+
PicturehouseUk::Internal::ApiResponse.new
|
|
51
|
+
end
|
|
52
|
+
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('019')))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
|
|
5
|
+
class PicturehouseUk::Internal::GuestTokenTest < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
@directory = Dir.mktmpdir('picturehouse-guest-token')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def teardown
|
|
11
|
+
FileUtils.remove_entry(@directory)
|
|
12
|
+
PicturehouseUk.token = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_fetch_returns_token_from_200_or_404_page
|
|
16
|
+
token = token_expiring_at(Time.now + 3600)
|
|
17
|
+
|
|
18
|
+
%w(200 404).each do |status|
|
|
19
|
+
assert_equal token, guest_token(fake_curl("#{page_with(token)}\n#{status}")).fetch
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_fetch_rejects_expired_token
|
|
24
|
+
curl = fake_curl("#{page_with(token_expiring_at(Time.now - 60))}\n200")
|
|
25
|
+
|
|
26
|
+
assert_raises(PicturehouseUk::AuthenticationError) { guest_token(curl).fetch }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_fetch_rejects_page_without_page_data
|
|
30
|
+
curl = fake_curl("<html></html>\n200")
|
|
31
|
+
|
|
32
|
+
assert_raises(PicturehouseUk::AuthenticationError) { guest_token(curl).fetch }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_fetch_rejects_blocked_page
|
|
36
|
+
curl = fake_curl("#{page_with(token_expiring_at(Time.now + 3600))}\n403")
|
|
37
|
+
|
|
38
|
+
assert_raises(PicturehouseUk::AuthenticationError) { guest_token(curl).fetch }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_acquire_token_sets_token
|
|
42
|
+
token = token_expiring_at(Time.now + 3600)
|
|
43
|
+
|
|
44
|
+
PicturehouseUk.acquire_token!(curl: fake_curl("#{page_with(token)}\n200"))
|
|
45
|
+
|
|
46
|
+
assert_equal token, PicturehouseUk.token
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def guest_token(curl)
|
|
52
|
+
PicturehouseUk::Internal::GuestToken.new(curl: curl)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def token_expiring_at(time)
|
|
56
|
+
payload = Base64.urlsafe_encode64(JSON.generate('exp' => time.to_i), padding: false)
|
|
57
|
+
"header.#{payload}.signature"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def page_with(token)
|
|
61
|
+
data = { 'props' => { 'pageProps' => { 'environment' => { 'gasToken' => token } } } }
|
|
62
|
+
%(<script id="__NEXT_DATA__" type="application/json">#{JSON.generate(data)}</script>)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fake_curl(output)
|
|
66
|
+
path = File.join(@directory, 'curl-impersonate')
|
|
67
|
+
File.write(path, "#!#{RbConfig.ruby}\nSTDOUT.write(#{output.dump})\n")
|
|
68
|
+
File.chmod(0o755, path)
|
|
69
|
+
path
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative '../../../../../test_helper'
|
|
2
|
+
|
|
3
|
+
class PicturehouseUk::Internal::Parser::Api::ShowtimeTest < Minitest::Test
|
|
4
|
+
FILMS = {
|
|
5
|
+
'HO1' => { 'title' => { 'text' => 'Sense and Sensibility (2026)' } },
|
|
6
|
+
'HO2' => { 'title' => { 'text' => 'CFF: Rocket Girl' } }
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
ATTRIBUTES = {
|
|
10
|
+
'1' => { 'shortName' => { 'text' => 'Kids\' Club' }, 'name' => { 'text' => 'Kids\' Club' } },
|
|
11
|
+
'2' => { 'shortName' => { 'text' => 'Q+A' }, 'name' => { 'text' => '+ Q&A ' } },
|
|
12
|
+
'3' => { 'shortName' => { 'text' => 'Relaxed' }, 'name' => { 'text' => 'Relaxed' } },
|
|
13
|
+
'4' => { 'shortName' => { 'text' => 'Laser' }, 'name' => { 'text' => 'Laser Projection' } }
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def test_to_hash
|
|
17
|
+
expected = {
|
|
18
|
+
booking_url: 'https://web.picturehouses.com/order/showtimes/019-1/seats',
|
|
19
|
+
dimension: '2d',
|
|
20
|
+
festival: nil,
|
|
21
|
+
film_name: 'Sense and Sensibility',
|
|
22
|
+
film_year: 2026,
|
|
23
|
+
starting_at: Time.utc(2026, 10, 24, 17, 30),
|
|
24
|
+
variant: []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
assert_equal expected, parse
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_3d_glasses
|
|
31
|
+
assert_equal '3d', parse(requires3d: true)[:dimension]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_maps_attribute_names_to_variants
|
|
35
|
+
assert_equal %w(kids q&a relaxed), parse(attribute_ids: %w(1 2 3 4))[:variant]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_festival
|
|
39
|
+
showtime = parse(film_id: 'HO2')
|
|
40
|
+
|
|
41
|
+
assert_equal Cinebase::Festival.new(name: 'CFF', year: 2026), showtime[:festival]
|
|
42
|
+
assert_equal 'Rocket Girl', showtime[:film_name]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def parse(attribute_ids: [], requires3d: false, film_id: 'HO1')
|
|
48
|
+
data = {
|
|
49
|
+
'id' => '019-1',
|
|
50
|
+
'filmId' => film_id,
|
|
51
|
+
'schedule' => { 'startsAt' => '2026-10-24T18:30:00+01:00' },
|
|
52
|
+
'attributeIds' => attribute_ids,
|
|
53
|
+
'requires3dGlasses' => requires3d
|
|
54
|
+
}
|
|
55
|
+
PicturehouseUk::Internal::Parser::Api::Showtime.new(
|
|
56
|
+
data, films: FILMS, attributes: ATTRIBUTES
|
|
57
|
+
).to_hash
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
require_relative '../../../test_helper'
|
|
2
|
+
|
|
3
|
+
class PicturehouseUkInternalTitleSanitizerTest < Minitest::Test
|
|
4
|
+
def test_sanitized_with_html_amp_in_title
|
|
5
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Withnail & I')
|
|
6
|
+
assert_equal 'Withnail & I', sanitizer.sanitized
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_sanitized_with_2d_in_title
|
|
10
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Iron Man 3 2D')
|
|
11
|
+
assert_equal 'Iron Man 3', sanitizer.sanitized
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_sanitized_with_2d_in_parentheses_in_title
|
|
15
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('ParaNorman (2D)')
|
|
16
|
+
assert_equal 'ParaNorman', sanitizer.sanitized
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_sanitized_with_film_release_year
|
|
20
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Miracle on 34th Street (1947)')
|
|
21
|
+
assert_equal 'Miracle on 34th Street', sanitizer.sanitized
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_sanitized_with_3d_in_title
|
|
25
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Iron Man 3 3d')
|
|
26
|
+
assert_equal 'Iron Man 3', sanitizer.sanitized
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_sanitized_with_autism_friendly
|
|
30
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Tarzan - Autism-Friendly Screening')
|
|
31
|
+
assert_equal 'Tarzan', sanitizer.sanitized
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_sanitized_with_no_cert_in_title
|
|
35
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Iron Man 3 [NO CERT]')
|
|
36
|
+
assert_equal 'Iron Man 3', sanitizer.sanitized
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_sanitized_with_blank_cert_in_title
|
|
40
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Iron Man 3 []')
|
|
41
|
+
assert_equal 'Iron Man 3', sanitizer.sanitized
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_sanitized_with_cert_in_title
|
|
45
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Beauty and the Beast 2D [U]')
|
|
46
|
+
assert_equal 'Beauty and the Beast', sanitizer.sanitized
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_sanitized_with_kids_club_in_title
|
|
50
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('KIDS CLUB Escape from Planet Earth')
|
|
51
|
+
assert_equal 'Escape from Planet Earth', sanitizer.sanitized
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_sanitized_with_discover_tue_in_title
|
|
55
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('DISCOVER TUE All this Mayhem')
|
|
56
|
+
assert_equal 'All this Mayhem', sanitizer.sanitized
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_sanitized_with_discover_tuesday_in_title
|
|
60
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Discover Tuesday: Manakamana [U]')
|
|
61
|
+
assert_equal 'Manakamana', sanitizer.sanitized
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_sanitized_with_singalong
|
|
65
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Frozen Singalong [PG]')
|
|
66
|
+
assert_equal 'Frozen', sanitizer.sanitized
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_sanitized_with_free_screening
|
|
70
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('FREE Screening - Withnail & I')
|
|
71
|
+
assert_equal 'Withnail & I', sanitizer.sanitized
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_sanitized_with_re_in_title
|
|
75
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Beauty and the Beast 2D (Re) [U]')
|
|
76
|
+
assert_equal 'Beauty and the Beast', sanitizer.sanitized
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_sanitized_with_q_and_a
|
|
80
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Hidden Colors 3: The Rules of Racism + Q&A')
|
|
81
|
+
assert_equal 'Hidden Colors 3: The Rules of Racism', sanitizer.sanitized
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_sanitized_with_large_q_and_a
|
|
85
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Luna Plus Q&A with Director and Artist Dave McKean')
|
|
86
|
+
assert_equal 'Luna', sanitizer.sanitized
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_sanitized_with_panel_discussion
|
|
90
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('A Dangerous Game + Panel Discussion')
|
|
91
|
+
assert_equal 'A Dangerous Game', sanitizer.sanitized
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_sanitized_with_cinemania_film_festival
|
|
95
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Cinemania: The Imposter')
|
|
96
|
+
assert_equal 'The Imposter', sanitizer.sanitized
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_sanitized_with_at_komedia_with_spaces
|
|
100
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Gone Girl @ Komedia [18]')
|
|
101
|
+
assert_equal 'Gone Girl', sanitizer.sanitized
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_sanitized_with_at_komedia_no_spaces
|
|
105
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Paddington@Komedia')
|
|
106
|
+
assert_equal 'Paddington', sanitizer.sanitized
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_sanitized_with_imax
|
|
110
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Dinosaurs Alive! IMAX')
|
|
111
|
+
assert_equal 'Dinosaurs Alive!', sanitizer.sanitized
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_sanitized_with_re_issue
|
|
115
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Metropolis (Re-issue)')
|
|
116
|
+
assert_equal 'Metropolis', sanitizer.sanitized
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_sanitized_with_hoh_subtitled
|
|
120
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new("'71 *HOH Subtitled*")
|
|
121
|
+
assert_equal "'71", sanitizer.sanitized
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_sanitized_with_big_scream
|
|
125
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Big Scream - Effie Gray')
|
|
126
|
+
assert_equal 'Effie Gray', sanitizer.sanitized
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_sanitized_with_kids_club
|
|
130
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new("ParaNorman (2D) - Kids' Club")
|
|
131
|
+
assert_equal 'ParaNorman', sanitizer.sanitized
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_sanitized_with_reminiscence
|
|
135
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new("Rosemary's Baby - Reminiscence")
|
|
136
|
+
assert_equal "Rosemary's Baby", sanitizer.sanitized
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_sanitized_with_bolshoi_screening
|
|
140
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Bolshoi: Spartacus [NO CERT]')
|
|
141
|
+
assert_equal 'Bolshoi: Spartacus', sanitizer.sanitized
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_sanitized_with_bolshoi_with_captured
|
|
145
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Bolshoi: La Bayadere (Captured Live in 2012)')
|
|
146
|
+
assert_equal 'Bolshoi: La Bayadere', sanitizer.sanitized
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_sanitized_with_glyndebourne
|
|
150
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Glyndebourne Tour 2014: The Cunning Little Vixen [CERT TBC]')
|
|
151
|
+
assert_equal 'Glyndebourne Tour 2014: The Cunning Little Vixen', sanitizer.sanitized
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_sanitized_with_met_opera_screening
|
|
155
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Met. Opera: Macbeth [NO CERT]')
|
|
156
|
+
assert_equal 'Met Opera: Macbeth', sanitizer.sanitized
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_sanitized_with_met_opera_screening_v2
|
|
160
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Met Opera: Rusalka [AS LIVE: 12A]')
|
|
161
|
+
assert_equal 'Met Opera: Rusalka', sanitizer.sanitized
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_sanitized_with_met_opera_screening_v3
|
|
165
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Met Opera:Le Nozze di Figaro [AS LIVE:12A]')
|
|
166
|
+
assert_equal 'Met Opera: Le Nozze di Figaro', sanitizer.sanitized
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def test_sanitized_with_nt_live_screening
|
|
170
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('National Theatre: Hamlet [PG]')
|
|
171
|
+
assert_equal 'NT Live: Hamlet', sanitizer.sanitized
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def test_sanitized_with_ourscreen
|
|
175
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('ourscreen: Northern Soul [15]')
|
|
176
|
+
assert_equal 'Northern Soul', sanitizer.sanitized
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_sanitized_with_re_release
|
|
180
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('To Kill A Mockingbird (Re) [PG]')
|
|
181
|
+
assert_equal 'To Kill A Mockingbird', sanitizer.sanitized
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_sanitized_with_re_release_with_year
|
|
185
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Withnail & I (re: 2014) [15]')
|
|
186
|
+
assert_equal 'Withnail & I', sanitizer.sanitized
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def test_sanitized_with_toddler_time
|
|
190
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('TODDLER TIME: Timmy Time Programme 15')
|
|
191
|
+
assert_equal 'Timmy Time Programme 15', sanitizer.sanitized
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_sanitized_with_royal_opera_house_screening
|
|
195
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Royal Shakespeare Company: Richard II [NO CERT]')
|
|
196
|
+
assert_equal 'Royal Shakespeare Company: Richard II', sanitizer.sanitized
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def test_sanitized_with_royal_opera_house_encore_screening
|
|
200
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('ROH Encore: Swan Lake')
|
|
201
|
+
assert_equal 'Royal Opera House: Swan Lake', sanitizer.sanitized
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def test_sanitized_with_parent_and_babies_screening
|
|
205
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Paddington - Parents & Babies')
|
|
206
|
+
assert_equal 'Paddington', sanitizer.sanitized
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def test_sanitized_with_subtitled_screening
|
|
210
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Subtitled: The Hobbit: The Battle of the Five Armies')
|
|
211
|
+
assert_equal 'The Hobbit: The Battle of the Five Armies', sanitizer.sanitized
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def test_sanitized_with_live
|
|
215
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('English National Opera: Carmen (Live)')
|
|
216
|
+
assert_equal 'English National Opera: Carmen', sanitizer.sanitized
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_sanitized_removes_tbc_certificate
|
|
220
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('The Living Room with Gavin Clark [TBC]')
|
|
221
|
+
assert_equal 'The Living Room with Gavin Clark', sanitizer.sanitized
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def test_sanitized_removes_na_certificate
|
|
225
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Woman In Gold [N/A]')
|
|
226
|
+
assert_equal 'Woman In Gold', sanitizer.sanitized
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def test_sanitized_removes_theatre_designation
|
|
230
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Maxine Peake As Hamlet (Theatre)')
|
|
231
|
+
assert_equal 'Maxine Peake As Hamlet', sanitizer.sanitized
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_sanitized_removes_r18_certificate
|
|
235
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new("Dr Sketchy's Anti-Art School [R18]")
|
|
236
|
+
assert_equal "Dr Sketchy's Anti-Art School", sanitizer.sanitized
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_sanitized_with_bolshoi_reorders_title
|
|
240
|
+
sanitizer = PicturehouseUk::Internal::TitleSanitizer.new('Spartacus (Bolshoi Ballet)')
|
|
241
|
+
assert_equal 'Bolshoi Ballet: Spartacus', sanitizer.sanitized
|
|
242
|
+
end
|
|
243
|
+
end
|