From c818509bb08dcd5f9bac35523fca425fdeb2672e Mon Sep 17 00:00:00 2001 From: loiswells97 Date: Fri, 25 Nov 2022 10:36:40 +0000 Subject: [PATCH 1/6] Create draft PR for #89 From 08a3888846e35eeae5d94d4db3c2293ac2b1afaf Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Fri, 25 Nov 2022 10:40:01 +0000 Subject: [PATCH 2/6] Update ability to prevent link sharing unless starter or example project --- app/models/ability.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 1e7745e14..fdac1845a 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -4,10 +4,10 @@ class Ability include CanCan::Ability def initialize(user) - can :show, Project + can :show, Project, user_id: nil return if user.blank? - can %i[create index destroy update], Project, user_id: user + can %i[create show index destroy update], Project, user_id: user end end From 356c7372ef3c5d5f80b4c489e81e6f406ea676c4 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 1 Dec 2022 09:43:59 +0000 Subject: [PATCH 3/6] some fixes --- Gemfile.lock | 17 +++++++++-------- lib/concepts/project/operations/create.rb | 3 +-- tmp/pids/.keep | 0 tmp/storage/.keep | 0 4 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 tmp/pids/.keep delete mode 100644 tmp/storage/.keep diff --git a/Gemfile.lock b/Gemfile.lock index b3391192e..b4437320b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -70,7 +70,7 @@ GEM public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.664.0) + aws-partitions (1.666.0) aws-sdk-core (3.168.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) @@ -85,7 +85,7 @@ GEM aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) aws-eventstream (~> 1, >= 1.0.2) - bootsnap (1.14.0) + bootsnap (1.15.0) msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) @@ -124,7 +124,7 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) - jmespath (1.6.1) + jmespath (1.6.2) json (2.6.2) loofah (2.19.0) crass (~> 1.0.2) @@ -145,10 +145,10 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) - nokogiri (1.13.9-x86_64-linux) + nokogiri (1.13.9-aarch64-linux) racc (~> 1.4) parallel (1.22.1) - parser (3.1.2.1) + parser (3.1.3.0) ast (~> 2.4.1) pg (1.4.5) pry (0.14.1) @@ -259,7 +259,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - stimulus-rails (1.1.1) + stimulus-rails (1.2.0) railties (>= 6.0.0) thor (1.2.1) timeout (0.3.0) @@ -280,6 +280,7 @@ GEM zeitwerk (2.6.6) PLATFORMS + aarch64-linux x86_64-linux DEPENDENCIES @@ -312,7 +313,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.1.2p20 + ruby 3.1.3p185 BUNDLED WITH - 2.3.7 + 2.3.26 diff --git a/lib/concepts/project/operations/create.rb b/lib/concepts/project/operations/create.rb index 6ad3d9be1..25b3dd11e 100644 --- a/lib/concepts/project/operations/create.rb +++ b/lib/concepts/project/operations/create.rb @@ -5,7 +5,6 @@ class Create class << self def call(project_hash:) response = OperationResponse.new - response[:project] = build_project(project_hash) response[:project].save! response @@ -22,7 +21,7 @@ def build_project(project_hash) new_project = Project.new(project_hash.except(:components, :image_list).merge(identifier:)) new_project.components.build(project_hash[:components]) - project_hash[:image_list].each do |image| + (project_hash[:image_list] || []).each do |image| new_project.images.attach(image.blob) end diff --git a/tmp/pids/.keep b/tmp/pids/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tmp/storage/.keep b/tmp/storage/.keep deleted file mode 100644 index e69de29bb..000000000 From 201efff00491c7e85ee9fe8f160e0ba4bac08b7a Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 1 Dec 2022 11:51:57 +0000 Subject: [PATCH 4/6] adding project show tests for new abilities --- .byebug_history | 120 +++++++++++++++++++++++++++++ Gemfile.lock | 2 + openAPI.yml | 44 +++++++++++ spec/request/projects/show_spec.rb | 108 ++++++++++++++++++++++---- 4 files changed, 259 insertions(+), 15 deletions(-) create mode 100644 .byebug_history create mode 100644 openAPI.yml diff --git a/.byebug_history b/.byebug_history new file mode 100644 index 000000000..9e90293df --- /dev/null +++ b/.byebug_history @@ -0,0 +1,120 @@ +c +project_hash +exit +pp project_hash +pp e +n +s +q +params.deep_symbolize_keys +params.to_h.deep_symbolize_keys +params.to_h.class +params.to_h.calss +params.to_h.deep_transform_keys(&:to_sym) +params.to_h +params +q +continue +q +continue +pp params +continue +params +continue +pp params +n +q +project +n +s +q +x.deep_transform_keys(&:to_sym) +x={"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]} +x.deep_transform_keys(&:to_sym) +x +x = params.to_h.dup +params.to_h +params +hashed_params.deep_transform_keys!(&:to_sym) +hashed_params.deep_transform_keys(&:to_sym) +hashed_params +hashed_params = params.to_h +params.to_h.dup.deep_transform_keys(&:to_sym) +params.to_h +params.to_h.deep_transform_keys(&:to_sym) +{"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]}.deep_transform_keys(&:to_sym) +{"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]}.deep_transform_params(&:to_sym) +dupped_params.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +dupped_params = params.dup +dupped_params_hash.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +dupped_params_hash = params_hash.dup +params_hash.dup.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +params_hash.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +params_hash = params.to_h +params +project +n +s +q +params.dup.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +params +project +n +q +params.to_h.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +dupped_params.to_h.deep_transform_keys do |key| + key.to_sym + rescue StandardError + key + end +dupped_params.deep_transform_keys do |key| +dupped_params = params.to_h.dup +params +params.to_h +params.deep_symbolize_keys +q +continue +params.to_h! +params +params.keys.each do |key| + params[(key.to_sym rescue key) || key] = params.delete(key) +end +require 'rails' +params.hash_with_indifferent_access +require 'rails' +params.to_h.deep_transform_keys(&:to_sym) +params.to_h.deep_transform_keys!(&:to_sym) +params.to_h.deep_transform_keys(&:to_sym) +params.to_h +params +c +s +params +continue +params +continue +params diff --git a/Gemfile.lock b/Gemfile.lock index b4437320b..974bb5ecd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -147,6 +147,8 @@ GEM nio4r (2.5.8) nokogiri (1.13.9-aarch64-linux) racc (~> 1.4) + nokogiri (1.13.9-arm64-darwin) + racc (~> 1.4) parallel (1.22.1) parser (3.1.3.0) ast (~> 2.4.1) diff --git a/openAPI.yml b/openAPI.yml new file mode 100644 index 000000000..4d963d33f --- /dev/null +++ b/openAPI.yml @@ -0,0 +1,44 @@ +openapi: 3.0.0 +info: + title: Raspberry Pi Code Editor API + description: API that serves the Rasperry Pi Code Editor at https://editor.raspberrypi.org + version: 0.1.0 + +servers: + - url: https://editor-api.raspberrypi.org/api + description: Production server + - url: https://staging-editor-api.raspberrypi.org/api + description: Staging server + +paths: + /projects/{identifier}: + get: + summary: Returns the project with the given identifier + responses: + '200': + description: A project object + content: + application/json: + schema: + type: object + properties: + identifier: + type: string + example: python-emoji-example + project_type: + type: string + example: python + name: + type: string + example: Hello World + user_id: + type: string + format: uuid + example: 3e1f2179-261a-4ebe-92b5-2c4b5c93193b + components: + type: array + image_list: + type: array + '404': + description: A project with the specified identifier was not found. + diff --git a/spec/request/projects/show_spec.rb b/spec/request/projects/show_spec.rb index ebdf9c4aa..adb536df0 100644 --- a/spec/request/projects/show_spec.rb +++ b/spec/request/projects/show_spec.rb @@ -4,7 +4,7 @@ RSpec.describe 'Project show requests', type: :request do let!(:project) { create(:project) } - let(:expected_json) do + let(:project_json) do { identifier: project.identifier, project_type: 'python', @@ -15,25 +15,103 @@ }.to_json end - it 'returns success response' do - get "/api/projects/#{project.identifier}" + context 'when user is logged in' do + before do + mock_oauth_user(project.user_id) + end - expect(response).to have_http_status(:ok) - end + context 'loading own project' do + it 'returns success response' do + get "/api/projects/#{project.identifier}" + + expect(response).to have_http_status(:ok) + end + + it 'returns json' do + get "/api/projects/#{project.identifier}" + expect(response.content_type).to eq('application/json; charset=utf-8') + end + + it 'returns the project json' do + get "/api/projects/#{project.identifier}" + expect(response.body).to eq(project_json) + end + end - it 'returns json' do - get "/api/projects/#{project.identifier}" - expect(response.content_type).to eq('application/json; charset=utf-8') - end + context 'loading another user\'s project' do + let!(:another_project) { create(:project) } + let(:another_project_json) do + { + identifier: another_project.identifier, + project_type: 'python', + name: another_project.name, + user_id: another_project.user_id, + components: [], + image_list: [] + }.to_json + end + + it 'returns forbidden response' do + get "/api/projects/#{another_project.identifier}" + + expect(response).to have_http_status(:forbidden) + end + + it 'does not return the project json' do + get "/api/projects/#{another_project.identifier}" + expect(response.body).not_to include(another_project_json) + end - it 'returns the project json' do - get "/api/projects/#{project.identifier}" - expect(response.body).to eq(expected_json) + end end - it 'returns 404 response if invalid project' do - get '/api/projects/no-such-project' + context 'when user is not logged in' do + context 'loading a starter project' do + let!(:starter_project) { create(:project, user_id: nil) } + let(:starter_project_json) do + { + identifier: starter_project.identifier, + project_type: 'python', + name: starter_project.name, + user_id: starter_project.user_id, + components: [], + image_list: [] + }.to_json + end + + it 'returns success response' do + get "/api/projects/#{starter_project.identifier}" + + expect(response).to have_http_status(:ok) + end + + it 'returns json' do + get "/api/projects/#{starter_project.identifier}" + expect(response.content_type).to eq('application/json; charset=utf-8') + end + + it 'returns the project json' do + get "/api/projects/#{starter_project.identifier}" + expect(response.body).to eq(starter_project_json) + end + + it 'returns 404 response if invalid project' do + get '/api/projects/no-such-project' + expect(response).to have_http_status(:not_found) + end + end - expect(response).to have_http_status(:not_found) + context 'loading an owned project' do + it 'returns forbidden response' do + get "/api/projects/#{project.identifier}" + + expect(response).to have_http_status(:forbidden) + end + + it 'does not return the project json' do + get "/api/projects/#{project.identifier}" + expect(response.body).not_to include(project_json) + end + end end end From 7606480ea6f60cdef19f01a36c69c4e960334c51 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 1 Dec 2022 11:56:36 +0000 Subject: [PATCH 5/6] removing files added by mistake --- .byebug_history | 120 ------------------------------------------------ openAPI.yml | 44 ------------------ 2 files changed, 164 deletions(-) delete mode 100644 .byebug_history delete mode 100644 openAPI.yml diff --git a/.byebug_history b/.byebug_history deleted file mode 100644 index 9e90293df..000000000 --- a/.byebug_history +++ /dev/null @@ -1,120 +0,0 @@ -c -project_hash -exit -pp project_hash -pp e -n -s -q -params.deep_symbolize_keys -params.to_h.deep_symbolize_keys -params.to_h.class -params.to_h.calss -params.to_h.deep_transform_keys(&:to_sym) -params.to_h -params -q -continue -q -continue -pp params -continue -params -continue -pp params -n -q -project -n -s -q -x.deep_transform_keys(&:to_sym) -x={"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]} -x.deep_transform_keys(&:to_sym) -x -x = params.to_h.dup -params.to_h -params -hashed_params.deep_transform_keys!(&:to_sym) -hashed_params.deep_transform_keys(&:to_sym) -hashed_params -hashed_params = params.to_h -params.to_h.dup.deep_transform_keys(&:to_sym) -params.to_h -params.to_h.deep_transform_keys(&:to_sym) -{"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]}.deep_transform_keys(&:to_sym) -{"type"=>"python", "components"=>[{"name"=>"main", "extension"=>"py", "content"=>"print(\"hello world\")", "index"=>0, "default"=>true}]}.deep_transform_params(&:to_sym) -dupped_params.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -dupped_params = params.dup -dupped_params_hash.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -dupped_params_hash = params_hash.dup -params_hash.dup.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -params_hash.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -params_hash = params.to_h -params -project -n -s -q -params.dup.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -params -project -n -q -params.to_h.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -dupped_params.to_h.deep_transform_keys do |key| - key.to_sym - rescue StandardError - key - end -dupped_params.deep_transform_keys do |key| -dupped_params = params.to_h.dup -params -params.to_h -params.deep_symbolize_keys -q -continue -params.to_h! -params -params.keys.each do |key| - params[(key.to_sym rescue key) || key] = params.delete(key) -end -require 'rails' -params.hash_with_indifferent_access -require 'rails' -params.to_h.deep_transform_keys(&:to_sym) -params.to_h.deep_transform_keys!(&:to_sym) -params.to_h.deep_transform_keys(&:to_sym) -params.to_h -params -c -s -params -continue -params -continue -params diff --git a/openAPI.yml b/openAPI.yml deleted file mode 100644 index 4d963d33f..000000000 --- a/openAPI.yml +++ /dev/null @@ -1,44 +0,0 @@ -openapi: 3.0.0 -info: - title: Raspberry Pi Code Editor API - description: API that serves the Rasperry Pi Code Editor at https://editor.raspberrypi.org - version: 0.1.0 - -servers: - - url: https://editor-api.raspberrypi.org/api - description: Production server - - url: https://staging-editor-api.raspberrypi.org/api - description: Staging server - -paths: - /projects/{identifier}: - get: - summary: Returns the project with the given identifier - responses: - '200': - description: A project object - content: - application/json: - schema: - type: object - properties: - identifier: - type: string - example: python-emoji-example - project_type: - type: string - example: python - name: - type: string - example: Hello World - user_id: - type: string - format: uuid - example: 3e1f2179-261a-4ebe-92b5-2c4b5c93193b - components: - type: array - image_list: - type: array - '404': - description: A project with the specified identifier was not found. - From 7c25431706e3c445d9bff62b3f76ae55e9b2851b Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 1 Dec 2022 12:27:55 +0000 Subject: [PATCH 6/6] Adding ability spec and fixing rubocop --- spec/models/ability_spec.rb | 60 ++++++++++++++++++++++++++++++ spec/request/projects/show_spec.rb | 29 +++++++-------- 2 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 spec/models/ability_spec.rb diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb new file mode 100644 index 000000000..ad3b02889 --- /dev/null +++ b/spec/models/ability_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'cancan/matchers' +require 'rails_helper' + +RSpec.describe Ability do + subject { described_class.new(user) } + + let(:project) { build(:project) } + let(:another_project) { build(:project) } + let(:starter_project) { build(:project, user_id: nil) } + + context 'when no user' do + let(:user) { nil } + + context 'with a starter project' do + it { is_expected.not_to be_able_to(:index, starter_project) } + it { is_expected.to be_able_to(:show, starter_project) } + it { is_expected.not_to be_able_to(:create, starter_project) } + it { is_expected.not_to be_able_to(:update, starter_project) } + it { is_expected.not_to be_able_to(:destroy, starter_project) } + end + + context 'with an owned project' do + it { is_expected.not_to be_able_to(:index, project) } + it { is_expected.not_to be_able_to(:show, project) } + it { is_expected.not_to be_able_to(:create, project) } + it { is_expected.not_to be_able_to(:update, project) } + it { is_expected.not_to be_able_to(:destroy, project) } + end + end + + context 'when user present' do + let(:user) { project.user_id } + + context 'with a starter project' do + it { is_expected.not_to be_able_to(:index, starter_project) } + it { is_expected.to be_able_to(:show, starter_project) } + it { is_expected.not_to be_able_to(:create, starter_project) } + it { is_expected.not_to be_able_to(:update, starter_project) } + it { is_expected.not_to be_able_to(:destroy, starter_project) } + end + + context 'with own project' do + it { is_expected.to be_able_to(:index, project) } + it { is_expected.to be_able_to(:show, project) } + it { is_expected.to be_able_to(:create, project) } + it { is_expected.to be_able_to(:update, project) } + it { is_expected.to be_able_to(:destroy, project) } + end + + context 'with another user\'s project' do + it { is_expected.not_to be_able_to(:index, another_project) } + it { is_expected.not_to be_able_to(:show, another_project) } + it { is_expected.not_to be_able_to(:create, another_project) } + it { is_expected.not_to be_able_to(:update, another_project) } + it { is_expected.not_to be_able_to(:destroy, another_project) } + end + end +end diff --git a/spec/request/projects/show_spec.rb b/spec/request/projects/show_spec.rb index adb536df0..019d5eb56 100644 --- a/spec/request/projects/show_spec.rb +++ b/spec/request/projects/show_spec.rb @@ -20,25 +20,25 @@ mock_oauth_user(project.user_id) end - context 'loading own project' do + context 'when loading own project' do it 'returns success response' do get "/api/projects/#{project.identifier}" - + expect(response).to have_http_status(:ok) end - + it 'returns json' do get "/api/projects/#{project.identifier}" expect(response.content_type).to eq('application/json; charset=utf-8') end - + it 'returns the project json' do get "/api/projects/#{project.identifier}" expect(response.body).to eq(project_json) end end - context 'loading another user\'s project' do + context 'when loading another user\'s project' do let!(:another_project) { create(:project) } let(:another_project_json) do { @@ -50,23 +50,22 @@ image_list: [] }.to_json end - + it 'returns forbidden response' do get "/api/projects/#{another_project.identifier}" - + expect(response).to have_http_status(:forbidden) end - - it 'does not return the project json' do + + it 'does not return the project json' do get "/api/projects/#{another_project.identifier}" expect(response.body).not_to include(another_project_json) end - end end context 'when user is not logged in' do - context 'loading a starter project' do + context 'when loading a starter project' do let!(:starter_project) { create(:project, user_id: nil) } let(:starter_project_json) do { @@ -101,14 +100,14 @@ end end - context 'loading an owned project' do + context 'when loading an owned project' do it 'returns forbidden response' do get "/api/projects/#{project.identifier}" - + expect(response).to have_http_status(:forbidden) end - - it 'does not return the project json' do + + it 'does not return the project json' do get "/api/projects/#{project.identifier}" expect(response.body).not_to include(project_json) end