diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index be1db9d..b60c3b1 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -19,7 +19,7 @@ jobs: gem install --no-document rubocop -v '>= 1.0, < 2.0' # https://docs.rubocop.org/en/stable/installation/ [ -f .rubocop.yml ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/ror/.rubocop.yml - name: Rubocop Report - run: rubocop --color + run: rubocop --color -A nodechecker: name: node_modules checker runs-on: ubuntu-22.04 diff --git a/Gemfile b/Gemfile index a4bc99a..b68eaff 100644 --- a/Gemfile +++ b/Gemfile @@ -54,6 +54,10 @@ group :development do # gem "spring" end +group :test do + gem 'shoulda-matchers' +end + gem 'devise' gem 'devise-jwt' gem 'jsonapi-serializer' diff --git a/Gemfile.lock b/Gemfile.lock index 91f9caf..a0d080f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,6 +276,8 @@ GEM parser (>= 3.2.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + shoulda-matchers (6.1.0) + activesupport (>= 5.2.0) stringio (3.1.0) thor (1.3.0) timeout (0.4.1) @@ -320,6 +322,7 @@ DEPENDENCIES rswag-specs rswag-ui rubocop + shoulda-matchers tzinfo-data RUBY VERSION diff --git a/app/controllers/api/v1/cars_controller.rb b/app/controllers/api/v1/cars_controller.rb new file mode 100644 index 0000000..92785a2 --- /dev/null +++ b/app/controllers/api/v1/cars_controller.rb @@ -0,0 +1,82 @@ +class API::V1::CarsController < ApplicationController + before_action :set_car, only: %i[show update destroy] + + def show + render json: { + status: { + code: 200, + message: 'Car fetched successfully' + }, + data: CarWithDetailsSerializer.new(@car).serializable_hash[:data][:attributes] + }, + status: :ok + end + + def create + @car = Car.new(car_params) + + if @car.save + render json: { + status: { + code: 200, + message: 'Car successfully created' + }, + data: CarWithDetailsSerializer.new(@car).serializable_hash[:data][:attributes] + }, + status: :created + else + render json: @car.errors, status: :unprocessable_entity + end + end + + def update + if @car.update(car_params) + render json: { + status: { + code: 200, + message: 'Car successfully updated' + }, + data: CarWithDetailsSerializer.new(@car).serializable_hash[:data][:attributes] + }, + status: :ok + else + render json: @car.errors, status: :unprocessable_entity + end + end + + def destroy + @car.destroy + render json: { + status: { + code: 200, + message: 'Car successfully deleted' + } + }, + status: :ok + end + + private + + def set_car + @car = Car.includes(:car_detail).find(params[:id]) + end + + def car_params + params.require(:car).permit( + :name, + :description, + car_detail_attributes: %i[ + engine_type_id + horsepower + torque + fuel_economy + seating_capacity + cargo_space + infotainment_system + safety_rating + tech_features + special_features + ] + ) + end +end diff --git a/app/serializers/car_with_details_serializer.rb b/app/serializers/car_with_details_serializer.rb new file mode 100644 index 0000000..7611605 --- /dev/null +++ b/app/serializers/car_with_details_serializer.rb @@ -0,0 +1,9 @@ +class CarWithDetailsSerializer + include JSONAPI::Serializer + + attributes :id, :name, :description + + attribute :car_detail do |car| + CarDetailSerializer.new(car.car_detail).serializable_hash[:data][:attributes] + end +end diff --git a/config/database.yml b/config/database.yml index 9a7d4a6..c43e48c 100644 --- a/config/database.yml +++ b/config/database.yml @@ -15,6 +15,8 @@ default: &default adapter: postgresql encoding: unicode + username: postgres + password: "BrunoyKino2019@" # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> diff --git a/config/routes.rb b/config/routes.rb index fba48f1..ce27b18 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,9 +3,11 @@ namespace :v1 do resources :cities resources :reservations + resources :cars, except: [:index] + resources :engine_type, except: [:index] end end - + mount Rswag::Ui::Engine => '/api-docs' mount Rswag::Api::Engine => '/api-docs' get '/current_user', to: 'users/current_user#index' diff --git a/db/seeds.rb b/db/seeds.rb index 77676d8..4fc5c20 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -35,9 +35,488 @@ cities.each { |city_name| City.find_or_create_by!(name: city_name) } # Create car models - car_names = ['Toyota Camry', 'Honda Accord', 'Ford Mustang', 'Chevrolet Malibu', 'Hyundai Sonata', 'Nissan Altima', 'Volkswagen Passat', 'Subaru Legacy', 'Kia Optima', 'Audi A4', 'BMW 3 Series', 'Mercedes-Benz C-Class', 'Lexus ES', 'Volvo S60', 'Infiniti Q50', 'Acura TLX', 'Cadillac CT5', 'Lincoln MKZ', 'Alfa Romeo Giulia', 'Jaguar XE'] car_names.each_with_index { |car_name, index| Car.find_or_create_by!(name: car_name, description: "Description #{index + 1}") } Reservation.create!(date: Date.tomorrow, car: Car.first, city: City.first, user: User.first) -Reservation.create!(date: Date.tomorrow, car:Car.last, city: City.last, user: User.last) \ No newline at end of file +Reservation.create!(date: Date.tomorrow, car:Car.last, city: City.last, user: User.last) +# Create engine types +engine_types = [ + "4-cylinder", + "V6", + "Hybrid", + "Turbocharged 4-cylinder", + "Electric" +] + +engine_types.each do |engine_type_name| + EngineType.find_or_create_by!(name: engine_type_name) +end + +# Create car models + +car = [ + { + name: 'Toyota Camry', + description: + 'Reliable and spacious sedan with comfortable seating for five. Great fuel efficiency and smooth ride for commuting or road trips.', + car_details: { + engine_type_id: 1, + horsepower: 203, + torque: 184, + fuel_economy: '32 mpg', + seating_capacity: 5, + cargo_space: '15.1 cu ft', + infotainment_system: '7-inch touchscreen, Apple CarPlay/Android Auto', + safety_rating: '5-star NHTSA', + tech_features: 'Bluetooth, Backup Camera, Adaptive Cruise Control', + special_features: 'LED Headlights, Available panoramic sunroof' + } + }, + { + name: 'Honda Accord', + description: + 'Sporty yet sensible, the Accord offers enjoyable handling, ample passenger room, and excellent gas mileage.', + car_details: { + engine_type_id: 4, + horsepower: 192, + torque: 192, + fuel_economy: '33 mpg', + seating_capacity: 5, + cargo_space: '16.7 cu ft', + infotainment_system: '8-inch touchscreen, Apple CarPlay/Android Auto', + safety_rating: '5-star NHTSA', + tech_features: 'Bluetooth, Backup Camera, Honda Sensing Safety Suite', + special_features: 'Available Head-up Display, Ventilated Front Seats' + } + }, + { + name: 'Chevrolet Malibu', + description: + 'Stylish and affordable mid-size sedan. Quiet ride, modern infotainment system, and fuel-efficient option.', + car_details: { + engine_type_id: 4, + horsepower: 160, + torque: 184, + fuel_economy: '32 mpg', + seating_capacity: 5, + cargo_space: '15.7 cu ft', + infotainment_system: 'Chevrolet Infotainment 3 with 8-inch touchscreen', + safety_rating: '5-star NHTSA', + tech_features: 'Bluetooth, Backup Camera, Teen Driver Technology', + special_features: 'Available Wi-Fi Hotspot, Wireless Charging' + } + }, + { + name: 'Hyundai Sonata', + description: + 'Value-packed sedan with eye-catching design, intuitive technology, and a generous warranty.', + car_details: { + engine_type_id: 1, + horsepower: 191, + torque: 181, + fuel_economy: '32 mpg', + seating_capacity: 5, + cargo_space: '16 cu ft', + infotainment_system: '8-inch touchscreen, Hyundai Bluelink Connectivity', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Forward Collision Avoidance, Smart Cruise Control, Lane Keep Assist', + special_features: + 'Hyundai Digital Key, Panoramic Sunroof, 10-year/100,000-mile Warranty' + } + }, + { + name: 'Nissan Altima', + description: + 'Comfortable and practical. Ideal for everyday driving with responsive handling and a roomy interior.', + car_details: { + engine_type_id: 1, + horsepower: 188, + torque: 180, + fuel_economy: '31 mpg', + seating_capacity: 5, + cargo_space: '15.4 cu ft', + infotainment_system: 'NissanConnect with 8-inch touchscreen', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Automatic Emergency Braking, Intelligent Forward Collision Warning', + special_features: 'Zero Gravity Seats (comfort focused), Available AWD' + } + }, + { + name: 'Volkswagen Passat', + description: + 'European-inspired handling in a spacious, affordable package. A refined choice for longer rides.', + car_details: { + engine_type_id: 4, + horsepower: 174, + torque: 206, + fuel_economy: '28 mpg', + seating_capacity: 5, + cargo_space: '15.9 cu ft', + infotainment_system: + 'Composition Media touchscreen, App-Connect smartphone integration ', + safety_rating: '5-star NHTSA (Check for recent model year)', + tech_features: 'Automatic Post-Collision Braking, Blind Spot Monitoring', + special_features: 'Spacious Rear Legroom, Quiet and Comfortable Ride' + } + }, + { + name: 'Subaru Legacy', + description: + 'The only standard all-wheel-drive sedan in its class. Perfect for all-weather adventures and enhanced traction.', + car_details: { + engine_type_id: 1, + horsepower: 182, + torque: 176, + fuel_economy: '30 mpg', + seating_capacity: 5, + cargo_space: '15.1 cu ft', + infotainment_system: 'STARLINK Multimedia with 7-inch touchscreen', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'EyeSight Driver Assist Technology, Adaptive Cruise Control', + special_features: + 'Symmetrical All-Wheel Drive, X-Mode (enhanced off-road traction)' + } + }, + { + name: 'Kia Optima', + description: + 'Sharp looks, comfortable cabin, and impressive technology features make the Optima a smart and stylish choice.', + car_details: { + engine_type_id: 1, + horsepower: 185, + torque: 178, + fuel_economy: '31 mpg', + seating_capacity: 5, + cargo_space: '15.9 cu ft', + infotainment_system: 'UVO infotainment with 8-inch touchscreen', + safety_rating: 'Top Safety Pick (IIHS - verify model year)', + tech_features: + 'Forward Collision Warning, Blind Spot Detection, Kia Drive Wise Features', + special_features: + 'Sporty exterior styling, Available Panoramic Sunroof, Harman Kardon Sound System' + } + }, + { + name: 'Audi A4', + description: + "Upscale interior, responsive handling, and Audi's legendary Quattro all-wheel drive for sporty confidence.", + car_details: { + engine_type_id: 4, + horsepower: 261, + torque: 273, + fuel_economy: '28 mpg', + seating_capacity: 5, + cargo_space: '12 cu ft', + infotainment_system: '10.1-inch MMI Touch Display, Virtual Cockpit', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Pre-Sense Automatic Braking, Lane Departure Warning, Wireless Charging', + special_features: + 'Bang & Olufsen Sound System, Panoramic Sunroof, Leather Upholstery' + } + }, + { + name: 'BMW 3 Series', + description: + 'The benchmark for sports sedans. Athletic handling, powerful engines, and a high-end cabin.', + car_details: { + engine_type_id: 4, + horsepower: 255, + torque: 295, + fuel_economy: '29 mpg', + seating_capacity: 5, + cargo_space: '13 cu ft', + infotainment_system: 'iDrive 7.0 with touchscreen and gesture control', + safety_rating: 'Top Safety Pick+ (IIHS) or similar', + tech_features: + 'Driving Assistant Professional, Active Blind Spot Detection, Parking Assistant', + special_features: + 'M Sport Package (handling upgrades), Premium Leather Upholstery, Advanced Driving Dynamics' + } + }, + { + name: 'Mercedes-Benz C-Class', + description: + 'Epitome of luxury. Comfortable ride, sophisticated interior, and a brand renowned for prestige.', + car_details: { + engine_type_id: 4, + horsepower: 255, + torque: 295, + fuel_economy: '27 mpg', + seating_capacity: 5, + cargo_space: '12.6 cu ft', + infotainment_system: 'MBUX infotainment system with voice control', + safety_rating: 'Top Safety Pick+ (IIHS) or similar', + tech_features: + 'Active Brake Assist, Pre-Safe (anticipatory safety systems), Attention Assist (drowsiness detection)', + special_features: + 'Burmester Surround Sound System, Ambient Interior Lighting, Optional Air Suspension' + } + }, + { + name: 'Lexus ES', + description: + 'Prioritizes comfort and refinement. Whisper-quiet cabin, plush seats, and renowned Lexus reliability.', + car_details: { + engine_type_id: 2, + horsepower: 302, + torque: 267, + fuel_economy: '26 mpg', + seating_capacity: 5, + cargo_space: '16.7 cu ft', + infotainment_system: + 'Lexus Enform system with touchscreen and voice control', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Lexus Safety System+ 2.5 (pre-collision warning, lane departure alert, etc.), Adaptive Cruise Control', + special_features: + 'Mark Levinson Surround Sound System, Heated and Ventilated Seats, Heads-Up Display (optional)' + } + }, + { + name: 'Volvo S60', + description: + 'Scandinavian design with excellent safety ratings. Understated elegance and innovative technology.', + car_details: { + engine_type_id: 4, + horsepower: 247, + torque: 258, + fuel_economy: '28 mpg', + seating_capacity: 5, + cargo_space: '11.6 cu ft', + infotainment_system: 'Sensus Connect with 9-inch vertical touchscreen', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'City Safety (Collision Mitigation), Pilot Assist (Semi-Autonomous Driving), Road Sign Detection', + special_features: + 'CleanZone Interior Air Quality System, Bowers & Wilkins Sound, Scandinavian Design Elements' + } + }, + { + name: 'Infiniti Q50', + description: + 'Powerful engine choices and driver-oriented feel. Offers sporty performance in a luxurious package.', + car_details: { + engine_type_id: 2, + horsepower: 300, + torque: 295, + fuel_economy: '23 mpg', + seating_capacity: 5, + cargo_space: '13.5 cu ft', + infotainment_system: 'Infiniti InTouch dual-screen system', + safety_rating: 'Top Safety Pick (IIHS) - check for the newest model year', + tech_features: + 'Predictive Forward Collision Warning, ProPILOT Assist (adaptive cruise)', + special_features: + 'Direct Adaptive Steering (unique option), Sport Seats, Available AWD' + } + }, + { + name: 'Acura TLX', + description: + "Combines a sporty character with Acura's reputation for reliability and a well-appointed interior.", + car_details: { + engine_type_id: 4, + horsepower: 272, + torque: 280, + fuel_economy: '25 mpg', + seating_capacity: 5, + cargo_space: '13.5 cu ft', + infotainment_system: 'True Touchpad Interface with 10.2-inch screen', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'AcuraWatch Safety Suite, Adaptive Cruise Control, Traffic Jam Assist', + special_features: + "ELS Studio 3D Premium Audio, Sport Seats, Available 'Type S' Performance Variant" + } + }, + { + name: 'Genesis G80', + description: + 'Luxury sedan with a strong value proposition. Spacious cabin, refined ride, and a long list of standard features.', + car_details: { + engine_type_id: 4, + horsepower: 300, + torque: 311, + fuel_economy: '26 mpg', + seating_capacity: 5, + cargo_space: '13.1 cu ft', + infotainment_system: '14.5-inch touchscreen, Genesis Connected Services', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Highway Driving Assist (adaptive cruise + lane centering), Forward Collision Avoidance', + special_features: + 'Lexicon Premium Audio, Nappa Leather Seating, 10-year/100,000-mile Powertrain Warranty' + } + }, + { + name: 'Tesla Model 3', + description: + 'Electric car with impressive range and rapid acceleration. Cutting-edge technology and minimalist design.', + car_details: { + engine_type_id: 5, + horsepower: 283, + torque: 325, + seating_capacity: 5, + cargo_space: '15 cu ft + front trunk', + infotainment_system: + '15-inch central touchscreen (controls most functions)', + safety_rating: '5-star NHTSA, Top Safety Pick+ (IIHS)', + tech_features: + 'Autopilot (adaptive cruise + lane control), Over-the-Air Updates, Sentry Mode (security system)', + special_features: + 'Supercharger Network Access, Full Self-Driving Capability (optional), Unique Design Features' + } + }, + { + name: 'Porsche Taycan', + description: + "Electric sports car with breathtaking performance and a luxurious interior. A true driver's car.", + car_details: { + engine_type_id: 5, + horsepower: 402, + torque: 469, + seating_capacity: 4, + cargo_space: '14.3 cu ft (split between front & rear)', + infotainment_system: + 'Porsche Communication Management (PCM), Curved Digital Display', + safety_rating: + 'Not yet rated (NHTSA/IIHS) likely due to low sales volume', + tech_features: + 'Adaptive Cruise Control, Porsche Dynamic Chassis Control (PDCC - adaptive suspension)', + special_features: + '800-volt Battery Architecture (fast charging), Overboost and Launch Control, Porsche Recuperation Management (energy recovery)' + } + }, + { + name: 'Jaguar XE', + description: + 'Nimble and luxurious compact sport sedan known for its engaging performance and British heritage.', + car_details: { + engine_type_id: 4, + horsepower: 247, + torque: 269, + fuel_economy: '26 mpg', + seating_capacity: 5, + cargo_space: '14.7 cu ft', + infotainment_system: 'Jaguar InControl Touch Pro Duo', + safety_rating: '5-star Euro NCAP (adjust if NHTSA/IIHS available)', + tech_features: + 'Adaptive Dynamics (adjustable suspension), Configurable Dynamics (drivetrain customization)', + special_features: + 'JaguarDrive Control (sport modes), Meridian Sound System (Optional), Wood Veneer trim' + } + }, + { + name: 'Maserati Ghibli', + description: + 'Italian luxury and performance. A stylish and exclusive choice for those who appreciate the finer things.', + car_details: { + engine_type_id: 2, + horsepower: 345, + torque: 369, + fuel_economy: '19 mpg', + seating_capacity: 5, + cargo_space: '17.7 cu ft', + infotainment_system: + 'Maserati Touch Control Plus (MTC+) with 10.1-inch screen', + safety_rating: 'Not fully rated (likely due to low volume)', + tech_features: + 'Highway Assist System (adaptive cruise + lane keep), Skyhook Performance Suspension', + special_features: + 'Ferrari-Built Engine, Pieno Fiore Italian Leather, Choice of Zegra Silk or Wood Trim, Unique Exhaust note' + } + }, + { + name: 'Cadillac CT5', + description: + 'American luxury with bold styling and modern technology. Spacious interior and smooth driving dynamics.', + car_details: { + engine_type_id: 4, + horsepower: 237, + torque: 258, + fuel_economy: '27 mpg', + seating_capacity: 5, + cargo_space: '11.9 cu ft', + infotainment_system: + 'Cadillac User Experience (CUE) with 10-inch touchscreen', + safety_rating: 'Top Safety Pick (IIHS) (may vary per year)', + tech_features: + 'Super Cruise (hands-free highway driving), Magnetic Ride Control (adaptive suspension)', + special_features: + 'Available Bose Sound System, Head-Up Display, Luxurious Interior Material Choices' + } + }, + { + name: 'Lincoln MKZ', + description: + 'Comfort-focused luxury sedan with an elegant interior, quiet ride, and a focus on relaxation.', + car_details: { + engine_type_id: 2, + horsepower: 350, + torque: 400, + fuel_economy: '20 mpg', + seating_capacity: 5, + cargo_space: '15.4 cu ft', + infotainment_system: + 'SYNC 3 with touchscreen, Lincoln Connect (remote features)', + safety_rating: + 'Top Safety Pick+ (IIHS) - check for most recent model year', + tech_features: + 'Co-Pilot 360 (Adaptive Cruise, Lane Keeping, etc.), Active Noise Control', + special_features: + 'Revel Premium Audio System, Massage Seats (Available), Lincoln Embrace (greeting sequence)' + } + }, + { + name: 'Alfa Romeo Giulia', + description: + 'Italian flair and passionate driving experience. Athletic handling and striking design set it apart.', + car_details: { + engine_type_id: 4, + horsepower: 280, + torque: 306, + fuel_economy: '27 mpg', + seating_capacity: 5, + cargo_space: '12 cu ft', + infotainment_system: + '8.8-inch touchscreen with rotary controller, Alfa Connect', + safety_rating: 'Top Safety Pick+ (IIHS)', + tech_features: + 'Alfa DNA Drive Mode Selector (adjusts throttle, steering, etc.), Limited-Slip Differential', + special_features: + 'Italian Design and Craftsmanship, Available Quadrifoglio Performance Trim, Sport-Tuned Suspension' + } + }, + { + name: 'Ford Mustang', + description: + 'Iconic American muscle car with powerful engine options and bold styling.', + car_details: { + engine_type_id: 1, + horsepower: 310, + torque: 350, + fuel_economy: '25 mpg', + seating_capacity: 4, + cargo_space: '13.5 cu ft', + infotainment_system: 'SYNC 3 infotainment, Optional large touchscreen', + safety_rating: '5-star NHTSA', + tech_features: 'Rear View Camera, Track Apps (performance metrics)', + special_features: + 'Available V8 Engine, Recaro Seats, MagneRide Suspension' + } + } +] + +car.each do |car| + new_car = Car.find_or_initialize_by(name: car[:name]) + if new_car.new_record? + new_car.description = car[:description] + new_car.build_car_detail(car[:car_details]) + new_car.save + end +end diff --git a/spec/factories/car_details.rb b/spec/factories/car_details.rb index 96f267c..a17edb0 100644 --- a/spec/factories/car_details.rb +++ b/spec/factories/car_details.rb @@ -1,7 +1,6 @@ FactoryBot.define do factory :car_detail do car { nil } - engine_type { nil } horsepower { 1 } torque { 1 } fuel_economy { 'MyString' } @@ -11,5 +10,7 @@ safety_rating { 'MyString' } tech_features { 'MyString' } special_features { 'MyString' } + + association :engine_type end end diff --git a/spec/factories/cars.rb b/spec/factories/cars.rb index b0486bd..a7f76fc 100644 --- a/spec/factories/cars.rb +++ b/spec/factories/cars.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :car do name { 'MyString' } - description { 'MyText' } + description { 'MyText with length of more than 10 characters' } end end diff --git a/spec/factories/reservations.rb b/spec/factories/reservations.rb index e93357b..51ddb6f 100644 --- a/spec/factories/reservations.rb +++ b/spec/factories/reservations.rb @@ -1,8 +1,9 @@ FactoryBot.define do factory :reservation do - date { '2024-02-15' } - city { nil } + date { Date.today } car { nil } user { nil } + + association :city end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..bfec392 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,13 @@ +FactoryBot.define do + factory :user do + sequence(:username) { |n| "username#{n}" } + name { 'John Doe' } + email { 'john@example.com' } + password { 'Password123' } + role { :user } + + trait :admin do + role { :admin } + end + end +end diff --git a/spec/models/car_detail_spec.rb b/spec/models/car_detail_spec.rb index 89b64b5..d4fe746 100644 --- a/spec/models/car_detail_spec.rb +++ b/spec/models/car_detail_spec.rb @@ -1,5 +1,30 @@ require 'rails_helper' RSpec.describe CarDetail, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:car) { FactoryBot.create(:car) } + let(:engine_type) { FactoryBot.create(:engine_type) } + + let(:car_detail) { FactoryBot.create(:car_detail, car:, engine_type:) } + + it 'Creates a reservation with valid attributes' do + expect(car_detail).to be_valid + end + + it 'is invalid withinvalid attributes' do + car_detail = FactoryBot.build( + :car_detail, + horsepower: nil, + car: nil, + torque: nil, + fuel_economy: nil, + seating_capacity: nil, + cargo_space: nil, + infotainment_system: nil, + safety_rating: nil, + tech_features: nil, + special_features: nil + ) + + expect(car_detail).to_not be_valid + end end diff --git a/spec/models/car_spec.rb b/spec/models/car_spec.rb index 0f2a3ac..6d6d8f7 100644 --- a/spec/models/car_spec.rb +++ b/spec/models/car_spec.rb @@ -1,5 +1,52 @@ require 'rails_helper' +require 'shoulda/matchers' RSpec.describe Car, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:car) { FactoryBot.create(:car) } + + it 'is valid with a name' do + expect(car).to be_valid + end + + it 'is invalid without a name' do + car = FactoryBot.build(:car, name: nil) + + expect(car).to_not be_valid + end + + it 'is invalid when the name is too short' do + car = FactoryBot.build(:car, name: 'V') + + expect(car).to_not be_valid + end + + it 'is invalid when the name is too long' do + car = FactoryBot.build(:car, name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sit amet + massa vel nulla eleifend semper sed vel est. Phasellus nulla metus, molestie vel condimentum sit amet, commodo sed + libero. Sed massa tellus, auctor sed varius vitae, egestas nec felis. Curabitur sit amet laoreet turpis, non + sagittis neque. Proin venenatis ipsum arcu, non aliquam risus blandit eget. Integer sed facilisis nibh. + Pellentesque eleifend dignissim leo, condimentum interdum metus molestie nec. Phasellus ut enim faucibus, + facilisis orci in, porttitor ex. Nunc mollis felis odio, sed sollicitudin metus iaculis eget. Nunc cursus, leo + vitae ultricies commodo, nunc elit porta ligula, in fermentum magna purus et arcu. Vivamus augue eros, elementum + egestas efficitur vel, feugiat quis mi. Ut sit amet venenatis ligula, id dignissim odio. Duis at magna ultricies, + vulputate sem non, ullamcorper arcu. Lorem ipsum dolor sit amet, consectetur adipiscing elit.") + + expect(car).to_not be_valid + end + + it 'can have many reservations' do + engine_type = FactoryBot.create(:engine_type) + FactoryBot.create(:car_detail, engine_type:, car:) + user = FactoryBot.create(:user) + city = FactoryBot.create(:city) + + reservation1 = FactoryBot.create(:reservation, car:, city:, user:) + reservation2 = FactoryBot.create(:reservation, car:, city:, user:) + + expect(car.reservations).to include(reservation1, reservation2) + end + + it 'accepts nested attributes for car_detail' do + should accept_nested_attributes_for(:car_detail) + end end diff --git a/spec/models/city_spec.rb b/spec/models/city_spec.rb index 17ead56..46cb31d 100644 --- a/spec/models/city_spec.rb +++ b/spec/models/city_spec.rb @@ -1,5 +1,49 @@ require 'rails_helper' RSpec.describe City, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:city) { FactoryBot.create(:city) } + + it 'Creates a city with valid attributes' do + expect(city).to be_valid + end + + describe 'fails to create a city with invalid attributes' do + it 'Name attribute is not present' do + city = FactoryBot.build(:city, name: nil) + + expect(city).to_not be_valid + end + + it 'name attribute is too long' do + city = FactoryBot.build(:city, name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut sed sem lectus. + Ut non fermentum velit. Nunc facilisis diam id ante auctor rutrum. Proin mauris ipsum, scelerisque et convallis + ac, molestie quis turpis. Vivamus rhoncus ex non arcu mattis, non sollicitudin est pellentesque. Curabitur neque + risus, dignissim placerat enim eget, facilisis efficitur nisl. In dictum, diam ullamcorper hendrerit malesuada, + augue magna cursus sapien, dignissim interdum elit ante non dui. Suspendisse tempus orci vel orci imperdiet, + eget rutrum lacus cursus. In dictum facilisis mauris, id euismod neque tincidunt aliquet. Aenean consectetur + lacus eget efficitur sagittis. Phasellus a sem eu leo viverra pharetra vitae in metus. Fusce suscipit scelerisque + faucibus. Proin et ex porta, suscipit erat et, semper enim. In lobortis egestas nulla dictum aliquet. Sed eget + consectetur nibh. Maecenas egestas et quam scelerisque consequat.") + + expect(city).to_not be_valid + end + + it 'name attribute is too short' do + city = FactoryBot.build(:city, name: '') + + expect(city).to_not be_valid + end + end + + it 'It can have many reservation records' do + engine_type = FactoryBot.create(:engine_type) + car = FactoryBot.create(:car) + FactoryBot.create(:car_detail, engine_type:, car:) + user = FactoryBot.create(:user) + + reservation1 = FactoryBot.create(:reservation, car:, city:, user:) + reservation2 = FactoryBot.create(:reservation, car:, city:, user:) + + expect(city.reservations).to include(reservation1, reservation2) + end end diff --git a/spec/models/engine_type_spec.rb b/spec/models/engine_type_spec.rb index 9cec12d..30bfcb9 100644 --- a/spec/models/engine_type_spec.rb +++ b/spec/models/engine_type_spec.rb @@ -1,5 +1,26 @@ require 'rails_helper' RSpec.describe EngineType, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:engine_type) { FactoryBot.create(:engine_type) } + + it 'is valid with a name' do + expect(engine_type.name).to eq('MyString') + expect(engine_type).to be_valid + end + + it 'is invalid without a name' do + engine_type = FactoryBot.build(:engine_type, name: nil) + + expect(engine_type.name).to eq(nil) + expect(engine_type).to_not be_valid + end + + it 'has can have many car details' do + engine_type = FactoryBot.create(:engine_type) + car = FactoryBot.create(:car) + car_detail1 = FactoryBot.create(:car_detail, car:, engine_type:) + car_detail2 = FactoryBot.create(:car_detail, car:, engine_type:) + + expect(engine_type.car_details).to include(car_detail1, car_detail2) + end end diff --git a/spec/models/reservation_spec.rb b/spec/models/reservation_spec.rb index 7f46ac2..38530fe 100644 --- a/spec/models/reservation_spec.rb +++ b/spec/models/reservation_spec.rb @@ -1,5 +1,38 @@ require 'rails_helper' RSpec.describe Reservation, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:city) { FactoryBot.create(:city) } + let(:car) { FactoryBot.create(:car) } + let(:user) { FactoryBot.create(:user) } + let(:reservation) { FactoryBot.create(:reservation, car:, city:, user:) } + + it 'Creates a reservation with valid attributes' do + expect(reservation).to be_valid + end + + describe 'with invalid attributes' do + it "Doesn't create a reservation with invalid car attribute" do + reservation = FactoryBot.build(:reservation, car: nil) + + expect(reservation).to_not be_valid + end + + it "Doesn't create a reservation with invalid city attribute" do + reservation = FactoryBot.build(:reservation, city: nil) + + expect(reservation).to_not be_valid + end + + it "Doesn't create a reservation with invalid user attribute" do + reservation = FactoryBot.build(:reservation, user: nil) + + expect(reservation).to_not be_valid + end + end + + describe 'associations' do + it { should belong_to(:city) } + it { should belong_to(:car) } + it { should belong_to(:user) } + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..e24fcde --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + describe 'validations' do + it { should validate_presence_of(:username) } + it { should validate_presence_of(:name) } + it { should validate_presence_of(:email) } + it { should validate_length_of(:password).is_at_least(8) } + it { should allow_value('username').for(:username) } + it { should_not allow_value('user name').for(:username) } + it { should_not allow_value('user@name').for(:username) } + it { should_not allow_value('user name!').for(:username) } + end + + describe 'associations' do + it { should have_many(:reservations).dependent(:destroy) } + end + + describe 'enums' do + it { should define_enum_for(:role).with_values(user: 0, admin: 1) } + end + + describe 'after_initialize' do + it 'sets the default role to user' do + user = User.new + expect(user.role).to eq('user') + end + end + + describe 'methods' do + describe '#admin?' do + it 'returns true if the user has admin role' do + admin_user = FactoryBot.create(:user, :admin) + expect(admin_user.admin?).to be_truthy + end + + it 'returns false if the user does not have admin role' do + user = FactoryBot.create(:user) + expect(user.admin?).to be_falsey + end + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index ba57c54..505f3f1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -30,6 +30,11 @@ abort e.to_s.strip end RSpec.configure do |config| + # configurations for Shoulda Matchers + config.include FactoryBot::Syntax::Methods + config.include Shoulda::Matchers::ActiveModel, type: :model + config.include Shoulda::Matchers::ActiveRecord, type: :model + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_paths = [ Rails.root.join('spec/fixtures') diff --git a/spec/requests/api/v1/cars_spec.rb b/spec/requests/api/v1/cars_spec.rb new file mode 100644 index 0000000..9506e50 --- /dev/null +++ b/spec/requests/api/v1/cars_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe 'API::V1::Cars', type: :request do + describe 'GET /index' do + pending "add some examples (or delete) #{__FILE__}" + end +end