user_spec.rb 63 KB

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