Finished phase005
CI / scan_ruby (push) Failing after 1m5s
CI / scan_js (push) Failing after 6s
CI / lint (push) Failing after 1m29s

This commit is contained in:
2026-09-16 15:34:50 -06:00
parent a3a65c24ed
commit 0826d267f2
38 changed files with 1596 additions and 9 deletions
+9 -2
View File
@@ -2,11 +2,13 @@ require "rails_helper"
RSpec.describe "Home" do
describe "GET /" do
it "returns a successful response" do
before { identify_user }
it "returns the vehicle index" do
get root_path
expect(response).to have_http_status(:ok)
expect(response.body).to include("Car Tracker")
expect(response.body).to include("Vehicles")
expect(response.body).to include("stylesheet")
expect(response.body).to include("importmap")
end
@@ -24,4 +26,9 @@ RSpec.describe "Home" do
Rails.application.config.instance_variable_set(:@content_security_policy, original_policy)
end
end
def identify_user(user = create(:user))
access_key = AccessKey.current
post session_path, params: { name: user.name, email: user.email, key: access_key.raw_key }
end
end
+79
View File
@@ -0,0 +1,79 @@
require "rails_helper"
RSpec.describe "Sessions" do
describe "GET /identify" do
it "renders the identification page" do
get identify_path
expect(response).to have_http_status(:ok)
expect(response.body).to include("Identify yourself")
end
end
describe "POST /identify" do
it "identifies a new user with the current access key" do
access_key = AccessKey.current
expect do
post session_path, params: { name: "Driver One", email: "DRIVER@example.com", key: access_key.raw_key }
end.to change(User, :count).by(1).and change(UserSession, :count).by(1)
expect(response).to redirect_to(vehicles_path)
expect(User.last.email).to eq("driver@example.com")
end
it "identifies an existing user when the name matches" do
access_key = AccessKey.current
user = create(:user, name: "Driver One", email: "driver@example.com")
expect do
post session_path, params: { name: "Driver One", email: "driver@example.com", key: access_key.raw_key }
end.to change(UserSession, :count).by(1).and change(User, :count).by(0)
expect(response).to redirect_to(vehicles_path)
expect(UserSession.last.user).to eq(user)
end
it "rejects an existing email when the name does not match" do
access_key = AccessKey.current
create(:user, name: "Driver One", email: "driver@example.com")
expect do
post session_path, params: { name: "Driver Two", email: "driver@example.com", key: access_key.raw_key }
end.not_to change(UserSession, :count)
expect(response).to have_http_status(:unprocessable_content)
expect(response.body).to include("name does not match")
end
it "rejects invalid access keys and logs the attempt" do
AccessKey.current
allow(Rails.logger).to receive(:warn)
expect do
post session_path, params: { name: "Driver One", email: "driver@example.com", key: "wrong-key" }
end.not_to change(UserSession, :count)
expect(response).to have_http_status(:unprocessable_content)
expect(response.body).to include("access key is not valid")
expect(Rails.logger).to have_received(:warn).with(/Invalid access key identification attempt/)
end
end
describe "DELETE /session" do
it "logs the user out" do
identify_user
expect do
delete logout_path
end.to change(UserSession, :count).by(-1)
expect(response).to redirect_to(identify_path)
end
end
def identify_user(user = create(:user))
access_key = AccessKey.current
post session_path, params: { name: user.name, email: user.email, key: access_key.raw_key }
end
end
+376
View File
@@ -0,0 +1,376 @@
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