diff --git a/.gitignore b/.gitignore index 18b43c9cd2..c93b66abd8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ # Ignore master key for decrypting credentials and more. /config/master.key +/coverage +# Ignore application configuration +/config/application.yml diff --git a/Gemfile b/Gemfile index 2e86ac3e16..dfd309f605 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,7 @@ gem 'jbuilder', '~> 2.5' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use ActiveStorage variant # gem 'mini_magick', '~> 4.8' @@ -34,25 +34,39 @@ gem 'jbuilder', '~> 2.5' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false +gem 'faraday' + +gem 'email_validator' + +gem 'bootstrap-sass', '~> 3.2.0' + +gem 'jquery-rails' + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'figaro' gem 'pry' end group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. - gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' gem 'rubocop-rails' + gem 'web-console', '>= 3.3.0' end group :test do - gem 'rspec-rails' gem 'capybara' + gem 'database_cleaner' + gem 'factory_bot_rails', '~> 4.0' + gem 'faker' gem 'launchy' + gem 'orderly' + gem 'rspec-rails' + gem 'shoulda-matchers', '~> 3.1' gem 'simplecov' + gem 'webmock' end - # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] +gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] diff --git a/Gemfile.lock b/Gemfile.lock index c8ab2fd7c1..106c4bf2a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,9 +46,12 @@ GEM public_suffix (>= 2.0.2, < 5.0) arel (9.0.0) ast (2.4.2) + bcrypt (3.1.18) bindex (0.8.1) bootsnap (1.9.1) msgpack (~> 1.0) + bootstrap-sass (3.2.0.4) + sass (~> 3.2) builder (3.2.4) capybara (3.36.0) addressable @@ -68,18 +71,46 @@ GEM execjs coffee-script-source (1.12.2) concurrent-ruby (1.1.9) + crack (0.4.5) + rexml crass (1.0.6) + database_cleaner (2.0.1) + database_cleaner-active_record (~> 2.0.0) + database_cleaner-active_record (2.0.1) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) diff-lcs (1.4.4) docile (1.4.0) + email_validator (2.2.4) + activemodel erubi (1.10.0) execjs (2.8.1) + factory_bot (4.11.1) + activesupport (>= 3.0.0) + factory_bot_rails (4.11.1) + factory_bot (~> 4.11.1) + railties (>= 3.0.0) + faker (3.1.0) + i18n (>= 1.8.11, < 2) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) ffi (1.15.4) + figaro (1.2.0) + thor (>= 0.14.0, < 2) globalid (0.5.2) activesupport (>= 5.0) + hashdiff (1.0.1) i18n (1.8.11) concurrent-ruby (~> 1.0) jbuilder (2.11.3) activesupport (>= 5.0.0) + jquery-rails (4.5.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) launchy (2.5.0) addressable (~> 2.7) listen (3.1.5) @@ -104,6 +135,9 @@ GEM racc (~> 1.4) nokogiri (1.12.5-arm64-darwin) racc (~> 1.4) + orderly (0.1.1) + capybara (>= 1.1) + rspec (>= 2.14) parallel (1.21.0) parser (3.0.2.0) ast (~> 2.4.1) @@ -148,6 +182,10 @@ GEM ffi (~> 1.0) regexp_parser (2.1.1) rexml (3.2.5) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) rspec-core (3.10.1) rspec-support (~> 3.10.0) rspec-expectations (3.10.1) @@ -181,6 +219,7 @@ GEM rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) ruby_dep (1.5.0) sass (3.7.4) sass-listen (~> 4.0.0) @@ -193,6 +232,8 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + shoulda-matchers (3.1.3) + activesupport (>= 4.0.0) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -219,6 +260,10 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) + webmock (3.18.1) + addressable (>= 2.8.0) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) websocket-driver (0.7.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -230,12 +275,22 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) bootsnap (>= 1.1.0) + bootstrap-sass (~> 3.2.0) capybara coffee-rails (~> 4.2) + database_cleaner + email_validator + factory_bot_rails (~> 4.0) + faker + faraday + figaro jbuilder (~> 2.5) + jquery-rails launchy listen (>= 3.0.5, < 3.2) + orderly pg (>= 0.18, < 2.0) pry puma (~> 3.11) @@ -243,10 +298,12 @@ DEPENDENCIES rspec-rails rubocop-rails sass-rails (~> 5.0) + shoulda-matchers (~> 3.1) simplecov tzinfo-data uglifier (>= 1.3.0) web-console (>= 3.3.0) + webmock RUBY VERSION ruby 2.7.4p191 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 43ba7e9701..2967dafe64 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,3 +13,6 @@ //= require rails-ujs //= require activestorage //= require_tree . + +//= require jquery +//= require bootstrap-sprockets diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.scss similarity index 92% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.scss index d05ea0f511..092592b5ed 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.scss @@ -13,3 +13,6 @@ *= require_tree . *= require_self */ + + @import "bootstrap-sprockets"; + @import "bootstrap"; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d12ab..9c000d85d1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,14 @@ class ApplicationController < ActionController::Base + helper_method :current_user + + def current_user + @user ||= User.find(session[:user_id]) if session[:user_id] + end + + def validate_user + unless current_user + flash[:error] = ['User must be logged in'] + redirect_to root_path + end + end end diff --git a/app/controllers/discover_controller.rb b/app/controllers/discover_controller.rb new file mode 100644 index 0000000000..338d6706b1 --- /dev/null +++ b/app/controllers/discover_controller.rb @@ -0,0 +1,4 @@ +class DiscoverController < ApplicationController + def index + end +end \ No newline at end of file diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb new file mode 100644 index 0000000000..6d96dde7b8 --- /dev/null +++ b/app/controllers/movies_controller.rb @@ -0,0 +1,26 @@ +class MoviesController < ApplicationController + def index + if params[:q] == 'top rated' + + @movies = MovieFacade.find_movies("discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000") + + elsif params[:commit] + + @movies = MovieFacade.find_movies("search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=#{params[:title]}&page=1&include_adult=false") + + end + + if @movies.is_a?(Hash) && (@movies[:success] == false) + flash[:error] = @movies[:errors] + redirect_to discover_index_path + end + end + + def show + @movie = MovieFacade.find_movie(params[:id]) + + @cast = MovieFacade.find_cast(params[:id]) + + @reviews = MovieFacade.find_reviews(params[:id]) + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000000..925c956aef --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,21 @@ +class SessionsController < ApplicationController + def new + + end + + def create + user = User.find_by(email: params[:email]) + if user&.authenticate(params[:password]) + session[:user_id] = user.id + redirect_to user_path(user) + else + flash[:error] = ['Login Failed'] + render :new + end + end + + def delete + session.clear + redirect_to root_path + end +end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000000..44b4a54265 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,27 @@ +class UsersController < ApplicationController + def new + @user = User.new + end + + def create + user = User.new(user_params) + + if user.save + session[:user_id] = user.id + redirect_to user_path(user) + else + flash[:error] = user.errors.full_messages + render :new + end + end + + def show + validate_user + end + + private + + def user_params + params.require(:user).permit(:name, :email, :password, :password_confirmation) + end +end diff --git a/app/controllers/viewing_parties_controller.rb b/app/controllers/viewing_parties_controller.rb new file mode 100644 index 0000000000..3d0a93552c --- /dev/null +++ b/app/controllers/viewing_parties_controller.rb @@ -0,0 +1,41 @@ +class ViewingPartiesController < ApplicationController + def new + @viewing_party = ViewingParty.new + if @user = current_user + @users = @user.all_but_self + @movie = MovieFacade.find_movie(params[:movie_id]) + else + flash[:error] = ['User must be logged in'] + redirect_to movie_path(params[:movie_id]) + end + end + + def create + @viewing_party = ViewingParty.new(viewing_party_params) + + if @viewing_party.save + redirect_to user_path(@user) + else + flash[:error] = @viewing_party.errors.full_messages + redirect_to new_movie_viewing_party_path(params[:movie_id]) + end + + create_user_viewing_parties + end + + private + + def create_user_viewing_parties + UserViewingParty.create({ user_id: current_user.id, viewing_party_id: @viewing_party.id, hosting: true }) + params[:viewing_party].each do |key, value| + if value == '1' && key != :movie_id + UserViewingParty.create({ user_id: key, viewing_party_id: @viewing_party.id }) + end + end + end + + def viewing_party_params + params[:viewing_party][:movie_id] = params[:movie_id] + params.require(:viewing_party).permit(:duration, :start_time, :date, :movie_id) + end +end diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb new file mode 100644 index 0000000000..ab4d827961 --- /dev/null +++ b/app/controllers/welcome_controller.rb @@ -0,0 +1,5 @@ +class WelcomeController < ApplicationController + def index + @users = User.all + end +end diff --git a/app/facades/movie_facade.rb b/app/facades/movie_facade.rb new file mode 100644 index 0000000000..27f1e20703 --- /dev/null +++ b/app/facades/movie_facade.rb @@ -0,0 +1,39 @@ +class MovieFacade + def self.find_movie(movie_id) + @movie = Movie.new(filter_movies("movie/#{movie_id}?api_key=#{ENV['movie_api_key']}&language=en-US")) + end + + def self.find_cast(movie_id) + @cast = filter_credits("movie/#{movie_id}/credits?api_key=#{ENV['movie_api_key']}&language=en-US").map do |character| + Character.new(character) + end + end + + def self.find_reviews(movie_id) + @reviews = filter_response("movie/#{movie_id}/reviews?api_key=#{ENV['movie_api_key']}&language=en-US").map do |review| + Review.new(review) + end + end + + def self.find_movies(uri) + if filter_movies(uri)[:success] == false + filter_movies(uri) + else + filter_movies(uri)[:results].map do |movie| + Movie.new(movie) + end + end + end + + def self.filter_movies(uri) + MovieService.parse_response(uri) + end + + def self.filter_response(uri) + MovieService.parse_response(uri)[:results] + end + + def self.filter_credits(uri) + MovieService.parse_response(uri)[:cast][0..9] + end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000000..d497459f22 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,18 @@ +class User < ApplicationRecord + has_many :user_viewing_parties + has_many :viewing_parties, through: :user_viewing_parties + + validates_presence_of :name, :email, :password + validates :email, uniqueness: { case_sensitive: false, message: 'User already exists with given email' } + validates :email, email: true + + has_secure_password + + def find_user_viewing_party(viewing_party) + user_viewing_parties.find_by(viewing_party_id: viewing_party.id) + end + + def all_but_self + User.where.not(id: id) + end +end diff --git a/app/models/user_viewing_party.rb b/app/models/user_viewing_party.rb new file mode 100644 index 0000000000..b8ab9167d1 --- /dev/null +++ b/app/models/user_viewing_party.rb @@ -0,0 +1,6 @@ +class UserViewingParty < ApplicationRecord + belongs_to :viewing_party + belongs_to :user + + validates_inclusion_of :hosting, in: [true, false] +end diff --git a/app/models/viewing_party.rb b/app/models/viewing_party.rb new file mode 100644 index 0000000000..7f6283e30a --- /dev/null +++ b/app/models/viewing_party.rb @@ -0,0 +1,20 @@ +class ViewingParty < ApplicationRecord + has_many :user_viewing_parties + has_many :users, through: :user_viewing_parties + + validates_numericality_of :duration + validates_presence_of :date, :duration, :movie_id, :start_time + validates_with DurationValidator + + def movie + MovieFacade.find_movie(movie_id) if movie_id + end + + def host + user_viewing_parties.find_by(hosting: true).user.name + end + + def invitees + user_viewing_parties.filter_map { |uvp| uvp.user.name unless uvp.hosting } + end +end diff --git a/app/poros/character.rb b/app/poros/character.rb new file mode 100644 index 0000000000..04577f95a9 --- /dev/null +++ b/app/poros/character.rb @@ -0,0 +1,8 @@ +class Character + attr_reader :actor, :character + + def initialize(data) + @actor = data[:name] + @character = data[:character] + end +end diff --git a/app/poros/movie.rb b/app/poros/movie.rb new file mode 100644 index 0000000000..03834e0c6b --- /dev/null +++ b/app/poros/movie.rb @@ -0,0 +1,29 @@ +class Movie + attr_reader :id, + :title, + :vote_average, + :runtime, + :genres, + :hours_mins, + :image, + :overview + + def initialize(data) + @id = data[:id] + @title = data[:title] + @vote_average = data[:vote_average] + @runtime = data[:runtime] + @overview = data[:overview] + @hours_mins = convert_runtime(data[:runtime]) if data[:runtime] + @genres = convert_genres(data[:genres]) if data[:genres] + @image = data[:poster_path] + end + + def convert_runtime(runtime) + "#{runtime / 60}hr #{runtime % 60}min" + end + + def convert_genres(genres) + genres.map { |genre| genre[:name] } + end +end diff --git a/app/poros/review.rb b/app/poros/review.rb new file mode 100644 index 0000000000..5ae8cc9b64 --- /dev/null +++ b/app/poros/review.rb @@ -0,0 +1,12 @@ +class Review + attr_reader :author, :content + + def initialize(data) + @author = data[:author] + @content = content_strip(data[:content]) + end + + def content_strip(content) + content.gsub('’', "'") + end +end diff --git a/app/services/movie_service.rb b/app/services/movie_service.rb new file mode 100644 index 0000000000..151e76124e --- /dev/null +++ b/app/services/movie_service.rb @@ -0,0 +1,13 @@ +class MovieService + def self.connection + Faraday.new(url: 'https://api.themoviedb.org/3') + end + + def self.parse_response(uri) + JSON.parse(response(uri).body, symbolize_names: true) + end + + def self.response(uri) + connection.get(uri) + end +end diff --git a/app/validators/duration_validator.rb b/app/validators/duration_validator.rb new file mode 100644 index 0000000000..747948ee9e --- /dev/null +++ b/app/validators/duration_validator.rb @@ -0,0 +1,7 @@ +class DurationValidator < ActiveModel::Validator + def validate(record) + if record.movie_id && (record.duration < record.movie.runtime) + record.errors.add :base, 'Duration must be greater than or equal to movie runtime' + end + end +end diff --git a/app/views/discover/index.html.erb b/app/views/discover/index.html.erb new file mode 100644 index 0000000000..4243900d4e --- /dev/null +++ b/app/views/discover/index.html.erb @@ -0,0 +1,13 @@ +
+
+ + <%= form_with url: movies_path, method: :get, local: true do |form| %> + <%= form.text_field :title %> + <%= form.submit "Find Movies" %> + <% end %> +
+
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 1379b8373b..5774cf732b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -10,6 +10,74 @@ +
+ <% if flash[:error] %> +
+ <% flash[:error].each do |msg| %> + <%= msg %>
+ <% end %> +
+ <% end %> +
+ +

Viewing Party

+
+
<%= yield %> + diff --git a/app/views/movies/index.html.erb b/app/views/movies/index.html.erb new file mode 100644 index 0000000000..74aff47b84 --- /dev/null +++ b/app/views/movies/index.html.erb @@ -0,0 +1,13 @@ +
+
+ + <% @movies.each do |movie| %> +
+ <%= link_to "#{movie.title}", movie_path(movie.id), class: 'col-sm-6' %> +
Vote Average: <%= movie.vote_average %>
+
+ <% end %> +
+
\ No newline at end of file diff --git a/app/views/movies/show.html.erb b/app/views/movies/show.html.erb new file mode 100644 index 0000000000..fb4af333d0 --- /dev/null +++ b/app/views/movies/show.html.erb @@ -0,0 +1,60 @@ +
+ +
+
+

Summary:

+

<%= @movie.overview %>

+ +

Cast:

+ + + + + + + + + <% @cast.each do |character| %> + + + + + <% end %> + +
Character NameActor Name
<%= character.character %><%= character.actor %>
+ +

<%= @reviews.count %> Reviews:

+ <% @reviews.each do |review| %> +
Written By: <%= review.author %>
+
Review:
+

<%= review.content %>

+ <% end %> +
\ No newline at end of file diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000000..731de71655 --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,15 @@ +
+

Log In

+ + <%= form_with url: login_path, method: :post, local: true do |form| %> +
+ <%= form.label :email, 'Email:' %> + <%= form.email_field :email, class: 'form-control' %> +
+
+ <%= form.label :password, 'Password:' %> + <%= form.password_field :password, class: 'form-control' %> +
+ <%= form.submit value: 'Log In', class: 'btn btn-default' %> + <% end %> +
\ No newline at end of file diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 0000000000..20d053c729 --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,23 @@ +
+

Register a New User

+ + <%= form_with model: @user, local: true do |form| %> +
+ <%= form.label :name, 'Name:' %> + <%= form.text_field :name, class: 'form-control' %> +
+
+ <%= form.label :email, 'Email:' %> + <%= form.email_field :email, class: 'form-control' %> +
+
+ <%= form.label :password, 'Password:' %> + <%= form.password_field :password, class: 'form-control' %> +
+
+ <%= form.label :password_confirmation, 'Confirm Password:' %> + <%= form.password_field :password_confirmation, class: 'form-control' %> +
+ <%= form.submit value: 'Register', class: 'btn btn-default' %> + <% end %> +
\ No newline at end of file diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 0000000000..7b4cf4f9f0 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1,59 @@ +
+
+ +
+
+ + +
+
+
+
+

Invited Parties

+ <% current_user.viewing_parties.each do |party| %> + <% unless current_user.find_user_viewing_party(party).hosting %> +
+ <% movie = party.movie %> + <%= image_tag "https://image.tmdb.org/t/p/w200/#{movie.image}" %>
+

<%= link_to movie.title, movie_path(movie.id) %>

+
Date:
<%= party.date.strftime('%-m/%-d/%Y') %>
+
Start Time:
<%= party.start_time.strftime('%l:%M %p') %>
+
Host:
<%= party.host %> +
Invitees:
+ <% party.invitees.each do |invitee| %> + <%= invitee %>
+ <% end %> +
+ <% end %> + <% end %> +
+
+
+ +
+
+
+

Hosted Parties

+ <% current_user.viewing_parties.each do |party| %> + <% if current_user.find_user_viewing_party(party).hosting %> +
+ <% movie = party.movie %> + <%= image_tag "https://image.tmdb.org/t/p/w200/#{movie.image}" %>
+

<%= link_to movie.title, movie_path(movie.id) %>

+
Date:
<%= party.date.strftime('%-m/%-d/%Y') %>
+
Start Time:
<%= party.start_time.strftime('%l:%M %p') %>
+
You are the Host
+
Invitees:
+ <% party.invitees.each do |invitee| %> + <%= invitee %>
+ <% end %> +
+ <% end %> + <% end %> +
+
+
+
diff --git a/app/views/viewing_parties/new.html.erb b/app/views/viewing_parties/new.html.erb new file mode 100644 index 0000000000..44a88cd2bd --- /dev/null +++ b/app/views/viewing_parties/new.html.erb @@ -0,0 +1,36 @@ +
+ + Movie Title: <%= @movie.title %> + <%= form_with model: [:movie, @viewing_party], local: true do |form| %> +
+ <%= form.label :duration, 'Duration of Party:' %> + <%= form.number_field :duration, value: @movie.runtime, min: @movie.runtime, class: 'form-control' %> +
+
+ <%= form.label :date, 'Day:' %> + <%= form.date_field :date, class: 'form-control' %> +
+
+ <%= form.label :start_time, 'Start Time:' %> + <%= form.time_field :start_time, class: 'form-control' %> +
+
+

Invite Other Users

+ <% @users.each do |user| %> +
+ <%= form.check_box :"#{user.id}" %> + <%= form.label :"#{user.id}", "#{user.name} (#{user.email})" %> +
+ <% end %> +
+ <%= form.submit value: 'Create Viewing Party', class: 'btn btn-default' %> + <% end %> +
diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb new file mode 100644 index 0000000000..1fafac9f82 --- /dev/null +++ b/app/views/welcome/index.html.erb @@ -0,0 +1,26 @@ +
+
+ +

Existing Users

