Friendlier write_pin errors for CeleryScript(TM) invalidation.
parent
0346fe9adb
commit
cc7402f9d6
|
@ -3,17 +3,18 @@
|
|||
# this part last
|
||||
module CeleryScript
|
||||
UNBOUND_VAR = "Unbound variable: %s"
|
||||
|
||||
class TypeCheckError < StandardError; end
|
||||
|
||||
class Checker
|
||||
MISSING_ARG = "Expected node '%s' to have a '%s', but got: %s."
|
||||
EXTRA_ARGS = "'%s' has unexpected arguments: %s. Allowed arguments: %s"
|
||||
BAD_LEAF = "Expected leaf '%{kind}' within '%{parent_kind}'"\
|
||||
" to be one of: %{allowed} but got %{actual}"
|
||||
MALFORMED = "Expected '%s' to be a node or leaf, but it was neither"
|
||||
BAD_BODY = "Body of '%s' node contains '%s' node. "\
|
||||
"Expected one of: %s"
|
||||
T_MISMATCH = "Type mismatch. %s must be one of: %s. Got: %s"
|
||||
EXTRA_ARGS = "'%s' has unexpected arguments: %s. Allowed arguments: %s"
|
||||
BAD_LEAF = "Expected leaf '%{kind}' within '%{parent_kind}'" \
|
||||
" to be one of: %{allowed} but got %{actual}"
|
||||
MALFORMED = "Expected '%s' to be a node or leaf, but it was neither"
|
||||
BAD_BODY = "Body of '%s' node contains '%s' node. " \
|
||||
"Expected one of: %s"
|
||||
T_MISMATCH = "Type mismatch. %s must be one of: %s. Got: %s"
|
||||
|
||||
# Certain CeleryScript pairing errors are more than just a syntax error.
|
||||
# For instance, A `nothing` node in a `parameter_declaration` is often an
|
||||
|
@ -22,6 +23,7 @@ module CeleryScript
|
|||
# BAD_LEAF template.
|
||||
FRIENDLY_ERRORS = {
|
||||
nothing: {
|
||||
write_pin: "You must select a Peripheral in the Control Peripheral step.",
|
||||
variable_declaration: "You must provide a value for all parameters",
|
||||
parameter_declaration: "You must provide a value for all parameters",
|
||||
},
|
||||
|
@ -89,7 +91,7 @@ module CeleryScript
|
|||
unless has_key
|
||||
msgs = node.args.keys.join(", ")
|
||||
msgs = "nothing" if msgs.length < 1
|
||||
msg = MISSING_ARG % [node.kind, arg, msgs]
|
||||
msg = MISSING_ARG % [node.kind, arg, msgs]
|
||||
raise TypeCheckError, msg
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,51 +1,72 @@
|
|||
require 'spec_helper'
|
||||
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'
|
||||
request.headers["accept"] = "application/json"
|
||||
end
|
||||
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
describe '#create' do
|
||||
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
|
||||
it "provides human readable errors for empty write_pin nodes" do
|
||||
sign_in user
|
||||
body = [
|
||||
{
|
||||
kind: "write_pin",
|
||||
args: {
|
||||
pin_number: { kind: "nothing", args: {} },
|
||||
pin_value: 0,
|
||||
pin_mode: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
input = { name: "Scare Birds", body: body }
|
||||
sequence_body_for(user)
|
||||
post :create, body: input.to_json, params: { format: :json }
|
||||
expect(response.status).to eq(422)
|
||||
err = json.fetch(:body)
|
||||
expected = "You must select a Peripheral in the Control Peripheral step."
|
||||
expect(err).to eq(expected)
|
||||
end
|
||||
|
||||
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}
|
||||
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
|
||||
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}
|
||||
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
|
||||
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
|
||||
it "handles invalid params" do
|
||||
# Needed to test the `else` branch of mutate() somewhere
|
||||
sign_in user
|
||||
input = {}
|
||||
|
@ -55,32 +76,31 @@ describe Api::SequencesController do
|
|||
expect(json[:name]).to eq("Name is required")
|
||||
end
|
||||
|
||||
it 'doesnt allow nonsense in `sequence.args.locals`' do
|
||||
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 } } }
|
||||
}
|
||||
args: { locals: { kind: "wait", args: { milliseconds: 5000 } } } }
|
||||
|
||||
sign_in user
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
post :create, body: input.to_json, params: { format: :json }
|
||||
expect(response.status).to eq(422)
|
||||
expect(Sequence.last).to_not be
|
||||
xpectd = "Expected leaf 'wait' within 'sequence' to be one of: "\
|
||||
xpectd = "Expected leaf 'wait' within 'sequence' to be one of: " \
|
||||
"[\"scope_declaration\"] but got wait"
|
||||
expect(json.fetch(:body)).to eq(xpectd)
|
||||
end
|
||||
|
||||
it 'strips excess `args`' do
|
||||
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}
|
||||
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
|
||||
|
@ -89,7 +109,7 @@ describe Api::SequencesController do
|
|||
expect(generated_result.dig(:args, :foo)).to eq(nil)
|
||||
end
|
||||
|
||||
it 'disallows bad default_values' do
|
||||
it "disallows bad default_values" do
|
||||
input = {
|
||||
name: "Scare Birds",
|
||||
body: [],
|
||||
|
@ -105,24 +125,24 @@ describe Api::SequencesController do
|
|||
label: "parent",
|
||||
default_value: {
|
||||
kind: "wait",
|
||||
args: { milliseconds: 12 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
args: { milliseconds: 12 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sign_in user
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
post :create, body: input.to_json, params: { format: :json }
|
||||
expect(response.status).to eq(422)
|
||||
expect(json.fetch(:body)).to include('"tool"')
|
||||
expect(json[:body]).to include("Expected leaf 'wait' within "\
|
||||
expect(json[:body]).to include("Expected leaf 'wait' within " \
|
||||
"'parameter_declaration' to be one of: [")
|
||||
end
|
||||
|
||||
it 'disallows erroneous `locals` declaration' do
|
||||
it "disallows erroneous `locals` declaration" do
|
||||
input = {
|
||||
name: "Scare Birds",
|
||||
body: [],
|
||||
|
@ -132,21 +152,21 @@ describe Api::SequencesController do
|
|||
kind: "scope_declaration",
|
||||
args: {},
|
||||
body: [
|
||||
{ kind: "wait", args: { milliseconds: 5000 } }
|
||||
]
|
||||
}
|
||||
}
|
||||
{ kind: "wait", args: { milliseconds: 5000 } },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sign_in user
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
post :create, body: input.to_json, params: { format: :json }
|
||||
expect(response.status).to eq(422)
|
||||
expctd =
|
||||
"Expected one of: [:variable_declaration, :parameter_declaration]"
|
||||
expect(json[:body]).to include(expctd)
|
||||
end
|
||||
|
||||
it 'allows declaration of a variable named `parent`' do
|
||||
it "allows declaration of a variable named `parent`" do
|
||||
input = {
|
||||
name: "Scare Birds",
|
||||
args: {
|
||||
|
@ -163,10 +183,10 @@ describe Api::SequencesController do
|
|||
args: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
z: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
|
@ -174,12 +194,12 @@ describe Api::SequencesController do
|
|||
label: "parent2",
|
||||
data_value: {
|
||||
kind: "coordinate",
|
||||
args: { x: 9, y: 9, z: 9, }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
args: { x: 9, y: 9, z: 9 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
body: [
|
||||
{
|
||||
|
@ -187,30 +207,34 @@ describe Api::SequencesController do
|
|||
args: {
|
||||
location: {
|
||||
kind: "identifier",
|
||||
args: { label: "parent" } },
|
||||
offset: {
|
||||
args: { label: "parent" },
|
||||
},
|
||||
offset: {
|
||||
kind: "coordinate",
|
||||
args: { x: 0, y: 0, z: 0 } },
|
||||
speed: 100,
|
||||
}
|
||||
args: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
speed: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "move_absolute",
|
||||
args: {
|
||||
location: {
|
||||
kind: "identifier",
|
||||
args: { label: "parent2" } },
|
||||
offset: {
|
||||
args: { label: "parent2" },
|
||||
},
|
||||
offset: {
|
||||
kind: "coordinate",
|
||||
args: { x: 0, y: 0, z: 0 } },
|
||||
speed: 100,
|
||||
}
|
||||
}
|
||||
]
|
||||
args: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
speed: 100,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
sign_in user
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
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
|
||||
|
@ -220,7 +244,7 @@ describe Api::SequencesController do
|
|||
expect(json.dig(*dig_path)).to eq("parent")
|
||||
end
|
||||
|
||||
it 'tracks Points' do
|
||||
it "tracks Points" do
|
||||
point = FactoryBot.create(:generic_pointer, device: user.device)
|
||||
PinBinding.destroy_all
|
||||
Sequence.destroy_all
|
||||
|
@ -231,14 +255,14 @@ describe Api::SequencesController do
|
|||
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}
|
||||
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
|
||||
it "prevents unbound variables" do
|
||||
sign_in user
|
||||
input = {
|
||||
name: "Unbound Variable Exception",
|
||||
|
@ -249,23 +273,23 @@ describe Api::SequencesController do
|
|||
args: {
|
||||
location: {
|
||||
kind: "identifier",
|
||||
args: { label: "parent" }
|
||||
args: { label: "parent" },
|
||||
},
|
||||
offset: {
|
||||
offset: {
|
||||
kind: "coordinate",
|
||||
args: { x: 0, y: 0, z: 0 }
|
||||
args: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
speed: 100,
|
||||
}
|
||||
}
|
||||
]
|
||||
speed: 100,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
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
|
||||
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)
|
||||
|
@ -277,52 +301,50 @@ describe Api::SequencesController do
|
|||
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 } }
|
||||
}
|
||||
}
|
||||
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}
|
||||
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
|
||||
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",
|
||||
default_value: { kind: "sync", args: {} }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
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}
|
||||
args: {
|
||||
locals: {
|
||||
kind: "scope_declaration",
|
||||
args: {},
|
||||
body: [
|
||||
{
|
||||
kind: "parameter_declaration",
|
||||
args: {
|
||||
label: "parent",
|
||||
default_value: { kind: "sync", args: {} },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
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.fetch(:body)).to include('"point"')
|
||||
expect(json[:body]).to include("but got sync")
|
||||
|
@ -331,27 +353,26 @@ describe Api::SequencesController do
|
|||
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",
|
||||
default_value: {
|
||||
kind: "nothing",
|
||||
args: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
args: {
|
||||
locals: {
|
||||
kind: "scope_declaration",
|
||||
args: {},
|
||||
body: [
|
||||
{
|
||||
kind: "parameter_declaration",
|
||||
args: {
|
||||
label: "x",
|
||||
default_value: {
|
||||
kind: "nothing",
|
||||
args: {},
|
||||
},
|
||||
body: [ ]
|
||||
}
|
||||
post :create, body: input.to_json, params: {format: :json}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue