require "rails_helper" RSpec.describe "Vehicles" do describe "unauthenticated access" do it "redirects vehicle pages to identification" do get vehicles_path expect(response).to redirect_to(identify_path) end it "does not create fuel entries" do vehicle = create(:vehicle, current_odometer: 10_000) expect do post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params } end.not_to change(FuelEntry, :count) expect(response).to redirect_to(identify_path) end it "does not create maintenance entries" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") expect do post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params } end.not_to change(MaintenanceEntry, :count) expect(response).to redirect_to(identify_path) end end describe "GET /" do before { identify_user } it "renders the active vehicle index" do active_vehicle = create(:vehicle, make: "Ford", model: "F-150", active: true) inactive_vehicle = create(:vehicle, make: "Chevrolet", model: "Silverado", active: false) get root_path expect(response).to have_http_status(:ok) expect(response.body).to include("Vehicles") expect(response.body).to include(active_vehicle.model) expect(response.body).not_to include(inactive_vehicle.model) end end describe "GET /vehicles" do before { identify_user } it "shows active vehicles by default" do active_vehicle = create(:vehicle, make: "Ford", model: "F-150", active: true) inactive_vehicle = create(:vehicle, make: "Chevrolet", model: "Silverado", active: false) get vehicles_path expect(response).to have_http_status(:ok) expect(response.body).to include(active_vehicle.model) expect(response.body).not_to include(inactive_vehicle.model) end it "shows inactive vehicles when requested" do active_vehicle = create(:vehicle, make: "Ford", model: "F-150", active: true) inactive_vehicle = create(:vehicle, make: "Chevrolet", model: "Silverado", active: false) get vehicles_path(status: "inactive") expect(response).to have_http_status(:ok) expect(response.body).not_to include(active_vehicle.model) expect(response.body).to include(inactive_vehicle.model) end it "shows all vehicles when requested" do active_vehicle = create(:vehicle, make: "Ford", model: "F-150", active: true) inactive_vehicle = create(:vehicle, make: "Chevrolet", model: "Silverado", active: false) get vehicles_path(status: "all") expect(response).to have_http_status(:ok) expect(response.body).to include(active_vehicle.model) expect(response.body).to include(inactive_vehicle.model) end end describe "GET /vehicles/:id" do before { identify_user } it "shows vehicle details and display-only history sections" do vehicle = create(:vehicle, make: "Ford", model: "F-150") older_fuel_entry = create(:fuel_entry, vehicle: vehicle, odometer: 11_000, date: Date.current - 2.days) newer_fuel_entry = create(:fuel_entry, vehicle: vehicle, odometer: 12_000, date: Date.current - 1.day) older_maintenance_entry = create( :maintenance_entry, vehicle: vehicle, name: "Oil Change", notes: "Older oil change", date: Date.current - 2.days ) newer_maintenance_entry = create( :maintenance_entry, vehicle: vehicle, name: "Oil Change", notes: "Newer oil change", date: Date.current - 1.day ) get vehicle_path(vehicle) expect(response).to have_http_status(:ok) expect(response.body).to include(vehicle.make) expect(response.body).to include(vehicle.model) expect(response.body).to include("Maintenance Schedules") expect(response.body).to include("Fuel History") expect(response.body).to include("Maintenance History") expect(response.body).to include(vehicle.maintenance_schedules.first.maintenance_type) expect(response.body).to include("12,000") expect(response.body).to include("11,000") expect(response.body).to include(newer_maintenance_entry.notes) expect(response.body).to include(older_maintenance_entry.notes) expect(response.body.index("12,000")).to be < response.body.index("11,000") expect(response.body.index(newer_maintenance_entry.notes)).to be < response.body.index(older_maintenance_entry.notes) end end describe "POST /vehicles/:vehicle_id/fuel_entries" do before { identify_user } it "creates a fuel entry and records the identified user" do vehicle = create(:vehicle, current_odometer: 10_000) expect do post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params } end.to change(FuelEntry, :count).by(1) expect(response).to redirect_to(vehicle_path(vehicle)) expect(FuelEntry.last.updated_by_user).to eq(@identified_user) end it "updates the vehicle odometer when the fuel odometer is newer" do vehicle = create(:vehicle, current_odometer: 10_000) post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params.merge(odometer: 10_500) } expect(vehicle.reload.current_odometer).to eq(10_500) end it "saves equal fuel odometer entries without changing the vehicle odometer" do vehicle = create(:vehicle, current_odometer: 10_000) expect do post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params.merge(odometer: 10_000) } end.to change(FuelEntry, :count).by(1) expect(vehicle.reload.current_odometer).to eq(10_000) end it "rejects fuel odometer entries below the current vehicle odometer" do vehicle = create(:vehicle, current_odometer: 10_000) expect do post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params.merge(odometer: 9_999) } end.not_to change(FuelEntry, :count) expect(response).to have_http_status(:unprocessable_content) expect(response.body).to include("prevented this fuel entry from being saved") expect(vehicle.reload.current_odometer).to eq(10_000) end it "renders validation errors for invalid fuel params" do vehicle = create(:vehicle, current_odometer: 10_000) expect do post vehicle_fuel_entries_path(vehicle), params: { fuel_entry: valid_fuel_entry_params.merge(gallons_pumped: 0) } end.not_to change(FuelEntry, :count) expect(response).to have_http_status(:unprocessable_content) expect(response.body).to include("prevented this fuel entry from being saved") end end describe "POST /vehicles/:vehicle_id/maintenance_entries" do before { identify_user } it "creates a maintenance entry and records the identified user" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") expect do post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params } end.to change(MaintenanceEntry, :count).by(1) expect(response).to redirect_to(vehicle_path(vehicle)) expect(MaintenanceEntry.last.updated_by_user).to eq(@identified_user) end it "updates the vehicle odometer when the maintenance odometer is newer" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params.merge(odometer: 10_500) } expect(vehicle.reload.current_odometer).to eq(10_500) end it "saves older maintenance odometers without lowering the vehicle odometer" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") expect do post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params.merge(odometer: 9_750) } end.to change(MaintenanceEntry, :count).by(1) expect(vehicle.reload.current_odometer).to eq(10_000) end it "warns and logs when maintenance odometer is more than 500 miles behind" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") allow(Rails.logger).to receive(:warn) post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params.merge(odometer: 9_499) } expect(response).to redirect_to(vehicle_path(vehicle)) expect(flash[:warning]).to include("more than 500 miles behind") expect(Rails.logger).to have_received(:warn).with(/vehicle_id=#{vehicle.id}.*entry_odometer=9499/) end it "rejects maintenance entries with an unmatched schedule name" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") expect do post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params.merge(name: "Tire Rotation") } end.not_to change(MaintenanceEntry, :count) expect(response).to have_http_status(:unprocessable_content) expect(response.body).to include("prevented this maintenance entry from being saved") end it "renders validation errors for invalid maintenance params" do vehicle = create(:vehicle, current_odometer: 10_000) create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change") expect do post vehicle_maintenance_entries_path(vehicle), params: { maintenance_entry: valid_maintenance_entry_params.merge(notes: "") } end.not_to change(MaintenanceEntry, :count) expect(response).to have_http_status(:unprocessable_content) expect(response.body).to include("prevented this maintenance entry from being saved") end end describe "GET /vehicles/new" do before { identify_user } it "renders the form" do get new_vehicle_path expect(response).to have_http_status(:ok) expect(response.body).to include("New Vehicle") end end describe "POST /vehicles" do before { identify_user } it "creates a vehicle with valid params" do expect do post vehicles_path, params: { vehicle: valid_vehicle_params } end.to change(Vehicle, :count).by(1) expect(response).to redirect_to(vehicle_path(Vehicle.last)) expect(Vehicle.last.vin).to eq("ABC123") end it "rejects invalid params" do expect do post vehicles_path, params: { vehicle: valid_vehicle_params.merge(make: "", vin: "") } end.not_to change(Vehicle, :count) expect(response).to have_http_status(:unprocessable_content) expect(response.body).to include("prevented this vehicle from being saved") end end describe "GET /vehicles/:id/edit" do before { identify_user } it "renders the form" do vehicle = create(:vehicle) get edit_vehicle_path(vehicle) expect(response).to have_http_status(:ok) expect(response.body).to include("Edit Vehicle") end end describe "PATCH /vehicles/:id" do before { identify_user } it "updates a vehicle with valid params" do vehicle = create(:vehicle, make: "Ford") patch vehicle_path(vehicle), params: { vehicle: valid_vehicle_params.merge(make: "Toyota") } expect(response).to redirect_to(vehicle_path(vehicle)) expect(vehicle.reload.make).to eq("Toyota") end it "rejects invalid params" do vehicle = create(:vehicle, make: "Ford") patch vehicle_path(vehicle), params: { vehicle: valid_vehicle_params.merge(make: "") } expect(response).to have_http_status(:unprocessable_content) expect(vehicle.reload.make).to eq("Ford") expect(response.body).to include("prevented this vehicle from being saved") end end describe "DELETE /vehicles/:id" do before { identify_user } it "deactivates the vehicle instead of deleting it" do vehicle = create(:vehicle, active: true) expect do delete vehicle_path(vehicle) end.not_to change(Vehicle, :count) expect(response).to redirect_to(vehicles_path) expect(vehicle.reload).not_to be_active end end def valid_vehicle_params { make: "Ford", model: "F-150", year: Date.current.year, color: "White", vin: "abc123", licence_plate: "FLT1234", current_odometer: 10_000, fuel_tank_size: 26.0, active: "1" } end def valid_fuel_entry_params { odometer: 10_250, gallons_pumped: 18.25, price_paid: 47.62, date: Date.current } end def valid_maintenance_entry_params { name: "Oil Change", notes: "Oil change performed by Fleet Service Center.", odometer: 10_250, cost: 89.95, date: Date.current } end def identify_user(user = create(:user)) @identified_user = user access_key = AccessKey.current post session_path, params: { name: user.name, email: user.email, key: access_key.raw_key } end end