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
before_action :authenticate_user!
# GET /app/:id.json
def show
@app = DataApp.find(params[:id])
@nilm = @app.nilm
head :unauthorized and return unless current_user.views_nilm?(@nilm)
# destroy any existing tokens
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(token)
@auth_url = _app_auth_url
end
private
def _app_auth_url(token)
def _app_auth_url
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}/#{token.data_app.id}/?auth_token=#{token.value}"
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
end
end
# PATCH/PUT /nilms/1/refresh.json
def refresh
end
# DELETE /nilms/1.json
def destroy
@service = StubService.new
@nilm.destroy
@service.set_notice('removed nilm')
render 'helpers/empty_response', status: :ok
end
......
......@@ -15,23 +15,28 @@ class ProxyController < ActionController::Base
response.set_header('X-JOULE-KEY', @nilm.key)
head :ok and return
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")
orig_query = URI.parse(request.headers["HTTP_X_ORIGINAL_URI"]).query
head :unauthorized and return if orig_query.nil?
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 = InterfaceAuthToken.find_by_value(token_value)
head :unauthorized and return if token.nil?
head :unauthorized and return if token.expiration < Time.now
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
# 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
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)
head :ok and return
end
......@@ -41,6 +46,7 @@ class ProxyController < ActionController::Base
def authenticate_interface_user
@current_user = User.find_by_id(session[:user_id])
return false if @current_user.nil?
@app = DataApp.find_by_id(params[:id])
# make sure the app is authorized by the cookie
return false unless session.include?(:app_ids)
......
......@@ -66,9 +66,11 @@ class UserGroupsController < ApplicationController
current_user,
params[:email],
params[:redirect_url])
unless invitation_service.success?
@service = invitation_service
render 'helpers/empty_response', status: :unprocessable_entity
return
end
user = invitation_service.user
@service = AddGroupMember.new
......
......@@ -23,20 +23,4 @@ class UsersController < ApplicationController
render json: {key: auth_key.key}
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
......@@ -22,8 +22,4 @@ class Db < ApplicationRecord
nilm.url
end
def self.json_keys
[:id, :url, :size_total, :size_db, :available,
:size_other, :version, :max_points_per_plot]
end
end
......@@ -4,7 +4,4 @@
class DbDecimation < ApplicationRecord
belongs_to :db_stream
def as_json(_options = {})
super(except: [:created_at, :updated_at])
end
end
......@@ -8,8 +8,7 @@ class DbDataTypeValidator < ActiveModel::Validator
return if record.db_elements.count.zero?
# TODO: check for valid format strings (float32, uint8, etc)
unless record.db_elements.count == record.column_count
record.errors[:base] << "must have #{record.column_count} \
elements for format #{record.data_type}"
record.errors[:base] << "must have #{record.column_count} elements for format #{record.data_type}"
end
end
end
......@@ -32,15 +31,6 @@ class DbStream < ApplicationRecord
[:name, :name_abbrev, :description, :hidden, :data_type, :locked]
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
/^(\w*)_\d*$/.match(data_type)[1]
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
has_many :user_groups, through: :permissions
has_many :data_views_nilms
has_many :data_views, through: :data_views_nilms
has_many :joule_modules, dependent: :destroy
has_many :data_apps, dependent: :destroy
#---Validations-----
validates :name, presence: true, uniqueness: true
......
......@@ -16,17 +16,11 @@ class Permission < ApplicationRecord
end
def target_name
if self.user_id?
if self.user.name.empty?
return self.user.email
else
return self.user.name
end
elsif self.user_group_id?
return self.user_group.name
else
return "[no target set]"
end
return self.user.name if self.user_id?
return self.user_group.name if self.user_group_id?
"[no target set]"
end
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
# Raises error for missing translations
# 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
Rails.application.routes.draw do
resources :nilms, only: [:index, :show, :create, :update, :destroy] do
member do
put 'refresh'
end
end
resources :nilms, only: [:index, :show, :create, :update, :destroy]
resources :data_views do
collection do
......@@ -32,7 +28,7 @@ Rails.application.routes.draw do
devise_for :users, path: "auth", only: [:invitations],
controllers: { invitations: 'invitations' }
resources :users, only: [:index, :create, :destroy] do
resources :users, only: [:index] do
collection do
post 'auth_token'
end
......
class RemoveUnusedModels < ActiveRecord::Migration[5.2]
def change
drop_table :joule_modules
drop_table :joule_pipes
end
end
......@@ -10,7 +10,7 @@
#
# 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|
t.string "name"
......@@ -141,33 +141,6 @@ ActiveRecord::Schema.define(version: 2019_08_20_005311) do
t.index ["interface_id"], name: "index_interface_permissions_on_interface_id"
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|
t.integer "user_group_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
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
# deletes specified annotation
context 'with owner' do
......
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
......@@ -169,6 +169,16 @@ RSpec.describe DataViewsController, type: :request do
expect(response).to have_http_status(:ok)
expect(response).to have_notice_message
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
context 'with anybody else' do
it 'returns unauthorized' do
......
......@@ -86,6 +86,28 @@ RSpec.describe DbElementsController, type: :request do
headers: @auth_headers
expect(response).to have_http_status(:unauthorized)
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
context 'without sign-in' do
it 'returns unauthorized' do
......
......@@ -40,6 +40,13 @@ RSpec.describe DbStreamsController, type: :request do
headers: @auth_headers
expect(response).to have_http_status(:unauthorized)
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
context 'without permissions' do
it 'returns unauthorized' do
......
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
require 'rails_helper'
RSpec.describe InterfacePermissionsController, type: :controller do
end
......@@ -149,6 +149,13 @@ RSpec.describe NilmsController, type: :request do
expect(response.header['Content-Type']).to include('application/json')
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
context 'with anyone else' do
it 'returns unauthorized' do
......
......@@ -212,6 +212,21 @@ RSpec.describe PermissionsController, type: :request do
data = JSON.parse(response.body)['data']
expect(data['target_name']).to eq 'sam davy'
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
context 'with anyone else' 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
members = JSON.parse(response.body)['data']['members']
expect(members.select { |u| u['id'] == user.id }).to_not be_empty
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
context 'with anyone else' do
it 'returns unauthorized' do
......
......@@ -4,5 +4,6 @@ FactoryBot.define do
content { Faker::Lorem.sentence }
start_time { Faker::Number.between(1000,2000) }
end_time { Faker::Number.between(3000,4000)}
id { Faker::Number.unique.number.to_i}
end
end
\ No newline at end of file
FactoryBot.define do
factory :data_app do
joule_id { Faker::Number.number(6).to_i}
end
end
......@@ -9,6 +9,7 @@ FactoryBot.define do
description { Faker::Lorem.sentence }
url {Faker::Internet.unique.url}
node_type { 'nilmdb' }
key {Faker::Lorem.characters(20)}
transient do
admins { [] }
owners { [] }
......
......@@ -30,6 +30,12 @@ RSpec.describe 'DbStream' do
stream2.validate
expect(stream2.errors[:name].any?).to be true
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
describe 'update' do
it 'saves attributes to child elements' do
......@@ -41,6 +47,19 @@ RSpec.describe 'DbStream' do
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
it 'are destroyed with parent stream' do
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
specify { expect(nilm).to respond_to(:description) }
specify { expect(nilm).to respond_to(:url) }
specify { expect(nilm).to respond_to(:db) }
specify { expect(nilm).to respond_to(:joule_modules) }
specify { expect(nilm).to respond_to(:data_apps) }
end
......
......@@ -52,4 +52,26 @@ RSpec.describe Permission, type: :model do
p.user = nil; p.user_group = g
expect(p.target_name).to match('a group')
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
......@@ -8,13 +8,21 @@ RSpec.describe User, type: :model do
specify { expect(user).to respond_to(:last_name) }
specify { expect(user).to respond_to(:email) }
specify { expect(user).to respond_to(:home_data_view)}
end
it 'responds to name' do
u = build(:user, first_name: 'Bill', last_name: '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
describe
describe "permission management" do
context "Given the Donnal's House and the Lab" do
......@@ -24,6 +32,7 @@ RSpec.describe User, type: :model do
@nicky = create(:user, first_name: "Nicky")
@pete = create(:user, first_name: "Pete")
@leeb = create(:user, first_name: "Leeb")
@other_user = create(:user, first_name: "Other")
#groups
@donnals = create(:user_group, name: "Donnals", members: [@john, @nicky])
@labmates = create(:user_group, name: "Labmates", members: [@john, @pete, @leeb])
......@@ -38,6 +47,7 @@ RSpec.describe User, type: :model do
expect(@john.get_nilm_permission(@donnal_house)).to eq('admin')
expect(@pete.get_nilm_permission(@lab)).to eq('owner')
expect(@nicky.get_nilm_permission(@lab)).to eq('viewer')
expect(@other_user.get_nilm_permission(@donnal_house)).to eq ('none')
end
it "lets John admin his house and the lab" do
expect(@john.admins_nilm?(@donnal_house)).to eq(true)
......@@ -71,6 +81,7 @@ RSpec.describe User, type: :model do
expect(@leeb.owns_nilm?(@lab)).to eq(true)
expect(@leeb.views_nilm?(@lab)).to eq(true)
end
end
end
end
......@@ -31,6 +31,15 @@ SimpleCov.start 'rails' do
add_group "Controllers", "app/controllers"
add_group "Services", "app/services"
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
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