+
+ <% if current_user %> + + + + + + + + + <% @users.each do |user| %> + + + + + <% end %> + +
NameEmail
<%= user.name %><%= user.email %>
+ <% end %> +
\ No newline at end of file diff --git a/bin/yarn b/bin/yarn index 460dd565b4..d3627c34cb 100755 --- a/bin/yarn +++ b/bin/yarn @@ -1,11 +1,9 @@ #!/usr/bin/env ruby APP_ROOT = File.expand_path('..', __dir__) Dir.chdir(APP_ROOT) do - begin - exec "yarnpkg", *ARGV - rescue Errno::ENOENT - $stderr.puts "Yarn executable was not detected in the system." - $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" - exit 1 - end + exec 'yarnpkg', *ARGV +rescue Errno::ENOENT + warn 'Yarn executable was not detected in the system.' + warn 'Download Yarn at https://yarnpkg.com/en/docs/install' + exit 1 end diff --git a/config/application.rb b/config/application.rb index 793027a648..0739dfb07b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,16 +1,16 @@ require_relative 'boot' -require "rails" +require 'rails' # Pick the frameworks you want: -require "active_model/railtie" -require "active_job/railtie" -require "active_record/railtie" -require "active_storage/engine" -require "action_controller/railtie" -require "action_mailer/railtie" -require "action_view/railtie" -require "action_cable/engine" -require "sprockets/railtie" +require 'active_model/railtie' +require 'active_job/railtie' +require 'active_record/railtie' +require 'active_storage/engine' +require 'action_controller/railtie' +require 'action_mailer/railtie' +require 'action_view/railtie' +require 'action_cable/engine' +require 'sprockets/railtie' # require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems diff --git a/config/environments/production.rb b/config/environments/production.rb index fa9e9d9f52..792ce6d351 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -54,7 +54,7 @@ config.log_level = :debug # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -83,7 +83,7 @@ # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV["RAILS_LOG_TO_STDOUT"].present? + if ENV['RAILS_LOG_TO_STDOUT'].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) diff --git a/config/puma.rb b/config/puma.rb index b2102072b0..0a5ce17c44 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -4,19 +4,19 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT') { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together diff --git a/config/routes.rb b/config/routes.rb index 787824f888..2d69ec8371 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,21 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root 'welcome#index' + + get '/register', to: 'users#new', as: :new_user + + get '/dashboard', to: 'users#show', as: :user + + get '/login', to: 'sessions#new', as: :users_login + + delete '/logout', to: 'sessions#delete', as: :logout + + post '/login', to: 'sessions#create' + + resources :users, only: [:index, :create] + + resources :discover, only: :index + resources :movies, only: %i[index show] do + resources :viewing_parties, only: %i[new create] + end end diff --git a/db/migrate/20230130232608_create_users.rb b/db/migrate/20230130232608_create_users.rb new file mode 100644 index 0000000000..5fd691b5d3 --- /dev/null +++ b/db/migrate/20230130232608_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration[5.2] + def change + create_table :users do |t| + t.string :name + t.string :email + + t.timestamps + end + end +end diff --git a/db/migrate/20230130232640_create_movies.rb b/db/migrate/20230130232640_create_movies.rb new file mode 100644 index 0000000000..52b13af47b --- /dev/null +++ b/db/migrate/20230130232640_create_movies.rb @@ -0,0 +1,12 @@ +class CreateMovies < ActiveRecord::Migration[5.2] + def change + create_table :movies do |t| + t.string :title + t.float :vote_average + t.integer :runtime + t.string :summary + + t.timestamps + end + end +end diff --git a/db/migrate/20230130232826_create_reviews.rb b/db/migrate/20230130232826_create_reviews.rb new file mode 100644 index 0000000000..5cd00af1b8 --- /dev/null +++ b/db/migrate/20230130232826_create_reviews.rb @@ -0,0 +1,11 @@ +class CreateReviews < ActiveRecord::Migration[5.2] + def change + create_table :reviews do |t| + t.string :author + t.string :text + t.references :movie, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230130232851_create_actors.rb b/db/migrate/20230130232851_create_actors.rb new file mode 100644 index 0000000000..c4dddad315 --- /dev/null +++ b/db/migrate/20230130232851_create_actors.rb @@ -0,0 +1,9 @@ +class CreateActors < ActiveRecord::Migration[5.2] + def change + create_table :actors do |t| + t.string :name + + t.timestamps + end + end +end diff --git a/db/migrate/20230130232926_create_actor_movies.rb b/db/migrate/20230130232926_create_actor_movies.rb new file mode 100644 index 0000000000..b09d84b791 --- /dev/null +++ b/db/migrate/20230130232926_create_actor_movies.rb @@ -0,0 +1,11 @@ +class CreateActorMovies < ActiveRecord::Migration[5.2] + def change + create_table :actor_movies do |t| + t.string :character + t.references :movie, foreign_key: true + t.references :actor, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230130233118_create_viewing_parties.rb b/db/migrate/20230130233118_create_viewing_parties.rb new file mode 100644 index 0000000000..1c4110530b --- /dev/null +++ b/db/migrate/20230130233118_create_viewing_parties.rb @@ -0,0 +1,12 @@ +class CreateViewingParties < ActiveRecord::Migration[5.2] + def change + create_table :viewing_parties do |t| + t.datetime :when + t.integer :duration + t.references :user, foreign_key: true + t.references :movie, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230130233257_create_user_viewing_parties.rb b/db/migrate/20230130233257_create_user_viewing_parties.rb new file mode 100644 index 0000000000..3c0136ab91 --- /dev/null +++ b/db/migrate/20230130233257_create_user_viewing_parties.rb @@ -0,0 +1,10 @@ +class CreateUserViewingParties < ActiveRecord::Migration[5.2] + def change + create_table :user_viewing_parties do |t| + t.references :viewing_party, foreign_key: true + t.references :user, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20230131001243_remove_user_from_viewing_parties.rb b/db/migrate/20230131001243_remove_user_from_viewing_parties.rb new file mode 100644 index 0000000000..fb64f179ee --- /dev/null +++ b/db/migrate/20230131001243_remove_user_from_viewing_parties.rb @@ -0,0 +1,5 @@ +class RemoveUserFromViewingParties < ActiveRecord::Migration[5.2] + def change + remove_foreign_key :viewing_parties, :users + end +end diff --git a/db/migrate/20230131001522_add_host_to_viewing_parties.rb b/db/migrate/20230131001522_add_host_to_viewing_parties.rb new file mode 100644 index 0000000000..e852ea860f --- /dev/null +++ b/db/migrate/20230131001522_add_host_to_viewing_parties.rb @@ -0,0 +1,5 @@ +class AddHostToViewingParties < ActiveRecord::Migration[5.2] + def change + add_column :viewing_parties, :host, :integer + end +end diff --git a/db/migrate/20230131001620_remove_user_id_from_viewing_parties.rb b/db/migrate/20230131001620_remove_user_id_from_viewing_parties.rb new file mode 100644 index 0000000000..385b8c884d --- /dev/null +++ b/db/migrate/20230131001620_remove_user_id_from_viewing_parties.rb @@ -0,0 +1,5 @@ +class RemoveUserIdFromViewingParties < ActiveRecord::Migration[5.2] + def change + remove_column :viewing_parties, :user_id + end +end diff --git a/db/migrate/20230131175115_delete_reviews.rb b/db/migrate/20230131175115_delete_reviews.rb new file mode 100644 index 0000000000..b6bf4a276f --- /dev/null +++ b/db/migrate/20230131175115_delete_reviews.rb @@ -0,0 +1,5 @@ +class DeleteReviews < ActiveRecord::Migration[5.2] + def change + drop_table :reviews + end +end diff --git a/db/migrate/20230131175126_delete_actor_movies.rb b/db/migrate/20230131175126_delete_actor_movies.rb new file mode 100644 index 0000000000..40c6f3d189 --- /dev/null +++ b/db/migrate/20230131175126_delete_actor_movies.rb @@ -0,0 +1,5 @@ +class DeleteActorMovies < ActiveRecord::Migration[5.2] + def change + drop_table :actor_movies + end +end diff --git a/db/migrate/20230131175351_drop_movies.rb b/db/migrate/20230131175351_drop_movies.rb new file mode 100644 index 0000000000..a0bf60f83b --- /dev/null +++ b/db/migrate/20230131175351_drop_movies.rb @@ -0,0 +1,6 @@ +class DropMovies < ActiveRecord::Migration[5.2] + def change + remove_foreign_key :viewing_parties, column: :movie_id + drop_table :movies + end +end diff --git a/db/migrate/20230131175400_drop_actors.rb b/db/migrate/20230131175400_drop_actors.rb new file mode 100644 index 0000000000..98d7d535d2 --- /dev/null +++ b/db/migrate/20230131175400_drop_actors.rb @@ -0,0 +1,5 @@ +class DropActors < ActiveRecord::Migration[5.2] + def change + drop_table :actors + end +end diff --git a/db/migrate/20230131175602_remove_movie_id_from_viewing_parties.rb b/db/migrate/20230131175602_remove_movie_id_from_viewing_parties.rb new file mode 100644 index 0000000000..188d2d52fe --- /dev/null +++ b/db/migrate/20230131175602_remove_movie_id_from_viewing_parties.rb @@ -0,0 +1,5 @@ +class RemoveMovieIdFromViewingParties < ActiveRecord::Migration[5.2] + def change + remove_column :viewing_parties, :movie_id + end +end diff --git a/db/migrate/20230131203503_change_when_from_viewing_parties.rb b/db/migrate/20230131203503_change_when_from_viewing_parties.rb new file mode 100644 index 0000000000..53cd2b0654 --- /dev/null +++ b/db/migrate/20230131203503_change_when_from_viewing_parties.rb @@ -0,0 +1,5 @@ +class ChangeWhenFromViewingParties < ActiveRecord::Migration[5.2] + def change + rename_column :viewing_parties, :when, :date_time + end +end diff --git a/db/migrate/20230201214137_add_movie_id_to_viewing_parties.rb b/db/migrate/20230201214137_add_movie_id_to_viewing_parties.rb new file mode 100644 index 0000000000..c2854ddb79 --- /dev/null +++ b/db/migrate/20230201214137_add_movie_id_to_viewing_parties.rb @@ -0,0 +1,5 @@ +class AddMovieIdToViewingParties < ActiveRecord::Migration[5.2] + def change + add_column :viewing_parties, :movie_id, :integer + end +end diff --git a/db/migrate/20230201214405_remove_host_from_viewing_parties.rb b/db/migrate/20230201214405_remove_host_from_viewing_parties.rb new file mode 100644 index 0000000000..bf325c7e02 --- /dev/null +++ b/db/migrate/20230201214405_remove_host_from_viewing_parties.rb @@ -0,0 +1,5 @@ +class RemoveHostFromViewingParties < ActiveRecord::Migration[5.2] + def change + remove_column :viewing_parties, :host + end +end diff --git a/db/migrate/20230201214527_add_hosting_to_user_viewing_parties.rb b/db/migrate/20230201214527_add_hosting_to_user_viewing_parties.rb new file mode 100644 index 0000000000..65901ebcc4 --- /dev/null +++ b/db/migrate/20230201214527_add_hosting_to_user_viewing_parties.rb @@ -0,0 +1,5 @@ +class AddHostingToUserViewingParties < ActiveRecord::Migration[5.2] + def change + add_column :user_viewing_parties, :hosting, :boolean + end +end diff --git a/db/migrate/20230201214716_rename_date_time_in_viewing_parties.rb b/db/migrate/20230201214716_rename_date_time_in_viewing_parties.rb new file mode 100644 index 0000000000..4952bc07d3 --- /dev/null +++ b/db/migrate/20230201214716_rename_date_time_in_viewing_parties.rb @@ -0,0 +1,5 @@ +class RenameDateTimeInViewingParties < ActiveRecord::Migration[5.2] + def change + rename_column :viewing_parties, :date_time, :date + end +end diff --git a/db/migrate/20230201214838_change_date_data_type_in_viewing_parties.rb b/db/migrate/20230201214838_change_date_data_type_in_viewing_parties.rb new file mode 100644 index 0000000000..1a701f0408 --- /dev/null +++ b/db/migrate/20230201214838_change_date_data_type_in_viewing_parties.rb @@ -0,0 +1,5 @@ +class ChangeDateDataTypeInViewingParties < ActiveRecord::Migration[5.2] + def change + change_column :viewing_parties, :date, :date + end +end diff --git a/db/migrate/20230201215000_add_start_time_to_viewing_parties.rb b/db/migrate/20230201215000_add_start_time_to_viewing_parties.rb new file mode 100644 index 0000000000..6f2f17125c --- /dev/null +++ b/db/migrate/20230201215000_add_start_time_to_viewing_parties.rb @@ -0,0 +1,5 @@ +class AddStartTimeToViewingParties < ActiveRecord::Migration[5.2] + def change + add_column :viewing_parties, :start_time, :time + end +end diff --git a/db/migrate/20230202214654_add_default_to_hosting_in_user_viewing_parties.rb b/db/migrate/20230202214654_add_default_to_hosting_in_user_viewing_parties.rb new file mode 100644 index 0000000000..1860f7f226 --- /dev/null +++ b/db/migrate/20230202214654_add_default_to_hosting_in_user_viewing_parties.rb @@ -0,0 +1,5 @@ +class AddDefaultToHostingInUserViewingParties < ActiveRecord::Migration[5.2] + def change + change_column :user_viewing_parties, :hosting, :boolean, default: false + end +end diff --git a/db/migrate/20230213175425_add_password_to_users.rb b/db/migrate/20230213175425_add_password_to_users.rb new file mode 100644 index 0000000000..4b446c0607 --- /dev/null +++ b/db/migrate/20230213175425_add_password_to_users.rb @@ -0,0 +1,5 @@ +class AddPasswordToUsers < ActiveRecord::Migration[5.2] + def change + add_column :users, :password_digest, :string + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000000..bba369c79a --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,47 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2023_02_13_175425) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "user_viewing_parties", force: :cascade do |t| + t.bigint "viewing_party_id" + t.bigint "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "hosting", default: false + t.index ["user_id"], name: "index_user_viewing_parties_on_user_id" + t.index ["viewing_party_id"], name: "index_user_viewing_parties_on_viewing_party_id" + end + + create_table "users", force: :cascade do |t| + t.string "name" + t.string "email" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "password_digest" + end + + create_table "viewing_parties", force: :cascade do |t| + t.date "date" + t.integer "duration" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "movie_id" + t.time "start_time" + end + + add_foreign_key "user_viewing_parties", "users" + add_foreign_key "user_viewing_parties", "viewing_parties" +end diff --git a/db/seeds.rb b/db/seeds.rb index 1beea2accd..5ca48d2a30 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,30 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) + +require 'faker' + +User.create!({ name: 'Samuel Cox', email: 'samuel@example.com' }) + +10.times do + name = Faker::FunnyName.name + User.create!({ name: name, email: Faker::Internet.email(name: name) }) +end + +3.times do + ViewingParty.create!({ start_time: Faker::Time.between(from: Time.now - 30, to: Time.now + 30), + date: Faker::Date.between(from: Date.today, to: 30.days.from_now), duration: Faker::Number.within(range: 175..240), movie_id: [238, 278, 315_162].shuffle.sample }) +end + +5.times do + viewing_party_id = ViewingParty.all.shuffle.sample.id + user = User.all.shuffle.sample + UserViewingParty.create!({ viewing_party_id: viewing_party_id, user_id: ( + if UserViewingParty.exists?(user_id: user.id, viewing_party_id: viewing_party_id) + (User.all - [user]).shuffle.sample.id + else + user.id + end + ), + hosting: !UserViewingParty.exists?(viewing_party_id: viewing_party_id) }) +end diff --git a/package.json b/package.json index 0284c160e7..c0c0dd416c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,14 @@ { "name": "viewing_party_lite", "private": true, - "dependencies": {} + "dependencies": { + "bootstrap": "^5.2.3", + "jquery": "^3.6.3", + "popper.js": "^1.16.1" + }, + "version": "1.0.0", + "main": "index.js", + "repository": "git@github.com:sambcox/viewing_party_lite.git", + "author": "Samuel Cox ", + "license": "MIT" } diff --git a/spec/facades/movie_spec.rb b/spec/facades/movie_spec.rb new file mode 100644 index 0000000000..e1779ebfd4 --- /dev/null +++ b/spec/facades/movie_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe MovieFacade do + before :each do + json_response_1 = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_1) + json_response_2 = File.read('spec/fixtures/the_godfather_credits.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238/credits?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_2) + json_response_3 = File.read('spec/fixtures/the_godfather_reviews.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238/reviews?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_3) + json_response_4 = File.read('spec/fixtures/top_rated_movies.json') + stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&include_adult=false&include_video=false&language=en-US&page=1&sort_by=vote_average.desc&vote_count.gte=1000") + .to_return(status: 200, body: json_response_4) + json_response_5 = File.read('spec/fixtures/movies_with_green.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Green&page=1&include_adult=false") + .to_return(status: 200, body: json_response_5) + end + + it '#find_movie' do + expect(MovieFacade.find_movie(238)).to be_a(Movie) + end + it '#find_cast' do + expect(MovieFacade.find_cast(238)).to be_a(Array) + end + it '#find_reviews' do + expect(MovieFacade.find_reviews(238)).to be_a(Array) + end + it '#find_movies by top rated' do + expect(MovieFacade.find_movies("https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&include_adult=false&include_video=false&language=en-US&page=1&sort_by=vote_average.desc&vote_count.gte=1000")).to be_a(Array) + end + it '#find_movies by top search' do + expect(MovieFacade.find_movies("https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&include_adult=false&language=en-US&page=1&query=Green")).to be_a(Array) + end +end diff --git a/spec/factories/user_viewing_parties.rb b/spec/factories/user_viewing_parties.rb new file mode 100644 index 0000000000..6e9b9d6207 --- /dev/null +++ b/spec/factories/user_viewing_parties.rb @@ -0,0 +1,14 @@ +FactoryBot.define do + factory :user_viewing_party do + viewing_party { ViewingParty.all.shuffle.sample } + user_id do + user = User.all.shuffle.sample + if user.find_user_viewing_party(viewing_party) + (User.all - [user]).shuffle.sample.id + else + user.id + end + end + hosting { !UserViewingParty.exists?(viewing_party_id: viewing_party.id) } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000000..d89f2bedc8 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :user do + name { Faker::FunnyName.name } + email { Faker::Internet.email(name: name) } + password { Faker::Internet.password } + password_confirmation { password } + end +end diff --git a/spec/factories/viewing_parties.rb b/spec/factories/viewing_parties.rb new file mode 100644 index 0000000000..0edfe8ba20 --- /dev/null +++ b/spec/factories/viewing_parties.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :viewing_party do + start_time { Faker::Time.between(from: Time.now - 30, to: Time.now + 30) } + date { Faker::Date.between(from: Date.today, to: 30.days.from_now) } + duration { Faker::Number.within(range: 175..240) } + movie_id { [238, 278, 315_162].shuffle.sample } + end +end diff --git a/spec/features/discover/index_spec.rb b/spec/features/discover/index_spec.rb new file mode 100644 index 0000000000..68eb73f845 --- /dev/null +++ b/spec/features/discover/index_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +RSpec.describe 'Discover Index' do + let!(:users) { create_list(:user, 10) } + let(:user) { users.first } + + it 'has a link to view top rated movies' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/top_rated_movies.json') + stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + click_button('Find Top Rated Movies') + + expect(current_path).to eq(movies_path) + end + + it 'has a field to search for movies by title' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/movies_with_green.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Green&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + fill_in(:title, with: 'Green') + click_button('Find Movies') + + expect(current_path).to eq(movies_path) + end + + it 'accepts multiple words in search field' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/movies_with_spider_man.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Spider%20Man&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + fill_in(:title, with: 'Spider Man') + click_button('Find Movies') + + expect(current_path).to eq(movies_path) + end + + it 'tells user to add a query if they dont fill out the field' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/failed_search.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&include_adult=false&language=en-US&page=1&query=") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + click_button('Find Movies') + + expect(current_path).to eq(discover_index_path) + expect(page).to have_content('query must be provided') + end +end diff --git a/spec/features/movies/index_spec.rb b/spec/features/movies/index_spec.rb new file mode 100644 index 0000000000..3bf694747a --- /dev/null +++ b/spec/features/movies/index_spec.rb @@ -0,0 +1,99 @@ +require 'rails_helper' + +RSpec.describe 'Movies Index' do + let!(:users) { create_list(:user, 10) } + let(:user) { users.first } + + it 'has a button to the discover page' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/top_rated_movies.json') + stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000") + .to_return(status: 200, body: json_response) + + visit discover_index_path + click_button 'Find Top Rated Movies' + + click_button('Discover Page') + expect(current_path).to eq(discover_index_path) + end + + it 'shows the top rated movies' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/top_rated_movies.json') + stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000") + .to_return(status: 200, body: json_response) + + visit discover_index_path + click_button 'Find Top Rated Movies' + + within('#movie_238') do + expect(page).to have_content('The Godfather') + expect(page).to have_content('Vote Average: 8.7') + end + + within('#movie_315162') do + expect(page).to have_content('Puss in Boots: The Last Wish') + expect(page).to have_content('Vote Average: 8.6') + end + + expect('The Godfather').to appear_before('Puss in Boots: The Last Wish') + + expect(page).to have_content('Vote Average:', count: 20) + end + + it 'shows movies filtered by title' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/movies_with_green.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Green&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + fill_in(:title, with: 'Green') + click_button 'Find Movies' + + expect(page).to have_content('Green', count: 20) + end + + it 'accepts multiple words in search field' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/movies_with_spider_man.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Spider%20Man&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + fill_in(:title, with: 'Spider Man') + click_button('Find Movies') + + expect(page).to have_content('Spider', count: 23) + end + + it 'only returns movies that meet the criteria, and responses are case insensitive' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/the_grand_budapest_hotel.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=the%20grand%20budapest%20hotel&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + fill_in(:title, with: 'the grand budapest hotel') + click_button('Find Movies') + + expect(page).to have_content('Vote Average', count: 2) + end + + it 'returns an error if nothing is inputted' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/failed_search.json') + stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=&page=1&include_adult=false") + .to_return(status: 200, body: json_response) + + visit discover_index_path + + click_button('Find Movies') + + expect(page).to have_button('Find Top Rated Movies') + expect(page).to have_button('Find Movies') + expect(page).to have_content('query must be provided') + end +end diff --git a/spec/features/movies/show_spec.rb b/spec/features/movies/show_spec.rb new file mode 100644 index 0000000000..208892c691 --- /dev/null +++ b/spec/features/movies/show_spec.rb @@ -0,0 +1,106 @@ +require 'rails_helper' + +RSpec.describe 'Movies Show' do + let!(:users) { create_list(:user, 10) } + let(:user) { users.first } + before :each do + json_response = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response) + + json_response_2 = File.read('spec/fixtures/the_godfather_credits.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238/credits?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_2) + + json_response_3 = File.read('spec/fixtures/the_godfather_reviews.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238/reviews?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_3) + end + + it 'is linked on the results page' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response_1 = File.read('spec/fixtures/top_rated_movies.json') + stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000") + .to_return(status: 200, body: json_response_1) + + visit discover_index_path + click_button 'Find Top Rated Movies' + + click_link('The Godfather') + + expect(current_path).to eq(movie_path(238)) + end + + describe 'Movie Information' do + it 'has a button to create a viewing party that will not work unless logged in' do + visit movie_path(238) + + click_button('Create Viewing Party for The Godfather') + expect(page).to have_content('User must be logged in') + end + + + it 'has a button to the discover page' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + click_button('Discover Page') + expect(current_path).to eq(discover_index_path) + end + + it 'has a title' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('The Godfather') + end + + it 'has a vote average' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('Vote Average: 8.7') + end + + it 'has a Runtime' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('Runtime: 2hr 55min') + end + + it 'has all genres' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('Genre: Drama, Crime') + end + + it 'has a summary' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.') + end + + it 'has a list of actors' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('Don Vito Corleone Marlon Brando') + expect(page).to have_content("Virgil 'The Turk' Sollozzo Al Lettieri") + expect(page).to_not have_content("Salvatore 'Sal' Tessio Abe Vigoda") + end + + it 'has a list of reviews' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit movie_path(238) + + expect(page).to have_content('2 Reviews:') + expect(page).to have_content('The Godfather is a film considered by most to be one of the greatest ever made.') + expect(page).to have_content('futuretv') + expect(page).to have_content('crastana') + expect(page).to have_content('A masterpiece by the young and talented Francis Ford Coppola, about a Mob family and their drama, the story telling is perfect, the acting good, sometimes a little over the top in the case of Thalia Shire (the sister of the director)') + end + end +end diff --git a/spec/features/sessions/new_spec.rb b/spec/features/sessions/new_spec.rb new file mode 100644 index 0000000000..941f7dd796 --- /dev/null +++ b/spec/features/sessions/new_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe 'Sessions New' do + let!(:user) { create(:user, password: 'testpass123', password_confirmation: 'testpass123') } + it 'can login a user' do + visit users_login_path + + fill_in('email', with: user.email) + fill_in('password', with: 'testpass123') + click_button('Log In') + + expect(current_path).to eq(user_path(user)) + end + + it 'will not log in if email is wrong' do + visit users_login_path + + fill_in('email', with: 'wrong@example.com') + fill_in('password', with: 'testpass123') + click_button('Log In') + + expect(page).to have_content('Login Failed') + end + + it 'will not log in if password is wrong' do + visit users_login_path + + fill_in('email', with: user.email) + fill_in('password', with: 'wrongpass123') + click_button('Log In') + + expect(page).to have_content('Login Failed') + end +end \ No newline at end of file diff --git a/spec/features/users/new_spec.rb b/spec/features/users/new_spec.rb new file mode 100644 index 0000000000..23196e8009 --- /dev/null +++ b/spec/features/users/new_spec.rb @@ -0,0 +1,90 @@ +require 'rails_helper' + +RSpec.describe 'Users New' do + let!(:user) { create(:user) } + it 'can create a new user' do + visit new_user_path + expect(current_path).to eq('/register') + + fill_in('user[name]', with: 'Samuel Cox') + fill_in('user[email]', with: 'samuel@example.com') + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass123') + click_button('Register') + + expect(User.exists?(name: 'Samuel Cox')).to eq true + expect(User.exists?(email: 'samuel@example.com')).to eq true + end + + it 'will not create a new user if the email already exists' do + visit new_user_path + + fill_in('user[name]', with: user.name) + fill_in('user[email]', with: user.email) + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass123') + click_button('Register') + + expect(page).to have_content('User already exists with given email') + end + + it 'will not create a user if the email is not filled out' do + visit new_user_path + + fill_in('user[name]', with: user.name) + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass123') + + click_button('Register') + + expect(page).to have_content("Email can't be blank") + end + + it 'will not create a user if the name is not filled out' do + visit new_user_path + + fill_in('user[email]', with: user.email) + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass123') + + click_button('Register') + + expect(page).to have_content("Name can't be blank") + end + + it 'will not create a user if the password is not filled out' do + visit new_user_path + + fill_in('user[email]', with: user.email) + fill_in('user[name]', with: user.name) + + click_button('Register') + + expect(page).to have_content("Password can't be blank") + end + + it 'makes sure that the email field is filled in with a real email' do + visit new_user_path + + fill_in('user[name]', with: user.name) + fill_in('user[email]', with: 'kyle.email.com') + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass123') + + click_button('Register') + + expect(page).to have_content('Email is invalid') + end + + it 'will not create if password and confirmation do not match' do + visit new_user_path + + fill_in('user[name]', with: 'Samuel Cox') + fill_in('user[email]', with: 'samuel@example.com') + fill_in('user[password]', with: 'testpass123') + fill_in('user[password_confirmation]', with: 'testpass125') + click_button('Register') + + expect(page).to have_content("Password confirmation doesn't match Password") + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb new file mode 100644 index 0000000000..31d11bb3c2 --- /dev/null +++ b/spec/features/users/show_spec.rb @@ -0,0 +1,90 @@ +require 'rails_helper' + +RSpec.describe 'Users Show' do + before :each do + json_response_1 = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_1) + json_response_2 = File.read('spec/fixtures/shawshank_redemption.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/278?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_2) + json_response_3 = File.read('spec/fixtures/puss_in_boots.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/315162?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_3) + end + describe 'Features' do + let!(:users) { create_list(:user, 10) } + let!(:viewing_parties) { create_list(:viewing_party, 3) } + let!(:user_viewing_parties) { create_list(:user_viewing_party, 20) } + let!(:user) { create(:user, password: 'testpass123', password_confirmation: 'testpass123') } + + it 'Shows the user name' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit user_path(user) + + expect(page).to have_content(user.name) + end + + it 'it has a button to discover movies' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit user_path(user) + + expect(page).to have_button('Discover Movies') + end + + it 'has a list of hosted parties' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit user_path(user) + + within '#hosted_parties' do + user.viewing_parties.each do |party| + user_party = user.user_viewing_parties.find_by(viewing_party_id: party.id) + if user_party.hosting + within "#viewing_party_#{party.id}" do + expect(page).to have_content(party.movie.title) + expect(page.find('img')[:src]).to eq("https://image.tmdb.org/t/p/w200/#{party.movie.image}") + expect(page).to have_content(party.date.strftime('%-m/%-d/%Y')) + expect(page).to have_content(party.start_time.strftime('%-l:%M %p')) + expect(page).to have_content('You are the Host') + party.users.each do |invitee| + expect(page).to have_content(invitee.name) unless invitee == user + end + end + else + expect(page).to_not have_css("#viewing_party_#{party.id}") + end + end + end + end + + it 'has a list of invited parties' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit user_path(user) + + within '#invited_parties' do + user.viewing_parties.each do |party| + user_party = user.user_viewing_parties.find_by(viewing_party_id: party.id) + if user_party.hosting + expect(page).to_not have_css("#viewing_party_#{party.id}") + else + within "#viewing_party_#{party.id}" do + expect(page).to have_content(party.movie.title) + expect(page).to have_content(party.date.strftime('%-m/%-d/%Y')) + expect(page).to have_content(party.start_time.strftime('%-l:%M %p')) + party.user_viewing_parties.each do |invitee| + expect(page).to have_content(invitee.user.name) + expect(page).to have_content("Host:\n#{invitee.user.name}") if invitee.hosting + end + end + end + end + end + end + + it 'will not visit the page if user is not logged in' do + visit user_path(user) + + expect(page).to have_content('User must be logged in') + end + end +end diff --git a/spec/features/viewing_parties/new_spec.rb b/spec/features/viewing_parties/new_spec.rb new file mode 100644 index 0000000000..5577888368 --- /dev/null +++ b/spec/features/viewing_parties/new_spec.rb @@ -0,0 +1,119 @@ +require 'rails_helper' + +RSpec.describe 'Viewing Party New' do + let!(:users) { create_list(:user, 10) } + let(:user) { users.first } + + before :each do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + json_response = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response) + end + + it 'can create a new viewing party' do + visit new_movie_viewing_party_path(238) + start_time = Time.now + + expect(page).to have_field('viewing_party[duration]', with: 175) + + fill_in('viewing_party[date]', with: Date.today) + fill_in('viewing_party[start_time]', with: start_time) + + page.check("viewing_party[#{users.second.id}]") + page.check("viewing_party[#{users.fourth.id}]") + + click_button('Create Viewing Party') + + expect(current_path).to eq(user_path) + viewing_party = ViewingParty.first + + expect(viewing_party.date).to eq(Date.today) + expect(viewing_party.start_time.strftime('%H:%M')).to eq(start_time.utc.strftime('%H:%M')) + expect(viewing_party.users).to eq([users.first, users.second, users.fourth]) + user_viewing_party_1 = UserViewingParty.find_by(user_id: user.id) + user_viewing_party_2 = UserViewingParty.find_by(user_id: users.second.id) + expect(user_viewing_party_1.hosting).to eq(true) + expect(user_viewing_party_2.hosting).to eq(false) + end + + it 'displays the information on the users page' do + visit new_movie_viewing_party_path(238) + start_time = Time.now + + fill_in('viewing_party[date]', with: Date.today) + fill_in('viewing_party[start_time]', with: start_time) + + page.check("viewing_party[#{users.second.id}]") + page.check("viewing_party[#{users.fourth.id}]") + + click_button('Create Viewing Party') + + viewing_party = ViewingParty.first + + within '#hosted_parties' do + expect(page).to have_link('The Godfather') + expect(page.find('img')[:src]).to eq("https://image.tmdb.org/t/p/w200/#{viewing_party.movie.image}") + expect(page).to have_content(viewing_party.date.strftime('%-m/%-d/%Y')) + expect(page).to have_content(viewing_party.start_time.utc.strftime('%-l:%M %p')) + expect(page).to have_content('You are the Host') + expect(page).to have_content(viewing_party.users[1].name) + expect(page).to have_content(viewing_party.users[2].name) + end + end + + it 'will not create if runtime is longer than duration' do + visit new_movie_viewing_party_path(238) + start_time = Time.now + + fill_in('viewing_party[duration]', with: 100) + fill_in('viewing_party[date]', with: Date.today) + fill_in('viewing_party[start_time]', with: start_time) + + page.check("viewing_party[#{users.second.id}]") + page.check("viewing_party[#{users.fourth.id}]") + + click_button('Create Viewing Party') + + expect(current_path).to eq(new_movie_viewing_party_path(238)) + expect(page).to have_content('Duration must be greater than or equal to movie runtime') + end + + describe 'sad paths' do + it 'wont go to the next page if no fields are filled in' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit new_movie_viewing_party_path(238) + start_time = Time.now + + click_button('Create Viewing Party') + expect(page).to have_content("Date can't be blank") + expect(page).to have_content("Start time can't be blank") + end + + it 'wont go to the next page if no date is selected' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit new_movie_viewing_party_path(238) + start_time = Time.now + + fill_in('viewing_party[start_time]', with: start_time) + + click_button('Create Viewing Party') + + expect(page).to have_content("Date can't be blank") + expect(page).to_not have_content("Start time can't be blank") + end + + it 'wont go to the next page if no start time is selected' do + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + visit new_movie_viewing_party_path(238) + start_time = Time.now + + fill_in('viewing_party[date]', with: Date.today) + + click_button('Create Viewing Party') + + expect(page).to_not have_content("Date can't be blank") + expect(page).to have_content("Start time can't be blank") + end + end +end diff --git a/spec/features/welcome/index_spec.rb b/spec/features/welcome/index_spec.rb new file mode 100644 index 0000000000..e557559925 --- /dev/null +++ b/spec/features/welcome/index_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +RSpec.describe 'Landing Page' do + let!(:users) { create_list(:user, 10) } + let!(:user) { create(:user, password: 'testpass123', password_confirmation: 'testpass123') } + + it 'Has all relevant content' do + visit root_path + + random_user = users.shuffle.sample + expect(page).to have_content('Viewing Party') + expect(page).to_not have_content(users.first.email) + expect(page).to_not have_content(users.last.email) + expect(page).to_not have_content(random_user.email) + + click_on('Log In') + + fill_in('email', with: user.email) + fill_in('password', with: 'testpass123') + click_button('Log In') + click_on('Back to Home') + + expect(page).to have_content(users.first.email) + expect(page).to have_content(users.last.email) + expect(page).to have_content(random_user.email) + end + + it 'has a has a create button' do + visit root_path + click_button('Create a New User') + + expect(current_path).to eq(new_user_path) + end + + it 'has navigation link' do + visit root_path + expect(page).to have_link('Back to Home', href: root_path) + expect(page).to have_link('Log In', href: users_login_path) + end + + it 'has a login button if no user is logged in and has a log out if user is logged in' do + visit root_path + expect(page).to_not have_link('Log Out') + click_on('Log In') + + fill_in('email', with: user.email) + fill_in('password', with: 'testpass123') + click_button('Log In') + + expect(page).to_not have_link('Log In') + click_on('Log Out') + expect(page).to have_link('Log In') + end +end diff --git a/spec/fixtures/failed_search.json b/spec/fixtures/failed_search.json new file mode 100644 index 0000000000..615718a3d1 --- /dev/null +++ b/spec/fixtures/failed_search.json @@ -0,0 +1,6 @@ +{ + "errors": [ + "query must be provided" + ], + "success": false +} \ No newline at end of file diff --git a/spec/fixtures/movies_with_green.json b/spec/fixtures/movies_with_green.json new file mode 100644 index 0000000000..98e8e6d415 --- /dev/null +++ b/spec/fixtures/movies_with_green.json @@ -0,0 +1,408 @@ +{ + "page": 1, + "results": [ + { + "adult": false, + "backdrop_path": "/l6hQWH9eDksNJNiXWYRkWqikOdu.jpg", + "genre_ids": [ + 14, + 18, + 80 + ], + "id": 497, + "original_language": "en", + "original_title": "The Green Mile", + "overview": "A supernatural tale set on death row in a Southern prison, where gentle giant John Coffey possesses the mysterious power to heal people's ailments. When the cell block's head guard, Paul Edgecomb, recognizes Coffey's miraculous gift, he tries desperately to help stave off the condemned man's execution.", + "popularity": 126.022, + "poster_path": "/velWPhVMQeQKcxggNEU8YmIo52R.jpg", + "release_date": "1999-12-10", + "title": "The Green Mile", + "video": false, + "vote_average": 8.5, + "vote_count": 14978 + }, + { + "adult": false, + "backdrop_path": "/2Xe9lISpwXKhvKiHttbFfVRERQX.jpg", + "genre_ids": [ + 18, + 35 + ], + "id": 490132, + "original_language": "en", + "original_title": "Green Book", + "overview": "Tony Lip, a bouncer in 1962, is hired to drive pianist Don Shirley on a tour through the Deep South in the days when African Americans, forced to find alternate accommodations and services due to segregation laws below the Mason-Dixon Line, relied on a guide called The Negro Motorist Green Book.", + "popularity": 42.12, + "poster_path": "/7BsvSuDQuoqhWmU2fL7W2GOcZHU.jpg", + "release_date": "2018-11-16", + "title": "Green Book", + "video": false, + "vote_average": 8.2, + "vote_count": 9796 + }, + { + "adult": false, + "backdrop_path": "/58r049aVTO1KjAYVuWJr0KgG5nY.jpg", + "genre_ids": [ + 12, + 28, + 53, + 878 + ], + "id": 44912, + "original_language": "en", + "original_title": "Green Lantern", + "overview": "For centuries, a small but powerful force of warriors called the Green Lantern Corps has sworn to keep intergalactic order. Each Green Lantern wears a ring that grants him superpowers. But when a new enemy called Parallax threatens to destroy the balance of power in the Universe, their fate and the fate of Earth lie in the hands of the first human ever recruited.", + "popularity": 41.274, + "poster_path": "/fj21HwUprqjjwTdkKC1XZurRSpV.jpg", + "release_date": "2011-06-16", + "title": "Green Lantern", + "video": false, + "vote_average": 5.2, + "vote_count": 6527 + }, + { + "adult": false, + "backdrop_path": "/rRGl4Uqo962I2iqQSOT8nArWNWd.jpg", + "genre_ids": [ + 16, + 28, + 878 + ], + "id": 887357, + "original_language": "en", + "original_title": "Green Lantern: Beware My Power", + "overview": "Recently discharged Marine sniper John Stewart is at a crossroads in his life, one which is only complicated by receiving an extraterrestrial ring which grants him the powers of the Green Lantern of Earth. Unfortunately, the ring doesn't come with instructions - but it does come with baggage, like a horde of interplanetary killers bent on eliminating every Green Lantern in the universe. Now, with the aid of the light-hearted Green Arrow, Adam Strange and Hawkgirl, this reluctant soldier must journey into the heart of a galactic Rann/Thanagar war and somehow succeed where all other Green Lanterns have failed.", + "popularity": 47.055, + "poster_path": "/jU8Y6JXoj163MmOCzrT9C85MRTC.jpg", + "release_date": "2022-05-19", + "title": "Green Lantern: Beware My Power", + "video": false, + "vote_average": 7.1, + "vote_count": 128 + }, + { + "adult": false, + "backdrop_path": "/b7ATgNqE84WrGKq7Y8JtrnAuAS8.jpg", + "genre_ids": [ + 878, + 53 + ], + "id": 12101, + "original_language": "en", + "original_title": "Soylent Green", + "overview": "This is the year 2022. Overcrowding, pollution, and resource depletion have reduced society’s leaders to finding food for the teeming masses. The answer is Soylent Green.", + "popularity": 15.728, + "poster_path": "/5nbkShkOEXUoKVhaX0XG41wyBkq.jpg", + "release_date": "1973-04-18", + "title": "Soylent Green", + "video": false, + "vote_average": 6.9, + "vote_count": 1041 + }, + { + "adult": false, + "backdrop_path": "/3EE9h3xYxIastf5xVLbezH3eGps.jpg", + "genre_ids": [ + 10752, + 28, + 12, + 18, + 53 + ], + "id": 22972, + "original_language": "en", + "original_title": "Green Zone", + "overview": "During the U.S.-led occupation of Baghdad in 2003, Chief Warrant Officer Roy Miller and his team of Army inspectors are dispatched to find weapons of mass destruction believed to be stockpiled in the Iraqi desert. Rocketing from one booby-trapped and treacherous site to the next, the men search for deadly chemical agents but stumble instead upon an elaborate cover-up that threatens to invert the purpose of their mission.", + "popularity": 16.674, + "poster_path": "/czFIaeukSxNEccysQeys1PCkuOG.jpg", + "release_date": "2010-03-11", + "title": "Green Zone", + "video": false, + "vote_average": 6.4, + "vote_count": 1920 + }, + { + "adult": false, + "backdrop_path": "/5XQobwJuqx0UFCHVDooWUBraeID.jpg", + "genre_ids": [ + 28, + 12, + 16, + 878, + 18 + ], + "id": 65291, + "original_language": "en", + "original_title": "Green Lantern: Emerald Knights", + "overview": "As the home planet of the Green Lantern Corps faces a battle with an ancient enemy, Hal Jordan prepares new recruit Arisia for the coming conflict by relating stories of the first Green Lantern and several of Hal's comrades.", + "popularity": 22.145, + "poster_path": "/weth8sLv9y8L9GsEyLRBUZewXUt.jpg", + "release_date": "2011-04-09", + "title": "Green Lantern: Emerald Knights", + "video": false, + "vote_average": 6.7, + "vote_count": 435 + }, + { + "adult": false, + "backdrop_path": "/wjTNqt0RLe4aRMJHvjlKLtzw1vr.jpg", + "genre_ids": [ + 27, + 80, + 53 + ], + "id": 313922, + "original_language": "en", + "original_title": "Green Room", + "overview": "A punk rock band becomes trapped in a secluded venue after finding a scene of violence. For what they saw, the band themselves become targets of violence from a gang of white power skinheads, who want to eliminate all evidence of the crime.", + "popularity": 11.763, + "poster_path": "/evZicaR7nXe4LiD9G6QYTorcJGO.jpg", + "release_date": "2015-09-25", + "title": "Green Room", + "video": false, + "vote_average": 6.7, + "vote_count": 2350 + }, + { + "adult": false, + "backdrop_path": "/ajX3chCByhvBOXn1rbi1dMux1Zg.jpg", + "genre_ids": [ + 27, + 53, + 12 + ], + "id": 171424, + "original_language": "en", + "original_title": "The Green Inferno", + "overview": "A group of student activists travel from New York City to the Amazon to save the rainforest. However, once they arrive in this vast green landscape, they soon discover that they are not alone… and that no good deed goes unpunished.", + "popularity": 23.347, + "poster_path": "/dTnAYyUwp6lT0cKrt4aNujj5mdF.jpg", + "release_date": "2013-09-08", + "title": "The Green Inferno", + "video": false, + "vote_average": 5.7, + "vote_count": 1191 + }, + { + "adult": false, + "backdrop_path": "/al0aXl47cWkf4jnKuPiEZtjWT8j.jpg", + "genre_ids": [ + 12, + 18, + 14 + ], + "id": 559907, + "original_language": "en", + "original_title": "The Green Knight", + "overview": "An epic fantasy adventure based on the timeless Arthurian legend, The Green Knight tells the story of Sir Gawain, King Arthur's reckless and headstrong nephew, who embarks on a daring quest to confront the eponymous Green Knight, a gigantic emerald-skinned stranger and tester of men.", + "popularity": 16.428, + "poster_path": "/if4hw3Ou5Sav9Em7WWHj66mnywp.jpg", + "release_date": "2021-07-29", + "title": "The Green Knight", + "video": false, + "vote_average": 6.6, + "vote_count": 1410 + }, + { + "adult": false, + "backdrop_path": "/vZkco9fDVHSsrZrWuI2U2WpyOEy.jpg", + "genre_ids": [ + 28, + 80, + 35 + ], + "id": 40805, + "original_language": "en", + "original_title": "The Green Hornet", + "overview": "Britt Reid, the heir to the largest newspaper fortune in Los Angeles, is a spoiled playboy who has been, thus far, happy to lead an aimless life. After his father dies, Britt meets Kato, a resourceful company employee. Realizing that they have the talent and resources to make something of their lives, Britt and Kato join forces as costumed crime-fighters to bring down the city's most-powerful criminal, Chudnofsky.", + "popularity": 16.506, + "poster_path": "/iMq5EaAQQ3B0Ta7TtZoOiJPnnto.jpg", + "release_date": "2011-01-12", + "title": "The Green Hornet", + "video": false, + "vote_average": 5.6, + "vote_count": 2846 + }, + { + "adult": false, + "backdrop_path": "/hgbRDIwSiQ7reWwzFtWFy0v02xo.jpg", + "genre_ids": [ + 18, + 35 + ], + "id": 1633, + "original_language": "en", + "original_title": "Fried Green Tomatoes", + "overview": "Amidst her own personality crisis, southern housewife Evelyn Couch meets Ninny, an outgoing old woman who tells her the story of Idgie Threadgoode and Ruth Jamison, two young women who experienced hardships and love in Whistle Stop, Alabama in the 1920s.", + "popularity": 19.519, + "poster_path": "/pnn2azXtMvDPqY5EZNGbndAxJmr.jpg", + "release_date": "1991-12-27", + "title": "Fried Green Tomatoes", + "video": false, + "vote_average": 7.7, + "vote_count": 1126 + }, + { + "adult": false, + "backdrop_path": "/zYARGakIhHUvPuVJzlbqU40FrnV.jpg", + "genre_ids": [ + 28, + 12, + 16, + 14, + 878 + ], + "id": 17445, + "original_language": "en", + "original_title": "Green Lantern: First Flight", + "overview": "Test pilot Hal Jordan finds himself recruited as the newest member of the intergalactic police force, The Green Lantern Corps.", + "popularity": 17.622, + "poster_path": "/zma3rOg6yJxgJ1Lo9uuw3tJOjbZ.jpg", + "release_date": "2009-07-28", + "title": "Green Lantern: First Flight", + "video": false, + "vote_average": 6.9, + "vote_count": 364 + }, + { + "adult": false, + "backdrop_path": "/qE8b8aCKEoZxDmS6PITSc4EiQ0F.jpg", + "genre_ids": [ + 80, + 18 + ], + "id": 8923, + "original_language": "en", + "original_title": "Green Street Hooligans", + "overview": "After being wrongfully expelled from Harvard University, American Matt Buckner flees to his sister's home in England. Once there, he is befriended by her charming and dangerous brother-in-law, Pete Dunham, and introduced to the underworld of British football hooliganism. Matt learns to stand his ground through a friendship that develops against the backdrop of this secret and often violent world. 'Green Street Hooligans' is a story of loyalty, trust and the sometimes brutal consequences of living close to the edge.", + "popularity": 12.48, + "poster_path": "/tfLj69EoP1b8ZWfLyg5FGJ5LC38.jpg", + "release_date": "2005-09-09", + "title": "Green Street Hooligans", + "video": false, + "vote_average": 7.3, + "vote_count": 2040 + }, + { + "adult": false, + "backdrop_path": "/55ZkbGyz6ls1ie217rqGpybQAOB.jpg", + "genre_ids": [ + 10751, + 18 + ], + "id": 17663, + "original_language": "en", + "original_title": "Anne of Green Gables", + "overview": "At the turn of the century on Prince Edward Island, Matthew Cuthbert and his sister Marilla decide to take on an orphan boy as help for their farm. But they get an unexpected jolt when they're mistakenly sent a girl instead: Anne Shirley.", + "popularity": 13.213, + "poster_path": "/wuF8aapqbDPFkoWbwI5LWfDXpVq.jpg", + "release_date": "1985-12-01", + "title": "Anne of Green Gables", + "video": false, + "vote_average": 8.1, + "vote_count": 208 + }, + { + "adult": false, + "backdrop_path": "/3vCaCANCMy50p4N5YreaoSnl92S.jpg", + "genre_ids": [ + 35, + 18, + 10749 + ], + "id": 12157, + "original_language": "en", + "original_title": "Green Card", + "overview": "Urban horticulturalist Brontë Mitchell has her eye on a gorgeous apartment, but the building's board will rent it only to a married couple. Georges Fauré, a waiter from France whose visa is expiring, needs to marry an American woman to stay in the country. Their marriage of convenience turns into a burden when they must live together to allay the suspicions of the immigration service, as the polar opposites grate on each other's nerves.", + "popularity": 9.237, + "poster_path": "/9UweLsv3pCORu25Uq72m2IBkKaK.jpg", + "release_date": "1990-12-23", + "title": "Green Card", + "video": false, + "vote_average": 6.1, + "vote_count": 409 + }, + { + "adult": false, + "backdrop_path": "/691aKLmln9XRXHI85lMZRK4SnV9.jpg", + "genre_ids": [ + 10752, + 18 + ], + "id": 22377, + "original_language": "en", + "original_title": "The Green Berets", + "overview": "Col. Mike Kirby picks two teams of crack Green Berets for two missions in South Vietnam. The first is to strengthen a camp that is trying to be taken by the enemy. The second is to kidnap a North Vietnamese General.", + "popularity": 6.915, + "poster_path": "/c5WG23iqf3RakGOTjZnBATbYM1f.jpg", + "release_date": "1968-07-04", + "title": "The Green Berets", + "video": false, + "vote_average": 5.7, + "vote_count": 140 + }, + { + "adult": false, + "backdrop_path": "/dEDo97Qccpm4DKHerohegv0lH3M.jpg", + "genre_ids": [ + 28, + 18 + ], + "id": 182873, + "original_language": "en", + "original_title": "Green Street Hooligans: Underground", + "overview": "An old firm leader returns to Green Street for revenge after receiving a call that his little brother was killed, but is he able to cope with a new type of hooliganism and can he find his killer?", + "popularity": 8.72, + "poster_path": "/jtlDK3r8G282Vi0cGMxLVJJkhke.jpg", + "release_date": "2013-10-21", + "title": "Green Street Hooligans: Underground", + "video": false, + "vote_average": 5.5, + "vote_count": 136 + }, + { + "adult": false, + "backdrop_path": "/fTW6w1aJl8aiWYdAJLyfhDXmDi2.jpg", + "genre_ids": [ + 18, + 10749, + 10751 + ], + "id": 40724, + "original_language": "en", + "original_title": "Anne of Green Gables: The Sequel", + "overview": "Anne Shirley, now a schoolteacher, has begun writing stories and collecting rejection slips. She acts as Diana's maid of honor, develops a relationship with Gilbert Blythe, and finds herself at Kingsport Ladies' College. But while Anne enjoys the battles and the friends she makes, she finds herself returning to Avonlea.", + "popularity": 12.739, + "poster_path": "/hofilKLz1UPR3nm7ooXXIdGhdTw.jpg", + "release_date": "1987-05-19", + "title": "Anne of Green Gables: The Sequel", + "video": false, + "vote_average": 8, + "vote_count": 103 + }, + { + "adult": false, + "backdrop_path": "/bSE6JsSOvY350NhaT2nhUhl5hUE.jpg", + "genre_ids": [ + 28, + 12, + 35, + 10751 + ], + "id": 26441, + "original_language": "en", + "original_title": "The Big Green", + "overview": "In a depressed Texas town, British foreign exchange teacher Anna attempts to inject some life into her hopeless kids by introducing them to soccer. They're terrible at first, but Anna and her football-hero assistant whip them into shape. As they work overtime, the pair help kids build their self-esteem and also get involved in solving family squabbles.", + "popularity": 6.967, + "poster_path": "/nVHqIuBTcTLmAIrML7T704UwfNI.jpg", + "release_date": "1995-09-29", + "title": "The Big Green", + "video": false, + "vote_average": 5.5, + "vote_count": 160 + } + ], + "total_pages": 61, + "total_results": 1213 +} \ No newline at end of file diff --git a/spec/fixtures/movies_with_spider_man.json b/spec/fixtures/movies_with_spider_man.json new file mode 100644 index 0000000000..b05111e0f0 --- /dev/null +++ b/spec/fixtures/movies_with_spider_man.json @@ -0,0 +1,403 @@ +{ + "page": 1, + "results": [ + { + "adult": false, + "backdrop_path": "/no2cllXSRA56LPipQnuPlg3RuTL.jpg", + "genre_ids": [ + 14, + 28 + ], + "id": 557, + "original_language": "en", + "original_title": "Spider-Man", + "overview": "After being bitten by a genetically altered spider at Oscorp, nerdy but endearing high school student Peter Parker is endowed with amazing powers to become the superhero known as Spider-Man.", + "popularity": 78.389, + "poster_path": "/gh4cZbhZxyTbgxQPxD0dOudNPTn.jpg", + "release_date": "2002-05-01", + "title": "Spider-Man", + "video": false, + "vote_average": 7.3, + "vote_count": 16668 + }, + { + "adult": false, + "backdrop_path": "/14QbnygCuTO0vl7CAFmPf1fgZfV.jpg", + "genre_ids": [ + 28, + 12, + 878 + ], + "id": 634649, + "original_language": "en", + "original_title": "Spider-Man: No Way Home", + "overview": "Peter Parker is unmasked and no longer able to separate his normal life from the high-stakes of being a super-hero. When he asks for help from Doctor Strange the stakes become even more dangerous, forcing him to discover what it truly means to be Spider-Man.", + "popularity": 376.703, + "poster_path": "/uJYYizSuA9Y3DCs0qS4qWvHfZg4.jpg", + "release_date": "2021-12-15", + "title": "Spider-Man: No Way Home", + "video": false, + "vote_average": 8, + "vote_count": 16554 + }, + { + "adult": false, + "backdrop_path": "/fn4n6uOYcB6Uh89nbNPoU2w80RV.jpg", + "genre_ids": [ + 28, + 12, + 878, + 18 + ], + "id": 315635, + "original_language": "en", + "original_title": "Spider-Man: Homecoming", + "overview": "Following the events of Captain America: Civil War, Peter Parker, with the help of his mentor Tony Stark, tries to balance his life as an ordinary high school student in Queens, New York City, with fighting crime as his superhero alter ego Spider-Man as a new threat, the Vulture, emerges.", + "popularity": 95.821, + "poster_path": "/c24sv2weTHPsmDa7jEMN0m2P3RT.jpg", + "release_date": "2017-07-05", + "title": "Spider-Man: Homecoming", + "video": false, + "vote_average": 7.4, + "vote_count": 19579 + }, + { + "adult": false, + "backdrop_path": "/6MQmtWk4cFwSDyNvIgoJRBIHUT3.jpg", + "genre_ids": [ + 14, + 28, + 12 + ], + "id": 559, + "original_language": "en", + "original_title": "Spider-Man 3", + "overview": "The seemingly invincible Spider-Man goes up against an all-new crop of villains—including the shape-shifting Sandman. While Spider-Man’s superpowers are altered by an alien organism, his alter ego, Peter Parker, deals with nemesis Eddie Brock and also gets caught up in a love triangle.", + "popularity": 67.971, + "poster_path": "/rmGLCH63IBByBx5SCbsn0pNWHdg.jpg", + "release_date": "2007-05-01", + "title": "Spider-Man 3", + "video": false, + "vote_average": 6.4, + "vote_count": 12389 + }, + { + "adult": false, + "backdrop_path": "/sLWUtbrpiLp23a0XDSiUiltdFPJ.jpg", + "genre_ids": [ + 28, + 12, + 14 + ], + "id": 1930, + "original_language": "en", + "original_title": "The Amazing Spider-Man", + "overview": "Peter Parker is an outcast high schooler abandoned by his parents as a boy, leaving him to be raised by his Uncle Ben and Aunt May. Like most teenagers, Peter is trying to figure out who he is and how he got to be the person he is today. As Peter discovers a mysterious briefcase that belonged to his father, he begins a quest to understand his parents' disappearance – leading him directly to Oscorp and the lab of Dr. Curt Connors, his father's former partner. As Spider-Man is set on a collision course with Connors' alter ego, The Lizard, Peter will make life-altering choices to use his powers and shape his destiny to become a hero.", + "popularity": 84.243, + "poster_path": "/fSbqPbqXa7ePo8bcnZYN9AHv6zA.jpg", + "release_date": "2012-06-23", + "title": "The Amazing Spider-Man", + "video": false, + "vote_average": 6.7, + "vote_count": 15669 + }, + { + "adult": false, + "backdrop_path": "/zlpZzccypkAYFZIyYLQcchl90ZC.jpg", + "genre_ids": [ + 878, + 28, + 80, + 10770 + ], + "id": 225914, + "original_language": "en", + "original_title": "Spider-Man", + "overview": "When an extortionist threatens to force a multi-suicide unless a huge ransom is paid, only Peter Parker can stop him with his new powers as Spider-Man.", + "popularity": 18.293, + "poster_path": "/jNxRHZ2cxVkNRtxgHuCtv7GY4JP.jpg", + "release_date": "1978-03-15", + "title": "Spider-Man", + "video": false, + "vote_average": 5.8, + "vote_count": 96 + }, + { + "adult": false, + "backdrop_path": "/zayhKuQp7lWrbfAQZdc2R9Y6foa.jpg", + "genre_ids": [ + 14, + 53, + 18 + ], + "id": 995951, + "original_language": "en", + "original_title": "Spider-Man: Convergence", + "overview": "Down on his luck Peter Parker, who after coming into contact with a device capable of inter-dimensional travel, meets another Spider-Man (Miles Morales) from a parallel earth. Miles and Peter are forced to work together to stop the forces that aim to harm them while Peter attempts to make his way home before it's too late.", + "popularity": 25.449, + "poster_path": "/xEBwdpXXS24WNqs3lMLU4bsee9i.jpg", + "release_date": "2023-12-31", + "title": "Spider-Man: Convergence", + "video": false, + "vote_average": 0, + "vote_count": 0 + }, + { + "adult": false, + "backdrop_path": "/ng6SSB3JhbcpKTwbPDsRwUYK8Cq.jpg", + "genre_ids": [ + 28, + 12, + 878 + ], + "id": 429617, + "original_language": "en", + "original_title": "Spider-Man: Far from Home", + "overview": "Peter Parker and his friends go on a summer trip to Europe. However, they will hardly be able to rest - Peter will have to agree to help Nick Fury uncover the mystery of creatures that cause natural disasters and destruction throughout the continent.", + "popularity": 73.837, + "poster_path": "/4q2NNj4S5dG2RLF9CpXsej7yXl.jpg", + "release_date": "2019-06-28", + "title": "Spider-Man: Far from Home", + "video": false, + "vote_average": 7.5, + "vote_count": 13617 + }, + { + "adult": false, + "backdrop_path": "/mPyiNWS0upEG1mGKOKyCQSoZpnp.jpg", + "genre_ids": [ + 28, + 12, + 14 + ], + "id": 102382, + "original_language": "en", + "original_title": "The Amazing Spider-Man 2", + "overview": "For Peter Parker, life is busy. Between taking out the bad guys as Spider-Man and spending time with the person he loves, Gwen Stacy, high school graduation cannot come quickly enough. Peter has not forgotten about the promise he made to Gwen’s father to protect her by staying away, but that is a promise he cannot keep. Things will change for Peter when a new villain, Electro, emerges, an old friend, Harry Osborn, returns, and Peter uncovers new clues about his past.", + "popularity": 81.419, + "poster_path": "/c3e9e18SSlvFd1cQaGmUj5tqL5P.jpg", + "release_date": "2014-04-16", + "title": "The Amazing Spider-Man 2", + "video": false, + "vote_average": 6.5, + "vote_count": 11694 + }, + { + "adult": false, + "backdrop_path": "/x2IqsMlpbOhS8zIUJfyl1yO4gHF.jpg", + "genre_ids": [ + 28, + 12, + 16, + 878 + ], + "id": 324857, + "original_language": "en", + "original_title": "Spider-Man: Into the Spider-Verse", + "overview": "Miles Morales is juggling his life between being a high school student and being a spider-man. When Wilson \"Kingpin\" Fisk uses a super collider, others from across the Spider-Verse are transported to this dimension.", + "popularity": 90.039, + "poster_path": "/iiZZdoQBEYBv6id8su7ImL0oCbD.jpg", + "release_date": "2018-12-14", + "title": "Spider-Man: Into the Spider-Verse", + "video": false, + "vote_average": 8.4, + "vote_count": 12271 + }, + { + "adult": false, + "backdrop_path": "/6al048Lat3eLVQOuKtc9h6Tu94d.jpg", + "genre_ids": [ + 28, + 12, + 14 + ], + "id": 558, + "original_language": "en", + "original_title": "Spider-Man 2", + "overview": "Peter Parker is going through a major identity crisis. Burned out from being Spider-Man, he decides to shelve his superhero alter ego, which leaves the city suffering in the wake of carnage left by the evil Doc Ock. In the meantime, Parker still can't act on his feelings for Mary Jane Watson, a girl he's loved since childhood. A certain anger begins to brew in his best friend Harry Osborn as well...", + "popularity": 24.017, + "poster_path": "/olxpyq9kJAZ2NU1siLshhhXEPR7.jpg", + "release_date": "2004-06-25", + "title": "Spider-Man 2", + "video": false, + "vote_average": 7.2, + "vote_count": 13385 + }, + { + "adult": false, + "backdrop_path": "/bpvjzk0QXbJPV4wVwrHuYiq1TbP.jpg", + "genre_ids": [ + 16, + 28, + 12, + 878 + ], + "id": 569094, + "original_language": "en", + "original_title": "Spider-Man: Across the Spider-Verse", + "overview": "After reuniting with Gwen Stacy, Miles Morales — Brooklyn's full-time, friendly neighborhood Spider-Man — is catapulted across the Multiverse, where he encounters a team of Spider-People charged with protecting its very existence. But when the heroes clash on how to handle a new threat, Miles finds himself pitted against the other Spiders and must redefine what it means to be a hero so he can save the people he loves most.", + "popularity": 66.686, + "poster_path": "/zknrogDlwcmaz3yHkA3yEhy005t.jpg", + "release_date": "2023-06-02", + "title": "Spider-Man: Across the Spider-Verse", + "video": false, + "vote_average": 0, + "vote_count": 0 + }, + { + "adult": false, + "backdrop_path": "/aKEINUeg3y324PxOwZfwM0uWmk7.jpg", + "genre_ids": [ + 28, + 12, + 10751, + 14, + 10770 + ], + "id": 225925, + "original_language": "en", + "original_title": "Spider-Man Strikes Back", + "overview": "At the New York State University, one of Peter Parker's tutors has accidentally given three students all the materials they need to create an atomic bomb. While Peter Parker tries to find out what's happened, the police suspect him of the crime, and Peter has to deal with an attractive journalist determined to get an interview with Spider-Man. Then dastardly millionaire Mr. White shows up, and will stop at nothing to get his hands on the atomic bomb. Spider-Man must defeat this scheming villain and stop him blowing up the World Trade Centre.", + "popularity": 8.859, + "poster_path": "/mwRss4cA5m4fH6izoTwwPfF3ZOU.jpg", + "release_date": "1978-12-21", + "title": "Spider-Man Strikes Back", + "video": false, + "vote_average": 5.5, + "vote_count": 41 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [ + 16, + 12, + 28 + ], + "id": 270768, + "original_language": "en", + "original_title": "Daredevil vs. Spider-Man", + "overview": "Spider-Man and Daredevil team up to fight Kingpin.", + "popularity": 8.001, + "poster_path": "/mEtyZBbVl0sx0y1blYuVDgeNLkr.jpg", + "release_date": "1994-11-19", + "title": "Daredevil vs. Spider-Man", + "video": true, + "vote_average": 7.4, + "vote_count": 106 + }, + { + "adult": false, + "backdrop_path": "/tObVe1R7JnO3VXBvAOWTEPmmegu.jpg", + "genre_ids": [ + 99 + ], + "id": 961651, + "original_language": "en", + "original_title": "Spider-Man: All Roads Lead to No Way Home", + "overview": "Join our hosts JB Smoove and Martin Starr for a celebration of 20 years of Spider-Man™ movies! From the original Sam Raimi trilogy to Marc Webb’s “amazing” movies to the latest trio from Director Jon Watts, we will take viewers through the stars, the stunts and action, the villains and heroes and an homage to Stan Lee, along with a few surprises. Find your favourite Spider-Man pajamas and prepare to swing through the past two decades of Spider-Man at the movies!", + "popularity": 33.651, + "poster_path": "/e2LLmI5wKIrWGAy9Of3yyWu7Szn.jpg", + "release_date": "2022-05-03", + "title": "Spider-Man: All Roads Lead to No Way Home", + "video": false, + "vote_average": 7, + "vote_count": 28 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [ + 28 + ], + "id": 445033, + "original_language": "en", + "original_title": "Spider-Man", + "overview": "Spider-man must rescue the beautiful daughter of the evil Dr. Lightning.", + "popularity": 3.207, + "poster_path": "/iYBfBk1i9zjN9Vybbj8UgTYzkyv.jpg", + "release_date": "1969-01-01", + "title": "Spider-Man", + "video": false, + "vote_average": 6.8, + "vote_count": 15 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [ + 16, + 28, + 12, + 878 + ], + "id": 911916, + "original_language": "en", + "original_title": "Spider-Man: Beyond the Spider-Verse", + "overview": "The further continuing story of Miles Morales and the many other Spider-People from different realities.", + "popularity": 11.441, + "poster_path": "/5cAuJOfd5tuuFBz2yQmBCtVBaj0.jpg", + "release_date": "2024-03-27", + "title": "Spider-Man: Beyond the Spider-Verse", + "video": false, + "vote_average": 0, + "vote_count": 0 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [ + 16 + ], + "id": 50410, + "original_language": "en", + "original_title": "Spider-Man: The Venom Saga", + "overview": "A space-shuttle crash-landing puts the famous web-slinger Spider-Man in contact with a living alien substance that bonds to his suit and enhances his super-powers. Unfortunately, the alien substance begins to change him and he feels the pull of evil, so discards the suit. The evil attaches itself to another host leading to an epic confrontation between good and evil.", + "popularity": 8.913, + "poster_path": "/ilmsQLtthtcD8EU1k25cp0xFQ9a.jpg", + "release_date": "1994-06-07", + "title": "Spider-Man: The Venom Saga", + "video": true, + "vote_average": 6.7, + "vote_count": 73 + }, + { + "adult": false, + "backdrop_path": "/rd8Ji5TbiTBNDQI7SSUYBv9XHLJ.jpg", + "genre_ids": [ + 28, + 12, + 878, + 99 + ], + "id": 470035, + "original_language": "en", + "original_title": "Spider-Man 2: Making the Amazing", + "overview": "A comprehensive 12-part documentary on the making of \"Spider-Man 2,\" covering everything from pre-production to premiere.", + "popularity": 11.394, + "poster_path": "/3oF3GbxSSQxO9rWjrUjwAFi1fSF.jpg", + "release_date": "2004-11-30", + "title": "Spider-Man 2: Making the Amazing", + "video": false, + "vote_average": 6.5, + "vote_count": 58 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [], + "id": 969681, + "original_language": "en", + "original_title": "Untitled Spider-Man 4 Movie", + "overview": "", + "popularity": 6.053, + "poster_path": null, + "title": "Untitled Spider-Man 4 Movie", + "video": false, + "vote_average": 0, + "vote_count": 0 + } + ], + "total_pages": 3, + "total_results": 55 +} \ No newline at end of file diff --git a/spec/fixtures/puss_in_boots.json b/spec/fixtures/puss_in_boots.json new file mode 100644 index 0000000000..727ed7a484 --- /dev/null +++ b/spec/fixtures/puss_in_boots.json @@ -0,0 +1,82 @@ +{ + "adult": false, + "backdrop_path": "/faXT8V80JRhnArTAeYXz0Eutpv9.jpg", + "belongs_to_collection": { + "id": 94602, + "name": "Puss in Boots Collection", + "poster_path": "/anHwj9IupRoRZZ98WTBvHpTiE6A.jpg", + "backdrop_path": "/feU1DWV5zMWxXUHJyAIk3dHRQ9c.jpg" + }, + "budget": 90000000, + "genres": [ + { + "id": 16, + "name": "Animation" + }, + { + "id": 12, + "name": "Adventure" + }, + { + "id": 35, + "name": "Comedy" + }, + { + "id": 10751, + "name": "Family" + }, + { + "id": 14, + "name": "Fantasy" + } + ], + "homepage": "https://www.dreamworks.com/movies/puss-in-boots-the-last-wish", + "id": 315162, + "imdb_id": "tt3915174", + "original_language": "en", + "original_title": "Puss in Boots: The Last Wish", + "overview": "Puss in Boots discovers that his passion for adventure has taken its toll: He has burned through eight of his nine lives, leaving him with only one life left. Puss sets out on an epic journey to find the mythical Last Wish and restore his nine lives.", + "popularity": 4471.915, + "poster_path": "/kuf6dutpsT0vSVehic3EZIqkOBt.jpg", + "production_companies": [ + { + "id": 33, + "logo_path": "/8lvHyhjr8oUKOOy2dKXoALWKdp0.png", + "name": "Universal Pictures", + "origin_country": "US" + }, + { + "id": 521, + "logo_path": "/kP7t6RwGz2AvvTkvnI1uteEwHet.png", + "name": "DreamWorks Animation", + "origin_country": "US" + } + ], + "production_countries": [ + { + "iso_3166_1": "US", + "name": "United States of America" + } + ], + "release_date": "2022-12-07", + "revenue": 336351055, + "runtime": 103, + "spoken_languages": [ + { + "english_name": "English", + "iso_639_1": "en", + "name": "English" + }, + { + "english_name": "Spanish", + "iso_639_1": "es", + "name": "Español" + } + ], + "status": "Released", + "tagline": "Say hola to his little friends.", + "title": "Puss in Boots: The Last Wish", + "video": false, + "vote_average": 8.556, + "vote_count": 3128 +} \ No newline at end of file diff --git a/spec/fixtures/shawshank_redemption.json b/spec/fixtures/shawshank_redemption.json new file mode 100644 index 0000000000..a3c308257d --- /dev/null +++ b/spec/fixtures/shawshank_redemption.json @@ -0,0 +1,54 @@ +{ + "adult": false, + "backdrop_path": "/wPU78OPN4BYEgWYdXyg0phMee64.jpg", + "belongs_to_collection": null, + "budget": 25000000, + "genres": [ + { + "id": 18, + "name": "Drama" + }, + { + "id": 80, + "name": "Crime" + } + ], + "homepage": "", + "id": 278, + "imdb_id": "tt0111161", + "original_language": "en", + "original_title": "The Shawshank Redemption", + "overview": "Framed in the 1940s for the double murder of his wife and her lover, upstanding banker Andy Dufresne begins a new life at the Shawshank prison, where he puts his accounting skills to work for an amoral warden. During his long stretch in prison, Dufresne comes to be admired by the other inmates -- including an older prisoner named Red -- for his integrity and unquenchable sense of hope.", + "popularity": 105.258, + "poster_path": "/hBcY0fE9pfXzvVaY4GKarweriG2.jpg", + "production_companies": [ + { + "id": 97, + "logo_path": "/7znWcbDd4PcJzJUlJxYqAlPPykp.png", + "name": "Castle Rock Entertainment", + "origin_country": "US" + } + ], + "production_countries": [ + { + "iso_3166_1": "US", + "name": "United States of America" + } + ], + "release_date": "1994-09-23", + "revenue": 28341469, + "runtime": 142, + "spoken_languages": [ + { + "english_name": "English", + "iso_639_1": "en", + "name": "English" + } + ], + "status": "Released", + "tagline": "Fear can hold you prisoner. Hope can set you free.", + "title": "The Shawshank Redemption", + "video": false, + "vote_average": 8.701, + "vote_count": 23196 +} \ No newline at end of file diff --git a/spec/fixtures/the_godfather.json b/spec/fixtures/the_godfather.json new file mode 100644 index 0000000000..9e91ff5f45 --- /dev/null +++ b/spec/fixtures/the_godfather.json @@ -0,0 +1,75 @@ +{ + "adult": false, + "backdrop_path": "/tmU7GeKVybMWFButWEGl2M4GeiP.jpg", + "belongs_to_collection": { + "id": 230, + "name": "The Godfather Collection", + "poster_path": "/9Baumh5J9N1nJUYzNkm0xsgjpwY.jpg", + "backdrop_path": "/3WZTxpgscsmoUk81TuECXdFOD0R.jpg" + }, + "budget": 6000000, + "genres": [ + { + "id": 18, + "name": "Drama" + }, + { + "id": 80, + "name": "Crime" + } + ], + "homepage": "http://www.thegodfather.com/", + "id": 238, + "imdb_id": "tt0068646", + "original_language": "en", + "original_title": "The Godfather", + "overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.", + "popularity": 92.736, + "poster_path": "/3bhkrj58Vtu7enYsRolD1fZdja1.jpg", + "production_companies": [ + { + "id": 4, + "logo_path": "/gz66EfNoYPqHTYI4q9UEN4CbHRc.png", + "name": "Paramount", + "origin_country": "US" + }, + { + "id": 10211, + "logo_path": null, + "name": "Alfran Productions", + "origin_country": "US" + } + ], + "production_countries": [ + { + "iso_3166_1": "US", + "name": "United States of America" + } + ], + "release_date": "1972-03-14", + "revenue": 245066411, + "runtime": 175, + "spoken_languages": [ + { + "english_name": "English", + "iso_639_1": "en", + "name": "English" + }, + { + "english_name": "Italian", + "iso_639_1": "it", + "name": "Italiano" + }, + { + "english_name": "Latin", + "iso_639_1": "la", + "name": "Latin" + } + ], + "status": "Released", + "tagline": "An offer you can't refuse.", + "title": "The Godfather", + "video": false, + "vote_average": 8.715, + "vote_count": 17397 +} \ No newline at end of file diff --git a/spec/fixtures/the_godfather_credits.json b/spec/fixtures/the_godfather_credits.json new file mode 100644 index 0000000000..4246b05b28 --- /dev/null +++ b/spec/fixtures/the_godfather_credits.json @@ -0,0 +1,1443 @@ +{ + "id": 238, + "cast": [ + { + "adult": false, + "gender": 2, + "id": 3084, + "known_for_department": "Acting", + "name": "Marlon Brando", + "original_name": "Marlon Brando", + "popularity": 14.992, + "profile_path": "/5o8whyfLBWXLODEcMP8K45lPTjT.jpg", + "cast_id": 5, + "character": "Don Vito Corleone", + "credit_id": "52fe422bc3a36847f8009357", + "order": 0 + }, + { + "adult": false, + "gender": 2, + "id": 1158, + "known_for_department": "Acting", + "name": "Al Pacino", + "original_name": "Al Pacino", + "popularity": 45.047, + "profile_path": "/fMDFeVf0pjopTJbyRSLFwNDm8Wr.jpg", + "cast_id": 6, + "character": "Don Michael Corleone", + "credit_id": "52fe422bc3a36847f800935b", + "order": 1 + }, + { + "adult": false, + "gender": 2, + "id": 3085, + "known_for_department": "Acting", + "name": "James Caan", + "original_name": "James Caan", + "popularity": 26.151, + "profile_path": "/bGyOCCOIgcIyKjOGLoXoyp0XWHf.jpg", + "cast_id": 7, + "character": "Santino 'Sonny' Corleone", + "credit_id": "52fe422bc3a36847f800935f", + "order": 2 + }, + { + "adult": false, + "gender": 2, + "id": 3087, + "known_for_department": "Acting", + "name": "Robert Duvall", + "original_name": "Robert Duvall", + "popularity": 30.939, + "profile_path": "/ybMmK25h4IVtfE7qrnlVp47RQlh.jpg", + "cast_id": 9, + "character": "Tom Hagen", + "credit_id": "52fe422bc3a36847f8009367", + "order": 3 + }, + { + "adult": false, + "gender": 2, + "id": 3086, + "known_for_department": "Acting", + "name": "Richard S. Castellano", + "original_name": "Richard S. Castellano", + "popularity": 9.855, + "profile_path": "/1vr75BdHWret81vuSJ3ugiCBkxw.jpg", + "cast_id": 8, + "character": "Pete Clemenza", + "credit_id": "52fe422bc3a36847f8009363", + "order": 4 + }, + { + "adult": false, + "gender": 1, + "id": 3092, + "known_for_department": "Acting", + "name": "Diane Keaton", + "original_name": "Diane Keaton", + "popularity": 17.101, + "profile_path": "/v2GpRxnfJKnbRP4xKzD9ZJo631s.jpg", + "cast_id": 13, + "character": "Kay Adams", + "credit_id": "52fe422bc3a36847f8009377", + "order": 5 + }, + { + "adult": false, + "gender": 1, + "id": 3094, + "known_for_department": "Acting", + "name": "Talia Shire", + "original_name": "Talia Shire", + "popularity": 12.387, + "profile_path": "/due9MtmbvCMDd1aNveuMyjsaZ8W.jpg", + "cast_id": 15, + "character": "Constanzia 'Connie' Corleone-Rizzi", + "credit_id": "52fe422bc3a36847f800937f", + "order": 6 + }, + { + "adult": false, + "gender": 2, + "id": 3095, + "known_for_department": "Acting", + "name": "Gianni Russo", + "original_name": "Gianni Russo", + "popularity": 5.477, + "profile_path": "/5fjg2WPvuk1NvUpLyQ7aMJqOqy.jpg", + "cast_id": 16, + "character": "Carlo Rizzi", + "credit_id": "52fe422bc3a36847f8009383", + "order": 7 + }, + { + "adult": false, + "gender": 2, + "id": 3088, + "known_for_department": "Acting", + "name": "Sterling Hayden", + "original_name": "Sterling Hayden", + "popularity": 16.492, + "profile_path": "/bMpJtVo741q83q3qLtraqYO4cVh.jpg", + "cast_id": 10, + "character": "Capt. Mark McCluskey", + "credit_id": "52fe422bc3a36847f800936b", + "order": 8 + }, + { + "adult": false, + "gender": 2, + "id": 3091, + "known_for_department": "Acting", + "name": "Al Lettieri", + "original_name": "Al Lettieri", + "popularity": 10.928, + "profile_path": "/fE5mEWPkkVJlCji0EoKht8PYw89.jpg", + "cast_id": 12, + "character": "Virgil 'The Turk' Sollozzo", + "credit_id": "52fe422bc3a36847f8009373", + "order": 9 + }, + { + "adult": false, + "gender": 2, + "id": 3093, + "known_for_department": "Acting", + "name": "Abe Vigoda", + "original_name": "Abe Vigoda", + "popularity": 7.003, + "profile_path": "/luWKPNb58IgJ2ovrtgPAzHGLLYL.jpg", + "cast_id": 14, + "character": "Salvatore 'Sal' Tessio", + "credit_id": "52fe422bc3a36847f800937b", + "order": 10 + }, + { + "adult": false, + "gender": 2, + "id": 3096, + "known_for_department": "Acting", + "name": "John Cazale", + "original_name": "John Cazale", + "popularity": 6.309, + "profile_path": "/xOfnXY65FbcJTbrHIPqYQT3AnSP.jpg", + "cast_id": 17, + "character": "Frederico 'Fredo' Corleone", + "credit_id": "52fe422bc3a36847f8009387", + "order": 11 + }, + { + "adult": false, + "gender": 2, + "id": 3090, + "known_for_department": "Acting", + "name": "Richard Conte", + "original_name": "Richard Conte", + "popularity": 10.173, + "profile_path": "/pTTzNets0bgWAqx18D7by0hWUea.jpg", + "cast_id": 11, + "character": "Don Emilio Barzini", + "credit_id": "52fe422bc3a36847f800936f", + "order": 12 + }, + { + "adult": false, + "gender": 2, + "id": 3142, + "known_for_department": "Acting", + "name": "John Marley", + "original_name": "John Marley", + "popularity": 11.71, + "profile_path": "/k8YnXZDhhn28bSE75tLz9juR46u.jpg", + "cast_id": 33, + "character": "Jack Woltz", + "credit_id": "52fe422bc3a36847f800938b", + "order": 13 + }, + { + "adult": false, + "gender": 2, + "id": 3143, + "known_for_department": "Acting", + "name": "Rudy Bond", + "original_name": "Rudy Bond", + "popularity": 3.626, + "profile_path": "/kP77m6ot9dfOR2V3ykbyOAx2diK.jpg", + "cast_id": 34, + "character": "Carmine Cuneo", + "credit_id": "52fe422bc3a36847f800938f", + "order": 14 + }, + { + "adult": false, + "gender": 2, + "id": 3144, + "known_for_department": "Acting", + "name": "Al Martino", + "original_name": "Al Martino", + "popularity": 1.62, + "profile_path": "/9luOyyiqWFNQgSOSStH66LTWZ3p.jpg", + "cast_id": 35, + "character": "Johnny Fontane", + "credit_id": "52fe422bc3a36847f8009393", + "order": 15 + }, + { + "adult": false, + "gender": 1, + "id": 933716, + "known_for_department": "Acting", + "name": "Morgana King", + "original_name": "Morgana King", + "popularity": 2.107, + "profile_path": "/zPsFAqH4NXD7rMWl2Kj3rEyGrKj.jpg", + "cast_id": 42, + "character": "Carmela 'Mama' Corleone", + "credit_id": "52fe422bc3a36847f80093a7", + "order": 16 + }, + { + "adult": false, + "gender": 2, + "id": 106811, + "known_for_department": "Acting", + "name": "Lenny Montana", + "original_name": "Lenny Montana", + "popularity": 5.695, + "profile_path": "/3R52Zh3MBCaSPqmcyWcD1Y0N4wo.jpg", + "cast_id": 45, + "character": "Luca Brasi", + "credit_id": "52fe422bc3a36847f80093b3", + "order": 17 + }, + { + "adult": false, + "gender": 2, + "id": 99724, + "known_for_department": "Acting", + "name": "John Martino", + "original_name": "John Martino", + "popularity": 2.222, + "profile_path": "/3c8BOgJ8UdHi5x5BS3lYnFngxB5.jpg", + "cast_id": 47, + "character": "Paulie Gatto", + "credit_id": "52fe422bc3a36847f80093bb", + "order": 18 + }, + { + "adult": false, + "gender": 2, + "id": 20752, + "known_for_department": "Acting", + "name": "Alex Rocco", + "original_name": "Alex Rocco", + "popularity": 7.113, + "profile_path": "/u2JStL4Pk9Huq1xmrjqezfysivt.jpg", + "cast_id": 41, + "character": "Moe Greene", + "credit_id": "52fe422bc3a36847f80093a3", + "order": 19 + }, + { + "adult": false, + "gender": 0, + "id": 1195877, + "known_for_department": "Acting", + "name": "Salvatore Corsitto", + "original_name": "Salvatore Corsitto", + "popularity": 3.163, + "profile_path": "/79OfgwtdP5qsR1UX6ERkrdmRT8c.jpg", + "cast_id": 46, + "character": "Amerigo Bonasera", + "credit_id": "52fe422bc3a36847f80093b7", + "order": 20 + }, + { + "adult": false, + "gender": 2, + "id": 3414, + "known_for_department": "Acting", + "name": "Tony Giorgio", + "original_name": "Tony Giorgio", + "popularity": 7.461, + "profile_path": "/1uhgwgjoX7WaakEOBmtBRJKtgEB.jpg", + "cast_id": 38, + "character": "Bruno Tattaglia", + "credit_id": "52fe422bc3a36847f800939f", + "order": 21 + }, + { + "adult": false, + "gender": 2, + "id": 3413, + "known_for_department": "Acting", + "name": "Victor Rendina", + "original_name": "Victor Rendina", + "popularity": 5.814, + "profile_path": "/vuNdsjDpgom80WBdB2WBTWuEIuM.jpg", + "cast_id": 37, + "character": "Philip Tattaglia", + "credit_id": "52fe422bc3a36847f800939b", + "order": 22 + }, + { + "adult": false, + "gender": 1, + "id": 3145, + "known_for_department": "Acting", + "name": "Simonetta Stefanelli", + "original_name": "Simonetta Stefanelli", + "popularity": 13.836, + "profile_path": "/sVDrhgxuE6SHnj7q07OG6ywPUYR.jpg", + "cast_id": 36, + "character": "Apollonia Vitelli-Corleone", + "credit_id": "52fe422bc3a36847f8009397", + "order": 23 + }, + { + "adult": false, + "gender": 2, + "id": 119431, + "known_for_department": "Acting", + "name": "Saro Urzì", + "original_name": "Saro Urzì", + "popularity": 3.688, + "profile_path": "/nPJytCpcNdG5ssPoKeEvxM6xifx.jpg", + "cast_id": 48, + "character": "Mr. Vitelli", + "credit_id": "52fe422bc3a36847f80093bf", + "order": 24 + }, + { + "adult": false, + "gender": 1, + "id": 1769, + "known_for_department": "Directing", + "name": "Sofia Coppola", + "original_name": "Sofia Coppola", + "popularity": 7.438, + "profile_path": "/dzHC2LxmarkBxWLhjp2DRa5oCev.jpg", + "cast_id": 43, + "character": "Michael Francis Rizzi", + "credit_id": "52fe422bc3a36847f80093ab", + "order": 25 + }, + { + "adult": false, + "gender": 2, + "id": 20973, + "known_for_department": "Acting", + "name": "Louis Guss", + "original_name": "Louis Guss", + "popularity": 1.988, + "profile_path": "/hz5Akuh4QcqitlifYwDiZ9ut6Tr.jpg", + "cast_id": 44, + "character": "Don Zaluchi", + "credit_id": "52fe422bc3a36847f80093af", + "order": 26 + }, + { + "adult": false, + "gender": 2, + "id": 3174, + "known_for_department": "Acting", + "name": "Richard Bright", + "original_name": "Richard Bright", + "popularity": 8.391, + "profile_path": "/potMaJ2u5PRjXZb7qF9lSW1ldNZ.jpg", + "cast_id": 70, + "character": "Neri", + "credit_id": "55be4cdbc3a3683921003065", + "order": 27 + }, + { + "adult": false, + "gender": 2, + "id": 82779, + "known_for_department": "Acting", + "name": "Vito Scotti", + "original_name": "Vito Scotti", + "popularity": 5.901, + "profile_path": "/zDGtXkASxpo3DzMNz6OI3NOp9v0.jpg", + "cast_id": 71, + "character": "Nazorine", + "credit_id": "55be4cff9251413e49004a59", + "order": 28 + }, + { + "adult": false, + "gender": 1, + "id": 982089, + "known_for_department": "Acting", + "name": "Tere Livrano", + "original_name": "Tere Livrano", + "popularity": 2.639, + "profile_path": "/eBoC3425jmwRlsT2W8qUDsNwVXB.jpg", + "cast_id": 72, + "character": "Theresa Hagen", + "credit_id": "55be4d40c3a368392100306d", + "order": 29 + }, + { + "adult": false, + "gender": 1, + "id": 160728, + "known_for_department": "Acting", + "name": "Julie Gregg", + "original_name": "Julie Gregg", + "popularity": 3.152, + "profile_path": "/8Kx71BnZNtpzb86jH7fcArWhJBh.jpg", + "cast_id": 77, + "character": "Sandrinella 'Sandra' Corleone", + "credit_id": "55ee4ac0c3a3686f170063c4", + "order": 30 + }, + { + "adult": false, + "gender": 2, + "id": 27647, + "known_for_department": "Acting", + "name": "Angelo Infanti", + "original_name": "Angelo Infanti", + "popularity": 4.101, + "profile_path": "/dkRVpiDj9YyiuM15XZKT11zZBIR.jpg", + "cast_id": 74, + "character": "Fabrizio", + "credit_id": "55be4dffc3a368652600471c", + "order": 31 + }, + { + "adult": false, + "gender": 2, + "id": 24604, + "known_for_department": "Acting", + "name": "Corrado Gaipa", + "original_name": "Corrado Gaipa", + "popularity": 3.083, + "profile_path": "/7vR2BOdVw9FwJKjgpcisjKEytnL.jpg", + "cast_id": 75, + "character": "Don Tommasino", + "credit_id": "55be4e2fc3a3686534005572", + "order": 32 + }, + { + "adult": false, + "gender": 2, + "id": 44860, + "known_for_department": "Acting", + "name": "Franco Citti", + "original_name": "Franco Citti", + "popularity": 3.917, + "profile_path": "/mttQPjOHaql4RKsfYriUA7HuROE.jpg", + "cast_id": 76, + "character": "Calò", + "credit_id": "55be4e5bc3a3683921003083", + "order": 33 + }, + { + "adult": false, + "gender": 0, + "id": 138211, + "known_for_department": "Acting", + "name": "Gabriele Torrei", + "original_name": "Gabriele Torrei", + "popularity": 4.135, + "profile_path": "/x3KeYWTWX5SqZ3NuQGiWiadO4DI.jpg", + "cast_id": 49, + "character": "Enzo the Baker (uncredited)", + "credit_id": "52fe422bc3a36847f80093c3", + "order": 34 + }, + { + "adult": false, + "gender": 2, + "id": 55672, + "known_for_department": "Acting", + "name": "Tony King", + "original_name": "Tony King", + "popularity": 4.544, + "profile_path": "/hOVVHEyc6euuuRhCLo6Q5YE4lLz.jpg", + "cast_id": 69, + "character": "Tony the Stablehand (uncredited)", + "credit_id": "54de315fc3a3685446001351", + "order": 35 + }, + { + "adult": false, + "gender": 0, + "id": 178006, + "known_for_department": "Acting", + "name": "Max Brandt", + "original_name": "Max Brandt", + "popularity": 1.96, + "profile_path": null, + "cast_id": 78, + "character": "Extra in Furniture-Moving Scene (uncredited)", + "credit_id": "55f1b3c992514167010020a7", + "order": 36 + }, + { + "adult": false, + "gender": 2, + "id": 2872, + "known_for_department": "Sound", + "name": "Carmine Coppola", + "original_name": "Carmine Coppola", + "popularity": 2.951, + "profile_path": "/iURpKXIgdtQoVCBQ8GKGvb0Qw5z.jpg", + "cast_id": 79, + "character": "Piano Player in Montage (uncredited)", + "credit_id": "55f1b3ffc3a368230a00239f", + "order": 37 + }, + { + "adult": false, + "gender": 2, + "id": 38803, + "known_for_department": "Writing", + "name": "Roman Coppola", + "original_name": "Roman Coppola", + "popularity": 5.371, + "profile_path": "/rKamKkx78qjZTfvqi2OWCo5p6x5.jpg", + "cast_id": 80, + "character": "Boy on Street Who Attended Funeral (uncredited)", + "credit_id": "55f1b452c3a36822f5002395", + "order": 38 + }, + { + "adult": false, + "gender": 0, + "id": 1209294, + "known_for_department": "Acting", + "name": "Don Costello", + "original_name": "Don Costello", + "popularity": 1.932, + "profile_path": null, + "cast_id": 81, + "character": "Don Victor Stracci (uncredited)", + "credit_id": "55f1b4bac3a3682303002284", + "order": 39 + }, + { + "adult": false, + "gender": 2, + "id": 1394703, + "known_for_department": "Acting", + "name": "Robert Dahdah", + "original_name": "Robert Dahdah", + "popularity": 1.96, + "profile_path": null, + "cast_id": 82, + "character": "Crowd (uncredited)", + "credit_id": "55f1b4de925141670d002392", + "order": 40 + }, + { + "adult": false, + "gender": 2, + "id": 2870, + "known_for_department": "Production", + "name": "Gray Frederickson", + "original_name": "Gray Frederickson", + "popularity": 2.652, + "profile_path": null, + "cast_id": 83, + "character": "Cowboy on the Set at Woltz's Studio (uncredited)", + "credit_id": "55f1b507925141670d00239a", + "order": 41 + }, + { + "adult": false, + "gender": 2, + "id": 160621, + "known_for_department": "Acting", + "name": "Ron Gilbert", + "original_name": "Ron Gilbert", + "popularity": 1.932, + "profile_path": "/qrY6w2k0YpuMB2yTCOv6cxeQtmw.jpg", + "cast_id": 84, + "character": "Usher in Bridal Party (uncredited)", + "credit_id": "55f1b52f9251416711002211", + "order": 42 + }, + { + "adult": false, + "gender": 2, + "id": 1233541, + "known_for_department": "Acting", + "name": "Sonny Grosso", + "original_name": "Sonny Grosso", + "popularity": 1.96, + "profile_path": null, + "cast_id": 87, + "character": "Cop Outside Hospital (uncredited)", + "credit_id": "55f1b5bb925141670100210a", + "order": 43 + }, + { + "adult": false, + "gender": 2, + "id": 1503036, + "known_for_department": "Acting", + "name": "Joe Lo Grippo", + "original_name": "Joe Lo Grippo", + "popularity": 1.96, + "profile_path": null, + "cast_id": 86, + "character": "Sonny's Bodyguard (uncredited)", + "credit_id": "55f1b59dc3a3683bec00231e", + "order": 44 + }, + { + "adult": false, + "gender": 2, + "id": 1068099, + "known_for_department": "Acting", + "name": "Randy Jurgensen", + "original_name": "Randy Jurgensen", + "popularity": 3.685, + "profile_path": "/xRoN79wRLndTMCJoaBSsdjQVIzI.jpg", + "cast_id": 89, + "character": "Sonny's Killer #1 (uncredited)", + "credit_id": "55f1b61ec3a3683bec002335", + "order": 45 + }, + { + "adult": false, + "gender": 2, + "id": 61241, + "known_for_department": "Acting", + "name": "Tony Lip", + "original_name": "Tony Lip", + "popularity": 6.271, + "profile_path": "/6xz6XmjgiCCOm067OzWYziD4Mxa.jpg", + "cast_id": 90, + "character": "Wedding Guest (uncredited)", + "credit_id": "55f1b685c3a36823070023b6", + "order": 46 + }, + { + "adult": false, + "gender": 2, + "id": 106187, + "known_for_department": "Acting", + "name": "Lou Martini Jr.", + "original_name": "Lou Martini Jr.", + "popularity": 4.22, + "profile_path": "/85s5KR0BA6dhMJtOrvtKGY8Bz8E.jpg", + "cast_id": 91, + "character": "Boy at Wedding (uncredited)", + "credit_id": "55f1b6c39251416701002141", + "order": 47 + }, + { + "adult": false, + "gender": 2, + "id": 100503, + "known_for_department": "Acting", + "name": "Raymond Martino", + "original_name": "Raymond Martino", + "popularity": 1.988, + "profile_path": null, + "cast_id": 92, + "character": "Corleone Family Member (uncredited)", + "credit_id": "55f1b6e59251416711002257", + "order": 48 + }, + { + "adult": false, + "gender": 2, + "id": 1503035, + "known_for_department": "Acting", + "name": "Joseph Medaglia", + "original_name": "Joseph Medaglia", + "popularity": 1.721, + "profile_path": null, + "cast_id": 93, + "character": "Priest at Baptism (uncredited)", + "credit_id": "55f1b73dc3a3685eb80005f7", + "order": 49 + }, + { + "adult": false, + "gender": 0, + "id": 1290895, + "known_for_department": "Acting", + "name": "Rick Petrucelli", + "original_name": "Rick Petrucelli", + "popularity": 1.96, + "profile_path": null, + "cast_id": 94, + "character": "Man in Passenger Seat (uncredited)", + "credit_id": "55f1b769925141671400218e", + "order": 50 + }, + { + "adult": false, + "gender": 0, + "id": 1237372, + "known_for_department": "Acting", + "name": "Sal Richards", + "original_name": "Sal Richards", + "popularity": 1.96, + "profile_path": "/gZSiZOtBVv1U4hqjlKkPr2p5rCP.jpg", + "cast_id": 95, + "character": "Drunk (uncredited)", + "credit_id": "55f1b7c29251410d6d000acb", + "order": 51 + }, + { + "adult": false, + "gender": 2, + "id": 1213795, + "known_for_department": "Acting", + "name": "Tom Rosqui", + "original_name": "Tom Rosqui", + "popularity": 2.744, + "profile_path": null, + "cast_id": 96, + "character": "Rocco Lampone (uncredited)", + "credit_id": "55f1b7e2925141670d00243a", + "order": 52 + }, + { + "adult": false, + "gender": 2, + "id": 11480, + "known_for_department": "Acting", + "name": "Frank Sivero", + "original_name": "Frank Sivero", + "popularity": 9.382, + "profile_path": "/eqvhj0iNtcsN6EJhd21Goqi1DSq.jpg", + "cast_id": 97, + "character": "Street Extra (uncredited)", + "credit_id": "55f1b81092514167140021b1", + "order": 53 + }, + { + "adult": false, + "gender": 0, + "id": 1209678, + "known_for_department": "Acting", + "name": "Filomena Spagnuolo", + "original_name": "Filomena Spagnuolo", + "popularity": 1.961, + "profile_path": null, + "cast_id": 98, + "character": "Extra in Wedding Scene (uncredited)", + "credit_id": "55f1b83592514167140021c1", + "order": 54 + }, + { + "adult": false, + "gender": 2, + "id": 78336, + "known_for_department": "Directing", + "name": "Nick Vallelonga", + "original_name": "Nick Vallelonga", + "popularity": 6.765, + "profile_path": "/h0GwAHN1J5lDysqgWxMIg66LNBW.jpg", + "cast_id": 100, + "character": "Wedding Party Guest (uncredited)", + "credit_id": "55f1b89b9251410d6d000af0", + "order": 55 + }, + { + "adult": false, + "gender": 2, + "id": 16525, + "known_for_department": "Acting", + "name": "Joe Spinell", + "original_name": "Joe Spinell", + "popularity": 14.062, + "profile_path": "/aTDs8125mhLKlQSbUlFJC2LJ8tU.jpg", + "cast_id": 99, + "character": "William 'Willie' Cicci (uncredited)", + "credit_id": "55f1b85d925141670500228d", + "order": 56 + }, + { + "adult": false, + "gender": 2, + "id": 1234848, + "known_for_department": "Acting", + "name": "Conrad Yama", + "original_name": "Conrad Yama", + "popularity": 2.391, + "profile_path": null, + "cast_id": 101, + "character": "Fruit Vendor (uncredited)", + "credit_id": "55f1b90892514167050022a8", + "order": 57 + } + ], + "crew": [ + { + "adult": false, + "gender": 2, + "id": 154, + "known_for_department": "Editing", + "name": "Walter Murch", + "original_name": "Walter Murch", + "popularity": 2.351, + "profile_path": "/kQh7U7kRLF9NKEMpxiGjZEIu0o3.jpg", + "credit_id": "62bd43fb63d9370092ba09da", + "department": "Crew", + "job": "Post Production Consulting" + }, + { + "adult": false, + "gender": 2, + "id": 1776, + "known_for_department": "Directing", + "name": "Francis Ford Coppola", + "original_name": "Francis Ford Coppola", + "popularity": 8.104, + "profile_path": "/mGKkVp3l9cipPt10AqoQnwaPrfI.jpg", + "credit_id": "52fe422bc3a36847f80093db", + "department": "Writing", + "job": "Screenplay" + }, + { + "adult": false, + "gender": 2, + "id": 1776, + "known_for_department": "Directing", + "name": "Francis Ford Coppola", + "original_name": "Francis Ford Coppola", + "popularity": 8.104, + "profile_path": "/mGKkVp3l9cipPt10AqoQnwaPrfI.jpg", + "credit_id": "5e92505cccb15f00136de455", + "department": "Directing", + "job": "Director" + }, + { + "adult": false, + "gender": 2, + "id": 1776, + "known_for_department": "Directing", + "name": "Francis Ford Coppola", + "original_name": "Francis Ford Coppola", + "popularity": 8.104, + "profile_path": "/mGKkVp3l9cipPt10AqoQnwaPrfI.jpg", + "credit_id": "5f9c86e3bc8abc003b2fe6b7", + "department": "Crew", + "job": "Thanks" + }, + { + "adult": false, + "gender": 2, + "id": 2870, + "known_for_department": "Production", + "name": "Gray Frederickson", + "original_name": "Gray Frederickson", + "popularity": 2.652, + "profile_path": null, + "credit_id": "53884fe1c3a3681ad7004590", + "department": "Production", + "job": "Associate Producer" + }, + { + "adult": false, + "gender": 2, + "id": 2871, + "known_for_department": "Production", + "name": "Fred Roos", + "original_name": "Fred Roos", + "popularity": 4.55, + "profile_path": "/qfXLY5E589l0TQnoayWmUrBDsgo.jpg", + "credit_id": "53885041c3a3681ad700459b", + "department": "Production", + "job": "Casting" + }, + { + "adult": false, + "gender": 2, + "id": 2872, + "known_for_department": "Sound", + "name": "Carmine Coppola", + "original_name": "Carmine Coppola", + "popularity": 2.951, + "profile_path": "/iURpKXIgdtQoVCBQ8GKGvb0Qw5z.jpg", + "credit_id": "62bd43877304b52384a221cb", + "department": "Crew", + "job": "Additional Music" + }, + { + "adult": false, + "gender": 2, + "id": 2875, + "known_for_department": "Art", + "name": "Dean Tavoularis", + "original_name": "Dean Tavoularis", + "popularity": 5.216, + "profile_path": "/hrRBaXUn2k29qd1MvAxuag5L32c.jpg", + "credit_id": "53884b4e0e0a261441005deb", + "department": "Art", + "job": "Production Design" + }, + { + "adult": false, + "gender": 2, + "id": 457, + "known_for_department": "Production", + "name": "Albert S. Ruddy", + "original_name": "Albert S. Ruddy", + "popularity": 11.382, + "profile_path": null, + "credit_id": "52fe422bc3a36847f80093cf", + "department": "Production", + "job": "Producer" + }, + { + "adult": false, + "gender": 2, + "id": 949, + "known_for_department": "Production", + "name": "Louis DiGiaimo", + "original_name": "Louis DiGiaimo", + "popularity": 2.744, + "profile_path": null, + "credit_id": "53885009c3a3682f780004c6", + "department": "Production", + "job": "Casting" + }, + { + "adult": false, + "gender": 2, + "id": 1044, + "known_for_department": "Camera", + "name": "Michael Chapman", + "original_name": "Michael Chapman", + "popularity": 2.517, + "profile_path": "/vFFWHlwwIgWoi2NPoVq3uzikCnM.jpg", + "credit_id": "62bd4492097c490051497166", + "department": "Camera", + "job": "Camera Operator" + }, + { + "adult": false, + "gender": 2, + "id": 3083, + "known_for_department": "Writing", + "name": "Mario Puzo", + "original_name": "Mario Puzo", + "popularity": 4.179, + "profile_path": "/lEsT1uCZAZg1nYDQe3Fsj9CalzT.jpg", + "credit_id": "52fe422bc3a36847f80093d5", + "department": "Writing", + "job": "Screenplay" + }, + { + "adult": false, + "gender": 2, + "id": 3083, + "known_for_department": "Writing", + "name": "Mario Puzo", + "original_name": "Mario Puzo", + "popularity": 4.179, + "profile_path": "/lEsT1uCZAZg1nYDQe3Fsj9CalzT.jpg", + "credit_id": "538848cec3a3681aea00405a", + "department": "Writing", + "job": "Novel" + }, + { + "adult": false, + "gender": 2, + "id": 3097, + "known_for_department": "Camera", + "name": "Gordon Willis", + "original_name": "Gordon Willis", + "popularity": 1.339, + "profile_path": "/6gOmfatlF7UdhaHi2tPsZMb5vmz.jpg", + "credit_id": "538848a0c3a3681ae3004283", + "department": "Camera", + "job": "Director of Photography" + }, + { + "adult": false, + "gender": 2, + "id": 3098, + "known_for_department": "Sound", + "name": "Nino Rota", + "original_name": "Nino Rota", + "popularity": 5.153, + "profile_path": "/v42v3uM7jqwoZxDJ8MlEtT6Ol7a.jpg", + "credit_id": "538848b1c3a3681aea004056", + "department": "Sound", + "job": "Original Music Composer" + }, + { + "adult": false, + "gender": 2, + "id": 3099, + "known_for_department": "Editing", + "name": "William Reynolds", + "original_name": "William Reynolds", + "popularity": 1.4, + "profile_path": null, + "credit_id": "52fe422bc3a36847f80093ed", + "department": "Editing", + "job": "Editor" + }, + { + "adult": false, + "gender": 2, + "id": 3100, + "known_for_department": "Editing", + "name": "Peter Zinner", + "original_name": "Peter Zinner", + "popularity": 1.96, + "profile_path": null, + "credit_id": "52fe422bc3a36847f80093f3", + "department": "Editing", + "job": "Editor" + }, + { + "adult": false, + "gender": 0, + "id": 3101, + "known_for_department": "Production", + "name": "Andrea Eastman", + "original_name": "Andrea Eastman", + "popularity": 1.4, + "profile_path": null, + "credit_id": "53885029c3a3681ad7004596", + "department": "Production", + "job": "Casting" + }, + { + "adult": false, + "gender": 2, + "id": 3103, + "known_for_department": "Sound", + "name": "Charles Grenzbach", + "original_name": "Charles Grenzbach", + "popularity": 1.22, + "profile_path": null, + "credit_id": "570006ec9251417647000790", + "department": "Sound", + "job": "Sound Re-Recording Mixer" + }, + { + "adult": false, + "gender": 2, + "id": 3104, + "known_for_department": "Sound", + "name": "Chris Newman", + "original_name": "Chris Newman", + "popularity": 1.306, + "profile_path": null, + "credit_id": "569d53449251415e67004177", + "department": "Sound", + "job": "Sound" + }, + { + "adult": false, + "gender": 2, + "id": 3105, + "known_for_department": "Sound", + "name": "Richard Portman", + "original_name": "Richard Portman", + "popularity": 1.96, + "profile_path": null, + "credit_id": "568c060ac3a3685f89018710", + "department": "Sound", + "job": "Sound Re-Recording Mixer" + }, + { + "adult": false, + "gender": 0, + "id": 3106, + "known_for_department": "Sound", + "name": "John C. Hammell", + "original_name": "John C. Hammell", + "popularity": 1.4, + "profile_path": null, + "credit_id": "56cb4864c3a3684e2500045e", + "department": "Sound", + "job": "Music Editor" + }, + { + "adult": false, + "gender": 2, + "id": 6099, + "known_for_department": "Production", + "name": "Valerio De Paolis", + "original_name": "Valerio De Paolis", + "popularity": 1.964, + "profile_path": "/rbsYkbJPgQAMuFY71RYrYatdXWT.jpg", + "credit_id": "563e3d18c3a3681b5402d6d3", + "department": "Production", + "job": "Production Manager" + }, + { + "adult": false, + "gender": 1, + "id": 6851, + "known_for_department": "Costume & Make-Up", + "name": "Anna Hill Johnstone", + "original_name": "Anna Hill Johnstone", + "popularity": 1.4, + "profile_path": null, + "credit_id": "53884fc3c3a3681add0044ad", + "department": "Costume & Make-Up", + "job": "Costume Design" + }, + { + "adult": false, + "gender": 0, + "id": 16194, + "known_for_department": "Costume & Make-Up", + "name": "Philip Leto", + "original_name": "Philip Leto", + "popularity": 1.4, + "profile_path": null, + "credit_id": "56a76fd49251412a04002fa6", + "department": "Costume & Make-Up", + "job": "Hairstylist" + }, + { + "adult": false, + "gender": 2, + "id": 10546, + "known_for_department": "Production", + "name": "Fred T. Gallo", + "original_name": "Fred T. Gallo", + "popularity": 1.4, + "profile_path": null, + "credit_id": "5e9250a3875d1a001442c342", + "department": "Directing", + "job": "Assistant Director" + }, + { + "adult": false, + "gender": 0, + "id": 11789, + "known_for_department": "Production", + "name": "Fred C. Caruso", + "original_name": "Fred C. Caruso", + "popularity": 1.662, + "profile_path": null, + "credit_id": "57447bba925141110900063d", + "department": "Production", + "job": "Unit Production Manager" + }, + { + "adult": false, + "gender": 2, + "id": 12288, + "known_for_department": "Production", + "name": "Robert Evans", + "original_name": "Robert Evans", + "popularity": 2.391, + "profile_path": "/rCiTT2VrBKeHWdnHAyfGtMXs9go.jpg", + "credit_id": "57447b41c3a36807770006ab", + "department": "Production", + "job": "Executive Producer" + }, + { + "adult": false, + "gender": 0, + "id": 14059, + "known_for_department": "Crew", + "name": "Sass Bedig", + "original_name": "Sass Bedig", + "popularity": 1.932, + "profile_path": null, + "credit_id": "57448523925141218f000400", + "department": "Crew", + "job": "Special Effects" + }, + { + "adult": false, + "gender": 2, + "id": 29389, + "known_for_department": "Sound", + "name": "Carlo Savina", + "original_name": "Carlo Savina", + "popularity": 2.188, + "profile_path": null, + "credit_id": "62bd436412aabc151f2dbaef", + "department": "Sound", + "job": "Conductor" + }, + { + "adult": false, + "gender": 2, + "id": 29654, + "known_for_department": "Costume & Make-Up", + "name": "Dick Smith", + "original_name": "Dick Smith", + "popularity": 1.88, + "profile_path": "/kKCenlQ8iQCTLGfX8ZPpJFpxvwS.jpg", + "credit_id": "56a76ff8c3a36819c9002fcb", + "department": "Costume & Make-Up", + "job": "Makeup Artist" + }, + { + "adult": false, + "gender": 2, + "id": 30580, + "known_for_department": "Art", + "name": "Philip Smith", + "original_name": "Philip Smith", + "popularity": 1.4, + "profile_path": null, + "credit_id": "53884fb2c3a3681ad700458c", + "department": "Art", + "job": "Set Decoration" + }, + { + "adult": false, + "gender": 0, + "id": 81519, + "known_for_department": "Art", + "name": "Warren Clymer", + "original_name": "Warren Clymer", + "popularity": 1.388, + "profile_path": null, + "credit_id": "53884f8dc3a3681add0044a6", + "department": "Art", + "job": "Art Direction" + }, + { + "adult": false, + "gender": 0, + "id": 81532, + "known_for_department": "Costume & Make-Up", + "name": "George Newman", + "original_name": "George Newman", + "popularity": 1.96, + "profile_path": null, + "credit_id": "62bd446a7304b523856c2c25", + "department": "Costume & Make-Up", + "job": "Wardrobe Supervisor" + }, + { + "adult": false, + "gender": 2, + "id": 51302, + "known_for_department": "Crew", + "name": "Joe Bucaro III", + "original_name": "Joe Bucaro III", + "popularity": 4.134, + "profile_path": "/gvJs1YQZRrSbkiSGAiCAugaXyzl.jpg", + "credit_id": "574486e0c3a368076b000ac6", + "department": "Crew", + "job": "Stunts" + }, + { + "adult": false, + "gender": 2, + "id": 96912, + "known_for_department": "Crew", + "name": "Joe Lombardi", + "original_name": "Joe Lombardi", + "popularity": 1.602, + "profile_path": null, + "credit_id": "574485a9925141218f000420", + "department": "Crew", + "job": "Special Effects" + }, + { + "adult": false, + "gender": 2, + "id": 718968, + "known_for_department": "Production", + "name": "Stephen F. Kesten", + "original_name": "Stephen F. Kesten", + "popularity": 2.216, + "profile_path": null, + "credit_id": "5e9250b284448e00148f0783", + "department": "Directing", + "job": "Assistant Director" + }, + { + "adult": false, + "gender": 2, + "id": 1318092, + "known_for_department": "Crew", + "name": "Paul J. Lombardi", + "original_name": "Paul J. Lombardi", + "popularity": 1.38, + "profile_path": null, + "credit_id": "5744865f92514120a70004b3", + "department": "Visual Effects", + "job": "Special Effects Supervisor" + }, + { + "adult": false, + "gender": 2, + "id": 1433439, + "known_for_department": "Crew", + "name": "Paul Baxley", + "original_name": "Paul Baxley", + "popularity": 3.175, + "profile_path": null, + "credit_id": "574486b4c3a3681c760004a9", + "department": "Crew", + "job": "Stunt Coordinator" + }, + { + "adult": false, + "gender": 1, + "id": 1511672, + "known_for_department": "Directing", + "name": "Nancy Hopton", + "original_name": "Nancy Hopton", + "popularity": 0.98, + "profile_path": null, + "credit_id": "62bd44c217b5ef04e6b3fc04", + "department": "Directing", + "job": "Script Supervisor" + }, + { + "adult": false, + "gender": 0, + "id": 1518601, + "known_for_department": "Costume & Make-Up", + "name": "Phil Rhodes", + "original_name": "Phil Rhodes", + "popularity": 1.96, + "profile_path": null, + "credit_id": "56a76fc79251412a04002fa3", + "department": "Costume & Make-Up", + "job": "Makeup Artist" + }, + { + "adult": false, + "gender": 2, + "id": 1546904, + "known_for_department": "Camera", + "name": "Robert Ward", + "original_name": "Robert Ward", + "popularity": 0.98, + "profile_path": null, + "credit_id": "566871e5c3a36836ac0038f7", + "department": "Camera", + "job": "Grip" + }, + { + "adult": false, + "gender": 0, + "id": 1614958, + "known_for_department": "Crew", + "name": "Steven Burnett", + "original_name": "Steven Burnett", + "popularity": 1.614, + "profile_path": null, + "credit_id": "5744870d92514133d400001b", + "department": "Crew", + "job": "Stunts" + }, + { + "adult": false, + "gender": 0, + "id": 1625346, + "known_for_department": "Production", + "name": "Ned Kopp", + "original_name": "Ned Kopp", + "popularity": 1.4, + "profile_path": null, + "credit_id": "57447c09925141110f000695", + "department": "Production", + "job": "Production Manager" + }, + { + "adult": false, + "gender": 2, + "id": 1625347, + "known_for_department": "Directing", + "name": "Tony Brandt", + "original_name": "Tony Brandt", + "popularity": 1.4, + "profile_path": null, + "credit_id": "5e92510accb15f00156de8d4", + "department": "Directing", + "job": "Assistant Director" + }, + { + "adult": false, + "gender": 0, + "id": 1789636, + "known_for_department": "Sound", + "name": "Carl Fortina", + "original_name": "Carl Fortina", + "popularity": 1.96, + "profile_path": null, + "credit_id": "58e3cf17c3a3684aa40124b5", + "department": "Sound", + "job": "Musician" + }, + { + "adult": false, + "gender": 0, + "id": 3357584, + "known_for_department": "Production", + "name": "Michael Briggs", + "original_name": "Michael Briggs", + "popularity": 1.094, + "profile_path": null, + "credit_id": "62bd45a47ef3812768ed8113", + "department": "Production", + "job": "Location Coordinator" + }, + { + "adult": false, + "gender": 0, + "id": 3606474, + "known_for_department": "Production", + "name": "Tony Bowers", + "original_name": "Tony Bowers", + "popularity": 0.98, + "profile_path": null, + "credit_id": "62bd45e59a9f9a008bf772b9", + "department": "Production", + "job": "Location Coordinator" + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/the_godfather_reviews.json b/spec/fixtures/the_godfather_reviews.json new file mode 100644 index 0000000000..ba2234159e --- /dev/null +++ b/spec/fixtures/the_godfather_reviews.json @@ -0,0 +1,36 @@ +{ + "id": 238, + "page": 1, + "results": [ + { + "author": "futuretv", + "author_details": { + "name": "", + "username": "futuretv", + "avatar_path": "/https://www.gravatar.com/avatar/f44259356bf6110070ed799323d539d6.jpg", + "rating": 10.0 + }, + "content": "The Godfather Review by Al Carlson\r\n\r\nThe Godfather is a film considered by most to be one of the greatest ever made. From The American Film Institute to as voted by users on the Internet Movie Database (IMDB) it is consider to be one of the best. As a film that ranks as high as other masterpieces including Citizen Kane, Pulp Fiction and 12 Angry Men, The Godfather is an exceptional piece of cinema excellence that is flawless and is simply the pinnacle crime drama.\r\nThe Godfather revolves around the Corleone’s, an Italian family with deep roots in the New York City mafia. The head of the Corleone’s is Don Vito Corleone, a man who takes care of his family and demands respect in return. His son Michael however, who just returned home from World War II, doesn’t want to become involved with the family business. The Sollozzo’s, a family of drug dealers, confront Don and request protection in exchange for profits from the Sollozzo’s drug sales. But Don declines the offer, for he is against selling narcotics. The rejected offer starts what turns into an all out mafia war between the two families with Michael diving deep into the mafia lifestyle.\r\nThe characters are portrayed by a legendary all-star cast including Marlon Brando as Don, Al Pacino as his son Michael and James Caan as Don’s oldest son Sonny. The casting for this film has been considered by many to be the best casted film in history for their astonishing performances. All three main actors were nominated for an academy award, but only Marlon Brando won an Oscar for best actor in a leading role. It’s fascinating to watch how these characters change over the course of the movie, with one in particular changing drastically.\r\nDirecter Francis Ford Coppola, being raised in an Italian-American family in New York, understood Italian culture exceptionally and made the film very authentically. Everything from the wedding dances to the cuisine to the terms used by the characters in Sicilian come from Coppola’s first-hand knowledge of Italian-American culture. Italian composer Nino Rota did an outstanding job making the soundtrack for the film, despite not getting the Oscar for best music (but he did win an Oscar for his work in the sequel, The Godfather: Part II). Virtually everyone recognizes that iconic trumpet solo once it starts playing. He also wrote the score for another great Italian film 8 1/2. \r\nThis film should be immediately followed up by it’s sequel, The Godfather: Part II, which also won best picture. The series still holds the title of most best picture awards for a film series to this day. They’re both flawless crime dramas and have earned their titles as some of the best pieces of cinema ever. I will guarantee you won’t be able to see this film only once, as it gets better after continual viewings. There is really nothing more to add other than if you haven’t seen this movie yet, it’s about time you did. The Godfather is a movie you can’t refuse.", + "created_at": "2014-04-10T20:09:40.500Z", + "id": "5346fa840e0a265ffa001e20", + "updated_at": "2021-06-23T15:57:25.895Z", + "url": "https://www.themoviedb.org/review/5346fa840e0a265ffa001e20" + }, + { + "author": "crastana", + "author_details": { + "name": "", + "username": "crastana", + "avatar_path": "/https://www.gravatar.com/avatar/87b1f10dd7dae245ac84657537983336.jpg", + "rating": 10.0 + }, + "content": "The best movie ever...\r\nA masterpiece by the young and talented Francis Ford Coppola, about a Mob family and their drama, the story telling is perfect, the acting good, sometimes a little over the top in the case of Thalia Shire (the sister of the director)\r\n\r\nThe 70's were the best years for Hollywood.", + "created_at": "2022-07-18T23:18:07.748Z", + "id": "62d5ea2fe93e95095cbddefe", + "updated_at": "2022-07-26T14:21:07.910Z", + "url": "https://www.themoviedb.org/review/62d5ea2fe93e95095cbddefe" + } + ], + "total_pages": 1, + "total_results": 2 +} \ No newline at end of file diff --git a/spec/fixtures/the_grand_budapest_hotel.json b/spec/fixtures/the_grand_budapest_hotel.json new file mode 100644 index 0000000000..2de5458b26 --- /dev/null +++ b/spec/fixtures/the_grand_budapest_hotel.json @@ -0,0 +1,44 @@ +{ + "page": 1, + "results": [ + { + "adult": false, + "backdrop_path": "/xHDynIimfsgj0ZOs0j5ma8v1vmM.jpg", + "genre_ids": [ + 35, + 18 + ], + "id": 120467, + "original_language": "en", + "original_title": "The Grand Budapest Hotel", + "overview": "The Grand Budapest Hotel tells of a legendary concierge at a famous European hotel between the wars and his friendship with a young employee who becomes his trusted protégé. The story involves the theft and recovery of a priceless Renaissance painting, the battle for an enormous family fortune and the slow and then sudden upheavals that transformed Europe during the first half of the 20th century.", + "popularity": 28.028, + "poster_path": "/eWdyYQreja6JGCzqHWXpWHDrrPo.jpg", + "release_date": "2014-02-26", + "title": "The Grand Budapest Hotel", + "video": false, + "vote_average": 8.1, + "vote_count": 13047 + }, + { + "adult": false, + "backdrop_path": null, + "genre_ids": [ + 99 + ], + "id": 665027, + "original_language": "en", + "original_title": "The Wes Anderson Collection: The Grand Budapest Hotel", + "overview": "Visual essay on Wes Anderson's 2014 film THE GRAND BUDAPEST HOTEL", + "popularity": 0.635, + "poster_path": "/gvC7lhYVzCPrsWLU1WmAfLtfmkE.jpg", + "release_date": "2015-02-16", + "title": "The Wes Anderson Collection: The Grand Budapest Hotel", + "video": false, + "vote_average": 7, + "vote_count": 1 + } + ], + "total_pages": 1, + "total_results": 2 +} \ No newline at end of file diff --git a/spec/fixtures/top_rated_movies.json b/spec/fixtures/top_rated_movies.json new file mode 100644 index 0000000000..6883aed5f7 --- /dev/null +++ b/spec/fixtures/top_rated_movies.json @@ -0,0 +1,397 @@ +{ + "page": 1, + "results": [ + { + "adult": false, + "backdrop_path": "/tmU7GeKVybMWFButWEGl2M4GeiP.jpg", + "genre_ids": [ + 18, + 80 + ], + "id": 238, + "original_language": "en", + "original_title": "The Godfather", + "overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son, Michael steps in to take care of the would-be killers, launching a campaign of bloody revenge.", + "popularity": 92.736, + "poster_path": "/3bhkrj58Vtu7enYsRolD1fZdja1.jpg", + "release_date": "1972-03-14", + "title": "The Godfather", + "video": false, + "vote_average": 8.7, + "vote_count": 17394 + }, + { + "adult": false, + "backdrop_path": "/wPU78OPN4BYEgWYdXyg0phMee64.jpg", + "genre_ids": [ + 18, + 80 + ], + "id": 278, + "original_language": "en", + "original_title": "The Shawshank Redemption", + "overview": "Framed in the 1940s for the double murder of his wife and her lover, upstanding banker Andy Dufresne begins a new life at the Shawshank prison, where he puts his accounting skills to work for an amoral warden. During his long stretch in prison, Dufresne comes to be admired by the other inmates -- including an older prisoner named Red -- for his integrity and unquenchable sense of hope.", + "popularity": 86.719, + "poster_path": "/hBcY0fE9pfXzvVaY4GKarweriG2.jpg", + "release_date": "1994-09-23", + "title": "The Shawshank Redemption", + "video": false, + "vote_average": 8.7, + "vote_count": 23186 + }, + { + "adult": false, + "backdrop_path": "/kGzFbGhp99zva6oZODW5atUtnqi.jpg", + "genre_ids": [ + 18, + 80 + ], + "id": 240, + "original_language": "en", + "original_title": "The Godfather Part II", + "overview": "In the continuing saga of the Corleone crime family, a young Vito Corleone grows up in Sicily and in 1910s New York. In the 1950s, Michael Corleone attempts to expand the family business into Las Vegas, Hollywood and Cuba.", + "popularity": 55.562, + "poster_path": "/hek3koDUyRQk7FIhPXsa6mT2Zc3.jpg", + "release_date": "1974-12-20", + "title": "The Godfather Part II", + "video": false, + "vote_average": 8.6, + "vote_count": 10531 + }, + { + "adult": false, + "backdrop_path": "/faXT8V80JRhnArTAeYXz0Eutpv9.jpg", + "genre_ids": [ + 16, + 28, + 12, + 35, + 10751, + 14 + ], + "id": 315162, + "original_language": "en", + "original_title": "Puss in Boots: The Last Wish", + "overview": "Puss in Boots discovers that his passion for adventure has taken its toll: He has burned through eight of his nine lives, leaving him with only one life left. Puss sets out on an epic journey to find the mythical Last Wish and restore his nine lives.", + "popularity": 5032.178, + "poster_path": "/kuf6dutpsT0vSVehic3EZIqkOBt.jpg", + "release_date": "2022-12-07", + "title": "Puss in Boots: The Last Wish", + "video": false, + "vote_average": 8.6, + "vote_count": 3032 + }, + { + "adult": false, + "backdrop_path": "/zb6fM1CX41D9rF9hdgclu0peUmy.jpg", + "genre_ids": [ + 18, + 36, + 10752 + ], + "id": 424, + "original_language": "en", + "original_title": "Schindler's List", + "overview": "The true story of how businessman Oskar Schindler saved over a thousand Jewish lives from the Nazis while they worked as slaves in his factory during World War II.", + "popularity": 50.058, + "poster_path": "/sF1U4EUQS8YHUYjNl3pMGNIQyr0.jpg", + "release_date": "1993-12-15", + "title": "Schindler's List", + "video": false, + "vote_average": 8.6, + "vote_count": 13708 + }, + { + "adult": false, + "backdrop_path": "/vI3aUGTuRRdM7J78KIdW98LdxE5.jpg", + "genre_ids": [ + 35, + 18, + 10749 + ], + "id": 19404, + "original_language": "hi", + "original_title": "दिलवाले दुल्हनिया ले जायेंगे", + "overview": "Raj is a rich, carefree, happy-go-lucky second generation NRI. Simran is the daughter of Chaudhary Baldev Singh, who in spite of being an NRI is very strict about adherence to Indian values. Simran has left for India to be married to her childhood fiancé. Raj leaves for India with a mission at his hands, to claim his lady love under the noses of her whole family. Thus begins a saga.", + "popularity": 21.261, + "poster_path": "/2CAL2433ZeIihfX1Hb2139CX0pW.jpg", + "release_date": "1995-10-19", + "title": "Dilwale Dulhania Le Jayenge", + "video": false, + "vote_average": 8.6, + "vote_count": 4038 + }, + { + "adult": false, + "backdrop_path": "/Ab8mkHmkYADjU7wQiOkia9BzGvS.jpg", + "genre_ids": [ + 16, + 10751, + 14 + ], + "id": 129, + "original_language": "ja", + "original_title": "千と千尋の神隠し", + "overview": "A young girl, Chihiro, becomes trapped in a strange new world of spirits. When her parents undergo a mysterious transformation, she must call upon the courage she never knew she had to free her family.", + "popularity": 73.321, + "poster_path": "/39wmItIWsg5sZMyRUHLkWBcuVCM.jpg", + "release_date": "2002-09-20", + "title": "Spirited Away", + "video": false, + "vote_average": 8.5, + "vote_count": 13877 + }, + { + "adult": false, + "backdrop_path": "/qqHQsStV6exghCM7zbObuYBiYxw.jpg", + "genre_ids": [ + 18 + ], + "id": 389, + "original_language": "en", + "original_title": "12 Angry Men", + "overview": "The defense and the prosecution have rested and the jury is filing into the jury room to decide if a young Spanish-American is guilty or innocent of murdering his father. What begins as an open and shut case soon becomes a mini-drama of each of the jurors' prejudices and preconceptions about the trial, the accused, and each other.", + "popularity": 35.251, + "poster_path": "/ppd84D2i9W8jXmsyInGyihiSyqz.jpg", + "release_date": "1957-04-10", + "title": "12 Angry Men", + "video": false, + "vote_average": 8.5, + "vote_count": 6999 + }, + { + "adult": false, + "backdrop_path": "/dIWwZW7dJJtqC6CgWzYkNVKIUm8.jpg", + "genre_ids": [ + 10749, + 16, + 18 + ], + "id": 372058, + "original_language": "ja", + "original_title": "君の名は。", + "overview": "High schoolers Mitsuha and Taki are complete strangers living separate lives. But one night, they suddenly switch places. Mitsuha wakes up in Taki’s body, and he in hers. This bizarre occurrence continues to happen randomly, and the two must adjust their lives around each other.", + "popularity": 90.823, + "poster_path": "/q719jXXEzOoYaps6babgKnONONX.jpg", + "release_date": "2016-08-26", + "title": "Your Name.", + "video": false, + "vote_average": 8.5, + "vote_count": 9450 + }, + { + "adult": false, + "backdrop_path": "/hiKmpZMGZsrkA3cdce8a7Dpos1j.jpg", + "genre_ids": [ + 35, + 53, + 18 + ], + "id": 496243, + "original_language": "ko", + "original_title": "기생충", + "overview": "All unemployed, Ki-taek's family takes peculiar interest in the wealthy and glamorous Parks for their livelihood until they get entangled in an unexpected incident.", + "popularity": 69.319, + "poster_path": "/7IiTTgloJzvGI1TAYymCfbfl3vT.jpg", + "release_date": "2019-05-30", + "title": "Parasite", + "video": false, + "vote_average": 8.5, + "vote_count": 15169 + }, + { + "adult": false, + "backdrop_path": "/l6hQWH9eDksNJNiXWYRkWqikOdu.jpg", + "genre_ids": [ + 14, + 18, + 80 + ], + "id": 497, + "original_language": "en", + "original_title": "The Green Mile", + "overview": "A supernatural tale set on death row in a Southern prison, where gentle giant John Coffey possesses the mysterious power to heal people's ailments. When the cell block's head guard, Paul Edgecomb, recognizes Coffey's miraculous gift, he tries desperately to help stave off the condemned man's execution.", + "popularity": 126.022, + "poster_path": "/velWPhVMQeQKcxggNEU8YmIo52R.jpg", + "release_date": "1999-12-10", + "title": "The Green Mile", + "video": false, + "vote_average": 8.5, + "vote_count": 14978 + }, + { + "adult": false, + "backdrop_path": "/pbEkjhdfP7yuDcMB78YEZwgD4IN.jpg", + "genre_ids": [ + 18, + 28, + 80, + 53 + ], + "id": 155, + "original_language": "en", + "original_title": "The Dark Knight", + "overview": "Batman raises the stakes in his war on crime. With the help of Lt. Jim Gordon and District Attorney Harvey Dent, Batman sets out to dismantle the remaining criminal organizations that plague the streets. The partnership proves to be effective, but they soon find themselves prey to a reign of chaos unleashed by a rising criminal mastermind known to the terrified citizens of Gotham as the Joker.", + "popularity": 73.58, + "poster_path": "/qJ2tW6WMUDux911r6m7haRef0WH.jpg", + "release_date": "2008-07-14", + "title": "The Dark Knight", + "video": false, + "vote_average": 8.5, + "vote_count": 29083 + }, + { + "adult": false, + "backdrop_path": "/suaEOtk1N1sgg2MTM7oZd2cfVp3.jpg", + "genre_ids": [ + 53, + 80 + ], + "id": 680, + "original_language": "en", + "original_title": "Pulp Fiction", + "overview": "A burger-loving hit man, his philosophical partner, a drug-addled gangster's moll and a washed-up boxer converge in this sprawling, comedic crime caper. Their adventures unfurl in three stories that ingeniously trip back and forth in time.", + "popularity": 57.582, + "poster_path": "/fIE3lAGcZDV1G6XM5KmuWnNsPp1.jpg", + "release_date": "1994-09-10", + "title": "Pulp Fiction", + "video": false, + "vote_average": 8.5, + "vote_count": 24566 + }, + { + "adult": false, + "backdrop_path": "/eoCSp75lxatmIa6aGqfnzwtbttd.jpg", + "genre_ids": [ + 37 + ], + "id": 429, + "original_language": "it", + "original_title": "Il buono, il brutto, il cattivo", + "overview": "While the Civil War rages on between the Union and the Confederacy, three men – a quiet loner, a ruthless hitman, and a Mexican bandit – comb the American Southwest in search of a strongbox containing $200,000 in stolen gold.", + "popularity": 45.365, + "poster_path": "/bX2xnavhMYjWDoZp1VM6VnU1xwe.jpg", + "release_date": "1966-12-23", + "title": "The Good, the Bad and the Ugly", + "video": false, + "vote_average": 8.5, + "vote_count": 7171 + }, + { + "adult": false, + "backdrop_path": "/3h1JZGDhZ8nzxdgvkxha0qBqi05.jpg", + "genre_ids": [ + 35, + 18, + 10749 + ], + "id": 13, + "original_language": "en", + "original_title": "Forrest Gump", + "overview": "A man with a low IQ has accomplished great things in his life and been present during significant historic events—in each case, far exceeding what anyone imagined he could do. But despite all he has achieved, his one true love eludes him.", + "popularity": 61.776, + "poster_path": "/arw2vcBveWOVZr6pxd9XTd1TdQa.jpg", + "release_date": "1994-06-23", + "title": "Forrest Gump", + "video": false, + "vote_average": 8.5, + "vote_count": 24061 + }, + { + "adult": false, + "backdrop_path": "/w2uGvCpMtvRqZg6waC1hvLyZoJa.jpg", + "genre_ids": [ + 10749 + ], + "id": 696374, + "original_language": "en", + "original_title": "Gabriel's Inferno", + "overview": "An intriguing and sinful exploration of seduction, forbidden love, and redemption, Gabriel's Inferno is a captivating and wildly passionate tale of one man's escape from his own personal hell as he tries to earn the impossible--forgiveness and love.", + "popularity": 13.989, + "poster_path": "/oyG9TL7FcRP4EZ9Vid6uKzwdndz.jpg", + "release_date": "2020-05-29", + "title": "Gabriel's Inferno", + "video": false, + "vote_average": 8.5, + "vote_count": 2321 + }, + { + "adult": false, + "backdrop_path": "/lXhgCODAbBXL5buk9yEmTpOoOgR.jpg", + "genre_ids": [ + 12, + 14, + 28 + ], + "id": 122, + "original_language": "en", + "original_title": "The Lord of the Rings: The Return of the King", + "overview": "Aragorn is revealed as the heir to the ancient kings as he, Gandalf and the other members of the broken fellowship struggle to save Gondor from Sauron's forces. Meanwhile, Frodo and Sam take the ring closer to the heart of Mordor, the dark lord's realm.", + "popularity": 75.701, + "poster_path": "/rCzpDGLbOoPwLjy3OAm5NUPOTrC.jpg", + "release_date": "2003-12-01", + "title": "The Lord of the Rings: The Return of the King", + "video": false, + "vote_average": 8.5, + "vote_count": 21010 + }, + { + "adult": false, + "backdrop_path": "/sw7mordbZxgITU877yTpZCud90M.jpg", + "genre_ids": [ + 18, + 80 + ], + "id": 769, + "original_language": "en", + "original_title": "GoodFellas", + "overview": "The true story of Henry Hill, a half-Irish, half-Sicilian Brooklyn kid who is adopted by neighbourhood gangsters at an early age and climbs the ranks of a Mafia family under the guidance of Jimmy Conway.", + "popularity": 35.297, + "poster_path": "/aKuFiU82s5ISJpGZp7YkIr3kCUd.jpg", + "release_date": "1990-09-12", + "title": "GoodFellas", + "video": false, + "vote_average": 8.5, + "vote_count": 10940 + }, + { + "adult": false, + "backdrop_path": "/jtAI6OJIWLWiRItNSZoWjrsUtmi.jpg", + "genre_ids": [ + 10749 + ], + "id": 724089, + "original_language": "en", + "original_title": "Gabriel's Inferno: Part II", + "overview": "Professor Gabriel Emerson finally learns the truth about Julia Mitchell's identity, but his realization comes a moment too late. Julia is done waiting for the well-respected Dante specialist to remember her and wants nothing more to do with him. Can Gabriel win back her heart before she finds love in another's arms?", + "popularity": 18.14, + "poster_path": "/x5o8cLZfEXMoZczTYWLrUo1P7UJ.jpg", + "release_date": "2020-07-31", + "title": "Gabriel's Inferno: Part II", + "video": false, + "vote_average": 8.5, + "vote_count": 1459 + }, + { + "adult": false, + "backdrop_path": "/zoVeIgKzGJzpdG6Gwnr7iOYfIMU.jpg", + "genre_ids": [ + 18, + 10749 + ], + "id": 11216, + "original_language": "it", + "original_title": "Nuovo Cinema Paradiso", + "overview": "A filmmaker recalls his childhood, when he fell in love with the movies at his village's theater and formed a deep friendship with the theater's projectionist.", + "popularity": 22.796, + "poster_path": "/8SRUfRUi6x4O68n0VCbDNRa6iGL.jpg", + "release_date": "1988-11-17", + "title": "Cinema Paradiso", + "video": false, + "vote_average": 8.5, + "vote_count": 3624 + } + ], + "total_pages": 184, + "total_results": 3678 +} \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000000..06932cfb80 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'Associations' do + it { should have_many(:user_viewing_parties) } + it { should have_many(:viewing_parties).through(:user_viewing_parties) } + end + + describe 'Validations' do + it { should validate_presence_of(:name) } + it { should validate_presence_of(:email) } + it { should validate_uniqueness_of(:email).with_message('User already exists with given email').case_insensitive } + it { should allow_value('sam@example.com').for(:email) } + it { should_not allow_value('samexample.com').for(:email) } + it { should validate_presence_of(:password) } + it { should validate_confirmation_of(:password)} + it { should have_secure_password} + it 'encrypts the password' do + user = User.create(name: 'Sam', email: 'sam@test.com', password: 'password123', password_confirmation: 'password123') + expect(user).to_not have_attribute(:password) + expect(user.password_digest).to_not eq('password123') + end + end + + describe 'Methods' do + before :each do + json_response_1 = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_1) + json_response_2 = File.read('spec/fixtures/shawshank_redemption.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/278?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_2) + json_response_3 = File.read('spec/fixtures/puss_in_boots.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/315162?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_3) + end + describe 'Instance Methods' do + let!(:users) { create_list(:user, 10) } + let!(:viewing_parties) { create_list(:viewing_party, 3) } + let!(:user_viewing_parties) { create_list(:user_viewing_party, 20) } + let(:user) { users.first } + let(:party) { user.viewing_parties.first } + + it 'returns the user viewing party for a given user and viewing party' do + expect(user.find_user_viewing_party(party)).to be_a(UserViewingParty) + expect(user.find_user_viewing_party(party).user_id).to eq(user.id) + expect(user.find_user_viewing_party(party).viewing_party_id).to eq(party.id) + end + + it 'returns all users apart from a specific user' do + expect(user.all_but_self.first).to be_a(User) + expect(user.all_but_self.count).to eq 9 + expect(user.all_but_self.filter_map { |other_user| other_user if other_user == user }).to eq([]) + end + end + end +end diff --git a/spec/models/user_viewing_party_spec.rb b/spec/models/user_viewing_party_spec.rb new file mode 100644 index 0000000000..13656d8be4 --- /dev/null +++ b/spec/models/user_viewing_party_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +RSpec.describe UserViewingParty, type: :model do + describe 'Associations' do + it { should belong_to(:viewing_party) } + it { should belong_to(:user) } + end + + describe 'Validations' do + it { should validate_inclusion_of(:hosting).in?([true, false]) } + end +end diff --git a/spec/models/viewing_party_spec.rb b/spec/models/viewing_party_spec.rb new file mode 100644 index 0000000000..d39e98144e --- /dev/null +++ b/spec/models/viewing_party_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +RSpec.describe ViewingParty, type: :model do + describe 'Associations' do + it { should have_many(:user_viewing_parties) } + it { should have_many(:users).through(:user_viewing_parties) } + end + + describe 'Validations' do + it { should validate_numericality_of(:duration) } + it { should validate_presence_of(:movie_id) } + it { should validate_presence_of(:start_time) } + it { should validate_presence_of(:date) } + end + + describe 'Instance Methods' do + let!(:users) { create_list(:user, 9) } + let!(:user) { create(:user) } + before :each do + json_response = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response) + end + it 'returns the movie for a given viewing party' do + viewing_party = ViewingParty.create!({ duration: 175, movie_id: 238, start_time: Time.now, date: Date.today }) + + expect(viewing_party.movie).to be_a(Movie) + expect(viewing_party.movie.title).to eq('The Godfather') + end + + it 'returns the host for a given viewing party' do + viewing_party = ViewingParty.create!({ duration: 175, movie_id: 238, start_time: Time.now, date: Date.today }) + user_viewing_party = viewing_party.user_viewing_parties.create!({ user_id: user.id, hosting: true }) + expect(viewing_party.host).to eq(user.name) + end + + it 'returns the invitees for a given viewing party' do + viewing_party = ViewingParty.create!({ duration: 175, movie_id: 238, start_time: Time.now, date: Date.today }) + user_viewing_party = viewing_party.user_viewing_parties.create!({ user_id: user.id, hosting: true }) + users.each_with_index do |invitee, i| + viewing_party.user_viewing_parties.create!({ user_id: invitee.id }) unless i == 4 + end + expect(viewing_party.invitees).to eq(users.map(&:name) - [users[4].name]) + end + end +end diff --git a/spec/poros/character_spec.rb b/spec/poros/character_spec.rb new file mode 100644 index 0000000000..0053df5865 --- /dev/null +++ b/spec/poros/character_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +RSpec.describe Character do + it 'exist with attributes' do + character = Character.new({ name: 'billy bob', character: 'joey' }) + + expect(character).to be_a(Character) + expect(character.actor).to eq('billy bob') + expect(character.character).to eq('joey') + end +end diff --git a/spec/poros/movie_spec.rb b/spec/poros/movie_spec.rb new file mode 100644 index 0000000000..7a3fdf9ecb --- /dev/null +++ b/spec/poros/movie_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +RSpec.describe Movie do + it 'exist with attributes' do + movie = Movie.new({ id: 1, title: 'Avatar', vote_average: 10, runtime: 180, overview: 'this is the overview', hours_mins: '3 hr 0 min', genres: [ + { + "id": 18, + "name": 'Drama' + }, + { + "id": 80, + "name": 'Crime' + } + ], image: '/9Baumh5J9N1nJUYzNkm0xsgjpwY.jpg' }) + + expect(movie).to be_a(Movie) + expect(movie.id).to eq(1) + expect(movie.vote_average).to eq(10) + expect(movie.runtime).to eq(180) + expect(movie.overview).to eq('this is the overview') + expect(movie.hours_mins).to eq('3hr 0min') + expect(movie.genres).to eq(%w[Drama Crime]) + end + + it '#convert_runtime' do + movie = Movie.new({ id: 1, title: 'Avatar', vote_average: 10, runtime: 175, overview: 'this is the overview', hours_mins: '3 hr 0 min', genres: [ + { + "id": 18, + "name": 'Drama' + }, + { + "id": 80, + "name": 'Crime' + } + ], image: '/9Baumh5J9N1nJUYzNkm0xsgjpwY.jpg' }) + expect(movie.convert_runtime(175)).to eq('2hr 55min') + end + + it '#convert_genres' do + movie = Movie.new({ id: 1, title: 'Avatar', vote_average: 10, runtime: 175, overview: 'this is the overview', hours_mins: '3 hr 0 min', genres: [ + { + "id": 18, + "name": 'Drama' + }, + { + "id": 80, + "name": 'Crime' + } + ], image: '/9Baumh5J9N1nJUYzNkm0xsgjpwY.jpg' }) + expect(movie.convert_genres([ + { + "id": 18, + "name": 'Drama' + }, + { + "id": 80, + "name": 'Crime' + } + ])).to eq(%w[Drama Crime]) + end +end diff --git a/spec/poros/review_spec.rb b/spec/poros/review_spec.rb new file mode 100644 index 0000000000..cdee4334f5 --- /dev/null +++ b/spec/poros/review_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe Review do + it 'exist with attributes' do + review = Review.new({ author: 'billy bob', content: 'this is the content' }) + + expect(review).to be_a(Review) + expect(review.author).to eq('billy bob') + expect(review.content).to eq('this is the content') + end + + it '#content_strip' do + review = Review.new({ author: 'billy bob', content: 'this is the content' }) + + expect(review.content_strip('this’ is’ the’ content’')).to eq("this' is' the' content'") + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 32d86a678c..ffd4de7502 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -3,9 +3,17 @@ ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? +abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! +require 'database_cleaner' + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are @@ -39,6 +47,50 @@ # instead of true. config.use_transactional_fixtures = true + config.include FactoryBot::Syntax::Methods + + config.before(:suite) do + # json_response_1 = File.read('spec/fixtures/the_godfather.json') + # stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US"). + # to_return(status: 200, body: json_response_1) + # json_response_2 = File.read('spec/fixtures/shawshank_redemption.json') + # stub_request(:get, "https://api.themoviedb.org/3/movie/278?api_key=#{ENV['movie_api_key']}&language=en-US"). + # to_return(status: 200, body: json_response_2) + # json_response_3 = File.read('spec/fixtures/puss_in_boots.json') + # stub_request(:get, "https://api.themoviedb.org/3/movie/315162?api_key=#{ENV['movie_api_key']}&language=en-US"). + # to_return(status: 200, body: json_response_3) + # json_response_4 = File.read('spec/fixtures/the_godfather_credits.json') + # stub_request(:get, "https://api.themoviedb.org/3/movie/238/credits?api_key=#{ENV['movie_api_key']}&language=en-US"). + # to_return(status: 200, body: json_response_4) + # json_response_5 = File.read('spec/fixtures/the_godfather_reviews.json') + # stub_request(:get, "https://api.themoviedb.org/3/movie/238/reviews?api_key=#{ENV['movie_api_key']}&language=en-US"). + # to_return(status: 200, body: json_response_5) + # json_response_6 = File.read('spec/fixtures/top_rated_movies.json') + # stub_request(:get, "https://api.themoviedb.org/3/discover/movie?api_key=#{ENV['movie_api_key']}&language=en-US&sort_by=vote_average.desc&include_adult=false&include_video=false&page=1&vote_count.gte=1000"). + # to_return(status: 200, body: json_response_6) + # json_response_7 = File.read('spec/fixtures/movies_with_green.json') + # stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Green&page=1&include_adult=false"). + # to_return(status: 200, body: json_response_7) + # json_response_8 = File.read('spec/fixtures/the_grand_budapest_hotel.json') + # stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=the%20grand%20budapest%20hotel&page=1&include_adult=false"). + # to_return(status: 200, body: json_response_8) + # json_response_9 = File.read('spec/fixtures/movies_with_spider_man.json') + # stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=Spider%20Man&page=1&include_adult=false") + # to_return(status: 200, body: json_response_9) + # json_response_10 = File.read('spec/fixtures/failed_search.json') + # stub_request(:get, "https://api.themoviedb.org/3/search/movie?api_key=#{ENV['movie_api_key']}&language=en-US&query=&page=1&include_adult=false"). + # to_return(status: 200, body: json_response_10) + + DatabaseCleaner.clean_with(:truncation) + DatabaseCleaner.strategy = :transaction + end + + config.around(:each) do |example| + DatabaseCleaner.cleaning do + example.run + end + end + # You can uncomment this line to turn off ActiveRecord support entirely. # config.use_active_record = false @@ -62,10 +114,3 @@ # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end - -Shoulda::Matchers.configure do |config| - config.integrate do |with| - with.test_framework :rspec - with.library :rails - end -end \ No newline at end of file diff --git a/spec/services/movie_service_spec.rb b/spec/services/movie_service_spec.rb new file mode 100644 index 0000000000..8de88e55bc --- /dev/null +++ b/spec/services/movie_service_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe MovieService do + before :each do + json_response_1 = File.read('spec/fixtures/the_godfather.json') + stub_request(:get, "https://api.themoviedb.org/3/movie/238?api_key=#{ENV['movie_api_key']}&language=en-US") + .to_return(status: 200, body: json_response_1) + end + it '#connection' do + expect(MovieService.connection).to be_a(Faraday::Connection) + end + it '#parse' do + expect(MovieService.parse_response("movie/238?api_key=#{ENV['movie_api_key']}&language=en-US")).to be_a(Hash) + end + it '#response' do + expect(MovieService.response("movie/238?api_key=#{ENV['movie_api_key']}&language=en-US")).to be_a(Faraday::Response) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1d8f63c286..6e9495bd15 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +require 'webmock/rspec' # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause @@ -14,7 +15,7 @@ # require 'simplecov' SimpleCov.start 'rails' -SimpleCov.add_filter ['spec', 'config'] +SimpleCov.add_filter ['spec', 'config', 'app/channels', 'app/jobs', 'app/mailers'] # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate @@ -47,53 +48,51 @@ # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ - # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode - config.disable_monkey_patching! - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed end diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..413d6113c4 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,18 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +bootstrap@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.3.tgz#54739f4414de121b9785c5da3c87b37ff008322b" + integrity sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ== + +jquery@^3.6.3: + version "3.6.3" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6" + integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg== + +popper.js@^1.16.1: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==