Farmbot-Web-App/spec/controllers/api/sequences/sequences_create_spec.rb

314 lines
9.7 KiB
Ruby

require 'spec_helper'
HAS_POINTS = JSON.parse(File.read("spec/lib/celery_script/ast_has_points.json"))
describe Api::SequencesController do
before :each do
request.headers["accept"] = 'application/json'
end
include Devise::Test::ControllerHelpers
describe '#create' do
let(:user) { FactoryBot.create(:user) }
let(:nodes) { sequence_body_for(user) }
it 'handles a well formed AST in the body attribute' do
sign_in user
input = { name: "Scare Birds",
body: nodes }
sequence_body_for(user)
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(200)
expect(json[:args]).to be_kind_of(Hash)
expect(json[:body]).to be_kind_of(Array)
expect(json[:body].length).to eq(nodes.length)
end
it 'disregards extra attrs (like `uuid`) on sequence body nodes' do
sign_in user
input = { name: "Scare Birds",
body: nodes }
input[:body].first[:uuid] = SecureRandom.uuid
input[:body].first["uuid"] = SecureRandom.uuid
sequence_body_for(user)
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(200)
expect(json[:args]).to be_kind_of(Hash)
expect(json[:body]).to be_kind_of(Array)
expect(json[:body].length).to eq(nodes.length)
end
it 'creates a new sequences for a user' do
sign_in user
input = { name: "Scare Birds", body: [] }
post :create, body: input.to_json, format: :json
expect(response.status).to eq(200)
end
it 'handles invalid params' do
# Needed to test the `else` branch of mutate() somewhere
sign_in user
input = {}
post :create, body: input.to_json, format: :json
expect(response.status).to eq(422)
expect(json[:name]).to eq("Name is required")
end
it 'doesnt allow nonsense in `sequence.args.locals`' do
PinBinding.destroy_all
Sequence.destroy_all
input = { name: "Scare Birds",
body: [],
# Intentional nonsense to check validation logic.
args: { locals: { kind: "wait", args: { milliseconds: 5000 } } }
}
sign_in user
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(Sequence.last).to_not be
end
it 'strips excess `args`' do
input = { name: "Scare Birds",
body: [],
# Intentional nonsense to check validation logic.
args: { foo: "BAR" } }
sign_in user
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(200)
expect(json[:args][:foo]).to eq(nil)
generated_result = CeleryScript::FetchCelery
.run!(sequence: Sequence.find(json[:id]))
.deep_symbolize_keys
expect(generated_result.dig(:args, :foo)).to eq(nil)
end
it 'disallows typos in `locals` declaration' do
input = {
name: "Scare Birds",
body: [],
# Intentional nonsense to check validation logic.
args: {
locals: {
kind: "scope_declaration",
args: {},
body: [
{
kind: "parameter_declaration",
args: {
label: "parent"
}
}
]
}
}
}
sign_in user
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(json[:body]).to include("not a valid default value")
end
it 'disallows erroneous `locals` declaration' do
input = {
name: "Scare Birds",
body: [],
# Intentional nonsense to check validation logic.
args: {
locals: {
kind: "scope_declaration",
args: {},
body: [
{ kind: "wait", args: { milliseconds: 5000 } }
]
}
}
}
sign_in user
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expctd =
"Expected one of: [:parameter_declaration, :parameter_declaration]"
expect(json[:body]).to include(expctd)
end
it 'allows declaration of a variable named `parent`' do
input = {
name: "Scare Birds",
args: {
locals: {
kind: "scope_declaration",
args: {},
body: [
{
kind: "parameter_declaration",
args: {
label: "parent",
}
}
]
}
},
body: [
{
kind: "move_absolute",
args: {
location: { kind: "identifier", args: { label: "parent" } },
offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } },
speed: 100,
}
}
]
}
sign_in user
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(200)
dig_path = [:args, :locals, :body, 0, :args, :label]
generated_result = CeleryScript::FetchCelery
.run!(sequence: Sequence.find(json[:id]))
.deep_symbolize_keys
expect(generated_result.dig(*dig_path)).to eq("parent")
expect(json.dig(*dig_path)).to eq("parent")
end
it 'tracks Points' do
point = FactoryBot.create(:generic_pointer, device: user.device)
PinBinding.destroy_all
Sequence.destroy_all
EdgeNode.destroy_all
PrimaryNode.destroy_all
HAS_POINTS["body"][0]["args"]["location"]["args"]["pointer_id"] =
point.id
sign_in user
input = { name: "Scare Birds", body: HAS_POINTS["body"] }
sequence_body_for(user)
before = EdgeNode.where(kind: "pointer_id").count
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(200)
now = EdgeNode.where(kind: "pointer_id").count
expect(now).to be > before
end
it 'prevents unbound variables' do
sign_in user
input = {
name: "Unbound Variable Exception",
args: { locals: Sequence::SCOPE_DECLARATION },
body: [
{
kind: "move_absolute",
args: {
location: {
kind: "identifier",
args: { label: "parent" }
},
offset: {
kind: "coordinate",
args: { x: 0, y: 0, z: 0 }
},
speed: 100,
}
}
]
}
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(json[:body]).to eq("Unbound variable: parent")
end
it 'does not let you use other peoples point resources' do
sign_in user
not_yours = FactoryBot.create(:plant)
expect(not_yours.device_id).to_not eq(user.device_id)
input = {
name: "bad point usage",
args: { locals: { kind: "scope_declaration", args: {} } },
body: [
{
kind: "move_absolute",
args: {
location: {
kind: "point",
args: { pointer_type: "Plant", pointer_id: not_yours.id }
},
speed: 100,
offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } }
}
}
],
}
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(json[:body]).to include("Bad point ID")
end
it 'prevents type errors from bad identifier / binding combos' do
sign_in user
input = { name: "type mismatch",
args: {
locals: {
kind: "scope_declaration",
args: {},
body: [
{
kind: "parameter_declaration",
args: { label: "parent" }
}
]
}
},
body: [
{ kind: "move_absolute",
args: {
location: { kind: "identifier", args: { label: "parent" } },
offset: {
kind: "coordinate",
args: { x: 0, y: 0, z: 0 }
},
speed: 100,
}
}
]
}
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(json[:body]).to include("not a valid default value")
end
it 'provides human readable errors for "nothing" mismatches' do
sign_in user
input = { name: "type mismatch",
args: {
locals: {
kind: "scope_declaration",
args: { },
body: [
{
kind: "parameter_declaration",
args: {
label: "x",
data_value: {
kind: "nothing",
args: {}
}
}
}
]
}
},
body: [ ]
}
post :create, body: input.to_json, params: {format: :json}
expect(response.status).to eq(422)
expect(json[:body]).to include("must provide a value for all parameters")
end
end
end