out_of_office_spec.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe User::OutOfOffice, type: :model do
  4. subject(:user) { create(:user) }
  5. let(:agent) { create(:agent) }
  6. describe 'Instance methods:' do
  7. describe '#out_of_office?' do
  8. context 'without any out_of_office_* attributes set' do
  9. it 'returns false' do
  10. expect(agent.out_of_office?).to be(false)
  11. end
  12. end
  13. context 'with valid #out_of_office_* attributes' do
  14. before do
  15. agent.update(
  16. out_of_office_start_at: Time.current.yesterday,
  17. out_of_office_end_at: Time.current.tomorrow,
  18. out_of_office_replacement_id: 1
  19. )
  20. end
  21. context 'when #out_of_office: false' do
  22. before { agent.update(out_of_office: false) }
  23. it 'returns false' do
  24. expect(agent.out_of_office?).to be(false)
  25. end
  26. end
  27. context 'when #out_of_office: true' do
  28. before { agent.update(out_of_office: true) }
  29. it 'returns true' do
  30. expect(agent.out_of_office?).to be(true)
  31. end
  32. context 'when the #out_of_office_end_at time has passed' do
  33. before { travel 2.days }
  34. it 'returns false (even though #out_of_office has not changed)' do
  35. expect(agent).to have_attributes(
  36. out_of_office: true,
  37. out_of_office?: false
  38. )
  39. end
  40. end
  41. end
  42. end
  43. context 'when date range is inclusive' do
  44. before do
  45. freeze_time
  46. agent.update(
  47. out_of_office: true,
  48. out_of_office_start_at: 1.day.from_now.to_date,
  49. out_of_office_end_at: 1.week.from_now.to_date,
  50. out_of_office_replacement_id: 1
  51. )
  52. end
  53. it 'today in office' do
  54. expect(agent).not_to be_out_of_office
  55. end
  56. it 'tomorrow not in office' do
  57. travel 1.day
  58. expect(agent).to be_out_of_office
  59. end
  60. it 'after 7 days not in office' do
  61. travel 7.days
  62. expect(agent).to be_out_of_office
  63. end
  64. it 'after 8 days in office' do
  65. travel 8.days
  66. expect(agent).not_to be_out_of_office
  67. end
  68. end
  69. # https://github.com/zammad/zammad/issues/3590
  70. context 'when setting the same date' do
  71. before do
  72. freeze_time
  73. target_date = 1.day.from_now.to_date
  74. agent.update(
  75. out_of_office: true,
  76. out_of_office_start_at: target_date,
  77. out_of_office_end_at: target_date,
  78. out_of_office_replacement_id: 1
  79. )
  80. end
  81. it 'agent is out of office tomorrow' do
  82. travel 1.day
  83. expect(agent).to be_out_of_office
  84. end
  85. it 'agent is not out of office the day after tomorrow' do
  86. travel 2.days
  87. expect(agent).not_to be_out_of_office
  88. end
  89. it 'agent is not out of office today' do
  90. expect(agent).not_to be_out_of_office
  91. end
  92. describe 'it respects system time zone' do
  93. before do
  94. travel_to Time.current.end_of_day
  95. end
  96. it 'agent is in office if in UTC' do
  97. expect(agent).not_to be_out_of_office
  98. end
  99. it 'agent is out of office if ahead of UTC' do
  100. travel_to Time.current.end_of_day
  101. Setting.set('timezone_default', 'Europe/Vilnius')
  102. expect(agent).to be_out_of_office
  103. end
  104. end
  105. end
  106. end
  107. describe '#someones_out_of_office_replacement?' do
  108. it 'returns true when is replacing someone' do
  109. create(:agent).update!(
  110. out_of_office: true,
  111. out_of_office_start_at: 1.day.ago,
  112. out_of_office_end_at: 1.day.from_now,
  113. out_of_office_replacement_id: user.id,
  114. )
  115. expect(user).to be_someones_out_of_office_replacement
  116. end
  117. it 'returns false when is not replacing anyone' do
  118. expect(user).not_to be_someones_out_of_office_replacement
  119. end
  120. end
  121. describe '#out_of_office_agent' do
  122. it { is_expected.to respond_to(:out_of_office_agent) }
  123. context 'when user has no designated substitute' do
  124. it 'returns nil' do
  125. expect(user.out_of_office_agent).to be_nil
  126. end
  127. end
  128. context 'when user has designated substitute' do
  129. subject(:user) do
  130. create(:user,
  131. out_of_office: out_of_office,
  132. out_of_office_start_at: Time.zone.yesterday,
  133. out_of_office_end_at: Time.zone.tomorrow,
  134. out_of_office_replacement_id: substitute.id,)
  135. end
  136. let(:substitute) { create(:user) }
  137. context 'when is not out of office' do
  138. let(:out_of_office) { false }
  139. it 'returns nil' do
  140. expect(user.out_of_office_agent).to be_nil
  141. end
  142. end
  143. context 'when is out of office' do
  144. let(:out_of_office) { true }
  145. it 'returns the designated substitute' do
  146. expect(user.out_of_office_agent).to eq(substitute)
  147. end
  148. end
  149. context 'with recursive out of office structure' do
  150. let(:out_of_office) { true }
  151. let(:substitute) do
  152. create(:user,
  153. out_of_office: out_of_office,
  154. out_of_office_start_at: Time.zone.yesterday,
  155. out_of_office_end_at: Time.zone.tomorrow,
  156. out_of_office_replacement_id: user_active.id,)
  157. end
  158. let!(:user_active) { create(:user) }
  159. it 'returns the designated substitute recursive' do
  160. expect(user.out_of_office_agent).to eq(user_active)
  161. end
  162. end
  163. context 'with recursive out of office structure with a endless loop' do
  164. let(:out_of_office) { true }
  165. let(:substitute) do
  166. create(:user,
  167. out_of_office: out_of_office,
  168. out_of_office_start_at: Time.zone.yesterday,
  169. out_of_office_end_at: Time.zone.tomorrow,
  170. out_of_office_replacement_id: user_active.id,)
  171. end
  172. let!(:user_active) do
  173. create(:user,
  174. out_of_office: out_of_office,
  175. out_of_office_start_at: Time.zone.yesterday,
  176. out_of_office_end_at: Time.zone.tomorrow,
  177. out_of_office_replacement_id: agent.id,)
  178. end
  179. before do
  180. user_active.update(out_of_office_replacement_id: substitute.id)
  181. end
  182. it 'returns the designated substitute recursive with a endless loop' do
  183. expect(user.out_of_office_agent).to eq(substitute)
  184. end
  185. end
  186. context 'with stack depth exceeding limit' do
  187. let(:replacement_chain) do
  188. user = create(:agent)
  189. 14
  190. .times
  191. .each_with_object([user]) do |_, memo|
  192. memo << create(:agent, :ooo, ooo_agent: memo.last)
  193. end
  194. .reverse
  195. end
  196. let(:ids_executed) { [] }
  197. before do
  198. allow_any_instance_of(User).to receive(:out_of_office_agent).and_wrap_original do |method, **kwargs|
  199. ids_executed << method.receiver.id
  200. method.call(**kwargs)
  201. end
  202. allow(Rails.logger).to receive(:warn)
  203. end
  204. it 'returns the last agent at the limit' do
  205. expect(replacement_chain.first.out_of_office_agent).to eq replacement_chain[10]
  206. end
  207. it 'does not evaluate element beyond the limit' do
  208. user_beyond_limit = replacement_chain[11]
  209. replacement_chain.first.out_of_office_agent
  210. expect(ids_executed).not_to include(user_beyond_limit.id)
  211. end
  212. it 'does evaluate element within the limit' do
  213. user_within_limit = replacement_chain[5]
  214. replacement_chain.first.out_of_office_agent
  215. expect(ids_executed).to include(user_within_limit.id)
  216. end
  217. it 'logs error below the limit' do
  218. replacement_chain.first.out_of_office_agent
  219. expect(Rails.logger).to have_received(:warn).with(%r{#{Regexp.escape('Found more than 10 replacement levels for agent')}})
  220. end
  221. it 'does not logs warn within the limit' do
  222. replacement_chain[10].out_of_office_agent
  223. expect(Rails.logger).not_to have_received(:warn)
  224. end
  225. end
  226. end
  227. end
  228. describe '#out_of_office_agent_of' do
  229. context 'when no other agents are out-of-office' do
  230. it 'returns an empty ActiveRecord::Relation' do
  231. expect(agent.out_of_office_agent_of)
  232. .to be_an(ActiveRecord::Relation)
  233. .and be_empty
  234. end
  235. end
  236. context 'when designated as the substitute' do
  237. let!(:agent_on_holiday) do
  238. create(
  239. :agent,
  240. out_of_office_start_at: Time.current.yesterday,
  241. out_of_office_end_at: Time.current.tomorrow,
  242. out_of_office_replacement_id: agent.id,
  243. out_of_office: out_of_office
  244. )
  245. end
  246. context 'with an in-office agent' do
  247. let(:out_of_office) { false }
  248. it 'returns an empty ActiveRecord::Relation' do
  249. expect(agent.out_of_office_agent_of)
  250. .to be_an(ActiveRecord::Relation)
  251. .and be_empty
  252. end
  253. end
  254. context 'with an out-of-office agent' do
  255. let(:out_of_office) { true }
  256. it 'returns an ActiveRecord::Relation including that agent' do
  257. expect(agent.out_of_office_agent_of)
  258. .to contain_exactly(agent_on_holiday)
  259. end
  260. end
  261. context 'when inherited' do
  262. let(:out_of_office) { true }
  263. let!(:agent_on_holiday_sub) do
  264. create(
  265. :agent,
  266. out_of_office_start_at: Time.current.yesterday,
  267. out_of_office_end_at: Time.current.tomorrow,
  268. out_of_office_replacement_id: agent_on_holiday.id,
  269. out_of_office: out_of_office
  270. )
  271. end
  272. it 'returns an ActiveRecord::Relation including both agents' do
  273. expect(agent.out_of_office_agent_of)
  274. .to contain_exactly(agent_on_holiday, agent_on_holiday_sub)
  275. end
  276. end
  277. context 'when inherited endless loop' do
  278. let(:out_of_office) { true }
  279. let!(:agent_on_holiday_sub) do
  280. create(
  281. :agent,
  282. out_of_office_start_at: Time.current.yesterday,
  283. out_of_office_end_at: Time.current.tomorrow,
  284. out_of_office_replacement_id: agent_on_holiday.id,
  285. out_of_office: out_of_office
  286. )
  287. end
  288. let!(:agent_on_holiday_sub2) do
  289. create(
  290. :agent,
  291. out_of_office_start_at: Time.current.yesterday,
  292. out_of_office_end_at: Time.current.tomorrow,
  293. out_of_office_replacement_id: agent_on_holiday_sub.id,
  294. out_of_office: out_of_office
  295. )
  296. end
  297. before do
  298. agent_on_holiday_sub.update(out_of_office_replacement_id: agent_on_holiday_sub2.id)
  299. end
  300. it 'returns an ActiveRecord::Relation including both agents referencing each other' do
  301. expect(agent_on_holiday_sub.out_of_office_agent_of)
  302. .to contain_exactly(agent_on_holiday_sub, agent_on_holiday_sub2)
  303. end
  304. end
  305. end
  306. end
  307. end
  308. end