stricter JSON validation

pull/1207/head
Rick Carlino 2019-05-22 15:19:26 -05:00
parent ed24e82c81
commit bf69ae09bd
26 changed files with 177 additions and 186 deletions

View File

@ -16,6 +16,7 @@ module Api
NOT_FBOS = Gem::Version.new("999.999.999") NOT_FBOS = Gem::Version.new("999.999.999")
respond_to :json respond_to :json
before_action :raw_json, only: [:update, :create]
before_action :check_fbos_version before_action :check_fbos_version
before_action :set_default_stuff before_action :set_default_stuff
before_action :authenticate_user! before_action :authenticate_user!
@ -41,8 +42,10 @@ module Api
sorry "You can't perform that action. #{exc.message}", 403 sorry "You can't perform that action. #{exc.message}", 403
end end
ONLY_JSON = "This is a JSON API. Please use _valid_ JSON. " \
"Validate JSON objects at https://jsonlint.com/"
rescue_from OnlyJson do |e| rescue_from OnlyJson do |e|
sorry "This is a JSON API. Please use _valid_ JSON.", 422 sorry ONLY_JSON, 422
end end
rescue_from Errors::NoBot do |exc| rescue_from Errors::NoBot do |exc|
@ -80,17 +83,14 @@ module Api
# Our API does not do things the "Rails way" (we use Mutations for input # Our API does not do things the "Rails way" (we use Mutations for input
# sanitation) so we can ignore this and grab the raw input. # sanitation) so we can ignore this and grab the raw input.
def raw_json def raw_json
@raw_json ||= JSON.parse(request.body.read).tap { |x| symbolize(x) } @raw_json ||= parse_json
rescue JSON::ParserError rescue JSON::ParserError
raise OnlyJson raise OnlyJson
end end
# PROBLEM: We want to deep_symbolize_keys! on all JSON inputs, but what if def parse_json
# the user POSTs an Array? It will crash because [] does not respond_to body = request.body.read
# deep_symbolize_keys! This is the workaround. I could probably use a body.present? ? JSON.parse(body, symbolize_names: true) : nil
# refinement.
def symbolize(x)
x.is_a?(Array) ? x.map(&:deep_symbolize_keys!) : x.deep_symbolize_keys!
end end
REQ_ID = "X-Farmbot-Rpc-Id" REQ_ID = "X-Farmbot-Rpc-Id"

View File

@ -12,12 +12,12 @@ module Api
# POST /api/device # POST /api/device
def create def create
mutate Devices::Create.run(params.as_json, user: current_user) mutate Devices::Create.run(raw_json, user: current_user)
end end
# PATCH/PUT /api/device # PATCH/PUT /api/device
def update def update
mutate Devices::Update.run(params.as_json, device: current_device) mutate Devices::Update.run(raw_json, device: current_device)
end end
# DELETE /api/devices/1 # DELETE /api/devices/1
@ -35,11 +35,11 @@ module Api
end end
def seed def seed
mutate Devices::CreateSeedData.run params.as_json, device: current_device mutate Devices::CreateSeedData.run raw_json, device: current_device
end end
def reset def reset
mutate Devices::Reset.run(params.as_json, device: current_device) mutate Devices::Reset.run(raw_json, device: current_device)
end end
private private

View File

@ -3,14 +3,15 @@ module Api
skip_before_action :authenticate_user!, only: [:create, :update] skip_before_action :authenticate_user!, only: [:create, :update]
def create def create
mutate PasswordResets::Create.run(email: params[:email]) mutate PasswordResets::Create.run(email: raw_json[:email])
end end
def update def update
mutate PasswordResets::Update.run( mutate PasswordResets::Update.run(
password: params[:password], password: raw_json[:password],
password_confirmation: params[:password_confirmation], password_confirmation: raw_json[:password_confirmation],
token: params[:id]) token: raw_json[:id],
)
end end
end end
end end

View File

@ -19,13 +19,13 @@ module Api
end end
def snapshot def snapshot
mutate SavedGardens::Snapshot.run(params.as_json, device: current_device) mutate SavedGardens::Snapshot.run(device: current_device)
end end
def apply def apply
params = { garden: garden, params = { garden: garden,
device: current_device, device: current_device,
destructive: (request.method == "POST") } destructive: (request.method == "POST") }
mutate SavedGardens::Apply.run(params) mutate SavedGardens::Apply.run(params)
end end

View File

@ -48,7 +48,7 @@ module Api
end end
def if_properly_formatted def if_properly_formatted
user = params.as_json.deep_symbolize_keys.fetch(:user, {}) user = raw_json.fetch(:user, {})
# If data handling for this method gets any more complicated, # If data handling for this method gets any more complicated,
# extract into a mutation. # extract into a mutation.
if(user.is_a?(Hash)) if(user.is_a?(Hash))

View File

@ -22,27 +22,27 @@ module Api
def resend_verification def resend_verification
mutate Users::ResendVerification mutate Users::ResendVerification
.run(user: User.find_by!(email: params[:email])) .run(user: User.find_by!(email: raw_json[:email]))
end end
def control_certificate def control_certificate
binding.pry unless raw_json.is_a?(Hash)
mutate Users::GenerateControlCert.run(raw_json, device: current_device) mutate Users::GenerateControlCert.run(raw_json, device: current_device)
end end
private private
def user_params def user_params
user = params user = raw_json
.as_json .merge!(raw_json[:user] || {})
.merge!(params.as_json["user"] || {})
.deep_symbolize_keys .deep_symbolize_keys
{email: user[:email], { email: user[:email],
name: user[:name], name: user[:name],
password: user[:password], password: user[:password],
password_confirmation: user[:password_confirmation], password_confirmation: user[:password_confirmation],
new_password: user[:new_password], new_password: user[:new_password],
new_password_confirmation: user[:new_password_confirmation], new_password_confirmation: user[:new_password_confirmation],
agree_to_terms: user[:agree_to_terms]} agree_to_terms: user[:agree_to_terms] }
end end
end end
end end

