From aaa7216d806fe5dfa17d41a5d811777309407491 Mon Sep 17 00:00:00 2001 From: Jens Ulferts Date: Fri, 5 Apr 2019 08:46:10 +0200 Subject: [PATCH] split my_page module off of grids --- Gemfile.lock | 7 + Gemfile.modules | 1 + .../grids/schemas/grid_schema_representer.rb | 3 +- modules/grids/bin/rails | 14 - modules/grids/lib/grids/engine.rb | 4 - .../contracts/grids/create_contract_spec.rb | 26 +- .../spec/contracts/grids/shared_examples.rb | 256 +---------- .../contracts/grids/update_contract_spec.rb | 2 +- modules/grids/spec/factories/grid_factory.rb | 24 - .../grid_payload_representer_parsing_spec.rb | 4 +- .../grids/grid_representer_rendering_spec.rb | 25 +- .../grids/grids_create_form_resource_spec.rb | 140 ------ .../api/v3/grids/grids_resource_spec.rb | 353 --------------- .../grids/grids_update_form_resource_spec.rb | 124 +----- .../services/grids/create_service_spec.rb | 4 +- .../grids/set_attributes_service_spec.rb | 2 +- .../services/grids/update_service_spec.rb | 2 +- modules/my_page/.gitignore | 7 + modules/my_page/Gemfile | 3 + .../app/models/grids/my_page.rb | 0 modules/my_page/lib/my_page.rb | 4 + modules/my_page/lib/my_page/engine.rb | 11 + .../lib/my_page/grid_registration.rb} | 4 +- modules/my_page/my_page.gemspec | 12 + .../contracts/grids/create_contract_spec.rb | 61 +++ .../spec/contracts/grids/shared_examples.rb | 320 ++++++++++++++ .../contracts/grids/update_contract_spec.rb | 39 ++ .../my_page/spec/factories/grid_factory.rb | 25 ++ .../spec/features/my/accountable_spec.rb} | 0 .../spec/features/my/assigned_to_me_spec.rb} | 0 .../spec/features/my/documents_spec.rb} | 0 .../spec/features/my/my_page_spec.rb | 0 .../spec/features/my/news_spec.rb} | 0 .../my/time_entries_current_user_spec.rb} | 0 .../features/my/work_package_table_spec.rb} | 0 .../spec/models/grids/my_page_spec.rb | 0 .../my_page/spec/models/grids/shared_model.rb | 77 ++++ .../grids/filters/scope_filter_spec.rb | 0 .../queries/grids/query_integration_spec.rb | 0 .../grids/grids_create_form_resource_spec.rb | 193 ++++++++ .../api/v3/grids/grids_resource_spec.rb | 414 ++++++++++++++++++ .../grids/grids_update_form_resource_spec.rb | 174 ++++++++ 42 files changed, 1383 insertions(+), 952 deletions(-) delete mode 100755 modules/grids/bin/rails create mode 100644 modules/my_page/.gitignore create mode 100644 modules/my_page/Gemfile rename modules/{grids => my_page}/app/models/grids/my_page.rb (100%) create mode 100644 modules/my_page/lib/my_page.rb create mode 100644 modules/my_page/lib/my_page/engine.rb rename modules/{grids/lib/grids/my_page_grid_registration.rb => my_page/lib/my_page/grid_registration.rb} (93%) create mode 100644 modules/my_page/my_page.gemspec create mode 100644 modules/my_page/spec/contracts/grids/create_contract_spec.rb create mode 100644 modules/my_page/spec/contracts/grids/shared_examples.rb create mode 100644 modules/my_page/spec/contracts/grids/update_contract_spec.rb create mode 100644 modules/my_page/spec/factories/grid_factory.rb rename modules/{grids/spec/features/my/my_page_accountable_spec.rb => my_page/spec/features/my/accountable_spec.rb} (100%) rename modules/{grids/spec/features/my/my_page_assigned_to_me_spec.rb => my_page/spec/features/my/assigned_to_me_spec.rb} (100%) rename modules/{grids/spec/features/my/my_page_documents_spec.rb => my_page/spec/features/my/documents_spec.rb} (100%) rename modules/{grids => my_page}/spec/features/my/my_page_spec.rb (100%) rename modules/{grids/spec/features/my/my_page_news_spec.rb => my_page/spec/features/my/news_spec.rb} (100%) rename modules/{grids/spec/features/my/my_page_time_entries_current_user_spec.rb => my_page/spec/features/my/time_entries_current_user_spec.rb} (100%) rename modules/{grids/spec/features/my/my_page_work_package_table_spec.rb => my_page/spec/features/my/work_package_table_spec.rb} (100%) rename modules/{grids => my_page}/spec/models/grids/my_page_spec.rb (100%) create mode 100644 modules/my_page/spec/models/grids/shared_model.rb rename modules/{grids => my_page}/spec/queries/grids/filters/scope_filter_spec.rb (100%) rename modules/{grids => my_page}/spec/queries/grids/query_integration_spec.rb (100%) create mode 100644 modules/my_page/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb create mode 100644 modules/my_page/spec/requests/api/v3/grids/grids_resource_spec.rb create mode 100644 modules/my_page/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index e9579cee39f..dc49639537f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -180,6 +180,12 @@ PATH openproject-meeting (1.0.0) icalendar (~> 2.5.0) +PATH + remote: modules/my_page + specs: + my_page (1.0.0) + grids + PATH remote: modules/my_project_page specs: @@ -970,6 +976,7 @@ DEPENDENCIES lograge (~> 0.10.0) meta-tags (~> 2.11.0) multi_json (~> 1.13.1) + my_page! mysql2 (~> 0.5.0) net-ldap (~> 0.16.0) newrelic_rpm diff --git a/Gemfile.modules b/Gemfile.modules index ff05bcd1480..e6608033d8c 100644 --- a/Gemfile.modules +++ b/Gemfile.modules @@ -40,6 +40,7 @@ group :opf_plugins do gem 'openproject-ldap_groups', path: 'modules/ldap_groups' gem 'grids', path: 'modules/grids' + gem 'my_page', path: 'modules/my_page' gem 'openproject-boards', path: 'modules/boards' gem 'openproject-bim_seeder', path: 'modules/bim_seeder', require: !!(ENV['OPENPROJECT_EDITION'] == 'bim') diff --git a/modules/grids/app/controllers/api/v3/grids/schemas/grid_schema_representer.rb b/modules/grids/app/controllers/api/v3/grids/schemas/grid_schema_representer.rb index ad5ffc594b7..c8343832b1a 100644 --- a/modules/grids/app/controllers/api/v3/grids/schemas/grid_schema_representer.rb +++ b/modules/grids/app/controllers/api/v3/grids/schemas/grid_schema_representer.rb @@ -76,8 +76,7 @@ module API value_representer: false, link_factory: ->(path) { { - href: path, - title: I18n.t(:label_my_page) + href: path } } diff --git a/modules/grids/bin/rails b/modules/grids/bin/rails deleted file mode 100755 index b676279b957..00000000000 --- a/modules/grids/bin/rails +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails gems -# installed from the root of your application. - -ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/grids/engine', __FILE__) -APP_PATH = File.expand_path('../../test/dummy/config/application', __FILE__) - -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) - -require 'rails/all' -require 'rails/engine/commands' diff --git a/modules/grids/lib/grids/engine.rb b/modules/grids/lib/grids/engine.rb index d0653721bb8..5a91abee352 100644 --- a/modules/grids/lib/grids/engine.rb +++ b/modules/grids/lib/grids/engine.rb @@ -9,9 +9,5 @@ module Grids Queries::Register.filter query, Grids::Filters::ScopeFilter end - - config.to_prepare do - Grids::MyPageGridRegistration.register! - end end end diff --git a/modules/grids/spec/contracts/grids/create_contract_spec.rb b/modules/grids/spec/contracts/grids/create_contract_spec.rb index 16511c79c0b..c2e6a183921 100644 --- a/modules/grids/spec/contracts/grids/create_contract_spec.rb +++ b/modules/grids/spec/contracts/grids/create_contract_spec.rb @@ -38,9 +38,11 @@ describe Grids::CreateContract do it_behaves_like 'shared grid contract attributes' describe 'type' do + let(:grid) { FactoryBot.build_stubbed(:grid, default_values) } + it_behaves_like 'is writable' do let(:attribute) { :type } - let(:value) { 'Grids::MyPage' } + let(:value) { 'Grids::Grid' } end end @@ -51,15 +53,6 @@ describe Grids::CreateContract do let(:attribute) { :user_id } let(:value) { 5 } end - - context 'for a Grids::MyPage' do - let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) } - - it_behaves_like 'is writable' do - let(:attribute) { :user_id } - let(:value) { 5 } - end - end end describe 'project_id' do @@ -69,15 +62,6 @@ describe Grids::CreateContract do let(:attribute) { :project_id } let(:value) { 5 } end - - context 'for a Grids::MyPage' do - let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) } - - it_behaves_like 'is not writable' do - let(:attribute) { :project_id } - let(:value) { 5 } - end - end end describe '#assignable_values' do @@ -104,12 +88,12 @@ describe Grids::CreateContract do allow(Grids::Configuration) .to receive(:allowed_widget?) - .with(Grids::MyPage, :widget1, user) + .with(Grids::Grid, :widget1, user) .and_return(true) allow(Grids::Configuration) .to receive(:allowed_widget?) - .with(Grids::MyPage, :widget2, user) + .with(Grids::Grid, :widget2, user) .and_return(false) expect(instance.assignable_values(:widgets, user)) diff --git a/modules/grids/spec/contracts/grids/shared_examples.rb b/modules/grids/spec/contracts/grids/shared_examples.rb index 4e9324db817..c01a216d756 100644 --- a/modules/grids/spec/contracts/grids/shared_examples.rb +++ b/modules/grids/spec/contracts/grids/shared_examples.rb @@ -39,7 +39,7 @@ shared_context 'grid contract' do } end let(:grid) do - FactoryBot.build_stubbed(:my_page, default_values) + FactoryBot.build_stubbed(:grid, default_values) end shared_examples_for 'validates positive integer' do @@ -113,261 +113,7 @@ shared_examples_for 'shared grid contract attributes' do end end - describe 'widgets' do - it_behaves_like 'is writable' do - let(:attribute) { :widgets } - let(:value) do - [ - Grids::Widget.new(start_row: 1, - end_row: 4, - start_column: 2, - end_column: 5, - identifier: 'work_packages_assigned') - ] - end - end - - context 'invalid identifier' do - before do - grid.widgets.build(start_row: 1, - end_row: 4, - start_column: 2, - end_column: 5, - identifier: 'bogus_identifier') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :inclusion }] - end - end - - context 'collisions between widgets' do - before do - grid.widgets.build(start_row: 1, - end_row: 3, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - grid.widgets.build(start_row: 2, - end_row: 4, - start_column: 2, - end_column: 4, - identifier: 'work_packages_created') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :overlaps }, { error: :overlaps }] - end - end - - context 'widgets having the same start column as another\'s end column' do - before do - grid.widgets.build(start_row: 1, - end_row: 3, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - grid.widgets.build(start_row: 1, - end_row: 3, - start_column: 3, - end_column: 4, - identifier: 'work_packages_created') - end - - it 'is valid' do - expect(instance.validate) - .to be_truthy - end - end - - context 'widgets having the same start row as another\'s end row' do - before do - grid.widgets.build(start_row: 1, - end_row: 3, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - grid.widgets.build(start_row: 3, - end_row: 4, - start_column: 1, - end_column: 3, - identifier: 'work_packages_created') - end - - it 'is valid' do - expect(instance.validate) - .to be_truthy - end - end - - context 'widgets being outside (max) of the grid' do - before do - grid.widgets.build(start_row: 1, - end_row: grid.row_count + 2, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :outside }] - end - end - - context 'widgets being outside (min) of the grid' do - before do - grid.widgets.build(start_row: 1, - end_row: 2, - start_column: -1, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :outside }] - end - end - - context 'widgets spanning the whole grid' do - before do - grid.widgets.build(start_row: 1, - end_row: grid.row_count + 1, - start_column: 1, - end_column: grid.column_count + 1, - identifier: 'work_packages_assigned') - end - - it 'is valid' do - expect(instance.validate) - .to be_truthy - end - end - - context 'widgets having start after end column' do - before do - grid.widgets.build(start_row: 1, - end_row: 2, - start_column: 4, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :end_before_start }] - end - end - - context 'widgets having start after end row' do - before do - grid.widgets.build(start_row: 4, - end_row: 2, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :end_before_start }] - end - end - - context 'widgets having start equals end column' do - before do - grid.widgets.build(start_row: 1, - end_row: 2, - start_column: 4, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :end_before_start }] - end - end - - context 'widgets having start equals end row' do - before do - grid.widgets.build(start_row: 2, - end_row: 2, - start_column: 1, - end_column: 3, - identifier: 'work_packages_assigned') - end - - it 'is invalid' do - expect(instance.validate) - .to be_falsey - end - - it 'notes the error' do - instance.validate - expect(instance.errors.details[:widgets]) - .to match_array [{ error: :end_before_start }] - end - end - end - describe 'valid grid subclasses' do - context 'for a registered subclass' do - let(:grid) do - FactoryBot.build_stubbed(:my_page, default_values) - end - - it 'is valid' do - expect(instance.validate) - .to be_truthy - end - end - context 'for the Grid superclass itself' do let(:grid) do FactoryBot.build_stubbed(:grid, default_values) diff --git a/modules/grids/spec/contracts/grids/update_contract_spec.rb b/modules/grids/spec/contracts/grids/update_contract_spec.rb index 1202f02a1af..5ed4ddec1fc 100644 --- a/modules/grids/spec/contracts/grids/update_contract_spec.rb +++ b/modules/grids/spec/contracts/grids/update_contract_spec.rb @@ -51,7 +51,7 @@ describe Grids::UpdateContract do instance.validate # scope because that is what type is called on the outside for grids expect(instance.errors.details[:scope]) - .to match_array [{ error: :error_readonly }] + .to match_array [{ error: :error_readonly }, { error: :inclusion }] end end diff --git a/modules/grids/spec/factories/grid_factory.rb b/modules/grids/spec/factories/grid_factory.rb index d0749efaff4..2250ad598c0 100644 --- a/modules/grids/spec/factories/grid_factory.rb +++ b/modules/grids/spec/factories/grid_factory.rb @@ -1,28 +1,4 @@ FactoryBot.define do factory :grid, class: Grids::Grid do end - - factory :my_page, class: Grids::MyPage do - user - row_count { 7 } - column_count { 4 } - widgets do - [ - Grids::Widget.new( - identifier: 'work_packages_assigned', - start_row: 1, - end_row: 7, - start_column: 1, - end_column: 3 - ), - Grids::Widget.new( - identifier: 'work_packages_created', - start_row: 1, - end_row: 7, - start_column: 3, - end_column: 5 - ) - ] - end - end end diff --git a/modules/grids/spec/lib/api/v3/grids/grid_payload_representer_parsing_spec.rb b/modules/grids/spec/lib/api/v3/grids/grid_payload_representer_parsing_spec.rb index 308d6b88ede..8cd431bb76f 100644 --- a/modules/grids/spec/lib/api/v3/grids/grid_payload_representer_parsing_spec.rb +++ b/modules/grids/spec/lib/api/v3/grids/grid_payload_representer_parsing_spec.rb @@ -71,7 +71,7 @@ describe ::API::V3::Grids::GridPayloadRepresenter, 'parsing' do ], "_links" => { "scope" => { - "href" => my_page_path + "href" => 'some_path' } } } @@ -82,7 +82,7 @@ describe ::API::V3::Grids::GridPayloadRepresenter, 'parsing' do it 'updates page' do grid = representer.from_hash(hash) expect(grid.scope) - .to eql(my_page_path) + .to eql('some_path') end end end diff --git a/modules/grids/spec/lib/api/v3/grids/grid_representer_rendering_spec.rb b/modules/grids/spec/lib/api/v3/grids/grid_representer_rendering_spec.rb index b17313eabe3..4b6477722a6 100644 --- a/modules/grids/spec/lib/api/v3/grids/grid_representer_rendering_spec.rb +++ b/modules/grids/spec/lib/api/v3/grids/grid_representer_rendering_spec.rb @@ -33,7 +33,7 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do let(:grid) do FactoryBot.build_stubbed( - :my_page, + :grid, row_count: 4, column_count: 5, widgets: [ @@ -68,6 +68,21 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do let(:current_user) { FactoryBot.build_stubbed(:user) } let(:representer) { described_class.new(grid, current_user: current_user) } + let(:writable) { true } + let(:scope_path) { 'bogus_scope' } + + before do + allow(::Grids::Configuration) + .to receive(:writable?) + .with(grid, current_user) + .and_return(writable) + + allow(::Grids::Configuration) + .to receive(:to_scope) + .with(Grids::Grid, []) + .and_return(scope_path) + end + context 'generation' do subject(:generated) { representer.to_json } @@ -78,12 +93,6 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do .at_path('_type') end - it 'identifies the url the grid is stored for' do - is_expected - .to be_json_eql(my_page_path.to_json) - .at_path('_links/scope/href') - end - it 'has an id' do is_expected .to be_json_eql(grid.id) @@ -180,7 +189,7 @@ describe ::API::V3::Grids::GridRepresenter, 'rendering' do context 'scope link' do it_behaves_like 'has an untitled link' do let(:link) { 'scope' } - let(:href) { my_page_path } + let(:href) { scope_path } let(:type) { "text/html" } it 'has a content type of html' do diff --git a/modules/grids/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb b/modules/grids/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb index 0252eb017f4..6047409c80c 100644 --- a/modules/grids/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb +++ b/modules/grids/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb @@ -61,16 +61,6 @@ describe "POST /api/v3/grids/form", type: :request, content_type: :json do .at_path('_type') end - it 'contains a Schema embedding the available values' do - expect(subject.body) - .to be_json_eql("Schema".to_json) - .at_path('_embedded/schema/_type') - - expect(subject.body) - .to be_json_eql(my_page_path.to_json) - .at_path('_embedded/schema/scope/_links/allowedValues/0/href') - end - it 'contains default data in the payload' do expected = { "rowCount": 4, @@ -95,135 +85,5 @@ describe "POST /api/v3/grids/form", type: :request, content_type: :json do expect(subject.body) .not_to have_json_path('_links/commit') end - - context 'with /my/page for the scope value' do - let(:params) do - { - '_links': { - 'scope': { - 'href': my_page_path - } - } - } - end - - it 'contains default data in the payload' do - expected = { - "rowCount": 7, - "columnCount": 4, - "options": {}, - "widgets": [ - { - "_type": "GridWidget", - identifier: 'work_packages_assigned', - "options": {}, - startRow: 1, - endRow: 7, - startColumn: 1, - endColumn: 3 - }, - { - "_type": "GridWidget", - identifier: 'work_packages_created', - "options": {}, - startRow: 1, - endRow: 7, - startColumn: 3, - endColumn: 5 - } - ], - "_links": { - "scope": { - "href": "/my/page", - "type": "text/html" - } - } - } - - expect(subject.body) - .to be_json_eql(expected.to_json) - .at_path('_embedded/payload') - end - - it 'has no validationErrors' do - expect(subject.body) - .to be_json_eql({}.to_json) - .at_path('_embedded/validationErrors') - end - - it 'has a commit link' do - expect(subject.body) - .to be_json_eql(api_v3_paths.grids.to_json) - .at_path('_links/commit/href') - end - end - - context 'with an unsupported widget identifier' do - let(:params) do - { - '_links': { - 'scope': { - 'href': my_page_path - } - }, - "widgets": [ - { - "_type": "GridWidget", - "identifier": "bogus_identifier", - "startRow": 4, - "endRow": 5, - "startColumn": 1, - "endColumn": 2 - } - ] - } - end - - it 'has a validationError on widget' do - expect(subject.body) - .to be_json_eql("Widgets is not set to one of the allowed values.".to_json) - .at_path('_embedded/validationErrors/widgets/message') - end - end - - context 'with name set' do - let(:params) do - { - name: 'My custom grid 1', - '_links': { - 'scope': { - 'href': my_page_path - } - } - } - end - - it 'feeds it back' do - expect(subject.body) - .to be_json_eql("My custom grid 1".to_json) - .at_path('_embedded/payload/name') - end - end - - context 'with options set' do - let(:params) do - { - options: { - foo: 'bar' - }, - '_links': { - 'scope': { - 'href': my_page_path - } - } - } - end - - it 'feeds them back' do - expect(subject.body) - .to be_json_eql("bar".to_json) - .at_path('_embedded/payload/options/foo') - end - end end end diff --git a/modules/grids/spec/requests/api/v3/grids/grids_resource_spec.rb b/modules/grids/spec/requests/api/v3/grids/grids_resource_spec.rb index 427df745137..3b7309406c0 100644 --- a/modules/grids/spec/requests/api/v3/grids/grids_resource_spec.rb +++ b/modules/grids/spec/requests/api/v3/grids/grids_resource_spec.rb @@ -37,12 +37,6 @@ describe 'API v3 Grids resource', type: :request, content_type: :json do FactoryBot.create(:user) end - let(:my_page_grid) { FactoryBot.create(:my_page, user: current_user) } - let(:other_user) do - FactoryBot.create(:user) - end - let(:other_my_page_grid) { FactoryBot.create(:my_page, user: other_user) } - before do login_as(current_user) end @@ -52,369 +46,22 @@ describe 'API v3 Grids resource', type: :request, content_type: :json do describe '#get INDEX' do let(:path) { api_v3_paths.grids } - let(:stored_grids) do - my_page_grid - other_my_page_grid - end - before do - stored_grids - get path end it 'responds with 200 OK' do expect(subject.status).to eq(200) end - - it 'sends a collection of grids but only those visible to the current user' do - expect(subject.body) - .to be_json_eql('Collection'.to_json) - .at_path('_type') - - expect(subject.body) - .to be_json_eql('Grid'.to_json) - .at_path('_embedded/elements/0/_type') - - expect(subject.body) - .to be_json_eql(1.to_json) - .at_path('total') - end - - context 'with a filter on the scope attribute' do - shared_let(:other_grid) do - grid = Grids::Grid.new(row_count: 20, - column_count: 20) - grid.save - - Grids::Grid - .where(id: grid.id) - .update_all(user_id: current_user.id) - - grid - end - - let(:stored_grids) do - my_page_grid - other_my_page_grid - other_grid - end - - let(:path) do - filter = [{ 'scope' => - { - 'operator' => '=', - 'values' => [my_page_path] - } }] - - "#{api_v3_paths.grids}?#{{ filters: filter.to_json }.to_query}" - end - - it 'responds with 200 OK' do - expect(subject.status).to eq(200) - end - - it 'sends only the my page of the current user' do - expect(subject.body) - .to be_json_eql('Collection'.to_json) - .at_path('_type') - - expect(subject.body) - .to be_json_eql('Grid'.to_json) - .at_path('_embedded/elements/0/_type') - - expect(subject.body) - .to be_json_eql(1.to_json) - .at_path('total') - end - end - end - - describe '#get' do - let(:path) { api_v3_paths.grid(my_page_grid.id) } - - let(:stored_grids) do - my_page_grid - end - - before do - stored_grids - - get path - end - - it 'responds with 200 OK' do - expect(subject.status).to eq(200) - end - - it 'sends a grid block' do - expect(subject.body) - .to be_json_eql('Grid'.to_json) - .at_path('_type') - end - - it 'identifies the url the grid is stored for' do - expect(subject.body) - .to be_json_eql(my_page_path.to_json) - .at_path('_links/scope/href') - end - - context 'with the page not existing' do - let(:path) { api_v3_paths.grid(5) } - - it 'responds with 404 NOT FOUND' do - expect(subject.status).to eql 404 - end - end - - context 'with the grid belonging to someone else' do - let(:stored_grids) do - my_page_grid - other_my_page_grid - end - - let(:path) { api_v3_paths.grid(other_my_page_grid.id) } - - it 'responds with 404 NOT FOUND' do - expect(subject.status).to eql 404 - end - end - end - - describe '#patch' do - let(:path) { api_v3_paths.grid(my_page_grid.id) } - - let(:params) do - { - "rowCount": 10, - "name": 'foo', - "columnCount": 15, - "widgets": [{ - "identifier": "work_packages_assigned", - "startRow": 4, - "endRow": 8, - "startColumn": 2, - "endColumn": 5 - }] - }.with_indifferent_access - end - - let(:stored_grids) do - my_page_grid - end - - before do - stored_grids - - patch path, params.to_json, 'CONTENT_TYPE' => 'application/json' - end - - it 'responds with 200 OK' do - expect(subject.status).to eq(200) - end - - it 'returns the altered grid block' do - expect(subject.body) - .to be_json_eql('Grid'.to_json) - .at_path('_type') - expect(subject.body) - .to be_json_eql('foo'.to_json) - .at_path('name') - expect(subject.body) - .to be_json_eql(params['rowCount'].to_json) - .at_path('rowCount') - expect(subject.body) - .to be_json_eql(params['widgets'][0]['identifier'].to_json) - .at_path('widgets/0/identifier') - end - - it 'perists the changes' do - expect(my_page_grid.reload.row_count) - .to eql params['rowCount'] - end - - context 'with invalid params' do - let(:params) do - { - "rowCount": -5, - "columnCount": 15, - "widgets": [{ - "identifier": "work_packages_assigned", - "startRow": 4, - "endRow": 8, - "startColumn": 2, - "endColumn": 5 - }] - }.with_indifferent_access - end - - it 'responds with 422 and mentions the error' do - expect(subject.status).to eq 422 - - expect(subject.body) - .to be_json_eql('Error'.to_json) - .at_path('_type') - - expect(subject.body) - .to be_json_eql("Widgets is outside of the grid.".to_json) - .at_path('_embedded/errors/0/message') - - expect(subject.body) - .to be_json_eql("Number of rows must be greater than 0.".to_json) - .at_path('_embedded/errors/1/message') - end - - it 'does not persist the changes to widgets' do - expect(my_page_grid.reload.widgets.count) - .to eql Grids::MyPageGridRegistration.defaults[:widgets].size - end - end - - context 'with a scope param' do - let(:params) do - { - "_links": { - "scope": { - "href": '' - } - } - }.with_indifferent_access - end - - it 'responds with 422 and mentions the error' do - expect(subject.status).to eq 422 - - expect(subject.body) - .to be_json_eql('Error'.to_json) - .at_path('_type') - - expect(subject.body) - .to be_json_eql("You must not write a read-only attribute.".to_json) - .at_path('message') - - expect(subject.body) - .to be_json_eql("scope".to_json) - .at_path('_embedded/details/attribute') - end - end - - context 'with the page not existing' do - let(:path) { api_v3_paths.grid(5) } - - it 'responds with 404 NOT FOUND' do - expect(subject.status).to eql 404 - end - end - - context 'with the grid belonging to someone else' do - let(:stored_grids) do - my_page_grid - other_my_page_grid - end - - let(:path) { api_v3_paths.grid(other_my_page_grid.id) } - - it 'responds with 404 NOT FOUND' do - expect(subject.status).to eql 404 - end - end end describe '#post' do let(:path) { api_v3_paths.grids } - let(:params) do - { - "rowCount": 10, - "columnCount": 15, - "widgets": [{ - "identifier": "work_packages_assigned", - "startRow": 4, - "endRow": 8, - "startColumn": 2, - "endColumn": 5 - }], - "_links": { - "scope": { - "href": my_page_path - } - } - }.with_indifferent_access - end - before do post path, params.to_json, 'CONTENT_TYPE' => 'application/json' end - it 'responds with 201 CREATED' do - expect(subject.status).to eq(201) - end - - it 'returns the created grid block' do - expect(subject.body) - .to be_json_eql('Grid'.to_json) - .at_path('_type') - expect(subject.body) - .to be_json_eql(params['rowCount'].to_json) - .at_path('rowCount') - expect(subject.body) - .to be_json_eql(params['widgets'][0]['identifier'].to_json) - .at_path('widgets/0/identifier') - end - - it 'persists the grid' do - expect(Grids::Grid.count) - .to eql(1) - end - - context 'with invalid params' do - let(:params) do - { - "rowCount": -5, - "columnCount": "sdjfksdfsdfdsf", - "widgets": [{ - "identifier": "work_packages_assigned", - "startRow": 4, - "endRow": 8, - "startColumn": 2, - "endColumn": 5 - }], - "_links": { - "scope": { - "href": my_page_path - } - } - }.with_indifferent_access - end - - it 'responds with 422' do - expect(subject.status).to eq(422) - end - - it 'does not create a grid' do - expect(Grids::Grid.count) - .to eql(0) - end - - it 'returns the errors' do - expect(subject.body) - .to be_json_eql('Error'.to_json) - .at_path('_type') - - expect(subject.body) - .to be_json_eql("Widgets is outside of the grid.".to_json) - .at_path('_embedded/errors/0/message') - - expect(subject.body) - .to be_json_eql("Number of rows must be greater than 0.".to_json) - .at_path('_embedded/errors/1/message') - - expect(subject.body) - .to be_json_eql("Number of columns must be greater than 0.".to_json) - .at_path('_embedded/errors/2/message') - end - end - context 'without a page link' do let(:params) do { diff --git a/modules/grids/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb b/modules/grids/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb index 5d4fba3b4c0..4110d6999e1 100644 --- a/modules/grids/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb +++ b/modules/grids/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb @@ -37,10 +37,6 @@ describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do FactoryBot.create(:user) end - let(:grid) do - FactoryBot.create(:my_page, user: current_user) - end - let(:path) { api_v3_paths.grid_form(grid.id) } let(:params) { {} } subject(:response) { last_response } @@ -53,126 +49,8 @@ describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do post path, params.to_json, 'CONTENT_TYPE' => 'application/json' end - it 'returns 200 OK' do - expect(subject.status) - .to eql 200 - end - - it 'is of type form' do - expect(subject.body) - .to be_json_eql("Form".to_json) - .at_path('_type') - end - - it 'contains a Schema disallowing setting scope' do - expect(subject.body) - .to be_json_eql("Schema".to_json) - .at_path('_embedded/schema/_type') - - expect(subject.body) - .to be_json_eql(false.to_json) - .at_path('_embedded/schema/scope/writable') - end - - it 'contains the current data in the payload' do - expected = { - rowCount: 7, - columnCount: 4, - options: {}, - widgets: [ - { - "_type": "GridWidget", - identifier: 'work_packages_assigned', - options: {}, - startRow: 1, - endRow: 7, - startColumn: 1, - endColumn: 3 - }, - { - "_type": "GridWidget", - identifier: 'work_packages_created', - options: {}, - startRow: 1, - endRow: 7, - startColumn: 3, - endColumn: 5 - } - ], - "_links": { - "scope": { - "href": "/my/page", - "type": "text/html" - } - } - } - - expect(subject.body) - .to be_json_eql(expected.to_json) - .at_path('_embedded/payload') - end - - it 'has a commit link' do - expect(subject.body) - .to be_json_eql(api_v3_paths.grid(grid.id).to_json) - .at_path('_links/commit/href') - end - - context 'with some value for the scope value' do - let(:params) do - { - '_links': { - 'scope': { - 'href': '/some/path' - } - } - } - end - - it 'has a validation error on scope as the value is not writeable' do - expect(subject.body) - .to be_json_eql("You must not write a read-only attribute.".to_json) - .at_path('_embedded/validationErrors/scope/message') - end - end - - context 'with an unsupported widget identifier' do - let(:params) do - { - "widgets": [ - { - "_type": "GridWidget", - "identifier": "bogus_identifier", - "startRow": 4, - "endRow": 5, - "startColumn": 1, - "endColumn": 2 - } - ] - } - end - - it 'has a validationError on widget' do - expect(subject.body) - .to be_json_eql("Widgets is not set to one of the allowed values.".to_json) - .at_path('_embedded/validationErrors/widgets/message') - end - end - context 'for a non existing grid' do - let(:path) { api_v3_paths.grid_form(grid.id + 5) } - - it 'returns 404 NOT FOUND' do - expect(subject.status) - .to eql 404 - end - end - - context 'for another user\'s grid' do - let(:other_user) { FactoryBot.create(:user) } - let(:other_grid) { FactoryBot.create(:my_page, user: other_user) } - - let(:path) { api_v3_paths.grid_form(other_grid.id) } + let(:path) { api_v3_paths.grid_form(5) } it 'returns 404 NOT FOUND' do expect(subject.status) diff --git a/modules/grids/spec/services/grids/create_service_spec.rb b/modules/grids/spec/services/grids/create_service_spec.rb index 8b25eac30ec..a052bfb6fd5 100644 --- a/modules/grids/spec/services/grids/create_service_spec.rb +++ b/modules/grids/spec/services/grids/create_service_spec.rb @@ -50,7 +50,9 @@ describe Grids::CreateService, type: :model do end let(:scope) { "some/scope/url" } let(:call_attributes) { { scope: scope } } - let(:grid_class) { Grids::MyPage } + let(:grid_class) do + Grids::Grid + end let(:set_attributes_success) do true end diff --git a/modules/grids/spec/services/grids/set_attributes_service_spec.rb b/modules/grids/spec/services/grids/set_attributes_service_spec.rb index d420c299587..12edfcb0d17 100644 --- a/modules/grids/spec/services/grids/set_attributes_service_spec.rb +++ b/modules/grids/spec/services/grids/set_attributes_service_spec.rb @@ -56,7 +56,7 @@ describe Grids::SetAttributesService, type: :model do contract_class: contract_class) end let(:call_attributes) { {} } - let(:grid_class) { Grids::MyPage } + let(:grid_class) { Grids::Grid } let(:grid) do FactoryBot.build_stubbed(grid_class.name.demodulize.underscore.to_sym, widgets: []) end diff --git a/modules/grids/spec/services/grids/update_service_spec.rb b/modules/grids/spec/services/grids/update_service_spec.rb index 84e3319d121..f6a1df81b41 100644 --- a/modules/grids/spec/services/grids/update_service_spec.rb +++ b/modules/grids/spec/services/grids/update_service_spec.rb @@ -42,7 +42,7 @@ describe Grids::UpdateService, type: :model do contract_class: contract_class) end let(:call_attributes) { {} } - let(:grid_class) { Grids::MyPage } + let(:grid_class) { Grids::Grid } let(:set_attributes_success) do true end diff --git a/modules/my_page/.gitignore b/modules/my_page/.gitignore new file mode 100644 index 00000000000..71f82cb25d0 --- /dev/null +++ b/modules/my_page/.gitignore @@ -0,0 +1,7 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/db/*.sqlite3-journal +test/dummy/log/*.log +test/dummy/tmp/ diff --git a/modules/my_page/Gemfile b/modules/my_page/Gemfile new file mode 100644 index 00000000000..fa75df15632 --- /dev/null +++ b/modules/my_page/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gemspec diff --git a/modules/grids/app/models/grids/my_page.rb b/modules/my_page/app/models/grids/my_page.rb similarity index 100% rename from modules/grids/app/models/grids/my_page.rb rename to modules/my_page/app/models/grids/my_page.rb diff --git a/modules/my_page/lib/my_page.rb b/modules/my_page/lib/my_page.rb new file mode 100644 index 00000000000..ab49814387d --- /dev/null +++ b/modules/my_page/lib/my_page.rb @@ -0,0 +1,4 @@ +require "my_page/engine" + +module MyPage +end diff --git a/modules/my_page/lib/my_page/engine.rb b/modules/my_page/lib/my_page/engine.rb new file mode 100644 index 00000000000..83a02d3b222 --- /dev/null +++ b/modules/my_page/lib/my_page/engine.rb @@ -0,0 +1,11 @@ +module MyPage + class Engine < ::Rails::Engine + isolate_namespace MyPage + + include OpenProject::Plugins::ActsAsOpEngine + + config.to_prepare do + MyPage::GridRegistration.register! + end + end +end diff --git a/modules/grids/lib/grids/my_page_grid_registration.rb b/modules/my_page/lib/my_page/grid_registration.rb similarity index 93% rename from modules/grids/lib/grids/my_page_grid_registration.rb rename to modules/my_page/lib/my_page/grid_registration.rb index 56f9c7c791c..3f9290e77a8 100644 --- a/modules/grids/lib/grids/my_page_grid_registration.rb +++ b/modules/my_page/lib/my_page/grid_registration.rb @@ -1,5 +1,5 @@ -module Grids - class MyPageGridRegistration < ::Grids::Configuration::Registration +module MyPage + class GridRegistration < ::Grids::Configuration::Registration grid_class 'Grids::MyPage' to_scope :my_page_path diff --git a/modules/my_page/my_page.gemspec b/modules/my_page/my_page.gemspec new file mode 100644 index 00000000000..b550f341b9d --- /dev/null +++ b/modules/my_page/my_page.gemspec @@ -0,0 +1,12 @@ +# encoding: UTF-8 + +Gem::Specification.new do |s| + s.name = "my_page" + s.version = '1.0.0' + s.authors = ["OpenProject"] + s.summary = "OpenProject MyPage." + + s.files = Dir["{app,config,db,lib}/**/*"] + + s.add_dependency 'grids' +end diff --git a/modules/my_page/spec/contracts/grids/create_contract_spec.rb b/modules/my_page/spec/contracts/grids/create_contract_spec.rb new file mode 100644 index 00000000000..41d9a31e138 --- /dev/null +++ b/modules/my_page/spec/contracts/grids/create_contract_spec.rb @@ -0,0 +1,61 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' +require_relative './shared_examples' + +describe Grids::CreateContract do + include_context 'grid contract' + include_context 'model contract' + + it_behaves_like 'shared grid contract attributes' + + describe 'user_id' do + context 'for a Grids::MyPage' do + let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) } + + it_behaves_like 'is writable' do + let(:attribute) { :user_id } + let(:value) { 5 } + end + end + end + + describe 'project_id' do + context 'for a Grids::MyPage' do + let(:grid) { FactoryBot.build_stubbed(:my_page, default_values) } + + it_behaves_like 'is not writable' do + let(:attribute) { :project_id } + let(:value) { 5 } + end + end + end +end diff --git a/modules/my_page/spec/contracts/grids/shared_examples.rb b/modules/my_page/spec/contracts/grids/shared_examples.rb new file mode 100644 index 00000000000..441637b37f9 --- /dev/null +++ b/modules/my_page/spec/contracts/grids/shared_examples.rb @@ -0,0 +1,320 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +shared_context 'grid contract' do + let(:user) { FactoryBot.build_stubbed(:user) } + let(:instance) { described_class.new(grid, user) } + let(:default_values) do + { + row_count: 6, + column_count: 7, + widgets: [] + } + end + let(:grid) do + FactoryBot.build_stubbed(:my_page, default_values) + end +end + +shared_examples_for 'shared grid contract attributes' do + include_context 'model contract' + let(:model) { grid } + + describe 'widgets' do + it_behaves_like 'is writable' do + let(:attribute) { :widgets } + let(:value) do + [ + Grids::Widget.new(start_row: 1, + end_row: 4, + start_column: 2, + end_column: 5, + identifier: 'work_packages_assigned') + ] + end + end + + context 'invalid identifier' do + before do + grid.widgets.build(start_row: 1, + end_row: 4, + start_column: 2, + end_column: 5, + identifier: 'bogus_identifier') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :inclusion }] + end + end + + context 'collisions between widgets' do + before do + grid.widgets.build(start_row: 1, + end_row: 3, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + grid.widgets.build(start_row: 2, + end_row: 4, + start_column: 2, + end_column: 4, + identifier: 'work_packages_created') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :overlaps }, { error: :overlaps }] + end + end + + context 'widgets having the same start column as another\'s end column' do + before do + grid.widgets.build(start_row: 1, + end_row: 3, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + grid.widgets.build(start_row: 1, + end_row: 3, + start_column: 3, + end_column: 4, + identifier: 'work_packages_created') + end + + it 'is valid' do + expect(instance.validate) + .to be_truthy + end + end + + context 'widgets having the same start row as another\'s end row' do + before do + grid.widgets.build(start_row: 1, + end_row: 3, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + grid.widgets.build(start_row: 3, + end_row: 4, + start_column: 1, + end_column: 3, + identifier: 'work_packages_created') + end + + it 'is valid' do + expect(instance.validate) + .to be_truthy + end + end + + context 'widgets being outside (max) of the grid' do + before do + grid.widgets.build(start_row: 1, + end_row: grid.row_count + 2, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :outside }] + end + end + + context 'widgets being outside (min) of the grid' do + before do + grid.widgets.build(start_row: 1, + end_row: 2, + start_column: -1, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :outside }] + end + end + + context 'widgets spanning the whole grid' do + before do + grid.widgets.build(start_row: 1, + end_row: grid.row_count + 1, + start_column: 1, + end_column: grid.column_count + 1, + identifier: 'work_packages_assigned') + end + + it 'is valid' do + expect(instance.validate) + .to be_truthy + end + end + + context 'widgets having start after end column' do + before do + grid.widgets.build(start_row: 1, + end_row: 2, + start_column: 4, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :end_before_start }] + end + end + + context 'widgets having start after end row' do + before do + grid.widgets.build(start_row: 4, + end_row: 2, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :end_before_start }] + end + end + + context 'widgets having start equals end column' do + before do + grid.widgets.build(start_row: 1, + end_row: 2, + start_column: 4, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :end_before_start }] + end + end + + context 'widgets having start equals end row' do + before do + grid.widgets.build(start_row: 2, + end_row: 2, + start_column: 1, + end_column: 3, + identifier: 'work_packages_assigned') + end + + it 'is invalid' do + expect(instance.validate) + .to be_falsey + end + + it 'notes the error' do + instance.validate + expect(instance.errors.details[:widgets]) + .to match_array [{ error: :end_before_start }] + end + end + end + + describe 'valid grid subclasses' do + context 'for a registered subclass' do + let(:grid) do + FactoryBot.build_stubbed(:my_page, default_values) + end + + it 'is valid' do + expect(instance.validate) + .to be_truthy + end + end + + context 'for the Grid superclass itself' do + let(:grid) do + FactoryBot.build_stubbed(:grid, default_values) + end + + before do + instance.validate + end + + it 'is invalid for the grid superclass itself' do + expect(instance.errors.details[:scope]) + .to match_array [{ error: :inclusion }] + end + end + end +end diff --git a/modules/my_page/spec/contracts/grids/update_contract_spec.rb b/modules/my_page/spec/contracts/grids/update_contract_spec.rb new file mode 100644 index 00000000000..65cded75c09 --- /dev/null +++ b/modules/my_page/spec/contracts/grids/update_contract_spec.rb @@ -0,0 +1,39 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See doc/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' +require_relative './shared_examples' + +describe Grids::UpdateContract do + include_context 'model contract' + include_context 'grid contract' + + it_behaves_like 'shared grid contract attributes' +end diff --git a/modules/my_page/spec/factories/grid_factory.rb b/modules/my_page/spec/factories/grid_factory.rb new file mode 100644 index 00000000000..d56d1e6c381 --- /dev/null +++ b/modules/my_page/spec/factories/grid_factory.rb @@ -0,0 +1,25 @@ +FactoryBot.define do + factory :my_page, class: Grids::MyPage do + user + row_count { 7 } + column_count { 4 } + widgets do + [ + Grids::Widget.new( + identifier: 'work_packages_assigned', + start_row: 1, + end_row: 7, + start_column: 1, + end_column: 3 + ), + Grids::Widget.new( + identifier: 'work_packages_created', + start_row: 1, + end_row: 7, + start_column: 3, + end_column: 5 + ) + ] + end + end +end diff --git a/modules/grids/spec/features/my/my_page_accountable_spec.rb b/modules/my_page/spec/features/my/accountable_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_accountable_spec.rb rename to modules/my_page/spec/features/my/accountable_spec.rb diff --git a/modules/grids/spec/features/my/my_page_assigned_to_me_spec.rb b/modules/my_page/spec/features/my/assigned_to_me_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_assigned_to_me_spec.rb rename to modules/my_page/spec/features/my/assigned_to_me_spec.rb diff --git a/modules/grids/spec/features/my/my_page_documents_spec.rb b/modules/my_page/spec/features/my/documents_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_documents_spec.rb rename to modules/my_page/spec/features/my/documents_spec.rb diff --git a/modules/grids/spec/features/my/my_page_spec.rb b/modules/my_page/spec/features/my/my_page_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_spec.rb rename to modules/my_page/spec/features/my/my_page_spec.rb diff --git a/modules/grids/spec/features/my/my_page_news_spec.rb b/modules/my_page/spec/features/my/news_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_news_spec.rb rename to modules/my_page/spec/features/my/news_spec.rb diff --git a/modules/grids/spec/features/my/my_page_time_entries_current_user_spec.rb b/modules/my_page/spec/features/my/time_entries_current_user_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_time_entries_current_user_spec.rb rename to modules/my_page/spec/features/my/time_entries_current_user_spec.rb diff --git a/modules/grids/spec/features/my/my_page_work_package_table_spec.rb b/modules/my_page/spec/features/my/work_package_table_spec.rb similarity index 100% rename from modules/grids/spec/features/my/my_page_work_package_table_spec.rb rename to modules/my_page/spec/features/my/work_package_table_spec.rb diff --git a/modules/grids/spec/models/grids/my_page_spec.rb b/modules/my_page/spec/models/grids/my_page_spec.rb similarity index 100% rename from modules/grids/spec/models/grids/my_page_spec.rb rename to modules/my_page/spec/models/grids/my_page_spec.rb diff --git a/modules/my_page/spec/models/grids/shared_model.rb b/modules/my_page/spec/models/grids/shared_model.rb new file mode 100644 index 00000000000..3e453343020 --- /dev/null +++ b/modules/my_page/spec/models/grids/shared_model.rb @@ -0,0 +1,77 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +shared_examples_for 'grid attributes' do + describe 'attributes' do + it '#row_count' do + instance.row_count = 5 + expect(instance.row_count) + .to eql 5 + end + + it '#column_count' do + instance.column_count = 5 + expect(instance.column_count) + .to eql 5 + end + + it '#name' do + instance.name = 'custom 123' + expect(instance.name) + .to eql 'custom 123' + + # can be empty + instance.name = nil + expect(instance).to be_valid + end + + it '#options' do + value = { + some: 'value', + and: { + also: 1 + } + } + + instance.options = value + expect(instance.options) + .to eql value + end + + it '#widgets' do + widgets = [ + Grids::Widget.new(start_row: 2), + Grids::Widget.new(start_row: 5) + ] + + instance.widgets = widgets + expect(instance.widgets) + .to match_array widgets + end + end +end diff --git a/modules/grids/spec/queries/grids/filters/scope_filter_spec.rb b/modules/my_page/spec/queries/grids/filters/scope_filter_spec.rb similarity index 100% rename from modules/grids/spec/queries/grids/filters/scope_filter_spec.rb rename to modules/my_page/spec/queries/grids/filters/scope_filter_spec.rb diff --git a/modules/grids/spec/queries/grids/query_integration_spec.rb b/modules/my_page/spec/queries/grids/query_integration_spec.rb similarity index 100% rename from modules/grids/spec/queries/grids/query_integration_spec.rb rename to modules/my_page/spec/queries/grids/query_integration_spec.rb diff --git a/modules/my_page/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb b/modules/my_page/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb new file mode 100644 index 00000000000..3b8f97ed525 --- /dev/null +++ b/modules/my_page/spec/requests/api/v3/grids/grids_create_form_resource_spec.rb @@ -0,0 +1,193 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' +require 'rack/test' + +describe "POST /api/v3/grids/form", type: :request, content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:current_user) do + FactoryBot.create(:user) + end + + let(:path) { api_v3_paths.create_grid_form } + let(:params) { {} } + subject(:response) { last_response } + + before do + login_as(current_user) + end + + describe '#post' do + before do + post path, params.to_json, 'CONTENT_TYPE' => 'application/json' + end + + it 'contains a Schema embedding the available values' do + expect(subject.body) + .to be_json_eql("Schema".to_json) + .at_path('_embedded/schema/_type') + + expect(subject.body) + .to be_json_eql(my_page_path.to_json) + .at_path('_embedded/schema/scope/_links/allowedValues/0/href') + end + + context 'with /my/page for the scope value' do + let(:params) do + { + '_links': { + 'scope': { + 'href': my_page_path + } + } + } + end + + it 'contains default data in the payload' do + expected = { + "rowCount": 7, + "columnCount": 4, + "options": {}, + "widgets": [ + { + "_type": "GridWidget", + identifier: 'work_packages_assigned', + "options": {}, + startRow: 1, + endRow: 7, + startColumn: 1, + endColumn: 3 + }, + { + "_type": "GridWidget", + identifier: 'work_packages_created', + "options": {}, + startRow: 1, + endRow: 7, + startColumn: 3, + endColumn: 5 + } + ], + "_links": { + "scope": { + "href": "/my/page", + "type": "text/html" + } + } + } + + expect(subject.body) + .to be_json_eql(expected.to_json) + .at_path('_embedded/payload') + end + + it 'has no validationErrors' do + expect(subject.body) + .to be_json_eql({}.to_json) + .at_path('_embedded/validationErrors') + end + + it 'has a commit link' do + expect(subject.body) + .to be_json_eql(api_v3_paths.grids.to_json) + .at_path('_links/commit/href') + end + end + + context 'with an unsupported widget identifier' do + let(:params) do + { + '_links': { + 'scope': { + 'href': my_page_path + } + }, + "widgets": [ + { + "_type": "GridWidget", + "identifier": "bogus_identifier", + "startRow": 4, + "endRow": 5, + "startColumn": 1, + "endColumn": 2 + } + ] + } + end + + it 'has a validationError on widget' do + expect(subject.body) + .to be_json_eql("Widgets is not set to one of the allowed values.".to_json) + .at_path('_embedded/validationErrors/widgets/message') + end + end + + context 'with name set' do + let(:params) do + { + name: 'My custom grid 1', + '_links': { + 'scope': { + 'href': my_page_path + } + } + } + end + + it 'feeds it back' do + expect(subject.body) + .to be_json_eql("My custom grid 1".to_json) + .at_path('_embedded/payload/name') + end + end + + context 'with options set' do + let(:params) do + { + options: { + foo: 'bar' + }, + '_links': { + 'scope': { + 'href': my_page_path + } + } + } + end + + it 'feeds them back' do + expect(subject.body) + .to be_json_eql("bar".to_json) + .at_path('_embedded/payload/options/foo') + end + end + end +end diff --git a/modules/my_page/spec/requests/api/v3/grids/grids_resource_spec.rb b/modules/my_page/spec/requests/api/v3/grids/grids_resource_spec.rb new file mode 100644 index 00000000000..7a43a68209a --- /dev/null +++ b/modules/my_page/spec/requests/api/v3/grids/grids_resource_spec.rb @@ -0,0 +1,414 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' +require 'rack/test' + +describe 'API v3 Grids resource', type: :request, content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:current_user) do + FactoryBot.create(:user) + end + + let(:my_page_grid) { FactoryBot.create(:my_page, user: current_user) } + let(:other_user) do + FactoryBot.create(:user) + end + let(:other_my_page_grid) { FactoryBot.create(:my_page, user: other_user) } + + before do + login_as(current_user) + end + + subject(:response) { last_response } + + describe '#get INDEX' do + let(:path) { api_v3_paths.grids } + + let(:stored_grids) do + my_page_grid + other_my_page_grid + end + + before do + stored_grids + + get path + end + + it 'sends a collection of grids but only those visible to the current user' do + expect(subject.body) + .to be_json_eql('Collection'.to_json) + .at_path('_type') + + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_embedded/elements/0/_type') + + expect(subject.body) + .to be_json_eql(1.to_json) + .at_path('total') + end + + context 'with a filter on the scope attribute' do + shared_let(:other_grid) do + grid = Grids::Grid.new(row_count: 20, + column_count: 20) + grid.save + + Grids::Grid + .where(id: grid.id) + .update_all(user_id: current_user.id) + + grid + end + + let(:stored_grids) do + my_page_grid + other_my_page_grid + other_grid + end + + let(:path) do + filter = [{ 'scope' => + { + 'operator' => '=', + 'values' => [my_page_path] + } }] + + "#{api_v3_paths.grids}?#{{ filters: filter.to_json }.to_query}" + end + + it 'responds with 200 OK' do + expect(subject.status).to eq(200) + end + + it 'sends only the my page of the current user' do + expect(subject.body) + .to be_json_eql('Collection'.to_json) + .at_path('_type') + + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_embedded/elements/0/_type') + + expect(subject.body) + .to be_json_eql(1.to_json) + .at_path('total') + end + end + end + + describe '#get' do + let(:path) { api_v3_paths.grid(my_page_grid.id) } + + let(:stored_grids) do + my_page_grid + end + + before do + stored_grids + + get path + end + + it 'responds with 200 OK' do + expect(subject.status).to eq(200) + end + + it 'sends a grid block' do + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_type') + end + + it 'identifies the url the grid is stored for' do + expect(subject.body) + .to be_json_eql(my_page_path.to_json) + .at_path('_links/scope/href') + end + + context 'with the page not existing' do + let(:path) { api_v3_paths.grid(5) } + + it 'responds with 404 NOT FOUND' do + expect(subject.status).to eql 404 + end + end + + context 'with the grid belonging to someone else' do + let(:stored_grids) do + my_page_grid + other_my_page_grid + end + + let(:path) { api_v3_paths.grid(other_my_page_grid.id) } + + it 'responds with 404 NOT FOUND' do + expect(subject.status).to eql 404 + end + end + end + + describe '#patch' do + let(:path) { api_v3_paths.grid(my_page_grid.id) } + + let(:params) do + { + "rowCount": 10, + "name": 'foo', + "columnCount": 15, + "widgets": [{ + "identifier": "work_packages_assigned", + "startRow": 4, + "endRow": 8, + "startColumn": 2, + "endColumn": 5 + }] + }.with_indifferent_access + end + + let(:stored_grids) do + my_page_grid + end + + before do + stored_grids + + patch path, params.to_json, 'CONTENT_TYPE' => 'application/json' + end + + it 'responds with 200 OK' do + expect(subject.status).to eq(200) + end + + it 'returns the altered grid block' do + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_type') + expect(subject.body) + .to be_json_eql('foo'.to_json) + .at_path('name') + expect(subject.body) + .to be_json_eql(params['rowCount'].to_json) + .at_path('rowCount') + expect(subject.body) + .to be_json_eql(params['widgets'][0]['identifier'].to_json) + .at_path('widgets/0/identifier') + end + + it 'perists the changes' do + expect(my_page_grid.reload.row_count) + .to eql params['rowCount'] + end + + context 'with invalid params' do + let(:params) do + { + "rowCount": -5, + "columnCount": 15, + "widgets": [{ + "identifier": "work_packages_assigned", + "startRow": 4, + "endRow": 8, + "startColumn": 2, + "endColumn": 5 + }] + }.with_indifferent_access + end + + it 'responds with 422 and mentions the error' do + expect(subject.status).to eq 422 + + expect(subject.body) + .to be_json_eql('Error'.to_json) + .at_path('_type') + + expect(subject.body) + .to be_json_eql("Widgets is outside of the grid.".to_json) + .at_path('_embedded/errors/0/message') + + expect(subject.body) + .to be_json_eql("Number of rows must be greater than 0.".to_json) + .at_path('_embedded/errors/1/message') + end + + it 'does not persist the changes to widgets' do + expect(my_page_grid.reload.widgets.count) + .to eql MyPage::GridRegistration.defaults[:widgets].size + end + end + + context 'with a scope param' do + let(:params) do + { + "_links": { + "scope": { + "href": '' + } + } + }.with_indifferent_access + end + + it 'responds with 422 and mentions the error' do + expect(subject.status).to eq 422 + + expect(subject.body) + .to be_json_eql('Error'.to_json) + .at_path('_type') + + expect(subject.body) + .to be_json_eql("You must not write a read-only attribute.".to_json) + .at_path('message') + + expect(subject.body) + .to be_json_eql("scope".to_json) + .at_path('_embedded/details/attribute') + end + end + + context 'with the page not existing' do + let(:path) { api_v3_paths.grid(5) } + + it 'responds with 404 NOT FOUND' do + expect(subject.status).to eql 404 + end + end + + context 'with the grid belonging to someone else' do + let(:stored_grids) do + my_page_grid + other_my_page_grid + end + + let(:path) { api_v3_paths.grid(other_my_page_grid.id) } + + it 'responds with 404 NOT FOUND' do + expect(subject.status).to eql 404 + end + end + end + + describe '#post' do + let(:path) { api_v3_paths.grids } + + let(:params) do + { + "rowCount": 10, + "columnCount": 15, + "widgets": [{ + "identifier": "work_packages_assigned", + "startRow": 4, + "endRow": 8, + "startColumn": 2, + "endColumn": 5 + }], + "_links": { + "scope": { + "href": my_page_path + } + } + }.with_indifferent_access + end + + before do + post path, params.to_json, 'CONTENT_TYPE' => 'application/json' + end + + it 'responds with 201 CREATED' do + expect(subject.status).to eq(201) + end + + it 'returns the created grid block' do + expect(subject.body) + .to be_json_eql('Grid'.to_json) + .at_path('_type') + expect(subject.body) + .to be_json_eql(params['rowCount'].to_json) + .at_path('rowCount') + expect(subject.body) + .to be_json_eql(params['widgets'][0]['identifier'].to_json) + .at_path('widgets/0/identifier') + end + + it 'persists the grid' do + expect(Grids::Grid.count) + .to eql(1) + end + + context 'with invalid params' do + let(:params) do + { + "rowCount": -5, + "columnCount": "sdjfksdfsdfdsf", + "widgets": [{ + "identifier": "work_packages_assigned", + "startRow": 4, + "endRow": 8, + "startColumn": 2, + "endColumn": 5 + }], + "_links": { + "scope": { + "href": my_page_path + } + } + }.with_indifferent_access + end + + it 'responds with 422' do + expect(subject.status).to eq(422) + end + + it 'does not create a grid' do + expect(Grids::Grid.count) + .to eql(0) + end + + it 'returns the errors' do + expect(subject.body) + .to be_json_eql('Error'.to_json) + .at_path('_type') + + expect(subject.body) + .to be_json_eql("Widgets is outside of the grid.".to_json) + .at_path('_embedded/errors/0/message') + + expect(subject.body) + .to be_json_eql("Number of rows must be greater than 0.".to_json) + .at_path('_embedded/errors/1/message') + + expect(subject.body) + .to be_json_eql("Number of columns must be greater than 0.".to_json) + .at_path('_embedded/errors/2/message') + end + end + end +end diff --git a/modules/my_page/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb b/modules/my_page/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb new file mode 100644 index 00000000000..fdca625f0d7 --- /dev/null +++ b/modules/my_page/spec/requests/api/v3/grids/grids_update_form_resource_spec.rb @@ -0,0 +1,174 @@ +#-- copyright +# OpenProject is a project management system. +# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2017 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' +require 'rack/test' + +describe "PATCH /api/v3/grids/:id/form", type: :request, content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:current_user) do + FactoryBot.create(:user) + end + + let(:grid) do + FactoryBot.create(:my_page, user: current_user) + end + let(:path) { api_v3_paths.grid_form(grid.id) } + let(:params) { {} } + subject(:response) { last_response } + + before do + login_as(current_user) + end + + describe '#post' do + before do + post path, params.to_json, 'CONTENT_TYPE' => 'application/json' + end + + it 'returns 200 OK' do + expect(subject.status) + .to eql 200 + end + + it 'is of type form' do + expect(subject.body) + .to be_json_eql("Form".to_json) + .at_path('_type') + end + + it 'contains a Schema disallowing setting scope' do + expect(subject.body) + .to be_json_eql("Schema".to_json) + .at_path('_embedded/schema/_type') + + expect(subject.body) + .to be_json_eql(false.to_json) + .at_path('_embedded/schema/scope/writable') + end + + it 'contains the current data in the payload' do + expected = { + rowCount: 7, + columnCount: 4, + options: {}, + widgets: [ + { + "_type": "GridWidget", + identifier: 'work_packages_assigned', + options: {}, + startRow: 1, + endRow: 7, + startColumn: 1, + endColumn: 3 + }, + { + "_type": "GridWidget", + identifier: 'work_packages_created', + options: {}, + startRow: 1, + endRow: 7, + startColumn: 3, + endColumn: 5 + } + ], + "_links": { + "scope": { + "href": "/my/page", + "type": "text/html" + } + } + } + + expect(subject.body) + .to be_json_eql(expected.to_json) + .at_path('_embedded/payload') + end + + it 'has a commit link' do + expect(subject.body) + .to be_json_eql(api_v3_paths.grid(grid.id).to_json) + .at_path('_links/commit/href') + end + + context 'with some value for the scope value' do + let(:params) do + { + '_links': { + 'scope': { + 'href': '/some/path' + } + } + } + end + + it 'has a validation error on scope as the value is not writeable' do + expect(subject.body) + .to be_json_eql("You must not write a read-only attribute.".to_json) + .at_path('_embedded/validationErrors/scope/message') + end + end + + context 'with an unsupported widget identifier' do + let(:params) do + { + "widgets": [ + { + "_type": "GridWidget", + "identifier": "bogus_identifier", + "startRow": 4, + "endRow": 5, + "startColumn": 1, + "endColumn": 2 + } + ] + } + end + + it 'has a validationError on widget' do + expect(subject.body) + .to be_json_eql("Widgets is not set to one of the allowed values.".to_json) + .at_path('_embedded/validationErrors/widgets/message') + end + end + + context 'for another user\'s grid' do + let(:other_user) { FactoryBot.create(:user) } + let(:other_grid) { FactoryBot.create(:my_page, user: other_user) } + + let(:path) { api_v3_paths.grid_form(other_grid.id) } + + it 'returns 404 NOT FOUND' do + expect(subject.status) + .to eql 404 + end + end + end +end