diff --git a/Gemfile.lock b/Gemfile.lock index d273a79f9..9546e6b0b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,6 +29,7 @@ GEM PLATFORMS arm64-darwin-20 arm64-darwin-21 + arm64-darwin-23 DEPENDENCIES faraday diff --git a/lib/dmv.rb b/lib/dmv.rb index f9e59bdf3..8debb6fd5 100644 --- a/lib/dmv.rb +++ b/lib/dmv.rb @@ -1,4 +1,8 @@ +#Class appears to track DMV facilities and services offered to customers +require 'pry' + class Dmv + attr_reader :facilities def initialize @facilities = [] @@ -9,8 +13,166 @@ def add_facility(facility) end def facilities_offering_service(service) - @facilities.find do |facility| + @facilities.find_all do |facility| facility.services.include?(service) end end + + #Larger methods below. For future: could consider classes for DMVs in each state, to better handle + #idiosyncracies to procedures and different dataset formats for different states. + + def create_state_facilities(state, facilities_incoming_data) + facilities_incoming_data.each do |facility_data| + #Construct facility based on state, since dataset must be converted differently due to idiosyncracies in each case + if state == "Colorado" + new_facility = Facility.new(facility_parameters_co(facility_data)) + elsif state == "New York" + new_facility = Facility.new(facility_parameters_ny(facility_data)) + elsif state == "Missouri" + new_facility = Facility.new(facility_parameters_mo(facility_data)) + end + + add_facility(new_facility) + + #Add services. CO specifies some of them, so those must be checked (in furture rewrite, could remake initialize() to deal with this more cleanly too) + new_facility.add_service("Written Test") + new_facility.add_service("Road Test") + if state == "Colorado" + new_facility.add_service("New Drivers License") if facility_data[:services_p].include?("registration") + new_facility.add_service("Renew Drivers License") if facility_data[:services_p].include?("renew") + else + new_facility.add_service("New Drivers License") + new_facility.add_service("Renew Drivers License") + end + end + end + + def get_ev_registration_analytics(state, specified_year) + #Determine most popular make/model registered, the # registered for specified year, and the county with most registered vehicles + #NOTE: I kept method naming based on EV vehicles. However, now that I've extended functionality to allow NY vehicles (later in iteration 4), this method is more general, + #and can analyze general NY vehicles. + + vehicle_tally = {} + @facilities.each do |facility| + if facility.state == state + facility.registered_vehicles.each do |vehicle| + if vehicle_tally.include?(vehicle.model) #Assume just 'model' is enough (i.e. its unique and not make + model is needed) + vehicle_tally[vehicle.model] += 1 + else + vehicle_tally[vehicle.model] = 1 + end + end + end + end + #Now find the largest count in the hash: + most_popular_model = vehicle_tally.key(vehicle_tally.values.max) + + #Count number of occurences of given registration year in vehicle list + vehicle_year_total = 0 + @facilities.each do |facility| + if facility.state == state + vehicle_year_total += facility.registered_vehicles.count do |vehicle| + vehicle.registration_date.year == specified_year + end + end + end + + #Find county which has the most vehicle registrations. Note high machinery overlap with most popular model...a way to combine? + county_tally = {} + @facilities.each do |facility| + if facility.state == state + facility.registered_vehicles.each do |vehicle| + if county_tally.include?(vehicle.registration_county) + county_tally[vehicle.registration_county] += 1 + else + county_tally[vehicle.registration_county] = 1 + end + end + end + end + most_popular_county = county_tally.key(county_tally.values.max) + + {most_popular_model: most_popular_model, number_registered_for_year: vehicle_year_total, county_most_registered_vehicles: most_popular_county} + end + + def facility_parameters_co(facility_co_data) + #Try to centralize idiosyncratic aspects of each state's dataset to specific spots in codebase + #Alternate: could hosue this in Facility class. Might try that another time - would require a different format since Facility wouldn't yet be initialized + { + name: facility_co_data[:dmv_office], + address: "#{facility_co_data[:address_li]} #{facility_co_data[:address__1]} #{facility_co_data[:location]} #{facility_co_data[:city]} #{facility_co_data[:state]} #{facility_co_data[:zip]}", + phone: facility_co_data[:phone], + hours: facility_co_data[:hours] #Iteration 4: add hours (direct copy for CO) + } + end + + def facility_parameters_ny(facility_ny_data) + #To format name: have to concatenate and then 'fix' capitlization the hard way (since each word is capitalized for a title usually) + name_formatted = facility_ny_data[:office_name].split.map { |word| word.capitalize } + name_formatted << (facility_ny_data[:office_type].split.map { |word| word.capitalize }).join(" ") + name_formatted = name_formatted.join(" ") + + #To format address: lots of overlap here with previous entry. If I knew how to make a method that accepted varying arguments, I might be able to; don't know this yet... + address_formatted = facility_ny_data[:street_address_line_1].split.map { |word| word.capitalize } + address_formatted << facility_ny_data[:city].split.map { |word| word.capitalize } + address_formatted << facility_ny_data[:state] + address_formatted << facility_ny_data[:zip_code] + address_formatted = address_formatted.join(" ") + + #Hours are listed for each day by opening and closing. For now, I'm going to brute-force this, + #since no exact format is expected. This is a mess though: + #This is gross...some facilities don't have keys for all M-F. So I need to run ifs now (otherwise problems with nil) + hours_formatted = "" + if facility_ny_data.include?(:monday_beginning_hours) + hours_formatted = "Monday: " + facility_ny_data[:monday_beginning_hours] + " - " + facility_ny_data[:monday_ending_hours] + "; " + end + if facility_ny_data.include?(:tuesday_beginning_hours) + hours_formatted << "Tuesday: " + facility_ny_data[:tuesday_beginning_hours] + " - " + facility_ny_data[:tuesday_ending_hours] + "; " + end + if facility_ny_data.include?(:wednesday_beginning_hours) + hours_formatted << "Wednesday: " + facility_ny_data[:wednesday_beginning_hours] + " - " + facility_ny_data[:wednesday_ending_hours] + "; " + end + if facility_ny_data.include?(:thursday_beginning_hours) + hours_formatted << "Thursday: " + facility_ny_data[:thursday_beginning_hours] + " - " + facility_ny_data[:thursday_ending_hours] + "; " + end + if facility_ny_data.include?(:friday_beginning_hours) + hours_formatted << "Friday: " + facility_ny_data[:friday_beginning_hours] + " - " + facility_ny_data[:friday_ending_hours] + end + + #Some of the office don't seem to have phone numbers...make sure to process 'nil' correctly here + phone = facility_ny_data[:public_phone_number] + if phone != nil + phone_formatted = "(" + phone.slice(0, 3) + ") " + phone.slice(3, 3) + "-" + phone.slice(6, 4) + else + phone_formatted = "not applicable" + end + + { + name: name_formatted, + address: address_formatted, + phone: phone_formatted, + hours: hours_formatted + } + end + + def facility_parameters_mo(facility_mo_data) + address_formatted = facility_mo_data[:address1].delete_suffix(",") #Certain (random?) entries end with it for some reason + address_formatted = "#{address_formatted} #{facility_mo_data[:city]} #{facility_mo_data[:state]} #{facility_mo_data[:zipcode]}" + hours_formatted = facility_mo_data[:daysopen] + if facility_mo_data.include?(:daysclosed) + hours_formatted << " except for " + facility_mo_data[:daysclosed] + end + + facility_info = { + name: facility_mo_data[:name] + " Office", #To be as consistent with other states as possible + address: address_formatted, + phone: facility_mo_data[:phone], + #Add hours (may need to format a little to be consistent across states) + #Add holidays here (since MO provides them) + #Need :daysopen, :daysclosed; :holidaysclosed + hours: hours_formatted, + holidays: facility_mo_data[:holidaysclosed] + } + end + end diff --git a/lib/dmv_data_service.rb b/lib/dmv_data_service.rb index 7c979a82c..c1ac02231 100644 --- a/lib/dmv_data_service.rb +++ b/lib/dmv_data_service.rb @@ -1,6 +1,8 @@ require 'faraday' require 'json' +#NOTE: I see that for the vehicle data loading, it only pulls 1000 vehicles for both WA and NY. No way that is coincidence...where is the '1000' constraint present? + class DmvDataService def load_data(source) response = Faraday.get(source) @@ -11,6 +13,11 @@ def wa_ev_registrations @wa_ev_registrations ||= load_data('https://data.wa.gov/resource/rpr4-cgyd.json') end + def ny_vehicle_registrations + #Iteration 4 - based on dataset URL provided. Should have same structure as other entries: + @ny_vehicle_registrations ||= load_data('https://data.ny.gov/resource/w4pv-hbkt.json') + end + def co_dmv_office_locations @co_dmv_office_locations ||= load_data('https://data.colorado.gov/resource/dsw3-mrn4.json') end diff --git a/lib/facility.rb b/lib/facility.rb index a65757687..ead1ed440 100644 --- a/lib/facility.rb +++ b/lib/facility.rb @@ -1,14 +1,66 @@ +#This tracks a specific DMV facility's core information and serviced rendered (or perhaps I should say """"""services""""""", given that it's the DMV) + class Facility - attr_reader :name, :address, :phone, :services + attr_reader :name, :address, :phone, :services, :collected_fees, :registered_vehicles, :state, :hours, :holidays + @@fee_chart = {ev: 200, antique: 25, regular: 100} #Fun new thing. Can use again and again (class var, not instance var) - def initialize(name, address, phone) - @name = name - @address = address - @phone = phone + def initialize(facility_info) + @name = facility_info[:name] + @address = facility_info[:address] + @phone = facility_info[:phone] @services = [] + @collected_fees = 0 + @registered_vehicles = [] + #Added in iteration 4 (for by-state searching). Default is nil, if not specified (backwards compatible methinks) + @state = facility_info[:state] + @hours = facility_info[:hours] + @holidays = facility_info[:holidays] end - def add_services(service) + def add_service(service) @services << service end + + def include?(service_specified) + #NOTE: find must return 'false' if nothing found to play nice with methods accessing it + @services.find(proc {false}) { |service| service == service_specified } + end + + #Facility services + + def register_vehicle(vehicle) + #Only register the vehicle if this facility offers the service! Otherwise don't waste time with processing. + return nil if !include?("Vehicle Registration") + + vehicle.registration_date = Date.today + + #Determine plate type and collect fees (this of course assumes the registrant has/will pay) + if vehicle.electric_vehicle?() + vehicle.plate_type = :ev + @collected_fees += @@fee_chart[:ev] + elsif vehicle.antique?() + vehicle.plate_type = :antique + @collected_fees += @@fee_chart[:antique] + else + vehicle.plate_type = :regular + @collected_fees += @@fee_chart[:regular] + end + + @registered_vehicles << vehicle + end + + def administer_written_test(registrant) + #ALL conditions must be met. Pretty compact! + registrant.license_data[:written] = include?("New Drivers License") && registrant.age >= 16 && registrant.permit?() + end + + def administer_road_test(registrant) + #Of course, this assumes the registrant actually passes the test (happy path) + registrant.license_data[:license] = include?("Road Test") && registrant.license_data[:written] == true + end + + def renew_drivers_license(registrant) + registrant.license_data[:renewed] = include?("Renew License") && registrant.license_data[:license] == true + end + end diff --git a/lib/registrant.rb b/lib/registrant.rb new file mode 100644 index 000000000..043456468 --- /dev/null +++ b/lib/registrant.rb @@ -0,0 +1,18 @@ +class Registrant + attr_reader :name, :age, :license_data + + def initialize(name, age, permit = false) + @name = name + @age = age + @permit = permit + @license_data = {:written => false, :license => false, :renewed => false} + end + + def permit?() + @permit + end + + def earn_permit() + @permit = true + end +end diff --git a/lib/vehicle.rb b/lib/vehicle.rb index 49ae83672..d0c9aa923 100644 --- a/lib/vehicle.rb +++ b/lib/vehicle.rb @@ -5,14 +5,21 @@ class Vehicle :year, :make, :model, - :engine + :engine, + :registration_county + attr_accessor :registration_date, :plate_type def initialize(vehicle_details) @vin = vehicle_details[:vin] - @year = vehicle_details[:year] + @year = vehicle_details[:year].to_i #Need to_i to permit proper function of antique?() method @make = vehicle_details[:make] @model = vehicle_details[:model] @engine = vehicle_details[:engine] + @registration_date = nil + + #Could make a registration_data hash for compactness, but will keep vars separate to mimic interaction pattern + @plate_type = nil + @registration_county = vehicle_details[:county] #This could either be set based on where car is built, OR where it's registered end def antique? diff --git a/lib/vehicle_factory.rb b/lib/vehicle_factory.rb new file mode 100644 index 000000000..0f9b87356 --- /dev/null +++ b/lib/vehicle_factory.rb @@ -0,0 +1,55 @@ +#This 'manufactures' cars. Kinda a misnomer, since it just takes existing registration data from API +#(doesn't really build them), but I see why we could do this to 'help' the Vehicle class along. + +class VehicleFactory + attr_reader :vehicles_manufactured + + def initialize() + @vehicles_manufactured = [] + end + + def create_vehicles(vehicle_registration_list, state) + #Different states produce different datasets, so need to switch based on this. + + vehicle_registration_list.each do |vehicle| + if state == "Washington" + vehicle_data = { + vin: vehicle[:vin_1_10], + make: vehicle[:make], + model: vehicle[:model], + year: vehicle[:model_year], + #Assume the engine is always ev here in WA (though not generally true - happy path for now) + engine: :ev, + county: vehicle[:county] + } + elsif state == "New York" + #Engine type isn't specified in dataset. Let's not get too fancy here, so just provide three options: :ev, :ice, or :other + case vehicle[:fuel_type] + when "ELECTRIC" + engine_type = :ev + when "GAS" || "DIESEL" || "PROPANE" || "FLEX" #There are several fossil-fuel types...these are the only ones I'm pretty confident about + engine_type = :ice + else + engine_type = :other + end + + vehicle_data = { + vin: vehicle[:vin], + make: vehicle[:make], + model: "data not available", #Not provided in NY dataset, hence this value given + year: vehicle[:model_year], + engine: engine_type, + county: vehicle[:county].capitalize #Be consistent between states + } + else + puts "Error: state invalid / not recognized. Cannot build vehicles from registry." + return [] #To be consistent with typical return type + end + + @vehicles_manufactured << Vehicle.new(vehicle_data) + end + + return @vehicles_manufactured + end + +end diff --git a/spec/dmv_data_service_spec.rb b/spec/dmv_data_service_spec.rb index 7baf709a0..803701623 100644 --- a/spec/dmv_data_service_spec.rb +++ b/spec/dmv_data_service_spec.rb @@ -22,24 +22,35 @@ describe '#wa_ev_registrations' do it 'can load washington ev registration data' do expect(@dds.wa_ev_registrations.size).to be_an(Integer) + expect(@dds.wa_ev_registrations).to be_an(Array) + end + end + + describe '#ny_vehicle_registrations' do + it 'can load new york general vehicle regisration data' do + expect(@dds.ny_vehicle_registrations.size).to be_an(Integer) + expect(@dds.ny_vehicle_registrations).to be_an(Array) end end describe '#co_dmv_office_locations' do it 'can load colorado dmv office locations' do expect(@dds.co_dmv_office_locations.size).to be_an(Integer) + expect(@dds.co_dmv_office_locations).to be_an(Array) end end describe '#ny_dmv_office_locations' do it 'can load new york dmv office locations' do expect(@dds.ny_dmv_office_locations.size).to be_an(Integer) + expect(@dds.ny_dmv_office_locations).to be_an(Array) end end describe '#mo_dmv_office_locations' do it 'can load missouri dmv office locations' do expect(@dds.mo_dmv_office_locations.size).to be_an(Integer) + expect(@dds.mo_dmv_office_locations).to be_an(Array) end end end diff --git a/spec/dmv_spec.rb b/spec/dmv_spec.rb index 70308d270..0b95c9acf 100644 --- a/spec/dmv_spec.rb +++ b/spec/dmv_spec.rb @@ -6,6 +6,9 @@ @facility_1 = Facility.new({name: 'DMV Tremont Branch', address: '2855 Tremont Place Suite 118 Denver CO 80205', phone: '(720) 865-4600'}) @facility_2 = Facility.new({name: 'DMV Northeast Branch', address: '4685 Peoria Street Suite 101 Denver CO 80239', phone: '(720) 865-4600'}) @facility_3 = Facility.new({name: 'DMV Northwest Branch', address: '3698 W. 44th Avenue Denver CO 80211', phone: '(720) 865-4600'}) + @wa_facility = Facility.new({name: 'DMV Tacoma Branch', address: 'Some address', phone: '(555) 555-5555', state: "Washington"}) + @ny_facility = Facility.new({name: 'DMV Nassau Branch', address: 'Some other address', phone: '(555) 555-5556', state: "New York"}) + end describe '#initialize' do @@ -40,4 +43,183 @@ expect(@dmv.facilities_offering_service('Road Test')).to eq([@facility_2, @facility_3]) end end + + describe '#create_state_facilities' do + it 'can create default facilities array for Colorado' do + colorado_facilities = DmvDataService.new.co_dmv_office_locations() + @dmv.create_state_facilities("Colorado", colorado_facilities) + + #Basic checks first + expect(@dmv.facilities).to be_a(Array) + expect(@dmv.facilities).to_not eq([]) + end + + it 'correctly create facilities array for Colorado based on API data' do + colorado_facilities = DmvDataService.new.co_dmv_office_locations() + @dmv.create_state_facilities("Colorado", colorado_facilities) + + #NOTE: I previously checked specific key-value pairs in the dataset to match, but the datasets can change (they DID for MO) + #So, now I'll check more permanent features, but with less specificity + expect(@dmv.facilities[0]).to be_a(Facility) + expect(@dmv.facilities[0].name).to be_a(String) + expect(@dmv.facilities[1].address).to be_a(String) + expect(@dmv.facilities[2].phone.length).to eq(14) + expect(@dmv.facilities[0].services).to be_a(Array) + expect(@dmv.facilities[3].hours).to be_a(String) + end + + it 'correctly create facilities array for New York based on API data' do + newyork_facilities = DmvDataService.new.ny_dmv_office_locations() + @dmv.create_state_facilities("New York", newyork_facilities) + + # binding.pry + + expect(@dmv.facilities[0]).to be_a(Facility) + expect(@dmv.facilities[0].name).to be_a(String) + expect(@dmv.facilities[1].address).to be_a(String) + expect(@dmv.facilities[2].phone.length).to eq(14) + expect(@dmv.facilities[0].services).to be_a(Array) + expect(@dmv.facilities[3].hours).to be_a(String) + end + + it 'correctly create facilities array for Missouri based on API data' do + missouri_facilities = DmvDataService.new.mo_dmv_office_locations() + @dmv.create_state_facilities("Missouri", missouri_facilities) + + # binding.pry + + #NOTE: this dataset list actually changed on me after I made tests; hence why I've moved to more 'permanent' testable aspects + expect(@dmv.facilities[0]).to be_a(Facility) + expect(@dmv.facilities[0].name).to be_a(String) + expect(@dmv.facilities[1].address).to be_a(String) + #Some office don't have a phone number listed in MO. Need to give default string, I guess + expect(@dmv.facilities[2].phone.length).to eq(14) + expect(@dmv.facilities[0].services).to be_a(Array) + expect(@dmv.facilities[3].hours).to be_a(String) + end + end + + describe '#get_ev_registration_analytics()' do + it 'can retun appropriate data structure' do + expect(@dmv.get_ev_registration_analytics("Washington", 2019)).to be_a(Hash) + end + + it 'can generate hash with correct most popular vehicle model' do + #First, we need to build the vehicle regitration list and 'make' the vehicles (kept forgetting to do this!) + factory = VehicleFactory.new() + factory.create_vehicles(DmvDataService.new().wa_ev_registrations, "Washington") + + #Now we need to register the vehicles to one or more facilities (keep it one / simple for the moment) + #NOTE: facility needs to have the appropriate service enabled, and actually be in correct state for everything to work + @wa_facility = Facility.new({name: 'DMV Tacoma Branch', address: 'Some address', phone: '(555) 555-5555', state: "Washington"}) + @wa_facility.add_service("Vehicle Registration") + + factory.vehicles_manufactured.each do |vehicle| + @wa_facility.register_vehicle(vehicle) + end + + @dmv.add_facility(@wa_facility) + + hash = @dmv.get_ev_registration_analytics("Washington", 2019) + + expect(hash[:most_popular_model]).to eq("Model 3") #Risky - using correct model actually in the dataset to verify (could change if dataset changes) + expect(hash[:most_popular_model]).to be_a(String) #Safer, but doesn't truly check if method is working correctly + + end + + it 'can generate hash with correct # of registrations for specified year' do + #Create the same machinery as in previous test in order to have everything set up correctly... + factory = VehicleFactory.new() + factory.create_vehicles(DmvDataService.new().wa_ev_registrations, "Washington") + @wa_facility.add_service("Vehicle Registration") + factory.vehicles_manufactured.each do |vehicle| + @wa_facility.register_vehicle(vehicle) + end + @dmv.add_facility(@wa_facility) + + #Because we're manually registering them now, all of them should be for 2024 + hash_2019 = @dmv.get_ev_registration_analytics("Washington", 2019) + expect(hash_2019[:number_registered_for_year]).to eq(0) + hash_2024 = @dmv.get_ev_registration_analytics("Washington", 2024) + + expect(hash_2024[:number_registered_for_year]).to eq(@wa_facility.registered_vehicles.length) + end + + it 'can generate hash with correct most common county registered' do + #Create the same machinery as in previous tests in order to have everything set up correctly... + factory = VehicleFactory.new() + factory.create_vehicles(DmvDataService.new().wa_ev_registrations, "Washington") + @wa_facility.add_service("Vehicle Registration") + factory.vehicles_manufactured.each do |vehicle| + @wa_facility.register_vehicle(vehicle) + end + @dmv.add_facility(@wa_facility) + + #Track county in Vehicle class; alternate would be deducing this from the facility it gets registered to, but we'd need a lookup function for zip code / similar + #(beyond the scope of this project at this point...) + hash = @dmv.get_ev_registration_analytics("Washington", 2019) + expect(hash[:county_most_registered_vehicles]).to eq("King") #Again, risky - depends on served dataset staying the same... + expect(hash[:county_most_registered_vehicles]).to be_a(String) #Safer, but doesn't truly see if it's working correctly + end + + it 'can generate appropriate information for both WA and NY vehicles' do + #Create general stuff: + wa_factory = VehicleFactory.new() + ny_factory = VehicleFactory.new() + @dmv.add_facility(@wa_facility) + @dmv.add_facility(@ny_facility) + + #Create WA vehicles and register with first facility + wa_factory.create_vehicles(DmvDataService.new().wa_ev_registrations, "Washington") + @wa_facility.add_service("Vehicle Registration") + wa_factory.vehicles_manufactured.each do |vehicle| + @wa_facility.register_vehicle(vehicle) + end + + #Check for WA + wa_hash = @dmv.get_ev_registration_analytics("Washington", 2024) + expect(wa_hash).to be_a(Hash) + expect(wa_hash[:county_most_registered_vehicles]).to eq("King") #Could change - risky (but very precise, no false positive here) + + #Create NY vehicles and register with second facility (don't have to, but it should really be in NY...) + ny_factory.create_vehicles(DmvDataService.new().ny_vehicle_registrations, "New York") + @ny_facility.add_service("Vehicle Registration") + ny_factory.vehicles_manufactured.each do |vehicle| + @ny_facility.register_vehicle(vehicle) + end + + #Check for NY + ny_hash = @dmv.get_ev_registration_analytics("New York", 2024) + expect(ny_hash).to be_a(Hash) + expect(ny_hash[:county_most_registered_vehicles]).to eq("Suffolk") #Could change - risky (but very precise, no false positive here) + end + + end + + describe '#facility_parameters_[state name]() helper methods' do + it 'facility_parameters_co() method returns standard formatted facility_info' do + colorado_facilities = DmvDataService.new.co_dmv_office_locations() + @dmv.create_state_facilities("Colorado", colorado_facilities) + + parameters_hash = @dmv.facility_parameters_co(colorado_facilities[0]) + expect(@dmv.facilities[0].name).to eq(parameters_hash[:name]) + end + + it 'facility_parameters_ny() method returns standard formatted facility_info' do + newyork_facilities = DmvDataService.new.co_dmv_office_locations() + @dmv.create_state_facilities("Colorado", newyork_facilities) + + parameters_hash = @dmv.facility_parameters_co(newyork_facilities[0]) + expect(@dmv.facilities[0].name).to eq(parameters_hash[:name]) + end + + it 'facility_parameters_mo() method returns standard formatted facility_info' do + missouri_facilities = DmvDataService.new.co_dmv_office_locations() + @dmv.create_state_facilities("Colorado", missouri_facilities) + + parameters_hash = @dmv.facility_parameters_co(missouri_facilities[0]) + expect(@dmv.facilities[0].name).to eq(parameters_hash[:name]) + end + end + end diff --git a/spec/facility_spec.rb b/spec/facility_spec.rb index c0f2f1233..d687b0825 100644 --- a/spec/facility_spec.rb +++ b/spec/facility_spec.rb @@ -2,25 +2,207 @@ RSpec.describe Facility do before(:each) do - @facility = Facility.new({name: 'DMV Tremont Branch', address: '2855 Tremont Place Suite 118 Denver CO 80205', phone: '(720) 865-4600'}) + @facility_1 = Facility.new({name: 'DMV Tremont Branch', address: '2855 Tremont Place Suite 118 Denver CO 80205', phone: '(720) 865-4600'}) + @facility_2 = Facility.new({name: 'DMV Northeast Branch', address: '4685 Peoria Street Suite 101 Denver CO 80239', phone: '(720) 865-4600'}) + + @cruz = Vehicle.new({vin: '123456789abcdefgh', year: 2012, make: 'Chevrolet', model: 'Cruz', engine: :ice}) + @bolt = Vehicle.new({vin: '987654321abcdefgh', year: 2019, make: 'Chevrolet', model: 'Bolt', engine: :ev}) + @camaro = Vehicle.new({vin: '1a2b3c4d5e6f', year: 1969, make: 'Chevrolet', model: 'Camaro', engine: :ice}) + + @registrant_1 = Registrant.new("Bruce", 18, true) + @registrant_2 = Registrant.new("Penny", 16) + @registrant_3 = Registrant.new("Tucker", 15) end + describe '#initialize' do it 'can initialize' do - expect(@facility).to be_an_instance_of(Facility) - expect(@facility.name).to eq('DMV Tremont Branch') - expect(@facility.address).to eq('2855 Tremont Place Suite 118 Denver CO 80205') - expect(@facility.phone).to eq('(720) 865-4600') - expect(@facility.services).to eq([]) + expect(@facility_1).to be_an_instance_of(Facility) + expect(@facility_1.name).to eq('DMV Tremont Branch') + expect(@facility_1.address).to eq('2855 Tremont Place Suite 118 Denver CO 80205') + expect(@facility_1.phone).to eq('(720) 865-4600') + expect(@facility_1.services).to eq([]) + end + + it 'starts with correct collected_fees and registered_vehicles' do + #Added for iteration 2 + expect(@facility_1.collected_fees).to eq(0) + expect(@facility_1.registered_vehicles).to eq([]) + end + + it 'can initialize with state specified' do + expect(@facility_1.state).to eq(nil) + + facility_4 = Facility.new({name: 'DMV Made-up Branch', address: '3698 W. 44th Avenue Denver CO 80211', phone: '(720) 865-4600', state: "Wyoming"}) + + expect(facility_4.state).to eq("Wyoming") + end + + it 'can initialize with hours and potential holidays specified' do + facility_5 = Facility.new({name: 'DMV Made-up Branch', address: '3698 W. 44th Avenue Denver CO 80211', phone: '(720) 865-4600', state: "Wyoming", hours: "M-F 9AM-5PM", holidays: "A few days listed here"}) + + expect(facility_5.hours).to eq("M-F 9AM-5PM") + expect(facility_5.holidays).to eq("A few days listed here") end + end describe '#add service' do it 'can add available services' do - expect(@facility.services).to eq([]) - @facility.add_service('New Drivers License') - @facility.add_service('Renew Drivers License') - @facility.add_service('Vehicle Registration') - expect(@facility.services).to eq(['New Drivers License', 'Renew Drivers License', 'Vehicle Registration']) + expect(@facility_1.services).to eq([]) + @facility_1.add_service('New Drivers License') + @facility_1.add_service('Renew Drivers License') + @facility_1.add_service('Vehicle Registration') + expect(@facility_1.services).to eq(['New Drivers License', 'Renew Drivers License', 'Vehicle Registration']) + end + end + + describe '#register_vehicle' do + it 'can register vehicles' do + @facility_1.add_service('Vehicle Registration') + @facility_1.register_vehicle(@cruz) + @facility_1.register_vehicle(@bolt) + @facility_1.register_vehicle(@camaro) + + expect(@facility_1.registered_vehicles).to eq([@cruz, @bolt, @camaro]) + end + + it 'can only register a vehicle if that service is provided by facility' do + @facility_1.add_service("New Drivers License") + @facility_1.register_vehicle(@cruz) + + expect(@facility_1.registered_vehicles).to eq([]) + end + + it 'collects fee and assigns plate appropriately' do + @facility_1.add_service('Vehicle Registration') + + @facility_1.register_vehicle(@cruz) + @facility_1.register_vehicle(@bolt) + @facility_1.register_vehicle(@camaro) + + #Oof, this is a lot, but needed to exahustively test possible cases: + expect(@facility_1.collected_fees).to eq(325) + expect(@facility_1.registered_vehicles[0].plate_type).to eq(:regular) + expect(@facility_1.registered_vehicles[1].plate_type).to eq(:ev) + expect(@facility_1.registered_vehicles[2].plate_type).to eq(:antique) + expect(@facility_1.registered_vehicles[0].registration_date).to_not eq(nil) + expect(@facility_1.registered_vehicles[1].registration_date).to_not eq(nil) + expect(@facility_1.registered_vehicles[2].registration_date).to_not eq(nil) end end + + describe '#administer_written_test' do + it 'can only administer written test if service provided by facility' do + expect(@facility_1.administer_written_test(@registrant_1)).to eq(false) + + @facility_1.add_service("New Drivers License") + + expect(@facility_1.administer_written_test(@registrant_1)).to eq(true) + end + + it 'cannot administer written test if registrant does not meet requirements' do + @facility_1.add_service("New Drivers License") + + expect(@facility_1.administer_written_test(@registrant_2)).to eq(false) + expect(@facility_1.administer_written_test(@registrant_3)).to eq(false) + end + + it 'updates license information for registrants who can/do complete written exam' do + @facility_1.add_service("New Drivers License") + + #Works immediately for registrant 1 + expect(@facility_1.administer_written_test(@registrant_1)).to eq(true) + expect(@registrant_1.license_data[:written]).to eq(true) + + #Works for registrant 2 after they get their permit + expect(@facility_1.administer_written_test(@registrant_2)).to eq(false) + @registrant_2.earn_permit + expect(@facility_1.administer_written_test(@registrant_2)).to eq(true) + expect(@registrant_2.license_data[:written]).to eq(true) + + #Cannot work for registrant 3 (at least not until next year) + expect(@facility_1.administer_written_test(@registrant_3)).to eq(false) + @registrant_3.earn_permit + expect(@facility_1.administer_written_test(@registrant_3)).to eq(false) + expect(@registrant_3.license_data[:written]).to eq(false) + end + end + + describe '#administer_road_test' do + it 'can only administer road test if service provided by facility' do + expect(@facility_1.administer_road_test(@registrant_3)).to eq(false) + #This part mimics interaction pattern, but isn't necessary since they can't take it regardless (and that has already been tested) + @registrant_3.earn_permit() + @facility_1.administer_written_test(@registrant_3) + + expect(@facility_1.administer_road_test(@registrant_3)).to eq(false) + expect(@registrant_3.license_data[:license]).to eq(false) + + #Also do this for registrant #1 (valid registrant) + expect(@facility_1.administer_written_test(@registrant_1)).to eq(false) + expect(@facility_1.administer_road_test(@registrant_1)).to eq(false) + end + + it 'road test success only for valid registrant, and updates license_data appropriately' do + #Remember that the written test must be successfully completed first... + @facility_1.add_service("Road Test") + @facility_1.add_service("New Drivers License") + + expect(@facility_1.administer_road_test(@registrant_1)).to eq(false) + + @facility_1.administer_written_test(@registrant_1) + + expect(@facility_1.administer_road_test(@registrant_1)).to eq(true) + expect(@registrant_1.license_data[:license]).to eq(true) + end + end + + describe '#renew_drivers_license' do + it 'can only renew license if service provided by facility' do + expect(@facility_1.renew_drivers_license(@registrant_1)).to eq(false) + + @facility_1.add_service("Road Test") + @facility_1.add_service("New Drivers License") + @facility_1.add_service("Written Test") + @facility_1.add_service("Renew License") + + expect(@facility_1.renew_drivers_license(@registrant_1)).to eq(false) + + #Registrant must already have a license to do this. + @facility_1.administer_written_test(@registrant_1) + @facility_1.administer_road_test(@registrant_1) + + expect(@facility_1.renew_drivers_license(@registrant_1)).to eq(true) + end + + it 'can only renew license for valid registrant' do + @facility_1.add_service("Road Test") + @facility_1.add_service("New Drivers License") + @facility_1.add_service("Written Test") + @facility_1.add_service("Renew License") + + #@registrant_3 cannot even have a license yet, so certainly cannot renew + @facility_1.administer_written_test(@registrant_3) + @facility_1.administer_road_test(@registrant_3) + + expect(@facility_1.renew_drivers_license(@registrant_3)).to eq(false) + end + + it 'properly sets all appropriate registrant license information' do + @facility_1.add_service("Road Test") + @facility_1.add_service("New Drivers License") + @facility_1.add_service("Written Test") + @facility_1.add_service("Renew License") + + expect(@registrant_1.license_data).to eq({written: false, license: false, renewed: false}) + + @facility_1.administer_written_test(@registrant_1) + @facility_1.administer_road_test(@registrant_1) + @facility_1.renew_drivers_license(@registrant_1) + + max_upgrades_hash = {written: true, license: true, renewed: true} + expect(@registrant_1.license_data).to eq(max_upgrades_hash) + end + end + end diff --git a/spec/registrant_spec.rb b/spec/registrant_spec.rb new file mode 100644 index 000000000..68c1e7b42 --- /dev/null +++ b/spec/registrant_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +RSpec.describe Registrant do + before(:each) do + @registrant_1 = Registrant.new("Bruce", 18, true) + @registrant_2 = Registrant.new("Penny", 15) + end + + describe '#initialize' do + #Note: permit?() method also tested here + it 'can initialize' do + default_hash = {:written => false, :license => false, :renewed => false} + + expect(@registrant_1).to be_a(Registrant) + expect(@registrant_1.name).to eq("Bruce") + expect(@registrant_1.age).to eq(18) + expect(@registrant_1.permit?()).to eq(true) + expect(@registrant_1.license_data).to eq(default_hash) + + end + + it 'can handle default paramters correctly on another registrant' do + default_hash = {:written => false, :license => false, :renewed => false} + + expect(@registrant_2).to be_a(Registrant) + expect(@registrant_2.name).to eq("Penny") + expect(@registrant_2.age).to eq(15) + expect(@registrant_2.permit?()).to eq(false) + expect(@registrant_2.license_data).to eq(default_hash) + end + end + + describe 'other methods' do + it 'can earn permit' do + expect(@registrant_2.permit?()).to eq(false) + + @registrant_2.earn_permit() + + expect(@registrant_2.permit?()).to eq(true) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 928255386..03f31942d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,9 @@ -require 'pry' -require './lib/dmv' -require './lib/facility' -require './lib/vehicle' -require './lib/dmv_data_service' +#Simple way to keep all of the file dependencies in one central location + +require 'pry' #To enable testing where needed +require './lib/dmv.rb' +require './lib/facility.rb' +require './lib/vehicle.rb' +require './lib/dmv_data_service.rb' +require './lib/vehicle_factory.rb' +require './lib/registrant.rb' diff --git a/spec/vehicle_factory_spec.rb b/spec/vehicle_factory_spec.rb new file mode 100644 index 000000000..147fe2acf --- /dev/null +++ b/spec/vehicle_factory_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +RSpec.describe VehicleFactory do + before(:each) do + @factory = VehicleFactory.new() + @dds = DmvDataService.new() + @registration_data_list = @dds.wa_ev_registrations + end + + describe '#initialize' do + it 'can initialize' do + expect(@factory).to be_a(VehicleFactory) + expect(@factory.vehicles_manufactured).to eq([]) + end + end + + describe '#create_vehicles()' do + it 'can create vehicles from acquired vehicle registration list for WA' do + expect(@registration_data_list.size).to be_a(Integer) #Like in other spec file. Don't know why it wouldn't be an integer... + expect(@registration_data_list).to be_a(Array) + + expect(@factory.create_vehicles(@registration_data_list, "Washington")).to be_a(Array) + expect(@factory.create_vehicles(@registration_data_list, "Washington")).to eq(@factory.vehicles_manufactured) + end + + it 'can create vehicles from acquired vehicle registration list for NY' do + ny_registration_data_list = @dds.ny_vehicle_registrations + expect(ny_registration_data_list.size).to be_a(Integer) + expect(ny_registration_data_list).to be_a(Array) + + ny_vehicles = @factory.create_vehicles(ny_registration_data_list, "New York") + + expect(ny_vehicles).to be_a(Array) + expect(ny_vehicles).to eq(@factory.vehicles_manufactured) + end + end + +end diff --git a/spec/vehicle_spec.rb b/spec/vehicle_spec.rb index 5c7f01184..ba1051aa2 100644 --- a/spec/vehicle_spec.rb +++ b/spec/vehicle_spec.rb @@ -15,6 +15,13 @@ expect(@cruz.model).to eq('Cruz') expect(@cruz.engine).to eq(:ice) expect(@cruz.registration_date).to eq(nil) + expect(@cruz.plate_type).to eq(nil) + end + + it 'can initialize optional county of registration correctly' do + bolt_2 = Vehicle.new({vin: '1111111111', year: 2020, make: 'Chevrolet', model: 'Bolt', engine: :ev, county: 'Tacoma'}) + expect(bolt_2).to be_a(Vehicle) + expect(bolt_2.registration_county).to eq("Tacoma") end end @@ -33,4 +40,6 @@ expect(@camaro.electric_vehicle?).to eq(false) end end + + #Skipping testing values of plate_type and registration_date here, since it is tested via Facility class / RSpec end