Skip to content
This repository has been archived by the owner on May 28, 2024. It is now read-only.

Commit

Permalink
Merge f5bae74 into 396bd0f
Browse files Browse the repository at this point in the history
  • Loading branch information
mjgiarlo committed Jun 3, 2020
2 parents 396bd0f + f5bae74 commit 87c26fe
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 21 deletions.
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ rvm:
- 2.7.1

before_install:
gem update --system
- gem update --system
# - nvm install node
- npm install -g openapi-enforcer-cli

script:
- ./validate-openapi.bash || travis_terminate 1
- bundle exec rubocop
- bundle exec rspec
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gem 'puma', '~> 3.0'

gem 'erubis'

gem 'committee' # Validates HTTP requests/responses per OpenAPI specification
gem 'config'
gem 'faraday'
gem 'honeybadger', '~> 4.5'
Expand Down
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ GEM
openapi_parser
thor
zeitwerk (~> 2.1)
committee (4.0.0)
json_schema (~> 0.14, >= 0.14.3)
openapi_parser (>= 0.11.1)
rack (>= 1.5)
commonmarker (0.21.0)
ruby-enum (~> 0.5)
concurrent-ruby (1.1.6)
Expand Down Expand Up @@ -231,6 +235,7 @@ GEM
iso-639 (0.3.5)
jaro_winkler (1.5.4)
json (2.3.0)
json_schema (0.20.8)
link_header (0.0.8)
listen (3.0.8)
rb-fsevent (~> 0.9, >= 0.9.4)
Expand Down Expand Up @@ -462,6 +467,7 @@ DEPENDENCIES
capistrano-passenger
capistrano-rails
capistrano-shared_configs
committee
config
coveralls
dlss-capistrano (~> 3.0)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[![Build Status](https://travis-ci.org/sul-dlss/dor_indexing_app.svg?branch=master)](https://travis-ci.org/sul-dlss/dor_indexing_app) | [![Coverage Status](https://coveralls.io/repos/github/sul-dlss/dor_indexing_app/badge.svg?branch=master)](https://coveralls.io/github/sul-dlss/dor_indexing_app?branch=master)

[![Build Status](https://travis-ci.org/sul-dlss/dor_indexing_app.svg?branch=master)](https://travis-ci.org/sul-dlss/dor_indexing_app)
[![Coverage Status](https://coveralls.io/repos/github/sul-dlss/dor_indexing_app/badge.svg?branch=master)](https://coveralls.io/github/sul-dlss/dor_indexing_app?branch=master)
[![OpenAPI Validator](http://validator.swagger.io/validator?url=https://raw.githubusercontent.com/sul-dlss/dor_indexing_app/master/openapi.yml)](http://validator.swagger.io/validator/debug?url=https://raw.githubusercontent.com/sul-dlss/dor_indexing_app/master/openapi.yml)

# Dor Indexing App

Expand Down
30 changes: 30 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

# JSONAPIError class for returning properly formatted errors in openapi
class JSONAPIError < Committee::ValidationError
def error_body
{
errors: [
{ status: id, detail: message }
]
}
end

def render
[
status,
{ 'Content-Type' => 'application/vnd.api+json' },
[JSON.generate(error_body)]
]
end
end

# Module surrounding Rails application
module DorIndexingApp
# Entrypoint to Rails application
Expand All @@ -31,6 +50,17 @@ class Application < Rails::Application
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.

# Do not validate e.g. OKComputer routes using OpenAPI
accept_proc = proc { |request| request.path.start_with?('/dor') }
config.middleware.use Committee::Middleware::RequestValidation, schema_path: 'openapi.yml',
strict: true,
error_class: JSONAPIError,
accept_request_filter: accept_proc

# TODO: Uncomment when API returns JSON or when Committee allows validating plain-text responses
#
# config.middleware.use Committee::Middleware::ResponseValidation, schema_path: 'openapi.yml'

# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
Expand Down
19 changes: 19 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>DOR Indexing API documentation</title>

<meta name="viewport" content="width=device-width, initial-scale=1">

<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='https://raw.githubusercontent.com/sul-dlss/dor_indexing_app/master/openapi.yml'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.30/bundles/redoc.standalone.js"> </script>
</body>
</html>
159 changes: 159 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
openapi: 3.0.0
info:
description: API for Stanford Digital Repository indexing service
version: 1.0.0
title: Dor Indexing API
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
servers:
- url: 'https://dor-indexing-app-{env}-{node}.stanford.edu'
description: Production service
variables:
env:
default: prod
node:
default: a
enum: [a b c]
- url: 'https://dor-indexing-app-{env}-{node}.stanford.edu'
description: Staging service
variables:
env:
default: stage
node:
default: a
enum: [a b]
- url: 'https://dor-indexing-app-{env}-{node}.stanford.edu'
description: Quality Assurance service
variables:
env:
default: qa
node:
default: a
enum: [a b]
tags:
- name: indexing
description: Indexing operations
- name: informational
description: Informational operations
paths:
/dor/reindex/{pid}:
post:
tags:
- indexing
summary: Reindex a repository object
description: ''
operationId: 'dor#reindex'
parameters:
- name: pid
in: path
description: 'a digital repository identifier'
required: true
schema:
$ref: '#/components/schemas/Druid'
- name: commitWithin
in: query
description: 'time within which to trigger Solr commit'
required: false
schema:
type: integer
responses:
'200':
description: Object successfully reindexed
content:
text/plain: # This is why we can't enable Committee response validation. See TODO in config/application.rb
schema:
type: string
'404':
description: Object not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/dor/delete_from_index/{pid}:
post:
tags:
- indexing
summary: Remove a repository object's index entry
description: ''
operationId: 'dor#delete_from_index'
parameters:
- name: pid
in: path
description: 'a digital repository identifier'
required: true
schema:
$ref: '#/components/schemas/Druid'
- name: commitWithin
in: query
description: 'time within which to trigger Solr commit'
required: false
schema:
type: integer
responses:
'200':
description: Object successfully removed from index
content:
text/plain: # This is why we can't enable Committee response validation. See TODO in config/application.rb
schema:
type: string
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/dor/queue_size:
get:
tags:
- informational
summary: Return size of indexing queue
description: ''
operationId: 'dor#queue_size'
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
value:
type: integer
components:
schemas:
Druid:
description: Digital Repository Unique Identifier (DRUID) with `druid:` prefix
type: string
pattern: '^druid:[b-df-hjkmnp-tv-z]{2}[0-9]{3}[b-df-hjkmnp-tv-z]{2}[0-9]{4}$'
example: 'druid: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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

require 'rails_helper'

RSpec.describe DorController, type: :controller do
describe '#reindex' do
RSpec.describe 'DOR', type: :request do
let(:mock_druid) { 'druid:bc123df5678' }

describe 'POST #reindex' do
before do
expect(Logger).to receive(:new).and_return(mock_logger)
allow(Logger).to receive(:new).and_return(mock_logger)
allow(ActiveFedora.solr).to receive(:conn).and_return(mock_solr_conn)
allow(Dor).to receive(:find).with(mock_druid).and_return(mock_af_doc)
allow(Indexer).to receive(:for).with(mock_af_doc).and_return(mock_indexer)
Expand All @@ -14,28 +16,27 @@
let(:mock_logger) { instance_double(Logger, :formatter= => true, info: true) }
let(:mock_solr_conn) { instance_double(RSolr::Client, add: true, commit: true) }
let(:mock_af_doc) { Dor::Item.new }
let(:mock_druid) { 'asdf:1234' }
let(:mock_indexer) { instance_double(CompositeIndexer::Instance, to_solr: mock_solr_doc) }
let(:mock_solr_doc) { { id: mock_druid, text_field_tesim: 'a field to be searched' } }

it 'reindexes an object with default commitWithin param and a hard commit' do
get :reindex, params: { pid: mock_druid }
expect(mock_solr_conn).to have_received(:add).with({ id: 'asdf:1234', text_field_tesim: 'a field to be searched' }, add_attributes: { commitWithin: 1000 })
post "/dor/reindex/#{mock_druid}"
expect(mock_solr_conn).to have_received(:add).with({ id: mock_druid, text_field_tesim: 'a field to be searched' }, add_attributes: { commitWithin: 1000 })
expect(mock_solr_conn).to have_received(:commit)
expect(response.body).to eq "Successfully updated index for #{mock_druid}"
expect(response.code).to eq '200'
end

it 'reindexes an object with specified commitWithin param and no hard commit' do
get :reindex, params: { pid: mock_druid, commitWithin: 10_000 }
expect(mock_solr_conn).to have_received(:add).with({ id: 'asdf:1234', text_field_tesim: 'a field to be searched' }, add_attributes: { commitWithin: 10_000 })
post "/dor/reindex/#{mock_druid}", params: { commitWithin: 10_000 }
expect(mock_solr_conn).to have_received(:add).with({ id: mock_druid, text_field_tesim: 'a field to be searched' }, add_attributes: { commitWithin: 10_000 })
expect(mock_solr_conn).not_to have_received(:commit)
expect(response.body).to eq "Successfully updated index for #{mock_druid}"
expect(response.code).to eq '200'
end

it 'can be used with asynchronous commits' do
get :reindex, params: { pid: mock_druid, commitWithin: 2 }
post "/dor/reindex/#{mock_druid}", params: { commitWithin: 2 }
expect(mock_solr_conn).to have_received(:add)
expect(mock_solr_conn).not_to have_received(:commit)
expect(response.body).to eq "Successfully updated index for #{mock_druid}"
Expand All @@ -44,32 +45,37 @@

it 'gives the right status if an object is not found' do
allow(Dor).to receive(:find).and_raise(ActiveFedora::ObjectNotFoundError)
get :reindex, params: { pid: mock_druid }
post "/dor/reindex/#{mock_druid}"
expect(response.body).to eq 'Object does not exist in Fedora.'
expect(response.code).to eq '404'
end
end

describe '#delete_from_index' do
it 'removes an object from the index' do
expect(ActiveFedora.solr.conn).to receive(:delete_by_id).with('asdf:1234', commitWithin: 1000)
expect(ActiveFedora.solr.conn).to receive(:commit)
get :delete_from_index, params: { pid: 'asdf:1234' }
allow(ActiveFedora.solr.conn).to receive(:delete_by_id)
allow(ActiveFedora.solr.conn).to receive(:commit)
post "/dor/delete_from_index/#{mock_druid}"
expect(ActiveFedora.solr.conn).to have_received(:delete_by_id).once.with(mock_druid, commitWithin: 1000)
expect(ActiveFedora.solr.conn).to have_received(:commit).once
end

it 'passes through the commitWithin parameter' do
expect(ActiveFedora.solr.conn).to receive(:delete_by_id).with('asdf:1234', commitWithin: 5000)
expect(ActiveFedora.solr.conn).not_to receive(:commit)
get :delete_from_index, params: { pid: 'asdf:1234', commitWithin: 5000 }
allow(ActiveFedora.solr.conn).to receive(:delete_by_id)
allow(ActiveFedora.solr.conn).to receive(:commit)
post "/dor/delete_from_index/#{mock_druid}", params: { commitWithin: 5000 }
expect(ActiveFedora.solr.conn).to have_received(:delete_by_id).once.with(mock_druid, commitWithin: 5000)
expect(ActiveFedora.solr.conn).not_to have_received(:commit)
end
end

describe '#queue_size' do
let(:mock_status) { instance_double(QueueStatus::All, queue_size: 15) }

it 'retrives the size of the backing message queues' do
expect(QueueStatus).to receive(:all).and_return(mock_status)
get :queue_size
allow(QueueStatus).to receive(:all).and_return(mock_status)
get '/dor/queue_size'
expect(QueueStatus).to have_received(:all).once
expect(JSON.parse(response.body)).to include('value' => 15)
end
end
Expand Down
11 changes: 11 additions & 0 deletions validate-openapi.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
set -ev

result=$(openapi-enforcer validate openapi.yml)
[[ $result =~ "Document is valid" ]] && {
echo "Validation good"
exit 0
} || {
echo $result
exit 1
}

0 comments on commit 87c26fe

Please sign in to comment.