phase002 complete

This commit is contained in:
2026-09-15 20:28:07 -06:00
parent 9793cd08bc
commit a3a65c24ed
28 changed files with 656 additions and 13 deletions
+10
View File
@@ -0,0 +1,10 @@
FactoryBot.define do
factory :fuel_entry do
vehicle
association :updated_by_user, factory: :user
odometer { 10_500 }
gallons_pumped { 18.25 }
price_paid { 47.62 }
date { Date.current }
end
end
+32
View File
@@ -0,0 +1,32 @@
FactoryBot.define do
factory :maintenance_entry do
vehicle
association :updated_by_user, factory: :user
name { "Oil Change" }
notes { "Oil change performed by Fleet Service Center." }
odometer { 10_500 }
cost { 89.95 }
date { Date.current }
transient do
ensure_matching_schedule { true }
end
after(:build) do |maintenance_entry, evaluator|
next unless evaluator.ensure_matching_schedule
next if maintenance_entry.vehicle.blank?
scheduled = maintenance_entry.vehicle.maintenance_schedules.any? do |schedule|
schedule.maintenance_type.casecmp?(maintenance_entry.name)
end
next if scheduled
maintenance_entry.vehicle.maintenance_schedules.build(
maintenance_type: maintenance_entry.name,
mileage_interval: 5_000,
time_interval: 180
)
end
end
end
+8
View File
@@ -0,0 +1,8 @@
FactoryBot.define do
factory :maintenance_schedule do
vehicle
maintenance_type { "Oil Change" }
mileage_interval { 5_000 }
time_interval { 180 }
end
end
+6
View File
@@ -0,0 +1,6 @@
FactoryBot.define do
factory :user do
sequence(:name) { |n| "User #{n}" }
sequence(:email) { |n| "user#{n}@example.com" }
end
end
+13
View File
@@ -0,0 +1,13 @@
FactoryBot.define do
factory :vehicle do
make { "Ford" }
model { "F-150" }
year { Date.current.year }
color { "White" }
sequence(:vin) { |n| "1FTFW1E#{n.to_s.rjust(10, '0')}" }
sequence(:licence_plate) { |n| "FLT#{n.to_s.rjust(4, '0')}" }
current_odometer { 10_000 }
fuel_tank_size { 26.0 }
active { true }
end
end
+38
View File
@@ -0,0 +1,38 @@
require "rails_helper"
RSpec.describe FuelEntry do
it "has a valid factory" do
expect(build(:fuel_entry)).to be_valid
end
it "requires all fields" do
entry = build(
:fuel_entry,
vehicle: nil,
updated_by_user: nil,
odometer: nil,
gallons_pumped: nil,
price_paid: nil,
date: nil
)
expect(entry).not_to be_valid
expect(entry.errors.attribute_names).to include(:vehicle, :updated_by_user, :odometer, :gallons_pumped, :price_paid, :date)
end
it "requires valid numeric values" do
entry = build(:fuel_entry, odometer: -1, gallons_pumped: 0, price_paid: 0)
expect(entry).not_to be_valid
expect(entry.errors[:odometer]).to be_present
expect(entry.errors[:gallons_pumped]).to be_present
expect(entry.errors[:price_paid]).to be_present
end
it "rejects future dates" do
entry = build(:fuel_entry, date: Date.current + 1.day)
expect(entry).not_to be_valid
expect(entry.errors[:date]).to be_present
end
end
+53
View File
@@ -0,0 +1,53 @@
require "rails_helper"
RSpec.describe MaintenanceEntry do
it "has a valid factory" do
expect(build(:maintenance_entry)).to be_valid
end
it "requires all fields" do
entry = build(
:maintenance_entry,
vehicle: nil,
updated_by_user: nil,
name: nil,
notes: nil,
odometer: nil,
cost: nil,
date: nil,
ensure_matching_schedule: false
)
expect(entry).not_to be_valid
expect(entry.errors.attribute_names).to include(:vehicle, :updated_by_user, :name, :notes, :odometer, :cost, :date)
end
it "requires valid numeric values and allows zero cost" do
invalid_entry = build(:maintenance_entry, odometer: -1, cost: -1)
no_cost_entry = build(:maintenance_entry, cost: 0)
expect(invalid_entry).not_to be_valid
expect(invalid_entry.errors[:odometer]).to be_present
expect(invalid_entry.errors[:cost]).to be_present
expect(no_cost_entry).to be_valid
end
it "rejects future dates" do
entry = build(:maintenance_entry, date: Date.current + 1.day)
expect(entry).not_to be_valid
expect(entry.errors[:date]).to be_present
end
it "requires name to match a vehicle maintenance schedule case-insensitively" do
vehicle = create(:vehicle)
create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change")
matching_entry = build(:maintenance_entry, vehicle: vehicle, name: "oil change")
unknown_entry = build(:maintenance_entry, vehicle: vehicle, name: "Tire Rotation", ensure_matching_schedule: false)
expect(matching_entry).to be_valid
expect(unknown_entry).not_to be_valid
expect(unknown_entry.errors[:name]).to be_present
end
end
+34
View File
@@ -0,0 +1,34 @@
require "rails_helper"
RSpec.describe MaintenanceSchedule do
it "has a valid factory" do
expect(build(:maintenance_schedule)).to be_valid
end
it "requires all fields" do
schedule = build(:maintenance_schedule, vehicle: nil, maintenance_type: nil, mileage_interval: nil, time_interval: nil)
expect(schedule).not_to be_valid
expect(schedule.errors.attribute_names).to include(:vehicle, :maintenance_type, :mileage_interval, :time_interval)
end
it "requires positive integer intervals" do
schedule = build(:maintenance_schedule, mileage_interval: 0, time_interval: 0)
expect(schedule).not_to be_valid
expect(schedule.errors[:mileage_interval]).to be_present
expect(schedule.errors[:time_interval]).to be_present
end
it "requires maintenance type to be unique per vehicle case-insensitively" do
vehicle = create(:vehicle)
create(:maintenance_schedule, vehicle: vehicle, maintenance_type: "Oil Change")
duplicate = build(:maintenance_schedule, vehicle: vehicle, maintenance_type: "oil change")
other_vehicle_schedule = build(:maintenance_schedule, maintenance_type: "oil change")
expect(duplicate).not_to be_valid
expect(duplicate.errors[:maintenance_type]).to be_present
expect(other_vehicle_schedule).to be_valid
end
end
+32
View File
@@ -0,0 +1,32 @@
require "rails_helper"
RSpec.describe User do
it "has a valid factory" do
expect(build(:user)).to be_valid
end
it "requires a name and email" do
user = build(:user, name: nil, email: nil)
expect(user).not_to be_valid
expect(user.errors[:name]).to be_present
expect(user.errors[:email]).to be_present
end
it "requires email to be unique case-insensitively" do
create(:user, email: "driver@example.com")
user = build(:user, email: "DRIVER@example.com")
expect(user).not_to be_valid
expect(user.errors[:email]).to be_present
end
it "cannot be destroyed when entries reference it" do
user = create(:user)
create(:fuel_entry, updated_by_user: user)
expect(user.destroy).to be(false)
expect(user.errors[:base]).to be_present
end
end
+89
View File
@@ -0,0 +1,89 @@
require "rails_helper"
RSpec.describe Vehicle do
it "has a valid factory" do
expect(build(:vehicle)).to be_valid
end
it "defaults to active" do
expect(described_class.new.active).to be(true)
end
it "requires all fields" do
vehicle = build(
:vehicle,
make: nil,
model: nil,
year: nil,
color: nil,
vin: nil,
licence_plate: nil,
current_odometer: nil,
fuel_tank_size: nil,
active: nil
)
expect(vehicle).not_to be_valid
expect(vehicle.errors.attribute_names).to include(
:make,
:model,
:year,
:color,
:vin,
:licence_plate,
:current_odometer,
:fuel_tank_size,
:active
)
end
it "normalizes VIN to uppercase" do
vehicle = build(:vehicle, vin: "abc123")
vehicle.valid?
expect(vehicle.vin).to eq("ABC123")
end
it "requires VIN to be unique after normalization" do
create(:vehicle, vin: "ABC123")
vehicle = build(:vehicle, vin: "abc123")
expect(vehicle).not_to be_valid
expect(vehicle.errors[:vin]).to be_present
end
it "requires licence plate to be unique case-insensitively" do
create(:vehicle, licence_plate: "ABC123")
vehicle = build(:vehicle, licence_plate: "abc123")
expect(vehicle).not_to be_valid
expect(vehicle.errors[:licence_plate]).to be_present
end
it "requires a realistic year" do
too_old = build(:vehicle, year: 1979)
future = build(:vehicle, year: Date.current.year + 1)
expect(too_old).not_to be_valid
expect(future).not_to be_valid
end
it "requires numeric mileage and fuel tank values" do
vehicle = build(:vehicle, current_odometer: -1, fuel_tank_size: 0)
expect(vehicle).not_to be_valid
expect(vehicle.errors[:current_odometer]).to be_present
expect(vehicle.errors[:fuel_tank_size]).to be_present
end
it "cannot be destroyed when entries reference it" do
vehicle = create(:vehicle)
create(:maintenance_schedule, vehicle: vehicle)
expect(vehicle.destroy).to be(false)
expect(vehicle.errors[:base]).to be_present
end
end
+15
View File
@@ -7,6 +7,21 @@ RSpec.describe "Home" do
expect(response).to have_http_status(:ok)
expect(response.body).to include("Car Tracker")
expect(response.body).to include("stylesheet")
expect(response.body).to include("importmap")
end
it "renders successfully when a content security policy is configured" do
original_policy = Rails.application.config.content_security_policy
Rails.application.config.content_security_policy do |policy|
policy.default_src :self
end
get root_path
expect(response).to have_http_status(:ok)
ensure
Rails.application.config.instance_variable_set(:@content_security_policy, original_policy)
end
end
end