user_spec.rb 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  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) }
  15. let(:agent) { create(:agent) }
  16. let(:admin) { create(:admin) }
  17. it_behaves_like 'ApplicationModel', can_assets: { associations: :organization }
  18. it_behaves_like 'HasGroups', group_access_factory: :agent
  19. it_behaves_like 'HasHistory'
  20. it_behaves_like 'HasRoles', group_access_factory: :agent
  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,
  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. let(:user) { create(:agent).tap { |u| u.roles = [u.roles.first] } }
  308. let(:role) { user.roles.first }
  309. let(:permissions) { role.permissions }
  310. it 'is a simple association getter' do
  311. expect(user.permissions).to match_array(permissions)
  312. end
  313. context 'for inactive permissions' do
  314. before { permissions.first.update(active: false) }
  315. it 'omits them from the returned hash' do
  316. expect(user.permissions).not_to include(permissions.first)
  317. end
  318. end
  319. context 'for permissions on inactive roles' do
  320. before { role.update(active: false) }
  321. it 'omits them from the returned hash' do
  322. expect(user.permissions).not_to include(*role.permissions)
  323. end
  324. end
  325. end
  326. describe '#permissions?' do
  327. subject(:user) { create(:user, roles: [role]) }
  328. let(:role) { create(:role, permissions: [permission]) }
  329. let(:permission) { create(:permission, name: permission_name) }
  330. context 'with privileges for a root permission (e.g., "foo", not "foo.bar")' do
  331. let(:permission_name) { 'foo' }
  332. context 'when given that exact permission' do
  333. it 'returns true' do
  334. expect(user.permissions?('foo')).to be(true)
  335. end
  336. end
  337. context 'when given an active sub-permission' do
  338. before { create(:permission, name: 'foo.bar') }
  339. it 'returns true' do
  340. expect(user.permissions?('foo.bar')).to be(true)
  341. end
  342. end
  343. describe 'chain-of-ancestry quirk' do
  344. context 'when given an inactive sub-permission' do
  345. before { create(:permission, name: 'foo.bar.baz', active: false) }
  346. it 'returns false, even with active ancestors' do
  347. expect(user.permissions?('foo.bar.baz')).to be(false)
  348. end
  349. end
  350. context 'when given a sub-permission that does not exist' do
  351. before { create(:permission, name: 'foo.bar', active: false) }
  352. it 'can return true, even with inactive ancestors' do
  353. expect(user.permissions?('foo.bar.baz')).to be(true)
  354. end
  355. end
  356. end
  357. context 'when given a glob' do
  358. context 'matching that permission' do
  359. it 'returns true' do
  360. expect(user.permissions?('foo.*')).to be(true)
  361. end
  362. end
  363. context 'NOT matching that permission' do
  364. it 'returns false' do
  365. expect(user.permissions?('bar.*')).to be(false)
  366. end
  367. end
  368. end
  369. end
  370. context 'with privileges for a sub-permission (e.g., "foo.bar", not "foo")' do
  371. let(:permission_name) { 'foo.bar' }
  372. context 'when given that exact sub-permission' do
  373. it 'returns true' do
  374. expect(user.permissions?('foo.bar')).to be(true)
  375. end
  376. context 'but the permission is inactive' do
  377. before { permission.update(active: false) }
  378. it 'returns false' do
  379. expect(user.permissions?('foo.bar')).to be(false)
  380. end
  381. end
  382. end
  383. context 'when given a sibling sub-permission' do
  384. let(:sibling_permission) { create(:permission, name: 'foo.baz') }
  385. context 'that exists' do
  386. before { sibling_permission }
  387. it 'returns false' do
  388. expect(user.permissions?('foo.baz')).to be(false)
  389. end
  390. end
  391. context 'that does not exist' do
  392. it 'returns false' do
  393. expect(user.permissions?('foo.baz')).to be(false)
  394. end
  395. end
  396. end
  397. context 'when given the parent permission' do
  398. it 'returns false' do
  399. expect(user.permissions?('foo')).to be(false)
  400. end
  401. end
  402. context 'when given a glob' do
  403. context 'matching that sub-permission' do
  404. it 'returns true' do
  405. expect(user.permissions?('foo.*')).to be(true)
  406. end
  407. context 'but the permission is inactive' do
  408. before { permission.update(active: false) }
  409. it 'returns false' do
  410. expect(user.permissions?('foo.*')).to be(false)
  411. end
  412. end
  413. end
  414. context 'NOT matching that sub-permission' do
  415. it 'returns false' do
  416. expect(user.permissions?('bar.*')).to be(false)
  417. end
  418. end
  419. end
  420. end
  421. end
  422. describe '#permissions_with_child_ids' do
  423. context 'with privileges for a root permission (e.g., "foo", not "foo.bar")' do
  424. subject(:user) { create(:user, roles: [role]) }
  425. let(:role) { create(:role, permissions: [permission]) }
  426. let!(:permission) { create(:permission, name: 'foo') }
  427. let!(:child_permission) { create(:permission, name: 'foo.bar') }
  428. let!(:inactive_child_permission) { create(:permission, name: 'foo.baz', active: false) }
  429. it 'includes the IDs of user’s explicit permissions' do
  430. expect(user.permissions_with_child_ids)
  431. .to include(permission.id)
  432. end
  433. it 'includes the IDs of user’s active sub-permissions' do
  434. expect(user.permissions_with_child_ids)
  435. .to include(child_permission.id)
  436. .and not_include(inactive_child_permission.id)
  437. end
  438. end
  439. end
  440. describe '#locale' do
  441. subject(:user) { create(:user, preferences: preferences) }
  442. context 'with no #preferences[:locale]' do
  443. let(:preferences) { {} }
  444. context 'with default locale' do
  445. before { Setting.set('locale_default', 'foo') }
  446. it 'returns the system-wide default locale' do
  447. expect(user.locale).to eq('foo')
  448. end
  449. end
  450. context 'without default locale' do
  451. before { Setting.set('locale_default', nil) }
  452. it 'returns en-us' do
  453. expect(user.locale).to eq('en-us')
  454. end
  455. end
  456. end
  457. context 'with a #preferences[:locale]' do
  458. let(:preferences) { { locale: 'bar' } }
  459. it 'returns the user’s configured locale' do
  460. expect(user.locale).to eq('bar')
  461. end
  462. end
  463. end
  464. end
  465. describe 'Attributes:' do
  466. describe '#out_of_office' do
  467. context 'with #out_of_office_start_at: nil' do
  468. before { agent.update(out_of_office_start_at: nil, out_of_office_end_at: Time.current) }
  469. it 'cannot be set to true' do
  470. expect { agent.update(out_of_office: true) }
  471. .to raise_error(Exceptions::UnprocessableEntity)
  472. end
  473. end
  474. context 'with #out_of_office_end_at: nil' do
  475. before { agent.update(out_of_office_start_at: Time.current, out_of_office_end_at: nil) }
  476. it 'cannot be set to true' do
  477. expect { agent.update(out_of_office: true) }
  478. .to raise_error(Exceptions::UnprocessableEntity)
  479. end
  480. end
  481. context 'when #out_of_office_start_at is AFTER #out_of_office_end_at' do
  482. before { agent.update(out_of_office_start_at: Time.current.tomorrow, out_of_office_end_at: Time.current.next_month) }
  483. it 'cannot be set to true' do
  484. expect { agent.update(out_of_office: true) }
  485. .to raise_error(Exceptions::UnprocessableEntity)
  486. end
  487. end
  488. context 'when #out_of_office_start_at is AFTER Time.current' do
  489. before { agent.update(out_of_office_start_at: Time.current.tomorrow, out_of_office_end_at: Time.current.yesterday) }
  490. it 'cannot be set to true' do
  491. expect { agent.update(out_of_office: true) }
  492. .to raise_error(Exceptions::UnprocessableEntity)
  493. end
  494. end
  495. context 'when #out_of_office_end_at is BEFORE Time.current' do
  496. before { agent.update(out_of_office_start_at: Time.current.last_month, out_of_office_end_at: Time.current.yesterday) }
  497. it 'cannot be set to true' do
  498. expect { agent.update(out_of_office: true) }
  499. .to raise_error(Exceptions::UnprocessableEntity)
  500. end
  501. end
  502. end
  503. describe '#out_of_office_replacement_id' do
  504. it 'cannot be set to invalid user ID' do
  505. expect { agent.update(out_of_office_replacement_id: described_class.pluck(:id).max.next) }
  506. .to raise_error(ActiveRecord::InvalidForeignKey)
  507. end
  508. it 'can be set to a valid user ID' do
  509. expect { agent.update(out_of_office_replacement_id: 1) }
  510. .not_to raise_error
  511. end
  512. end
  513. describe '#login_failed' do
  514. before { user.update(login_failed: 1) }
  515. it 'is reset to 0 when password is updated' do
  516. expect { user.update(password: Faker::Internet.password) }
  517. .to change(user, :login_failed).to(0)
  518. end
  519. end
  520. describe '#password' do
  521. let(:password) { Faker::Internet.password }
  522. context 'when set to plaintext password' do
  523. it 'hashes password before saving to DB' do
  524. user.password = password
  525. expect { user.save }
  526. .to change { PasswordHash.crypted?(user.password) }
  527. end
  528. end
  529. context 'for existing user records' do
  530. context 'when changed to empty string' do
  531. before { user.update(password: password) }
  532. it 'keeps previous password' do
  533. expect { user.update!(password: '') }
  534. .not_to change(user, :password)
  535. end
  536. end
  537. context 'when changed to nil' do
  538. before { user.update(password: password) }
  539. it 'keeps previous password' do
  540. expect { user.update!(password: nil) }
  541. .not_to change(user, :password)
  542. end
  543. end
  544. end
  545. context 'for new user records' do
  546. context 'when passed as an empty string' do
  547. let(:another_user) { create(:user, password: '') }
  548. it 'sets password to nil' do
  549. expect(another_user.password).to eq(nil)
  550. end
  551. end
  552. context 'when passed as nil' do
  553. let(:another_user) { create(:user, password: nil) }
  554. it 'sets password to nil' do
  555. expect(another_user.password).to eq(nil)
  556. end
  557. end
  558. end
  559. context 'when set to SHA2 digest (to facilitate OTRS imports)' do
  560. it 'does not re-hash before saving' do
  561. user.password = "{sha2}#{Digest::SHA2.hexdigest(password)}"
  562. expect { user.save }.not_to change(user, :password)
  563. end
  564. end
  565. context 'when set to Argon2 digest' do
  566. it 'does not re-hash before saving' do
  567. user.password = PasswordHash.crypt(password)
  568. expect { user.save }.not_to change(user, :password)
  569. end
  570. end
  571. context 'when creating two users with the same password' do
  572. before { user.update(password: password) }
  573. let(:another_user) { create(:user, password: password) }
  574. it 'does not generate the same password hash' do
  575. expect(user.password).not_to eq(another_user.password)
  576. end
  577. end
  578. end
  579. describe '#phone' do
  580. subject(:user) { create(:user, phone: orig_number) }
  581. context 'when included on create' do
  582. let(:orig_number) { '1234567890' }
  583. it 'adds corresponding CallerId record' do
  584. expect { user }
  585. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(1)
  586. end
  587. end
  588. context 'when added on update' do
  589. let(:orig_number) { nil }
  590. let(:new_number) { '1234567890' }
  591. before { user } # create user
  592. it 'adds corresponding CallerId record' do
  593. expect { user.update(phone: new_number) }
  594. .to change { Cti::CallerId.where(caller_id: new_number).count }.by(1)
  595. end
  596. end
  597. context 'when falsely added on update (change: [nil, ""])' do
  598. let(:orig_number) { nil }
  599. let(:new_number) { '' }
  600. before { user } # create user
  601. it 'does not attempt to update CallerId record' do
  602. allow(Cti::CallerId).to receive(:build).with(any_args)
  603. expect(Cti::CallerId.where(object: 'User', o_id: user.id).count)
  604. .to eq(0)
  605. expect { user.update(phone: new_number) }
  606. .to change { Cti::CallerId.where(object: 'User', o_id: user.id).count }.by(0)
  607. expect(Cti::CallerId).not_to have_received(:build)
  608. end
  609. end
  610. context 'when removed on update' do
  611. let(:orig_number) { '1234567890' }
  612. let(:new_number) { nil }
  613. before { user } # create user
  614. it 'removes corresponding CallerId record' do
  615. expect { user.update(phone: nil) }
  616. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(-1)
  617. end
  618. end
  619. context 'when changed on update' do
  620. let(:orig_number) { '1234567890' }
  621. let(:new_number) { orig_number.next }
  622. before { user } # create user
  623. it 'replaces CallerId record' do
  624. expect { user.update(phone: new_number) }
  625. .to change { Cti::CallerId.where(caller_id: orig_number).count }.by(-1)
  626. .and change { Cti::CallerId.where(caller_id: new_number).count }.by(1)
  627. end
  628. end
  629. end
  630. describe '#preferences' do
  631. describe '"mail_delivery_failed{,_data}" keys' do
  632. before do
  633. user.update(
  634. preferences: {
  635. mail_delivery_failed: true,
  636. mail_delivery_failed_data: Time.current
  637. }
  638. )
  639. end
  640. it 'deletes "mail_delivery_failed"' do
  641. expect { user.update(email: Faker::Internet.email) }
  642. .to change { user.preferences.key?(:mail_delivery_failed) }.to(false)
  643. end
  644. it 'leaves "mail_delivery_failed_data" untouched' do
  645. expect { user.update(email: Faker::Internet.email) }
  646. .to not_change { user.preferences[:mail_delivery_failed_data] }
  647. end
  648. end
  649. end
  650. end
  651. describe 'Associations:' do
  652. describe '#organization' do
  653. describe 'email domain-based assignment' do
  654. subject(:user) { build(:user) }
  655. context 'when not set on creation' do
  656. before { user.assign_attributes(organization: nil) }
  657. context 'and #email domain matches an existing Organization#domain' do
  658. before { user.assign_attributes(email: 'user@example.com') }
  659. let(:organization) { create(:organization, domain: 'example.com') }
  660. context 'and Organization#domain_assignment is false (default)' do
  661. before { organization.update(domain_assignment: false) }
  662. it 'remains nil' do
  663. expect { user.save }.not_to change(user, :organization)
  664. end
  665. end
  666. context 'and Organization#domain_assignment is true' do
  667. before { organization.update(domain_assignment: true) }
  668. it 'is automatically set to matching Organization' do
  669. expect { user.save }
  670. .to change(user, :organization).to(organization)
  671. end
  672. end
  673. end
  674. context 'and #email domain doesn’t match any Organization#domain' do
  675. before { user.assign_attributes(email: 'user@example.net') }
  676. let(:organization) { create(:organization, domain: 'example.com') }
  677. context 'and Organization#domain_assignment is true' do
  678. before { organization.update(domain_assignment: true) }
  679. it 'remains nil' do
  680. expect { user.save }.not_to change(user, :organization)
  681. end
  682. end
  683. end
  684. end
  685. context 'when set on creation' do
  686. before { user.assign_attributes(organization: specified_organization) }
  687. let(:specified_organization) { create(:organization, domain: 'example.net') }
  688. context 'and #email domain matches a DIFFERENT Organization#domain' do
  689. before { user.assign_attributes(email: 'user@example.com') }
  690. let!(:matching_organization) { create(:organization, domain: 'example.com') }
  691. context 'and Organization#domain_assignment is true' do
  692. before { matching_organization.update(domain_assignment: true) }
  693. it 'is NOT automatically set to matching Organization' do
  694. expect { user.save }
  695. .not_to change(user, :organization).from(specified_organization)
  696. end
  697. end
  698. end
  699. end
  700. end
  701. end
  702. end
  703. describe 'Callbacks, Observers, & Async Transactions -' do
  704. describe 'System-wide agent limit checks:' do
  705. let(:agent_role) { Role.lookup(name: 'Agent') }
  706. let(:admin_role) { Role.lookup(name: 'Admin') }
  707. let(:current_agents) { described_class.with_permissions('ticket.agent') }
  708. describe '#validate_agent_limit_by_role' do
  709. context 'for Integer value of system_agent_limit' do
  710. context 'before exceeding the agent limit' do
  711. before { Setting.set('system_agent_limit', current_agents.count + 1) }
  712. it 'grants agent creation' do
  713. expect { create(:agent) }
  714. .to change(current_agents, :count).by(1)
  715. end
  716. it 'grants role change' do
  717. future_agent = create(:customer)
  718. expect { future_agent.roles = [agent_role] }
  719. .to change(current_agents, :count).by(1)
  720. end
  721. describe 'role updates' do
  722. let(:agent) { create(:agent) }
  723. it 'grants update by instances' do
  724. expect { agent.roles = [admin_role, agent_role] }
  725. .not_to raise_error
  726. end
  727. it 'grants update by id (Integer)' do
  728. expect { agent.role_ids = [admin_role.id, agent_role.id] }
  729. .not_to raise_error
  730. end
  731. it 'grants update by id (String)' do
  732. expect { agent.role_ids = [admin_role.id.to_s, agent_role.id.to_s] }
  733. .not_to raise_error
  734. end
  735. end
  736. end
  737. context 'when exceeding the agent limit' do
  738. it 'creation of new agents' do
  739. Setting.set('system_agent_limit', current_agents.count + 2)
  740. create_list(:agent, 2)
  741. expect { create(:agent) }
  742. .to raise_error(Exceptions::UnprocessableEntity)
  743. .and change(current_agents, :count).by(0)
  744. end
  745. it 'prevents role change' do
  746. Setting.set('system_agent_limit', current_agents.count)
  747. future_agent = create(:customer)
  748. expect { future_agent.roles = [agent_role] }
  749. .to raise_error(Exceptions::UnprocessableEntity)
  750. .and change(current_agents, :count).by(0)
  751. end
  752. end
  753. end
  754. context 'for String value of system_agent_limit' do
  755. context 'before exceeding the agent limit' do
  756. before { Setting.set('system_agent_limit', (current_agents.count + 1).to_s) }
  757. it 'grants agent creation' do
  758. expect { create(:agent) }
  759. .to change(current_agents, :count).by(1)
  760. end
  761. it 'grants role change' do
  762. future_agent = create(:customer)
  763. expect { future_agent.roles = [agent_role] }
  764. .to change(current_agents, :count).by(1)
  765. end
  766. describe 'role updates' do
  767. let(:agent) { create(:agent) }
  768. it 'grants update by instances' do
  769. expect { agent.roles = [admin_role, agent_role] }
  770. .not_to raise_error
  771. end
  772. it 'grants update by id (Integer)' do
  773. expect { agent.role_ids = [admin_role.id, agent_role.id] }
  774. .not_to raise_error
  775. end
  776. it 'grants update by id (String)' do
  777. expect { agent.role_ids = [admin_role.id.to_s, agent_role.id.to_s] }
  778. .not_to raise_error
  779. end
  780. end
  781. end
  782. context 'when exceeding the agent limit' do
  783. it 'creation of new agents' do
  784. Setting.set('system_agent_limit', (current_agents.count + 2).to_s)
  785. create_list(:agent, 2)
  786. expect { create(:agent) }
  787. .to raise_error(Exceptions::UnprocessableEntity)
  788. .and change(current_agents, :count).by(0)
  789. end
  790. it 'prevents role change' do
  791. Setting.set('system_agent_limit', current_agents.count.to_s)
  792. future_agent = create(:customer)
  793. expect { future_agent.roles = [agent_role] }
  794. .to raise_error(Exceptions::UnprocessableEntity)
  795. .and change(current_agents, :count).by(0)
  796. end
  797. end
  798. end
  799. end
  800. describe '#validate_agent_limit_by_attributes' do
  801. context 'for Integer value of system_agent_limit' do
  802. before { Setting.set('system_agent_limit', current_agents.count) }
  803. context 'when exceeding the agent limit' do
  804. it 'prevents re-activation of agents' do
  805. inactive_agent = create(:agent, active: false)
  806. expect { inactive_agent.update!(active: true) }
  807. .to raise_error(Exceptions::UnprocessableEntity)
  808. .and change(current_agents, :count).by(0)
  809. end
  810. end
  811. end
  812. context 'for String value of system_agent_limit' do
  813. before { Setting.set('system_agent_limit', current_agents.count.to_s) }
  814. context 'when exceeding the agent limit' do
  815. it 'prevents re-activation of agents' do
  816. inactive_agent = create(:agent, active: false)
  817. expect { inactive_agent.update!(active: true) }
  818. .to raise_error(Exceptions::UnprocessableEntity)
  819. .and change(current_agents, :count).by(0)
  820. end
  821. end
  822. end
  823. end
  824. end
  825. describe 'Touching associations on update:' do
  826. subject!(:user) { create(:customer) }
  827. let!(:organization) { create(:organization) }
  828. context 'when a customer gets a organization' do
  829. it 'touches its organization' do
  830. expect { user.update(organization: organization) }
  831. .to change { organization.reload.updated_at }
  832. end
  833. end
  834. end
  835. describe 'Cti::CallerId syncing:' do
  836. context 'with a #phone attribute' do
  837. subject(:user) { build(:user, phone: '1234567890') }
  838. it 'adds CallerId record on creation (via Cti::CallerId.build)' do
  839. expect(Cti::CallerId).to receive(:build).with(user)
  840. user.save
  841. end
  842. it 'updates CallerId record on touch/update (via Cti::CallerId.build)' do
  843. user.save
  844. expect(Cti::CallerId).to receive(:build).with(user)
  845. user.touch
  846. end
  847. it 'destroys CallerId record on deletion' do
  848. user.save
  849. expect { user.destroy }
  850. .to change { Cti::CallerId.count }.by(-1)
  851. end
  852. end
  853. end
  854. describe 'Cti::Log syncing:' do
  855. context 'with existing Log records' do
  856. context 'for incoming calls from an unknown number' do
  857. let!(:log) { create(:'cti/log', :with_preferences, from: '1234567890', direction: 'in') }
  858. context 'when creating a new user with that number' do
  859. subject(:user) { build(:user, phone: log.from) }
  860. it 'populates #preferences[:from] hash in all associated Log records (in a bg job)' do
  861. expect do
  862. user.save
  863. Observer::Transaction.commit
  864. Scheduler.worker(true)
  865. end.to change { log.reload.preferences[:from]&.first }
  866. .to(hash_including('caller_id' => user.phone))
  867. end
  868. end
  869. context 'when updating a user with that number' do
  870. subject(:user) { create(:user) }
  871. it 'populates #preferences[:from] hash in all associated Log records (in a bg job)' do
  872. expect do
  873. user.update(phone: log.from)
  874. Observer::Transaction.commit
  875. Scheduler.worker(true)
  876. end.to change { log.reload.preferences[:from]&.first }
  877. .to(hash_including('object' => 'User', 'o_id' => user.id))
  878. end
  879. end
  880. context 'when creating a new user with an empty number' do
  881. subject(:user) { build(:user, phone: '') }
  882. it 'does not modify any Log records' do
  883. expect do
  884. user.save
  885. Observer::Transaction.commit
  886. Scheduler.worker(true)
  887. end.not_to change { log.reload.attributes }
  888. end
  889. end
  890. context 'when creating a new user with no number' do
  891. subject(:user) { build(:user, phone: nil) }
  892. it 'does not modify any Log records' do
  893. expect do
  894. user.save
  895. Observer::Transaction.commit
  896. Scheduler.worker(true)
  897. end.not_to change { log.reload.attributes }
  898. end
  899. end
  900. end
  901. context 'for incoming calls from the given user' do
  902. subject(:user) { create(:user, phone: '1234567890') }
  903. let!(:logs) { create_list(:'cti/log', 5, :with_preferences, from: user.phone, direction: 'in') }
  904. context 'when updating #phone attribute' do
  905. context 'to another number' do
  906. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  907. expect do
  908. user.update(phone: '0123456789')
  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. context 'to an empty string' do
  916. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  917. expect do
  918. user.update(phone: '')
  919. Observer::Transaction.commit
  920. Scheduler.worker(true)
  921. end.to change { logs.map(&:reload).map(&:preferences) }
  922. .to(Array.new(5) { {} })
  923. end
  924. end
  925. context 'to nil' do
  926. it 'empties #preferences[:from] hash in all associated Log records (in a bg job)' do
  927. expect do
  928. user.update(phone: nil)
  929. Observer::Transaction.commit
  930. Scheduler.worker(true)
  931. end.to change { logs.map(&:reload).map(&:preferences) }
  932. .to(Array.new(5) { {} })
  933. end
  934. end
  935. end
  936. context 'when updating attributes other than #phone' do
  937. it 'does not modify any Log records' do
  938. expect do
  939. user.update(mobile: '2345678901')
  940. Observer::Transaction.commit
  941. Scheduler.worker(true)
  942. end.not_to change { logs.map(&:reload).map(&:attributes) }
  943. end
  944. end
  945. end
  946. end
  947. end
  948. end
  949. end