cineworld_uk 3.1.0 → 4.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 (45) hide show
  1. checksums.yaml +5 -5
  2. data/.agents/setup +51 -0
  3. data/.github/workflows/test.yml +17 -0
  4. data/.gitignore +1 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +13 -0
  7. data/README.md +67 -16
  8. data/Rakefile +4 -37
  9. data/cineworld_uk.gemspec +6 -4
  10. data/lib/cineworld_uk/cinema.rb +7 -4
  11. data/lib/cineworld_uk/internal/api_response.rb +99 -56
  12. data/lib/cineworld_uk/internal/parser/api/cinema_address.rb +24 -24
  13. data/lib/cineworld_uk/internal/parser/api/film.rb +10 -8
  14. data/lib/cineworld_uk/internal/parser/api/performance.rb +6 -11
  15. data/lib/cineworld_uk/performance.rb +42 -23
  16. data/lib/cineworld_uk/version.rb +2 -2
  17. data/lib/cineworld_uk.rb +6 -2
  18. data/test/fixtures/api/cinema-list.json +1 -1
  19. data/test/fixtures/api/dates-14.json +1 -0
  20. data/test/fixtures/api/performances-tomorrow-14.json +1 -0
  21. data/test/fixtures/api/vista-showtimes-brighton.json +1 -0
  22. data/test/lib/cineworld_uk/cinema_test.rb +47 -47
  23. data/test/lib/cineworld_uk/internal/api_response_test.rb +111 -68
  24. data/test/lib/cineworld_uk/internal/parser/api/cinema_address_test.rb +14 -14
  25. data/test/lib/cineworld_uk/internal/parser/api/film_test.rb +43 -55
  26. data/test/lib/cineworld_uk/internal/parser/api/performance_test.rb +11 -12
  27. data/test/lib/cineworld_uk/internal/title_sanitizer_test.rb +18 -18
  28. data/test/lib/cineworld_uk/performance_test.rb +31 -32
  29. data/test/lib/cineworld_uk/version_test.rb +1 -1
  30. data/test/live/integration_test.rb +9 -9
  31. data/test/support/fixture_reader.rb +2 -14
  32. data/test/test_helper.rb +3 -2
  33. metadata +47 -36
  34. data/.travis.yml +0 -10
  35. data/lib/cineworld_uk/internal/parser/api/film_lookup.rb +0 -38
  36. data/lib/cineworld_uk/internal/parser/api/performances_by_day.rb +0 -54
  37. data/test/fixtures/api/cinema-detail-10.json +0 -1
  38. data/test/fixtures/api/cinema-detail-21.json +0 -1
  39. data/test/fixtures/api/cinema-detail-3.json +0 -1
  40. data/test/fixtures/api/cinema-detail-96.json +0 -1
  41. data/test/fixtures/api/dates-3.json +0 -1
  42. data/test/fixtures/api/film-list-comingsoon.json +0 -1
  43. data/test/fixtures/api/film-list.json +0 -1
  44. data/test/fixtures/api/performances-tomorrow-3.json +0 -1
  45. data/test/lib/cineworld_uk/internal/parser/api/film_lookup_test.rb +0 -31
