ruby_raider 4.1.0 → 4.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/e2e_tests.yml +3 -0
- data/README.md +52 -53
- data/RELEASE.md +14 -1
- data/lib/commands/schedule_commands.rb +84 -0
- data/lib/generators/templates/common/git_ignore.tt +1 -0
- data/lib/ruby_raider/runtime/retry_reset.rb +32 -0
- data/lib/ruby_raider/runtime/rspec.rb +1 -0
- data/lib/ruby_raider/runtime/show_browser.rb +53 -0
- data/lib/ruby_raider/runtime.rb +2 -0
- data/lib/ruby_raider.rb +22 -7
- data/lib/utilities/schedules/adapter.rb +22 -0
- data/lib/utilities/schedules/base.rb +27 -0
- data/lib/utilities/schedules/kind.rb +74 -0
- data/lib/utilities/schedules/legacy.rb +196 -0
- data/lib/utilities/schedules/legacy_stub.rb +62 -0
- data/lib/utilities/{flows → schedules}/next_run.rb +2 -2
- data/lib/utilities/{flows → schedules}/notifier.rb +6 -6
- data/lib/utilities/{flows → schedules}/os/command_runner.rb +1 -1
- data/lib/utilities/{flows → schedules}/os/cron.rb +27 -17
- data/lib/utilities/{flows → schedules}/os/launchd.rb +32 -14
- data/lib/utilities/{flows → schedules}/os/linux.rb +23 -5
- data/lib/utilities/{flows → schedules}/os/systemd.rb +33 -17
- data/lib/utilities/{flows → schedules}/os/task_scheduler.rb +36 -20
- data/lib/utilities/schedules/os_enumerator.rb +30 -0
- data/lib/utilities/schedules/project.rb +70 -0
- data/lib/utilities/schedules/rule.rb +162 -0
- data/lib/utilities/schedules/run_lock.rb +160 -0
- data/lib/utilities/schedules/run_log.rb +122 -0
- data/lib/utilities/schedules/run_report.rb +139 -0
- data/lib/utilities/schedules/run_wrapper.rb +216 -0
- data/lib/utilities/schedules/schedule.rb +89 -0
- data/lib/utilities/schedules/schedule_store.rb +53 -0
- data/lib/utilities/schedules/scheduler.rb +178 -0
- data/lib/utilities/schedules/scheduler_steps.rb +69 -0
- data/lib/utilities/schedules/selection.rb +133 -0
- data/lib/utilities/schedules/spawn.rb +68 -0
- data/lib/utilities/{flows/schedule_store.rb → schedules/store.rb} +17 -13
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/commands/schedule_commands.rbs +12 -0
- data/sig/ruby_raider/runtime/retry_reset.rbs +7 -0
- data/sig/ruby_raider/runtime/show_browser.rbs +27 -0
- data/sig/ruby_raider.rbs +4 -0
- data/sig/utilities/schedules/adapter.rbs +5 -0
- data/sig/utilities/schedules/base.rbs +15 -0
- data/sig/utilities/schedules/kind.rbs +32 -0
- data/sig/utilities/schedules/legacy.rbs +45 -0
- data/sig/utilities/schedules/legacy_stub.rbs +15 -0
- data/sig/utilities/{flows → schedules}/next_run.rbs +3 -3
- data/sig/utilities/{flows → schedules}/notifier.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/command_runner.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/cron.rbs +7 -7
- data/sig/utilities/{flows → schedules}/os/launchd.rbs +7 -6
- data/sig/utilities/{flows → schedules}/os/linux.rbs +10 -4
- data/sig/utilities/{flows → schedules}/os/systemd.rbs +8 -7
- data/sig/utilities/{flows → schedules}/os/task_scheduler.rbs +13 -10
- data/sig/utilities/schedules/os_enumerator.rbs +12 -0
- data/sig/utilities/schedules/project.rbs +22 -0
- data/sig/utilities/{flows/schedule.rbs → schedules/rule.rbs} +23 -12
- data/sig/utilities/schedules/run_lock.rbs +29 -0
- data/sig/utilities/schedules/run_log.rbs +33 -0
- data/sig/utilities/schedules/run_report.rbs +34 -0
- data/sig/utilities/schedules/run_wrapper.rbs +45 -0
- data/sig/utilities/schedules/schedule.rbs +27 -0
- data/sig/utilities/schedules/schedule_store.rbs +21 -0
- data/sig/utilities/{flows → schedules}/scheduler.rbs +22 -15
- data/sig/utilities/schedules/scheduler_steps.rbs +22 -0
- data/sig/utilities/schedules/selection.rbs +36 -0
- data/sig/utilities/schedules/spawn.rbs +10 -0
- data/sig/utilities/{flows/schedule_store.rbs → schedules/store.rbs} +14 -9
- data/spec/commands/schedule_commands_spec.rb +284 -0
- data/spec/integration/content/common_content_spec.rb +8 -2
- data/spec/integration/content/gemfile_content_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +0 -63
- data/spec/runtime/retry_reset_spec.rb +112 -0
- data/spec/runtime/show_browser_project_spec.rb +70 -0
- data/spec/runtime/show_browser_spec.rb +101 -0
- data/spec/utilities/schedules/kind_spec.rb +81 -0
- data/spec/utilities/schedules/legacy_spec.rb +433 -0
- data/spec/utilities/schedules/legacy_stub_spec.rb +144 -0
- data/spec/utilities/{flows → schedules}/next_run_spec.rb +5 -5
- data/spec/utilities/{flows → schedules}/notifier_spec.rb +3 -4
- data/spec/utilities/{flows → schedules}/os/command_runner_spec.rb +2 -3
- data/spec/utilities/{flows → schedules}/os/cron_spec.rb +34 -29
- data/spec/utilities/{flows → schedules}/os/launchd_spec.rb +42 -29
- data/spec/utilities/{flows → schedules}/os/linux_spec.rb +27 -13
- data/spec/utilities/schedules/os/list_ids_spec.rb +202 -0
- data/spec/utilities/{flows → schedules}/os/systemd_spec.rb +35 -31
- data/spec/utilities/{flows → schedules}/os/task_scheduler_spec.rb +40 -34
- data/spec/utilities/schedules/os_entry_spec.rb +54 -0
- data/spec/utilities/schedules/os_enumerator_spec.rb +57 -0
- data/spec/utilities/schedules/project_spec.rb +125 -0
- data/spec/utilities/schedules/run_lock_spec.rb +143 -0
- data/spec/utilities/schedules/run_log_spec.rb +83 -0
- data/spec/utilities/schedules/run_report_spec.rb +150 -0
- data/spec/utilities/schedules/run_wrapper_integration_spec.rb +166 -0
- data/spec/utilities/schedules/run_wrapper_spec.rb +430 -0
- data/spec/utilities/schedules/schedule_spec.rb +120 -0
- data/spec/utilities/schedules/schedule_store_spec.rb +151 -0
- data/spec/utilities/schedules/scheduler_launchd_spec.rb +77 -0
- data/spec/utilities/schedules/scheduler_spec.rb +445 -0
- data/spec/utilities/schedules/selection_spec.rb +176 -0
- data/spec/utilities/schedules/spawn_spec.rb +104 -0
- metadata +106 -53
- data/lib/commands/flow_commands.rb +0 -104
- data/lib/generators/templates/flows/flow_helper.tt +0 -42
- data/lib/generators/templates/flows/run.tt +0 -332
- data/lib/utilities/flows/project.rb +0 -170
- data/lib/utilities/flows/run_log.rb +0 -61
- data/lib/utilities/flows/runner.rb +0 -206
- data/lib/utilities/flows/schedule.rb +0 -221
- data/lib/utilities/flows/scheduler.rb +0 -187
- data/sig/commands/flow_commands.rbs +0 -13
- data/sig/utilities/flows/project.rbs +0 -46
- data/sig/utilities/flows/run_log.rbs +0 -22
- data/sig/utilities/flows/runner.rbs +0 -35
- data/spec/commands/flow_commands_spec.rb +0 -128
- data/spec/generators/flow_helper_spec.rb +0 -40
- data/spec/integration/flows/flow_project.rb +0 -62
- data/spec/integration/flows/run_command_spec.rb +0 -91
- data/spec/integration/flows/run_lifecycle_spec.rb +0 -383
- data/spec/utilities/flows/project_spec.rb +0 -178
- data/spec/utilities/flows/run_log_spec.rb +0 -47
- data/spec/utilities/flows/runner_spec.rb +0 -313
- data/spec/utilities/flows/schedule_spec.rb +0 -233
- data/spec/utilities/flows/schedule_store_spec.rb +0 -137
- data/spec/utilities/flows/scheduler_spec.rb +0 -329
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'json'
|
|
4
|
-
require 'stringio'
|
|
5
|
-
require 'tmpdir'
|
|
6
|
-
require_relative '../../../lib/utilities/flows/scheduler'
|
|
7
|
-
|
|
8
|
-
# Flow specs live under spec/utilities/flows, next to the code in lib/utilities/flows
|
|
9
|
-
# The fake adapter, store and streams sit next to the project and scheduler
|
|
10
|
-
# rubocop:disable RSpec/MultipleMemoizedHelpers
|
|
11
|
-
RSpec.describe RubyRaider::Flows::Scheduler do # rubocop:disable RSpec/FilePath
|
|
12
|
-
# Stands in for an OS scheduler: records every call, keeps the installed os_ids
|
|
13
|
-
let(:fake_adapter) do
|
|
14
|
-
Class.new do
|
|
15
|
-
attr_reader :calls, :installed
|
|
16
|
-
attr_accessor :fail_present
|
|
17
|
-
|
|
18
|
-
def initialize
|
|
19
|
-
@calls = []
|
|
20
|
-
@installed = []
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def label = 'fakeos'
|
|
24
|
-
|
|
25
|
-
def install(schedule)
|
|
26
|
-
@calls << [:install, schedule.os_id]
|
|
27
|
-
@installed |= [schedule.os_id]
|
|
28
|
-
true
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def remove(os_id)
|
|
32
|
-
@calls << [:remove, os_id]
|
|
33
|
-
!@installed.delete(os_id).nil?
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def present?(os_id)
|
|
37
|
-
raise RubyRaider::Flows::Error, 'task list unreadable' if fail_present
|
|
38
|
-
|
|
39
|
-
@installed.include?(os_id)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
let(:dir) { File.realpath(Dir.mktmpdir('flows-scheduler')) }
|
|
45
|
-
let(:project) { RubyRaider::Flows::Project.new(dir) }
|
|
46
|
-
let(:adapter) { fake_adapter.new }
|
|
47
|
-
let(:store) { RubyRaider::Flows::ScheduleStore.new(project) }
|
|
48
|
-
let(:out) { StringIO.new }
|
|
49
|
-
let(:err) { StringIO.new }
|
|
50
|
-
# Sun 8 Nov 2026 12:00 local
|
|
51
|
-
let(:now) { Time.local(2026, 11, 8, 12, 0) }
|
|
52
|
-
let(:scheduler) { described_class.new(project, adapter: adapter, store: store, out: out, err: err, now: -> { now }) }
|
|
53
|
-
let(:os_id) { RubyRaider::Flows::Schedule.os_id_for(dir, 'report') }
|
|
54
|
-
|
|
55
|
-
before do
|
|
56
|
-
%w[flows/flow_helper.rb flows/support/run.rb flows/report_flow.rb].each do |file|
|
|
57
|
-
FileUtils.mkdir_p(File.dirname(File.join(dir, file)))
|
|
58
|
-
File.write(File.join(dir, file), '')
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
after { FileUtils.rm_rf(dir) }
|
|
63
|
-
|
|
64
|
-
def log_run(name, line)
|
|
65
|
-
file = File.join(dir, ".raider/flows/#{name}/runs.jsonl")
|
|
66
|
-
FileUtils.mkdir_p(File.dirname(file))
|
|
67
|
-
File.write(file, "#{JSON.generate(line)}\n", mode: 'a')
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
describe '.adapter' do
|
|
71
|
-
let(:runner) { instance_double(RubyRaider::Flows::Os::CommandRunner) }
|
|
72
|
-
|
|
73
|
-
def result(success)
|
|
74
|
-
RubyRaider::Flows::Os::CommandRunner::Result.new(stdout: '', stderr: '', exitstatus: success ? 0 : 1)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it 'picks launchd on macOS and Task Scheduler on Windows without calling them' do
|
|
78
|
-
picked = %w[darwin25 mingw32 mswin64 cygwin].map { |host| described_class.adapter(runner: runner, host_os: host).class }
|
|
79
|
-
|
|
80
|
-
expect(picked).to eq([RubyRaider::Flows::Os::Launchd] + ([RubyRaider::Flows::Os::TaskScheduler] * 3))
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it 'picks the Linux adapter, which labels itself by the scheduler that answers' do
|
|
84
|
-
allow(runner).to receive(:call).and_return(result(true), result(false))
|
|
85
|
-
|
|
86
|
-
picked = 2.times.map { described_class.adapter(runner: runner, host_os: 'linux-gnu') }
|
|
87
|
-
|
|
88
|
-
expect(picked.map { |adapter| [adapter.class, adapter.label] })
|
|
89
|
-
.to eq([[RubyRaider::Flows::Os::Linux, 'systemd'], [RubyRaider::Flows::Os::Linux, 'cron']])
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
it 'refuses another OS' do
|
|
93
|
-
expect { described_class.adapter(runner: runner, host_os: 'solaris2') }
|
|
94
|
-
.to raise_error(RubyRaider::Flows::Error, 'Scheduling flows is not supported on solaris2')
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
describe '#schedule' do
|
|
99
|
-
it 'installs the OS entry, then writes the yml, and says when it next runs' do
|
|
100
|
-
allow(store).to receive(:save).and_wrap_original do |original, schedule|
|
|
101
|
-
expect(adapter.installed).to include(schedule.os_id)
|
|
102
|
-
original.call(schedule)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
scheduler.schedule('report', every: 'weekday', at: '08:00')
|
|
106
|
-
|
|
107
|
-
expect(out.string).to eq("Scheduled report: every weekday at 08:00. Next run Mon 9 Nov 08:00.\n")
|
|
108
|
-
expect([adapter.calls, store.find('report').rule_text]).to eq([[[:install, os_id]], 'every weekday at 08:00'])
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
it 'stores notify and adds --notify to the OS entry' do
|
|
112
|
-
allow(adapter).to receive(:install).and_wrap_original do |original, schedule|
|
|
113
|
-
expect(schedule.argv).to include('--notify')
|
|
114
|
-
original.call(schedule)
|
|
115
|
-
end
|
|
116
|
-
scheduler.schedule('report', every: 'day', at: '08:00', notify: true)
|
|
117
|
-
|
|
118
|
-
expect([store.find('report').notify, out.string]).to eq([true, "Scheduled report: every day at 08:00 (notify). Next run Mon 9 Nov 08:00.\n"])
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
it 'says Replaced when the flow already had a schedule' do
|
|
122
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
123
|
-
scheduler.schedule('report', every: 'week', at: '09:30', on: 'wed')
|
|
124
|
-
|
|
125
|
-
expect(out.string.lines.last).to eq("Replaced the schedule for report: every week on wed at 09:30. Next run Wed 11 Nov 09:30.\n")
|
|
126
|
-
expect(store.all.map(&:rule_text)).to eq(['every week on wed at 09:30'])
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it 'removes the OS entry again when the yml cannot be written' do
|
|
130
|
-
allow(store).to receive(:save).and_raise(RubyRaider::Flows::Error, 'disk full')
|
|
131
|
-
|
|
132
|
-
expect { scheduler.schedule('report', every: 'day', at: '08:00') }
|
|
133
|
-
.to raise_error(RubyRaider::Flows::Error, 'disk full: the fakeos entry was removed too, so report is not scheduled')
|
|
134
|
-
expect([adapter.calls, adapter.installed]).to eq([[[:install, os_id], [:remove, os_id]], []])
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
it 'needs the flow file and flows set up, and checks the rule, before touching the OS' do
|
|
138
|
-
expect { scheduler.schedule('nope', every: 'day', at: '08:00') }.to raise_error(RubyRaider::Flows::Error, /No flow nope/)
|
|
139
|
-
expect { scheduler.schedule('report', every: 'day') }.to raise_error(RubyRaider::Flows::Error, /needs at/)
|
|
140
|
-
File.delete(File.join(dir, 'flows/support/run.rb'))
|
|
141
|
-
expect { scheduler.schedule('report', every: 'day', at: '08:00') }
|
|
142
|
-
.to raise_error(RubyRaider::Flows::Error, 'Flows are not set up in this project: run raider flow init')
|
|
143
|
-
expect(adapter.calls).to be_empty
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it 'checks schedules.yml can be written before installing the OS entry' do
|
|
147
|
-
FileUtils.mkdir_p(File.join(dir, '.raider/flows'))
|
|
148
|
-
File.write(File.join(dir, '.raider/flows/schedules.yml'), "schedules: [\n")
|
|
149
|
-
|
|
150
|
-
expect { scheduler.schedule('report', every: 'day', at: '08:00') }
|
|
151
|
-
.to raise_error(RubyRaider::Flows::Error, /schedules.yml is not a schedules file/)
|
|
152
|
-
expect(adapter.calls).to be_empty
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it 'refuses with a Flows::Error when the flow folder cannot be made' do
|
|
156
|
-
File.write(File.join(dir, '.raider'), '')
|
|
157
|
-
|
|
158
|
-
expect { scheduler.schedule('report', every: 'day', at: '08:00') }
|
|
159
|
-
.to raise_error(RubyRaider::Flows::Error, %r{\ACould not make \.raider/flows: })
|
|
160
|
-
expect(adapter.calls).to be_empty
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
it 'makes .raider/flows/<name> a 0700 folder and refuses a link there' do
|
|
164
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
165
|
-
expect(File.stat(File.join(dir, '.raider/flows/report')).mode & 0o777).to eq(0o700)
|
|
166
|
-
|
|
167
|
-
FileUtils.rm_rf(File.join(dir, '.raider/flows/report'))
|
|
168
|
-
File.symlink(FileUtils.mkdir_p(File.join(dir, 'elsewhere')).first, File.join(dir, '.raider/flows/report'))
|
|
169
|
-
expect { scheduler.schedule('report', every: 'day', at: '08:00') }
|
|
170
|
-
.to raise_error(RubyRaider::Flows::Error, '.raider/flows/report must be a folder, not a link')
|
|
171
|
-
expect(adapter.calls).to eq([[:install, os_id]])
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
describe '#unschedule' do
|
|
176
|
-
it 'keeps the yml entry when the OS scheduler refuses the removal' do
|
|
177
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
178
|
-
allow(adapter).to receive(:remove).and_raise(RubyRaider::Flows::Error, 'systemd units found')
|
|
179
|
-
|
|
180
|
-
expect { scheduler.unschedule('report') }.to raise_error(RubyRaider::Flows::Error, 'systemd units found')
|
|
181
|
-
expect(store.find('report')).not_to be_nil
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
it 'removes the yml entry and the OS entry' do
|
|
185
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
186
|
-
scheduler.unschedule('report')
|
|
187
|
-
|
|
188
|
-
expect([out.string.lines.last, store.all, adapter.installed]).to eq(["Unscheduled report\n", [], []])
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
it 'says when the OS entry was already gone' do
|
|
192
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
193
|
-
adapter.installed.clear
|
|
194
|
-
scheduler.unschedule('report')
|
|
195
|
-
|
|
196
|
-
expect([out.string.lines.last, store.all]).to eq(["Unscheduled report (it was already gone from fakeos)\n", []])
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
it 'removes an OS entry with no yml entry, even for a deleted flow' do
|
|
200
|
-
adapter.installed << RubyRaider::Flows::Schedule.os_id_for(dir, 'gone')
|
|
201
|
-
scheduler.unschedule('gone')
|
|
202
|
-
|
|
203
|
-
expect([out.string, adapter.installed]).to eq(["Unscheduled gone\n", []])
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
it 'refuses a flow with no schedule anywhere, and an invalid name' do
|
|
207
|
-
expect { scheduler.unschedule('report') }.to raise_error(RubyRaider::Flows::Error, 'No schedule for report')
|
|
208
|
-
expect { scheduler.unschedule('Bad-Name') }.to raise_error(RubyRaider::Flows::Error, /Invalid flow name/)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
it 'says the OS entry was already removed when schedules.yml can not be written' do
|
|
212
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
213
|
-
allow(store).to receive(:delete).and_raise(Errno::EACCES, 'schedules.yml')
|
|
214
|
-
|
|
215
|
-
expect { scheduler.unschedule('report') }.to raise_error(RubyRaider::Flows::Error) do |error|
|
|
216
|
-
expect([error.message, error.exit_code]).to eq(
|
|
217
|
-
['Permission denied - schedules.yml: the fakeos entry was already removed, but schedules.yml still lists report', 2]
|
|
218
|
-
)
|
|
219
|
-
end
|
|
220
|
-
expect(adapter.installed).to eq([])
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
it 'warns that an OS entry from another project path may remain when the yml entry was unreadable' do
|
|
224
|
-
FileUtils.mkdir_p(File.join(dir, '.raider/flows'))
|
|
225
|
-
File.write(File.join(dir, '.raider/flows/schedules.yml'), { 'schedules' => [{ 'name' => 'report' }] }.to_yaml)
|
|
226
|
-
scheduler.unschedule('report')
|
|
227
|
-
|
|
228
|
-
expect(out.string).to eq("Unscheduled report (it was already gone from fakeos)\n")
|
|
229
|
-
expect(err.string).to include('could not be read', 'fakeos entry made from a different project path may remain')
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
describe '#schedules' do
|
|
234
|
-
it 'lists each schedule with its rule, next run, last result and OS state' do
|
|
235
|
-
scheduler.schedule('report', every: 'hour', at: ':15', retries: 2, inputs: ['who=me'], notify: true)
|
|
236
|
-
log_run('report', 'status' => 'failed', 'finished_at' => "2026-11-09T08:00:12Z\e[2J", 'trigger' => 'schedule',
|
|
237
|
-
'failed_step' => 'Open', 'results_dir' => 'flow-results/report/x', 'run_id' => 'abc')
|
|
238
|
-
out.truncate(0)
|
|
239
|
-
out.rewind
|
|
240
|
-
scheduler.schedules
|
|
241
|
-
|
|
242
|
-
expect(out.string).to eq("#{'report'.ljust(30)} every hour at :15 (notify) next Sun 8 Nov 12:15 failed 2026-11-09T08:00:12Z?[2J ok\n")
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
it 'prints the JSON the desktop app reads' do
|
|
246
|
-
scheduler.schedule('report', every: 'week', at: '08:00', on: 'mon', inputs: ['who=me'], notify: true)
|
|
247
|
-
log_run('report', 'status' => 'completed', 'finished_at' => 'f', 'trigger' => 't', 'failed_step' => nil, 'results_dir' => 'r', 'run_id' => 'x')
|
|
248
|
-
out.truncate(0)
|
|
249
|
-
out.rewind
|
|
250
|
-
scheduler.schedules(json: true)
|
|
251
|
-
|
|
252
|
-
expect(JSON.parse(out.string)).to eq(
|
|
253
|
-
[{ 'name' => 'report', 'every' => 'week', 'at' => '08:00', 'on' => 'mon', 'retries' => 0, 'inputs' => ['who=me'], 'notify' => true,
|
|
254
|
-
'next_run_at' => Time.local(2026, 11, 9, 8, 0).iso8601, 'os_state' => 'ok',
|
|
255
|
-
'last' => { 'status' => 'completed', 'finished_at' => 'f', 'trigger' => 't', 'failed_step' => nil, 'results_dir' => 'r' } }]
|
|
256
|
-
)
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
it 'says Ruby not found when the stored ruby is gone, and missing when the OS entry is gone' do
|
|
260
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
261
|
-
FileUtils.touch(File.join(dir, 'flows/other_flow.rb'))
|
|
262
|
-
fields = store.find('report').to_h.transform_keys(&:to_sym)
|
|
263
|
-
other = { name: 'other', os_id: RubyRaider::Flows::Schedule.os_id_for(dir, 'other'), ruby: '/nonexistent/ruby' }
|
|
264
|
-
store.save(RubyRaider::Flows::Schedule.new(**fields, **other, project_root: dir))
|
|
265
|
-
adapter.installed.clear
|
|
266
|
-
out.truncate(0)
|
|
267
|
-
out.rewind
|
|
268
|
-
scheduler.schedules(json: true)
|
|
269
|
-
|
|
270
|
-
expect(JSON.parse(out.string).map { |row| [row['name'], row['os_state'], row['last']] })
|
|
271
|
-
.to eq([['other', 'Ruby not found: re-schedule', nil], ['report', 'missing from fakeos', nil]])
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
it 'says flow not found when the flow file is gone, after Ruby not found and before the OS state' do
|
|
275
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
276
|
-
FileUtils.touch(File.join(dir, 'flows/other_flow.rb'))
|
|
277
|
-
fields = store.find('report').to_h.transform_keys(&:to_sym)
|
|
278
|
-
other = { name: 'other', os_id: RubyRaider::Flows::Schedule.os_id_for(dir, 'other'), ruby: '/nonexistent/ruby' }
|
|
279
|
-
store.save(RubyRaider::Flows::Schedule.new(**fields, **other, project_root: dir))
|
|
280
|
-
%w[report other].each { |name| File.delete(File.join(dir, "flows/#{name}_flow.rb")) }
|
|
281
|
-
adapter.fail_present = true
|
|
282
|
-
scheduler.schedules(json: true)
|
|
283
|
-
|
|
284
|
-
expect(JSON.parse(out.string.lines.last).map { |row| [row['name'], row['os_state']] })
|
|
285
|
-
.to eq([['other', 'Ruby not found: re-schedule'], ['report', 'flow not found: unschedule']])
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it 'adds a row for a flow the OS still schedules with no yml entry' do
|
|
289
|
-
adapter.installed << os_id
|
|
290
|
-
scheduler.schedules(json: true)
|
|
291
|
-
|
|
292
|
-
expect(JSON.parse(out.string)).to eq(
|
|
293
|
-
[{ 'name' => 'report', 'every' => nil, 'at' => nil, 'on' => nil, 'retries' => nil, 'inputs' => nil, 'notify' => nil,
|
|
294
|
-
'next_run_at' => nil, 'os_state' => 'missing from schedules.yml', 'last' => nil }]
|
|
295
|
-
)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
it 'shows unknown when the OS state cannot be read' do
|
|
299
|
-
scheduler.schedule('report', every: 'day', at: '08:00')
|
|
300
|
-
adapter.fail_present = true
|
|
301
|
-
out.truncate(0)
|
|
302
|
-
out.rewind
|
|
303
|
-
scheduler.schedules
|
|
304
|
-
|
|
305
|
-
expect(out.string).to end_with(" never run unknown (task list unreadable)\n")
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
it 'prints the missed runs note of an adapter that has one, once' do
|
|
309
|
-
adapter.define_singleton_method(:missed_runs_note) { '(fakeos: missed runs are skipped)' }
|
|
310
|
-
%w[report other].each do |name|
|
|
311
|
-
FileUtils.touch(File.join(dir, "flows/#{name}_flow.rb"))
|
|
312
|
-
scheduler.schedule(name, every: 'day', at: '08:00')
|
|
313
|
-
end
|
|
314
|
-
out.truncate(0)
|
|
315
|
-
out.rewind
|
|
316
|
-
scheduler.schedules
|
|
317
|
-
|
|
318
|
-
expect(out.string.lines.size).to eq(3)
|
|
319
|
-
expect(out.string.lines.last).to eq("(fakeos: missed runs are skipped)\n")
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
it 'says how to add one when there are none' do
|
|
323
|
-
scheduler.schedules
|
|
324
|
-
|
|
325
|
-
expect(out.string).to eq("No schedules yet: raider flow schedule <name> --every day --at 08:00\n")
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
# rubocop:enable RSpec/MultipleMemoizedHelpers
|