require 'spec_helper' describe Gush::Workflow do subject { TestWorkflow.create } describe "#initialize" do it "passes constructor arguments to the method" do klass = Class.new(Gush::Workflow) do def configure(*args) run FetchFirstJob run PersistFirstJob, after: FetchFirstJob end end expect_any_instance_of(klass).to receive(:configure).with("arg1", "arg2") klass.new("arg1", "arg2") end it "passes constructor keyword arguments to the method" do klass = Class.new(Gush::Workflow) do def configure(*args, **kwargs) run FetchFirstJob run PersistFirstJob, after: FetchFirstJob end end expect_any_instance_of(klass).to receive(:configure).with("arg1", "arg2", arg3: 123) klass.new("arg1", "arg2", arg3: 123) end it "accepts globals" do flow = TestWorkflow.new(globals: { global1: 'foo' }) expect(flow.globals[:global1]).to eq('foo') end it "accepts internal_state" do flow = TestWorkflow.new internal_state = { id: flow.id, jobs: flow.jobs, dependencies: flow.dependencies, persisted: true, stopped: true } flow_copy = TestWorkflow.new(internal_state: internal_state) expect(flow_copy.id).to eq(flow.id) expect(flow_copy.jobs).to eq(flow.jobs) expect(flow_copy.dependencies).to eq(flow.dependencies) expect(flow_copy.persisted).to eq(true) expect(flow_copy.stopped).to eq(true) end it "does not call #configure if needs_setup is false" do INTERNAL_SETUP_SPY = double('configure spy') klass = Class.new(Gush::Workflow) do def configure(*args) INTERNAL_SETUP_SPY.some_method end end expect(INTERNAL_SETUP_SPY).not_to receive(:some_method) flow = TestWorkflow.new(internal_state: { needs_setup: false }) end end describe "#find" do it "fiends a workflow by id" do expect(Gush::Workflow.find(subject.id).id).to eq(subject.id) end end describe "#page" do it "returns a page of registered workflows" do flow = TestWorkflow.create expect(Gush::Workflow.page.map(&:id)).to eq([flow.id]) end end describe "#save" do context "workflow not persisted" do it "sets persisted to true" do flow = TestWorkflow.new flow.save expect(flow.persisted).to be(true) end it "assigns new unique id" do flow = TestWorkflow.new flow.save expect(flow.id).to_not be_nil end end context "workflow persisted" do it "does not assign new id" do flow = TestWorkflow.new flow.save id = flow.id flow.save expect(flow.id).to eq(id) end end end describe "#continue" do it "enqueues failed jobs" do flow = TestWorkflow.create flow.find_job('Prepare').fail! expect(flow.jobs.select(&:failed?)).not_to be_empty flow.continue expect(flow.jobs.select(&:failed?)).to be_empty expect(flow.find_job('Prepare').failed_at).to be_nil end end describe "#mark_as_stopped" do it "marks workflow as stopped" do expect{ subject.mark_as_stopped }.to change{subject.stopped?}.from(false).to(true) end end describe "#mark_as_started" do it "removes stopped flag" do subject.stopped = true expect{ subject.mark_as_started }.to change{subject.stopped?}.from(true).to(false) end end describe "#status" do context "when failed" do it "returns :failed" do flow = TestWorkflow.create flow.find_job("Prepare").fail! flow.persist! expect(flow.reload.status).to eq(:failed) end end it "returns failed" do subject.find_job('Prepare').fail! expect(subject.status).to eq(:failed) end it "returns running" do subject.find_job('Prepare').start! expect(subject.status).to eq(:running) end it "returns finished" do subject.jobs.each {|n| n.finish! } expect(subject.status).to eq(:finished) end it "returns stopped" do subject.stopped = true expect(subject.status).to eq(:stopped) end it "returns pending" do expect(subject.status).to eq(:pending) end end describe "#to_json" do it "returns correct hash" do klass = Class.new(Gush::Workflow) do def configure(*args) run FetchFirstJob run PersistFirstJob, after: FetchFirstJob end end result = JSON.parse(klass.create("arg1", "arg2", arg3: 123).to_json) expected = { "id" => an_instance_of(String), "name" => klass.to_s, "klass" => klass.to_s, "job_klasses" => ["FetchFirstJob", "PersistFirstJob"], "status" => "pending", "total" => 2, "finished" => 0, "started_at" => nil, "finished_at" => nil, "stopped" => false, "dependencies" => [{ "from" => "FetchFirstJob", "to" => job_with_id("PersistFirstJob") }], "arguments" => ["arg1", "arg2"], "kwargs" => {"arg3" => 123}, "globals" => {} } expect(result).to match(expected) end end describe "#find_job" do it "finds job by its name" do expect(TestWorkflow.create.find_job("PersistFirstJob")).to be_instance_of(PersistFirstJob) end end describe "#run" do it "allows passing additional params to the job" do flow = Gush::Workflow.new flow.run(Gush::Job, params: { something: 1 }) flow.save expect(flow.jobs.first.params).to eq({ something: 1 }) end it "merges globals with params and passes them to the job, with job param taking precedence" do flow = Gush::Workflow.new(globals: { something: 2, global1: 123 }) flow.run(Gush::Job, params: { something: 1 }) flow.save expect(flow.jobs.first.params).to eq({ something: 1, global1: 123 }) end it "allows passing wait param to the job" do flow = Gush::Workflow.new flow.run(Gush::Job, wait: 5.seconds) flow.save expect(flow.jobs.first.wait).to eq(5.seconds) end context "when graph is empty" do it "adds new job with the given class as a node" do flow = Gush::Workflow.new flow.run(Gush::Job) flow.save expect(flow.jobs.first).to be_instance_of(Gush::Job) end end it "allows `after` to accept an array of jobs" do tree = Gush::Workflow.new klass1 = Class.new(Gush::Job) klass2 = Class.new(Gush::Job) klass3 = Class.new(Gush::Job) tree.run(klass1) tree.run(klass2, after: [klass1, klass3]) tree.run(klass3) tree.resolve_dependencies expect(tree.jobs.first.outgoing).to match_array(jobs_with_id([klass2.to_s])) end it "allows `before` to accept an array of jobs" do tree = Gush::Workflow.new klass1 = Class.new(Gush::Job) klass2 = Class.new(Gush::Job) klass3 = Class.new(Gush::Job) tree.run(klass1) tree.run(klass2, before: [klass1, klass3]) tree.run(klass3) tree.resolve_dependencies expect(tree.jobs.first.incoming).to match_array(jobs_with_id([klass2.to_s])) end it "attaches job as a child of the job in `after` key" do tree = Gush::Workflow.new klass1 = Class.new(Gush::Job) klass2 = Class.new(Gush::Job) tree.run(klass1) tree.run(klass2, after: klass1) tree.resolve_dependencies job = tree.jobs.first expect(job.outgoing).to match_array(jobs_with_id([klass2.to_s])) end it "attaches job as a parent of the job in `before` key" do tree = Gush::Workflow.new klass1 = Class.new(Gush::Job) klass2 = Class.new(Gush::Job) tree.run(klass1) tree.run(klass2, before: klass1) tree.resolve_dependencies job = tree.jobs.first expect(job.incoming).to match_array(jobs_with_id([klass2.to_s])) end end describe "#failed?" do context "when one of the jobs failed" do it "returns true" do subject.find_job('Prepare').fail! expect(subject.failed?).to be_truthy end end context "when no jobs failed" do it "returns true" do expect(subject.failed?).to be_falsy end end end describe "#running?" do context "when no enqueued or running jobs" do it "returns false" do expect(subject.running?).to be_falsy end end context "when some jobs are running" do it "returns true" do subject.find_job('Prepare').start! expect(subject.running?).to be_truthy end end end describe "#finished?" do it "returns false if any jobs are unfinished" do expect(subject.finished?).to be_falsy end it "returns true if all jobs are finished" do subject.jobs.each {|n| n.finish! } expect(subject.finished?).to be_truthy end end end