Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/views/api/v2/_standard_response.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ json.ignore_nil!
json.server @server
json.source "#{request.method} #{request.path}"
json.time Time.now.to_formatted_s(:iso8601)
json.client @client.name
json.client @client&.name
json.code response.status
json.message Rack::Utils::HTTP_STATUS_CODES[response.status]

Expand Down
1 change: 0 additions & 1 deletion app/views/api/v2/datasets/_show.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ if output.is_a?(ResearchOutput)

json.type output.output_type
json.title output.title
json.doi_url output.doi_url
json.description output.description
json.personal_data Api::V2::ApiPresenter.boolean_to_yes_no_unknown(value: output.personal_data)
json.sensitive_data Api::V2::ApiPresenter.boolean_to_yes_no_unknown(value: output.sensitive_data)
Expand Down
2 changes: 1 addition & 1 deletion app/views/api/v2/templates/index.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ json.items @items do |template|
end

json.template_id do
identifier = Api::V2::ConversionService.to_identifier(context: @application,
identifier = Api::V2::ConversionService.to_identifier(context: @server,
value: template.id)
json.partial! 'api/v2/identifiers/show', identifier: identifier
end
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
get :me, controller: :base_api

resources :plans, only: %i[index show]
resources :templates, only: :index
end
end

Expand Down
14 changes: 14 additions & 0 deletions spec/factories/identifier_schemes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,19 @@
identifier_scheme.update("#{identifier_scheme.all_context[idx]}": true)
end
end

%i[
authentication
orgs
plans
users
contributors
identification
research_outputs
].each do |context|
trait :"for_#{context}" do
add_attribute(:"for_#{context}") { true }
end
end
end
end
36 changes: 36 additions & 0 deletions spec/factories/oauth_access_grants.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: oauth_access_grants
#
# id :integer not null, primary key
# resource_owner_id :integer not null
# application_id :integer not null
# token :string not null
# expires_in :integer
# revoked_at :datetime
# created_at :datetime not null
# scopes :string not null
#
# Indexes
#
# index_oauth_access_grants_on_token (token)
#
# Foreign Keys
#
# fk_rails_... (resource_owner_id => users.id)
# fk_rails_... (application_id => oauth_applications.id)

FactoryBot.define do
factory :oauth_access_grant, class: 'doorkeeper/access_grant' do
token { SecureRandom.uuid }
expires_in { Faker::Number.number(digits: 8) }
scopes { Doorkeeper.config.default_scopes + Doorkeeper.config.optional_scopes }
redirect_uri { Faker::Internet.url }

trait :revoked do
revoked_at { 2.hours.ago }
end
end
end
38 changes: 38 additions & 0 deletions spec/factories/oauth_access_tokens.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: oauth_access_tokens
#
# id :integer not null, primary key
# resource_owner_id :integer not null
# application_id :integer not null
# token :string not null
# refresh_token :string
# expires_in :integer
# revoked_at :datetime
# created_at :datetime not null
# scopes :string not null
# previous_refresh_token :string
#
# Indexes
#
# index_oauth_access_tokens_on_token (token)
#
# Foreign Keys
#
# fk_rails_... (resource_owner_id => users.id)
# fk_rails_... (application_id => oauth_applications.id)

FactoryBot.define do
factory :oauth_access_token, class: 'doorkeeper/access_token' do
token { SecureRandom.uuid }
refresh_token { SecureRandom.uuid }
expires_in { Faker::Number.number(digits: 8) }
scopes { Doorkeeper.config.default_scopes + Doorkeeper.config.optional_scopes }

trait :revoked do
revoked_at { 2.hours.ago }
end
end
end
25 changes: 25 additions & 0 deletions spec/factories/oauth_applications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: oauth_application
#
# id: :integer
# name: :string
# uid: :string
# secret: :string
# redirect_uri: :text
# scopes: :string
# confidential: :boolean
# created_at: :datetime
# updated_at: :datetime

FactoryBot.define do
factory :oauth_application, class: 'doorkeeper/application' do
name { Faker::Lorem.unique.word }
uid { SecureRandom.uuid }
secret { SecureRandom.uuid }
redirect_uri { "https://#{Faker::Internet.unique.domain_name}/callback" }
scopes { 'read' }
end
end
1 change: 0 additions & 1 deletion spec/factories/research_domains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@
factory :research_domain do
identifier { SecureRandom.uuid }
label { Faker::Lorem.unique.word }
uri { Faker::Internet.url }
end
end
126 changes: 126 additions & 0 deletions spec/requests/api/v2/plans_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Api::V2::PlansController do
include ApiHelper
include Mocks::ApiV2JsonSamples
include Webmocks
include IdentifierHelper

context 'OAuth (authorization_code grant type) — on behalf of a user' do
before do
@user = create(:user)
@client = create(:oauth_application)
token = mock_authorization_code_token(oauth_application: @client, user: @user).token

@headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: "Bearer #{token}"
}
end

def fetch_plans_json_response
get(api_v2_plans_path, headers: @headers)
expect(response).to render_template('api/v2/_standard_response')
expect(response).to render_template('api/v2/plans/index')
JSON.parse(response.body).with_indifferent_access
end

describe 'GET /api/v2/plans (index)' do
context 'an invalid API token is included' do
it 'returns a 401 and the expected Oauth 2.0 headers' do
# Swap actual token with a random string
@headers['Authorization'] = "Bearer #{SecureRandom.uuid}"
get(api_v2_plans_path, headers: @headers)

expect(response.code).to eql('401')
expect(response.body).to be_empty

# Expect Doorkeeper to return the standard OAuth 2.0 WWW-Authenticate header for invalid tokens
expect(response.headers['WWW-Authenticate']).to match(
/Bearer realm="Doorkeeper", error="invalid_token", error_description="The access token is invalid"/
)
end
end

context 'a valid API token is included' do
let(:json) { fetch_plans_json_response }
it 'returns a 200 and the expected response body' do
# Items array is empty
expect(json[:items]).to eq([])

# total_items reflects that nothing is returned
expect(json[:total_items]).to eq(0)

# Status code and message are correct
expect(json[:code]).to eq(200)
expect(json[:message]).to eq('OK')

# Server and source are present and sensible
expect(json[:server]).to eq(ApplicationService.application_name)
expect(json[:source]).to eq('GET /api/v2/plans')

# Time is present and parseable
expect { Time.iso8601(json[:time]) }.not_to raise_error

# Client is included
expect(json[:client]).to eq(@client.name)
end

it 'returns an empty array if no plans are available' do
# Items array is empty
expect(json[:items]).to eq([])

# total_items reflects that nothing is returned
expect(json[:total_items]).to eq(0)
end

it 'returns the expected plans' do
# See `app/policies/api/v2/plans_policy.rb for plans included/excluded via `GET api/v2/plans`

# Create the included plans
included_plans = [create(:plan, org: @user.org), create(:plan)]
included_plans[0].add_user!(@user.id, :creator)
# Add multiple roles for testing (ensure duplicate plans will not returned)
included_plans[1].add_user!(@user.id, :editor)
included_plans[1].add_user!(@user.id, :commenter)

# Created the excluded plans
create(:plan, :creator, org: @user.org)
inactive_plan = create(:plan, :creator)
inactive_plan.add_user!(@user.id, :editor)
Role.where(plan_id: inactive_plan.id, user_id: @user.id).update!(active: false)

expect(json[:items].length).to be(included_plans.length)

# Api::V2::PlanPresenter.identifier uses api_v2_plan_url(@plan) to set the "identifier".
# That url is constructed using `request.host` / "www.example.com"
# api_v2_plan_url(@plan) within this test will construct the url via
# default_url_options[:host] / "example.org"
# Because the urls are misaligned, we will only compare the paths here.
# TODO: Consider aligning default_url_options[:host] (in test.rb) with `request.host`
returned_identifiers = json[:items].map { |item| item[:dmp][:dmp_id][:identifier] }
returned_paths = returned_identifiers.map { |url| URI(url).path }
expected_paths = included_plans.map { |plan| api_v2_plan_path(plan) }
expect(returned_paths).to eq(expected_paths)
end

it 'allows for paging' do
original_page_size = Rails.configuration.x.application.api_max_page_size
Rails.configuration.x.application.api_max_page_size = 10

create_list(:plan, 11, :publicly_visible) do |plan|
plan.add_user!(@user.id, :commenter)
end
json = fetch_plans_json_response

test_paging(json: json, headers: @headers)

Rails.configuration.x.application.api_max_page_size = original_page_size
end
end
end
end
end
Loading