@@ -0,0 +1 @@
1
+ {"businessDate":"2026-10-14","showtimes":[{"id":"014-165507","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T15:40:00+01:00","endsAt":"2026-10-14T17:33:00+01:00","filmStartsAt":"2026-10-14T16:00:00+01:00","filmEndsAt":"2026-10-14T17:33:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-8-56","filmId":"HO00015464","siteId":"014","screenId":"014-8","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165455","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T15:50:00+01:00","endsAt":"2026-10-14T18:07:00+01:00","filmStartsAt":"2026-10-14T16:10:00+01:00","filmEndsAt":"2026-10-14T18:07:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-5-63","filmId":"HO00015366","siteId":"014","screenId":"014-5","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165467","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T16:50:00+01:00","endsAt":"2026-10-14T19:18:00+01:00","filmStartsAt":"2026-10-14T17:10:00+01:00","filmEndsAt":"2026-10-14T19:18:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-6-59","filmId":"HO00015391","siteId":"014","screenId":"014-6","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165380","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T17:00:00+01:00","endsAt":"2026-10-14T19:17:00+01:00","filmStartsAt":"2026-10-14T17:20:00+01:00","filmEndsAt":"2026-10-14T19:17:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-3-62","filmId":"HO00015366","siteId":"014","screenId":"014-3","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-163298","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T17:00:00+01:00","endsAt":"2026-10-14T19:42:00+01:00","filmStartsAt":"2026-10-14T17:20:00+01:00","filmEndsAt":"2026-10-14T19:42:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-1-61","filmId":"HO00015041","siteId":"014","screenId":"014-1","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000012","0000000037","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-164903","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T17:20:00+01:00","endsAt":"2026-10-14T19:32:00+01:00","filmStartsAt":"2026-10-14T17:40:00+01:00","filmEndsAt":"2026-10-14T19:32:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-4-57","filmId":"HO00015462","siteId":"014","screenId":"014-4","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165389","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T17:20:00+01:00","endsAt":"2026-10-14T19:49:00+01:00","filmStartsAt":"2026-10-14T17:40:00+01:00","filmEndsAt":"2026-10-14T19:49:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-2-58","filmId":"HO00015453","siteId":"014","screenId":"014-2","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165491","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T18:00:00+01:00","endsAt":"2026-10-14T19:54:00+01:00","filmStartsAt":"2026-10-14T18:20:00+01:00","filmEndsAt":"2026-10-14T19:54:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-7-55","filmId":"HO00015169","siteId":"014","screenId":"014-7","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165510","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T18:10:00+01:00","endsAt":"2026-10-14T20:03:00+01:00","filmStartsAt":"2026-10-14T18:30:00+01:00","filmEndsAt":"2026-10-14T20:03:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-8-56","filmId":"HO00015464","siteId":"014","screenId":"014-8","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165453","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T18:40:00+01:00","endsAt":"2026-10-14T20:57:00+01:00","filmStartsAt":"2026-10-14T19:00:00+01:00","filmEndsAt":"2026-10-14T20:57:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-5-63","filmId":"HO00015366","siteId":"014","screenId":"014-5","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165378","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T19:50:00+01:00","endsAt":"2026-10-14T22:07:00+01:00","filmStartsAt":"2026-10-14T20:10:00+01:00","filmEndsAt":"2026-10-14T22:07:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-3-62","filmId":"HO00015366","siteId":"014","screenId":"014-3","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165465","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:00:00+01:00","endsAt":"2026-10-14T22:28:00+01:00","filmStartsAt":"2026-10-14T20:20:00+01:00","filmEndsAt":"2026-10-14T22:28:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-6-59","filmId":"HO00015391","siteId":"014","screenId":"014-6","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-164902","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:10:00+01:00","endsAt":"2026-10-14T22:22:00+01:00","filmStartsAt":"2026-10-14T20:30:00+01:00","filmEndsAt":"2026-10-14T22:22:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-4-57","filmId":"HO00015462","siteId":"014","screenId":"014-4","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165417","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:15:00+01:00","endsAt":"2026-10-14T22:44:00+01:00","filmStartsAt":"2026-10-14T20:35:00+01:00","filmEndsAt":"2026-10-14T22:44:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-1-61","filmId":"HO00015453","siteId":"014","screenId":"014-1","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165406","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:20:00+01:00","endsAt":"2026-10-14T22:21:00+01:00","filmStartsAt":"2026-10-14T20:40:00+01:00","filmEndsAt":"2026-10-14T22:21:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-2-58","filmId":"HO00015373","siteId":"014","screenId":"014-2","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165492","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:30:00+01:00","endsAt":"2026-10-14T22:24:00+01:00","filmStartsAt":"2026-10-14T20:50:00+01:00","filmEndsAt":"2026-10-14T22:24:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-7-55","filmId":"HO00015169","siteId":"014","screenId":"014-7","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null},{"id":"014-165509","schedule":{"businessDate":"2026-10-14","startsAt":"2026-10-14T20:40:00+01:00","endsAt":"2026-10-14T22:33:00+01:00","filmStartsAt":"2026-10-14T21:00:00+01:00","filmEndsAt":"2026-10-14T22:33:00+01:00","inSeatItemDelivery":null},"isSoldOut":false,"seatLayoutId":"014-8-56","filmId":"HO00015464","siteId":"014","screenId":"014-8","areaCategories":[{"areaCategoryId":"014-0000000001","isAllocatedSeating":true}],"attributeIds":["0000000011","0000000005","0000000042"],"isAllocatedSeating":true,"requires3dGlasses":false,"eventId":null,"restrictions":[],"filmAdvanceBookingRuleId":null}],"relatedData":{"sites":[{"id":"014","name":{"text":"Brighton","translations":[]},"location":{"latitude":50.8125991821289,"longitude":-0.104699999094009},"contactDetails":{"phoneNumbers":[],"email":"014@cineworld.co.uk","address":{"line1":"Brighton Marina Village","line2":"Brighton","city":"East Sussex"},"addressV2":{"line1":{"text":"Brighton Marina Village","translations":[]},"line2":{"text":"Brighton","translations":[]},"line3":{"text":"East Sussex","translations":[]},"line4":{"text":"BN2 5UF","translations":[]}}},"ianaTimeZoneName":"Europe/London","hasSellableItems":false,"allowedItemDeliveryMethods":[]}],"films":[{"id":"HO00015041","title":{"text":"Hadestown: The Musical","translations":[]},"synopsis":{"text":"The live theater capture of the hit musical Hadestown, which won eight Tony Awards in 2019 including Best Musical and the 2020 Grammy Award for Best Musical Theater Album, featuring the five original principals of the Broadway company. The genre-defying new musical blends modern American folk music with New Orleans-inspired jazz to reimagine a sweeping ancient tale. Following two intertwining love stories — that of young dreamers Orpheus and Eurydice, and that of King Hades and his wife Persephone —Hadestowninvites audiences on a hell-raising journey to the underworld and back.","translations":[]},"shortSynopsis":{"text":"The live theater capture of the hit musical Hadestown, which won eight Tony Awards in 2019 including Best Musical and the 2020 Grammy Award for Best Musical Theater Album","translations":[]},"censorRatingId":"HO00000003","censorRatingNote":null,"releaseDate":"2026-10-13","runtimeInMinutes":142,"trailers":[],"trailerUrl":null,"displayPriority":50,"castAndCrew":[{"castAndCrewMemberId":"0000027053","roles":["Director"]},{"castAndCrewMemberId":"0000029690","roles":["Actor"]},{"castAndCrewMemberId":"0000035048","roles":["Actor"]},{"castAndCrewMemberId":"0000035049","roles":["Actor"]},{"castAndCrewMemberId":"0000035050","roles":["Actor"]},{"castAndCrewMemberId":"0000035051","roles":["Actor"]},{"castAndCrewMemberId":"0000035052","roles":["Actor"]},{"castAndCrewMemberId":"0000035053","roles":["Actor"]},{"castAndCrewMemberId":"0000035054","roles":["Actor"]}],"actors":[{"castAndCrewMemberId":"0000029690"},{"castAndCrewMemberId":"0000035048"},{"castAndCrewMemberId":"0000035049"},{"castAndCrewMemberId":"0000035050"},{"castAndCrewMemberId":"0000035051"},{"castAndCrewMemberId":"0000035052"},{"castAndCrewMemberId":"0000035053"},{"castAndCrewMemberId":"0000035054"}],"directors":[{"castAndCrewMemberId":"0000027053"}],"producers":[],"genreIds":["0000000016","0000000017"],"externalIds":{"moviexchangeReleaseId":"25c418ac-80f5-493d-8964-dc63373ef46b","corporateId":null},"hopk":"HO00015041","hoCode":"A000018271","eventId":null,"distributorName":"TRAFALGAR RELEASING"},{"id":"HO00015169","title":{"text":"Resident Evil (2026)","translations":[]},"synopsis":{"text":"From the mind of visionary filmmaker Zach Cregger (Weapons, Barbarian) comes a thrilling — and terrifying — reinvention of the Resident Evil franchise.\n\nIn an all-new story, Resident Evil follows Bryan (Austin Abrams), a medical courier who unwittingly finds himself in an action-packed, non-stop race for survival as one fateful, horrifying night collapses around him in chaos.","translations":[]},"shortSynopsis":{"text":"From the mind of visionary filmmaker Zach Cregger (Weapons, Barbarian) comes a thrilling — and terrifying — reinvention of the Resident Evil franchise.","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":null,"releaseDate":"2026-09-18","runtimeInMinutes":94,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=9Aiv1cqmH4w"}],"trailerUrl":"https://www.youtube.com/watch?v=9Aiv1cqmH4w","displayPriority":8,"castAndCrew":[{"castAndCrewMemberId":"0000004214","roles":["Director","Producer"]},{"castAndCrewMemberId":"0000020073","roles":["Actor"]},{"castAndCrewMemberId":"0000028311","roles":["Producer"]},{"castAndCrewMemberId":"0000028766","roles":["Actor"]},{"castAndCrewMemberId":"0000029942","roles":["Producer"]},{"castAndCrewMemberId":"0000031328","roles":["Producer"]},{"castAndCrewMemberId":"0000031329","roles":["Producer"]},{"castAndCrewMemberId":"HO00000363","roles":["Producer"]},{"castAndCrewMemberId":"HO00006519","roles":["Actor"]},{"castAndCrewMemberId":"HO00008050","roles":["Actor"]}],"actors":[{"castAndCrewMemberId":"0000020073"},{"castAndCrewMemberId":"0000028766"},{"castAndCrewMemberId":"HO00006519"},{"castAndCrewMemberId":"HO00008050"}],"directors":[{"castAndCrewMemberId":"0000004214"}],"producers":[{"castAndCrewMemberId":"0000004214"},{"castAndCrewMemberId":"0000028311"},{"castAndCrewMemberId":"0000029942"},{"castAndCrewMemberId":"0000031328"},{"castAndCrewMemberId":"0000031329"},{"castAndCrewMemberId":"HO00000363"}],"genreIds":["0000000001","0000000002","0000000028"],"externalIds":{"moviexchangeReleaseId":"9ad02bee-61da-40ca-9ede-5f3ea62c40f4","corporateId":null},"hopk":"HO00015169","hoCode":"A000018406","eventId":null,"distributorName":"SONY PICTURES"},{"id":"HO00015366","title":{"text":"Verity","translations":[]},"synopsis":{"text":"Adapted from Colleen Hoover's best-selling novel, this seductive, psychological thriller follows renowned author Verity Crawford (Anne Hathaway) and Lowen Ashleigh (Dakota Johnson), a struggling writer who relocates to the remote Crawford estate to ghostwrite for Verity. After Lowen uncovers what appears to be Verity's chilling autobiographical notes, she wrestles with the disturbing and twisted confessions about Verity’s husband Jeremy (Josh Hartnett), and finds it hard to separate fiction from reality, manipulation from attraction, and opportunity from obsession.","translations":[]},"shortSynopsis":{"text":"Adapted from Colleen Hoover's best-selling novel, this seductive, psychological thriller follows renowned author Verity Crawford (Anne Hathaway) and Lowen Ashleigh.","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":null,"releaseDate":"2026-10-02","runtimeInMinutes":117,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=2l3rfJVPjRY"}],"trailerUrl":"https://www.youtube.com/watch?v=2l3rfJVPjRY","displayPriority":1,"castAndCrew":[{"castAndCrewMemberId":"0000007978","roles":["Actor"]},{"castAndCrewMemberId":"0000008008","roles":["Actor","Producer"]},{"castAndCrewMemberId":"0000009241","roles":["Actor"]},{"castAndCrewMemberId":"0000026686","roles":["Director","Producer"]},{"castAndCrewMemberId":"HO00004655","roles":["Producer"]},{"castAndCrewMemberId":"HO00006669","roles":["Producer"]},{"castAndCrewMemberId":"HO00008305","roles":["Actor"]},{"castAndCrewMemberId":"HO00008306","roles":["Actor"]},{"castAndCrewMemberId":"HO00008307","roles":["Producer"]},{"castAndCrewMemberId":"HO00008308","roles":["Producer"]},{"castAndCrewMemberId":"HO00008309","roles":["Producer"]}],"actors":[{"castAndCrewMemberId":"0000007978"},{"castAndCrewMemberId":"0000008008"},{"castAndCrewMemberId":"0000009241"},{"castAndCrewMemberId":"HO00008305"},{"castAndCrewMemberId":"HO00008306"}],"directors":[{"castAndCrewMemberId":"0000026686"}],"producers":[{"castAndCrewMemberId":"0000008008"},{"castAndCrewMemberId":"0000026686"},{"castAndCrewMemberId":"HO00004655"},{"castAndCrewMemberId":"HO00006669"},{"castAndCrewMemberId":"HO00008307"},{"castAndCrewMemberId":"HO00008308"},{"castAndCrewMemberId":"HO00008309"}],"genreIds":["0000000003","0000000004","0000000023"],"externalIds":{"moviexchangeReleaseId":"84b84a49-45be-4d39-b552-4362cdc17af4","corporateId":null},"hopk":"HO00015366","hoCode":"A000018608","eventId":null,"distributorName":"SONY PICTURES"},{"id":"HO00015373","title":{"text":"Heart Of The Beast","translations":[]},"synopsis":{"text":"After a harrowing plane crash, Special Forces officer James Belmont (Brad Pitt) and his combat dog, Odin, find themselves stranded deep in the Alaskan wilderness. Together, they are forced into a brutal fight for survival against the elements. From acclaimed filmmaker David Ayer, Heart of the Beast is an intense adventure thriller that explores the unbreakable bond between a man and his best friend as they face their greatest battle yet.","translations":[]},"shortSynopsis":{"text":"After a harrowing plane crash, Special Forces officer James Belmont (Brad Pitt) and his combat dog, Odin, find themselves stranded deep in the Alaskan wilderness. ","translations":[]},"censorRatingId":"HO00000003","censorRatingNote":{"text":"moderate injury detail, threat, violence, language","translations":[]},"releaseDate":"2026-09-23","runtimeInMinutes":101,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=UkMuGKqAux4"}],"trailerUrl":"https://www.youtube.com/watch?v=UkMuGKqAux4","displayPriority":9,"castAndCrew":[{"castAndCrewMemberId":"0000001086","roles":["Director","Producer"]},{"castAndCrewMemberId":"0000014661","roles":["Actor","Producer"]},{"castAndCrewMemberId":"0000016972","roles":["Actor"]},{"castAndCrewMemberId":"0000028343","roles":["Producer"]},{"castAndCrewMemberId":"0000030671","roles":["Producer"]},{"castAndCrewMemberId":"HO00008148","roles":["Actor"]}],"actors":[{"castAndCrewMemberId":"0000014661"},{"castAndCrewMemberId":"0000016972"},{"castAndCrewMemberId":"HO00008148"}],"directors":[{"castAndCrewMemberId":"0000001086"}],"producers":[{"castAndCrewMemberId":"0000001086"},{"castAndCrewMemberId":"0000014661"},{"castAndCrewMemberId":"0000028343"},{"castAndCrewMemberId":"0000030671"}],"genreIds":["0000000001","0000000003","0000000004"],"externalIds":{"moviexchangeReleaseId":"3afe1331-dcd8-4a31-8245-b432201b6062","corporateId":null},"hopk":"HO00015373","hoCode":"A000018615","eventId":null,"distributorName":"PARAMOUNT"},{"id":"HO00015391","title":{"text":"The Uprising","translations":[]},"synopsis":{"text":"From Paul Greengrass, director of Captain Phillips and Jason Bourne. Starring Andrew Garfield, The Uprising tells the story of the Peasants' Revolt of 1381 — when ordinary people stood against the might of a kingdom.","translations":[]},"shortSynopsis":{"text":"Starring Andrew Garfield, The Uprising tells the story of the Peasants' Revolt of 1381 — when ordinary people stood against the might of a kingdom.","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":{"text":"strong violence, injury detail, sexual assault","translations":[]},"releaseDate":"2026-10-09","runtimeInMinutes":128,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=lyhCxZN4Ro8"}],"trailerUrl":"https://www.youtube.com/watch?v=lyhCxZN4Ro8","displayPriority":4,"castAndCrew":[{"castAndCrewMemberId":"0000001716","roles":["Actor"]},{"castAndCrewMemberId":"0000005120","roles":["Actor"]},{"castAndCrewMemberId":"0000006776","roles":["Actor"]},{"castAndCrewMemberId":"0000007439","roles":["Director","Producer"]},{"castAndCrewMemberId":"0000008439","roles":["Actor"]},{"castAndCrewMemberId":"0000018343","roles":["Actor"]},{"castAndCrewMemberId":"0000027677","roles":["Actor"]},{"castAndCrewMemberId":"0000028129","roles":["Actor"]},{"castAndCrewMemberId":"0000028738","roles":["Producer"]},{"castAndCrewMemberId":"0000029509","roles":["Actor"]},{"castAndCrewMemberId":"0000032658","roles":["Actor"]},{"castAndCrewMemberId":"HO00000387","roles":["Producer"]},{"castAndCrewMemberId":"HO00001048","roles":["Actor"]},{"castAndCrewMemberId":"HO00008313","roles":["Actor"]},{"castAndCrewMemberId":"HO00008314","roles":["Producer"]},{"castAndCrewMemberId":"HO00008315","roles":["Producer"]},{"castAndCrewMemberId":"HO00008316","roles":["Producer"]}],"actors":[{"castAndCrewMemberId":"0000001716"},{"castAndCrewMemberId":"0000005120"},{"castAndCrewMemberId":"0000006776"},{"castAndCrewMemberId":"0000008439"},{"castAndCrewMemberId":"0000018343"},{"castAndCrewMemberId":"0000027677"},{"castAndCrewMemberId":"0000028129"},{"castAndCrewMemberId":"0000029509"},{"castAndCrewMemberId":"0000032658"},{"castAndCrewMemberId":"HO00001048"},{"castAndCrewMemberId":"HO00008313"}],"directors":[{"castAndCrewMemberId":"0000007439"}],"producers":[{"castAndCrewMemberId":"0000007439"},{"castAndCrewMemberId":"0000028738"},{"castAndCrewMemberId":"HO00000387"},{"castAndCrewMemberId":"HO00008314"},{"castAndCrewMemberId":"HO00008315"},{"castAndCrewMemberId":"HO00008316"}],"genreIds":["0000000001","0000000003","0000000043"],"externalIds":{"moviexchangeReleaseId":"ab951e6e-10f9-48a6-8952-b089ee7a8a79","corporateId":null},"hopk":"HO00015391","hoCode":"A000018636","eventId":null,"distributorName":"ENTERTAINMENT"},{"id":"HO00015453","title":{"text":"Digger","translations":[]},"synopsis":{"text":"The most powerful man in the world embarks on a frantic mission to prove he is humanity's saviour before the disaster he’s unleashed destroys everything.","translations":[]},"shortSynopsis":{"text":"The most powerful man in the world embarks on a frantic mission to prove he is humanity's saviour before the disaster he’s unleashed destroys everything.","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":null,"releaseDate":"2026-10-02","runtimeInMinutes":129,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=SdaTRvmndu8"}],"trailerUrl":"https://www.youtube.com/watch?v=SdaTRvmndu8","displayPriority":5,"castAndCrew":[{"castAndCrewMemberId":"0000000265","roles":["Actor"]},{"castAndCrewMemberId":"0000004278","roles":["Actor","Producer"]},{"castAndCrewMemberId":"0000007252","roles":["Actor"]},{"castAndCrewMemberId":"0000007294","roles":["Actor"]},{"castAndCrewMemberId":"0000009221","roles":["Actor"]},{"castAndCrewMemberId":"0000014682","roles":["Actor"]},{"castAndCrewMemberId":"0000022736","roles":["Director","Producer"]},{"castAndCrewMemberId":"0000023276","roles":["Actor"]},{"castAndCrewMemberId":"0000027028","roles":["Actor"]},{"castAndCrewMemberId":"0000028360","roles":["Producer"]},{"castAndCrewMemberId":"0000031734","roles":["Actor"]},{"castAndCrewMemberId":"0000035046","roles":["Actor"]},{"castAndCrewMemberId":"0000035047","roles":["Producer"]}],"actors":[{"castAndCrewMemberId":"0000000265"},{"castAndCrewMemberId":"0000004278"},{"castAndCrewMemberId":"0000007252"},{"castAndCrewMemberId":"0000007294"},{"castAndCrewMemberId":"0000009221"},{"castAndCrewMemberId":"0000014682"},{"castAndCrewMemberId":"0000023276"},{"castAndCrewMemberId":"0000027028"},{"castAndCrewMemberId":"0000031734"},{"castAndCrewMemberId":"0000035046"}],"directors":[{"castAndCrewMemberId":"0000022736"}],"producers":[{"castAndCrewMemberId":"0000004278"},{"castAndCrewMemberId":"0000022736"},{"castAndCrewMemberId":"0000028360"},{"castAndCrewMemberId":"0000035047"}],"genreIds":["0000000003","0000000006","0000000026"],"externalIds":{"moviexchangeReleaseId":"ecac1911-421f-4911-b357-d24b28f19d35","corporateId":null},"hopk":"HO00015453","hoCode":"A000018699","eventId":null,"distributorName":"WARNER BROS"},{"id":"HO00015462","title":{"text":"The Social Reckoning","translations":[]},"synopsis":{"text":"A companion piece to the hit film The Social Network, Sorkin’s original screenplay is based on the events that gave rise to the Wall Street Journal’s shocking exposé The Facebook Files. The film is inspired by the true story of how Frances Haugen (Madison), a young Facebook engineer, enlists the help of Jeff Horwitz (White), a Wall Street Journal reporter, to go on a dangerous journey that ends up blowing the whistle on the social network’s most guarded secrets.","translations":[]},"shortSynopsis":{"text":"A companion piece to the hit film The Social Network, Sorkin’s original screenplay is based on the events that gave rise to the Wall Street Journal’s shocking exposé","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":null,"releaseDate":"2026-10-09","runtimeInMinutes":112,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=3RFFgrB9YlI"}],"trailerUrl":"https://www.youtube.com/watch?v=3RFFgrB9YlI","displayPriority":2,"castAndCrew":[{"castAndCrewMemberId":"0000011614","roles":["Actor"]},{"castAndCrewMemberId":"0000017634","roles":["Actor"]},{"castAndCrewMemberId":"0000022663","roles":["Actor"]},{"castAndCrewMemberId":"0000025024","roles":["Actor"]},{"castAndCrewMemberId":"0000028250","roles":["Director","Producer"]},{"castAndCrewMemberId":"0000028251","roles":["Producer"]},{"castAndCrewMemberId":"0000028321","roles":["Producer"]},{"castAndCrewMemberId":"0000029577","roles":["Actor"]},{"castAndCrewMemberId":"0000032000","roles":["Actor"]},{"castAndCrewMemberId":"0000033878","roles":["Producer"]},{"castAndCrewMemberId":"HO00008335","roles":["Actor"]}],"actors":[{"castAndCrewMemberId":"0000011614"},{"castAndCrewMemberId":"0000017634"},{"castAndCrewMemberId":"0000022663"},{"castAndCrewMemberId":"0000025024"},{"castAndCrewMemberId":"0000029577"},{"castAndCrewMemberId":"0000032000"},{"castAndCrewMemberId":"HO00008335"}],"directors":[{"castAndCrewMemberId":"0000028250"}],"producers":[{"castAndCrewMemberId":"0000028250"},{"castAndCrewMemberId":"0000028251"},{"castAndCrewMemberId":"0000028321"},{"castAndCrewMemberId":"0000033878"}],"genreIds":["0000000003","0000000006"],"externalIds":{"moviexchangeReleaseId":"8227e192-c18c-4920-a673-27489ccea7f9","corporateId":null},"hopk":"HO00015462","hoCode":"A000018708","eventId":null,"distributorName":"SONY PICTURES"},{"id":"HO00015464","title":{"text":"Other Mommy","translations":[]},"synopsis":{"text":"Bela, an 8-year-old girl living in a home with marital problems, confronts a sinister entity that comes out of her closet. The presence, whom she calls \"Other Mommy,\" becomes increasingly threatening to her and her family.","translations":[]},"shortSynopsis":{"text":"Bela, an 8-year-old girl living in a home with marital problems, confronts a sinister entity that comes out of her closet. The presence, whom she calls \"Other Mommy\"...","translations":[]},"censorRatingId":"HO00000004","censorRatingNote":null,"releaseDate":"2026-10-09","runtimeInMinutes":93,"trailers":[{"provider":"External","uri":"https://www.youtube.com/watch?v=-2-eZPGMugw"}],"trailerUrl":"https://www.youtube.com/watch?v=-2-eZPGMugw","displayPriority":3,"castAndCrew":[{"castAndCrewMemberId":"0000003496","roles":["Actor"]},{"castAndCrewMemberId":"0000005484","roles":["Actor"]},{"castAndCrewMemberId":"0000019078","roles":["Producer"]},{"castAndCrewMemberId":"0000028725","roles":["Director"]},{"castAndCrewMemberId":"0000029889","roles":["Actor"]},{"castAndCrewMemberId":"HO00008336","roles":["Actor"]}],"actors":[{"castAndCrewMemberId":"0000003496"},{"castAndCrewMemberId":"0000005484"},{"castAndCrewMemberId":"0000029889"},{"castAndCrewMemberId":"HO00008336"}],"directors":[{"castAndCrewMemberId":"0000028725"}],"producers":[{"castAndCrewMemberId":"0000019078"}],"genreIds":["0000000002","0000000003","0000000028"],"externalIds":{"moviexchangeReleaseId":"63994af3-33f0-4506-9331-cf8e1be0d1a2","corporateId":null},"hopk":"HO00015464","hoCode":"A000018710","eventId":null,"distributorName":"UNIVERSAL INT'L"}],"castAndCrew":[{"id":"0000027053","name":{"givenName":"Brett ","familyName":"Sullivan","middleName":null}},{"id":"0000029690","name":{"givenName":"Eva","familyName":"Noblezada","middleName":null}},{"id":"0000035048","name":{"givenName":"Reeve","familyName":"Carney","middleName":null}},{"id":"0000035049","name":{"givenName":"André","familyName":"De Shields","middleName":null}},{"id":"0000035050","name":{"givenName":"Amber","familyName":"Gray","middleName":null}},{"id":"0000035051","name":{"givenName":"Patrick","familyName":"Page","middleName":null}},{"id":"0000035052","name":{"givenName":"Bella","familyName":"Brown","middleName":null}},{"id":"0000035053","name":{"givenName":"Madeline","familyName":"Charlemagne","middleName":null}},{"id":"0000035054","name":{"givenName":"Allie","familyName":"Daniel","middleName":null}},{"id":"0000004214","name":{"givenName":"Zach ","familyName":"Cregger","middleName":null}},{"id":"0000020073","name":{"givenName":"Austin ","familyName":"Abrams","middleName":null}},{"id":"0000028311","name":{"givenName":"Robert","familyName":"Kulzer","middleName":null}},{"id":"0000028766","name":{"givenName":"Paul Walter","familyName":"Hauser","middleName":null}},{"id":"0000029942","name":{"givenName":"Miri","familyName":"Yoon","middleName":null}},{"id":"0000031328","name":{"givenName":"Asad","familyName":"Qizilbash","middleName":null}},{"id":"0000031329","name":{"givenName":"Carter","familyName":"Swan","middleName":null}},{"id":"HO00000363","name":{"givenName":"Roy ","familyName":"Lee","middleName":null}},{"id":"HO00006519","name":{"givenName":"Kali","familyName":"Reis","middleName":null}},{"id":"HO00008050","name":{"givenName":"Zach","familyName":"Cherry","middleName":null}},{"id":"0000007978","name":{"givenName":"Josh ","familyName":"Hartnett","middleName":null}},{"id":"0000008008","name":{"givenName":"Anne ","familyName":"Hathaway","middleName":null}},{"id":"0000009241","name":{"givenName":"Dakota ","familyName":"Johnson","middleName":null}},{"id":"0000026686","name":{"givenName":"Michael ","familyName":"Showalter","middleName":null}},{"id":"HO00004655","name":{"givenName":"Stacey","familyName":"Sher","middleName":null}},{"id":"HO00006669","name":{"givenName":"Colleen","familyName":"Hoover","middleName":null}},{"id":"HO00008305","name":{"givenName":"Ismael Cruz","familyName":"Cordóva","middleName":null}},{"id":"HO00008306","name":{"givenName":"Brady","familyName":"Wagner","middleName":null}},{"id":"HO00008307","name":{"givenName":"Nick","familyName":"Antosca","middleName":null}},{"id":"HO00008308","name":{"givenName":"Alex","familyName":"Hedlund","middleName":null}},{"id":"HO00008309","name":{"givenName":"Jordana","familyName":"Mollick","middleName":null}},{"id":"0000001086","name":{"givenName":"David ","familyName":"Ayer","middleName":null}},{"id":"0000014661","name":{"givenName":"Brad ","familyName":"Pitt","middleName":null}},{"id":"0000016972","name":{"givenName":"J.K. ","familyName":"Simmons","middleName":null}},{"id":"0000028343","name":{"givenName":"Marty","familyName":"Bowen","middleName":null}},{"id":"0000030671","name":{"givenName":"Olivia","familyName":"Hamilton","middleName":null}},{"id":"HO00008148","name":{"givenName":"Anna","familyName":"Lambe","middleName":null}},{"id":"0000001716","name":{"givenName":"Jamie ","familyName":"Bell","middleName":null}},{"id":"0000005120","name":{"givenName":"Stephen ","familyName":"Dillane","middleName":null}},{"id":"0000006776","name":{"givenName":"Andrew ","familyName":"Garfield","middleName":null}},{"id":"0000007439","name":{"givenName":"Paul ","familyName":"Greengrass","middleName":null}},{"id":"0000008439","name":{"givenName":"Tom ","familyName":"Hollander","middleName":null}},{"id":"0000018343","name":{"givenName":"Stanley ","familyName":"Townsend","middleName":null}},{"id":"0000027677","name":{"givenName":"Katherine ","familyName":"Waterston","middleName":null}},{"id":"0000028129","name":{"givenName":"Thomasin","familyName":"McKenzie","middleName":null}},{"id":"0000028738","name":{"givenName":"Gregory","familyName":"Goodman","middleName":null}},{"id":"0000029509","name":{"givenName":"Woody","familyName":"Norman","middleName":null}},{"id":"0000032658","name":{"givenName":"Jonny Lee","familyName":"Miller","middleName":null}},{"id":"HO00000387","name":{"givenName":"Jason","familyName":"Blum","middleName":null}},{"id":"HO00001048","name":{"givenName":"Cosmo ","familyName":"Jarvis","middleName":null}},{"id":"HO00008313","name":{"givenName":"Sky","familyName":"Yang","middleName":null}},{"id":"HO00008314","name":{"givenName":"Joanna","familyName":"Kaye","middleName":null}},{"id":"HO00008315","name":{"givenName":"Lars","familyName":"Sylvest","middleName":null}},{"id":"HO00008316","name":{"givenName":"Joe","familyName":"Neurauter","middleName":null}},{"id":"0000000265","name":{"givenName":"Riz ","familyName":"Ahmed","middleName":null}},{"id":"0000004278","name":{"givenName":"Tom ","familyName":"Cruise","middleName":null}},{"id":"0000007252","name":{"givenName":"John ","familyName":"Goodman","middleName":null}},{"id":"0000007294","name":{"givenName":"Burn ","familyName":"Gorman","middleName":null}},{"id":"0000009221","name":{"givenName":"Robert ","familyName":"John Burke","middleName":null}},{"id":"0000014682","name":{"givenName":"Jesse ","familyName":"Plemons","middleName":null}},{"id":"0000022736","name":{"givenName":"Alejandro ","familyName":"González Iñárritu","middleName":null}},{"id":"0000023276","name":{"givenName":"Sandra ","familyName":"Hüller","middleName":null}},{"id":"0000027028","name":{"givenName":"Michael ","familyName":"Stuhlbarg","middleName":null}},{"id":"0000028360","name":{"givenName":"Mary","familyName":"Parent","middleName":null}},{"id":"0000031734","name":{"givenName":"Sophie","familyName":"Wilde","middleName":null}},{"id":"0000035046","name":{"givenName":"Emma","familyName":"D’Arcy","middleName":null}},{"id":"0000035047","name":{"givenName":"Michael","familyName":"Sharp","middleName":null}},{"id":"0000011614","name":{"givenName":"Billy ","familyName":"Magnussen","middleName":null}},{"id":"0000017634","name":{"givenName":"Jeremy ","familyName":"Strong","middleName":null}},{"id":"0000022663","name":{"givenName":"Betty ","familyName":"Gilpin","middleName":null}},{"id":"0000025024","name":{"givenName":"Wunmi ","familyName":"Mosaku","middleName":null}},{"id":"0000028250","name":{"givenName":"Aaron ","familyName":"Sorkin","middleName":null}},{"id":"0000028251","name":{"givenName":"Stuart M.","familyName":"Besser","middleName":null}},{"id":"0000028321","name":{"givenName":"Todd","familyName":"Black","middleName":null}},{"id":"0000029577","name":{"givenName":"Mikey","familyName":"Madison","middleName":null}},{"id":"0000032000","name":{"givenName":"Jeremy Allen","familyName":"White","middleName":null}},{"id":"0000033878","name":{"givenName":"Peter","familyName":"Rice","middleName":null}},{"id":"HO00008335","name":{"givenName":"Bill","familyName":"Burr","middleName":null}},{"id":"0000003496","name":{"givenName":"Jessica ","familyName":"Chastain","middleName":null}},{"id":"0000005484","name":{"givenName":"Jay ","familyName":"Duplass","middleName":null}},{"id":"0000019078","name":{"givenName":"James ","familyName":"Wan","middleName":null}},{"id":"0000028725","name":{"givenName":"Rob","familyName":"Savage","middleName":null}},{"id":"0000029889","name":{"givenName":"Dichen","familyName":"Lachman","middleName":null}},{"id":"HO00008336","name":{"givenName":"Arabella","familyName":"Olivia Clark","middleName":null}}],"genres":[{"id":"0000000001","name":{"text":"Action","translations":[]},"description":null},{"id":"0000000002","name":{"text":"Horror","translations":[]},"description":null},{"id":"0000000003","name":{"text":"Drama","translations":[]},"description":null},{"id":"0000000004","name":{"text":"Thriller","translations":[]},"description":null},{"id":"0000000006","name":{"text":"Comedy","translations":[]},"description":{"text":"Comedy","translations":[]}},{"id":"0000000016","name":{"text":"Event Cinema","translations":[]},"description":null},{"id":"0000000017","name":{"text":"Musical","translations":[]},"description":null},{"id":"0000000023","name":{"text":"Crime","translations":[]},"description":null},{"id":"0000000026","name":{"text":"Satire","translations":[]},"description":null},{"id":"0000000028","name":{"text":"Suspense","translations":[]},"description":null},{"id":"0000000043","name":{"text":"War","translations":[]},"description":{"text":"War","translations":[]}}],"censorRatings":[{"id":"HO00000003","classification":{"text":"12A","translations":[]},"classificationDescription":{"text":"12A","translations":[]},"ageRestriction":{"minimumAge":12}},{"id":"HO00000004","classification":{"text":"15","translations":[]},"classificationDescription":{"text":"Restricted To 15+","translations":[]},"ageRestriction":{"minimumAge":15}}],"attributes":[{"id":"0000000011","name":{"text":"2D Film","translations":[]},"shortName":{"text":"2D","translations":[]},"description":{"text":"A standard 2D film presentation. The film is shown without stereoscopic 3D effects, so 3D glasses are not required.","translations":[]},"importantMessage":{"text":"A 2D film showing.","translations":[]},"displayPriority":2,"isPromoted":false},{"id":"0000000005","name":{"text":"Audio Described","translations":[]},"shortName":{"text":"AD","translations":[]},"description":{"text":"Audio description is available for this screening, providing additional spoken information about important visual elements of the film. A compatible headset or receiver may be required; availability can vary by cinema.","translations":[]},"importantMessage":null,"displayPriority":3,"isPromoted":false},{"id":"0000000042","name":{"text":"Laser Projection","translations":[]},"shortName":{"text":"Laser","translations":[]},"description":{"text":"This screening uses laser projection rather than a conventional lamp-based projector. Laser systems are designed to provide consistent brightness, contrast and image clarity; equipment may vary by auditorium.","translations":[]},"importantMessage":null,"displayPriority":2,"isPromoted":false},{"id":"0000000012","name":{"text":"Event Cinema","translations":[]},"shortName":{"text":"AC","translations":[]},"description":null,"importantMessage":null,"displayPriority":100,"isPromoted":false},{"id":"0000000037","name":{"text":"Alt Content Recorded","translations":[]},"shortName":{"text":"ACR","translations":[]},"description":null,"importantMessage":null,"displayPriority":100,"isPromoted":false}],"screens":[{"id":"014-8","name":{"text":"Screen 8","translations":[]}},{"id":"014-5","name":{"text":"Screen 5","translations":[]}},{"id":"014-6","name":{"text":"Screen 6","translations":[]}},{"id":"014-3","name":{"text":"ScreenX","translations":[]}},{"id":"014-1","name":{"text":"Screen 1","translations":[]}},{"id":"014-4","name":{"text":"Screen 4","translations":[]}},{"id":"014-2","name":{"text":"Screen 2","translations":[]}},{"id":"014-7","name":{"text":"Screen 7","translations":[]}}],"events":[],"filmAdvanceBookingRules":[],"areaCategories":[{"id":"014-0000000001","name":{"text":"Standard","translations":[]},"hopk":"0000000000","isAllocatedSeating":true,"isInSeatItemDeliveryAvailable":false}]}}
@@ -17,16 +17,16 @@ describe CineworldUk::Cinema do
17
17
 
18
18
  it 'returns an Array of CineworldUK::Cinemas' do
19
19
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
20
- subject.must_be_instance_of(Array)
20
+ _(subject).must_be_instance_of(Array)
21
21
  subject.each do |value|
22
- value.must_be_instance_of(CineworldUk::Cinema)
22
+ _(value).must_be_instance_of(CineworldUk::Cinema)
23
23
  end
24
24
  end
25
25
  end
26
26
 
27
27
  it 'returns the correctly sized array' do
28
28
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
29
- subject.size.must_equal(96)
29
+ _(subject.size).must_equal(87)
30
30
  end
31
31
  end
32
32
  end
@@ -35,15 +35,15 @@ describe CineworldUk::Cinema do
35
35
  subject { described_class.new(id).adr }
36
36
 
37
37
  before do
38
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
38
+ api_response.expect(:cinema_list, cinema_list_json)
39
39
  end
40
40
 
41
- describe 'Brighton (3)' do
42
- let(:id) { 3 }
41
+ describe 'Brighton (14)' do
42
+ let(:id) { 14 }
43
43
 
44
44
  it 'returns the address hash' do
45
45
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
46
- subject.must_equal(
46
+ _(subject).must_equal(
47
47
  street_address: 'Brighton Marina Village',
48
48
  extended_address: nil,
49
49
  locality: 'Brighton',
@@ -60,15 +60,15 @@ describe CineworldUk::Cinema do
60
60
  subject { described_class.new(id).address }
61
61
 
62
62
  before do
63
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
63
+ api_response.expect(:cinema_list, cinema_list_json)
64
64
  end
65
65
 
66
- describe 'Brighton (3)' do
67
- let(:id) { 3 }
66
+ describe 'Brighton (14)' do
67
+ let(:id) { 14 }
68
68
 
69
69
  it 'returns the address hash' do
70
70
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
71
- subject.must_equal(
71
+ _(subject).must_equal(
72
72
  street_address: 'Brighton Marina Village',
73
73
  extended_address: nil,
74
74
  locality: 'Brighton',
@@ -85,15 +85,15 @@ describe CineworldUk::Cinema do
85
85
  subject { described_class.new(id).extended_address }
86
86
 
87
87
  before do
88
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
88
+ api_response.expect(:cinema_list, cinema_list_json)
89
89
  end
90
90
 
91
- describe 'Brighton (3)' do
92
- let(:id) { 3 }
91
+ describe 'Brighton (14)' do
92
+ let(:id) { 14 }
93
93
 
94
94
  it 'returns the address hash' do
95
95
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
96
- subject.must_equal('')
96
+ _(subject).must_equal('')
97
97
  end
98
98
  end
99
99
  end
@@ -105,31 +105,31 @@ describe CineworldUk::Cinema do
105
105
  before { api_response.expect(:cinema_list, cinema_list_json) }
106
106
 
107
107
  describe 'simple name (Brighton)' do
108
- let(:id) { 3 }
108
+ let(:id) { 14 }
109
109
 
110
110
  it 'returns the brand in the name' do
111
111
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
112
- subject.must_equal 'Cineworld Brighton'
112
+ _(subject).must_equal 'Cineworld Brighton'
113
113
  end
114
114
  end
115
115
  end
116
116
 
117
117
  describe 'simple name (Brighton) called with String' do
118
- let(:id) { '3' }
118
+ let(:id) { '14' }
119
119
 
120
120
  it 'returns the brand in the name' do
121
121
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
122
- subject.must_equal 'Cineworld Brighton'
122
+ _(subject).must_equal 'Cineworld Brighton'
123
123
  end
124
124
  end
125
125
  end
126
126
 
127
- describe 'complex name (Glasgow IMAX)' do
127
+ describe 'complex name (Glasgow Silverburn)' do
128
128
  let(:id) { 88 }
129
129
 
130
130
  it 'returns the brand in the name' do
131
131
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
132
- subject.must_equal 'Cineworld Glasgow: IMAX at GSC'
132
+ _(subject).must_equal 'Cineworld Glasgow: Silverburn'
133
133
  end
134
134
  end
135
135
  end
@@ -139,25 +139,25 @@ describe CineworldUk::Cinema do
139
139
  subject { described_class.new(id).locality }
140
140
 
141
141
  before do
142
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
142
+ api_response.expect(:cinema_list, cinema_list_json)
143
143
  end
144
144
 
145
- describe 'Brighton (3)' do
146
- let(:id) { 3 }
145
+ describe 'Brighton (14)' do
146
+ let(:id) { 14 }
147
147
 
148
148
  it 'returns the town/city' do
149
149
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
150
- subject.must_equal 'Brighton'
150
+ _(subject).must_equal 'Brighton'
151
151
  end
152
152
  end
153
153
  end
154
154
 
155
- describe 'London - 10 (Chelsea)' do
156
- let(:id) { 10 }
155
+ describe 'London - Leicester Square (103)' do
156
+ let(:id) { 103 }
157
157
 
158
158
  it 'returns borough of London' do
159
159
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
160
- subject.must_equal 'Chelsea'
160
+ _(subject).must_equal 'London'
161
161
  end
162
162
  end
163
163
  end
@@ -167,15 +167,15 @@ describe CineworldUk::Cinema do
167
167
  subject { described_class.new(id).postal_code }
168
168
 
169
169
  before do
170
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
170
+ api_response.expect(:cinema_list, cinema_list_json)
171
171
  end
172
172
 
173
- describe 'Brighton (3)' do
174
- let(:id) { 3 }
173
+ describe 'Brighton (14)' do
174
+ let(:id) { 14 }
175
175
 
176
176
  it 'returns the post code' do
177
177
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
178
- subject.must_equal 'BN2 5UF'
178
+ _(subject).must_equal 'BN2 5UF'
179
179
  end
180
180
  end
181
181
  end
@@ -185,25 +185,25 @@ describe CineworldUk::Cinema do
185
185
  subject { described_class.new(id).region }
186
186
 
187
187
  before do
188
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
188
+ api_response.expect(:cinema_list, cinema_list_json)
189
189
  end
190
190
 
191
- describe 'no region - Brighton (3)' do
192
- let(:id) { 3 }
191
+ describe 'no region - Brighton (14)' do
192
+ let(:id) { 14 }
193
193
 
194
194
  it 'returns the empty string is none exists' do
195
195
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
196
- subject.must_equal ''
196
+ _(subject).must_equal ''
197
197
  end
198
198
  end
199
199
  end
200
200
 
201
- describe 'London - Chelsea (10)' do
202
- let(:id) { 10 }
201
+ describe 'London - Leicester Square (103)' do
202
+ let(:id) { 103 }
203
203
 
204
204
  it 'returns "London"' do
205
205
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
206
- subject.must_equal 'London'
206
+ _(subject).must_equal 'London'
207
207
  end
208
208
  end
209
209
  end
@@ -213,15 +213,15 @@ describe CineworldUk::Cinema do
213
213
  subject { described_class.new(id).street_address }
214
214
 
215
215
  before do
216
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
216
+ api_response.expect(:cinema_list, cinema_list_json)
217
217
  end
218
218
 
219
- describe 'Brighton (3)' do
220
- let(:id) { 3 }
219
+ describe 'Brighton (14)' do
220
+ let(:id) { 14 }
221
221
 
222
222
  it 'returns the first line of the Address' do
223
223
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
224
- subject.must_equal 'Brighton Marina Village'
224
+ _(subject).must_equal 'Brighton Marina Village'
225
225
  end
226
226
  end
227
227
  end
@@ -231,15 +231,15 @@ describe CineworldUk::Cinema do
231
231
  subject { described_class.new(id).url }
232
232
 
233
233
  before do
234
- api_response.expect(:cinema_detail, cinema_detail_json(id), [id])
234
+ api_response.expect(:cinema_list, cinema_list_json)
235
235
  end
236
236
 
237
- describe 'Brighton (3)' do
238
- let(:id) { 3 }
237
+ describe 'Brighton (14)' do
238
+ let(:id) { 14 }
239
239
 
240
240
  it 'returns the url' do
241
241
  CineworldUk::Internal::ApiResponse.stub :new, api_response do
242
- subject.must_equal(
242
+ _(subject).must_equal(
243
243
  "http://www.cineworld.co.uk/cinemas/#{id}/information"
244
244
  )
245
245
  end
@@ -1,88 +1,131 @@
1
- require_relative '../../../test_helper'
2
- require_relative '../../../support/fixture_reader'
3
-
4
- describe CineworldUk::Internal::ApiResponse do
5
- include Support::FixtureReader
6
-
7
- let(:described_class) { CineworldUk::Internal::ApiResponse }
8
- let(:standard) { 'key=ios&territory=GB' }
9
-
10
- before { WebMock.disable_net_connect! }
11
- after { WebMock.allow_net_connect! }
12
-
13
- describe '#cinema_list' do
14
- subject { described_class.new.cinema_list }
15
-
16
- before { stub_get("cinema/list?full=true&#{standard}", cinema_list_json) }
17
-
18
- it 'returns a string' do
19
- subject.class.must_equal String
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+
5
+ # This block groups independent Minitest cases, not application logic.
6
+ describe "Vista retrieval" do # rubocop:disable Metrics/BlockLength
7
+ before do
8
+ CineworldUk.token = "test-guest-token"
9
+ [CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
10
+ klass.instance_variables.each do |name|
11
+ klass.remove_instance_variable(name)
12
+ end
20
13
  end
14
+ fixture = "../../../fixtures/api/vista-showtimes-brighton.json"
15
+ @capture = JSON.parse(File.read(File.expand_path(fixture, __dir__)))
16
+ @base = "https://digital-api.cineworld.co.uk/ocapi/v1/"
17
+ sites = @capture.fetch("relatedData").fetch("sites")
18
+ stub_request(:get, @base + "sites").with(
19
+ headers: {
20
+ "Authorization" => "Bearer test-guest-token",
21
+ "Connect-Region-Code" => "GB"
22
+ }
23
+ ).to_return(body: JSON.generate("sites" => sites))
21
24
  end
22
25
 
23
- describe '#cinema_detail(cinema_id, date)' do
24
- subject { described_class.new.cinema_detail(3) }
25
-
26
- before do
27
- stub_get("cinema/detail?cinema=3&#{standard}", cinema_detail_json(3))
28
- end
29
-
30
- it 'returns a string' do
31
- subject.class.must_equal String
26
+ after do
27
+ CineworldUk.token = nil
28
+ [CineworldUk::Cinema, CineworldUk::Performance].each do |klass|
29
+ klass.instance_variables.each do |name|
30
+ klass.remove_instance_variable(name)
31
+ end
32
32
  end
33
33
  end
34
34
 
35
- describe '#dates(cinema_id)' do
36
- subject { described_class.new.dates(3) }
37
-
38
- before { stub_get("dates?cinema=3&#{standard}", dates_json(3)) }
39
-
40
- it 'returns a string' do
41
- subject.class.must_equal String
42
- end
35
+ def stub_day(day, body)
36
+ stub_request(:get, @base + "showtimes/by-business-date/#{day}").with(
37
+ query: { "siteIds" => "014" },
38
+ headers: { "Authorization" => "Bearer test-guest-token",
39
+ "Connect-Region-Code" => "GB" }
40
+ ).to_return(body: JSON.generate(body))
43
41
  end
44
42
 
45
- describe '#film_list' do
46
- subject { described_class.new.film_list }
47
-
48
- before { stub_get("film/list?full=true&#{standard}", film_list_json) }
49
-
50
- it 'returns a string' do
51
- subject.class.must_equal String
52
- end
43
+ it "retrieves cinemas and advertised session times from Vista" do
44
+ stub_day("2026-10-14", @capture)
45
+ cinema = CineworldUk::Cinema.all.first
46
+ _(cinema.id).must_equal 14
47
+ _(cinema.name).must_equal "Brighton"
48
+ _(cinema.postal_code).must_equal "BN2 5UF"
49
+ _(cinema.street_address).must_equal "Brighton Marina Village"
50
+ assert_requested :get, @base + "sites", times: 1
51
+ performances = CineworldUk::Performance.at(
52
+ 14, from: Date.new(2026, 10, 14), to: Date.new(2026, 10, 14)
53
+ )
54
+ _(performances.size).must_equal 17
55
+ first = performances.first
56
+ _(first.film_name).must_equal "Other Mommy"
57
+ _(first.starting_at).must_equal Time.utc(2026, 10, 14, 14, 40)
58
+ _(first.booking_url).must_equal(
59
+ "https://web.cineworld.co.uk/order/showtimes/014-165507/seats"
60
+ )
61
+ _(first.variant).must_include "audio_described"
62
+ _(first.dimension).must_equal "2d"
63
+ assert_requested :get, @base + "sites", times: 1
64
+ assert_requested :get, @base + "showtimes/by-business-date/2026-10-14",
65
+ query: { "siteIds" => "014" }, times: 1
53
66
  end
54
67
 
55
- describe '#film_list_comingsoon' do
56
- subject { described_class.new.film_list_comingsoon }
57
-
58
- before do
59
- stub_get("film/list/comingsoon?full=true&#{standard}",
60
- film_list_comingsoon_json)
61
- end
68
+ it "keeps format session-specific and includes the last requested day" do
69
+ @capture["showtimes"] =
70
+ [@capture["showtimes"].first,
71
+ Marshal.load(Marshal.dump(@capture["showtimes"].first))]
72
+ @capture["showtimes"][1]["id"] = "014-second"
73
+ @capture["showtimes"][1]["requires3dGlasses"] = true
74
+ @capture["showtimes"][1]["schedule"]["startsAt"] =
75
+ "2026-10-25T16:40:00+00:00"
76
+ stub_day("2026-10-24",
77
+ "showtimes" => [],
78
+ "relatedData" => { "films" => [], "attributes" => [],
79
+ "sites" => [] })
80
+ stub_day("2026-10-25", @capture)
81
+ results = CineworldUk::Performance.at("014", from: Date.new(2026, 10, 24),
82
+ to: Date.new(2026, 10, 25))
83
+ _(results.map(&:dimension)).must_equal %w[2d 3d]
84
+ _(results.last.starting_at).must_equal Time.utc(2026, 10, 25, 16, 40)
85
+ _(results.first.cinema_name).must_equal "Brighton"
86
+ assert_not_requested :get, @base + "sites"
87
+ end
62
88
 
63
- it 'returns a string' do
64
- subject.class.must_equal String
89
+ it "rejects missing and rejected tokens without exposing credentials" do
90
+ CineworldUk.token = nil
91
+ assert_raises(CineworldUk::AuthenticationError) { CineworldUk::Cinema.all }
92
+ CineworldUk.token = "expired-secret"
93
+ stub_request(:get, @base + "sites").to_return(status: 401,
94
+ body: "expired-secret")
95
+ error = assert_raises(CineworldUk::AuthenticationError) do
96
+ CineworldUk::Cinema.all
65
97
  end
98
+ refute_includes error.message, "expired-secret"
99
+ CineworldUk.token = "replacement"
100
+ stub_request(:get, @base + "sites").with(
101
+ headers: { "Authorization" => "Bearer replacement" }
102
+ ).to_return(body: '{"sites":[]}')
103
+ _(CineworldUk::Cinema.all).must_equal []
66
104
  end
67
105
 
68
- describe '#performances(cinema_id, date)' do
69
- subject { described_class.new.performances(3, Date.today + 1) }
70
-
71
- before do
72
- stub_get("performances?cinema=3&date=#{tomorrow_s}&#{standard}",
73
- performances_tomorrow_json(3))
106
+ it "defaults to fourteen days and rejects reversed ranges" do
107
+ day = Date.new(2026, 10, 14)
108
+ (day..day + 13).each do |date|
109
+ stub_day(date.iso8601,
110
+ "showtimes" => [],
111
+ "relatedData" => { "films" => [], "attributes" => [],
112
+ "sites" => [] })
74
113
  end
75
-
76
- it 'returns a string' do
77
- subject.class.must_equal String
114
+ _(CineworldUk::Performance.at(14, from: day)).must_equal []
115
+ assert_requested :get, %r{showtimes/by-business-date/}, times: 14
116
+ assert_raises(ArgumentError) do
117
+ CineworldUk::Performance.at(14, from: day, to: day - 1)
78
118
  end
119
+ assert_not_requested :get, @base + "sites"
79
120
  end
80
121
 
81
- private
82
-
83
- def stub_get(site_path, response_body)
84
- url = "https://www.cineworld.co.uk/api/#{site_path}"
85
- response = { status: 200, body: response_body, headers: {} }
86
- stub_request(:get, url).to_return(response)
122
+ it "does not follow redirects or hide forbidden responses" do
123
+ [302, 403].each do |status|
124
+ stub_request(:get, @base + "sites").to_return(
125
+ status: status, headers: { "Location" => "https://example.com/" }
126
+ )
127
+ error = assert_raises(RuntimeError) { CineworldUk::Cinema.all }
128
+ assert_includes error.message, status.to_s
129
+ end
87
130
  end
88
131
  end