phase002 complete
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:name) { |n| "User #{n}" }
|
||||
sequence(:email) { |n| "user#{n}@example.com" }
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user