user_spec.rb 60 KB

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