user_spec.rb 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. require 'rails_helper'
  2. require 'models/application_model_examples'
  3. require 'models/concerns/has_groups_examples'
  4. require 'models/concerns/has_history_examples'
  5. require 'models/concerns/has_roles_examples'
  6. require 'models/concerns/has_groups_permissions_examples'
  7. require 'models/concerns/has_xss_sanitized_note_examples'
  8. require 'models/concerns/can_be_imported_examples'
  9. require 'models/concerns/has_object_manager_attributes_validation_examples'
  10. require 'models/user/has_ticket_create_screen_impact_examples'
  11. require 'models/user/can_lookup_search_index_attributes_examples'
  12. RSpec.describe User, type: :model do
  13. subject(:user) { create(:user) }
  14. let(:customer) { create(:customer_user) }
  15. let(:agent) { create(:agent_user) }
  16. let(:admin) { create(:admin_user) }
  17. it_behaves_like 'ApplicationModel', can_assets: { associations: :organization }
  18. it_behaves_like 'HasGroups', group_access_factory: :agent_user
  19. it_behaves_like 'HasHistory'
  20. it_behaves_like 'HasRoles', group_access_factory: :agent_user
  21. it_behaves_like 'HasXssSanitizedNote', model_factory: :user
  22. it_behaves_like 'HasGroups and Permissions', group_access_no_permission_factory: :user
  23. it_behaves_like 'CanBeImported'
  24. it_behaves_like 'HasObjectManagerAttributesValidation'
  25. it_behaves_like 'HasTicketCreateScreenImpact'
  26. it_behaves_like 'CanLookupSearchIndexAttributes'
  27. describe 'Class methods:' do
  28. describe '.authenticate' do
  29. subject(:user) { create(:user, password: password) }
  30. let(:password) { Faker::Internet.password }
  31. context 'with valid credentials' do
  32. context 'using #login' do
  33. it 'returns the matching user' do
  34. expect(described_class.authenticate(user.login, password))
  35. .to eq(user)
  36. end
  37. it 'is not case-sensitive' do
  38. expect(described_class.authenticate(user.login.upcase, password))
  39. .to eq(user)
  40. end
  41. end
  42. context 'using #email' do
  43. it 'returns the matching user' do
  44. expect(described_class.authenticate(user.email, password))
  45. .to eq(user)
  46. end
  47. it 'is not case-sensitive' do
  48. expect(described_class.authenticate(user.email.upcase, password))
  49. .to eq(user)
  50. end
  51. end
  52. context 'but exceeding failed login limit' do
  53. before { user.update(login_failed: 999) }
  54. it 'returns nil' do
  55. expect(described_class.authenticate(user.login, password))
  56. .to be(nil)
  57. end
  58. end
  59. context 'when previous login was' do
  60. context 'never' do
  61. it 'updates #last_login and #updated_at' do
  62. expect { described_class.authenticate(user.login, password) }
  63. .to change { user.reload.last_login }
  64. .and change { user.reload.updated_at }
  65. end
  66. end
  67. context 'less than 10 minutes ago' do
  68. before do
  69. described_class.authenticate(user.login, password)
  70. travel 9.minutes
  71. end
  72. it 'does not update #last_login and #updated_at' do
  73. expect { described_class.authenticate(user.login, password) }
  74. .to not_change { user.reload.last_login }
  75. .and not_change { user.reload.updated_at }
  76. end
  77. end
  78. context 'more than 10 minutes ago' do
  79. before do
  80. described_class.authenticate(user.login, password)
  81. travel 11.minutes
  82. end
  83. it 'updates #last_login and #updated_at' do
  84. expect { described_class.authenticate(user.login, password) }
  85. .to change { user.reload.last_login }
  86. .and change { user.reload.updated_at }
  87. end
  88. end
  89. end
  90. end
  91. context 'with valid user and invalid password' do
  92. it 'increments failed login count' do
  93. expect(described_class).to receive(:sleep).with(1)
  94. expect { described_class.authenticate(user.login, password.next) }
  95. .to change { user.reload.login_failed }.by(1)
  96. end
  97. it 'returns nil' do
  98. expect(described_class).to receive(:sleep).with(1)
  99. expect(described_class.authenticate(user.login, password.next)).to be(nil)
  100. end
  101. end
  102. context 'with inactive user’s login' do
  103. before { user.update(active: false) }
  104. it 'returns nil' do
  105. expect(described_class.authenticate(user.login, password)).to be(nil)
  106. end
  107. end
  108. context 'with non-existent user login' do
  109. it 'returns nil' do
  110. expect(described_class.authenticate('john.doe', password)).to be(nil)
  111. end
  112. end
  113. context 'with empty login string' do
  114. it 'returns nil' do
  115. expect(described_class.authenticate('', password)).to be(nil)
  116. end
  117. end
  118. context 'with empty password string' do
  119. it 'returns nil' do
  120. expect(described_class.authenticate(user.login, '')).to be(nil)
  121. end
  122. end
  123. context 'with empty password string when the stored password is an empty string' do
  124. before { user.update_column(:password, '') }
  125. context 'when password is an empty string' do
  126. it 'returns nil' do
  127. expect(described_class.authenticate(user.login, '')).to be(nil)
  128. end
  129. end
  130. context 'when password is nil' do
  131. it 'returns nil' do
  132. expect(described_class.authenticate(user.login, nil)).to be(nil)
  133. end
  134. end
  135. end
  136. context 'with empty password string when the stored hash represents an empty string' do
  137. before { user.update(password: PasswordHash.crypt('')) }
  138. context 'when password is an empty string' do
  139. it 'returns nil' do
  140. expect(described_class.authenticate(user.login, '')).to be(nil)
  141. end
  142. end
  143. context 'when password is nil' do
  144. it 'returns nil' do
  145. expect(described_class.authenticate(user.login, nil)).to be(nil)
  146. end
  147. end
  148. end
  149. end
  150. describe '.identify' do
  151. it 'returns users by given login' do
  152. expect(described_class.identify(user.login)).to eq(user)
  153. end
  154. it 'returns users by given email' do
  155. expect(described_class.identify(user.email)).to eq(user)
  156. end
  157. end
  158. end
  159. describe 'Instance methods:' do
  160. describe '#max_login_failed?' do
  161. it { is_expected.to respond_to(:max_login_failed?) }
  162. context 'with "password_max_login_failed" setting' do
  163. before do
  164. Setting.set('password_max_login_failed', 5)
  165. user.update(login_failed: 5)
  166. end
  167. it 'returns true once user’s #login_failed count exceeds the setting' do
  168. expect { user.update(login_failed: 6) }
  169. .to change(user, :max_login_failed?).to(true)
  170. end
  171. end
  172. context 'without password_max_login_failed setting' do
  173. before do
  174. Setting.set('password_max_login_failed', nil)
  175. user.update(login_failed: 0)
  176. end
  177. it 'defaults to 0' do
  178. expect { user.update(login_failed: 1) }
  179. .to change(user, :max_login_failed?).to(true)
  180. end
  181. end
  182. end
  183. describe '#out_of_office?' do
  184. context 'without any out_of_office_* attributes set' do
  185. it 'returns false' do
  186. expect(agent.out_of_office?).to be(false)
  187. end
  188. end
  189. context 'with valid #out_of_office_* attributes' do
  190. before do
  191. agent.update(
  192. out_of_office_start_at: Time.current.yesterday,
  193. out_of_office_end_at: Time.current.tomorrow,
  194. out_of_office_replacement_id: 1
  195. )
  196. end
  197. context 'but #out_of_office: false' do
  198. before { agent.update(out_of_office: false) }
  199. it 'returns false' do
  200. expect(agent.out_of_office?).to be(false)
  201. end
  202. end
  203. context 'and #out_of_office: true' do
  204. before { agent.update(out_of_office: true) }
  205. it 'returns true' do
  206. expect(agent.out_of_office?).to be(true)
  207. end
  208. context 'after the #out_of_office_end_at time has passed' do
  209. before { travel 2.days }
  210. it 'returns false (even though #out_of_office has not changed)' do
  211. expect(agent.out_of_office).to be(true)
  212. expect(agent.out_of_office?).to be(false)
  213. end
  214. end
  215. end
  216. end
  217. end
  218. describe '#out_of_office_agent' do
  219. it { is_expected.to respond_to(:out_of_office_agent) }
  220. context 'when user has no designated substitute' do
  221. it 'returns nil' do
  222. expect(user.out_of_office_agent).to be(nil)
  223. end
  224. end
  225. context 'when user has designated substitute' do
  226. subject(:user) do
  227. create(:user,
  228. out_of_office: out_of_office,
  229. out_of_office_start_at: Time.zone.yesterday,
  230. out_of_office_end_at: Time.zone.tomorrow,
  231. out_of_office_replacement_id: substitute.id,)
  232. end
  233. let(:substitute) { create(:user) }
  234. context 'but is not out of office' do
  235. let(:out_of_office) { false }
  236. it 'returns nil' do
  237. expect(user.out_of_office_agent).to be(nil)
  238. end
  239. end
  240. context 'and is out of office' do
  241. let(:out_of_office) { true }
  242. it 'returns the designated substitute' do
  243. expect(user.out_of_office_agent).to eq(substitute)
  244. end
  245. end
  246. end
  247. end
  248. describe '#out_of_office_agent_of' do
  249. context 'when no other agents are out-of-office' do
  250. it 'returns an empty ActiveRecord::Relation' do
  251. expect(agent.out_of_office_agent_of)
  252. .to be_an(ActiveRecord::Relation)
  253. .and be_empty
  254. end
  255. end
  256. context 'when designated as the substitute' do
  257. let!(:agent_on_holiday) do
  258. create(
  259. :agent_user,
  260. out_of_office_start_at: Time.current.yesterday,
  261. out_of_office_end_at: Time.current.tomorrow,
  262. out_of_office_replacement_id: agent.id,
  263. out_of_office: out_of_office
  264. )
  265. end
  266. context 'of an in-office agent' do
  267. let(:out_of_office) { false }
  268. it 'returns an empty ActiveRecord::Relation' do
  269. expect(agent.out_of_office_agent_of)
  270. .to be_an(ActiveRecord::Relation)
  271. .and be_empty
  272. end
  273. end
  274. context 'of an out-of-office agent' do
  275. let(:out_of_office) { true }
  276. it 'returns an ActiveRecord::Relation including that agent' do
  277. expect(agent.out_of_office_agent_of)
  278. .to match_array([agent_on_holiday])
  279. end
  280. end
  281. end
  282. end
  283. describe '#by_reset_token' do
  284. subject(:user) { token.user }
  285. let(:token) { create(:token_password_reset) }
  286. context 'with a valid token' do
  287. it 'returns the matching user' do
  288. expect(described_class.by_reset_token(token.name)).to eq(user)
  289. end
  290. end
  291. context 'with an invalid token' do
  292. it 'returns nil' do
  293. expect(described_class.by_reset_token('not-existing')).to be(nil)
  294. end
  295. end
  296. end
  297. describe '#password_reset_via_token' do
  298. subject(:user) { token.user }
  299. let!(:token) { create(:token_password_reset) }
  300. it 'changes the password of the token user and destroys the token' do
  301. expect { described_class.password_reset_via_token(token.name, Faker::Internet.password) }
  302. .to change { user.reload.password }
  303. .and change(Token, :count).by(-1)
  304. end
  305. end
  306. describe '#permissions?' do
  307. subject(:user) { create(:user, roles: [role]) }
  308. let(:role) { create(:role, permissions: [permission]) }
  309. let(:permission) { create(:permission, name: permission_name) }
  310. context 'with privileges for a root permission (e.g., "foo", not "foo.bar")' do
  311. let(:permission_name) { 'foo' }
  312. context 'when given that exact permission' do
  313. it 'returns true' do
  314. expect(user.permissions?('foo')).to be(true)
  315. end
  316. end
  317. context 'when given a sub-permission (i.e., child permission)' do
  318. let(:subpermission) { create(:permission, name: 'foo.bar') }
  319. context 'that exists' do
  320. before { subpermission }
  321. it 'returns true' do
  322. expect(user.permissions?('foo.bar')).to be(true)
  323. end
  324. end
  325. context 'that is inactive' do
  326. before { subpermission.update(active: false) }
  327. it 'returns false' do
  328. expect(user.permissions?('foo.bar')).to be(false)
  329. end
  330. end
  331. context 'that does not exist' do
  332. it 'returns true' do
  333. expect(user.permissions?('foo.bar')).to be(true)
  334. end
  335. end
  336. end
  337. context 'when given a glob' do
  338. context 'matching that permission' do
  339. it 'returns true' do
  340. expect(user.permissions?('foo.*')).to be(true)
  341. end
  342. end
  343. context 'NOT matching that permission' do
  344. it 'returns false' do
  345. expect(user.permissions?('bar.*')).to be(false)
  346. end
  347. end
  348. end
  349. end
  350. context 'with privileges for a sub-permission (e.g., "foo.bar", not "foo")' do
  351. let(:permission_name) { 'foo.bar' }
  352. context 'when given that exact sub-permission' do
  353. it 'returns true' do
  354. expect(user.permissions?('foo.bar')).to be(true)
  355. end
  356. context 'but the permission is inactive' do
  357. before { permission.update(active: false) }
  358. it 'returns false' do
  359. expect(user.permissions?('foo.bar')).to be(false)
  360. end
  361. end
  362. end
  363. context 'when given a sibling sub-permission' do
  364. let(:sibling_permission) { create(:permission, name: 'foo.baz') }
  365. context 'that exists' do
  366. before { sibling_permission }
  367. it 'returns false' do
  368. expect(user.permissions?('foo.baz')).to be(false)
  369. end
  370. end
  371. context 'that does not exist' do
  372. it 'returns false' do
  373. expect(user.permissions?('foo.baz')).to be(false)
  374. end
  375. end
  376. end
  377. context 'when given the parent permission' do
  378. it 'returns false' do
  379. expect(user.permissions?('foo')).to be(false)
  380. end
  381. end
  382. context 'when given a glob' do
  383. context 'matching that sub-permission' do
  384. it 'returns true' do
  385. expect(user.permissions?('foo.*')).to be(true)
  386. end
  387. context 'but the permission is inactive' do
  388. before { permission.update(active: false) }
  389. it 'returns false' do
  390. expect(user.permissions?('foo.bar')).to be(false)
  391. end
  392. end
  393. end
  394. context 'NOT matching that sub-permission' do
  395. it 'returns false' do
  396. expect(user.permissions?('bar.*')).to be(false)
  397. end
  398. end
  399. end
  400. end
  401. end
  402. describe '#permissions_with_child_ids' do
  403. context 'with privileges for a root permission (e.g., "foo", not "foo.bar")' do
  404. subject(:user) { create(:user, roles: [role]) }
  405. let(:role) { create(:role, permissions: [permission]) }
  406. let!(:permission) { create(:permission, name: 'foo') }
  407. let!(:child_permission) { create(:permission, name: 'foo.bar') }
  408. let!(:inactive_child_permission) { create(:permission, name: 'foo.baz', active: false) }
  409. it 'includes the IDs of user’s explicit permissions' do
  410. expect(user.permissions_with_child_ids)
  411. .to include(permission.id)
  412. end
  413. it 'includes the IDs of user’s active sub-permissions' do
  414. expect(user.permissions_with_child_ids)
  415. .to include(child_permission.id)
  416. .and not_include(inactive_child_permission.id)
  417. end
  418. end
  419. end
  420. describe '#locale' do
  421. subject(:user) { create(:user, preferences: preferences) }
  422. context 'with no #preferences[:locale]' do
  423. let(:preferences) { {} }
  424. context 'with default locale' do
  425. before { Setting.set('locale_default', 'foo') }
  426. it 'returns the system-wide default locale' do
  427. expect(user.locale).to eq('foo')
  428. end
  429. end
  430. context 'without default locale' do
  431. before { Setting.set('locale_default', nil) }
  432. it 'returns en-us' do
  433. expect(user.locale).to eq('en-us')
  434. end
  435. end
  436. end
  437. context 'with a #preferences[:locale]' do
  438. let(:preferences) { { locale: 'bar' } }
  439. it 'returns the user’s configured locale' do
  440. expect(user.locale).to eq('bar')
  441. end
  442. end
  443. end
  444. end
  445. describe 'Attributes:' do
  446. describe '#out_of_office' do
  447. context 'with #out_of_office_start_at: nil' do
  448. before { agent.update(out_of_office_start_at: nil, out_of_office_end_at: Time.current) }
  449. it 'cannot be set to true' do
  450. expect { agent.update(out_of_office: true) }
  451. .to raise_error(Exceptions::UnprocessableEntity)
  452. end
  453. end
  454. context 'with #out_of_office_end_at: nil' do
  455. before { agent.update(out_of_office_start_at: Time.current, out_of_office_end_at: nil) }
  456. it 'cannot be set to true' do
  457. expect { agent.update(out_of_office: true) }
  458. .to raise_error(Exceptions::UnprocessableEntity)
  459. end
  460. end
  461. context 'when #out_of_office_start_at is AFTER #out_of_office_end_at' do
  462. before { agent.update(out_of_office_start_at: Time.current.tomorrow, out_of_office_end_at: Time.current.next_month) }
  463. it 'cannot be set to true' do
  464. expect { agent.update(out_of_office: true) }
  465. .to raise_error(Exceptions::UnprocessableEntity)
  466. end
  467. end
  468. context 'when #out_of_office_start_at is AFTER Time.current' do
  469. before { agent.update(out_of_office_start_at: Time.current.tomorrow, out_of_office_end_at: Time.current.yesterday) }
  470. it 'cannot be set to true' do
  471. expect { agent.update(out_of_office: true) }
  472. .to raise_error(Exceptions::UnprocessableEntity)
  473. end
  474. end
  475. context 'when #out_of_office_end_at is BEFORE Time.current' do
  476. before { agent.update(out_of_office_start_at: Time.current.last_month, out_of_office_end_at: Time.current.yesterday) }
  477. it 'cannot be set to true' do
  478. expect { agent.update(out_of_office: true) }
  479. .to raise_error(Exceptions::UnprocessableEntity)
  480. end
  481. end
  482. end
  483. describe '#out_of_office_replacement_id' do
  484. it 'cannot be set to invalid user ID' do
  485. expect { agent.update(out_of_office_replacement_id: described_class.pluck(:id).max.next) }
  486. .to raise_error(ActiveRecord::InvalidForeignKey)
  487. end
  488. it 'can be set to a valid user ID' do
  489. expect { agent.update(out_of_office_replacement_id: 1) }
  490. .not_to raise_error
  491. end
  492. end
  493. describe '#login_failed' do
  494. before { user.update(login_failed: 1) }
  495. it 'is reset to 0 when password is updated' do
  496. expect { user.update(password: Faker::Internet.password) }
  497. .to change(user, :login_failed).to(0)
  498. end
  499. end
  500. describe '#password' do
  501. let(:password) { Faker::Internet.password }
  502. context 'when set to plaintext password' do
  503. it 'hashes password before saving to DB' do
  504. user.password = password
  505. expect { user.save }
  506. .to change { PasswordHash.crypted?(user.password) }
  507. end
  508. end
  509. context 'for existing user records' do
  510. context 'when changed to empty string' do
  511. before { user.update(password: password) }
  512. it 'keeps previous password' do
  513. expect { user.update!(password: '') }
  514. .not_to change(user, :password)
  515. end
  516. end
  517. context 'when changed to nil' do
  518. before { user.update(password: password) }
  519. it 'keeps previous password' do
  520. expect { user.update!(password: nil) }
  521. .not_to change(user, :password)
  522. end
  523. end
  524. end
  525. context 'for new user records' do
  526. context 'when passed as an empty string' do
  527. let(:another_user) { create(:user, password: '') }
  528. it 'sets password to nil' do
  529. expect(another_user.password).to eq(nil)
  530. end
  531. end
  532. context 'when passed as nil' do
  533. let(:another_user) { create(:user, password: nil) }
  534. it 'sets password to nil' do
  535. expect(another_user.password).to eq(nil)
  536. end
  537. end
  538. end
  539. context 'when set to SHA2 digest (to facilitate OTRS imports)' do
  540. it 'does not re-hash before saving' do
  541. user.password = "{sha2}#{Digest::SHA2.hexdigest(password)}"
  542. expect { user.save }.not_to change(user, :password)
  543. end
  544. end
  545. context 'when set to Argon2 digest' do
  546. it 'does not re-hash before saving' do
  547. user.password = PasswordHash.crypt(password)
  548. expect { user.save }.not_to change(user, :password)
  549. end
  550. end
  551. context 'when creating two users with the same password' do
  552. before { user.update(password: password) }
  553. let(:another_user) { create(:user, password: password) }
  554. it 'does not generate the same password hash' do
  555. expect(user.password).not_to eq(another_user.password)
  556. end
  557. end
  558. end
  559. describe '#phone' do
  560. subject(:user) { create(:user, phone: orig_number) }
  561. context 'when included on create' do
  562. let(:orig_number) { '1234567890' }
  563. it 'adds corresponding CallerId record' do
  564. expect { user }
  565. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(1)
  566. end
  567. end
  568. context 'when added on update' do
  569. let(:orig_number) { nil }
  570. let(:new_number) { '1234567890' }
  571. before { user } # create user
  572. it 'adds corresponding CallerId record' do
  573. expect { user.update(phone: new_number) }
  574. .to change { Cti::CallerId.where(caller_id: new_number).count }.by(1)
  575. end
  576. end
  577. context 'when falsely added on update (change: [nil, ""])' do
  578. let(:orig_number) { nil }
  579. let(:new_number) { '' }
  580. before { user } # create user
  581. it 'does not attempt to update CallerId record' do
  582. allow(Cti::CallerId).to receive(:build).with(any_args)
  583. expect(Cti::CallerId.where(object: 'User', o_id: user.id).count)
  584. .to eq(0)
  585. expect { user.update(phone: new_number) }
  586. .to change { Cti::CallerId.where(object: 'User', o_id: user.id).count }.by(0)
  587. expect(Cti::CallerId).not_to have_received(:build)
  588. end
  589. end
  590. context 'when removed on update' do
  591. let(:orig_number) { '1234567890' }
  592. let(:new_number) { nil }
  593. before { user } # create user
  594. it 'removes corresponding CallerId record' do
  595. expect { user.update(phone: nil) }
  596. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(-1)
  597. end
  598. end
  599. context 'when changed on update' do
  600. let(:orig_number) { '1234567890' }
  601. let(:new_number) { orig_number.next }
  602. before { user } # create user
  603. it 'replaces CallerId record' do
  604. expect { user.update(phone: new_number) }
  605. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(-1)
  606. .and change { Cti::CallerId.where(caller_id: new_number).count }.by(1)
  607. end
  608. end
  609. end
  610. describe '#preferences' do
  611. describe '"mail_delivery_failed{,_data}" keys' do
  612. before do
  613. user.update(
  614. preferences: {
  615. mail_delivery_failed: true,
  616. mail_delivery_failed_data: Time.current
  617. }
  618. )
  619. end
  620. it 'deletes "mail_delivery_failed"' do
  621. expect { user.update(email: Faker::Internet.email) }
  622. .to change { user.preferences.key?(:mail_delivery_failed) }.to(false)
  623. end
  624. it 'leaves "mail_delivery_failed_data" untouched' do
  625. expect { user.update(email: Faker::Internet.email) }
  626. .to not_change { user.preferences[:mail_delivery_failed_data] }
  627. end
  628. end
  629. end
  630. end
  631. describe 'Associations:' do
  632. describe '#organization' do
  633. describe 'email domain-based assignment' do
  634. subject(:user) { build(:user) }
  635. context 'when not set on creation' do
  636. before { user.assign_attributes(organization: nil) }
  637. context 'and #email domain matches an existing Organization#domain' do
  638. before { user.assign_attributes(email: 'user@example.com') }
  639. let(:organization) { create(:organization, domain: 'example.com') }
  640. context 'and Organization#domain_assignment is false (default)' do
  641. before { organization.update(domain_assignment: false) }
  642. it 'remains nil' do
  643. expect { user.save }.not_to change(user, :organization)
  644. end
  645. end
  646. context 'and Organization#domain_assignment is true' do
  647. before { organization.update(domain_assignment: true) }
  648. it 'is automatically set to matching Organization' do
  649. expect { user.save }
  650. .to change(user, :organization).to(organization)
  651. end
  652. end
  653. end
  654. context 'and #email domain doesn’t match any Organization#domain' do
  655. before { user.assign_attributes(email: 'user@example.net') }
  656. let(:organization) { create(:organization, domain: 'example.com') }
  657. context 'and Organization#domain_assignment is true' do
  658. before { organization.update(domain_assignment: true) }
  659. it 'remains nil' do
  660. expect { user.save }.not_to change(user, :organization)
  661. end
  662. end
  663. end
  664. end
  665. context 'when set on creation' do
  666. before { user.assign_attributes(organization: specified_organization) }
  667. let(:specified_organization) { create(:organization, domain: 'example.net') }
  668. context 'and #email domain matches a DIFFERENT Organization#domain' do
  669. before { user.assign_attributes(email: 'user@example.com') }
  670. let!(:matching_organization) { create(:organization, domain: 'example.com') }
  671. context 'and Organization#domain_assignment is true' do
  672. before { matching_organization.update(domain_assignment: true) }
  673. it 'is NOT automatically set to matching Organization' do
  674. expect { user.save }
  675. .not_to change(user, :organization).from(specified_organization)
  676. end
  677. end
  678. end
  679. end
  680. end
  681. end
  682. end
  683. describe 'Callbacks, Observers, & Async Transactions -' do
  684. describe 'System-wide agent limit checks:' do
  685. let(:agent_role) { Role.lookup(name: 'Agent') }
  686. let(:admin_role) { Role.lookup(name: 'Admin') }
  687. let(:current_agents) { described_class.with_permissions('ticket.agent') }
  688. describe '#validate_agent_limit_by_role' do
  689. context 'for Integer value of system_agent_limit' do
  690. context 'before exceeding the agent limit' do
  691. before { Setting.set('system_agent_limit', current_agents.count + 1) }
  692. it 'grants agent creation' do
  693. expect { create(:agent_user) }
  694. .to change(current_agents, :count).by(1)
  695. end
  696. it 'grants role change' do
  697. future_agent = create(:customer_user)
  698. expect { future_agent.roles = [agent_role] }
  699. .to change(current_agents, :count).by(1)
  700. end
  701. describe 'role updates' do
  702. let(:agent) { create(:agent_user) }
  703. it 'grants update by instances' do
  704. expect { agent.roles = [admin_role, agent_role] }
  705. .not_to raise_error
  706. end
  707. it 'grants update by id (Integer)' do
  708. expect { agent.role_ids = [admin_role.id, agent_role.id] }
  709. .not_to raise_error
  710. end
  711. it 'grants update by id (String)' do
  712. expect { agent.role_ids = [admin_role.id.to_s, agent_role.id.to_s] }
  713. .not_to raise_error
  714. end
  715. end
  716. end
  717. context 'when exceeding the agent limit' do
  718. it 'creation of new agents' do
  719. Setting.set('system_agent_limit', current_agents.count + 2)
  720. create_list(:agent_user, 2)
  721. expect { create(:agent_user) }
  722. .to raise_error(Exceptions::UnprocessableEntity)
  723. .and change(current_agents, :count).by(0)
  724. end
  725. it 'prevents role change' do
  726. Setting.set('system_agent_limit', current_agents.count)
  727. future_agent = create(:customer_user)
  728. expect { future_agent.roles = [agent_role] }
  729. .to raise_error(Exceptions::UnprocessableEntity)
  730. .and change(current_agents, :count).by(0)
  731. end
  732. end
  733. end
  734. context 'for String value of system_agent_limit' do
  735. context 'before exceeding the agent limit' do
  736. before { Setting.set('system_agent_limit', (current_agents.count + 1).to_s) }
  737. it 'grants agent creation' do
  738. expect { create(:agent_user) }
  739. .to change(current_agents, :count).by(1)
  740. end
  741. it 'grants role change' do
  742. future_agent = create(:customer_user)
  743. expect { future_agent.roles = [agent_role] }
  744. .to change(current_agents, :count).by(1)
  745. end
  746. describe 'role updates' do
  747. let(:agent) { create(:agent_user) }
  748. it 'grants update by instances' do
  749. expect { agent.roles = [admin_role, agent_role] }
  750. .not_to raise_error
  751. end
  752. it 'grants update by id (Integer)' do
  753. expect { agent.role_ids = [admin_role.id, agent_role.id] }
  754. .not_to raise_error
  755. end
  756. it 'grants update by id (String)' do
  757. expect { agent.role_ids = [admin_role.id.to_s, agent_role.id.to_s] }
  758. .not_to raise_error
  759. end
  760. end
  761. end
  762. context 'when exceeding the agent limit' do
  763. it 'creation of new agents' do
  764. Setting.set('system_agent_limit', (current_agents.count + 2).to_s)
  765. create_list(:agent_user, 2)
  766. expect { create(:agent_user) }
  767. .to raise_error(Exceptions::UnprocessableEntity)
  768. .and change(current_agents, :count).by(0)
  769. end
  770. it 'prevents role change' do
  771. Setting.set('system_agent_limit', current_agents.count.to_s)
  772. future_agent = create(:customer_user)
  773. expect { future_agent.roles = [agent_role] }
  774. .to raise_error(Exceptions::UnprocessableEntity)
  775. .and change(current_agents, :count).by(0)
  776. end
  777. end
  778. end
  779. end
  780. describe '#validate_agent_limit_by_attributes' do
  781. context 'for Integer value of system_agent_limit' do
  782. before { Setting.set('system_agent_limit', current_agents.count) }
  783. context 'when exceeding the agent limit' do
  784. it 'prevents re-activation of agents' do
  785. inactive_agent = create(:agent_user, active: false)
  786. expect { inactive_agent.update!(active: true) }
  787. .to raise_error(Exceptions::UnprocessableEntity)
  788. .and change(current_agents, :count).by(0)
  789. end
  790. end
  791. end
  792. context 'for String value of system_agent_limit' do
  793. before { Setting.set('system_agent_limit', current_agents.count.to_s) }
  794. context 'when exceeding the agent limit' do
  795. it 'prevents re-activation of agents' do
  796. inactive_agent = create(:agent_user, active: false)
  797. expect { inactive_agent.update!(active: true) }
  798. .to raise_error(Exceptions::UnprocessableEntity)
  799. .and change(current_agents, :count).by(0)
  800. end
  801. end
  802. end
  803. end
  804. end
  805. describe 'Touching associations on update:' do
  806. subject!(:user) { create(:customer_user) }
  807. let!(:organization) { create(:organization) }
  808. context 'when a customer gets a organization' do
  809. it 'touches its organization' do
  810. expect { user.update(organization: organization) }
  811. .to change { organization.reload.updated_at }
  812. end
  813. end
  814. end
  815. describe 'Cti::CallerId syncing:' do
  816. context 'with a #phone attribute' do
  817. subject(:user) { build(:user, phone: '1234567890') }
  818. it 'adds CallerId record on creation (via Cti::CallerId.build)' do
  819. expect(Cti::CallerId).to receive(:build).with(user)
  820. user.save
  821. end
  822. it 'updates CallerId record on touch/update (via Cti::CallerId.build)' do
  823. user.save
  824. expect(Cti::CallerId).to receive(:build).with(user)
  825. user.touch
  826. end
  827. it 'destroys CallerId record on deletion' do
  828. user.save
  829. expect { user.destroy }
  830. .to change { Cti::CallerId.count }.by(-1)
  831. end
  832. end
  833. end
  834. describe 'Cti::Log syncing:' do
  835. context 'with existing Log records' do
  836. context 'for incoming calls from an unknown number' do
  837. let!(:log) { create(:'cti/log', :with_preferences, from: '1234567890', direction: 'in') }
  838. context 'when creating a new user with that number' do
  839. subject(:user) { build(:user, phone: log.from) }
  840. it 'populates #preferences[:from] hash in all associated Log records (in a bg job)' do
  841. expect do
  842. user.save
  843. Observer::Transaction.commit
  844. Scheduler.worker(true)
  845. end.to change { log.reload.preferences[:from]&.first }
  846. .to(hash_including('caller_id' => user.phone))
  847. end
  848. end
  849. context 'when updating a user with that number' do
  850. subject(:user) { create(:user) }
  851. it 'populates #preferences[:from] hash in all associated Log records (in a bg job)' do
  852. expect do
  853. user.update(phone: log.from)
  854. Observer::Transaction.commit
  855. Scheduler.worker(true)
  856. end.to change { log.reload.preferences[:from]&.first }
  857. .to(hash_including('object' => 'User', 'o_id' => user.id))
  858. end
  859. end
  860. context 'when creating a new user with an empty number' do
  861. subject(:user) { build(:user, phone: '') }
  862. it 'does not modify any Log records' do
  863. expect do
  864. user.save
  865. Observer::Transaction.commit
  866. Scheduler.worker(true)
  867. end.not_to change { log.reload.attributes }
  868. end
  869. end
  870. context 'when creating a new user with no number' do
  871. subject(:user) { build(:user, phone: nil) }
  872. it 'does not modify any Log records' do
  873. expect do
  874. user.save
  875. Observer::Transaction.commit
  876. Scheduler.worker(true)
  877. end.not_to change { log.reload.attributes }
  878. end
  879. end
  880. end
  881. context 'for incoming calls from the given user' do
  882. subject(:user) { create(:user, phone: '1234567890') }
  883. let!(:logs) { create_list(:'cti/log', 5, :with_preferences, from: user.phone, direction: 'in') }
  884. context 'when updating #phone attribute' do
  885. context 'to another number' do
  886. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  887. expect do
  888. user.update(phone: '0123456789')
  889. Observer::Transaction.commit
  890. Scheduler.worker(true)
  891. end.to change { logs.map(&:reload).map(&:preferences) }
  892. .to(Array.new(5) { {} })
  893. end
  894. end
  895. context 'to an empty string' do
  896. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  897. expect do
  898. user.update(phone: '')
  899. Observer::Transaction.commit
  900. Scheduler.worker(true)
  901. end.to change { logs.map(&:reload).map(&:preferences) }
  902. .to(Array.new(5) { {} })
  903. end
  904. end
  905. context 'to nil' do
  906. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  907. expect do
  908. user.update(phone: nil)
  909. Observer::Transaction.commit
  910. Scheduler.worker(true)
  911. end.to change { logs.map(&:reload).map(&:preferences) }
  912. .to(Array.new(5) { {} })
  913. end
  914. end
  915. end
  916. context 'when updating attributes other than #phone' do
  917. it 'does not modify any Log records' do
  918. expect do
  919. user.update(mobile: '2345678901')
  920. Observer::Transaction.commit
  921. Scheduler.worker(true)
  922. end.not_to change { logs.map(&:reload).map(&:attributes) }
  923. end
  924. end
  925. end
  926. end
  927. end
  928. end
  929. end