diff --git a/Gemfile.lock b/Gemfile.lock index 68f2b7db..dcb04e4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,6 +116,7 @@ GEM nokogiri (1.10.10) mini_portile2 (~> 2.4.0) pg (1.2.3) + public_suffix (4.0.6) puma (5.1.0) nio4r (~> 2.0) rack (2.2.3) diff --git a/app/filters/validate_schema.rb b/app/filters/validate_schema.rb index 0ced429a..d55922c2 100644 --- a/app/filters/validate_schema.rb +++ b/app/filters/validate_schema.rb @@ -16,16 +16,22 @@ def before(controller) end def validate(metadata, schema_name) - schema = Rails.application.config.schemas[schema_name] ||= find(schema_name) + schema = JSON::Validator.schema_for_uri(schema_name)&.schema || find(schema_name) JSON::Validator.validate!(schema, metadata) end def find(schema_name) schema_file = schema_name.gsub('.', '/') - schema = File.read(File.join( - Rails.application.config.schemas_directory, "#{schema_file}.json") + schema = JSON.parse( + File.read( + File.join( + Rails.application.config.schemas_directory, "#{schema_file}.json" + ) + ) ) - JSON.parse(schema) + jschema = JSON::Schema.new(schema, Addressable::URI.parse(schema['_name'])) + JSON::Validator.add_schema(jschema) + JSON::Validator.schema_for_uri(schema_name).schema rescue Errno::ENOENT raise SchemaNotFoundError.new("Schema not found => #{schema_name}") end diff --git a/config/initializers/schemas.rb b/config/initializers/schemas.rb index 00b4d7b2..0f823cda 100644 --- a/config/initializers/schemas.rb +++ b/config/initializers/schemas.rb @@ -1,3 +1,8 @@ -Rails.application.config.schemas_directory = File.join(File.dirname(__FILE__), '..', '..', 'schemas') +Rails.application.config.schemas_directory = File.join(Rails.root, 'schemas') -Rails.application.config.schemas = {} +schemas = Dir.glob("#{Rails.application.config.schemas_directory}/*/**") +schemas.each do |schema_file| + schema = JSON.parse(File.read(schema_file)) + jschema = JSON::Schema.new(schema, Addressable::URI.parse(schema['_name'])) + JSON::Validator.add_schema(jschema) +end diff --git a/schemas/config/service.json b/schemas/config/service.json index b81f8a79..ea6bcbdc 100644 --- a/schemas/config/service.json +++ b/schemas/config/service.json @@ -82,7 +82,7 @@ }, "allOf": [ { - "$ref": "../definition/data.json" + "$ref": "definition.data" } ], "category": [ diff --git a/schemas/definition/block.json b/schemas/definition/block.json index b19265be..c3325fda 100644 --- a/schemas/definition/block.json +++ b/schemas/definition/block.json @@ -11,7 +11,7 @@ "description": "Whether to show or hide the page/component", "oneOf": [ { - "$ref": "../definition/conditional.boolean.json" + "$ref": "definition.conditional.boolean" } ], "default": true @@ -19,7 +19,7 @@ }, "allOf": [ { - "$ref": "../definition/data.json" + "$ref": "definition.data" } ], "category": [ diff --git a/schemas/definition/component.json b/schemas/definition/component.json index 9d1ccac1..ae327fc2 100644 --- a/schemas/definition/component.json +++ b/schemas/definition/component.json @@ -4,7 +4,7 @@ "title": "Component definition", "allOf": [ { - "$ref": "../definition/block.json" + "$ref": "definition.block" } ], "category": [ diff --git a/schemas/definition/conditional.boolean.json b/schemas/definition/conditional.boolean.json index 1bc01047..b81e0b46 100644 --- a/schemas/definition/conditional.boolean.json +++ b/schemas/definition/conditional.boolean.json @@ -11,7 +11,7 @@ "type": "boolean" }, { - "$ref": "../condition/base.json" + "$ref": "condition.base" } ], "category": [ diff --git a/schemas/definition/control.json b/schemas/definition/control.json index a7c28442..582b3ade 100644 --- a/schemas/definition/control.json +++ b/schemas/definition/control.json @@ -4,16 +4,16 @@ "title": "Control definition", "allOf": [ { - "$ref": "../definition/component.json" + "$ref": "definition.component" }, { - "$ref": "../definition/html_attributes" + "$ref": "definition.html_attributes" }, { - "$ref": "../definition/namespace" + "$ref": "definition.namespace" }, { - "$ref": "http://gov.uk/schema/v1.0.0/validations#/definitions/requiredBundle" + "$ref": "validations#/definitions/required_bundle" } ], "properties": { @@ -60,7 +60,7 @@ "description": "Whether the control should be disabled", "oneOf": [ { - "$ref": "../definition/conditional.boolean.json" + "$ref": "definition.conditional.boolean" } ], "default": false, diff --git a/schemas/definition/field.json b/schemas/definition/field.json index 5d0ae535..a883edb3 100644 --- a/schemas/definition/field.json +++ b/schemas/definition/field.json @@ -4,16 +4,16 @@ "title": "Field definition", "allOf": [ { - "$ref": "../definition/label.json" + "$ref": "definition.label" }, { - "$ref": "../definition/control.json" + "$ref": "definition.control" }, { - "$ref": "../definition/repeatable.json" + "$ref": "definition.repeatable" }, { - "$ref": "../definition/name.json" + "$ref": "definition.name" } ], "required": [ diff --git a/schemas/definition/repeatable.json b/schemas/definition/repeatable.json index f16345b6..7a366411 100644 --- a/schemas/definition/repeatable.json +++ b/schemas/definition/repeatable.json @@ -63,7 +63,7 @@ }, "allOf": [ { - "$ref": "../definition/namespace.json" + "$ref": "definition.namespace" } ], "category": [ diff --git a/schemas/request/services.json b/schemas/request/services.json index f1a684c9..e0046c94 100644 --- a/schemas/request/services.json +++ b/schemas/request/services.json @@ -15,10 +15,10 @@ "type": "string" }, "configuration": { - "$ref": "schemas/config/service.json" + "$ref": "config.service" }, "locale": { - "$ref": "schemas/service/locale.json" + "$ref": "service.locale" }, "pages": { "type": "array", diff --git a/schemas/request/versions.json b/schemas/request/versions.json index 80f4ab5f..cf351738 100644 --- a/schemas/request/versions.json +++ b/schemas/request/versions.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "metadata": { - "$ref": "schemas/service/base.json" + "$ref": "service.base" } }, "required": ["metadata"] diff --git a/schemas/request/versions/create.json b/schemas/request/versions/create.json deleted file mode 100644 index 02a9aed2..00000000 --- a/schemas/request/versions/create.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$id": "http://gov.uk/schema/v1.0.0/request/versions/create", - "_name": "request.versions.create", - "title": "Create new service version", - "description": "Request schema for creating a new version of a service", - "type": "object", - "properties": { - "metadata": { - "$ref": "schemas/service/base.json" - } - }, - "required": ["metadata"] -} diff --git a/schemas/service/base.json b/schemas/service/base.json index ffdaf2dc..48b3e000 100644 --- a/schemas/service/base.json +++ b/schemas/service/base.json @@ -21,10 +21,10 @@ "type": "string" }, "locale": { - "$ref": "../service/locale.json" + "$ref": "service.locale" }, "configuration": { - "$ref": "../config/service.json" + "$ref": "config.service" }, "pages": { "type": "array", diff --git a/spec/fixtures/service.json b/spec/fixtures/service.json new file mode 100644 index 00000000..64dece4a --- /dev/null +++ b/spec/fixtures/service.json @@ -0,0 +1,23 @@ +{ + "service_name": "Complain about a court or tribunal", + "created_by": "4634ec01-5618-45ec-a4e2-bb5aa587e751", + "configuration": { + "_id": "service", + "_type": "config.service" + }, + "pages": [ + { + "_id": "page.start", + "_type": "page.start", + "body": "You cannot use this form to complain about:\r\n\r\n* the result of a case\r\n* a judge, magistrate, coroner or member of a tribunal\r\n\r\nThis online form is also available in [Welsh (Cymraeg)](https://complain-about-a-court-or-tribunal.form.service.justice.gov.uk/cy).", + "heading": "Complain about a court or tribunal", + "lede": "Your complaint will not affect your case.", + "steps": [ + "page.name", + "page.email-address" + ], + "url": "/" + } + ], + "locale": "en" +} diff --git a/spec/fixtures/version.json b/spec/fixtures/version.json new file mode 100644 index 00000000..e7a1e26a --- /dev/null +++ b/spec/fixtures/version.json @@ -0,0 +1,62 @@ +{ + "service_id": "d243f5c0-e8a2-4b13-982c-7b5f3fa128cf", + "service_name": "Complain about a court or tribunal", + "created_at": "2020-10-09T11:51:46", + "created_by": "4634ec01-5618-45ec-a4e2-bb5aa587e751", + "configuration": { + "_id": "service", + "_type": "config.service" + }, + "pages": [ + { + "_id": "page.start", + "_type": "page.start", + "body": "You cannot use this form to complain about:\r\n\r\n* the result of a case\r\n* a judge, magistrate, coroner or member of a tribunal\r\n\r\nThis online form is also available in [Welsh (Cymraeg)](https://complain-about-a-court-or-tribunal.form.service.justice.gov.uk/cy).", + "heading": "Complain about a court or tribunal", + "lede": "Your complaint will not affect your case.", + "steps": [ + "page.name", + "page.email-address" + ], + "url": "/" + }, + { + "_id": "page.name", + "_type": "page.singlequestion", + "components": [ + { + "_id": "page.name--text.auto_name__1", + "_type": "text", + "label": "Full name", + "name": "full_name" + } + ], + "heading": "Your name", + "url": "/name" + }, + { + "_id": "page.email-address", + "_type": "page.singlequestion", + "heading": "Email address", + "components": [ + { + "_id": "page.email-address--email.auto_name__2", + "_type": "text", + "errors": { + "format": {}, + "required": { + "any": "Enter an email address" + } + }, + "label": "Your email address", + "name": "email_address", + "validation": { + "required": true + } + } + ], + "url": "/email-address" + } + ], + "locale": "en" +} diff --git a/spec/integration/endpoints_spec.rb b/spec/integration/endpoints_spec.rb index a9977b3f..d93b341e 100644 --- a/spec/integration/endpoints_spec.rb +++ b/spec/integration/endpoints_spec.rb @@ -20,16 +20,14 @@ { Authorization: "Bearer #{access_token}" } end let(:service) do - { - "service_name": "Service Name", - "created_by": "4634ec01-5618-45ec-a4e2-bb5aa587e751", - "configuration": { - "_id": "service", - "_type": "config.service" - }, - "pages": [], - "locale": "en" - } + JSON.parse( + File.read(Rails.root.join('spec', 'fixtures', 'service.json')) + ).deep_symbolize_keys + end + let(:version) do + JSON.parse( + File.read(Rails.root.join('spec', 'fixtures', 'version.json')) + ).deep_symbolize_keys end let(:request_body) { { "metadata": service }.to_json } @@ -41,7 +39,7 @@ def parse_response(response) context 'when a new service is created' do it 'we can request a specific version' do response = metadata_api_test_client.create_service( - body: request_body, + body: { "metadata": service }.to_json, authorisation_headers: authorisation_headers ) @@ -60,18 +58,7 @@ def parse_response(response) metadata = parse_response(response) expect(response.code).to be(200) expect(metadata).to include( - { - "service_name": "Service Name", - "service_id": service_id, - "version_id": version_id, - "created_by": "4634ec01-5618-45ec-a4e2-bb5aa587e751", - "configuration": { - "_id": "service", - "_type": "config.service" - }, - "pages": [], - "locale": "en" - } + service.merge({ service_id: service_id, version_id: version_id }) ) end end @@ -84,14 +71,7 @@ def parse_response(response) ) metadata = parse_response(response) - pages = [ - { - "_id": "page.start", - "_type": "page.start", - "url": "/" - } - ] - updated_payload = { "metadata": metadata.merge("pages": pages) } + updated_payload = { "metadata": version.merge(service_id: metadata[:service_id]) } response = metadata_api_test_client.new_version( service_id: metadata[:service_id], body: updated_payload.to_json, @@ -100,7 +80,7 @@ def parse_response(response) updated_metadata = parse_response(response) expect(response.code).to be(201) - expect(updated_metadata).to include(pages: pages) + expect(updated_metadata).to include(pages: version[:pages]) end end diff --git a/spec/serialisers/metadata_serialiser_spec.rb b/spec/serialisers/metadata_serialiser_spec.rb new file mode 100644 index 00000000..a1966806 --- /dev/null +++ b/spec/serialisers/metadata_serialiser_spec.rb @@ -0,0 +1,24 @@ +RSpec.describe MetadataSerialiser do + let(:service_metadata) do + JSON.parse(File.read(Rails.root.join('spec', 'fixtures', 'service.json'))) + end + let(:service_params) do + { + name: service_metadata['service_name'], + created_by: service_metadata['created_by'], + metadata_attributes: [{ + data: service_metadata, + created_by: service_metadata['created_by'], + locale: service_metadata['locale'] || 'en' + }] + } + end + + context '#latest_metadata' do + it 'metadata should be valid against the base schema' do + service = Service.create(service_params) + serialiser = MetadataSerialiser.new(service, service.latest_metadata) + expect(serialiser.attributes).to match_schema('service.base') + end + end +end diff --git a/spec/support/schema_matcher.rb b/spec/support/schema_matcher.rb new file mode 100644 index 00000000..d3107fe4 --- /dev/null +++ b/spec/support/schema_matcher.rb @@ -0,0 +1,5 @@ +RSpec::Matchers.define :match_schema do |schema| + match do |metadata| + ValidateSchema.validate(metadata, schema) + end +end