From 1291707dbfecee1e67e3900b503072da4f0c738f Mon Sep 17 00:00:00 2001 From: Ana Ulin Date: Wed, 8 Dec 2021 17:32:03 -0800 Subject: [PATCH] Add new `status` attribute to DoorCode, and remove obsolete door-code-related code (#596) * Add 'status' enum field to DoorCode. * Remove enabled field on DoorCode, remove requirement that a code always be assigned to a user, and delete obsolete doorbell controller code. * Add `blacklisted` DoorCode status. --- app/controllers/admin/door_code_controller.rb | 23 +-- app/controllers/doorbell_controller.rb | 122 ------------ app/models/door_code.rb | 49 +++-- app/models/user.rb | 2 +- .../admin/memberships/_members.html.haml | 14 -- app/views/members/users/index.html.haml | 4 +- config/routes.rb | 3 - ...20211206011624_add_status_to_door_codes.rb | 5 + ...06013540_remove_enabled_from_door_codes.rb | 5 + ...1206014929_make_door_code_user_optional.rb | 6 + db/schema.rb | 6 +- .../admin/door_code_controller_spec.rb | 74 +------ spec/controllers/doorbell_controller_spec.rb | 183 ------------------ spec/factories.rb | 5 +- spec/features/members_home_spec.rb | 6 - spec/models/door_code_spec.rb | 46 ++--- spec/models/user_spec.rb | 18 +- 17 files changed, 82 insertions(+), 489 deletions(-) delete mode 100644 app/controllers/doorbell_controller.rb create mode 100644 db/migrate/20211206011624_add_status_to_door_codes.rb create mode 100644 db/migrate/20211206013540_remove_enabled_from_door_codes.rb create mode 100644 db/migrate/20211206014929_make_door_code_user_optional.rb delete mode 100644 spec/controllers/doorbell_controller_spec.rb diff --git a/app/controllers/admin/door_code_controller.rb b/app/controllers/admin/door_code_controller.rb index 6c0883f0..95bac6be 100644 --- a/app/controllers/admin/door_code_controller.rb +++ b/app/controllers/admin/door_code_controller.rb @@ -1,26 +1,5 @@ class Admin::DoorCodeController < ApplicationController before_action :ensure_admin - def disable_door_code - door_code = DoorCode.find(params[:id]) - door_code.enabled = false - door_code.save! - flash[:message] = "#{door_code.user.name}'s door code is now disabled." - redirect_to admin_memberships_path - end - - def enable_door_code - door_code = DoorCode.find(params[:id]) - door_code.enabled = true - door_code.save! - flash[:message] = "#{door_code.user.name}'s door code is now enabled." - redirect_to admin_memberships_path - end - - def generate_new_for_user - user = User.find(params[:id]) - DoorCode.new_for_user(user) - flash[:message] = "A door code was generated for #{user.name}." - redirect_to admin_memberships_path - end + # TODO: add actions for new door code administration workflow end diff --git a/app/controllers/doorbell_controller.rb b/app/controllers/doorbell_controller.rb deleted file mode 100644 index 865615f2..00000000 --- a/app/controllers/doorbell_controller.rb +++ /dev/null @@ -1,122 +0,0 @@ -require "redis" -require "twilio-ruby" - -class DoorbellController < ApplicationController - - def welcome - response = Twilio::TwiML::VoiceResponse.new do |r| - name = use_authorized_member - if name - r.say message: "Welcome #{name}!", voice: 'alice' - r.play digits: '9' - else - r.say message: 'Due to the shelter in place order for the city of San Francisco, Double Union is closed.', voice: 'alice' - r.say message: 'Contact the Double Union board via email or slack if you have any questions.', voice: 'alice' - # r.say message: 'If you are a guest or a member without a keycode, stay on the line.', voice: 'alice' - # r.say message: 'If you are a member with a keycode, text it to 415-214-8843 and try again.', voice: 'alice' - # r.dial do |dial| - # dial.number('+14154890104') - # end - end - end - render xml: response.to_xml - end - - def sms - raise NoMethodError, 'Required doorcode was not provided' unless params['Body'].present? - - response = Twilio::TwiML::MessagingResponse.new do |r| - keycode = params['Body'].strip - member = get_user_by_code(keycode) - if member - record_authorized_member get_name_to_say(member) - r.message body: 'Access granted. Dial 111 on the keypad now to unlock the door.' - else - r.message body: 'Due to the shelter in place order for the city of San Francisco, Double Union is closed until April 7th. Email board@doubleunion.org if you have any questions.' - # r.message body: 'Invalid code. Please text your six-digit access code.' - end - end - render xml: response.to_xml - end - - def gather_ismember - # only 1 or 2 is a valid selection - redirect_to action: "welcome" and return unless %w[guest member].include?(params['SpeechResult']) - if params['SpeechResult'] == 'guest' - # they are a guest, just call the landline - response = redirect_call_response('Calling the landline.') - elsif params['SpeechResult'] == 'member' - # they are a member, ask for a code - response = Twilio::TwiML::VoiceResponse.new do |r| - r.gather(input: 'speech', hints: '1, 2, 3, 4, 5, 6, 7, 8, 9, 0', speechTimeout: 'auto', - speechModel: 'numbers_and_commands', action: doorbell_gather_keycode_path, method: 'get') do |g| - g.say message: 'Please say your Double Union keycode.', voice: 'alice' - end - end - end - render xml: response.to_xml - end - - def gather_keycode - keycode = params['SpeechResult'].gsub(' ','') - - member = get_user_by_code(keycode) - if member - # they are a key member, let them in - response = Twilio::TwiML::VoiceResponse.new do |r| - r.say message: "Welcome #{member.name}!", voice: 'alice' - r.play digits: '9' - end - else - # invalid code, try again - response = Twilio::TwiML::VoiceResponse.new do |r| - r.gather(input: 'speech', numDigits: '6', hints: '1, 2, 3, 4, 5, 6, 7, 8, 9, 0', - speechModel: 'numbers_and_commands', action: doorbell_gather_keycode_path, method: 'get') do |g| - g.say message: 'I’m sorry, I didn’t quite get that. Please say your code again.', voice: 'alice' - end - end - end - render xml: response.to_xml - end - - private - # records the last authorized member for 2 minutes, or until the door is opened - def record_authorized_member(member) - redis = Redis.new(url: ENV['REDIS_URL']) - redis.set('member', member, ex: 120) - end - - # fetches and deletes the last authorized member - def use_authorized_member - redis = Redis.new(url: ENV['REDIS_URL']) - member = redis.get('member') - redis.del('member') if member - member - end - - # returns a response object that redirects the call to the DU landline - def redirect_call_response(explanation) - response = Twilio::TwiML::VoiceResponse.new do |r| - r.say message: explanation, voice: 'alice' - r.dial number: '+14154890104' - r.say message: 'The call failed. Please try again.', voice: 'alice' - end - response - end - - # Gets a User object based on a keycode. - # Returns nil if no matching code was found. - def get_user_by_code(keycode) - door_code = DoorCode.enabled.find_by(code: keycode) - return nil unless door_code - - return door_code.user - end - - # @return [String] The user's pronounceable name, if they have one, or their full name. - def get_name_to_say(user) - return user.pronounceable_name if user.pronounceable_name - - user.name - end -end diff --git a/app/models/door_code.rb b/app/models/door_code.rb index b1dc6c18..7378c81e 100644 --- a/app/models/door_code.rb +++ b/app/models/door_code.rb @@ -1,32 +1,41 @@ class DoorCode < ApplicationRecord - belongs_to :user + # We don't need an "assigned to user" status: can use the user_id field to check if a code belongs to a user + enum status: { + # This code is not entered in the physical lock. + not_in_lock: 'not_in_lock', + # This code is entered in the physical lock. + in_lock: 'in_lock', + # This code was previously assigned to a user and is still available in the lock. + formerly_assigned_in_lock: 'formerly_assigned_in_lock', + # This code was previously assigned to a user, and is no longer in the lock. + # We keep such codes around to avoid code re-use. + formerly_assigned_not_in_lock: 'formerly_assigned_not_in_lock', + # Blacklisted (assumed to not be in the lock). + # Used for disallowing easily guessable codes. + blacklisted: 'blacklisted' + } - validates :user, presence: true - validates_uniqueness_of :user + # A code without a user is available for assigning to a member + belongs_to :user, optional: true validates :code, presence: true validates_uniqueness_of :code, case_sensitive: false - scope :enabled, -> { where(enabled: true) } - class << self - # @param user [User] User to assign the new doorcode to. - # @return [DoorCode] a newly created doorcode, assigned to the given user - def new_for_user(user) - # Find a new unused random code. - code = make_random_code() - while DoorCode.find_by(code: code) - code = make_random_code() - end - # Assign this random code to the user. - DoorCode.create!(code: code, user: user) - end - # @return [String] A randomly generated number of the requested length, as a string. May be zero-padded. - def make_random_code(digits: 6) + def make_random_code(digits: 7) (1..digits).map{ "0123456789".chars.to_a.sample }.join end end + + def is_assigned? + return user.present? + end + + def unassign + new_status = in_lock? ? :formerly_assigned_in_lock : :formerly_assigned_not_in_lock + update!(user: nil, status: new_status) + end end # == Schema Information @@ -35,10 +44,10 @@ def make_random_code(digits: 6) # # id :integer not null, primary key # code :string not null -# enabled :boolean default(FALSE), not null +# status :string default("not_in_lock"), not null # created_at :datetime # updated_at :datetime -# user_id :integer not null +# user_id :integer # # Indexes # diff --git a/app/models/user.rb b/app/models/user.rb index e33cf99e..576bf938 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -121,7 +121,7 @@ class User < ApplicationRecord end after_transition on: all - [:key_member] do |user, _| - user.door_code.update!(enabled: false) if user.door_code + user.door_code.unassign if user.door_code.present? end state :visitor diff --git a/app/views/admin/memberships/_members.html.haml b/app/views/admin/memberships/_members.html.haml index 82c935f0..a6645991 100644 --- a/app/views/admin/memberships/_members.html.haml +++ b/app/views/admin/memberships/_members.html.haml @@ -42,20 +42,6 @@ %td - if user.door_code = "Keycode: #{user.door_code.code}" - = user.door_code.enabled? ? "(enabled)" : "(disabled)" - - if user.door_code.enabled - = form_for user.door_code, url: admin_disable_door_code_path(user.door_code) do |f| - = f.hidden_field(:id) - = f.submit "Disable door code", class: "btn", data: { confirm: "Are you sure? This keycode will no longer work." } - - else - = form_for user.door_code, url: admin_enable_door_code_path(user.door_code) do |f| - = f.hidden_field(:id) - = f.submit "Enable door code", class: "btn", data: { confirm: "Are you sure? This keycode will be enabled." } - - else - = "Keycode not set." - = form_for user, url: admin_generate_door_code_path(user) do |f| - = f.hidden_field(:id) - = f.submit "Generate new door code", class: "btn", data: { confirm: "Do you want to generate a door code for #{user.name}?" } %td = form_for :user, url: admin_save_membership_note_path, remote: true, html: { class: "js-membership-note js-membership-note-#{user.id}" } do |f| diff --git a/app/views/members/users/index.html.haml b/app/views/members/users/index.html.haml index 578f8785..3dc98a67 100644 --- a/app/views/members/users/index.html.haml +++ b/app/views/members/users/index.html.haml @@ -10,7 +10,7 @@ - if current_user.key_member? - if current_user.door_code - %p Your door code is: #{current_user.door_code.code} #{current_user.door_code.enabled? ? "(enabled)" : "(disabled)"} + %p Your door code is: #{current_user.door_code.code} - else %p You don't seem to have a door code set. If you need one, contact @@ -19,7 +19,7 @@ - if @all_admins.any? %h3 Admins - %p This internal DU app is administered by Membership Coordinators (#{ mail_to MEMBERSHIP_EMAIL }) and Board Members (#{ mail_to BOARD_EMAIL }), and it may also have Web App committee members supporting it. Members with admin access: + %p This internal DU app is administered by Membership Coordinators (#{ mail_to MEMBERSHIP_EMAIL }) and Board Members (#{ mail_to BOARD_EMAIL }), and it may also have Web App committee members supporting it. Members with admin access: = render partial: 'list', locals: { users: @all_admins } - if @all_members.any? diff --git a/config/routes.rb b/config/routes.rb index 68f8d002..88f4cf90 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,9 +34,6 @@ patch "memberships/:id/change_membership_state" => "memberships#change_membership_state", :as => "change_membership_state" patch "memberships/:id/make_admin" => "memberships#make_admin", :as => "make_admin" patch "memberships/:id/unmake_admin" => "memberships#unmake_admin", :as => "unmake_admin" - patch "door_codes/:id/disable_door_code" => "door_code#disable_door_code", :as => "disable_door_code" - patch "door_codes/:id/enable_door_code" => "door_code#enable_door_code", :as => "enable_door_code" - patch "users/:id/generate_door_code" => "door_code#generate_new_for_user", :as => "generate_door_code" end get "admin/new_members" => "admin#new_members" diff --git a/db/migrate/20211206011624_add_status_to_door_codes.rb b/db/migrate/20211206011624_add_status_to_door_codes.rb new file mode 100644 index 00000000..8956e368 --- /dev/null +++ b/db/migrate/20211206011624_add_status_to_door_codes.rb @@ -0,0 +1,5 @@ +class AddStatusToDoorCodes < ActiveRecord::Migration[6.0] + def change + add_column :door_codes, :status, string, null: false, default: "not_in_lock" + end +end diff --git a/db/migrate/20211206013540_remove_enabled_from_door_codes.rb b/db/migrate/20211206013540_remove_enabled_from_door_codes.rb new file mode 100644 index 00000000..cf8a2c80 --- /dev/null +++ b/db/migrate/20211206013540_remove_enabled_from_door_codes.rb @@ -0,0 +1,5 @@ +class RemoveEnabledFromDoorCodes < ActiveRecord::Migration[6.0] + def change + remove_column :door_codes, :enabled, :boolean + end +end diff --git a/db/migrate/20211206014929_make_door_code_user_optional.rb b/db/migrate/20211206014929_make_door_code_user_optional.rb new file mode 100644 index 00000000..9dc80806 --- /dev/null +++ b/db/migrate/20211206014929_make_door_code_user_optional.rb @@ -0,0 +1,6 @@ +class MakeDoorCodeUserOptional < ActiveRecord::Migration[6.0] + def change + change_column_null :door_codes, :user_id, true + change_column_default :door_codes, :user_id, nil + end +end diff --git a/db/schema.rb b/db/schema.rb index b6a8dc7a..ad5bae9f 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.define(version: 2021_04_09_202422) do +ActiveRecord::Schema.define(version: 2021_12_06_014929) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -56,11 +56,11 @@ end create_table "door_codes", id: :serial, force: :cascade do |t| - t.integer "user_id", null: false + t.integer "user_id" t.string "code", null: false - t.boolean "enabled", default: false, null: false t.datetime "created_at" t.datetime "updated_at" + t.string "status", default: "not_in_lock", null: false t.index ["code"], name: "index_door_codes_on_code", unique: true t.index ["user_id"], name: "index_door_codes_on_user_id", unique: true end diff --git a/spec/controllers/admin/door_code_controller_spec.rb b/spec/controllers/admin/door_code_controller_spec.rb index adcba1b3..9dd8a0b0 100644 --- a/spec/controllers/admin/door_code_controller_spec.rb +++ b/spec/controllers/admin/door_code_controller_spec.rb @@ -8,77 +8,5 @@ let(:door_code) { create(:door_code) } let(:params) { { id: door_code.id } } - describe 'PATCH disable_door_code' do - subject { patch :disable_door_code, params: params } - - context 'logged in as a non-admin member' do - before { login_as(:member) } - - it 'redirects to root' do - subject - expect(response).to redirect_to :root - end - end - - context 'logged in as an admin' do - before { login_as(:voting_member, is_admin: true) } - - it 'disables the door code' do - door_code.update_attributes!(enabled: true) - subject - door_code.reload - expect(door_code.enabled).to be false - end - end - end - - describe 'PATCH enable_door_code' do - subject { patch :enable_door_code, params: params } - - context 'logged in as a non-admin member' do - before { login_as(:member) } - - it 'redirects to root' do - subject - expect(response).to redirect_to :root - end - end - - context 'logged in as an admin' do - before { login_as(:voting_member, is_admin: true) } - - it 'disables the door code' do - door_code.update_attributes!(enabled: false) - subject - door_code.reload - expect(door_code.enabled).to be true - end - end - end - - describe 'PATCH generate_new_for_user' do - let(:key_member) { create(:key_member) } - - subject { patch :generate_new_for_user, params: { id: key_member.id } } - - context 'logged in as a non-admin member' do - before { login_as(:member) } - - it 'redirects to root' do - subject - expect(response).to redirect_to :root - end - end - - context 'logged in as an admin' do - before { login_as(:voting_member, is_admin: true) } - - it 'creates a new door code for the user' do - expect(key_member.door_code).to be_nil - expect { subject }.to change { DoorCode.count }.by(1) - key_member.reload - expect(key_member.door_code).to_not be_nil - end - end - end + # TODO: add tests for new workflow actions, once they are added end diff --git a/spec/controllers/doorbell_controller_spec.rb b/spec/controllers/doorbell_controller_spec.rb deleted file mode 100644 index 32dade65..00000000 --- a/spec/controllers/doorbell_controller_spec.rb +++ /dev/null @@ -1,183 +0,0 @@ -require "spec_helper" - -describe DoorbellController do - let(:redis_double) { double(Redis) } - - before do - allow(Redis).to receive(:new).and_return(redis_double) - allow(redis_double).to receive(:set) - allow(redis_double).to receive(:del) - allow(redis_double).to receive(:get).and_return(nil) - end - - describe "#welcome" do - subject { get :welcome } - context "without a recently authorized member name" do - it "responds with a default welcome message" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.search("Say").count).to eq(2) - expect(xml.search("Say").first.text).to include("Double Union") - end - end - - context "with a recently authorized member" do - let!(:member_name) { "Ariel Jones" } - - before do - allow(redis_double).to receive(:get).and_return(member_name) - end - - it "welcomes the member" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.search("Say").first.text).to include(member_name) - end - - it "plays the digit 9" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Play").attribute("digits").value).to eq("9") - end - end - end - - describe "#sms" do - let(:door_code) { create(:door_code, enabled: true) } - let(:key_code) { door_code.code } - - subject { get :sms, params: { Body: key_code } } - - context "with an invalid keycode" do - let(:key_code) { "bogus_code" } - - it "responds with a generic message" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Message").text).to include("is closed") - end - - it "does not record any authorized member in Redis" do - expect(redis_double).not_to receive(:set) - subject - end - end - - context "with a valid keycode" do - it "records the authorized member's pronounceable name in Redis" do - door_code.user.update!(pronounceable_name: "Cee Jay") - expect(redis_double).to receive(:set).with("member", door_code.user.reload.pronounceable_name, ex: 120) - subject - end - - it "records the member name in Redis if they don't have a pronounceable name" do - door_code.user.update!(pronounceable_name: nil) - expect(redis_double).to receive(:set).with("member", door_code.user.name, ex: 120) - subject - end - - it "responds with a message to dial 111" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Message").text).to include("Dial 111") - end - end - - context "with a valid, but disabled keycode" do - let(:door_code) { create(:door_code, enabled: false)} - - it "responds with a generic message" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Message").text).to include("is closed") - end - - it "does not record any authorized member in Redis" do - expect(redis_double).not_to receive(:set) - subject - end - end - - context "without a doorcode" do - subject { get :sms } - - it "should raise a NoMethodError with clearer error message" do - expect { subject }.to raise_error.with_message(/Required doorcode was not provided/) - end - end - end - - describe "#gather_ismember" do - let(:selected_option) { "guest" } - subject { get :gather_ismember, params: { SpeechResult: selected_option } } - - context "with an invalid selection" do - let(:selected_option) { "bogus_stuff" } - - it "redirects to doorbell welcome message" do - expect(subject).to redirect_to(action: :welcome) - end - end - - context "with a guest selection" do - let(:selected_option) { "guest" } - - it "redirects to calling the landline" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Dial").text).not_to be_empty - end - end - - context "with a member selection" do - let(:selected_option) { "member" } - - it "redirects to the gather-keycode action" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Gather").attribute("action").value).to eq(doorbell_gather_keycode_path) - end - end - end - - describe "#gather_keycode" do - let(:door_code) { create(:door_code, enabled: true) } - let(:key_code) { door_code.code } - - subject { get :gather_keycode, params: { SpeechResult: key_code } } - - context "with valid keycode" do - it "welcomes the member" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.search("Say").first.text).to include(door_code.user.name) - end - - it "plays the digit 9" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Play").attribute("digits").value).to eq("9") - end - end - - context "with invalid keycode" do - let(:key_code) { "what is this i can't even" } - - it "redirects to gather keycode" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Gather").attribute("action").value).to eq(doorbell_gather_keycode_path) - end - end - - context "with disabled keycode" do - let(:door_code) { create(:door_code, enabled: false) } - - it "redirects to gather keycode" do - subject - xml = Nokogiri::XML(response.body) - expect(xml.at("Gather").attribute("action").value).to eq(doorbell_gather_keycode_path) - end - end - end -end diff --git a/spec/factories.rb b/spec/factories.rb index 77d08458..7882c6ee 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -110,7 +110,10 @@ end factory :door_code do - association :user, factory: :member sequence(:code) { |n| "#{1000+n}" } + + trait :assigned do + association :user, factory: :key_member + end end end diff --git a/spec/features/members_home_spec.rb b/spec/features/members_home_spec.rb index 57a8cdbd..4c7f964c 100644 --- a/spec/features/members_home_spec.rb +++ b/spec/features/members_home_spec.rb @@ -20,12 +20,6 @@ expect(page).to have_content "Your door code" expect(page).to have_content door_code.code end - - it "shows if the door code is disabled" do - door_code = create(:door_code, user: member, enabled: false) - visit members_root_path - expect(page).to have_content "#{door_code.code} (disabled)" - end end context "when logged in as a non-key-member" do diff --git a/spec/models/door_code_spec.rb b/spec/models/door_code_spec.rb index 5d4160c2..2a9e86a9 100644 --- a/spec/models/door_code_spec.rb +++ b/spec/models/door_code_spec.rb @@ -3,31 +3,9 @@ describe DoorCode do subject { create(:door_code) } - describe "associations" do - it { is_expected.to belong_to(:user) } - end - describe "validations" do it { is_expected.to validate_presence_of(:code) } it { is_expected.to validate_uniqueness_of(:code).case_insensitive } - it { is_expected.to validate_presence_of(:user) } - it { is_expected.to validate_uniqueness_of(:user).case_insensitive } - end - - describe ".new_for_user" do - let(:member) { create(:member) } - - it "returns a persisted door code" do - expect(DoorCode.new_for_user(member).persisted?).to eq(true) - end - - it "is assigned to the given user" do - expect(DoorCode.new_for_user(member).user).to eq(member) - end - - it "has a 6-digit code" do - expect(DoorCode.new_for_user(member).code.length).to eq(6) - end end describe ".make_random_code" do @@ -40,23 +18,25 @@ end end - describe "#enabled" do - it "is false by default" do + describe "#status" do + it "defaults to not_in_lock" do new_door_code = DoorCode.create!(user: create(:user), code: "12345") - expect(new_door_code.enabled).to be false + expect(new_door_code.not_in_lock?).to be true end end - describe ".enabled" do - let!(:disabled_code) { create(:door_code, enabled: false)} - let!(:enabled_code) { create(:door_code, enabled: true)} - - it "includes enabled doorcodes" do - expect(DoorCode.enabled).to include(enabled_code) + describe "#unassign" do + it "removes the association to a user" do + door_code = create(:door_code, :assigned) + expect { door_code.unassign }.to change { door_code.user }.to(nil) end - it "does not include disabled doorcodes" do - expect(DoorCode.enabled).to_not include(disabled_code) + it "updates the door code status" do + door_code = create(:door_code, :assigned, status: :in_lock) + expect { door_code.unassign }.to change { door_code.status }.to(:formerly_assigned_in_lock.to_s) + + door_code = create(:door_code, :assigned, status: :not_in_lock) + expect { door_code.unassign }.to change { door_code.status }.to(:formerly_assigned_not_in_lock.to_s) end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 47b35373..677c97f8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -71,11 +71,14 @@ expect { subject }.to change { member.state }.from("member").to("former_member") end - context "when member had enabled door code" do - let(:door_code) { create(:door_code, enabled: true, user: member) } + context "when member had a door code" do + let!(:door_code) { create(:door_code, user: member) } - it "disables the door code" do - expect { subject }.to change { door_code.enabled }.from(true).to(false) + it "unassigns the door code" do + subject + door_code.reload + expect(door_code.user_id).to eq(nil) + expect(door_code.is_assigned?).to be false end end end @@ -99,10 +102,13 @@ context "with a key member" do let(:member) { create(:key_member) } - let(:door_code) { create(:door_code, enabled: true, user: member) } + let!(:door_code) { create(:door_code, user: member) } it "disables their door code" do - expect { subject }.to change { door_code.enabled }.from(true).to(false) + subject + door_code.reload + expect(door_code.user_id).to eq(nil) + expect(door_code.is_assigned?).to be false end end end