user_spec.rb 52 KB

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