View File

@ -3,7 +3,7 @@
module Api module Api
class WebcamFeedsController < Api::AbstractController class WebcamFeedsController < Api::AbstractController
def create def create
mutate WebcamFeeds::Create.run(params.as_json, device: current_device) mutate WebcamFeeds::Create.run(raw_json, device: current_device)
end end
def index def index
@ -15,14 +15,14 @@ module Api
end end
def update def update
mutate WebcamFeeds::Update.run(params.as_json, webcam_feed: webcam) mutate WebcamFeeds::Update.run(raw_json, webcam_feed: webcam)
end end
def destroy def destroy
render json: webcam.destroy! && "" render json: webcam.destroy! && ""
end end
private private
def webcam def webcam
webcams.find(params[:id]) webcams.find(params[:id])

View File

@ -15,7 +15,7 @@ module Auth
def validate def validate
cipher_text = Base64.decode64(credentials) cipher_text = Base64.decode64(credentials)
plain_text = PRIVATE_KEY.private_decrypt(cipher_text) plain_text = PRIVATE_KEY.private_decrypt(cipher_text)
cred_info = JSON.parse(plain_text).symbolize_keys! cred_info = JSON.parse(plain_text, symbolize_names: true)
User.try_auth(cred_info[:email], cred_info[:password]) do |maybe_user| User.try_auth(cred_info[:email], cred_info[:password]) do |maybe_user|
whoops! unless maybe_user whoops! unless maybe_user
@user = maybe_user @user = maybe_user

View File

@ -44,7 +44,7 @@ if Rails.env == "development"
u = User.last u = User.last
u.update_attributes(device: Devices::Create.run!(user: u)) u.update_attributes(device: Devices::Create.run!(user: u))
# === Parameterized Sequence stuff # === Parameterized Sequence stuff
json = JSON.parse(File.read("spec/lib/celery_script/ast_fixture5.json")).deep_symbolize_keys json = JSON.parse(File.read("spec/lib/celery_script/ast_fixture5.json"), symbolize_names: true)
Sequences::Create.run!(json, device: u.device) Sequences::Create.run!(json, device: u.device)
# === Parameterized Sequence stuff # === Parameterized Sequence stuff
Log.transaction do Log.transaction do

View File

@ -10,7 +10,7 @@ describe Api::DevicesController do
it "creates a new device for a user" do it "creates a new device for a user" do
sign_in user sign_in user
params = { user_id: user.id, name: Faker::Food.vegetables } params = { user_id: user.id, name: Faker::Food.vegetables }
post :create, params: params post :create, body: params.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
resp = JSON.parse(response.body) resp = JSON.parse(response.body)
new_device = Device.find(resp["id"]) new_device = Device.find(resp["id"])
@ -22,7 +22,7 @@ describe Api::DevicesController do
it "defaults name to `FarmBot`" do it "defaults name to `FarmBot`" do
sign_in user sign_in user
params = { user_id: user.id } params = { user_id: user.id }
post :create, params: params post :create, body: params.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json.fetch(:name)).to eq("FarmBot") expect(json.fetch(:name)).to eq("FarmBot")
end end

View File

@ -24,7 +24,7 @@ describe Api::DevicesController do
device.update_attributes(name: "#{SecureRandom.hex(10)}") device.update_attributes(name: "#{SecureRandom.hex(10)}")
run_jobs_now { post :reset, params: { password: password } } run_jobs_now { post :reset, body: { password: password }.to_json }
resources resources
.without("token_issuance") .without("token_issuance")
@ -44,7 +44,7 @@ describe Api::DevicesController do
sign_in user sign_in user
device = user.device device = user.device
run_jobs_now { post :reset, params: {} } run_jobs_now { post :reset, body: {}.to_json }
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json.fetch(:password)).to eq("Password is required") expect(json.fetch(:password)).to eq("Password is required")
end end

View File

@ -183,7 +183,7 @@ describe Api::DevicesController do
old_name = device.name old_name = device.name
expect(device.plants.count).to eq(0) expect(device.plants.count).to eq(0)
run_jobs_now do run_jobs_now do
post :seed, params: { product_line: "none" } post :seed, body: { product_line: "none" }.to_json
end end
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(device.plants.count).to eq(0) expect(device.plants.count).to eq(0)
@ -192,7 +192,9 @@ describe Api::DevicesController do
def start_tests(product_line) def start_tests(product_line)
sign_in user sign_in user
run_jobs_now { post :seed, params: { product_line: product_line } } run_jobs_now do
post :seed, body: { product_line: product_line }.to_json
end
expect(response.status).to eq(200) expect(response.status).to eq(200)
device.reload device.reload
end end

View File

@ -5,17 +5,16 @@ require "spec_helper"
describe Api::DevicesController do describe Api::DevicesController do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
describe "#update" do describe "#update" do
let(:user) { FactoryBot.create(:user) }
let(:user) { FactoryBot.create(:user) } let(:user2) { FactoryBot.create(:user) }
let(:user2) { FactoryBot.create(:user) }
let(:device) { user.device } let(:device) { user.device }
let(:tool) { FactoryBot.create(:tool, device: user.device) } let(:tool) { FactoryBot.create(:tool, device: user.device) }
it "updates a Device" do it "updates a Device" do
sign_in user sign_in user
fake_name = Faker::Name.name fake_name = Faker::Name.name
put :update, put :update,
params: {id: user.device.id, name: fake_name}, body: { id: user.device.id, name: fake_name }.to_json,
session: { format: :json } session: { format: :json }
# put path, params, options # put path, params, options
user.reload user.reload
@ -28,7 +27,7 @@ describe Api::DevicesController do
sign_in user sign_in user
before = user.device.timezone before = user.device.timezone
put :update, put :update,
params: {id: user.device.id, timezone: "NO!"}, body: { id: user.device.id, timezone: "NO!" }.to_json,
session: { format: :json } session: { format: :json }
# put path, params, options # put path, params, options
user.reload user.reload
@ -41,7 +40,7 @@ describe Api::DevicesController do
it "updates a Device timezone correctly" do it "updates a Device timezone correctly" do
sign_in user sign_in user
fake_tz = Device::TIMEZONES.sample fake_tz = Device::TIMEZONES.sample
put :update, params: {id: user.device.id, timezone: fake_tz}, session: { format: :json } put :update, body: { id: user.device.id, timezone: fake_tz }.to_json, session: { format: :json }
user.reload user.reload
device = user.reload.device.reload device = user.reload.device.reload
expect(device.timezone).to eq(fake_tz) expect(device.timezone).to eq(fake_tz)
@ -51,10 +50,10 @@ describe Api::DevicesController do
it "mounts a tool" do it "mounts a tool" do
sign_in user sign_in user
put :update, put :update,
params: { body: {
id: user.device.id, id: user.device.id,
mounted_tool_id: tool.id mounted_tool_id: tool.id,
}, }.to_json,
session: { format: :json } session: { format: :json }
user.reload user.reload
device = user.reload.device.reload device = user.reload.device.reload
@ -65,10 +64,10 @@ describe Api::DevicesController do
it "performs referential integrity checks on mounted_tool_id" do it "performs referential integrity checks on mounted_tool_id" do
sign_in user sign_in user
put :update, put :update,
params: { body: {
id: user.device.id, id: user.device.id,
mounted_tool_id: (FactoryBot.create(:tool).id + 1) mounted_tool_id: (FactoryBot.create(:tool).id + 1),
}, }.to_json,
session: { format: :json } session: { format: :json }
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:mounted_tool_id]).to include("Can't mount to tool") expect(json[:mounted_tool_id]).to include("Can't mount to tool")
@ -79,10 +78,10 @@ describe Api::DevicesController do
device.update_attributes!(mounted_tool_id: tool.id) device.update_attributes!(mounted_tool_id: tool.id)
expect(device.mounted_tool_id).to be expect(device.mounted_tool_id).to be
put :update, put :update,
params: { id: user.device.id, mounted_tool_id: 0 }, body: { id: user.device.id, mounted_tool_id: 0 }.to_json,
session: { format: :json } session: { format: :json }
expect(device.reload.mounted_tool_id).not_to be expect(device.reload.mounted_tool_id).not_to be
expect(json[:mounted_tool_id]).to be(nil) expect(json[:mounted_tool_id]).to be(nil)
end end
end end
end end

View File

@ -1,16 +1,16 @@
require 'spec_helper' require "spec_helper"
describe Api::PasswordResetsController do describe Api::PasswordResetsController do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
describe '#create' do describe "#create" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
it 'resets password for a user' do it "resets password for a user" do
params = { email: user.email } params = { email: user.email }
old_email_count = ActionMailer::Base.deliveries.length old_email_count = ActionMailer::Base.deliveries.length
run_jobs_now do run_jobs_now do
post :create, params: params post :create, body: params.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(ActionMailer::Base.deliveries.length).to be > old_email_count expect(ActionMailer::Base.deliveries.length).to be > old_email_count
message = last_email.to_s message = last_email.to_s
@ -18,53 +18,53 @@ describe Api::PasswordResetsController do
end end
end end
it 'resets password using a reset token' do it "resets password using a reset token" do
params = {password: "xpassword123", params = { password: "xpassword123",
password_confirmation: "xpassword123", password_confirmation: "xpassword123",
fbos_version: Gem::Version.new("999.9.9"), fbos_version: Gem::Version.new("999.9.9"),
id: PasswordResetToken id: PasswordResetToken
.issue_to(user) .issue_to(user)
.encoded } .encoded }
put :update, params: params put :update, body: params.to_json, format: :json
expect(user expect(user
.reload .reload
.valid_password?(params[:password])).to eq(true) .valid_password?(params[:password])).to eq(true)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json.keys).to include(:token) expect(json.keys).to include(:token)
expect(json.keys).to include(:user) expect(json.keys).to include(:user)
end end
it 'disallows short passwords' do it "disallows short passwords" do
params = {password: "xpass", params = { password: "xpass",
password_confirmation: "xpass", password_confirmation: "xpass",
fbos_version: Gem::Version.new("999.9.9"), fbos_version: Gem::Version.new("999.9.9"),
id: PasswordResetToken id: PasswordResetToken
.issue_to(user) .issue_to(user)
.encoded } .encoded }
put :update, params: params put :update, body: params.to_json, format: :json
expect(user expect(user
.reload .reload
.valid_password?(params[:password])).to eq(false) .valid_password?(params[:password])).to eq(false)
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:password]).to include("too short") expect(json[:password]).to include("too short")
end end
it 'handles token expiration' do it "handles token expiration" do
token = PasswordResetToken token = PasswordResetToken
.issue_to(user, {exp: Time.now.yesterday}) .issue_to(user, { exp: Time.now.yesterday })
.encoded .encoded
params = { password: "xpassword123", params = { password: "xpassword123",
password_confirmation: "xpassword123", password_confirmation: "xpassword123",
id: token } id: token }
put :update, params: params put :update, body: params.to_json, format: :json
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(user.reload.valid_password?(params[:password])).to eq(false) expect(user.reload.valid_password?(params[:password])).to eq(false)
expect(json.to_json).to include(PasswordResets::Update::OLD_TOKEN) expect(json.to_json).to include(PasswordResets::Update::OLD_TOKEN)
end end
it 'handles bad emails' do it "handles bad emails" do
result = PasswordResets::Create.run(email: "bad@wrong.com") result = PasswordResets::Create.run(email: "bad@wrong.com")
expect(result.errors["email"].message).to eq("Email not found") expect(result.errors["email"].message).to eq("Email not found")
end end

