Test case that reproeduces Trello card #1943.
parent
c99e2dd5ee
commit
4529b236be
|
@ -2,16 +2,17 @@
|
|||
# PROBABLY THE MOST COMPLICATED CODE IN ALL OF FARMBOT.
|
||||
module CeleryScript
|
||||
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"
|
||||
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"
|
||||
UNBOUND_VAR = "Unbound variable: %s"
|
||||
T_MISMATCH = "Type mismatch. %s must be one of: %s. Got: %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 `variable_declaration` is often an
|
||||
|
@ -20,8 +21,8 @@ module CeleryScript
|
|||
# BAD_LEAF template.
|
||||
FRIENDLY_ERRORS = {
|
||||
nothing: {
|
||||
variable_declaration: "You must provide a value for all parameters"
|
||||
}
|
||||
variable_declaration: "You must provide a value for all parameters",
|
||||
},
|
||||
}.with_indifferent_access
|
||||
attr_reader :tree, :corpus, :device
|
||||
|
||||
|
@ -52,8 +53,8 @@ module CeleryScript
|
|||
end
|
||||
|
||||
def check_leaf(node)
|
||||
allowed = corpus.values(node)
|
||||
actual = node.value.class
|
||||
allowed = corpus.values(node)
|
||||
actual = node.value.class
|
||||
|
||||
maybe_bad_leaf(node.kind, node.parent.kind, allowed, actual)
|
||||
end
|
||||
|
@ -81,17 +82,17 @@ module CeleryScript
|
|||
end
|
||||
|
||||
def check_arity(node)
|
||||
allowed = corpus.args(node)
|
||||
allowed.map do |arg|
|
||||
has_key = node.args.has_key?(arg) || node.args.has_key?(arg.to_s)
|
||||
unless has_key
|
||||
msgs = node.args.keys.join(", ")
|
||||
msgs = "nothing" if msgs.length < 1
|
||||
allowed = corpus.args(node)
|
||||
allowed.map do |arg|
|
||||
has_key = node.args.has_key?(arg) || node.args.has_key?(arg.to_s)
|
||||
unless has_key
|
||||
msgs = node.args.keys.join(", ")
|
||||
msgs = "nothing" if msgs.length < 1
|
||||
msg = MISSING_ARG % [node.kind, arg, msgs]
|
||||
raise TypeCheckError, msg
|
||||
end
|
||||
end
|
||||
has = node.args.keys.map(&:to_sym) # Either bigger or equal.
|
||||
end
|
||||
has = node.args.keys.map(&:to_sym) # Either bigger or equal.
|
||||
required = corpus.args(node) # Always smallest.
|
||||
if !(has.length === required.length)
|
||||
extras = has - required
|
||||
|
@ -119,7 +120,6 @@ module CeleryScript
|
|||
# value.invalidate!(T_MISMATCH % [label, expected, actual])
|
||||
# end SEE_MY_NOTE =============================^ RC 4 Oct 18
|
||||
|
||||
|
||||
def type_check_parameter(var, expected)
|
||||
data_type = var.args[:data_type].value
|
||||
|
||||
|
@ -131,13 +131,12 @@ module CeleryScript
|
|||
# end SEE_MY_NOTE =============================^ RC 4 Oct 18
|
||||
end
|
||||
|
||||
|
||||
def validate_node_pairing(key, value)
|
||||
actual = value.kind
|
||||
actual = value.kind
|
||||
allowed = corpus.fetchArg(key).allowed_values.map(&:to_s)
|
||||
# It would be safe to run type checking here.
|
||||
if (actual == "identifier")
|
||||
allowed_types = allowed.without("identifier")
|
||||
allowed_types = allowed.without("identifier")
|
||||
# Resolve the identifier.
|
||||
# Someday, we might need to use the return value to perform more
|
||||
# in depth type checking. We're not there yet, though.
|
||||
|
@ -149,15 +148,15 @@ module CeleryScript
|
|||
type_check_parameter(var, allowed_types)
|
||||
when "variable_declaration"
|
||||
actual = var.args[:data_value].kind
|
||||
# Don't delete this- it is currently unreachable code, but as soon as we
|
||||
# allow identifiers other than `point`, `tool` and `coordinate` we will
|
||||
# need it again (and can write tests)
|
||||
# unless allowed_types.include?(actual)
|
||||
# bad_var!(value, var.args[:label].value, allowed_types, actual)
|
||||
# end
|
||||
# else
|
||||
# raise ("Bad kind: " + var.kind)
|
||||
# SEE_MY_NOTE =============================^ RC 4 Oct 18
|
||||
# Don't delete this- it is currently unreachable code, but as soon as we
|
||||
# allow identifiers other than `point`, `tool` and `coordinate` we will
|
||||
# need it again (and can write tests)
|
||||
# unless allowed_types.include?(actual)
|
||||
# bad_var!(value, var.args[:label].value, allowed_types, actual)
|
||||
# end
|
||||
# else
|
||||
# raise ("Bad kind: " + var.kind)
|
||||
# SEE_MY_NOTE =============================^ RC 4 Oct 18
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -167,18 +166,17 @@ module CeleryScript
|
|||
def maybe_bad_leaf(kind, parent_kind, allowed, actual)
|
||||
unless allowed.include?(actual)
|
||||
message = (FRIENDLY_ERRORS.dig(kind, parent_kind) || BAD_LEAF) % {
|
||||
kind: kind,
|
||||
kind: kind,
|
||||
parent_kind: parent_kind,
|
||||
allowed: allowed,
|
||||
actual: actual
|
||||
allowed: allowed,
|
||||
actual: actual,
|
||||
}
|
||||
|
||||
raise TypeCheckError, message
|
||||
end
|
||||
end
|
||||
|
||||
def validate_leaf_pairing(key, value)
|
||||
actual = value.value.class
|
||||
actual = value.value.class
|
||||
allowed = corpus.fetchArg(key).allowed_values
|
||||
maybe_bad_leaf(value.kind, value.parent.kind, allowed, actual)
|
||||
end
|
||||
|
@ -201,7 +199,7 @@ module CeleryScript
|
|||
locals = node.args[:locals]
|
||||
|
||||
if locals&.kind === "scope_declaration"
|
||||
label = origin.args[:label]&.value
|
||||
label = origin.args[:label]&.value
|
||||
result = (locals.body || []).select do |x|
|
||||
x.args[:label]&.value == label
|
||||
end.first
|
||||
|
|
|
@ -1,33 +1,58 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe Api::RegimensController do
|
||||
|
||||
include Devise::Test::ControllerHelpers
|
||||
|
||||
describe "#create" do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:sequence) { FakeSequence.create(device: user.device) }
|
||||
|
||||
it "kicks back missing parameters" do
|
||||
sign_in user
|
||||
celery = File.read("spec/lib/celery_script/ast_fixture5.json")
|
||||
json = JSON.parse(celery).deep_symbolize_keys
|
||||
s = Sequences::Create.run!(json, device: user.device)
|
||||
# No paramaters here.
|
||||
|
||||
payload = {
|
||||
name: "New regimen 1",
|
||||
color: "gray",
|
||||
regimen_items: [{ time_offset: 300000, sequence_id: s.fetch(:id) }],
|
||||
body: [
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
args: { label: "parent", data_value: { kind: "nothing", args: {} } },
|
||||
},
|
||||
],
|
||||
}
|
||||
before_count = Regimen.count
|
||||
post :create, body: payload.to_json, format: :json
|
||||
after_count = Regimen.count
|
||||
|
||||
expect(response.status).to eq(422)
|
||||
expect(before_count).to eq(after_count)
|
||||
end
|
||||
|
||||
it "creates a regimen that uses variables" do
|
||||
sign_in user
|
||||
s = FakeSequence.with_parameters
|
||||
s = FakeSequence.with_parameters
|
||||
payload = { device: s.device,
|
||||
name: "specs",
|
||||
color: "red",
|
||||
body: [
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
args: {
|
||||
label: "parent",
|
||||
data_value: {
|
||||
kind: "every_point", args: { every_point_type: "Plant" }
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
regimen_items: [
|
||||
{ time_offset: 100, sequence_id: s.id }
|
||||
] }
|
||||
name: "specs",
|
||||
color: "red",
|
||||
body: [
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
args: {
|
||||
label: "parent",
|
||||
data_value: {
|
||||
kind: "every_point", args: { every_point_type: "Plant" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
regimen_items: [
|
||||
{ time_offset: 100, sequence_id: s.id },
|
||||
] }
|
||||
post :create, body: payload.to_json, format: :json
|
||||
expect(response.status).to eq(200)
|
||||
declr = json.fetch(:body).first
|
||||
|
@ -41,11 +66,11 @@ describe Api::RegimensController do
|
|||
sign_in user
|
||||
color = %w(blue green yellow orange purple pink gray red).sample
|
||||
|
||||
name = (1..3).map{ Faker::Games::Pokemon.name }.join(" ")
|
||||
name = (1..3).map { Faker::Games::Pokemon.name }.join(" ")
|
||||
payload = {
|
||||
name: name,
|
||||
color: color ,
|
||||
regimen_items: [ {time_offset: 123, sequence_id: sequence.id} ]
|
||||
name: name,
|
||||
color: color,
|
||||
regimen_items: [{ time_offset: 123, sequence_id: sequence.id }],
|
||||
}
|
||||
|
||||
old_regimen_count = Regimen.count
|
||||
|
@ -62,26 +87,25 @@ describe Api::RegimensController do
|
|||
|
||||
it "handles CeleryScript::TypeCheckError" do
|
||||
sign_in user
|
||||
s = FakeSequence.with_parameters
|
||||
s = FakeSequence.with_parameters
|
||||
payload = { device: s.device,
|
||||
name: "specs",
|
||||
color: "red",
|
||||
body: [
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
args: {
|
||||
label: "parent",
|
||||
data_value: { kind: "nothing", args: { } }
|
||||
}
|
||||
}
|
||||
],
|
||||
regimen_items: [
|
||||
{ time_offset: 100, sequence_id: s.id }
|
||||
] }
|
||||
name: "specs",
|
||||
color: "red",
|
||||
body: [
|
||||
{
|
||||
kind: "variable_declaration",
|
||||
args: {
|
||||
label: "parent",
|
||||
data_value: { kind: "nothing", args: {} },
|
||||
},
|
||||
},
|
||||
],
|
||||
regimen_items: [
|
||||
{ time_offset: 100, sequence_id: s.id },
|
||||
] }
|
||||
post :create, body: payload.to_json, format: :json
|
||||
expect(response.status).to eq(422)
|
||||
expect(json.fetch(:error))
|
||||
.to include("must provide a value for all parameters")
|
||||
expect(json.fetch(:error)).to include("must provide a value for all parameters")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue