user_spec.rb 49 KB

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