View File

@ -1,12 +1,12 @@
require 'spec_helper' require "spec_helper"
describe Api::PeripheralsController do describe Api::PeripheralsController do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
describe '#create' do describe "#create" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
it 'makes a Peripheral' do it "makes a Peripheral" do
sign_in user sign_in user
before = Peripheral.count before = Peripheral.count
post :create, post :create,
@ -18,12 +18,12 @@ describe Api::PeripheralsController do
expect(before < Peripheral.count).to be_truthy expect(before < Peripheral.count).to be_truthy
end end
it 'requires logged in user' do it "requires logged in user" do
post :create, params: { pin: 13, label: "LED" } post :create, body: { pin: 13, label: "LED" }.to_json
expect(response.status).to eq(401) expect(response.status).to eq(401)
end end
it 'limits label length' do it "limits label length" do
sign_in user sign_in user
before = Peripheral.count before = Peripheral.count
post :create, post :create,

View File

@ -10,7 +10,7 @@ describe Api::RegimensController do
it "kicks back missing parameters" do it "kicks back missing parameters" do
sign_in user sign_in user
celery = File.read("spec/lib/celery_script/ast_fixture5.json") celery = File.read("spec/lib/celery_script/ast_fixture5.json")
json = JSON.parse(celery).deep_symbolize_keys json = JSON.parse(celery, symbolize_names: true)
s = Sequences::Create.run!(json, device: user.device) s = Sequences::Create.run!(json, device: user.device)
# No paramaters here. # No paramaters here.

View File

