Commit f4f863c5 by source_reader

improving test coverage

parent e1a35380
Showing with 424 additions and 432 deletions
# frozen_string_literal: true
module Joule
# Handles construction of database objects
class UpdateModules
include ServiceStatus
def initialize(nilm)
super()
@nilm = nilm
end
def run(module_schemas)
#module_info as returned by JouleBackend
if module_schemas.nil?
add_error("unable to retrieve module information")
return self
end
#remove the previous modules
@nilm.joule_modules.destroy_all
module_schemas.each do |schema|
@nilm.joule_modules << _build_module(schema)
end
set_notice("refreshed modules")
self
end
def _build_module(schema)
# create JouleModule and associated pipes from
# hash returned by the JouleAdapter.module_info
attrs = schema.slice(*JouleModule.defined_attributes)
attrs[:pid] = schema[:statistics][:pid]
attrs[:web_interface] = schema[:is_app]
attrs[:joule_id] = "m#{schema[:id]}"
m = JouleModule.create(attrs)
# link inputs to database streams
schema[:inputs].each do |name, path|
m.joule_pipes << JoulePipe.new(direction: 'input',
name: name,
db_stream: _retrieve_stream(path))
end
schema[:outputs].each do |name, path|
m.joule_pipes << JoulePipe.new(direction: 'output',
name: name,
db_stream: _retrieve_stream(path))
end
m
end
def _retrieve_stream(path)
db_stream = @nilm.db.db_streams.find_by_path(path)
if db_stream.nil?
add_warning("[#{path}] not in database")
end
db_stream
end
end
end
class DataAppController < ApplicationController class DataAppController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
# GET /app/:id.json
def show def show
@app = DataApp.find(params[:id]) @app = DataApp.find(params[:id])
@nilm = @app.nilm @nilm = @app.nilm
head :unauthorized and return unless current_user.views_nilm?(@nilm) head :unauthorized and return unless current_user.views_nilm?(@nilm)
# destroy any existing tokens # destroy any existing tokens
InterfaceAuthToken.where(user: current_user, data_app: @app).destroy_all InterfaceAuthToken.where(user: current_user, data_app: @app).destroy_all
token = InterfaceAuthToken.create(data_app: @app,
user: current_user, expiration: 5.minutes.from_now) @auth_url = _app_auth_url
@auth_url = _app_auth_url(token)
end end
private private
def _app_auth_url(token) def _app_auth_url
return "#" unless request.headers.key?("HTTP_X_APP_BASE_URI") # apps not enabled return "#" unless request.headers.key?("HTTP_X_APP_BASE_URI") # apps not enabled
token = InterfaceAuthToken.create(data_app: @app,
user: current_user, expiration: 5.minutes.from_now)
base = request.headers["HTTP_X_APP_BASE_URI"] base = request.headers["HTTP_X_APP_BASE_URI"]
"#{base}/#{token.data_app.id}/?auth_token=#{token.value}" "#{base}/#{token.data_app.id}/?auth_token=#{token.value}"
end end
......
class InterfacePermissionsController < ApplicationController
before_action :authenticate_user!
before_action :set_nilm
before_action :authorize_admin
# GET /interface/:id/permissions.json
def index
# return permissions for nilm specified by nilm_id
@permissions = @nilm.permissions.includes(:user, :user_group)
end
# POST /permissions
# POST /permissions.json
def create
# create permission for nilm specified by nilm_id
@service = CreatePermission.new
@service.run(@nilm, params[:role], params[:target], params[:target_id])
@permission = @service.permission
render status: @service.success? ? :ok : :unprocessable_entity
end
# DELETE /permissions/1
# DELETE /permissions/1.json
def destroy
# remove permission from nilm specified by nilm_id
@service = DestroyPermission.new
@service.run(@nilm, current_user, params[:id])
render status: @service.success? ? :ok : :unprocessable_entity
end
# PUT /permissions/create_user.json
def create_user
@service = StubService.new
user = User.new(user_params)
unless user.save
@service.errors = user.errors.full_messages
render 'helpers/empty_response', status: :unprocessable_entity
return
end
@service = CreatePermission.new
@service.run(@nilm, params[:role], 'user', user.id)
@permission = @service.permission
@service.add_notice('created user')
render :create, status: @service.success? ? :ok : :unprocessable_entity
end
# PUT /permissions/invite_user.json
def invite_user
invitation_service = InviteUser.new()
invitation_service.run(
current_user,
params[:email],
params[:redirect_url])
unless invitation_service.success?
@service = invitation_service
render 'helpers/empty_response', status: :unprocessable_entity
return
end
@service = CreatePermission.new
@service.absorb_status(invitation_service)
@service.run(@nilm, params[:role], 'user', invitation_service.user.id)
@permission = @service.permission
render :create, status: @service.success? ? :ok : :unprocessable_entity
end
private
def set_nilm
@nilm = Nilm.find_by_id(params[:nilm_id])
head :not_found unless @nilm
end
def user_params
params.permit(:first_name, :last_name, :email,
:password, :password_confirmation)
end
# authorization based on nilms
def authorize_admin
head :unauthorized unless current_user.admins_nilm?(@nilm)
end
end
...@@ -64,16 +64,12 @@ class NilmsController < ApplicationController ...@@ -64,16 +64,12 @@ class NilmsController < ApplicationController
end end
end end
# PATCH/PUT /nilms/1/refresh.json
def refresh
end
# DELETE /nilms/1.json # DELETE /nilms/1.json
def destroy def destroy
@service = StubService.new @service = StubService.new
@nilm.destroy @nilm.destroy
@service.set_notice('removed nilm') @service.set_notice('removed nilm')
render 'helpers/empty_response', status: :ok
end end
......
...@@ -15,23 +15,28 @@ class ProxyController < ActionController::Base ...@@ -15,23 +15,28 @@ class ProxyController < ActionController::Base
response.set_header('X-JOULE-KEY', @nilm.key) response.set_header('X-JOULE-KEY', @nilm.key)
head :ok and return head :ok and return
end end
app = DataApp.find_by_id(params[:id])
head :not_found and return if app.nil?
(head :unauthorized and return) unless request.headers.key?("HTTP_X_ORIGINAL_URI") (head :unauthorized and return) unless request.headers.key?("HTTP_X_ORIGINAL_URI")
orig_query = URI.parse(request.headers["HTTP_X_ORIGINAL_URI"]).query orig_query = URI.parse(request.headers["HTTP_X_ORIGINAL_URI"]).query
head :unauthorized and return if orig_query.nil? head :unauthorized and return if orig_query.nil?
params = CGI.parse(orig_query) params = CGI.parse(orig_query)
head :unathorized and return unless params.key?("auth_token") head :unauthorized and return unless params.key?("auth_token")
token_value = params["auth_token"][0] token_value = params["auth_token"][0]
token = InterfaceAuthToken.find_by_value(token_value) token = InterfaceAuthToken.find_by_value(token_value)
head :unauthorized and return if token.nil? head :unauthorized and return if token.nil?
head :unauthorized and return if token.expiration < Time.now head :unauthorized and return if token.expiration < Time.now
token.destroy token.destroy
# make sure the user is authorized
head :unauthorized and return unless token.user.views_nilm?(app.nilm)
session[:user_id]=token.user.id session[:user_id]=token.user.id
# if the app_ids key does not exist initialize it to this app # if the app_ids key does not exist initialize it to this app
session[:app_ids] = session[:app_ids] || [@app.id] session[:app_ids] = session[:app_ids] || [app.id]
# if it does exist append this app if it is not already in the array # if it does exist append this app if it is not already in the array
session[:app_ids] |=[@app.id] session[:app_ids] |=[app.id]
response.set_header('X-PROXY-URL', @app.url) response.set_header('X-PROXY-URL', app.url)
response.set_header('X-JOULE-KEY', token.data_app.nilm.key) response.set_header('X-JOULE-KEY', token.data_app.nilm.key)
head :ok and return head :ok and return
end end
...@@ -41,6 +46,7 @@ class ProxyController < ActionController::Base ...@@ -41,6 +46,7 @@ class ProxyController < ActionController::Base
def authenticate_interface_user def authenticate_interface_user
@current_user = User.find_by_id(session[:user_id]) @current_user = User.find_by_id(session[:user_id])
return false if @current_user.nil?
@app = DataApp.find_by_id(params[:id]) @app = DataApp.find_by_id(params[:id])
# make sure the app is authorized by the cookie # make sure the app is authorized by the cookie
return false unless session.include?(:app_ids) return false unless session.include?(:app_ids)
......
...@@ -66,9 +66,11 @@ class UserGroupsController < ApplicationController ...@@ -66,9 +66,11 @@ class UserGroupsController < ApplicationController
current_user, current_user,
params[:email], params[:email],
params[:redirect_url]) params[:redirect_url])
unless invitation_service.success? unless invitation_service.success?
@service = invitation_service @service = invitation_service
render 'helpers/empty_response', status: :unprocessable_entity render 'helpers/empty_response', status: :unprocessable_entity
return
end end
user = invitation_service.user user = invitation_service.user
@service = AddGroupMember.new @service = AddGroupMember.new
......
...@@ -23,20 +23,4 @@ class UsersController < ApplicationController ...@@ -23,20 +23,4 @@ class UsersController < ApplicationController
render json: {key: auth_key.key} render json: {key: auth_key.key}
end end
# POST /users.json
def create
@service = StubService.new
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def nilm_params
params.permit(:first_name,
:last_name,
:email,
:password,
:password_confirmation)
end
end end
...@@ -22,8 +22,4 @@ class Db < ApplicationRecord ...@@ -22,8 +22,4 @@ class Db < ApplicationRecord
nilm.url nilm.url
end end
def self.json_keys
[:id, :url, :size_total, :size_db, :available,
:size_other, :version, :max_points_per_plot]
end
end end
...@@ -4,7 +4,4 @@ ...@@ -4,7 +4,4 @@
class DbDecimation < ApplicationRecord class DbDecimation < ApplicationRecord
belongs_to :db_stream belongs_to :db_stream
def as_json(_options = {})
super(except: [:created_at, :updated_at])
end
end end
...@@ -8,8 +8,7 @@ class DbDataTypeValidator < ActiveModel::Validator ...@@ -8,8 +8,7 @@ class DbDataTypeValidator < ActiveModel::Validator
return if record.db_elements.count.zero? return if record.db_elements.count.zero?
# TODO: check for valid format strings (float32, uint8, etc) # TODO: check for valid format strings (float32, uint8, etc)
unless record.db_elements.count == record.column_count unless record.db_elements.count == record.column_count
record.errors[:base] << "must have #{record.column_count} \ record.errors[:base] << "must have #{record.column_count} elements for format #{record.data_type}"
elements for format #{record.data_type}"
end end
end end
end end
...@@ -32,15 +31,6 @@ class DbStream < ApplicationRecord ...@@ -32,15 +31,6 @@ class DbStream < ApplicationRecord
[:name, :name_abbrev, :description, :hidden, :data_type, :locked] [:name, :name_abbrev, :description, :hidden, :data_type, :locked]
end end
# def name_path
# "#{db_folder.name_path}/#{self.name}"
# end
def remove(db_service:)
db_service.remove_file(path)
destroy
end
def data_format def data_format
/^(\w*)_\d*$/.match(data_type)[1] /^(\w*)_\d*$/.match(data_type)[1]
end end
......
class JouleModule < ApplicationRecord
belongs_to :nilm
has_many :joule_pipes, dependent: :destroy
# attributes accepted from the Joule json response
def self.defined_attributes
[:name, :description, :web_interface, :exec_cmd,
:status, :pid, :joule_id]
end
# attributes sent to the client
def self.json_keys
[:id, :name, :description, :web_interface, :exec_cmd,
:status, :pid, :joule_id]
end
def url
"#{nilm.url}/app/#{joule_id}"
end
end
class JoulePipe < ApplicationRecord
belongs_to :joule_module
belongs_to :db_stream
end
...@@ -10,7 +10,6 @@ class Nilm < ApplicationRecord ...@@ -10,7 +10,6 @@ class Nilm < ApplicationRecord
has_many :user_groups, through: :permissions has_many :user_groups, through: :permissions
has_many :data_views_nilms has_many :data_views_nilms
has_many :data_views, through: :data_views_nilms has_many :data_views, through: :data_views_nilms
has_many :joule_modules, dependent: :destroy
has_many :data_apps, dependent: :destroy has_many :data_apps, dependent: :destroy
#---Validations----- #---Validations-----
validates :name, presence: true, uniqueness: true validates :name, presence: true, uniqueness: true
......
...@@ -16,17 +16,11 @@ class Permission < ApplicationRecord ...@@ -16,17 +16,11 @@ class Permission < ApplicationRecord
end end
def target_name def target_name
if self.user_id? return self.user.name if self.user_id?
if self.user.name.empty? return self.user_group.name if self.user_group_id?
return self.user.email
else "[no target set]"
return self.user.name
end
elsif self.user_group_id?
return self.user_group.name
else
return "[no target set]"
end
end end
def target_type def target_type
......
# frozen_string_literal: true
# NOTE: This file is out of date!!
# Agent class for DbFolders
class InsertStream
attr_accessor :error_msg
def initialize(db_service:, db_builder:)
@db_service = db_service
@db_builder = db_builder
end
def insert_stream(folder:, stream:)
@error_msg = ''
return false unless __put_stream_in_folder(stream: stream, folder: folder)
return false unless __make_path_for_stream(stream: stream, folder: folder)
return false unless __create_stream_on_db(stream: stream)
stream.save!
end
def __put_stream_in_folder(stream:, folder:)
return true if folder.insert_stream(stream: stream)
@error_msg = "could not add stream to folder #{folder.name}"
false
end
def __make_path_for_stream(stream:, folder:)
stream.path = @db_builder.build_path(folder_path: folder.path,
stream_name: stream.name)
true
end
def __create_stream_on_db(stream:)
return true if @db_service.create_stream(stream)
@error_msg = "from db_service: #{db_service.error_msg}"
stream.path = '' # clear out the stream settings
stream.folder = nil
false
end
end
json.db do
json.extract! db, *Db.json_keys
if(db.root_folder != nil)
json.contents do
root = db.root_folder
json.extract! root, *DbFolder.json_keys
json.subfolders(root.subfolders) do |folder|
json.extract! folder, *DbFolder.json_keys
end
json.streams(root.db_streams) do |stream|
json.extract! stream, *DbStream.json_keys
json.elements(stream.db_elements) do |element|
json.extract! element, *DbElement.json_keys
end
end
end
end
end
json.data do
# nothing here
end
json.partial! "helpers/messages", service: @service
# frozen_string_literal: true
json.data do
json.extract! @nilm, *Nilm.json_keys
unless @nilm.db.nil?
json.db_id @nilm.db.id
json.available @nilm.db.available
#show the database which was refreshed
json.db do
json.partial! 'dbs/db', db: @nilm.db
end
end
end
json.partial! 'helpers/messages', service: @service
...@@ -40,4 +40,13 @@ Rails.application.configure do ...@@ -40,4 +40,13 @@ Rails.application.configure do
# Raises error for missing translations # Raises error for missing translations
# config.action_view.raise_on_missing_translations = true # config.action_view.raise_on_missing_translations = true
# display custom label in page header
#
config.node_name = "Test Environment"
# enable password recovery and e-mail invitations
# NOTE: configure smtp.rb with SMTP server details
#
config.send_emails = false
end end
Rails.application.routes.draw do Rails.application.routes.draw do
resources :nilms, only: [:index, :show, :create, :update, :destroy] do resources :nilms, only: [:index, :show, :create, :update, :destroy]
member do
put 'refresh'
end
end
resources :data_views do resources :data_views do
collection do collection do
...@@ -32,7 +28,7 @@ Rails.application.routes.draw do ...@@ -32,7 +28,7 @@ Rails.application.routes.draw do
devise_for :users, path: "auth", only: [:invitations], devise_for :users, path: "auth", only: [:invitations],
controllers: { invitations: 'invitations' } controllers: { invitations: 'invitations' }
resources :users, only: [:index, :create, :destroy] do resources :users, only: [:index] do
collection do collection do
post 'auth_token' post 'auth_token'
end end
......
class RemoveUnusedModels < ActiveRecord::Migration[5.2]
def change
drop_table :joule_modules
drop_table :joule_pipes
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_08_20_005311) do ActiveRecord::Schema.define(version: 2019_09_18_011239) do
create_table "data_apps", force: :cascade do |t| create_table "data_apps", force: :cascade do |t|
t.string "name" t.string "name"
...@@ -141,33 +141,6 @@ ActiveRecord::Schema.define(version: 2019_08_20_005311) do ...@@ -141,33 +141,6 @@ ActiveRecord::Schema.define(version: 2019_08_20_005311) do
t.index ["interface_id"], name: "index_interface_permissions_on_interface_id" t.index ["interface_id"], name: "index_interface_permissions_on_interface_id"
end end
create_table "joule_modules", force: :cascade do |t|
t.string "name"
t.string "description"
t.boolean "web_interface"
t.string "exec_cmd"
t.string "status"
t.integer "pid"
t.string "joule_id"
t.integer "nilm_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["nilm_id"], name: "index_joule_modules_on_nilm_id"
end
create_table "joule_pipes", force: :cascade do |t|
t.integer "joule_pipe_id"
t.integer "joule_module_id"
t.integer "db_stream_id"
t.string "name"
t.string "direction"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["db_stream_id"], name: "index_joule_pipes_on_db_stream_id"
t.index ["joule_module_id"], name: "index_joule_pipes_on_joule_module_id"
t.index ["joule_pipe_id"], name: "index_joule_pipes_on_joule_pipe_id"
end
create_table "memberships", force: :cascade do |t| create_table "memberships", force: :cascade do |t|
t.integer "user_group_id" t.integer "user_group_id"
t.integer "user_id" t.integer "user_id"
......
# frozen_string_literal: true
require 'rails_helper'
describe Joule::UpdateModules do
before do
raw = File.read(File.dirname(__FILE__)+"/test_module_schema.json")
@schema = JSON.parse(raw).map{|item| item.deep_symbolize_keys}
end
it 'replaces existing modules with new ones' do
nilm = create(:nilm)
nilm.joule_modules << create(:joule_module, name: 'prev1')
nilm.joule_modules << create(:joule_module, name: 'prev2')
service = Joule::UpdateModules.new(nilm)
service.run(@schema)
expect(service.success?).to be true
# new modules are in the database
%w(Module1 Module2 Module3 Module4).each do |name|
expect(nilm.joule_modules.find_by_name(name)).to be_present
end
# old ones are gone
expect(JouleModule.count).to eq 4
# pipes are updated as well
m2 = nilm.joule_modules.find_by_name('Module2')
expect(m2.joule_pipes.count).to eq 1
m3 = nilm.joule_modules.find_by_name('Module3')
expect(m3.joule_pipes.count).to eq 3
# web interface status is correct
expect(m2.web_interface).to be false
expect(m3.web_interface).to be true
# old pipes are gone
expect(JoulePipe.count).to eq 9
end
it 'produces a warning if a stream is not in the database' do
nilm = create(:nilm)
service = Joule::UpdateModules.new(nilm)
service.run(@schema)
expect(service.warnings?).to be true
end
it 'links db_stream to the pipe if the stream is in the database' do
nilm = create(:nilm)
# create streams for pipe connections
nilm.db.db_streams << create(:db_stream, path: '/folder_1/stream_1_1')
nilm.db.db_streams << create(:db_stream, path: '/folder_1/stream_1_2')
nilm.db.db_streams << create(:db_stream, path: '/folder_2/stream_2_1')
service = Joule::UpdateModules.new(nilm)
#just run module3
service.run([@schema[0]])
# make sure pipes are connected
m3 = nilm.joule_modules.find_by_name('Module3')
pipe = m3.joule_pipes.where(direction: 'input', name: 'input1').first
expect(pipe.db_stream.path).to eq('/folder_1/stream_1_1')
pipe = m3.joule_pipes.where(direction: 'input', name: 'input2').first
expect(pipe.db_stream.path).to eq('/folder_1/stream_1_2')
pipe = m3.joule_pipes.where(direction: 'output', name: 'output').first
expect(pipe.db_stream.path).to eq('/folder_2/stream_2_1')
expect(service.warnings?).to be false
end
end
...@@ -113,6 +113,66 @@ RSpec.describe AnnotationsController, type: :request do ...@@ -113,6 +113,66 @@ RSpec.describe AnnotationsController, type: :request do
end end
end end
describe 'PUT #annotations' do
# updates specified annotation
context 'with owner' do
it 'updates the annotation' do
my_annotation= build(:annotation, db_stream: stream)
mock_adapter = instance_double(Joule::Adapter)
expect(mock_adapter).to(receive(:edit_annotation) do |id, title, content, stream|
expect(id.to_i).to eq my_annotation.id
expect(title).to eq "new title"
expect(content).to eq "new content"
end).and_return(my_annotation)
allow(NodeAdapterFactory).to receive(:from_nilm).and_return(mock_adapter)
put "/db_streams/#{stream.id}/annotations/#{my_annotation.id}.json",
headers: owner.create_new_auth_token,
params: {title: "new title", content: "new content"}
expect(response).to have_http_status(:ok)
expect(response.header['Content-Type']).to include('application/json')
body = JSON.parse(response.body)
# returns the updated annotation
expect(body['data'][0]['id']).to eq my_annotation.id
end
it 'returns error if backend fails' do
my_annotation= build(:annotation, db_stream: stream)
mock_adapter = instance_double(Joule::Adapter)
expect(mock_adapter).to(receive(:edit_annotation) do |id, title, content, stream|
expect(id.to_i).to eq my_annotation.id
expect(title).to eq "new title"
expect(content).to eq "new content"
end).and_raise(RuntimeError.new("test error"))
allow(NodeAdapterFactory).to receive(:from_nilm).and_return(mock_adapter)
put "/db_streams/#{stream.id}/annotations/#{my_annotation.id}.json",
headers: owner.create_new_auth_token,
params: {title: "new title", content: "new content"}
expect(response).to have_http_status(:unprocessable_entity)
expect(response.header['Content-Type']).to include('application/json')
expect(response).to have_error_message
end
end
context 'with viewer' do
it 'returns unauthorized' do
put "/db_streams/#{stream.id}/annotations/10.json", headers: viewer.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'with anyone else' do
it 'returns unauthorized' do
put "/db_streams/#{stream.id}/annotations/10.json", headers: user.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
end
context 'without sign-in' do
it 'returns unauthorized' do
put "/db_streams/#{stream.id}/annotations/10.json"
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'DESTROY #annotations' do describe 'DESTROY #annotations' do
# deletes specified annotation # deletes specified annotation
context 'with owner' do context 'with owner' do
......
require 'rails_helper' require 'rails_helper'
RSpec.describe DataAppController, type: :controller do RSpec.describe DataAppController, type: :request do
let(:admin) { create(:user, first_name: 'Admin') }
let(:owner) {create(:user, first_name: 'Owner')}
let(:other) { create(:user, first_name: 'Other') }
let(:test_nilm) { create(:nilm, name: "Test NILM", admins: [admin], owners: [owner]) }
let(:test_app) { create(:data_app, name: "Test App", nilm: test_nilm)}
let(:other_app) { create(:data_app, name: "Other App", nilm: test_nilm)}
describe 'GET show' do
context 'with any permissions' do
it 'returns the data_app as json' do
@auth_headers = owner.create_new_auth_token
get "/app/#{test_app.id}.json",
headers: @auth_headers
expect(response.status).to eq(200)
expect(response.header['Content-Type']).to include('application/json')
body = JSON.parse(response.body)
expect(body["name"]).to eq test_app.name
# no URL if not proxied
expect(body["url"]).to eq '#'
expect(InterfaceAuthToken.count).to eq 0
end
it 'returns url if proxied' do
@auth_headers = owner.create_new_auth_token
get "/app/#{test_app.id}.json",
headers: @auth_headers.merge({HTTP_X_APP_BASE_URI:'/lumen'})
expect(response.status).to eq(200)
expect(response.header['Content-Type']).to include('application/json')
body = JSON.parse(response.body)
expect(body["name"]).to eq test_app.name
# URL has an auth token
token = InterfaceAuthToken.where(data_app:test_app).first
expect(body["url"]).to end_with token.value
end
end
context 'without permissions' do
it 'returns unauthorized' do
@auth_headers = other.create_new_auth_token
get "/app/#{test_app.id}.json",
headers: @auth_headers
expect(response.status).to eq(401)
expect(InterfaceAuthToken.count).to eq 0
end
end
context 'without sign-in' do
it 'returns unauthorized' do
# no headers: nobody is signed in, deny all
get "/app/#{test_app.id}.json"
expect(response.status).to eq(401)
expect(InterfaceAuthToken.count).to eq 0
end
end
end
end end
...@@ -169,6 +169,16 @@ RSpec.describe DataViewsController, type: :request do ...@@ -169,6 +169,16 @@ RSpec.describe DataViewsController, type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(response).to have_notice_message expect(response).to have_notice_message
end end
it 'returns error with invalid parameters' do
view = create(:data_view, name: 'old name', owner: viewer)
@auth_headers = viewer.create_new_auth_token
put "/data_views/#{view.id}.json",
params: {
name: '', home: false
}, headers: @auth_headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(DataView.find(view.id).name).to eq "old name"
end
end end
context 'with anybody else' do context 'with anybody else' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
...@@ -86,6 +86,28 @@ RSpec.describe DbElementsController, type: :request do ...@@ -86,6 +86,28 @@ RSpec.describe DbElementsController, type: :request do
headers: @auth_headers headers: @auth_headers
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
it 'auto calculates time bounds if not specified' do
@service_data = [{ id: @elem1.id, data: 'mock1' },
{ id: @elem2.id, data: 'mock2' }]
@mock_service = instance_double(LoadElementData,
run: StubService.new,
start_time: 985,
end_time: 10001,
success?: true, notices: [], warnings: [], errors: [],
data: @service_data)
allow(LoadElementData).to receive(:new).and_return(@mock_service)
@auth_headers = user1.create_new_auth_token
get '/db_elements/data.json',
params: { elements: [@elem1.id, @elem2.id].to_json },
headers: @auth_headers
expect(response).to have_http_status(:ok)
# check to make sure JSON renders the elements
body = JSON.parse(response.body)
expect(body['data'].count).to eq(2)
expect(body['data'][0]['start_time']).to eq(985)
expect(body['data'][0]['end_time']).to eq(10001)
end
end end
context 'without sign-in' do context 'without sign-in' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
...@@ -40,6 +40,13 @@ RSpec.describe DbStreamsController, type: :request do ...@@ -40,6 +40,13 @@ RSpec.describe DbStreamsController, type: :request do
headers: @auth_headers headers: @auth_headers
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
it 'requires streams parameter' do
@auth_headers = viewer.create_new_auth_token
get "/db_streams.json",
params: {},
headers: @auth_headers
expect(response).to have_http_status(:unprocessable_entity)
end
end end
context 'without permissions' do context 'without permissions' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
require 'rails_helper' require 'rails_helper'
RSpec.describe HomeController, type: :controller do RSpec.describe HomeController, type: :request do
describe 'GET index' do
it 'returns html' do
# no authentication required
get "/"
expect(response).to have_http_status(:ok)
expect(response.body).to include("Lumen")
end
it 'returns json' do
get "/index.json"
expect(response).to have_http_status(:ok)
expect(response.header['Content-Type']).to include('application/json')
body = JSON.parse(response.body)
expect(body['node_name']).to eq "Test Environment"
expect(body['send_emails']).to eq false
expect(body.keys).to include "version"
end
end
end end
require 'rails_helper'
RSpec.describe InterfacePermissionsController, type: :controller do
end
...@@ -149,6 +149,13 @@ RSpec.describe NilmsController, type: :request do ...@@ -149,6 +149,13 @@ RSpec.describe NilmsController, type: :request do
expect(response.header['Content-Type']).to include('application/json') expect(response.header['Content-Type']).to include('application/json')
end end
end end
it 'returns error if installation type cannot be determined' do
expect(NodeAdapterFactory).to receive(:from_nilm).and_return nil
get "/nilms/#{lab_nilm.id}.json",
headers: john.create_new_auth_token
expect(response).to have_http_status(:unprocessable_entity)
end
end end
context 'with anyone else' do context 'with anyone else' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
...@@ -212,6 +212,21 @@ RSpec.describe PermissionsController, type: :request do ...@@ -212,6 +212,21 @@ RSpec.describe PermissionsController, type: :request do
data = JSON.parse(response.body)['data'] data = JSON.parse(response.body)['data']
expect(data['target_name']).to eq 'sam davy' expect(data['target_name']).to eq 'sam davy'
end end
it 'returns error if service call fails' do
failed_service = InviteUser.new
failed_service.add_error("test message")
expect(InviteUser).to receive(:new).and_return failed_service
put '/permissions/invite_user.json',
params: { nilm_id: john_nilm.id,
role: 'owner',
email: 'test@test.com',
redirect_url: 'localhost' },
headers: @auth_headers
expect(response).to have_http_status(:unprocessable_entity)
expect(response).to have_error_message
end
end end
context 'with anyone else' do context 'with anyone else' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ProxyController, type: :request do
let(:admin) { create(:user, first_name: 'Admin') }
let(:owner) {create(:user, first_name: 'Owner')}
let(:other) { create(:user, first_name: 'Other') }
let(:test_nilm) { create(:nilm, name: "Test NILM", admins: [admin], owners: [owner]) }
let(:test_app) { create(:data_app, name: "Test App", nilm: test_nilm)}
let(:other_app) { create(:data_app, name: "Other App", nilm: test_nilm)}
describe 'GET authenticate' do
context 'with authorized user' do
it 'creates a cookie from a token' do
# The first request requires a token
token = InterfaceAuthToken.create(data_app: test_app,
user: owner,
expiration: 5.minutes.from_now)
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen?auth_token=#{token.value}"}
# request is authorized
expect(response).to have_http_status(:ok)
# connection headers are provided
expect(response.headers['X-PROXY-URL']).to eq test_app.url
expect(response.headers['X-JOULE-KEY']).to eq test_nilm.key
# cookie is set
expect(session[:user_id]).to eq owner.id
expect(session[:app_ids]).to include test_app.id
# the token is destroyed
expect(InterfaceAuthToken.find_by_id(token.id)).to be_nil
# The next request is authorized by cookie
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen"}
# request is authorized
expect(response).to have_http_status(:ok)
# connection headers are provided
expect(response.headers['X-PROXY-URL']).to eq test_app.url
expect(response.headers['X-JOULE-KEY']).to eq test_nilm.key
# If permission is revoked the cookie is invalid
Permission.where(user: owner).destroy_all
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen"}
# request is unauthorized
expect(response).to have_http_status(:unauthorized)
end
it 'denies expired tokens' do
# The first request requires a token
token = InterfaceAuthToken.create(data_app: test_app,
user: owner,
expiration: 5.minutes.ago)
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen?auth_token=#{token.value}"}
expect(response).to have_http_status(:unauthorized)
end
it 'requires a valid token' do
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen?auth_token=invalid"}
expect(response).to have_http_status(:unauthorized)
end
it 'requires a proxy' do
get "/app/#{test_app.id}/auth"
expect(response).to have_http_status(:unauthorized)
end
it 'requires a token or a cookie' do
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen"}
expect(response).to have_http_status(:unauthorized)
end
it 'does not allow cross app requests' do
# validate to test_app
token = InterfaceAuthToken.create(data_app: test_app,
user: owner,
expiration: 5.minutes.from_now)
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen?auth_token=#{token.value}"}
# request is authorized
expect(response).to have_http_status(:ok)
# cannot send request to other_app even though user is authorized
get "/app/#{other_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen"}
expect(response).to have_http_status(:unauthorized)
end
end
context 'with unauthorized user' do
it 'blocks access even with a valid token' do
token = InterfaceAuthToken.create(data_app: test_app,
user: other,
expiration: 5.minutes.from_now)
get "/app/#{test_app.id}/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen?auth_token=#{token.value}"}
# request is unauthorized
expect(response).to have_http_status(:unauthorized)
# the token is destroyed
expect(InterfaceAuthToken.find_by_id(token.id)).to be_nil
end
end
context 'with invalid app' do
it 'returns not found' do
get "/app/200003/auth",
headers: {HTTP_X_ORIGINAL_URI: "/lumen"}
expect(response).to have_http_status(:not_found)
end
end
end
end
...@@ -202,6 +202,16 @@ RSpec.describe UserGroupsController, type: :request do ...@@ -202,6 +202,16 @@ RSpec.describe UserGroupsController, type: :request do
members = JSON.parse(response.body)['data']['members'] members = JSON.parse(response.body)['data']['members']
expect(members.select { |u| u['id'] == user.id }).to_not be_empty expect(members.select { |u| u['id'] == user.id }).to_not be_empty
end end
it 'returns error if service call fails' do
failed_service = InviteUser.new
failed_service.add_error("test message")
expect(InviteUser).to receive(:new).and_return failed_service
put "/user_groups/#{group.id}/invite_member.json",
params: { email: 'test@test.com', redirect_url: 'localhost' },
headers: @auth_headers
expect(response).to have_http_status(:unprocessable_entity)
expect(response).to have_error_message
end
end end
context 'with anyone else' do context 'with anyone else' do
it 'returns unauthorized' do it 'returns unauthorized' do
......
...@@ -4,5 +4,6 @@ FactoryBot.define do ...@@ -4,5 +4,6 @@ FactoryBot.define do
content { Faker::Lorem.sentence } content { Faker::Lorem.sentence }
start_time { Faker::Number.between(1000,2000) } start_time { Faker::Number.between(1000,2000) }
end_time { Faker::Number.between(3000,4000)} end_time { Faker::Number.between(3000,4000)}
id { Faker::Number.unique.number.to_i}
end end
end end
\ No newline at end of file
FactoryBot.define do FactoryBot.define do
factory :data_app do factory :data_app do
joule_id { Faker::Number.number(6).to_i}
end end
end end
...@@ -9,6 +9,7 @@ FactoryBot.define do ...@@ -9,6 +9,7 @@ FactoryBot.define do
description { Faker::Lorem.sentence } description { Faker::Lorem.sentence }
url {Faker::Internet.unique.url} url {Faker::Internet.unique.url}
node_type { 'nilmdb' } node_type { 'nilmdb' }
key {Faker::Lorem.characters(20)}
transient do transient do
admins { [] } admins { [] }
owners { [] } owners { [] }
......
...@@ -30,6 +30,12 @@ RSpec.describe 'DbStream' do ...@@ -30,6 +30,12 @@ RSpec.describe 'DbStream' do
stream2.validate stream2.validate
expect(stream2.errors[:name].any?).to be true expect(stream2.errors[:name].any?).to be true
end end
it 'requires a valid data type' do
my_stream = create(:db_stream, name: 'invalid')
my_stream.data_type = "float32_5"
expect(my_stream).to_not be_valid
expect(my_stream.errors.full_messages[0]).to include "5 elements"
end
end end
describe 'update' do describe 'update' do
it 'saves attributes to child elements' do it 'saves attributes to child elements' do
...@@ -41,6 +47,19 @@ RSpec.describe 'DbStream' do ...@@ -41,6 +47,19 @@ RSpec.describe 'DbStream' do
end end
end end
describe 'meta_attributes' do
it 'parses data format' do
my_stream = create(:db_stream, name: 'invalid')
my_stream.data_type="uint8_4"
expect(my_stream).to be_valid
expect(my_stream.data_format).to eq "uint8"
end
it 'parses column count' do
my_stream = create(:db_stream, name: 'invalid', elements_count: 8)
expect(my_stream.column_count).to eq 8
end
end
describe 'child elements' do describe 'child elements' do
it 'are destroyed with parent stream' do it 'are destroyed with parent stream' do
element = DbElement.create element = DbElement.create
......
require 'rails_helper'
RSpec.describe JouleModule, type: :model do
describe 'object' do
let(:joule_module) {JouleModule.new}
specify { expect(joule_module).to respond_to(:name) }
specify { expect(joule_module).to respond_to(:description) }
specify { expect(joule_module).to respond_to(:exec_cmd) }
specify { expect(joule_module).to respond_to(:web_interface) }
specify { expect(joule_module).to respond_to(:status) }
specify { expect(joule_module).to respond_to(:joule_id) }
end
it 'removes pipes when destroyed' do
@joule_module = JouleModule.create
@joule_module.joule_pipes << JoulePipe.create(
db_stream: create(:db_stream),
direction: 'output')
expect(JouleModule.find_by_id(@joule_module.id).joule_pipes.count).to equal 1
@joule_module.destroy
expect(JouleModule.count).to equal 0
# deletes associated pipes
expect(JoulePipe.count).to equal 0
# does not delete the streams
expect(DbStream.count).to equal 1
end
end
...@@ -8,7 +8,7 @@ RSpec.describe 'Nilm' do ...@@ -8,7 +8,7 @@ RSpec.describe 'Nilm' do
specify { expect(nilm).to respond_to(:description) } specify { expect(nilm).to respond_to(:description) }
specify { expect(nilm).to respond_to(:url) } specify { expect(nilm).to respond_to(:url) }
specify { expect(nilm).to respond_to(:db) } specify { expect(nilm).to respond_to(:db) }
specify { expect(nilm).to respond_to(:joule_modules) } specify { expect(nilm).to respond_to(:data_apps) }
end end
......
...@@ -52,4 +52,26 @@ RSpec.describe Permission, type: :model do ...@@ -52,4 +52,26 @@ RSpec.describe Permission, type: :model do
p.user = nil; p.user_group = g p.user = nil; p.user_group = g
expect(p.target_name).to match('a group') expect(p.target_name).to match('a group')
end end
it 'uses user e-mail if name is blank' do
u = build(:user, first_name: nil, last_name: nil, email: "test@email.com")
# simulate user invited by e-mail who hasn't created their account yet
u.save!(validate: false) # skip validation
p = build(:permission, user: u)
expect(p.target_name).to match('test@email.com')
end
it 'provides target_type' do
p = build(:permission)
p.user_id = nil
p.user_group_id = nil
expect(p.target_type).to eq 'unknown'
p.user = create(:user)
expect(p.target_type).to eq 'user'
p.user=nil
p.user_group = create(:user_group)
expect(p.target_type).to eq 'group'
end
end end
...@@ -8,13 +8,21 @@ RSpec.describe User, type: :model do ...@@ -8,13 +8,21 @@ RSpec.describe User, type: :model do
specify { expect(user).to respond_to(:last_name) } specify { expect(user).to respond_to(:last_name) }
specify { expect(user).to respond_to(:email) } specify { expect(user).to respond_to(:email) }
specify { expect(user).to respond_to(:home_data_view)} specify { expect(user).to respond_to(:home_data_view)}
end end
it 'responds to name' do it 'responds to name' do
u = build(:user, first_name: 'Bill', last_name: 'Test') u = build(:user, first_name: 'Bill', last_name: 'Test')
expect(u.name).to match('Bill Test') expect(u.name).to match('Bill Test')
u.first_name=nil
u.last_name=nil
u.email="test@email.com"
expect(u.name).to match('test@email.com')
u.email=nil
expect(u.name).to match("UNKNOWN NAME")
end end
describe describe
describe "permission management" do describe "permission management" do
context "Given the Donnal's House and the Lab" do context "Given the Donnal's House and the Lab" do
...@@ -24,6 +32,7 @@ RSpec.describe User, type: :model do ...@@ -24,6 +32,7 @@ RSpec.describe User, type: :model do
@nicky = create(:user, first_name: "Nicky") @nicky = create(:user, first_name: "Nicky")
@pete = create(:user, first_name: "Pete") @pete = create(:user, first_name: "Pete")
@leeb = create(:user, first_name: "Leeb") @leeb = create(:user, first_name: "Leeb")
@other_user = create(:user, first_name: "Other")
#groups #groups
@donnals = create(:user_group, name: "Donnals", members: [@john, @nicky]) @donnals = create(:user_group, name: "Donnals", members: [@john, @nicky])
@labmates = create(:user_group, name: "Labmates", members: [@john, @pete, @leeb]) @labmates = create(:user_group, name: "Labmates", members: [@john, @pete, @leeb])
...@@ -38,6 +47,7 @@ RSpec.describe User, type: :model do ...@@ -38,6 +47,7 @@ RSpec.describe User, type: :model do
expect(@john.get_nilm_permission(@donnal_house)).to eq('admin') expect(@john.get_nilm_permission(@donnal_house)).to eq('admin')
expect(@pete.get_nilm_permission(@lab)).to eq('owner') expect(@pete.get_nilm_permission(@lab)).to eq('owner')
expect(@nicky.get_nilm_permission(@lab)).to eq('viewer') expect(@nicky.get_nilm_permission(@lab)).to eq('viewer')
expect(@other_user.get_nilm_permission(@donnal_house)).to eq ('none')
end end
it "lets John admin his house and the lab" do it "lets John admin his house and the lab" do
expect(@john.admins_nilm?(@donnal_house)).to eq(true) expect(@john.admins_nilm?(@donnal_house)).to eq(true)
...@@ -71,6 +81,7 @@ RSpec.describe User, type: :model do ...@@ -71,6 +81,7 @@ RSpec.describe User, type: :model do
expect(@leeb.owns_nilm?(@lab)).to eq(true) expect(@leeb.owns_nilm?(@lab)).to eq(true)
expect(@leeb.views_nilm?(@lab)).to eq(true) expect(@leeb.views_nilm?(@lab)).to eq(true)
end end
end end
end end
end end
...@@ -31,6 +31,15 @@ SimpleCov.start 'rails' do ...@@ -31,6 +31,15 @@ SimpleCov.start 'rails' do
add_group "Controllers", "app/controllers" add_group "Controllers", "app/controllers"
add_group "Services", "app/services" add_group "Services", "app/services"
add_group "Adapters","app/adapters" add_group "Adapters","app/adapters"
# ignore the top level controller
add_filter "app/controllers/application_controller.rb"
# ignore devise-provided files
add_filter "app/controllers/invitations_controller.rb"
add_filter "app/controllers/concerns/invitable_methods.rb"
# ignore interface permission model (not currently used)
add_filter "app/models/interface_permission.rb"
end end
require 'webmock/rspec' require 'webmock/rspec'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment