odeon_uk 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +43 -0
  3. data/.gitignore +1 -1
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +28 -0
  6. data/README.md +90 -21
  7. data/Rakefile +14 -15
  8. data/lib/odeon_uk/cinema.rb +40 -22
  9. data/lib/odeon_uk/internal/api_response.rb +19 -39
  10. data/lib/odeon_uk/internal/guest_token.rb +23 -0
  11. data/lib/odeon_uk/internal/parser/api/showtime.rb +80 -0
  12. data/lib/odeon_uk/internal/title_sanitizer.rb +3 -8
  13. data/lib/odeon_uk/performance.rb +34 -41
  14. data/lib/odeon_uk/version.rb +3 -3
  15. data/lib/odeon_uk.rb +16 -2
  16. data/odeon_uk.gemspec +10 -6
  17. data/test/fixtures/api/showtimes-824.json +66 -0
  18. data/test/fixtures/api/sites.json +32 -0
  19. data/test/lib/odeon_uk/cinema_test.rb +66 -215
  20. data/test/lib/odeon_uk/internal/api_response_test.rb +48 -29
  21. data/test/lib/odeon_uk/internal/fixture_shape_test.rb +14 -0
  22. data/test/lib/odeon_uk/internal/guest_token_test.rb +84 -0
  23. data/test/lib/odeon_uk/internal/parser/api/showtime_test.rb +68 -0
  24. data/test/lib/odeon_uk/internal/title_sanitizer_test.rb +109 -89
  25. data/test/lib/odeon_uk/performance_test.rb +124 -146
  26. data/test/lib/odeon_uk/version_test.rb +3 -3
  27. data/test/live/api_shape_test.rb +19 -0
  28. data/test/live/integration_test.rb +21 -0
  29. data/test/live/live_helper.rb +11 -0
  30. data/test/package_test.rb +56 -0
  31. data/test/support/api_fixtures_helper.rb +5 -30
  32. data/test/support/api_shapes.rb +46 -0
  33. data/test/support/fake_api_response.rb +9 -6
  34. data/test/test_helper.rb +6 -3
  35. metadata +66 -89
  36. data/.travis.yml +0 -7
  37. data/lib/odeon_uk/internal/parser/api/film_lookup.rb +0 -34
  38. data/lib/odeon_uk/internal/parser/api/performance_day.rb +0 -92
  39. data/rake/fixture_creator.rb +0 -46
  40. data/test/fixtures/api/all_cinemas.plist +0 -0
  41. data/test/fixtures/api/app_init.plist +0 -0
  42. data/test/fixtures/api/film_times/71-14876.plist +0 -0
  43. data/test/fixtures/api/film_times/71-15408.plist +0 -0
  44. data/test/fixtures/api/film_times/71-15684.plist +0 -0
  45. data/test/fixtures/api/film_times/71-15866.plist +0 -0
  46. data/test/fixtures/api/film_times/71-15868.plist +0 -0
  47. data/test/fixtures/api/film_times/71-15961.plist +0 -0
  48. data/test/fixtures/api/film_times/71-15963.plist +0 -0
  49. data/test/fixtures/api/film_times/71-15965.plist +0 -0
  50. data/test/fixtures/api/film_times/71-15967.plist +0 -0
  51. data/test/fixtures/api/film_times/71-15969.plist +0 -0
  52. data/test/fixtures/api/film_times/71-15977.plist +0 -0
  53. data/test/fixtures/api/film_times/71-15980.plist +0 -0
  54. data/test/fixtures/api/film_times/71-16037.plist +0 -0
  55. data/test/fixtures/api/film_times/71-16136.plist +0 -0
  56. data/test/fixtures/api/film_times/71-16142.plist +0 -0
  57. data/test/fixtures/api/film_times/71-16143.plist +0 -0
  58. data/test/fixtures/api/film_times/71-16187.plist +0 -0
  59. data/test/fixtures/api/film_times/71-16369.plist +0 -0
  60. data/test/fixtures/api/film_times/71-16370.plist +0 -0
  61. data/test/fixtures/api/film_times/71-16374.plist +0 -0
  62. data/test/fixtures/api/film_times/71-16386.plist +0 -0
  63. data/test/fixtures/api/film_times/71-16387.plist +0 -0
  64. data/test/fixtures/api/film_times/71-16388.plist +0 -0
  65. data/test/fixtures/api/film_times/71-16438.plist +0 -0
  66. data/test/fixtures/api/film_times/71-16454.plist +0 -0
  67. data/test/fixtures/api/film_times/71-16457.plist +0 -0
  68. data/test/fixtures/api/film_times/71-16477.plist +0 -0
  69. data/test/fixtures/api/film_times/71-16478.plist +0 -0
  70. data/test/fixtures/api/film_times/71-16480.plist +0 -0
  71. data/test/fixtures/api/film_times/71-16482.plist +0 -0
  72. data/test/fixtures/api/film_times/71-16489.plist +0 -0
@@ -1,47 +1,66 @@
1
1
  require_relative '../../../test_helper'
2
2
 
3
- describe OdeonUk::Internal::ApiResponse do
3
+ class OdeonUk::Internal::ApiResponseTest < Minitest::Test
4
4
  include ApiFixturesHelper
5
5
 
6
- let(:described_class) { OdeonUk::Internal::ApiResponse }
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
- before { WebMock.disable_net_connect! }
9
- after { WebMock.allow_net_connect! }
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
- describe '#app_init' do
12
- subject { described_class.new.app_init }
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
- before { stub_post('app-init', nil, app_init_plist) }
36
+ showtimes = api.showtimes('824', Date.new(2026, 10, 24))
15
37
 
16
- it 'returns a hash' do
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
- describe '#all_cinemas' do
23
- subject { described_class.new.all_cinemas }
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
- before { stub_post('all-cinemas', nil, all_cinemas_plist) }
47
+ def test_rejected_token_raises
48
+ stub_request(:get, "#{BASE_URL}/sites").to_return(status: 401)
26
49
 
27
- it 'returns a hash' do
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
- describe '#showtimes(id)' do
34
- subject { described_class.new.film_times(71, 15_130) }
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
- before do
37
- stub_post('film-times',
38
- { s: '71', m: '15130' },
39
- film_times_plist_random)
40
- end
61
+ private
41
62
 
42
- it 'returns an array' do
43
- subject.class.must_equal Array
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,68 @@
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
+ }.freeze
8
+
9
+ ATTRIBUTES = {
10
+ '1' => { 'shortName' => { 'text' => 'TamilA' }, 'name' => { 'text' => 'Tamil (Audio)' } },
11
+ '2' => { 'shortName' => { 'text' => 'EnglishSub' }, 'name' => { 'text' => 'English (Sub)' } },
12
+ '3' => { 'shortName' => { 'text' => 'IMAX' }, 'name' => { 'text' => 'IMAX' } },
13
+ '4' => { 'shortName' => { 'text' => 'OpenCapt' }, 'name' => { 'text' => 'Open Captioned' } },
14
+ '5' => { 'shortName' => { 'text' => 'Wheelchair' }, 'name' => { 'text' => 'Wheelchair Accessible' } }
15
+ }.freeze
16
+
17
+ def test_to_hash
18
+ expected = {
19
+ booking_url: 'https://www.odeon.co.uk/ticketing/seat-picker/?showtimeId=824-1',
20
+ dimension: '2d',
21
+ festival: nil,
22
+ film_name: 'Jailer 2',
23
+ language: nil,
24
+ starting_at: Time.utc(2026, 10, 24, 17, 30),
25
+ variant: []
26
+ }
27
+
28
+ assert_equal expected, parse
29
+ end
30
+
31
+ def test_3d_glasses
32
+ assert_equal '3d', parse(requires3d: true)[:dimension]
33
+ end
34
+
35
+ def test_audio_language
36
+ assert_equal 'Tamil', parse(attribute_ids: %w(1 2 3 5))[:language]
37
+ end
38
+
39
+ def test_maps_known_attributes_to_variants
40
+ assert_equal %w(subtitled imax), parse(attribute_ids: %w(1 2 3 5))[:variant]
41
+ end
42
+
43
+ def test_festival_moves_from_title_to_attribute
44
+ showtime = parse(film_id: 'HO2')
45
+
46
+ assert_equal Cinebase::Festival.new(name: 'Cine-Excess', year: 2026), showtime[:festival]
47
+ assert_equal 'Scum Of The Earth', showtime[:film_name]
48
+ end
49
+
50
+ def test_open_captions_and_subtitles_are_one_variant
51
+ assert_equal %w(subtitled), parse(attribute_ids: %w(2 4))[:variant]
52
+ end
53
+
54
+ private
55
+
56
+ def parse(attribute_ids: [], requires3d: false, film_id: 'HO1')
57
+ data = {
58
+ 'id' => '824-1',
59
+ 'filmId' => film_id,
60
+ 'schedule' => { 'startsAt' => '2026-10-24T18:30:00+01:00' },
61
+ 'attributeIds' => attribute_ids,
62
+ 'requires3dGlasses' => requires3d
63
+ }
64
+ OdeonUk::Internal::Parser::Api::Showtime.new(
65
+ data, films: FILMS, attributes: ATTRIBUTES
66
+ ).to_hash
67
+ end
68
+ end
@@ -1,121 +1,141 @@
1
1
  require_relative '../../../test_helper'
2
2
 
3
- describe OdeonUk::Internal::TitleSanitizer do
4
- let(:described_class) { OdeonUk::Internal::TitleSanitizer }
3
+ class OdeonUk::Internal::TitleSanitizerTest < Minitest::Test
4
+ def test_removes_2d
5
+ assert_sanitized 'Iron Man 3', 'Iron Man 3 2D'
6
+ end
5
7
 
6
- describe '#sanitized' do
7
- subject { described_class.new(title).sanitized }
8
+ def test_removes_3d
9
+ assert_sanitized 'Iron Man 3', 'Iron Man 3 3D'
10
+ end
8
11
 
9
- describe 'with 2d in title' do
10
- let(:title) { 'Iron Man 3 2D' }
12
+ def test_removes_autism_prefix
13
+ assert_sanitized 'Planes', 'Autism Friendly Screening - Planes'
14
+ end
11
15
 
12
- it 'removes dimension' do
13
- subject.must_equal('Iron Man 3')
14
- end
15
- end
16
+ def test_bolshoi
17
+ assert_sanitized 'Bolshoi: Spartacus', 'Bolshoi - Spartacus (Live)'
18
+ end
16
19
 
17
- describe 'with 3d in title' do
18
- let(:title) { 'Iron Man 3 3D' }
20
+ def test_removes_cinemagic_prefix
21
+ assert_sanitized 'Echo Planet', 'Cinemagic 2013 - Echo Planet'
22
+ end
19
23
 
20
- it 'removes dimension' do
21
- subject.must_equal('Iron Man 3')
22
- end
23
- end
24
+ def test_globe_on_screen
25
+ assert_sanitized 'Globe: Twelfth Night', 'Globe On Screen: Twelfth Night'
26
+ end
24
27
 
25
- describe 'Austism screening' do
26
- let(:title) { 'Autism Friendly Screening - Planes' }
28
+ def test_met_opera
29
+ assert_sanitized 'Met Opera: Eugene Onegin 2013', 'Met Opera - Eugene Onegin 2013'
30
+ end
27
31
 
28
- it 'removes prefix' do
29
- subject.must_equal('Planes')
30
- end
31
- end
32
+ def test_met_opera_season
33
+ assert_sanitized 'Met Opera: La Fanciulla del West',
34
+ 'Met Opera 2026-27: La Fanciulla del West'
35
+ end
32
36
 
33
- describe 'Bolshoi screeening' do
34
- let(:title) { 'Bolshoi - Spartacus (Live)' }
37
+ def test_nt_live
38
+ assert_sanitized 'National Theatre: War Horse', 'NT Live - War Horse'
39
+ end
35
40
 
36
- it 'removes prefix' do
37
- subject.must_equal('Bolshoi: Spartacus')
38
- end
39
- end
41
+ def test_national_theatre_encore
42
+ assert_sanitized 'National Theatre: Frankenstein',
43
+ 'National Theatre Live - Frankenstein (Encore Screening)'
44
+ end
40
45
 
41
- describe 'Cinemagic screeening' do
42
- let(:title) { 'Cinemagic 2013 - Echo Planet' }
46
+ def test_roh
47
+ assert_sanitized 'Royal Opera House: Carmen', 'ROH - Carmen'
48
+ end
43
49
 
44
- it 'removes prefix' do
45
- subject.must_equal('Echo Planet')
46
- end
47
- end
50
+ def test_rbo_season
51
+ assert_sanitized 'Royal Ballet and Opera: Swan Lake',
52
+ 'RBO Cinema Season 2026-27: Swan Lake'
53
+ end
48
54
 
49
- describe 'Globe on Screen screeening' do
50
- let(:title) { 'Globe On Screen: Twelfth Night' }
55
+ def test_short_rbo_season
56
+ assert_sanitized "Royal Ballet and Opera: Alice's Adventures in Wonderland",
57
+ "RBO 2026-27: Alice's Adventures in Wonderland"
58
+ end
51
59
 
52
- it 'removes prefix' do
53
- subject.must_equal('Globe: Twelfth Night')
54
- end
55
- end
60
+ def test_rsc
61
+ assert_sanitized 'Royal Shakespeare Company: Richard II', 'Richard II - RSC Live 2013'
62
+ end
56
63
 
57
- describe 'Met Opera screeening' do
58
- let(:title) { 'Met Opera - Eugene Onegin 2013' }
64
+ def test_removes_ukjff_prefix
65
+ assert_sanitized 'From Cable Street To Brick Lane',
66
+ 'UKJFF - From Cable Street To Brick Lane'
67
+ end
59
68
 
60
- it 'removes prefix' do
61
- subject.must_equal('Met Opera: Eugene Onegin 2013')
62
- end
63
- end
69
+ def test_removes_singalong
70
+ assert_sanitized 'Frozen', 'Frozen Sing-a-long'
71
+ end
64
72
 
65
- describe 'NT Live screeening' do
66
- let(:title) { 'NT Live - War Horse' }
73
+ def test_html_ampersands
74
+ assert_sanitized 'Fast & Furious 7', 'Fast &amp; Furious 7'
75
+ end
67
76
 
68
- it 'removes prefix' do
69
- subject.must_equal('National Theatre: War Horse')
70
- end
71
- end
77
+ def test_removes_language_suffix
78
+ assert_sanitized 'Jailer 2', 'Jailer 2 (Telugu)'
79
+ end
80
+
81
+ def test_removes_encore_suffix
82
+ assert_sanitized 'Avengers Endgame', 'Avengers Endgame: Encore'
83
+ end
84
+
85
+ def test_keeps_sing_along_within_title
86
+ assert_sanitized 'Bebefinn Sing-Along Movie', 'Bebefinn Sing-Along Movie'
87
+ end
88
+
89
+ def test_removes_dubbed
90
+ assert_sanitized 'Arrietty', 'Arrietty (Dubbed)'
91
+ end
72
92
 
73
- describe 'National Theatre screeening' do
74
- let(:title) { 'National Theatre Live - Frankenstein (Encore Screening)' }
93
+ def test_removes_subbed
94
+ assert_sanitized 'Arrietty', 'Arrietty (Subbed)'
95
+ end
75
96
 
76
- it 'removes prefix' do
77
- subject.must_equal('National Theatre: Frankenstein')
78
- end
79
- end
97
+ def test_removes_english_dub
98
+ assert_sanitized 'Kung Fu Soccer', 'Kung Fu Soccer (English Dub)'
99
+ end
80
100
 
81
- describe 'ROH screeening' do
82
- let(:title) { 'Autism Friendly Screening - Planes' }
101
+ def test_removes_subtitle_description
102
+ assert_sanitized 'Kung Fu Soccer', 'Kung Fu Soccer (Mandarin with EN/CH subtitles)'
103
+ end
83
104
 
84
- it 'removes prefix' do
85
- subject.must_equal('Planes')
86
- end
87
- end
105
+ def test_removes_q_and_a
106
+ assert_sanitized 'NAZA', 'NAZA + Q&A'
107
+ end
88
108
 
89
- describe 'RSC screeening' do
90
- let(:title) { 'Richard II - RSC Live 2013' }
109
+ def test_removes_in_person_q_and_a
110
+ assert_sanitized "Charlie Shaw's Revenge", "Charlie Shaw's Revenge + In-Person Q&A"
111
+ end
91
112
 
92
- it 'removes prefix' do
93
- subject.must_equal('Royal Shakespeare Company: Richard II')
94
- end
95
- end
113
+ def test_removes_festival_prefix
114
+ assert_sanitized 'Scum Of The Earth', 'Cine-Excess 2026: Scum Of The Earth'
115
+ end
96
116
 
97
- describe 'UK Jewish FF screeening' do
98
- let(:title) { 'UKJFF - From Cable Street To Brick Lane' }
117
+ def test_removes_anniversary
118
+ assert_sanitized 'SCREAM', 'SCREAM (30th Anniversary)'
119
+ end
99
120
 
100
- it 'removes prefix' do
101
- subject.must_equal('From Cable Street To Brick Lane')
102
- end
103
- end
121
+ def test_world_tour_is_not_capitalised
122
+ assert_sanitized "ENHYPEN World Tour 'BLOOD SAGA'", "ENHYPEN WORLD TOUR 'BLOOD SAGA'"
123
+ end
104
124
 
105
- describe 'singalong screeening' do
106
- let(:title) { 'Frozen Sing-a-long' }
125
+ def test_keeps_titles_with_their_own_subtitles
126
+ [
127
+ 'Harry Potter and the Deathly Hallows: Part 1',
128
+ 'Spider-Man: Brand New Day',
129
+ 'Hadestown: The Musical',
130
+ 'Rammstein - Live in Mexico City',
131
+ 'Ozzy & Black Sabbath: Back to the Beginning',
132
+ "BTS 'ARIRANG' IN BUENOS AIRES: LIVE VIEWING"
133
+ ].each { |title| assert_sanitized title, title }
134
+ end
107
135
 
108
- it 'removes prefix' do
109
- subject.must_equal('Frozen')
110
- end
111
- end
112
-
113
- describe 'HTML ampersands' do
114
- let(:title) { 'Fast &amp; Furious 7' }
136
+ private
115
137
 
116
- it 'removes prefix' do
117
- subject.must_equal('Fast & Furious 7')
118
- end
119
- end
138
+ def assert_sanitized(expected, title)
139
+ assert_equal expected, OdeonUk::Internal::TitleSanitizer.new(title).sanitized
120
140
  end
121
141
  end