@ -3,11 +3,10 @@ require "spec_helper"
describe Api::SavedGardensController do describe Api::SavedGardensController do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:saved_gardens) { FactoryBot.create_list(:saved_garden, 3, device: user.device) } let(:saved_gardens) { FactoryBot.create_list(:saved_garden, 3, device: user.device) }
describe "#index" do describe "#index" do
it "shows all saved_gardens" do it "shows all saved_gardens" do
sign_in user sign_in user
garden_size = saved_gardens.length garden_size = saved_gardens.length
@ -23,7 +22,7 @@ describe Api::SavedGardensController do
sign_in user sign_in user
b4 = user.device.saved_gardens.count b4 = user.device.saved_gardens.count
params = { name: Faker::Food.vegetables } params = { name: Faker::Food.vegetables }
post :create, params: {format: :json}, body: params.to_json post :create, params: { format: :json }, body: params.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json[:name]).to be_kind_of(String) expect(json[:name]).to be_kind_of(String)
expect(json[:name]).to eq(params[:name]) expect(json[:name]).to eq(params[:name])
@ -35,7 +34,7 @@ describe Api::SavedGardensController do
it "updates attributes" do it "updates attributes" do
sign_in user sign_in user
garden = saved_gardens.first garden = saved_gardens.first
b4 = garden.name b4 = garden.name
params = { name: Faker::Food.vegetables } params = { name: Faker::Food.vegetables }
put :update, params: { format: :json, id: garden.id }, body: params.to_json put :update, params: { format: :json, id: garden.id }, body: params.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -48,7 +47,7 @@ describe Api::SavedGardensController do
it "destroys saved_gardens" do it "destroys saved_gardens" do
sign_in user sign_in user
garden = saved_gardens.first garden = saved_gardens.first
b4 = saved_gardens.length b4 = saved_gardens.length
delete :destroy, params: { id: garden.id } delete :destroy, params: { id: garden.id }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(user.device.saved_gardens.count).to be < b4 expect(user.device.saved_gardens.count).to be < b4
@ -60,7 +59,7 @@ describe Api::SavedGardensController do
SavedGarden.destroy_all SavedGarden.destroy_all
PlantTemplate.destroy_all PlantTemplate.destroy_all
sign_in user sign_in user
gardens_b4 = user.device.saved_gardens.count gardens_b4 = user.device.saved_gardens.count
templates_b4 = user.device.plant_templates.count templates_b4 = user.device.plant_templates.count
plants = FactoryBot.create_list(:plant, 3, device: user.device) plants = FactoryBot.create_list(:plant, 3, device: user.device)
post :snapshot post :snapshot
@ -81,7 +80,7 @@ describe Api::SavedGardensController do
saved_garden = FactoryBot.create(:saved_garden, device: user.device) saved_garden = FactoryBot.create(:saved_garden, device: user.device)
FactoryBot.create_list(:plant_template, 3, device: user.device, saved_garden: saved_garden) FactoryBot.create_list(:plant_template, 3, device: user.device, saved_garden: saved_garden)
old_plant_count = user.device.plants.count old_plant_count = user.device.plants.count
patch :apply, params: {id: saved_garden.id } patch :apply, params: { id: saved_garden.id }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(user.device.plants.count).to be > old_plant_count expect(user.device.plants.count).to be > old_plant_count
end end
@ -96,25 +95,22 @@ describe Api::SavedGardensController do
saved_garden: saved_garden) saved_garden: saved_garden)
plant = FactoryBot.create(:plant, device: user.device) plant = FactoryBot.create(:plant, device: user.device)
FakeSequence.create(device: user.device, FakeSequence.create(device: user.device,
body: [{ kind: "move_absolute", body: [{ kind: "move_absolute",
args: { args: {
location: { location: {
kind: "point", kind: "point",
args: { pointer_type: "Plant", pointer_id: plant.id } args: { pointer_type: "Plant", pointer_id: plant.id },
}, },
speed: 100, speed: 100,
offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } } offset: { kind: "coordinate", args: { x: 0, y: 0, z: 0 } },
} } }])
}])
old_plant_count = user.device.plants.count old_plant_count = user.device.plants.count
post :apply, params: {id: saved_garden.id } post :apply, params: { id: saved_garden.id }
expect(response.status).to be(422) expect(response.status).to be(422)
expect(user.device.plants.count).to eq(old_plant_count) expect(user.device.plants.count).to eq(old_plant_count)
expect(json[:whoops]) expect(json[:whoops]).to include("Unable to remove the following plants from the garden")
.to include("Unable to remove the following plants from the garden") expect(json[:whoops]).to include("plant at (#{plant.x}, #{plant.y}, #{plant.z})")
expect(json[:whoops])
.to include("plant at (#{plant.x}, #{plant.y}, #{plant.z})")
end end
it "performs 'destructive' garden application" do it "performs 'destructive' garden application" do
@ -123,10 +119,10 @@ describe Api::SavedGardensController do
PlantTemplate.destroy_all PlantTemplate.destroy_all
sign_in user sign_in user
saved_garden = FactoryBot.create(:saved_garden, device: user.device) saved_garden = FactoryBot.create(:saved_garden, device: user.device)
plant = FactoryBot.create(:plant, device: user.device) plant = FactoryBot.create(:plant, device: user.device)
FactoryBot.create_list(:plant_template, 3, device: user.device, saved_garden: saved_garden) FactoryBot.create_list(:plant_template, 3, device: user.device, saved_garden: saved_garden)
old_plant_count = user.device.plants.count old_plant_count = user.device.plants.count
post :apply, params: {id: saved_garden.id } post :apply, params: { id: saved_garden.id }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(user.device.plants.count).to be > old_plant_count expect(user.device.plants.count).to be > old_plant_count
expect(Plant.exists?(plant.id)).to be false expect(Plant.exists?(plant.id)).to be false

View File

@ -1,45 +1,45 @@
require 'spec_helper' require "spec_helper"
describe Api::SensorReadingsController do describe Api::SensorReadingsController do
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
describe 'CRUD actions' do describe "CRUD actions" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let (:reading) { FactoryBot.create(:sensor_reading, device: user.device) } let (:reading) { FactoryBot.create(:sensor_reading, device: user.device) }
it 'makes a sensor reading' do it "makes a sensor reading" do
sign_in user sign_in user
before = SensorReading.count before = SensorReading.count
post :create, post :create,
body: { pin: 13, value: 128, x: nil, y: 1, z: 2, mode: 1 }.to_json, body: { pin: 13, value: 128, x: nil, y: 1, z: 2, mode: 1 }.to_json,
params: { format: :json } params: { format: :json }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json[:id]).to be_kind_of(Integer) expect(json[:id]).to be_kind_of(Integer)
expect(json[:created_at]).to be_kind_of(String) expect(json[:created_at]).to be_kind_of(String)
expect(json[:value]).to eq(128) expect(json[:value]).to eq(128)
expect(json[:device_id]).to eq(nil) # Use the serializer, not as_json. expect(json[:device_id]).to eq(nil) # Use the serializer, not as_json.
expect(json[:x]).to eq(nil) expect(json[:x]).to eq(nil)
expect(json[:y]).to eq(1) expect(json[:y]).to eq(1)
expect(json[:z]).to eq(2) expect(json[:z]).to eq(2)
expect(json[:pin]).to eq(13) expect(json[:pin]).to eq(13)
expect(json[:mode]).to eq(1) expect(json[:mode]).to eq(1)
expect(before < SensorReading.count).to be_truthy expect(before < SensorReading.count).to be_truthy
end end
it 'shows one reading' do it "shows one reading" do
sign_in user sign_in user
SensorReading.destroy_all SensorReading.destroy_all
id = reading.id id = reading.id
get :show, params: { format: :json, id: id } get :show, params: { format: :json, id: id }
expect(json).to be_kind_of(Hash) expect(json).to be_kind_of(Hash)
reading.reload reading.reload
[ :id, :value, :x, :y, :z, :pin, :mode ].map do |attr| [:id, :value, :x, :y, :z, :pin, :mode].map do |attr|
expect(json[attr]).to eq(reading.send(attr)) expect(json[attr]).to eq(reading.send(attr))
end end
end end
it 'shows all readings' do it "shows all readings" do
sign_in user sign_in user
SensorReading.destroy_all SensorReading.destroy_all
id = reading.id id = reading.id
@ -47,11 +47,11 @@ describe Api::SensorReadingsController do
expect(json).to be_kind_of(Array) expect(json).to be_kind_of(Array)
expect(json.length).to eq(user.device.sensor_readings.length) expect(json.length).to eq(user.device.sensor_readings.length)
keys = json.first.keys keys = json.first.keys
expect(json.map{|x| x[:id] }).to include(id) expect(json.map { |x| x[:id] }).to include(id)
expect(keys).to include(:x, :y, :z, :value, :pin) expect(keys).to include(:x, :y, :z, :value, :pin)
end end
it 'destroys a reading' do it "destroys a reading" do
sign_in user sign_in user
SensorReading.destroy_all SensorReading.destroy_all
id = reading.id id = reading.id
@ -62,8 +62,8 @@ describe Api::SensorReadingsController do
expect(before).to be > SensorReading.count expect(before).to be > SensorReading.count
end end
it 'requires logged in user' do it "requires logged in user" do
post :create, params: {} post :create, body: {}.to_json
expect(response.status).to eq(401) expect(response.status).to eq(401)
end end
end end

