user_spec.rb 54 KB

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