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: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ gem "puma", ">= 5.0"
gem "rails", "~> 8.1.3"
gem "record_tag_helper", require: false
gem "rinku", require: "rails_rinku"
gem "rswag-api"
gem "rswag-ui"
gem "sentry-sidekiq"
gem "sprockets-rails"
gem "terser"
Expand Down
10 changes: 10 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -771,11 +771,17 @@ GEM
rspec-mocks (>= 3.13.0, < 5.0.0)
rspec-support (>= 3.13.0, < 5.0.0)
rspec-support (3.13.7)
rswag-api (2.17.0)
activesupport (>= 5.2, < 8.2)
railties (>= 5.2, < 8.2)
rswag-specs (2.17.0)
activesupport (>= 5.2, < 8.2)
json-schema (>= 2.2, < 7.0)
railties (>= 5.2, < 8.2)
rspec-core (>= 2.14)
rswag-ui (2.17.0)
actionpack (>= 5.2, < 8.2)
railties (>= 5.2, < 8.2)
rubocop (1.86.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
Expand Down Expand Up @@ -1000,7 +1006,9 @@ DEPENDENCIES
record_tag_helper
rinku
rspec-rails
rswag-api
rswag-specs
rswag-ui
rubocop-govuk
sentry-sidekiq
shoulda-matchers
Expand Down Expand Up @@ -1280,7 +1288,9 @@ CHECKSUMS
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
rswag-api (2.17.0) sha256=728b336b65168ab8ab6024b0e5d267b485c22ccdeb9dfbfb6ec3bac423545a13
rswag-specs (2.17.0) sha256=a3b2bdf6df89f8741fe4a4ee47ceb1e77dc13e1c96bbe07352117d6e61afa9e3
rswag-ui (2.17.0) sha256=5f707b9b5e8171ddf9f519f6e401e79e419bd1d07387508603e76124f2443212
rubocop (1.86.0) sha256=4ff1186fe16ebe9baff5e7aad66bb0ad4cabf5cdcd419f773146dbba2565d186
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
Expand Down
2 changes: 1 addition & 1 deletion app/public/queries/content_block/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def by_lead_organisation_id(lead_organisation_id)
def by_keyword(keyword)
return self if keyword.blank?

where("documents.id IN (?)", Document.with_keyword(keyword).pluck(:id))
where(id: Document.with_keyword(keyword).select(:id))
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions config/initializers/rswag_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Rswag::Api.configure do |c|
c.openapi_root = Rails.root.join("engines/api/swagger").to_s
end
3 changes: 3 additions & 0 deletions config/initializers/rswag_ui.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Rswag::Ui.configure do |c|
c.openapi_endpoint "/api-docs/v1/swagger.json", "Content Block Manager API V1"
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
get "/healthcheck/ready", to: GovukHealthcheck.rack_response
get "/accessibility-statement", to: "pages#accessibility_statement"
mount Flipflop::Engine => "/flipflop"
mount Rswag::Ui::Engine => "/api-docs"
mount Rswag::Api::Engine => "/api-docs"
mount FactCheck::Engine => "/fact-check"
mount BlockPreview::Engine => "/preview"
mount GovukSidekiq::GdsSsoMiddleware, at: "/sidekiq"
Expand Down
13 changes: 13 additions & 0 deletions engines/api/app/controllers/api/blocks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class Api::BlocksController < Api::ApplicationController
def search
return invalid_page_error if invalid_page?

result = ContentBlock::Query.call(filters)
render json: Api::ResultsPresenter.present(result, request.original_url)
end
Expand All @@ -9,4 +11,15 @@ def search
def filters
params.permit(:block_type, :lead_organisation_id, :keyword, :page)
end

def invalid_page?
return false if params[:page].blank?

page = Integer(params[:page], exception: false)
page.nil? || page < 1
end

def invalid_page_error
render json: { error: "page must be a positive integer" }, status: :bad_request
end
end
19 changes: 15 additions & 4 deletions engines/api/app/presenters/api/block_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Api::BlockPresenter
class OrganisationNotFound < StandardError; end

class << self
def present(block)
new(block).present
Expand All @@ -17,13 +19,22 @@ def present
{
title: @block.title,
block_type: @block.block_type,
organisation: {
name: @block.lead_organisation.name,
content_id: @block.lead_organisation.id,
},
organisation: organisation,
state: "published",
embed_code: @block.embed_code,
formats: @block.formats,
}
end

private

def organisation
org = @block.lead_organisation
unless org
raise OrganisationNotFound,
"Organisation not found for block '#{@block.title}'"
end

{ name: org.name, content_id: org.id }
end
end
15 changes: 10 additions & 5 deletions engines/api/app/presenters/api/results_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,16 @@ def self_link
end

def page_href(offset)
request_url.tap { |url|
query_hash = Rack::Utils.parse_query(url.query)
query_hash["page"] = result.current_page + offset
url.query = Rack::Utils.build_query(query_hash)
}.to_s
url = request_url.dup
query = query_with_page(result.current_page + offset)
url.query = query
url.to_s
end

def query_with_page(page_number)
query_hash = Rack::Utils.parse_query(request_url.query)
query_hash["page"] = page_number
Rack::Utils.build_query(query_hash)
end

def previous_page?
Expand Down
3 changes: 2 additions & 1 deletion engines/api/features/api/step_definitions/api_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
end

Given(/^there are the following published content blocks:$/) do |table|
table.hashes.each do |hash|
table.hashes.each_with_index do |hash, index|
hash["lead_organisation_id"] = Organisation.all.find { |org| org.name == hash["organisation"] }.id
hash["document"] = create(:document, block_type: hash["block_type"])
hash["created_at"] = index.days.ago
create(:edition, :published, **hash.except("block_type", "organisation"))
end
end
Expand Down
3 changes: 2 additions & 1 deletion engines/api/lib/tasks/generate_swagger.rake
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace :api do
desc "Generate Swagger documentation from RSpec tests"
task generate_swagger: :environment do
ENV["PATTERN"] = "{spec,engines/*/spec}/**/*_spec.rb"
ENV["PATTERN"] = "engines/api/spec/requests/**/*_spec.rb"
ENV["SWAGGER_DRY_RUN"] = "0"
Rake::Task["rswag:specs:swaggerize"].invoke
end
end
61 changes: 58 additions & 3 deletions engines/api/spec/requests/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
RSpec.describe "API" do
path "/blocks/search" do
let(:organisations) do
build_list(:organisation, 3)
[
build(:organisation, id: "aa1b2c3d-1234-5678-abcd-000000000001", name: "HM Revenue & Customs"),
build(:organisation, id: "aa1b2c3d-1234-5678-abcd-000000000002", name: "Foreign, Commonwealth & Development Office"),
build(:organisation, id: "aa1b2c3d-1234-5678-abcd-000000000003", name: "Department for Work and Pensions"),
]
end

before do
Expand All @@ -30,7 +34,13 @@

response "200", "blocks found" do
before do
create(:edition, :published, document: create(:document), lead_organisation_id: organisations.first.id)
create(
:edition,
:published,
title: "Current Tax Year",
document: create(:document, block_type: "time_period", sluggable_string: "current-tax-year"),
lead_organisation_id: organisations.first.id,
)
end

schema type: :object,
Expand Down Expand Up @@ -70,6 +80,16 @@
},
},
}

