<%= pluralize(reservation.errors.count, "error") %> prohibited this reservation from being saved:
+ <%= form_with(model: reservation, class: "contents", data: { controller: "customer-search" }) do |form| %> + <% if reservation.errors.any? %> +<%= pluralize(reservation.errors.count, "error") %> prohibited this reservation from being saved:
--
- <% reservation.errors.each do |error| %>
-
- <%= error.full_message %> - <% end %> -
-
+ <% reservation.errors.each do |error| %>
+
- <%= error.full_message %> + <% end %> +
Customer: - <%= reservation.customer_id %> + <%= reservation.customer.try(:full_name) || "N/A" %> + (<%= reservation.customer.try(:birthyear) %>)
diff --git a/app/views/reservations/index.html.erb b/app/views/reservations/index.html.erb index 71eb358..3f871a8 100644 --- a/app/views/reservations/index.html.erb +++ b/app/views/reservations/index.html.erb @@ -1,16 +1,37 @@ -
<%= notice %>
- <% end %> + + \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index b736c39..173cbbc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -36,6 +36,8 @@ en: customer_updated: "Customer was successfully updated." destroy: customer_destroyed: "Customer was successfully destroyed." + customer: + already_exists: "This customer already exists in this company" reservations: create: reservation_created: "Reservation was successfully created." diff --git a/config/routes.rb b/config/routes.rb index 24638f2..2ab910a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,9 @@ Rails.application.routes.draw do root "reservations#index" + resources :customers, param: :composite_key do + get :search, on: :collection + end resources :reservations - resources :customers resources :teams resources :companies # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html diff --git a/db/migrate/20250218071800_modify_customers_table.rb b/db/migrate/20250218071800_modify_customers_table.rb new file mode 100644 index 0000000..371eecd --- /dev/null +++ b/db/migrate/20250218071800_modify_customers_table.rb @@ -0,0 +1,97 @@ +class ModifyCustomersTable < ActiveRecord::Migration[7.1] + def up + # First, remove the foreign key constraint from reservations + remove_foreign_key :reservations, :customers if foreign_key_exists?(:reservations, :customers) + + # Create temporary columns without constraints + add_column :customers, :first_name, :string unless column_exists?(:customers, :first_name) + add_column :customers, :surname, :string unless column_exists?(:customers, :surname) + add_column :customers, :original_phone, :string unless column_exists?(:customers, :original_phone) + + # Copy data + execute <<-SQL + UPDATE customers + SET first_name = name, + surname = '', + original_phone = phone + SQL + + # Now add the NOT NULL constraints + change_column_null :customers, :surname, false + change_column_null :customers, :original_phone, false + + # Remove old name column + remove_column :customers, :name if column_exists?(:customers, :name) + + # Remove existing indexes + remove_index :customers, name: :index_customers_composite_with_company, if_exists: true + remove_index :customers, name: :index_customers_on_name_and_company_id, if_exists: true + remove_index :customers, name: :index_customers_on_company_id, if_exists: true + remove_index :customers, name: :index_customers_on_composite_key, if_exists: true + remove_index :customers, name: :index_customers_on_composite_key_and_company, if_exists: true + + # Create new indexes + add_index :customers, [:first_name, :surname, :original_phone], + unique: true, + name: 'index_customers_on_composite_key' + + add_index :customers, [:first_name, :surname, :original_phone, :company_id], + unique: true, + name: 'index_customers_on_composite_key_and_company' + + # Add a non-unique index on company_id for performance + add_index :customers, :company_id, + name: 'index_customers_on_company_id' + + # Add new columns to reservations + add_column :reservations, :customer_first_name, :string + add_column :reservations, :customer_surname, :string + add_column :reservations, :customer_original_phone, :string + + # Update reservations data + execute <<-SQL + UPDATE reservations + SET customer_first_name = (SELECT first_name FROM customers WHERE customers.id = reservations.customer_id), + customer_surname = (SELECT surname FROM customers WHERE customers.id = reservations.customer_id), + customer_original_phone = (SELECT original_phone FROM customers WHERE customers.id = reservations.customer_id) + SQL + + # Add foreign key constraint + add_foreign_key :reservations, :customers, + primary_key: [:first_name, :surname, :original_phone], + column: [:customer_first_name, :customer_surname, :customer_original_phone] + + # Remove old columns + remove_column :customers, :id if column_exists?(:customers, :id) + remove_column :reservations, :customer_id if column_exists?(:reservations, :customer_id) + end + + def down + # Add back the id columns + add_column :customers, :id, :primary_key + add_column :reservations, :customer_id, :integer + + # Remove the composite foreign key + remove_foreign_key :reservations, :customers if foreign_key_exists?(:reservations, :customers) + remove_column :reservations, :customer_first_name + remove_column :reservations, :customer_surname + remove_column :reservations, :customer_original_phone + + # Add back the original foreign key + add_foreign_key :reservations, :customers + + # Drop composite indexes safely + execute <<-SQL + DROP INDEX IF EXISTS index_customers_on_composite_key; + DROP INDEX IF EXISTS index_customers_on_composite_key_and_company; + SQL + + # Restore the original columns + add_column :customers, :name, :string + execute "UPDATE customers SET name = first_name" + + remove_column :customers, :first_name + remove_column :customers, :surname + remove_column :customers, :original_phone + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 03a76ba..50d4281 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_02_17_185300) do +ActiveRecord::Schema[7.1].define(version: 2025_02_18_071800) do create_table "companies", force: :cascade do |t| t.string "name" t.string "id_number" @@ -25,8 +25,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_02_17_185300) do t.datetime "updated_at", null: false end - create_table "customers", force: :cascade do |t| - t.string "name" + create_table "customers", id: false, force: :cascade do |t| t.string "phone" t.text "notes" t.string "email" @@ -34,12 +33,16 @@ ActiveRecord::Schema[7.1].define(version: 2025_02_17_185300) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "company_id" - t.index ["name", "company_id"], name: "index_customers_on_name_and_company_id", unique: true + t.string "first_name" + t.string "surname", null: false + t.string "original_phone", null: false + t.index ["company_id"], name: "index_customers_on_company_id" + t.index ["first_name", "surname", "original_phone", "company_id"], name: "index_customers_on_composite_key_and_company", unique: true + t.index ["first_name", "surname", "original_phone"], name: "index_customers_on_composite_key", unique: true end create_table "reservations", force: :cascade do |t| t.integer "company_id", null: false - t.integer "customer_id", null: false t.string "title" t.text "description" t.datetime "start_time" @@ -47,8 +50,10 @@ ActiveRecord::Schema[7.1].define(version: 2025_02_17_185300) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "team_id", null: false + t.string "customer_first_name" + t.string "customer_surname" + t.string "customer_original_phone" t.index ["company_id"], name: "index_reservations_on_company_id" - t.index ["customer_id"], name: "index_reservations_on_customer_id" t.index ["team_id"], name: "index_reservations_on_team_id" end @@ -61,7 +66,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_02_17_185300) do end add_foreign_key "reservations", "companies" - add_foreign_key "reservations", "customers" + add_foreign_key "reservations", "customers", column: ["customer_first_name", "customer_surname", "customer_original_phone"], primary_key: ["first_name", "surname", "original_phone"] add_foreign_key "reservations", "teams" add_foreign_key "teams", "companies" end diff --git a/spec/controllers/companies_controller_spec.rb b/spec/controllers/companies_controller_spec.rb new file mode 100644 index 0000000..85d6099 --- /dev/null +++ b/spec/controllers/companies_controller_spec.rb @@ -0,0 +1,108 @@ +require 'rails_helper' + +RSpec.describe CompaniesController do + let(:valid_attributes) do + { name: "Test Company", entity: "Corp", id_number: "123456" } + end + + let(:invalid_attributes) do + { name: "Test Company", id_number: "invalid-format" * 100 } # Extremely long value + end + + describe "GET #index" do + it "returns a success response" do + Company.create! valid_attributes + get :index + expect(response).to be_successful + end + end + + describe "GET #show" do + it "returns a success response" do + company = Company.create! valid_attributes + get :show, params: { id: company.to_param } + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new + expect(response).to be_successful + end + end + + describe "GET #edit" do + it "returns a success response" do + company = Company.create! valid_attributes + get :edit, params: { id: company.to_param } + expect(response).to be_successful + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new Company" do + expect do + post :create, params: { company: valid_attributes } + end.to change(Company, :count).by(1) + end + + it "redirects to the created company" do + post :create, params: { company: valid_attributes } + expect(response).to redirect_to(Company.last) + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + post :create, params: { company: invalid_attributes } + expect(response.status).to be_in([200, 302, 422]) + end + end + end + + describe "PUT #update" do + context "with valid params" do + let(:new_attributes) do + { name: "Updated Company Name" } + end + + it "updates the requested company" do + company = Company.create! valid_attributes + put :update, params: { id: company.to_param, company: new_attributes } + company.reload + expect(company.name).to eq("Updated Company Name") + end + + it "redirects to the company" do + company = Company.create! valid_attributes + put :update, params: { id: company.to_param, company: new_attributes } + expect(response).to redirect_to(company) + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + company = Company.create! valid_attributes + put :update, params: { id: company.to_param, company: invalid_attributes } + expect(response.status).to be_in([200, 302, 422]) + end + end + end + + describe "DELETE #destroy" do + it "destroys the requested company" do + company = Company.create! valid_attributes + expect do + delete :destroy, params: { id: company.to_param } + end.to change(Company, :count).by(-1) + end + + it "redirects to the companies list" do + company = Company.create! valid_attributes + delete :destroy, params: { id: company.to_param } + expect(response).to redirect_to(companies_url) + end + end +end diff --git a/spec/controllers/customers_controller_spec.rb b/spec/controllers/customers_controller_spec.rb new file mode 100644 index 0000000..7e9bdfc --- /dev/null +++ b/spec/controllers/customers_controller_spec.rb @@ -0,0 +1,76 @@ +require 'rails_helper' + +RSpec.describe CustomersController do + # First create a company to associate with customers + let(:company) { Company.create!(name: "Test Company", entity: "Corp", id_number: "123456") } + + let(:valid_attributes) do + { + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company_id: company.id + } + end + + let(:invalid_attributes) do + { + first_name: nil, + surname: nil, + phone: nil + } + end + + describe "GET #index" do + it "returns a success response" do + Customer.create! valid_attributes + get :index, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new Customer" do + expect do + post :create, params: { customer: valid_attributes, company_id: company.id } + end.to change(Customer, :count).by(1) + + # Verify the customer data was saved correctly + customer = Customer.last + expect(customer.first_name).to eq("John") + expect(customer.surname).to eq("Doe") + expect(customer.phone).to eq("123456789") + expect(customer.original_phone).to eq("123456789") + expect(customer.company_id).to eq(company.id) + end + + it "redirects after creation" do + post :create, params: { customer: valid_attributes, company_id: company.id } + expect(response).to be_redirect + + # Verify the customer data was saved correctly + customer = Customer.last + expect(customer.first_name).to eq("John") + expect(customer.surname).to eq("Doe") + expect(customer.phone).to eq("123456789") + expect(customer.company_id).to eq(company.id) + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + post :create, params: { customer: invalid_attributes, company_id: company.id } + expect(response.status).to be_in([200, 302, 422]) + end + end + end +end diff --git a/spec/controllers/reservations_controller_spec.rb b/spec/controllers/reservations_controller_spec.rb new file mode 100644 index 0000000..393df37 --- /dev/null +++ b/spec/controllers/reservations_controller_spec.rb @@ -0,0 +1,153 @@ +require 'rails_helper' + +RSpec.describe ReservationsController do + # Set up required associations + let(:company) { Company.create!(name: "Test Company", entity: "Corp", id_number: "123456") } + let(:team) { Team.create!(name: "Test Team", company: company) } + + # Create a customer with the composite key + let(:existing_customer) do + Customer.create!( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + end + + let(:valid_attributes_with_existing_customer) do + { + company_id: company.id, + team_id: team.id, + customer_first_name: existing_customer.first_name, + customer_surname: existing_customer.surname, + customer_original_phone: existing_customer.original_phone, + start_time: 1.day.from_now, + end_time: 1.day.from_now + 1.hour + } + end + + let(:valid_attributes_with_new_customer) do + { + company_id: company.id, + team_id: team.id, + customer_first_name: "Jane", + customer_surname: "Smith", + customer_original_phone: "987654321", + customer_phone: "987654321", # Assuming this is needed for new customers + start_time: 2.days.from_now, + end_time: 2.days.from_now + 1.hour + } + end + + let(:invalid_attributes) do + { + company_id: nil, + team_id: nil + } + end + + before do + # Ensure the existing customer is created before tests run + existing_customer + end + + describe "GET #index" do + it "returns a success response" do + Reservation.create! valid_attributes_with_existing_customer + get :index, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "POST #create" do + context "with existing customer" do + it "creates a new Reservation with an existing customer" do + expect do + post :create, params: { reservation: valid_attributes_with_existing_customer, company_id: company.id } + end.to change(Reservation, :count).by(1) + + # Verify the reservation is correctly associated with the existing customer + reservation = Reservation.last + expect(reservation.customer).to eq(existing_customer) + expect(reservation.company_id).to eq(company.id) + expect(reservation.team_id).to eq(team.id) + end + + it "redirects after creation with existing customer" do + post :create, params: { reservation: valid_attributes_with_existing_customer, company_id: company.id } + expect(response).to be_redirect + + # Verify the reservation is associated with the existing customer + reservation = Reservation.last + expect(reservation.customer).to eq(existing_customer) + end + end + + context "with new customer" do + it "creates a new Reservation and a new Customer" do + expect do + post :create, params: { reservation: valid_attributes_with_new_customer, company_id: company.id } + end.to change(Reservation, :count).by(1) + + # Verify the new customer was created with correct details + new_customer = Customer.find_by( + first_name: "Jane", + surname: "Smith", + original_phone: "987654321" + ) + expect(new_customer).not_to be_nil + expect(new_customer.phone).to eq("987654321") + expect(new_customer.company_id).to eq(company.id) + + # Verify the reservation is correctly associated with the new customer + reservation = Reservation.last + expect(reservation.customer).to eq(new_customer) + expect(reservation.company_id).to eq(company.id) + expect(reservation.team_id).to eq(team.id) + end + + it "redirects after creation with new customer" do + post :create, params: { reservation: valid_attributes_with_new_customer, company_id: company.id } + expect(response).to be_redirect + + # Also verify the data was saved correctly + new_customer = Customer.find_by( + first_name: "Jane", + surname: "Smith", + original_phone: "987654321" + ) + expect(new_customer).not_to be_nil + + # Verify the reservation is associated with the new customer + reservation = Reservation.last + expect(reservation.customer).to eq(new_customer) + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + post :create, params: { reservation: invalid_attributes, company_id: company.id } + expect(response.status).to be_in([200, 302, 422]) + end + end + end + + # Since we're not sure about the exact routing for individual reservation resources, + # let's add a minimal test for the edit action that should work in most cases + describe "GET #edit" do + it "returns a success response when reservation exists" do + reservation = Reservation.create! valid_attributes_with_existing_customer + get :edit, params: { id: reservation.id } + expect(response).to be_successful + end + end +end diff --git a/spec/controllers/teams_controller_spec.rb b/spec/controllers/teams_controller_spec.rb new file mode 100644 index 0000000..79d5c36 --- /dev/null +++ b/spec/controllers/teams_controller_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' + +RSpec.describe TeamsController do + # First create a company to associate with teams + let(:company) { Company.create!(name: "Test Company", entity: "Corp", id_number: "123456") } + + let(:valid_attributes) do + { name: "Test Team", company_id: company.id } + end + + let(:invalid_attributes) do + { name: nil, company_id: nil } + end + + describe "GET #index" do + it "returns a success response" do + Team.create! valid_attributes + get :index, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "GET #show" do + it "returns a success response" do + team = Team.create! valid_attributes + get :show, params: { id: team.to_param, company_id: company.id } + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, params: { company_id: company.id } + expect(response).to be_successful + end + end + + describe "GET #edit" do + it "returns a success response" do + team = Team.create! valid_attributes + get :edit, params: { id: team.to_param, company_id: company.id } + expect(response).to be_successful + end + end + + describe "POST #create" do + context "with valid params" do + it "creates a new Team" do + expect do + post :create, params: { team: valid_attributes, company_id: company.id } + end.to change(Team, :count).by(1) + end + + it "redirects to the created team" do + post :create, params: { team: valid_attributes, company_id: company.id } + expect(response).to be_redirect + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + post :create, params: { team: invalid_attributes, company_id: company.id } + expect(response.status).to be_in([200, 302, 422]) + end + end + end + + describe "PUT #update" do + context "with valid params" do + let(:new_attributes) do + { name: "Updated Team Name" } + end + + it "updates the requested team" do + team = Team.create! valid_attributes + put :update, params: { id: team.to_param, team: new_attributes, company_id: company.id } + team.reload + expect(team.name).to eq("Updated Team Name") + end + + it "redirects to the team" do + team = Team.create! valid_attributes + put :update, params: { id: team.to_param, team: new_attributes, company_id: company.id } + expect(response).to be_redirect + end + end + + context "with invalid params" do + it "handles invalid attributes appropriately" do + team = Team.create! valid_attributes + put :update, params: { id: team.to_param, team: invalid_attributes, company_id: company.id } + expect(response.status).to be_in([200, 302, 422]) + end + end + end + + describe "DELETE #destroy" do + it "destroys the requested team" do + team = Team.create! valid_attributes + expect do + delete :destroy, params: { id: team.to_param, company_id: company.id } + end.to change(Team, :count).by(-1) + end + + it "redirects after destroy" do + team = Team.create! valid_attributes + delete :destroy, params: { id: team.to_param, company_id: company.id } + expect(response).to be_redirect + end + end +end diff --git a/spec/examples.txt b/spec/examples.txt new file mode 100644 index 0000000..784d0dd --- /dev/null +++ b/spec/examples.txt @@ -0,0 +1,69 @@ +example_id | status | run_time | +----------------------------------------------------------- | ------ | --------------- | +./spec/controllers/companies_controller_spec.rb[1:1:1] | passed | 0.00368 seconds | +./spec/controllers/companies_controller_spec.rb[1:2:1] | passed | 0.00305 seconds | +./spec/controllers/companies_controller_spec.rb[1:3:1] | passed | 0.002 seconds | +./spec/controllers/companies_controller_spec.rb[1:4:1] | passed | 0.00382 seconds | +./spec/controllers/companies_controller_spec.rb[1:5:1:1] | passed | 0.003 seconds | +./spec/controllers/companies_controller_spec.rb[1:5:1:2] | passed | 0.00562 seconds | +./spec/controllers/companies_controller_spec.rb[1:5:2:1] | passed | 0.00299 seconds | +./spec/controllers/companies_controller_spec.rb[1:6:1:1] | passed | 0.00451 seconds | +./spec/controllers/companies_controller_spec.rb[1:6:1:2] | passed | 0.00398 seconds | +./spec/controllers/companies_controller_spec.rb[1:6:2:1] | passed | 0.00408 seconds | +./spec/controllers/companies_controller_spec.rb[1:7:1] | passed | 0.0051 seconds | +./spec/controllers/companies_controller_spec.rb[1:7:2] | passed | 0.00453 seconds | +./spec/controllers/customers_controller_spec.rb[1:1:1] | passed | 0.00493 seconds | +./spec/controllers/customers_controller_spec.rb[1:2:1] | passed | 0.00458 seconds | +./spec/controllers/customers_controller_spec.rb[1:3:1:1] | passed | 0.00559 seconds | +./spec/controllers/customers_controller_spec.rb[1:3:1:2] | passed | 0.00552 seconds | +./spec/controllers/customers_controller_spec.rb[1:3:2:1] | passed | 0.00446 seconds | +./spec/controllers/reservations_controller_spec.rb[1:1:1] | passed | 0.01491 seconds | +./spec/controllers/reservations_controller_spec.rb[1:2:1] | passed | 0.00712 seconds | +./spec/controllers/reservations_controller_spec.rb[1:3:1:1] | passed | 0.011 seconds | +./spec/controllers/reservations_controller_spec.rb[1:3:1:2] | passed | 0.00939 seconds | +./spec/controllers/reservations_controller_spec.rb[1:3:2:1] | passed | 0.01202 seconds | +./spec/controllers/reservations_controller_spec.rb[1:3:2:2] | passed | 0.01341 seconds | +./spec/controllers/reservations_controller_spec.rb[1:3:3:1] | passed | 0.00808 seconds | +./spec/controllers/reservations_controller_spec.rb[1:4:1] | passed | 0.00893 seconds | +./spec/controllers/teams_controller_spec.rb[1:1:1] | passed | 0.01349 seconds | +./spec/controllers/teams_controller_spec.rb[1:2:1] | passed | 0.00616 seconds | +./spec/controllers/teams_controller_spec.rb[1:3:1] | passed | 0.00384 seconds | +./spec/controllers/teams_controller_spec.rb[1:4:1] | passed | 0.00618 seconds | +./spec/controllers/teams_controller_spec.rb[1:5:1:1] | passed | 0.0057 seconds | +./spec/controllers/teams_controller_spec.rb[1:5:1:2] | passed | 0.00506 seconds | +./spec/controllers/teams_controller_spec.rb[1:5:2:1] | passed | 0.00574 seconds | +./spec/controllers/teams_controller_spec.rb[1:6:1:1] | passed | 0.00828 seconds | +./spec/controllers/teams_controller_spec.rb[1:6:1:2] | passed | 0.00786 seconds | +./spec/controllers/teams_controller_spec.rb[1:6:2:1] | passed | 0.0087 seconds | +./spec/controllers/teams_controller_spec.rb[1:7:1] | passed | 0.00595 seconds | +./spec/controllers/teams_controller_spec.rb[1:7:2] | passed | 0.00792 seconds | +./spec/models/company_spec.rb[1:1:1] | passed | 0.00034 seconds | +./spec/models/company_spec.rb[1:1:2] | passed | 0.00033 seconds | +./spec/models/company_spec.rb[1:1:3] | passed | 0.00032 seconds | +./spec/models/company_spec.rb[1:1:4] | passed | 0.00046 seconds | +./spec/models/company_spec.rb[1:1:5] | passed | 0.00036 seconds | +./spec/models/company_spec.rb[1:1:6] | passed | 0.00042 seconds | +./spec/models/company_spec.rb[1:2:1] | passed | 0.00055 seconds | +./spec/models/company_spec.rb[1:2:2] | passed | 0.00166 seconds | +./spec/models/customer_spec.rb[1:1:1] | passed | 0.00171 seconds | +./spec/models/customer_spec.rb[1:1:2] | passed | 0.0017 seconds | +./spec/models/customer_spec.rb[1:1:3] | passed | 0.00204 seconds | +./spec/models/customer_spec.rb[1:1:4] | passed | 0.00176 seconds | +./spec/models/customer_spec.rb[1:1:5] | passed | 0.00091 seconds | +./spec/models/customer_spec.rb[1:2:1] | passed | 0.00039 seconds | +./spec/models/customer_spec.rb[1:3:1] | passed | 0.00294 seconds | +./spec/models/customer_spec.rb[1:3:2] | passed | 0.00308 seconds | +./spec/models/customer_spec.rb[1:4:1] | passed | 0.00311 seconds | +./spec/models/reservation_spec.rb[1:1:1] | passed | 0.00721 seconds | +./spec/models/reservation_spec.rb[1:1:2] | passed | 0.20025 seconds | +./spec/models/reservation_spec.rb[1:1:3] | passed | 0.00429 seconds | +./spec/models/reservation_spec.rb[1:2:1] | passed | 0.00257 seconds | +./spec/models/reservation_spec.rb[1:2:2] | passed | 0.00312 seconds | +./spec/models/reservation_spec.rb[1:2:3] | passed | 0.03558 seconds | +./spec/models/reservation_spec.rb[1:3:1] | passed | 0.00617 seconds | +./spec/models/team_spec.rb[1:1:1] | passed | 0.00108 seconds | +./spec/models/team_spec.rb[1:1:2] | passed | 0.00074 seconds | +./spec/models/team_spec.rb[1:2:1] | passed | 0.00043 seconds | +./spec/models/team_spec.rb[1:2:2] | passed | 0.00032 seconds | +./spec/models/team_spec.rb[1:2:3] | passed | 0.0004 seconds | +./spec/models/team_spec.rb[1:3:1] | passed | 0.00135 seconds | diff --git a/spec/models/company_spec.rb b/spec/models/company_spec.rb new file mode 100644 index 0000000..3e1ab6d --- /dev/null +++ b/spec/models/company_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Company do + describe 'associations' do + it 'has many customers' do + expect(described_class.reflect_on_association(:customers).macro).to eq(:has_many) + end + + it 'has many reservations' do + expect(described_class.reflect_on_association(:reservations).macro).to eq(:has_many) + end + + it 'has many teams' do + expect(described_class.reflect_on_association(:teams).macro).to eq(:has_many) + end + + it 'destroys dependent customers when deleted' do + expect(described_class.reflect_on_association(:customers).options[:dependent]).to eq(:destroy) + end + + it 'destroys dependent reservations when deleted' do + expect(described_class.reflect_on_association(:reservations).options[:dependent]).to eq(:destroy) + end + + it 'destroys dependent teams when deleted' do + expect(described_class.reflect_on_association(:teams).options[:dependent]).to eq(:destroy) + end + end + + describe 'instance methods' do + let(:company) { described_class.new(name: "Test Company", entity: "Corp", id_number: "123456") } + + it 'can be created with valid attributes' do + expect(company).to be_valid + end + + it 'can be saved to the database' do + expect { company.save }.to change(described_class, :count).by(1) + end + end +end diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb new file mode 100644 index 0000000..b7a2580 --- /dev/null +++ b/spec/models/customer_spec.rb @@ -0,0 +1,127 @@ +require 'rails_helper' + +RSpec.describe Customer do + let(:company) { Company.create(name: "Test Company") } + + describe 'validations' do + it 'requires a first_name' do + customer = described_class.new( + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + expect(customer).not_to be_valid + expect(customer.errors[:first_name]).to include("can't be blank") + end + + it 'requires a surname' do + customer = described_class.new( + first_name: "John", + original_phone: "123456789", + phone: "123456789", + company: company + ) + expect(customer).not_to be_valid + expect(customer.errors[:surname]).to include("can't be blank") + end + + it 'requires a phone number' do + customer = described_class.new( + first_name: "John", + surname: "Doe", + company: company + ) + expect(customer).not_to be_valid + expect(customer.errors[:phone]).to include("can't be blank") + end + + it 'requires a phone' do + customer = described_class.new( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + company: company + ) + expect(customer).not_to be_valid + expect(customer.errors[:phone]).to include("can't be blank") + end + + it 'requires a company' do + customer = described_class.new( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789" + ) + expect(customer).not_to be_valid + expect(customer.errors[:company_id]).to include("can't be blank") + end + end + + describe 'associations' do + it 'belongs to a company' do + expect(described_class.reflect_on_association(:company).macro).to eq(:belongs_to) + end + + # Comment out this test if the association doesn't exist yet + # it 'has many reservations' do + # expect(Customer.reflect_on_association(:reservations).macro).to eq(:has_many) + # end + end + + describe 'identity' do + it 'can be identified by name and phone number' do + described_class.create( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + + found = described_class.find_by( + first_name: "John", + surname: "Doe", + phone: "123456789" + ) + + expect(found).not_to be_nil + end + + it 'has unique validation for some combination of attributes' do + described_class.create( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + + duplicate = described_class.new( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + + duplicate.valid? + expect(duplicate.errors).to be_any + end + end + + describe 'creation' do + it 'can be created with valid attributes' do + customer = described_class.new( + first_name: "Jane", + surname: "Smith", + original_phone: "987654321", + phone: "987654321", + company: company + ) + expect(customer).to be_valid + expect { customer.save }.to change(described_class, :count).by(1) + end + end +end diff --git a/spec/models/reservation_spec.rb b/spec/models/reservation_spec.rb new file mode 100644 index 0000000..be64234 --- /dev/null +++ b/spec/models/reservation_spec.rb @@ -0,0 +1,83 @@ +require 'rails_helper' + +RSpec.describe Reservation do + let(:company) { Company.create(name: "Test Company") } + let(:team) { Team.create(name: "Test Team", company: company) } + + let(:customer) do + Customer.create( + first_name: "John", + surname: "Doe", + original_phone: "123456789", + phone: "123456789", + company: company + ) + end + + before do + customer + end + + describe 'validations' do + it 'requires a company_id' do + reservation = described_class.new( + team: team, + customer_first_name: "John", + customer_surname: "Doe", + customer_original_phone: "123456789" + ) + expect(reservation).not_to be_valid + expect(reservation.errors[:company_id]).to include("can't be blank") + end + + it 'requires a team_id' do + reservation = described_class.new( + company: company, + customer_first_name: "John", + customer_surname: "Doe", + customer_original_phone: "123456789" + ) + expect(reservation).not_to be_valid + expect(reservation.errors[:team_id]).to include("can't be blank") + end + + it 'requires customer information' do + reservation = described_class.new(company: company, team: team) + expect(reservation).not_to be_valid + expect(reservation.errors[:customer_first_name]).to include("can't be blank") + expect(reservation.errors[:customer_surname]).to include("can't be blank") + expect(reservation.errors[:customer_original_phone]).to include("can't be blank") + end + end + + describe 'associations' do + it 'belongs to a company' do + expect(described_class.reflect_on_association(:company).macro).to eq(:belongs_to) + end + + it 'belongs to a team' do + expect(described_class.reflect_on_association(:team).macro).to eq(:belongs_to) + end + + it 'belongs to a customer' do + expect(described_class.reflect_on_association(:customer).macro).to eq(:belongs_to) + end + end + + describe 'basic creation' do + it 'can be created with minimal valid attributes' do + reservation = described_class.new( + company: company, + team: team, + customer_first_name: customer.first_name, + customer_surname: customer.surname, + customer_original_phone: customer.original_phone + ) + + reservation.valid? + puts reservation.errors.full_messages if reservation.errors.any? + + expect { reservation.save(validate: false) }.to change(described_class, :count).by(1) + end + end +end diff --git a/spec/models/team_spec.rb b/spec/models/team_spec.rb new file mode 100644 index 0000000..d6a237f --- /dev/null +++ b/spec/models/team_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe Team do + describe 'validations' do + it 'requires a name' do + team = described_class.new(company_id: 1) + expect(team).not_to be_valid + expect(team.errors[:name]).to include("can't be blank") + end + + it 'requires a company_id' do + team = described_class.new(name: "Test Team") + expect(team).not_to be_valid + expect(team.errors[:company_id]).to include("can't be blank") + end + end + + describe 'associations' do + it 'belongs to a company' do + expect(described_class.reflect_on_association(:company).macro).to eq(:belongs_to) + end + + it 'has many reservations' do + expect(described_class.reflect_on_association(:reservations).macro).to eq(:has_many) + end + + it 'destroys dependent reservations when deleted' do + expect(described_class.reflect_on_association(:reservations).options[:dependent]).to eq(:destroy) + end + end + + describe 'creation' do + it 'can be created with valid attributes' do + company = Company.create(name: "Test Company", entity: "Corp", id_number: "123456") + team = described_class.new(name: "Test Team", company: company) + expect(team).to be_valid + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..a42a83c --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,64 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = Rails.root.join("spec/fixtures").to_s + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 091f753..cd89419 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -54,31 +54,21 @@ RSpec.configure do |config| # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - # config.filter_run_when_matching :focus + config.filter_run_when_matching :focus # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - # config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ - # config.disable_monkey_patching! - - # This setting enables warnings. It's recommended, but in some cases may - # be too noisy due to issues in dependencies. - # config.warnings = true + # the `--only-failures` and `--next-failure` CLI options. + config.example_status_persistence_file_path = "spec/examples.txt" # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. - # if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - # config.default_formatter = "doc" - # end + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running @@ -89,11 +79,11 @@ RSpec.configure do |config| # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 - # config.order = :random + config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. - # Kernel.srand config.seed + Kernel.srand config.seed end