crono 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -3
  3. data/Rakefile +3 -4
  4. data/app/assets/javascripts/crono/materialize.min.js +6 -0
  5. data/app/assets/stylesheets/crono/application.css +26 -0
  6. data/app/assets/stylesheets/crono/materialize.min.css +31 -0
  7. data/app/controllers/crono/application_controller.rb +5 -0
  8. data/app/controllers/crono/jobs_controller.rb +11 -0
  9. data/app/models/crono/application_record.rb +5 -0
  10. data/{lib/crono/orm/active_record → app/models/crono}/crono_job.rb +1 -2
  11. data/app/views/crono/jobs/index.html.erb +50 -0
  12. data/app/views/crono/jobs/show.html.erb +16 -0
  13. data/app/views/layouts/crono/application.html.erb +31 -0
  14. data/{exe → bin}/crono +0 -0
  15. data/config/routes.rb +4 -0
  16. data/lib/crono/cli.rb +3 -20
  17. data/lib/crono/config.rb +2 -3
  18. data/lib/crono/engine.rb +15 -0
  19. data/lib/crono/job.rb +10 -6
  20. data/lib/crono/performer_proxy.rb +2 -2
  21. data/lib/crono/period.rb +6 -5
  22. data/lib/crono/time_of_day.rb +2 -2
  23. data/lib/crono/version.rb +1 -1
  24. data/lib/crono.rb +3 -3
  25. data/lib/generators/crono/install/install_generator.rb +12 -1
  26. data/lib/generators/crono/install/templates/migrations/create_crono_jobs.rb +2 -6
  27. data/spec/assets/bad_cronotab.rb +12 -0
  28. data/spec/assets/good_cronotab.rb +9 -0
  29. data/spec/cli_spec.rb +109 -0
  30. data/spec/config_spec.rb +47 -0
  31. data/spec/crono_spec.rb +7 -0
  32. data/spec/cronotab_spec.rb +20 -0
  33. data/spec/internal/:memory +0 -0
  34. data/{log/.keep → spec/internal/app/assets/config/manifest.js} +0 -0
  35. data/spec/internal/app/controllers/application_controller.rb +3 -0
  36. data/spec/internal/app/controllers/pages_controller.rb +5 -0
  37. data/spec/internal/app/views/pages/index.html.erb +1 -0
  38. data/spec/internal/config/application.rb +22 -0
  39. data/spec/internal/config/boot.rb +5 -0
  40. data/spec/internal/config/database.yml +3 -0
  41. data/spec/internal/config/environment.rb +2 -0
  42. data/spec/internal/config/routes.rb +3 -0
  43. data/spec/internal/config/storage.yml +3 -0
  44. data/spec/internal/db/crono_test.sqlite +0 -0
  45. data/spec/internal/db/schema.rb +10 -0
  46. data/spec/internal/log/test.log +3868 -0
  47. data/{tmp/.gitkeep → spec/internal/public/favicon.ico} +0 -0
  48. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/-5/-5qRtN26mFn5ud6yyw5E5MpFhT61YcDJPMTlAoDLkEs.cache +2 -0
  49. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/-C/-C4fFLLfC1HB9flvtZbgPTj9oNiDtq1bvp8LPbnRv1k.cache +0 -0
  50. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/01/01tpm-y3svQKpsPvOHFWMxWBaG_35EVrgmnB5HKppZw.cache +1 -0
  51. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/0K/0KEVKDTACcfyL6Fl5QZ6qSNOeswfd6FloUlWXQvwsHg.cache +0 -0
  52. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1F/1FwwOzgRWJEUxIctLIsrhuh9z3QKl1XWDCcdfRhgKPk.cache +2 -0
  53. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/1n/1n5Wp1qC4BYhgEaXBnkRpO1N0oVAwLVdn47Ebk5nduY.cache +1 -0
  54. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/22/22NEh4OGcNmK5Qh9ss73s622T6309DH8zv5ABF6-6Uk.cache +0 -0
  55. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/2n/2nEu77IGZHZPjlliLtJofREUZgmt-DGEhQnGIDMd0vA.cache +1 -0
  56. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/2q/2qyBEUowJIKm_1EF_tbDWLjGDvVCbTjiHtRX_PEVn-U.cache +1 -0
  57. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/3k/3khLNNSEoPg-2idoNmihX2Ba1loklzJsXvSRXujPaRk.cache +1 -0
  58. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/7A/7A_YhyGyNk4Me5J0-p-XwK0qelTclb_2WX5pq0eY9dY.cache +1 -0
  59. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/8d/8daZARHc9PDr5TZ5fqfR-_vJ5kCRzsZQKhQxFkrg_XI.cache +0 -0
  60. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/90/90vzKM32bOX3CR_IP7i2_AHgZSmn82XN_9gLyqidtUc.cache +2 -0
  61. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/9V/9V7dOKixQfEwAEIweL7rD4CUSL51MqiMeB67I5xhwXs.cache +0 -0
  62. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/9a/9aYqlHyIyZYckdfIJ3GbxrCRrsRgbc0wfZBCfd1lVWE.cache +0 -0
  63. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ac/AcdbxQ2RjSGjrVJSdCjS-HN_RI3GZbW86dZGhSD8Ots.cache +0 -0
  64. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Cj/Cjzu7j1PyqHJD0alH5TiAYfJDN0Zbxhg4zFZegEzuYM.cache +3 -0
  65. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/DC/DC_euXbiKWPXx4qSkPGLNU4C2RIvjMBWliBrTgl6Mqo.cache +1 -0
  66. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Db/DboywI_AoqjU7aQB5p90ZP76JW8sinmm4tMgjEG8Iu8.cache +1 -0
  67. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Dz/DzN3uO_usT53JAsXp4Td8TcNu4kjXPgqTUbLJUG7220.cache +0 -0
  68. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/F3/F3BxEXMKcd4KO0HPnLrN1g6pJIJ_iQXCz9vW--uEixY.cache +1 -0
  69. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/FE/FEVuJQTU_YF19u2IzUvuuYlUOoKsyk4JA9kX5UCIelU.cache +2 -0
  70. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/GG/GGT27f9SL2RsHpi8_pEJ1NHNgLBqDui3sDZOTgeD8z0.cache +0 -0
  71. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Gj/Gj2Go21wf2SZAnyJXKNSGfiGzYXAtrIdSHB2wuGrB6k.cache +0 -0
  72. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Hi/HicniSJ1JdM-SmZBY-KtuXL6pubtiugbtOJ-qF07hfk.cache +1 -0
  73. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Hm/HmvhHkuG82Y4O19j_mfQOw9Q4VEq5hW1Ny0Pzh3gY00.cache +3 -0
  74. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ic/IcQIS6wTKG3ZKN6foQYUMxNU4dNQRhrAdwVrAi8u11k.cache +0 -0
  75. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Kz/KzqcEtJH4QmChDeq7W5WHCOe_NOjqNrAWOzOI0b0RrU.cache +0 -0
  76. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/LE/LE5Is_h8o9DKajRisizRsovSfpVc8T-Z_xScq0YxBMg.cache +1 -0
  77. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Mc/Mcvqw-yzpBv2GpUMmKfPZed-6wXUYd8rDdGJ12xAEsA.cache +0 -0
  78. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Nn/NneCOb3TsKS1FT6ImmBbFe5UuI_LxAmaYyk5o3st8jc.cache +1 -0
  79. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/Ro/RogqNsDfrMolvoqWLn8lCbweCzxIhN-fgXnLfBMFo8A.cache +0 -0
  80. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/T9/T9uigSdiu4wxaUieqeBq4uQYRdwGsT2acN15p0osfMU.cache +3 -0
  81. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/TH/THqp0Tr7zg3pz3WISBe0_u-QcPXHIesH0WlH_6zKzco.cache +1 -0
  82. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/VF/VFaPzl3--nBqsNzvUgJUt2TjANPLzaelSx_eOc_J090.cache +1 -0
  83. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/XG/XG0dD9kx-qBYVC-WwhzECOTsSICqvnAqLOCq1JWWrRI.cache +2 -0
  84. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ZT/ZTJFqWngu6gOr5GuiqAV52YNHRuJeuwQelNDhtm8pKU.cache +1 -0
  85. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ZT/zT_x9gWE8eLO9s8Ul_dtwvAqzekg3DpeuSRkQepkKJ4.cache +2 -0
  86. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/aW/aWSe6QsJ8Dk7ZAuTOYgjegrAoC2XYQtCCeo9KVzVVQs.cache +2 -0
  87. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/at/ataSdIHBESkY5GN7sMG-XyOftCkIWDK0QaKEewVkqIU.cache +2 -0
  88. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/au/aukwvh3eZhFFGyc5qZWT9KMJQHxFrTDEqfjXr4VM4dc.cache +3 -0
  89. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/bu/bu6XdfzbXfiTnSQ_6Shml3tpB7ovoUAcuCGNs-eYehI.cache +3 -0
  90. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/c2/c2yubRDZmdi6tFc__QitGtUTkpKt0Goc7FNKhRNw0Uk.cache +2 -0
  91. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/fu/fuVeiMpLNyElLwjOR6EBQP7-ZUwuJTsQe_Ie11TlX7Y.cache +3 -0
  92. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/hY/hY9drCAzyxN-KBhXav5VLmk4-AFlKT2yguKNLm7wGsA.cache +3 -0
  93. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/hg/hgkvHB4OV3E0Gq-Ud_lH2ljTzyb-8nGquMAFTBr4O0o.cache +2 -0
  94. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/jV/jVXcSpsjtyBHtJaFkxwLVcVm79OT5CVEA9XuzV01O2o.cache +2 -0
  95. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kK/kKAy2xIb8ozBaCVqr7LkIsO3-78l5U08uq1Ot7JvES0.cache +0 -0
  96. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kU/kUed8S8DBmWvnTdMtg0PzTYj51Rbq9R9yfr6bhPzp2I.cache +1 -0
  97. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/kw/kwNFhW24nxXy_HfVKYdXJyRqhu7ljgl9wR2DcJUo-nA.cache +3 -0
  98. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/ls/ls-nV5B50MgpIYfcYZWWWjiNsU2tD2DrEbiiEmGtArk.cache +0 -0
  99. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/m4/m47fbWgjCvvfU0XLMaGJE1zb9zh2STuMZFp7umYjLTA.cache +1 -0
  100. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/m_/m_VPZpr_Dhvko_jzP2-4VRhDpknOtciX8pqAq3jY4P8.cache +3 -0
  101. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/n5/n5rslESPawJxj7Ii3QNWxFl-2sUJE4leiEyzbuFE9Mc.cache +1 -0
  102. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/nz/nzkJWHKhKCtwI9w3TBcwyGzKEik4UFYwulf2jO7xPXc.cache +2 -0
  103. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/o4/o42lMuA3eKF-eY5RsJO4kHIGfba2QHOCO0b9rz5iNIU.cache +0 -0
  104. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/oF/oFrXjOEKX9hseSPY19vWwGHfET4QpdjGD5UdFIEN8mo.cache +0 -0
  105. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/od/od3K7nGVNq7YsrTVF7ItWWkHs8F_XEGn_BlCD5wW_gc.cache +1 -0
  106. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/qo/qoEv51qzi0h4mpNP5B4vQydW2FmhC_vbbx6vWJ99Jls.cache +1 -0
  107. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/tQ/tQ-nAfm23F-ckwkWPuftbJS6tumDQhNxQgPIZSrfPM0.cache +1 -0
  108. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/tr/trn1zjMZoBOIgSYgI84OzddhfICB2KnU4XbV8axLVGo.cache +1 -0
  109. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/u3/u3VRW5zPBM7BcNRi3G4B8M6CdscKSLJ3xYmp44ekmjU.cache +0 -0
  110. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/uG/UgcLSZ8AKbE1AYf75cSUXI6uefPECNfBcVQ7L0BDzHg.cache +3 -0
  111. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/uG/uGhNDzX7DxymN3xqHLHTndSae3bN0A-MlTk05m6wV4A.cache +1 -0
  112. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/wF/wFT7zoeQQwz-V8syWaS01xr7xgZWr3UNQLWbYSKXirw.cache +1 -0
  113. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/wN/wNw4kAG0E_RrvLeFAizMmTp_0jh-HGw1ONLtRyw92z0.cache +2 -0
  114. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/x-/x-qCUFpNxK1jTHH2qV_VgVk_KrGKdHzNGOnDMVFstuQ.cache +1 -0
  115. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/yk/ykU9I1MzGh7clO4pu853ZKl4LtMlDtyr8BjpiozFQ8E.cache +2 -0
  116. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/yk/ykWLbyEuqa0JHcwHJ-SdVxv4ImwnymlVxvbPKtk6HUM.cache +1 -0
  117. data/spec/internal/tmp/cache/assets/sprockets/v4.0.0/zO/zOr1jCikukgv8-bNHvK43vGLa3CSjtmiwf2K7dUjTnw.cache +0 -0
  118. data/spec/job_spec.rb +183 -0
  119. data/spec/models/crono/crono_job_spec.rb +31 -0
  120. data/spec/performer_proxy_spec.rb +39 -0
  121. data/spec/period_spec.rb +141 -0
  122. data/spec/rails_helper.rb +89 -0
  123. data/spec/scheduler_spec.rb +62 -0
  124. data/spec/spec_helper.rb +19 -0
  125. data/spec/tasks/crono_tasks_spec.rb +23 -0
  126. data/spec/web_spec.rb +61 -0
  127. metadata +233 -68
  128. data/.gitignore +0 -10
  129. data/.rspec +0 -2
  130. data/.travis.yml +0 -14
  131. data/Changes.md +0 -88
  132. data/Gemfile +0 -4
  133. data/Gemfile.lock +0 -78
  134. data/NOTICE +0 -2
  135. data/README.md +0 -218
  136. data/bin/console +0 -14
  137. data/bin/setup +0 -7
  138. data/crono.gemspec +0 -34
  139. data/examples/crono_web_ui.png +0 -0
  140. data/examples/cronotab.rb +0 -14
  141. data/examples/monitrc.conf +0 -6
  142. data/lib/crono/web.rb +0 -22
  143. data/web/assets/css/custom.css +0 -19
  144. data/web/assets/css/materialize.min.css +0 -16
  145. data/web/assets/font/material-design-icons/LICENSE.txt +0 -428
  146. data/web/assets/font/material-design-icons/Material-Design-Icons.eot +0 -0
  147. data/web/assets/font/material-design-icons/Material-Design-Icons.svg +0 -751
  148. data/web/assets/font/material-design-icons/Material-Design-Icons.ttf +0 -0
  149. data/web/assets/font/material-design-icons/Material-Design-Icons.woff +0 -0
  150. data/web/assets/font/material-design-icons/Material-Design-Icons.woff2 +0 -0
  151. data/web/assets/font/roboto/Roboto-Bold.ttf +0 -0
  152. data/web/assets/font/roboto/Roboto-Bold.woff +0 -0
  153. data/web/assets/font/roboto/Roboto-Bold.woff2 +0 -0
  154. data/web/assets/font/roboto/Roboto-Light.ttf +0 -0
  155. data/web/assets/font/roboto/Roboto-Light.woff +0 -0
  156. data/web/assets/font/roboto/Roboto-Light.woff2 +0 -0
  157. data/web/assets/font/roboto/Roboto-Medium.ttf +0 -0
  158. data/web/assets/font/roboto/Roboto-Medium.woff +0 -0
  159. data/web/assets/font/roboto/Roboto-Medium.woff2 +0 -0
  160. data/web/assets/font/roboto/Roboto-Regular.ttf +0 -0
  161. data/web/assets/font/roboto/Roboto-Regular.woff +0 -0
  162. data/web/assets/font/roboto/Roboto-Regular.woff2 +0 -0
  163. data/web/assets/font/roboto/Roboto-Thin.ttf +0 -0
  164. data/web/assets/font/roboto/Roboto-Thin.woff +0 -0
  165. data/web/assets/font/roboto/Roboto-Thin.woff2 +0 -0
  166. data/web/assets/js/jquery-2.1.3.min.js +0 -4
  167. data/web/assets/js/materialize.min.js +0 -10
  168. data/web/views/dashboard.haml +0 -36
  169. data/web/views/job.haml +0 -15
  170. data/web/views/layout.haml +0 -29
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Crono::Period do
4
+ describe '#description' do
5
+ it 'should return period description' do
6
+ @period = Crono::Period.new(1.week, on: :monday, at: '15:20')
7
+ expected_description = if ActiveSupport::VERSION::MAJOR >= 5
8
+ 'every 1 week at 15:20 on Monday'
9
+ else
10
+ 'every 7 days at 15:20 on Monday'
11
+ end
12
+ expect(@period.description).to be_eql(expected_description)
13
+ end
14
+ end
15
+
16
+ describe '#next' do
17
+ context 'in weakly basis' do
18
+ it "should raise error if 'on' is wrong" do
19
+ expect { @period = Crono::Period.new(7.days, on: :bad_day) }
20
+ .to raise_error("Wrong 'on' day")
21
+ end
22
+
23
+ it 'should raise error when period is less than 1 week' do
24
+ expect { @period = Crono::Period.new(6.days, on: :monday) }
25
+ .to raise_error("period should be at least 1 week to use 'on'")
26
+ end
27
+
28
+ it "should return a 'on' day" do
29
+ @period = Crono::Period.new(1.week, on: :thursday, at: '15:30')
30
+ current_week = Time.zone.now.beginning_of_week
31
+ last_run_time = current_week.advance(days: 1) # last run on the tuesday
32
+ next_run_at = Time.zone.now.next_week.advance(days: 3)
33
+ .change(hour: 15, min: 30)
34
+ expect(@period.next(since: last_run_time)).to be_eql(next_run_at)
35
+ end
36
+
37
+ it "should return a next week day 'on'" do
38
+ @period = Crono::Period.new(1.week, on: :thursday)
39
+ Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 4)) do
40
+ expect(@period.next).to be_eql(Time.zone.now.next_week.advance(days: 3))
41
+ end
42
+ end
43
+
44
+ it 'should return a current week day on the first run if not too late' do
45
+ @period = Crono::Period.new(7.days, on: :tuesday)
46
+ beginning_of_the_week = Time.zone.now.beginning_of_week
47
+ tuesday = beginning_of_the_week.advance(days: 1)
48
+ Timecop.freeze(beginning_of_the_week) do
49
+ expect(@period.next).to be_eql(tuesday)
50
+ end
51
+ end
52
+
53
+ it 'should return today on the first run if not too late' do
54
+ @period = Crono::Period.new(1.week, on: :sunday, at: '22:00')
55
+ Timecop.freeze(Time.zone.now.beginning_of_week.advance(days: 6)
56
+ .change(hour: 21, min: 0)) do
57
+ expect(@period.next).to be_eql(
58
+ Time.zone.now.beginning_of_week.advance(days: 6).change(hour: 22, min: 0)
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'in daily basis' do
65
+ it "should return Time.zone.now if the next time in past" do
66
+ @period = Crono::Period.new(1.day, at: '06:00')
67
+ expect(@period.next(since: 2.days.ago).to_s).to be_eql(Time.zone.now.to_s)
68
+ end
69
+
70
+ it 'should return time 2 days from now' do
71
+ @period = Crono::Period.new(2.day)
72
+ expect(@period.next.to_s).to be_eql(2.days.from_now.to_s)
73
+ end
74
+
75
+ it "should set time to 'at' time as a string" do
76
+ time = 10.minutes.ago
77
+ at = [time.hour, time.min].join(':')
78
+ @period = Crono::Period.new(2.days, at: at)
79
+ expect(@period.next.to_s).to be_eql(2.days.from_now.change(hour: time.hour, min: time.min).to_s)
80
+ end
81
+
82
+ it "should set time to 'at' time as a hash" do
83
+ time = 10.minutes.ago
84
+ at = { hour: time.hour, min: time.min }
85
+ @period = Crono::Period.new(2.days, at: at)
86
+ expect(@period.next.to_s).to be_eql(2.days.from_now.change(at).to_s)
87
+ end
88
+
89
+ it "should raise error when 'at' is wrong" do
90
+ expect {
91
+ Crono::Period.new(2.days, at: 1)
92
+ }.to raise_error("Unknown 'at' format")
93
+ end
94
+
95
+ it 'should raise error when period is less than 1 day' do
96
+ expect {
97
+ Crono::Period.new(5.hours, at: '15:30')
98
+ }.to raise_error("period should be at least 1 day to use 'at' with specified hour")
99
+ end
100
+
101
+ it 'should return time in relation to last time' do
102
+ @period = Crono::Period.new(2.days)
103
+ expect(@period.next(since: 1.day.ago).to_s).to be_eql(1.day.from_now.to_s)
104
+ end
105
+
106
+ it 'should return today time if it is first run and not too late' do
107
+ time = 10.minutes.from_now
108
+ at = { hour: time.hour, min: time.min }
109
+ @period = Crono::Period.new(2.days, at: at)
110
+ expect(@period.next.utc.to_s).to be_eql(Time.zone.now.change(at).utc.to_s)
111
+ end
112
+ end
113
+
114
+ context 'in hourly basis' do
115
+ it 'should return next hour minutes if current hour minutes passed' do
116
+ Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 20)) do
117
+ @period = Crono::Period.new(1.hour, at: { min: 15 })
118
+ expect(@period.next.utc.to_s).to be_eql 1.hour.from_now.beginning_of_hour.advance(minutes: 15).utc.to_s
119
+ end
120
+ end
121
+
122
+ it 'should return current hour minutes if current hour minutes not passed yet' do
123
+ Timecop.freeze(Time.zone.now.beginning_of_hour.advance(minutes: 10)) do
124
+ @period = Crono::Period.new(1.hour, at: { min: 15 })
125
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.beginning_of_hour.advance(minutes: 15).utc.to_s
126
+ end
127
+ end
128
+
129
+ it 'should return next hour minutes within the given interval' do
130
+ Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
131
+ @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '08:00-16:00')
132
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.tomorrow.change(hour: 8, min: 15).utc.to_s
133
+ end
134
+ Timecop.freeze(Time.zone.now.change(hour: 16, min: 10)) do
135
+ @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '23:00-07:00')
136
+ expect(@period.next.utc.to_s).to be_eql Time.zone.now.change(hour: 23, min: 15).utc.to_s
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,89 @@
1
+ # This file is copied to spec/ when you run 'rails generate rspec:install'
2
+ require 'spec_helper'
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+ require File.expand_path('../spec/internal/config/environment.rb', __dir__)
5
+ # Prevent database truncation if the environment is production
6
+ abort('The Rails environment is running in production mode!') if Rails.env.production?
7
+ require 'rspec/rails'
8
+ # Add additional requires below this line. Rails is not loaded until this point!
9
+ require 'bundler/setup'
10
+ Bundler.setup
11
+
12
+ $LOAD_PATH.unshift File.expand_path('../../lib', __dir__)
13
+
14
+ require 'timecop'
15
+ require 'byebug'
16
+ require 'crono'
17
+
18
+ # setting default time zone
19
+ # In Rails project, Time.zone_default equals "UTC"
20
+ Time.zone_default = Time.find_zone('UTC')
21
+
22
+ ActiveRecord::Base.logger = Logger.new($stdout)
23
+
24
+ ActiveRecord::Base.establish_connection(
25
+ adapter: 'sqlite3',
26
+ database: ':memory'
27
+ )
28
+ ActiveRecord::Schema.define do
29
+ require_relative '../lib/generators/crono/install/templates/migrations/create_crono_jobs.rb'
30
+ @migration_version = '[6.1]'
31
+ end
32
+ CreateCronoJobs.up
33
+
34
+
35
+ # Requires supporting ruby files with custom matchers and macros, etc, in
36
+ # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
37
+ # run as spec files by default. This means that files in spec/support that end
38
+ # in _spec.rb will both be required and run as specs, causing the specs to be
39
+ # run twice. It is recommended that you do not name files matching this glob to
40
+ # end with _spec.rb. You can configure this pattern with the --pattern
41
+ # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
42
+ #
43
+ # The following line is provided for convenience purposes. It has the downside
44
+ # of increasing the boot-up time by auto-requiring all files in the support
45
+ # directory. Alternatively, in the individual `*_spec.rb` files, manually
46
+ # require only the support files necessary.
47
+ #
48
+ # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
49
+
50
+ # Checks for pending migrations and applies them before tests are run.
51
+ # If you are not using ActiveRecord, you can remove these lines.
52
+ begin
53
+ ActiveRecord::Migration.maintain_test_schema!
54
+ rescue ActiveRecord::PendingMigrationError => e
55
+ puts e.to_s.strip
56
+ exit 1
57
+ end
58
+ RSpec.configure do |config|
59
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
60
+ # config.fixture_path = "#{::Rails.root}/spec/fixtures"
61
+
62
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
63
+ # examples within a transaction, remove the following line or assign false
64
+ # instead of true.
65
+ # config.use_transactional_fixtures = true
66
+
67
+ # You can uncomment this line to turn off ActiveRecord support entirely.
68
+ # config.use_active_record = false
69
+
70
+ # RSpec Rails can automatically mix in different behaviours to your tests
71
+ # based on their file location, for example enabling you to call `get` and
72
+ # `post` in specs under `spec/controllers`.
73
+ #
74
+ # You can disable this behaviour by removing the line below, and instead
75
+ # explicitly tag your specs with their type, e.g.:
76
+ #
77
+ # RSpec.describe UsersController, type: :controller do
78
+ # # ...
79
+ # end
80
+ #
81
+ # The different available types are documented in the features, such as in
82
+ # https://relishapp.com/rspec/rspec-rails/docs
83
+ config.infer_spec_type_from_file_location!
84
+
85
+ # Filter lines from Rails gems in backtraces.
86
+ config.filter_rails_from_backtrace!
87
+ # arbitrary gems may also be filtered via:
88
+ # config.filter_gems_from_backtrace("gem name")
89
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class TestJob
4
+ def perform
5
+ puts 'Test!'
6
+ end
7
+ end
8
+
9
+ describe Crono::Scheduler do
10
+ let(:scheduler) { Crono::Scheduler.new }
11
+
12
+ describe '#add_job' do
13
+ it 'should call Job#load on Job' do
14
+ @job = Crono::Job.new(TestJob, Crono::Period.new(10.day, at: '04:05'), [])
15
+ expect(@job).to receive(:load)
16
+ scheduler.add_job(@job)
17
+ end
18
+ end
19
+
20
+ describe '#next_jobs' do
21
+ it 'should return next job in schedule' do
22
+ scheduler.jobs = jobs = [
23
+ Crono::Period.new(3.days, at: 10.minutes.from_now.strftime('%H:%M')),
24
+ Crono::Period.new(1.day, at: 20.minutes.from_now.strftime('%H:%M')),
25
+ Crono::Period.new(7.days, at: 40.minutes.from_now.strftime('%H:%M'))
26
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
27
+
28
+ time, jobs = scheduler.next_jobs
29
+ expect(jobs).to be_eql [jobs[0]]
30
+ end
31
+
32
+ it 'should return an array of jobs scheduled at same time with `at`' do
33
+ time = 5.minutes.from_now
34
+ scheduler.jobs = jobs = [
35
+ Crono::Period.new(1.day, at: time.strftime('%H:%M')),
36
+ Crono::Period.new(1.day, at: time.strftime('%H:%M')),
37
+ Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
38
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
39
+
40
+ time, jobs = scheduler.next_jobs
41
+ expect(jobs).to be_eql [jobs[0], jobs[1]]
42
+ end
43
+
44
+ it 'should handle a few jobs scheduled at same time without `at`' do
45
+ scheduler.jobs = jobs = [
46
+ Crono::Period.new(10.seconds),
47
+ Crono::Period.new(10.seconds),
48
+ Crono::Period.new(1.day, at: 10.minutes.from_now.strftime('%H:%M'))
49
+ ].map { |period| Crono::Job.new(TestJob, period, []) }
50
+
51
+ _, next_jobs = scheduler.next_jobs
52
+ expect(next_jobs).to be_eql [jobs[0]]
53
+
54
+ Timecop.travel(4.seconds.from_now)
55
+ expect(Thread).to receive(:new)
56
+ jobs[0].perform
57
+
58
+ _, next_jobs = scheduler.next_jobs
59
+ expect(next_jobs).to be_eql [jobs[1]]
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+
3
+ Bundler.require :default, :development
4
+
5
+ # If you're using all parts of Rails:
6
+ Combustion.initialize! :all
7
+ # Or, load just what you need:
8
+ # Combustion.initialize! :active_record, :action_controller
9
+
10
+ require 'rspec/rails'
11
+ # If you're using Capybara:
12
+ # require 'capybara/rails'
13
+
14
+ RSpec.configure do |config|
15
+ config.use_transactional_fixtures = true
16
+ config.mock_with :rspec do |mocks|
17
+ mocks.allow_message_expectations_on_nil = true
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'rake'
3
+
4
+ load 'tasks/crono_tasks.rake'
5
+ Rake::Task.define_task(:environment)
6
+
7
+ describe 'rake' do
8
+ describe 'crono:clean' do
9
+ it 'should clean unused tasks from DB' do
10
+ Crono::CronoJob.create!(job_id: 'used_job')
11
+ ENV['CRONOTAB'] = File.expand_path('../../assets/good_cronotab.rb', __FILE__)
12
+ Rake::Task['crono:clean'].invoke
13
+ expect(Crono::CronoJob.where(job_id: 'used_job')).not_to exist
14
+ end
15
+ end
16
+
17
+ describe 'crono:check' do
18
+ it 'should check cronotab syntax' do
19
+ ENV['CRONOTAB'] = File.expand_path('../../assets/bad_cronotab.rb', __FILE__)
20
+ expect { Rake::Task['crono:check'].invoke }.to raise_error
21
+ end
22
+ end
23
+ end
data/spec/web_spec.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+ include Rack::Test::Methods
4
+
5
+ describe Crono::Engine do
6
+ let(:app) { Crono::Engine }
7
+
8
+ before do
9
+ Crono::CronoJob.destroy_all
10
+ @test_job_id = 'Perform TestJob every 5 seconds'
11
+ @test_job_log = 'All runs ok'
12
+ @test_job = Crono::CronoJob.create!(
13
+ job_id: @test_job_id,
14
+ log: @test_job_log
15
+ )
16
+ end
17
+
18
+ after { @test_job.destroy }
19
+
20
+ describe '/' do
21
+ it 'should show all jobs' do
22
+ get '/'
23
+ expect(last_response).to be_ok
24
+ expect(last_response.body).to include @test_job_id
25
+ end
26
+
27
+ it 'should show a error mark when a job is unhealthy' do
28
+ @test_job.update(healthy: false, last_performed_at: 10.minutes.ago)
29
+ get '/'
30
+ expect(last_response.body).to include 'Error'
31
+ end
32
+
33
+ it 'should show a success mark when a job is healthy' do
34
+ @test_job.update(healthy: true, last_performed_at: 10.minutes.ago)
35
+ get '/'
36
+ expect(last_response.body).to include 'Success'
37
+ end
38
+
39
+ it 'should show a pending mark when a job is pending' do
40
+ @test_job.update(healthy: nil)
41
+ get '/'
42
+ expect(last_response.body).to include 'Pending'
43
+ end
44
+ end
45
+
46
+ describe '/job/:id' do
47
+ it 'should show job log' do
48
+ get "/jobs/#{@test_job.id}"
49
+ expect(last_response).to be_ok
50
+ expect(last_response.body).to include @test_job_id
51
+ expect(last_response.body).to include @test_job_log
52
+ end
53
+
54
+ it 'should show a message about the unhealthy job' do
55
+ message = 'An error occurs during the last execution of this job'
56
+ @test_job.update(healthy: false)
57
+ get "/jobs/#{@test_job.id}"
58
+ expect(last_response.body).to include message
59
+ end
60
+ end
61
+ end