after do |example|
content = example.metadata[:response][:content] || {}
example.metadata[:response][:content] = content.merge(
"application/json" => {
example: JSON.parse(response.body, symbolize_names: true),
},
)
end

run_test!
end

Expand Down Expand Up @@ -148,7 +168,7 @@
before do
# Stub the default page size to 1 so that we can test pagination with a small number of records
stub_const("ContentBlock::Query::DEFAULT_PAGE_SIZE", 1)
create_list(:edition, 3, :published, document: create(:document), lead_organisation_id: organisations.first.id)
3.times { create(:edition, :published, document: create(:document), lead_organisation_id: organisations.first.id) }
end

response "200", "returns the first page", document: false do
Expand Down Expand Up @@ -206,6 +226,41 @@
end
end
end

context "invalid page numbers" do
before do
create(:edition, :published, document: create(:document), lead_organisation_id: organisations.first.id)
end

response "200", "returns empty results for out-of-range page", document: false do
let(:page) { 999 }

run_test! do |response|
data = JSON.parse(response.body)
expect(data["results"]).to be_empty
expect(data["total"]).to eq(1)
expect(data["current_page"]).to eq(999)
end
end

response "400", "returns error for negative page number", document: false do
let(:page) { -1 }

run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to be_present
end
end

response "400", "returns error for zero page number", document: false do
let(:page) { 0 }

run_test! do |response|
data = JSON.parse(response.body)
expect(data["error"]).to be_present
end
end
end
end
end

Expand Down
11 changes: 11 additions & 0 deletions engines/api/spec/unit/presenters/api/block_presenter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
content_id: lead_org.id,
)
end

it "raises OrganisationNotFound when lead_organisation is nil" do
block = build(:content_block)
allow(block).to receive(:lead_organisation).and_return(nil)

expect { described_class.present(block) }
.to raise_error(
Api::BlockPresenter::OrganisationNotFound,
/Organisation not found/,
)
end
end

describe ".present_collection" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,37 @@
"description": "blocks found",
"content": {
"application/json": {
"example": {
"total": 1,
"pages": 1,
"current_page": 1,
"links": [
{
"href": "http://www.example.com/api/blocks/search?page=1",
"rel": "self"
}
],
"results": [
{
"title": "Current Tax Year",
"block_type": "Time period",
"organisation": {
"name": "HM Revenue & Customs",
"content_id": "aa1b2c3d-1234-5678-abcd-000000000001"
},
"state": "published",
"embed_code": "{{embed:content_block_time_period:current-tax-year}}",
"formats": [
"long_form",
"months_and_years_long",
"start_day_and_month",
"start_month_as_word",
"years",
"years_short"
]
}
]
},
"schema": {
"type": "object",
"additionalProperties": false,
Expand Down
2 changes: 1 addition & 1 deletion spec/swagger_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
config.openapi_root = Rails.root.join("engines/api/swagger").to_s

config.openapi_specs = {
"v1/swagger.yaml" => {
"v1/swagger.json" => {
openapi: "3.0.1",
info: {
title: "Content Block Manager API",
Expand Down
4 changes: 3 additions & 1 deletion spec/unit/app/public/queries/content_block/query_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
_edition_without_first_keyword = create(:edition, :published, document: document_without_first_keyword,
title: "second")

allow(Document).to receive(:with_keyword).and_return([document_with_first_keyword])
allow(Document).to receive(:with_keyword)
.with("keyword")
.and_return(Document.where(id: document_with_first_keyword.id))

result = described_class.call(keyword: "keyword")

Expand Down