user_spec.rb 53 KB

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