View File

@ -8,7 +8,7 @@ describe Api::TokensController do
let(:user) { FactoryBot.create(:user, password: "password") } let(:user) { FactoryBot.create(:user, password: "password") }
it 'creates a new token' do it 'creates a new token' do
payload = {user: {email: user.email, password: "password"}} payload = {user: {email: user.email, password: "password"}}
post :create, params: payload post :create, body: payload.to_json
token = json[:token][:unencoded] token = json[:token][:unencoded]
expect(token[:iss].last).not_to eq("/") # Trailing slashes are BAD! expect(token[:iss].last).not_to eq("/") # Trailing slashes are BAD!
expect(token[:iss]).to include($API_URL) expect(token[:iss]).to include($API_URL)
@ -17,14 +17,14 @@ describe Api::TokensController do
it 'handles bad params' do it 'handles bad params' do
err_msg = Api::TokensController::NO_USER_ATTR err_msg = Api::TokensController::NO_USER_ATTR
payload = {user: "NOPE!"} payload = {user: "NOPE!"}
post :create, params: payload post :create, body: payload.to_json
expect(json[:error]).to include(err_msg) expect(json[:error]).to include(err_msg)
end end
it 'does not bump last_saw_api if it is not a bot' do it 'does not bump last_saw_api if it is not a bot' do
payload = {user: {email: user.email, password: "password"}} payload = {user: {email: user.email, password: "password"}}
before = user.device.last_saw_api before = user.device.last_saw_api
post :create, params: payload post :create, body: payload.to_json
after = user.device.reload.last_saw_api after = user.device.reload.last_saw_api
expect(before).to eq(after) expect(before).to eq(after)
end end
@ -35,7 +35,7 @@ describe Api::TokensController do
request.env["HTTP_USER_AGENT"] = ua request.env["HTTP_USER_AGENT"] = ua
payload = {user: {email: user.email, password: "password"}} payload = {user: {email: user.email, password: "password"}}
before = user.device.last_saw_api || Time.now before = user.device.last_saw_api || Time.now
post :create, params: payload post :create, body: payload.to_json
after = user.device.reload.last_saw_api after = user.device.reload.last_saw_api
expect(after).to be expect(after).to be
expect(after).to be > before expect(after).to be > before
@ -48,7 +48,7 @@ describe Api::TokensController do
payload = {user: {email: user.email, password: "password"}} payload = {user: {email: user.email, password: "password"}}
allow_any_instance_of(Api::TokensController) allow_any_instance_of(Api::TokensController)
.to receive(:xhr?).and_return(true) .to receive(:xhr?).and_return(true)
post :create, params: payload post :create, body: payload.to_json
expect(json.dig(:token, :unencoded, :aud)).to be expect(json.dig(:token, :unencoded, :aud)).to be
expect(json.dig(:token, :unencoded, :aud)) expect(json.dig(:token, :unencoded, :aud))
.to eq(AbstractJwtToken::HUMAN_AUD) .to eq(AbstractJwtToken::HUMAN_AUD)
@ -56,7 +56,7 @@ describe Api::TokensController do
it "issues a '?' AUD to all others" do it "issues a '?' AUD to all others" do
payload = {user: {email: user.email, password: "password"}} payload = {user: {email: user.email, password: "password"}}
post :create, params: payload post :create, body: payload.to_json
expect(json.dig(:token, :unencoded, :aud)).to be expect(json.dig(:token, :unencoded, :aud)).to be
expect(json.dig(:token, :unencoded, :aud)) expect(json.dig(:token, :unencoded, :aud))
.to eq(AbstractJwtToken::UNKNOWN_AUD) .to eq(AbstractJwtToken::UNKNOWN_AUD)

View File

@ -26,7 +26,7 @@ describe Api::UsersController do
it "errors if you try to delete with the wrong password" do it "errors if you try to delete with the wrong password" do
sign_in user sign_in user
delete :destroy, params: { password: "NOPE!" }, format: :json delete :destroy, body: { password: "NOPE!" }.to_json, format: :json
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:password]).to eq(Users::Destroy::BAD_PASSWORD) expect(json[:password]).to eq(Users::Destroy::BAD_PASSWORD)
end end
@ -34,7 +34,7 @@ describe Api::UsersController do
it "deletes a user account" do it "deletes a user account" do
sign_in user sign_in user
run_jobs_now do run_jobs_now do
delete :destroy, params: { password: user.password }, format: :json delete :destroy, body: { password: user.password }.to_json, format: :json
end end
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(User.where(id: user.id).count).to eq(0) expect(User.where(id: user.id).count).to eq(0)
@ -48,7 +48,7 @@ describe Api::UsersController do
name: "Ricky McRickerson", name: "Ricky McRickerson",
format: :json, format: :json,
} }
patch :update, params: input patch :update, body: input.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json[:name]).to eq("Ricky McRickerson") expect(json[:name]).to eq("Ricky McRickerson")
unless User::SKIP_EMAIL_VALIDATION unless User::SKIP_EMAIL_VALIDATION
@ -73,7 +73,7 @@ describe Api::UsersController do
format: :json, format: :json,
} }
expect(TokenIssuance.where(device: user.device).count).to be >= 1 expect(TokenIssuance.where(device: user.device).count).to be >= 1
patch :update, params: input patch :update, body: input.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(TokenIssuance.where(device: user.device).count).to be <= 1 expect(TokenIssuance.where(device: user.device).count).to be <= 1
user.reload user.reload
@ -90,7 +90,7 @@ describe Api::UsersController do
password_confirmation: "123456789", password_confirmation: "123456789",
format: :json, format: :json,
} }
patch :update, params: input patch :update, body: input.to_json
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:password]).to eq(Users::Update::PASSWORD_PROBLEMS) expect(json[:password]).to eq(Users::Update::PASSWORD_PROBLEMS)
end end
@ -104,7 +104,7 @@ describe Api::UsersController do
password_confirmation: "123456789", password_confirmation: "123456789",
format: :json, format: :json,
} }
patch :update, params: input patch :update, body: input.to_json
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:password]).to eq(Users::Update::PASSWORD_PROBLEMS) expect(json[:password]).to eq(Users::Update::PASSWORD_PROBLEMS)
end end
@ -119,7 +119,7 @@ describe Api::UsersController do
name: "Frank" } name: "Frank" }
old_email_count = ActionMailer::Base.deliveries.length old_email_count = ActionMailer::Base.deliveries.length
run_jobs_now do run_jobs_now do
post :create, params: params post :create, body: params.to_json
user = User.last user = User.last
if User::SKIP_EMAIL_VALIDATION if User::SKIP_EMAIL_VALIDATION
puts BIG_WARNING puts BIG_WARNING
@ -151,7 +151,7 @@ describe Api::UsersController do
password: "Password321", password: "Password321",
email: email, email: email,
name: "Frank" } name: "Frank" }
post :create, params: params post :create, body: params.to_json
expect(User.count > original_count).to be_falsy expect(User.count > original_count).to be_falsy
expect(json[:password]).to include("do not match") expect(json[:password]).to include("do not match")
expect(response.status).to eq(422) expect(response.status).to eq(422)
@ -160,9 +160,9 @@ describe Api::UsersController do
it "generates a certificate to transfer device control" do it "generates a certificate to transfer device control" do
user1 = FactoryBot.create(:user, password: "password123") user1 = FactoryBot.create(:user, password: "password123")
user2 = FactoryBot.create(:user, password: "password456") user2 = FactoryBot.create(:user, password: "password456")
body = { email: user2.email, password: "password456" }.to_json body = { email: user2.email, password: "password456" }
sign_in user1 sign_in user1
post :control_certificate, body: body, format: :json post :control_certificate, body: body.to_json, format: :json
expect(response.status).to eq(200) expect(response.status).to eq(200)
credentials = response.body credentials = response.body
expect(credentials).to be_kind_of(String) expect(credentials).to be_kind_of(String)
@ -173,9 +173,9 @@ describe Api::UsersController do
it "prevents creating control certs for bad credentials" do it "prevents creating control certs for bad credentials" do
user1 = FactoryBot.create(:user, password: "password123") user1 = FactoryBot.create(:user, password: "password123")
body = { email: "wrong@wrong.com", password: "password456" }.to_json body = { email: "wrong@wrong.com", password: "password456" }
sign_in user1 sign_in user1
post :control_certificate, body: body, format: :json post :control_certificate, body: body.to_json, format: :json
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(json[:credentials]).to include("can't proceed") expect(json[:credentials]).to include("can't proceed")
end end
@ -187,7 +187,7 @@ describe Api::UsersController do
confirmed_at: Time.now) confirmed_at: Time.now)
post :resend_verification, post :resend_verification,
params: { email: verified.email }, body: { email: verified.email }.to_json,
format: :json format: :json
expect(response.status).to eq(422) expect(response.status).to eq(422)
@ -200,9 +200,8 @@ describe Api::UsersController do
password_confirmation: "password123") password_confirmation: "password123")
post :resend_verification, post :resend_verification,
params: { email: unverified.email }, body: { email: unverified.email }.to_json,
format: :json format: :json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json[:user]).to include(Users::ResendVerification::SENT) expect(json[:user]).to include(Users::ResendVerification::SENT)
end end

View File

@ -8,7 +8,7 @@ describe Api::WebcamFeedsController do
sign_in user sign_in user
input = { name: "name1", url: "url1" } input = { name: "name1", url: "url1" }
b4 = WebcamFeed.count b4 = WebcamFeed.count
post :create, params: input post :create, body: input.to_json
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(WebcamFeed.count).to be > b4 expect(WebcamFeed.count).to be > b4
expect(user.device.webcam_feeds.count).to eq(1) expect(user.device.webcam_feeds.count).to eq(1)

View File

@ -1,18 +1,18 @@
require 'spec_helper' require "spec_helper"
describe Api::WebcamFeedsController do describe Api::WebcamFeedsController do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
include Devise::Test::ControllerHelpers include Devise::Test::ControllerHelpers
it 'updates a webcam feed URL' do it "updates a webcam feed URL" do
# Create a webcam feed first.... # Create a webcam feed first....
sign_in user sign_in user
feed = WebcamFeed.create! name: "wow", feed = WebcamFeed.create! name: "wow",
device: user.device, device: user.device,
url: "bar.jpg" url: "bar.jpg"
input = { url: "/foo.jpg", name: "ok", format: :json, id: feed.id } input = { url: "/foo.jpg", name: "ok" }
patch :update, params: input patch :update, body: input.to_json, params: { format: :json, id: feed.id }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json[:url]).to eq("/foo.jpg") expect(json[:url]).to eq("/foo.jpg")
expect(json[:name]).to eq("ok") expect(json[:name]).to eq("ok")

View File

@ -4,7 +4,7 @@ describe CeleryScript::AstNode do
FIXTURE_FILE = File.read("./spec/lib/celery_script/ast_fixture2.json") FIXTURE_FILE = File.read("./spec/lib/celery_script/ast_fixture2.json")
let(:hash) do let(:hash) do
JSON.parse(FIXTURE_FILE).deep_symbolize_keys JSON.parse(FIXTURE_FILE, symbolize_names: true)
end end
let (:node) { CeleryScript::AstNode.new(**hash) } let (:node) { CeleryScript::AstNode.new(**hash) }

View File

@ -49,7 +49,7 @@ describe Resources::Preprocessor do
expect(err.last[:routing_key]).to be_kind_of(String) expect(err.last[:routing_key]).to be_kind_of(String)
dev_id = err.last[:routing_key].split(".").second dev_id = err.last[:routing_key].split(".").second
expect(dev_id).to eq("device_#{props[:device_id]}") expect(dev_id).to eq("device_#{props[:device_id]}")
body = JSON.parse(err.first).deep_symbolize_keys body = JSON.parse(err.first, symbolize_names: true)
expect(body[:kind]).to eq("rpc_error") expect(body[:kind]).to eq("rpc_error")
expect(body[:args]).to be_kind_of(Hash) expect(body[:args]).to be_kind_of(Hash)
expect(body[:body]).to be_kind_of(Array) expect(body[:body]).to be_kind_of(Array)

View File

@ -86,7 +86,7 @@ RSpec.configure do |config|
end end
config.color = true config.color = true
config.fail_fast = 10 # config.fail_fast = 10
config.backtrace_exclusion_patterns = [/gems/] config.backtrace_exclusion_patterns = [/gems/]
config.filter_run_excluding type: :feature unless DO_INTEGRATION config.filter_run_excluding type: :feature unless DO_INTEGRATION
config.include Helpers config.include Helpers

View File

@ -1,5 +1,5 @@
class Hash class Hash
def traverse(parent=nil, &blk) def traverse(parent = nil, &blk)
each do |k, v| each do |k, v|
Hash === v ? v.traverse(k, &blk) : blk.call([parent, k, v]) Hash === v ? v.traverse(k, &blk) : blk.call([parent, k, v])
end end
@ -7,9 +7,9 @@ class Hash
end end
module Helpers module Helpers
MAGIC_NUMBER_SEQ_ID = "9999" MAGIC_NUMBER_SEQ_ID = "9999"
MAGIC_NUMBER_TOOL_ID = "8888" MAGIC_NUMBER_TOOL_ID = "8888"
AST_FIXTURE = File.read("./spec/lib/celery_script/ast_fixture3.json") AST_FIXTURE = File.read("./spec/lib/celery_script/ast_fixture3.json")
def last_email def last_email
ActionMailer::Base.deliveries.last ActionMailer::Base.deliveries.last
@ -35,8 +35,8 @@ module Helpers
sid = FakeSequence.create(device: device).id sid = FakeSequence.create(device: device).id
tid = FactoryBot.create(:tool, device: device).id tid = FactoryBot.create(:tool, device: device).id
str = AST_FIXTURE str = AST_FIXTURE
.gsub(MAGIC_NUMBER_SEQ_ID, sid.to_s) .gsub(MAGIC_NUMBER_SEQ_ID, sid.to_s)
.gsub(MAGIC_NUMBER_TOOL_ID, tid.to_s) .gsub(MAGIC_NUMBER_TOOL_ID, tid.to_s)
JSON.parse(str)["body"] JSON.parse(str)["body"]
end end
@ -44,18 +44,12 @@ module Helpers
# For when you're actually testing the login UI components. Otherwise, # For when you're actually testing the login UI components. Otherwise,
# consider using the devise test helper `sign_in` # consider using the devise test helper `sign_in`
visit new_user_session_path visit new_user_session_path
fill_in 'user_email', with: user.email fill_in "user_email", with: user.email
fill_in 'user_password', with: user.password fill_in "user_password", with: user.password
click_button 'Sign in' click_button "Sign in"
end end
def json def json
json = JSON.parse(response.body) JSON.parse(response.body, symbolize_names: true)
if json.is_a?(Array)
json.map(&:deep_symbolize_keys!)
else
json.deep_symbolize_keys!
end
end end
end end