Merge pull request #2214 from biosistemika/features/task-api-endpoints

Develop << Features/task api endpoints
This commit is contained in:
Urban Rotnik 2019-11-18 11:16:44 +01:00 committed by GitHub
commit 759b3acfe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 996 additions and 58 deletions

View file

@ -478,6 +478,11 @@ Rails/ScopeArgs:
Include:
- app/models/**/*.rb
Rails/SkipsModelValidations:
Exclude:
- spec/**/*_spec.rb
- spec/factories/*.rb
Rails/TimeZone:
EnforcedStyle: flexible

View file

@ -0,0 +1,50 @@
# frozen_string_literal: true
module Api
module V1
class AssetsController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task, :load_protocol, :load_step
before_action :load_asset, only: :show
def index
attachments = @step.assets
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: attachments, each_serializer: AssetSerializer
end
def show
render jsonapi: @asset, serializer: AssetSerializer
end
def create
raise PermissionError.new(Asset, :create) unless can_manage_protocol_in_module?(@protocol)
asset = @step.assets.new(asset_params)
asset.save!(context: :on_api_upload)
asset.post_process_file
render jsonapi: asset,
serializer: AssetSerializer,
status: :created
end
private
def asset_params
raise TypeError unless params.require(:data).require(:type) == 'attachments'
attr_list = %i(file)
params.require(:data).require(:attributes).require(attr_list)
params.require(:data).require(:attributes).permit(attr_list)
end
def load_asset
@asset = @step.assets.find(params.require(:id))
raise PermissionError.new(Asset, :read) unless can_read_protocol_in_module?(@asset.step.protocol)
end
end
end
end

View file

@ -110,21 +110,27 @@ module Api
def load_project(key = :project_id)
@project = @team.projects.find(params.require(key))
unless can_read_project?(@project)
raise PermissionError.new(Project, :read)
end
raise PermissionError.new(Project, :read) unless can_read_project?(@project)
end
def load_experiment(key = :experiment_id)
@experiment = @project.experiments.find(params.require(key))
unless can_read_experiment?(@experiment)
raise PermissionError.new(Experiment, :read)
end
raise PermissionError.new(Experiment, :read) unless can_read_experiment?(@experiment)
end
def load_task(key = :task_id)
@task = @experiment.my_modules.find(params.require(key))
end
def load_protocol(key = :protocol_id)
@protocol = @task.protocols.find(params.require(key))
raise PermissionError.new(Protocol, :read) unless can_read_protocol_in_module?(@protocol)
end
def load_step(key = :step_id)
@step = @protocol.steps.find(params.require(key))
raise PermissionError.new(Protocol, :read) unless can_read_protocol_in_module?(@step.protocol)
end
end
end
end

View file

@ -1,15 +1,11 @@
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
module Api
module V1
class ResultsController < BaseController
before_action :load_team
before_action :load_project
before_action :load_experiment
before_action :load_task
before_action :load_result, only: %i(show)
before_action :check_manage_permissions, only: %i(create)
before_action :load_team, :load_project, :load_experiment, :load_task
before_action :load_result, only: %i(show update)
before_action :check_manage_permissions, only: %i(create update)
def index
results = @task.results
@ -21,12 +17,31 @@ module Api
def create
create_text_result if result_text_params.present?
create_file_result if !@result && result_file_params.present?
render jsonapi: @result,
serializer: ResultSerializer,
include: %i(text table file),
status: :created
end
def update
@result.attributes = result_params
update_file_result if result_file_params.present? && @result.is_asset
update_text_result if result_text_params.present? && @result.is_text
if (@result.changed? && @result.save!) || @asset_result_updated
render jsonapi: @result,
serializer: ResultSerializer,
include: %i(text table file),
status: :ok
else
render body: nil, status: :no_content
end
end
def show
render jsonapi: @result, serializer: ResultSerializer,
include: %i(text table file)
@ -39,12 +54,11 @@ module Api
end
def check_manage_permissions
unless can_manage_module?(@task)
raise PermissionError.new(MyModule, :manage)
end
raise PermissionError.new(MyModule, :manage) unless can_manage_module?(@task)
end
def create_text_result
# rubocop:disable Metrics/BlockLength:
Result.transaction do
@result = Result.create!(
user: current_user,
@ -80,6 +94,29 @@ module Api
result_text.save!
end
end
# rubocop:enable Metrics/BlockLength:
end
def update_text_result
raise NotImplementedError, 'update_text_result should be implemented!'
end
def create_file_result
Result.transaction do
@result = @task.results.create!(result_params.merge(user_id: current_user.id))
asset = Asset.create!(result_file_params)
ResultAsset.create!(asset: asset, result: @result)
end
end
def update_file_result
old_checksum, new_checksum = nil
Result.transaction do
old_checksum = @result.asset.file.blob.checksum
@result.asset.file.attach(result_file_params[:file])
new_checksum = @result.asset.file.blob.checksum
end
@asset_result_updated = old_checksum != new_checksum
end
def result_params
@ -92,10 +129,19 @@ module Api
# Partially implement sideposting draft
# https://github.com/json-api/json-api/pull/1197
def result_text_params
prms =
params[:included]&.select { |el| el[:type] == 'result_texts' }&.first
prms = params[:included]&.select { |el| el[:type] == 'result_texts' }&.first
return nil unless prms
prms.require(:attributes).require(:text)
prms[:attributes]
prms.dig(:attributes).permit(:text)
end
def result_file_params
prms = params[:included]&.select { |el| el[:type] == 'result_files' }&.first
return nil unless prms
prms.require(:attributes).require(:file)
prms.dig(:attributes).permit(:file)
end
def tiny_mce_asset_params
@ -126,4 +172,3 @@ module Api
end
end
end
# rubocop:enable Metrics/LineLength

View file

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Api
module V1
class StepsController < BaseController
before_action :load_team, :load_project, :load_experiment, :load_task, :load_protocol
before_action only: :show do
load_step(:id)
end
def index
steps = @protocol.steps
.page(params.dig(:page, :number))
.per(params.dig(:page, :size))
render jsonapi: steps, each_serializer: StepSerializer
end
def show
render jsonapi: @step, serializer: StepSerializer
end
def create
raise PermissionError.new(Protocol, :create) unless can_manage_protocol_in_module?(@protocol)
step = @protocol.steps.create!(step_params.merge!(completed: false,
user_id: current_user.id,
position: @protocol.number_of_steps))
render jsonapi: step,
serializer: StepSerializer,
status: :created
end
private
def step_params
raise TypeError unless params.require(:data).require(:type) == 'steps'
attr_list = %i(name)
params.require(:data).require(:attributes).require(attr_list)
params.require(:data).require(:attributes).permit(attr_list + [:description])
end
end
end
end

View file

@ -23,6 +23,16 @@ module Api
render jsonapi: @task, serializer: TaskSerializer
end
def create
raise PermissionError.new(MyModule, :create) unless can_manage_experiment?(@experiment)
my_module = @experiment.my_modules.create!(my_module_params)
render jsonapi: my_module,
serializer: TaskSerializer,
status: :created
end
def activities
activities = ActivitiesService.my_module_activities(@task)
.page(params.dig(:page, :number))
@ -34,6 +44,14 @@ module Api
private
def my_module_params
raise TypeError unless params.require(:data).require(:type) == 'tasks'
attr_list = %i(name x y)
params.require(:data).require(:attributes).require(attr_list)
params.require(:data).require(:attributes).permit(attr_list + [:description])
end
# Made the method below because its more elegant than changing parameters
# in routes file, and here. It exists because when we call input or output
# for a task, the "id" that used to be task id is now an id for the output

View file

@ -19,8 +19,8 @@ class Asset < ApplicationRecord
# This could cause some problems if you create empty asset and want to
# assign it to result
validate :step_or_result_or_repository_asset_value
validate :wopi_filename_valid,
on: :wopi_file_creation
validate :wopi_filename_valid, on: :wopi_file_creation
validate :check_file_size, on: :on_api_upload
belongs_to :created_by,
foreign_key: 'created_by_id',
@ -448,4 +448,12 @@ class Asset < ApplicationRecord
)
end
end
def check_file_size
if file.attached?
if file.blob.byte_size > Rails.application.config.x.file_max_size_mb.megabytes
errors.add(:file, I18n.t('activerecord.errors.models.asset.attributes.file.too_big'))
end
end
end
end

View file

@ -15,8 +15,8 @@ class MyModule < ApplicationRecord
validates :description, length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
validates :x, :y, :workflow_order, presence: true
validates :experiment, presence: true
validates :my_module_group, presence: true,
if: proc { |mm| !mm.my_module_group_id.nil? }
validates :my_module_group, presence: true, if: proc { |mm| !mm.my_module_group_id.nil? }
validate :coordinates_uniqueness_check, if: :active?
belongs_to :created_by,
foreign_key: 'created_by_id',
@ -419,13 +419,12 @@ class MyModule < ApplicationRecord
def deep_clone_to_experiment(current_user, experiment)
# Copy the module
clone = MyModule.new(
name: self.name,
experiment: experiment,
description: self.description,
x: self.x,
y: self.y)
clone.save
clone = MyModule.new(name: name, experiment: experiment, description: description, x: x, y: y)
# set new position if cloning in the same experiment
clone.attributes = get_new_position if clone.experiment == experiment
clone.save!
# Remove the automatically generated protocol,
# & clone the protocol instead
@ -434,17 +433,18 @@ class MyModule < ApplicationRecord
# Update the cloned protocol if neccesary
clone_tinymce_assets(clone, clone.experiment.project.team)
clone.protocols << self.protocol.deep_clone_my_module(self, current_user)
clone.protocols << protocol.deep_clone_my_module(self, current_user)
clone.reload
# fixes linked protocols
clone.protocols.each do |protocol|
next unless protocol.linked?
protocol.updated_at = protocol.parent_updated_at
protocol.save
end
return clone
clone
end
# Find an empty position for the restored module. It's
@ -500,4 +500,10 @@ class MyModule < ApplicationRecord
def create_blank_protocol
protocols << Protocol.new_blank_for_module(self)
end
def coordinates_uniqueness_check
if experiment && experiment.my_modules.active.where(x: x, y: y).where.not(id: id).any?
errors.add(:position, I18n.t('activerecord.errors.models.my_module.attributes.position.not_unique'))
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Api
module V1
class AssetSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
type :attachments
attributes :id, :file_name, :file_size, :file_type, :file_url
belongs_to :step, serializer: StepSerializer
def file_type
object.content_type
end
def file_url
rails_blob_url(object.file, disposition: 'attachment') if object.file.attached?
end
end
end
end

View file

@ -10,8 +10,8 @@ module Api
serializer: ProtocolKeywordSerializer,
class_name: 'ProtocolKeyword',
unless: -> { object.protocol_keywords.empty? }
belongs_to :parent, serializer: ProtocolSerializer,
if: -> { object.parent.present? }
has_many :steps, serializer: StepSerializer, if: -> { object.steps.any? }
belongs_to :parent, serializer: ProtocolSerializer, if: -> { object.parent.present? }
end
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Api
module V1
class StepSerializer < ActiveModel::Serializer
type :steps
attributes :id, :name, :description, :position, :completed
attribute :completed_on, if: :completed?
belongs_to :protocol, serializer: ProtocolSerializer
has_many :assets, serializer: AssetSerializer
def completed?
object.completed
end
end
end
end

View file

@ -81,6 +81,15 @@ en:
attributes:
team_id:
same_team: "Inventory can't be shared to the same team as it belongs to"
my_module:
attributes:
position:
not_unique: "X and Y position has already been taken by another task in the experiment."
asset:
attributes:
file:
too_big: "is too big"
helpers:
label:

View file

@ -620,7 +620,7 @@ Rails.application.routes.draw do
namespace :api, defaults: { format: 'json' } do
get 'health', to: 'api#health'
get 'status', to: 'api#status'
if Api.configuration.core_api_v1_enabled
if Api.configuration.core_api_v1_enabled || Rails.env.development?
namespace :v1 do
resources :teams, only: %i(index show) do
resources :inventories,
@ -655,7 +655,7 @@ Rails.application.routes.draw do
resources :experiments, only: %i(index show) do
resources :task_groups, only: %i(index show)
resources :connections, only: %i(index show)
resources :tasks, only: %i(index show) do
resources :tasks, only: %i(index show create) do
resources :task_inventory_items, only: %i(index show),
path: 'items',
as: :items
@ -665,8 +665,12 @@ Rails.application.routes.draw do
resources :task_tags, only: %i(index show),
path: 'tags',
as: :tags
resources :protocols, only: %i(index)
resources :results, only: %i(index create show)
resources :protocols, only: %i(index) do
resources :steps, only: %i(index show create) do
resources :assets, only: %i(index show create), path: 'attachments'
end
end
resources :results, only: %i(index create show update)
get 'activities', to: 'tasks#activities'
end
end

40
lib/tasks/my_modules.rake Normal file
View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
# On production around 2k records for repair, for production DB it tooked cca 1 min
# rubocop:disable Metrics/BlockLength
namespace :my_modules do
desc 'Find new positions on canvas for already taken coordiantes'
task fix_positions: :environment do
query = MyModule.select('COUNT(*) as duplicates', :x, :y, :experiment_id)
.where(archived: false)
.group(:x, :y, :experiment_id)
.having('COUNT(my_modules.id)>1')
Rails.logger.info '*********************************************************************************'
Rails.logger.info "You have to relocate #{query.sum { |a| a.duplicates - 1 }} tasks"
Rails.logger.info '*********************************************************************************'
query.each do |row|
tasks_to_update = MyModule.where(experiment_id: row.experiment, x: row.x, y: row.y, archived: false)
.order(created_at: :asc)
.offset(1)
tasks_to_update.find_each do |task|
coordinates = task.get_new_position
task.attributes = coordinates
begin
task.save!
Rails.logger.info "Relocated task with ID #{task.id}"
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error "Unable to save task with ID #{task.id}: #{e.message}"
end
end
end
Rails.logger.info '*********************************************************************************'
Rails.logger.info "You have #{query.reload.sum { |a| a.duplicates - 1 }} tasks on invalid positions"
Rails.logger.info '*********************************************************************************'
end
end
# rubocop:enable Metrics/BlockLength

View file

@ -4,7 +4,7 @@ FactoryBot.define do
factory :my_module do
sequence(:name) { |n| "Task-#{n}" }
x { Faker::Number.between(from: 1, to: 100) }
y { Faker::Number.between(from: 1, to: 100) }
sequence(:y) { |n| n }
workflow_order { MyModule.where(experiment_id: experiment.id).count + 1 }
experiment
my_module_group { create :my_module_group, experiment: experiment }

BIN
spec/fixtures/files/apple.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View file

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'my_modules:fix_positions' do
include_context 'rake'
before(:all) do
experiment = create :experiment
100.times do
create :my_module, experiment: experiment
end
# set 10 tasks same position
my_modules_with_same_position = MyModule.limit(10)
my_modules_with_same_position.update_all(x: 0, y: 0)
# 1 module should be invalid
my_modules_with_same_position.second.update_column(:name, 'a')
my_modules_with_same_position.third.update_column(:archived, true)
@my_module_id = my_modules_with_same_position.fourth.id
end
context 'when record is valid except position' do
it 'changes position for my_module' do
expect { subject.invoke }.to(change { MyModule.find(@my_module_id).y })
end
end
context 'when record is invalid' do
it 'remains error on position' do
subject.invoke
my_module = MyModule.find_by(name: 'a')
my_module.valid?
expect(my_module.errors.messages[:position])
.to(eq ['X and Y position has already been taken by another task in the experiment.'])
end
end
end

View file

@ -120,7 +120,7 @@ describe Experiment, type: :model do
{ id: 'n' + i.to_s,
name: t.name + '_new',
x: 50,
y: 50 }
y: 50 + i }
end
end
let(:to_clone) do

View file

@ -63,19 +63,48 @@ describe MyModule, type: :model do
it { should have_many(:my_module_antecessors).class_name('MyModule') }
end
describe 'Should be a valid object' do
it { should validate_presence_of :x }
it { should validate_presence_of :y }
it { should validate_presence_of :workflow_order }
it { should validate_presence_of :experiment }
it do
should validate_length_of(:name)
.is_at_least(Constants::NAME_MIN_LENGTH)
.is_at_most(Constants::NAME_MAX_LENGTH)
describe 'Validations' do
describe '#name' do
it do
is_expected.to(validate_length_of(:name)
.is_at_least(Constants::NAME_MIN_LENGTH)
.is_at_most(Constants::NAME_MAX_LENGTH))
end
end
it do
should validate_length_of(:description)
.is_at_most(Constants::RICH_TEXT_MAX_LENGTH)
describe '#description' do
it do
is_expected.to(validate_length_of(:description)
.is_at_most(Constants::RICH_TEXT_MAX_LENGTH))
end
end
describe '#x, #y scoped to experiment for active modules' do
it { is_expected.to validate_presence_of :x }
it { is_expected.to validate_presence_of :y }
it 'should be invalid for same x, y, and experiment' do
my_module.save
new_my_module = my_module.dup
expect(new_my_module).not_to be_valid
end
it 'should be valid when module with same x, y and expriment is archived' do
my_module.save
new_my_module = my_module.dup
my_module.update_column(:archived, true)
expect(new_my_module).to be_valid
end
end
describe '#workflow_order' do
it { is_expected.to validate_presence_of :workflow_order }
end
describe '#experiment' do
it { is_expected.to validate_presence_of :experiment }
end
end
end

View file

@ -0,0 +1,180 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::AssetsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team)
@experiment = create(:experiment, :with_tasks, project: @project)
@task = @experiment.my_modules.first
@protocol = create(:protocol, my_module: @task)
@step = create(:step, protocol: @protocol)
create(:user_team, user: @user, team: @team)
create(:user_project, :normal_user, user: @user, project: @project)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
let(:asset) { create :asset, step: @step }
describe 'GET steps, #index' do
context 'when has valid params' do
it 'renders 200' do
create(:step_asset, step: @step, asset: asset)
get(api_v1_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
), headers: @valid_headers)
expect(response).to have_http_status(200)
end
end
context 'when protocol is not found' do
it 'renders 404' do
get(api_v1_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: -1,
step_id: @step.id
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
end
describe 'GET step, #show' do
context 'when has valid params' do
it 'renders 200' do
create(:step_asset, step: @step, asset: asset)
get(api_v1_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: asset.id
), headers: @valid_headers)
expect(response).to have_http_status(200)
end
end
context 'when experiment is not found' do
it 'renders 404' do
get(api_v1_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: -1,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: asset.id
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
context 'when asset is not found' do
it 'renders 404' do
get(api_v1_team_project_experiment_task_protocol_step_asset_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id,
id: -1
), headers: @valid_headers)
expect(response).to have_http_status(404)
end
end
end
describe 'POST step, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
before :each do
@file = fixture_file_upload('files/test.jpg', 'image/jpg')
allow_any_instance_of(Asset).to receive(:post_process_file)
end
let(:action) do
post(api_v1_team_project_experiment_task_protocol_step_assets_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: @protocol.id,
step_id: @step.id
),
params: request_body,
headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'attachments',
attributes: {
file: @file
}
}
}
end
it 'creates new asset' do
expect { action }.to change { Asset.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'calls post_process_file function for text extraction' do
expect_any_instance_of(Asset).to receive(:post_process_file)
action
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'attachments',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
end

View file

@ -1,6 +1,5 @@
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
require 'rails_helper'
RSpec.describe 'Api::V1::ResultsController', type: :request do
@ -47,7 +46,7 @@ RSpec.describe 'Api::V1::ResultsController', type: :request do
attributes: {
name: Faker::Name.unique.name
} },
included: [
included: [
{ type: 'result_texts',
attributes: {
text: Faker::Lorem.sentence(word_count: 25)
@ -267,6 +266,44 @@ RSpec.describe 'Api::V1::ResultsController', type: :request do
expect { hash_body = json }.not_to raise_exception
expect(hash_body['errors'][0]).to include('status': 404)
end
context 'when resultType is File' do
let(:file) { fixture_file_upload('files/test.jpg', 'image/jpg') }
let(:request_body) do
{
data: {
type: 'results',
attributes: {
name: 'my result'
}
},
included: [
{ type: 'result_files',
attributes: {
file: file
} }
]
}
end
let(:action) do
post(api_v1_team_project_experiment_task_results_path(
team_id: @teams.first.id,
project_id: @valid_project,
experiment_id: @valid_experiment,
task_id: @valid_task
), params: request_body, headers: @valid_headers)
end
it 'creates new asset' do
expect { action }.to change { ResultAsset.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
end
end
describe 'GET result, #show' do
@ -329,5 +366,126 @@ RSpec.describe 'Api::V1::ResultsController', type: :request do
expect(hash_body['errors'][0]).to include('status': 404)
end
end
describe 'PUT result, #update' do
context 'when resultType is file' do
let(:result_file) { @valid_task.results.last }
let(:file) { fixture_file_upload('files/test.jpg', 'image/jpg') }
let(:second_file) { fixture_file_upload('files/apple.jpg', 'image/jpg') }
let(:request_body) do
{
data: {
type: 'results',
attributes: {
name: 'my result'
}
},
included: [
{ type: 'result_files',
attributes: {
file: file
} }
]
}
end
let(:action) do
put(api_v1_team_project_experiment_task_result_path(
team_id: @teams.first.id,
project_id: @valid_project,
experiment_id: @valid_experiment,
task_id: @valid_task,
id: result_file.id
), params: request_body, headers: @valid_headers)
end
context 'when has attributes for update' do
it 'updates tasks name' do
action
expect(result_file.reload.name).to eq('my result')
end
it 'returns status 200' do
action
expect(response).to have_http_status 200
end
end
context 'when has new image for update' do
let(:request_body_with_same_name_new_file) do
{
data: {
type: 'results',
attributes: {
name: result_file.reload.name
}
},
included: [
{ type: 'result_files',
attributes: {
file: second_file
} }
]
}
end
it 'returns status 200' do
put(api_v1_team_project_experiment_task_result_path(
team_id: @teams.first.id,
project_id: @valid_project,
experiment_id: @valid_experiment,
task_id: @valid_task,
id: result_file.id
), params: request_body_with_same_name_new_file, headers: @valid_headers)
expect(response).to have_http_status 200
end
end
context 'when there is nothing to update' do
let(:request_body_with_same_name) do
{
data: {
type: 'results',
attributes: {
name: result_file.reload.name
}
}
}
end
it 'returns 204' do
put(api_v1_team_project_experiment_task_result_path(
team_id: @teams.first.id,
project_id: @valid_project,
experiment_id: @valid_experiment,
task_id: @valid_task,
id: result_file.id
), params: request_body_with_same_name.to_json, headers: @valid_headers)
expect(response).to have_http_status 204
end
end
end
context 'when resultType is text' do
let(:result_text) { @valid_task.results.first }
let(:action) do
put(api_v1_team_project_experiment_task_result_path(
team_id: @teams.first.id,
project_id: @valid_project,
experiment_id: @valid_experiment,
task_id: @valid_task,
id: result_text.id
), params: @valid_text_hash_body.to_json, headers: @valid_headers)
end
it 'returns status 500' do
action
expect(response).to have_http_status 500
end
end
end
end
# rubocop:enable Metrics/LineLength

View file

@ -0,0 +1,158 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::StepsController', type: :request do
before :all do
@user = create(:user)
@team = create(:team, created_by: @user)
@project = create(:project, team: @team)
@experiment = create(:experiment, :with_tasks, project: @project)
@task = @experiment.my_modules.first
create(:user_team, user: @user, team: @team)
create(:user_project, :normal_user, user: @user, project: @project)
@valid_headers =
{ 'Authorization': 'Bearer ' + generate_token(@user.id) }
end
let(:protocol) { create :protocol, my_module: @task }
let(:steps) { create_list(:step, 3, protocol: protocol) }
describe 'GET steps, #index' do
context 'when has valid params' do
it 'renders 200' do
get api_v1_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when protocol is not found' do
it 'renders 404' do
get api_v1_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: -1
), headers: @valid_headers
expect(response).to have_http_status(404)
end
end
end
describe 'GET step, #show' do
context 'when has valid params' do
it 'renders 200' do
get api_v1_team_project_experiment_task_protocol_step_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id,
id: steps.first.id
), headers: @valid_headers
expect(response).to have_http_status(200)
end
end
context 'when experiment is archived and permission checks fails' do
it 'renders 403' do
@experiment.update_attribute(:archived, true)
get api_v1_team_project_experiment_task_protocol_step_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id,
id: steps.first.id
), headers: @valid_headers
expect(response).to have_http_status(403)
end
end
end
describe 'POST step, #create' do
before :all do
@valid_headers['Content-Type'] = 'application/json'
end
let(:action) do
post(api_v1_team_project_experiment_task_protocol_steps_path(
team_id: @team.id,
project_id: @project.id,
experiment_id: @experiment.id,
task_id: @task.id,
protocol_id: protocol.id
),
params: request_body.to_json,
headers: @valid_headers)
end
context 'when has valid params' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
name: 'Step name',
description: 'Description about step'
}
}
}
end
it 'creates new step' do
expect { action }.to change { Step.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'steps',
attributes: hash_including(name: 'Step name')
)
)
)
end
end
context 'when has missing param' do
let(:request_body) do
{
data: {
type: 'steps',
attributes: {
}
}
}
end
it 'renders 400' do
action
expect(response).to have_http_status(400)
end
end
end
end

View file

@ -2,7 +2,7 @@
require 'rails_helper'
RSpec.describe "Api::V1::TasksController", type: :request do
RSpec.describe 'Api::V1::TasksController', type: :request do
before :all do
@user = create(:user)
@teams = create_list(:team, 2, created_by: @user)
@ -139,4 +139,101 @@ RSpec.describe "Api::V1::TasksController", type: :request do
expect(hash_body['errors'][0]).to include('status': 404)
end
end
describe 'POST tasks, #create' do
before :all do
create :user_project, :normal_user, user: @user, project: @valid_project
@valid_headers['Content-Type'] = 'application/json'
end
let(:request_body) do
{
data: {
type: 'tasks',
attributes: {
name: 'task name',
x: 1,
y: 4
}
}
}
end
context 'when has valid params' do
let(:action) do
post(api_v1_team_project_experiment_tasks_path(
team_id: @teams.first.id,
project_id: @valid_project.id,
experiment_id: @valid_experiment.id
),
params: request_body.to_json,
headers: @valid_headers)
end
it 'creates new my module' do
expect { action }.to change { MyModule.count }.by(1)
end
it 'returns status 201' do
action
expect(response).to have_http_status 201
end
it 'returns well formated response' do
action
expect(json).to match(
hash_including(
data: hash_including(
type: 'tasks',
attributes: hash_including(name: 'task name'),
relationships: hash_including(outputs: { data: [] }, inputs: { data: [] })
)
)
)
end
end
context 'when has not valid params' do
it 'renders 404 when project not found' do
post(api_v1_team_project_experiment_tasks_path(
team_id: @teams.first.id,
project_id: -1,
experiment_id: @valid_experiment.id
),
params: request_body.to_json,
headers: @valid_headers)
expect(response).to have_http_status(404)
end
it 'renders 403 when user is not member of the team' do
post(api_v1_team_project_experiment_tasks_path(
team_id: @teams.second.id,
project_id: @valid_project.id,
experiment_id: @valid_experiment.id
),
params: request_body.to_json,
headers: @valid_headers)
expect(response).to have_http_status(403)
end
it 'renders 403 for use with view permissions' do
up = UserProject.where(user: @user, project: @valid_project).first
up.update_attribute(:role, :viewer)
post(api_v1_team_project_experiment_tasks_path(
team_id: @teams.first.id,
project_id: @valid_project.id,
experiment_id: @valid_experiment.id
),
params: request_body.to_json,
headers: @valid_headers)
expect(response).to have_http_status(403)
end
end
end
end

View file

@ -53,7 +53,7 @@
"updated_at": "2019-01-21T13:32:46.278Z",
"workflow_order": -1,
"x": 64,
"y": 46
"y": 22
},
"my_module_repository_rows": [],
"my_module_tags": [],