diff --git a/.circleci/config.yml b/.circleci/config.yml index 15ea94d..2563b01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,8 +18,16 @@ jobs: BUNDLE_JOBS: 3 BUNDLE_RETRY: 3 BUNDLE_PATH: vendor/bundle + PGHOST: 127.0.0.1 + PGUSER: circleci-demo-ruby + PGPASSWORD: sekret RAILS_ENV: test NOKOGIRI_USE_SYSTEM_LIBRARIES: true + - image: circleci/postgres:11 + environment: + POSTGRES_USER: postgres + POSTGRES_DB: suri + POSTGRES_PASSWORD: sekret working_directory: ~/repo @@ -47,25 +55,18 @@ jobs: name: Which bundler? command: bundle -v - - restore_cache: - keys: - - suri-rails-dependencies-{{ checksum "Gemfile.lock" }} - # fallback to using the latest cache if no exact match is found - - suri-rails-dependencies- - - run: name: Install dependencies command: bundle check || bundle install - - save_cache: - key: dor-services-app-bundle-v2-{{ checksum "Gemfile.lock" }} - paths: - - vendor/bundle - - run: name: Run code linter & style checker command: bundle exec rubocop + - run: + name: Wait for DB + command: dockerize -wait tcp://localhost:5432 -timeout 1m + - run: name: Set up database command: bin/rails db:test:prepare @@ -88,51 +89,52 @@ jobs: build-image: executor: docker-publisher steps: - - checkout - - setup_remote_docker - - run: - name: Build Docker image - command: | - docker build -t $IMAGE_NAME:latest . - - run: - name: Archive Docker image - command: | - docker save -o app_image.tar $IMAGE_NAME - - persist_to_workspace: - root: . - paths: - - ./app_image.tar + - checkout + - setup_remote_docker + - run: + name: Build Docker image + command: | + docker build -t $IMAGE_NAME:latest . + - run: + name: Archive Docker image + command: | + docker save -o app_image.tar $IMAGE_NAME + - persist_to_workspace: + root: . + paths: + - ./app_image.tar publish-latest: executor: docker-publisher steps: - - attach_workspace: - at: /tmp/workspace - - setup_remote_docker - - run: - name: Load archived Docker image - command: | - docker load -i /tmp/workspace/app_image.tar - - run: - name: Publish Docker Image to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - docker push $IMAGE_NAME:latest + - attach_workspace: + at: /tmp/workspace + - setup_remote_docker + - run: + name: Load archived Docker image + command: | + docker load -i /tmp/workspace/app_image.tar + - run: + name: Publish Docker Image to Docker Hub + command: | + echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin + docker push $IMAGE_NAME:latest publish-tag: executor: docker-publisher steps: - - attach_workspace: - at: /tmp/workspace - - setup_remote_docker - - run: - name: Load archived Docker image - command: | - docker load -i /tmp/workspace/app_image.tar - - run: - name: Publish Docker Image to Docker Hub - command: | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CIRCLE_TAG - docker push $IMAGE_NAME:$CIRCLE_TAG + - attach_workspace: + at: /tmp/workspace + - setup_remote_docker + - run: + name: Load archived Docker image + command: | + docker load -i /tmp/workspace/app_image.tar + - run: + name: Publish Docker Image to Docker Hub + command: | + echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin + docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CIRCLE_TAG + docker push $IMAGE_NAME:$CIRCLE_TAG + workflows: version: 2 @@ -148,7 +150,7 @@ workflows: only: master - publish-latest: requires: - - build-image + - build-image filters: branches: only: master @@ -162,7 +164,7 @@ workflows: ignore: /.*/ - publish-tag: requires: - - build-image + - build-image filters: tags: only: /^[0-9]+\.[0-9]+\.[0-9]+/ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ed6c95b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.dockerignore +.byebug_history +log +tmp +coverage +# Required for puma to have a place to put the pid file +!tmp/pids/.keep diff --git a/.rubocop.yml b/.rubocop.yml index 46b1952..1503538 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,6 +15,26 @@ Metrics/BlockLength: Exclude: - 'spec/**/*.rb' +Metrics/MethodLength: + Exclude: + - 'bin/bundle' + +Metrics/CyclomaticComplexity: + Exclude: + - 'bin/bundle' + +Metrics/PerceivedComplexity: + Exclude: + - 'bin/bundle' + +Metrics/AbcSize: + Exclude: + - 'bin/bundle' + +Layout/LineLength: + Exclude: + - 'bin/bundle' + Style/MixinUsage: Exclude: - 'bin/**/*' @@ -22,6 +42,9 @@ Style/MixinUsage: RSpec/MultipleExpectations: Max: 5 +RSpec/ExampleLength: + Enabled: false + Style/HashEachMethods: Enabled: true diff --git a/Dockerfile b/Dockerfile index b7d43c4..14653f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,27 @@ -FROM ruby:2.7.0-alpine +FROM ruby:2.7.1-alpine +LABEL maintainer="Stanford Libraries Infrastructure Team " -RUN apk update && apk add build-base sqlite-dev tzdata git +RUN apk add --update --no-cache \ + build-base \ + git \ + postgresql-dev \ + postgresql-client \ + libxml2-dev \ + libxslt-dev \ + tzdata RUN mkdir /app WORKDIR /app -COPY Gemfile Gemfile.lock ./ -RUN gem install bundler -v 2.0.2 -RUN bundle install +RUN gem update --system && \ + gem install bundler && \ + bundle config build.nokogiri --use-system-libraries -COPY . . +COPY Gemfile Gemfile.lock ./ -RUN bundle exec rails db:setup +RUN bundle config set without 'production' && bundle install -LABEL maintainer="Justin Coyne " +COPY . . -CMD puma -C config/puma.rb +CMD ["./docker/invoke.sh"] diff --git a/Gemfile b/Gemfile index bc4c7a2..345fd75 100644 --- a/Gemfile +++ b/Gemfile @@ -20,9 +20,7 @@ group :development, :test do gem 'rubocop-performance' gem 'rubocop-rails' gem 'rubocop-rspec' - # Use sqlite3 as the database for Active Record gem 'simplecov', '~> 0.17.1' # 0.18 breaks reporting https://github.com/codeclimate/test-reporter/issues/418 - gem 'sqlite3', '~> 1.4' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index d98da43..a257aa5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -233,7 +233,6 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.4.2) sshkit (1.21.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) @@ -273,7 +272,6 @@ DEPENDENCIES simplecov (~> 0.17.1) spring spring-watcher-listen (~> 2.0.0) - sqlite3 (~> 1.4) BUNDLED WITH 2.1.4 diff --git a/README.md b/README.md index 741d064..a277cf7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ NOTE: This is currently used for running specs; we still use https://github.com/ Clone the repository - $ git clone git@github.com:sul-dlss/suri_rails.git + $ git clone git@github.com:sul-dlss/suri-rails.git Change directories into the app and install dependencies @@ -32,27 +32,35 @@ Start the development server ## Testing +First, ensure the database container is spun up: + + $ docker-compose up db # use -d to daemonize/run in background + +And if you haven't yet prepared the test database, run: + + $ bin/rails db:setup + The test suite (with RuboCop style enforcement) will be run with the default rake task (also run on CI). - $ rake + $ bin/rake The specs can be run without RuboCop enforcement - $ rake spec + $ bin/rake spec The RuboCop style enforcement can be run without running the tests - $ rake rubocop + $ bin/rake rubocop ## Building for Docker -```shell -$ docker build -t suldlss/suri-rails:latest . -``` +Spin up the application and its database: + + $ docker-compose up --build + +## Updating Docker Image -Then run as: -```shell -$ docker run -d -p 127.0.0.1:3002:3000 suldlss/suri-rails:latest -``` +Note that CI is configured to automatically update the image hosted on DockerHub on every commit to `master`. If you want to build and push an image manually, run: -Note that CI is configured to automatically update the image hosted on DockerHub on every commit to `master`. + $ docker build -t suldlss/suri-rails:latest . + $ docker push suldlss/suri-rails:latest diff --git a/app/controllers/identifiers_controller.rb b/app/controllers/identifiers_controller.rb index c81bf38..a756659 100644 --- a/app/controllers/identifiers_controller.rb +++ b/app/controllers/identifiers_controller.rb @@ -2,7 +2,6 @@ class IdentifiersController < ApplicationController # GET /identifiers - # GET /identifiers.json def index @identifiers = Identifier.all @@ -10,21 +9,42 @@ def index end # GET /identifiers/1 - # GET /identifiers/1.json def show - @identifier = Identifier.find_by(identifier: params[:id]) + @identifier = Identifier.find_by!(identifier: params[:id]) render json: @identifier + rescue ActiveRecord::RecordNotFound + render build_error("Identifier not found: #{params[:id]}", :not_found) end # POST /identifiers - # POST /identifiers.json def create @identifier = Identifier.mint if @identifier render plain: @identifier.identifier, status: :created, location: @identifier else - render json: { error: 'Unable to mint a druid' }, status: :internal_server_error + render build_error('Unable to mint a druid', :internal_server_error) end end + + private + + # rubocop:disable Metrics/MethodLength + # JSON-API error response + def build_error(msg, status) + { + json: { + errors: [ + { + "status": Rack::Utils::SYMBOL_TO_STATUS_CODE[status].to_s, + "title": msg, + "detail": msg + } + ] + }, + content_type: 'application/vnd.api+json', + status: status + } + end + # rubocop:enable Metrics/MethodLength end diff --git a/bin/bundle b/bin/bundle index 2dbb717..5880eb6 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,5 +1,116 @@ #!/usr/bin/env ruby # frozen_string_literal: true -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -load Gem.bin_path('bundler', 'bundle') +# +# This file was generated by Bundler. +# +# The application 'bundle' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +m = Module.new do + module_function + + def invoked_as_script? + File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__) + end + + def env_var_version + ENV['BUNDLER_VERSION'] + end + + def cli_arg_version + return unless invoked_as_script? # don't want to hijack other binstubs + return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update` + + bundler_version = nil + update_index = nil + ARGV.each_with_index do |a, i| + bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ + + bundler_version = Regexp.last_match(1) + update_index = i + end + bundler_version + end + + def gemfile + gemfile = ENV['BUNDLE_GEMFILE'] + return gemfile if gemfile.present? + + File.expand_path('../Gemfile', __dir__) + end + + def lockfile + lockfile = + case File.basename(gemfile) + when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) + else "#{gemfile}.lock" + end + File.expand_path(lockfile) + end + + def lockfile_version + return unless File.file?(lockfile) + + lockfile_contents = File.read(lockfile) + return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ + + Regexp.last_match(1) + end + + def bundler_version + @bundler_version ||= + env_var_version || cli_arg_version || + lockfile_version + end + + def bundler_requirement + return "#{Gem::Requirement.default}.a" unless bundler_version + + bundler_gem_version = Gem::Version.new(bundler_version) + + requirement = bundler_gem_version.approximate_recommendation + + return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0') + + requirement += '.a' if bundler_gem_version.prerelease? + + requirement + end + + def load_bundler! + ENV['BUNDLE_GEMFILE'] ||= gemfile + + activate_bundler + end + + def activate_bundler + gem_error = activation_error_handling do + gem 'bundler', bundler_requirement + end + return if gem_error.nil? + + require_error = activation_error_handling do + require 'bundler/version' + end + return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) + + warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" + exit 42 + end + + def activation_error_handling + yield + nil + rescue StandardError, LoadError => e + e + end +end + +m.load_bundler! + +load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? diff --git a/bin/puma b/bin/puma new file mode 100755 index 0000000..7830c34 --- /dev/null +++ b/bin/puma @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'puma' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('puma', 'puma') diff --git a/bin/pumactl b/bin/pumactl new file mode 100755 index 0000000..19670eb --- /dev/null +++ b/bin/pumactl @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'pumactl' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 300)) + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('puma', 'pumactl') diff --git a/config/application.rb b/config/application.rb index d5578e7..cf40619 100644 --- a/config/application.rb +++ b/config/application.rb @@ -47,8 +47,12 @@ class Application < Rails::Application error_class: JSONAPIError, accept_request_filter: accept_proc - # TODO: we can uncomment this at a later date to ensure we are passing back valid responses - config.middleware.use Committee::Middleware::ResponseValidation, schema_path: 'openapi.yml' + # TODO: we can uncomment this once we can change all consumers of SURI to + # expect a JSON array as a return type, rather than plain-text. The + # Committee gem does not currently allow response validation for non-JSON + # responses. + # + # config.middleware.use Committee::Middleware::ResponseValidation, schema_path: 'openapi.yml' # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers diff --git a/config/database.yml b/config/database.yml index 0d02f24..d7b7956 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,25 +1,22 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -# default: &default - adapter: sqlite3 + adapter: postgresql + encoding: unicode + database: "<%= ENV.fetch('DATABASE_NAME', 'suri') %>" + username: "<%= ENV.fetch('DATABASE_USERNAME', 'postgres') %>" + password: "<%= ENV.fetch('DATABASE_PASSWORD', 'sekret') %>" + host: "<%= ENV.fetch('DATABASE_HOSTNAME', 'localhost') %>" + port: "<%= ENV.fetch('DATABASE_PORT', 5432) %>" pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default - database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default - database: db/test.sqlite3 production: <<: *default - database: db/production.sqlite3 diff --git a/db/schema.rb b/db/schema.rb index ca22c3f..2f137f6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,6 +13,9 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 20_170_825_145_538) do + # These are extensions that must be enabled in order to support this database + enable_extension 'plpgsql' + create_table 'identifiers', force: :cascade do |t| t.string 'identifier' t.string 'created_by' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1d8d454 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.6' + +services: + web: + build: . + volumes: + - ./:/app + working_dir: /app + depends_on: + - db + ports: + - 3000:3000 + environment: + DATABASE_NAME: suri + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: sekret + DATABASE_HOSTNAME: db + DATABASE_PORT: 5432 + RAILS_LOG_TO_STDOUT: 'true' + db: + image: postgres:11 + ports: + - 5432:5432 + environment: + - POSTGRES_PASSWORD=sekret + volumes: + - postgres-data:/var/lib/postgresql/data +volumes: + postgres-data: diff --git a/docker/invoke.sh b/docker/invoke.sh new file mode 100755 index 0000000..17455c8 --- /dev/null +++ b/docker/invoke.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Don't allow this command to fail +set -e + +echo "HOST IS: $DATABASE_HOSTNAME" +until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOSTNAME" -U $DATABASE_USERNAME -c '\q'; do + echo "Postgres is unavailable - sleeping" + sleep 1 +done + +echo "Postgres is up - Setting up database" + +# Allow this command to fail +set +e +echo "Creating DB. OK to ignore errors about test db." +# https://github.com/rails/rails/issues/27299 +bin/rails db:create + +# Don't allow any following commands to fail +set -e +echo "Migrating db" +bin/rails db:migrate + +echo "Running server" +exec bin/puma -C config/puma.rb config.ru diff --git a/openapi.yml b/openapi.yml index af1f970..dede042 100644 --- a/openapi.yml +++ b/openapi.yml @@ -51,11 +51,15 @@ paths: schema: type: string content: - text/plain: + text/plain: # This is why we can't enable Committee response validation. See TODO in config/application.rb schema: $ref: '#/components/schemas/BareDruid' '500': description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' get: tags: - identifiers @@ -70,7 +74,10 @@ paths: schema: type: array items: - $ref: '#/components/schemas/BareDruid' + type: object + properties: + identifier: + $ref: '#/components/schemas/BareDruid' /identifiers/{id}: get: tags: @@ -84,9 +91,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/BareDruid' + $ref: '#/components/schemas/Identifier' '404': description: Identifier not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' parameters: - name: id in: path @@ -94,7 +105,7 @@ paths: required: true schema: $ref: '#/components/schemas/BareDruid' - /suri2/namespaces/druids/identifiers: + /suri2/namespaces/druid/identifiers: post: tags: - identifiers @@ -130,3 +141,48 @@ components: type: string pattern: '^[b-df-hjkmnp-tv-z]{2}[0-9]{3}[b-df-hjkmnp-tv-z]{2}[0-9]{4}$' example: 'bc123df4567' + ErrorResponse: + type: object + properties: + errors: + type: array + items: + $ref: '#/components/schemas/Error' + Error: + type: object + properties: + title: + type: string + description: 'a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem.' + example: Invalid Attribute + detail: + type: string + description: a human-readable explanation specific to this occurrence of the problem. + example: Title must contain at least three characters. + source: + type: object + properties: + pointer: + type: string + example: /data/attributes/title + Identifier: + description: an ID record stored in the database + type: object + properties: + id: + type: integer + example: 123 + identifier: + $ref: '#/components/schemas/BareDruid' + created_by: + type: string + nullable: true + created_at: + type: string + updated_at: + type: string + required: + - id + - identifier + - created_at + - updated_at diff --git a/spec/controllers/identifiers_controller_spec.rb b/spec/controllers/identifiers_controller_spec.rb deleted file mode 100644 index 7988da0..0000000 --- a/spec/controllers/identifiers_controller_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe IdentifiersController, type: :controller do - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # IdentifiersController. Be sure to keep this updated too. - let(:valid_session) { {} } - - describe 'GET #index' do - it 'returns a success response' do - identifier = Identifier.mint - get :index, params: {}, session: valid_session - expect(response).to have_http_status(:ok) - expect(JSON.parse(response.body)).to include hash_including('identifier' => identifier.identifier) - end - end - - describe 'GET #show' do - it 'returns a success response' do - identifier = Identifier.mint - get :show, params: { id: identifier.to_param }, session: valid_session - expect(response).to have_http_status(:ok) - end - end - - describe 'POST #create' do - context 'with valid params' do - it 'creates a new Identifier' do - expect do - post :create, session: valid_session - end.to change(Identifier, :count).by(1) - end - - it 'renders a text response with the new identifier' do - post :create, session: valid_session - expect(response).to have_http_status(:created) - expect(response.media_type).to eq('text/plain') - expect(response.location).to eq(identifier_url(Identifier.last)) - expect(response.body).to eq Identifier.last.identifier - end - end - - context 'with a broken minter' do - it 'renders an errors' do - allow(Identifier).to receive(:mint).and_return(false) - post :create, session: valid_session - expect(response).to have_http_status(:internal_server_error) - end - end - end -end diff --git a/spec/requests/identifiers_spec.rb b/spec/requests/identifiers_spec.rb new file mode 100644 index 0000000..d6cf233 --- /dev/null +++ b/spec/requests/identifiers_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Identifiers', type: :request do + let!(:identifier) { Identifier.mint } + + describe 'GET #index' do + it 'returns a success response' do + get '/identifiers' + expect(response).to have_http_status(:ok) + expect(JSON.parse(response.body)).to include hash_including('identifier' => identifier.identifier) + end + end + + describe 'GET #show' do + context 'when identifier is minted' do + it 'returns a success response' do + get "/identifiers/#{identifier.identifier.to_param}" + expect(response).to have_http_status(:ok) + expect(JSON.parse(response.body)).to include('identifier' => identifier.identifier) + end + end + + context 'when identifier is not found' do + it 'returns a not found response' do + get '/identifiers/bc123df4567' + expect(response).to have_http_status(:not_found) + expect(JSON.parse(response.body)['errors'].first).to include( + 'status' => '404', + 'title' => 'Identifier not found: bc123df4567', + 'detail' => 'Identifier not found: bc123df4567' + ) + end + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new Identifier' do + expect do + post '/identifiers' + end.to change(Identifier, :count).by(1) + end + + it 'renders a text response with the new identifier' do + post '/identifiers' + expect(response).to have_http_status(:created) + expect(response.media_type).to eq('text/plain') + expect(response.location).to eq(identifier_url(Identifier.last)) + expect(response.body).to eq Identifier.last.identifier + end + end + + context 'with a broken minter' do + before do + allow(Identifier).to receive(:mint).and_return(nil) + end + + it 'renders an error' do + post '/identifiers' + expect(response).to have_http_status(:internal_server_error) + expect(JSON.parse(response.body)['errors'].first).to include( + 'status' => '500', + 'title' => 'Unable to mint a druid', + 'detail' => 'Unable to mint a druid' + ) + end + end + end + + describe 'POST #create (LEGACY ROUTE)' do + context 'with valid params' do + it 'creates a new Identifier' do + expect do + post '/suri2/namespaces/druid/identifiers' + end.to change(Identifier, :count).by(1) + end + + it 'renders a text response with the new identifier' do + post '/suri2/namespaces/druid/identifiers' + expect(response).to have_http_status(:created) + expect(response.media_type).to eq('text/plain') + expect(response.location).to eq(identifier_url(Identifier.last)) + expect(response.body).to eq Identifier.last.identifier + end + end + + context 'with a broken minter' do + before do + allow(Identifier).to receive(:mint).and_return(nil) + end + + it 'renders an error' do + post '/suri2/namespaces/druid/identifiers' + expect(response).to have_http_status(:internal_server_error) + expect(JSON.parse(response.body)['errors'].first).to include( + 'status' => '500', + 'title' => 'Unable to mint a druid', + 'detail' => 'Unable to mint a druid' + ) + end + end + end +end