Schema Validation tests

This commit is contained in:
simonredfern 2026-01-21 10:10:26 +01:00
parent aaf04ee036
commit 7a27d3ac0d
2 changed files with 84 additions and 30 deletions

View File

@ -33,14 +33,8 @@ object JsonSchemaGenerator {
("message_format" -> messageDoc.messageFormat) ~
("outbound_topic" -> messageDoc.outboundTopic) ~
("inbound_topic" -> messageDoc.inboundTopic) ~
("outbound_schema" -> (
("$schema" -> "http://json-schema.org/draft-07/schema#") ~
typeToJsonSchema(outboundType)
)) ~
("inbound_schema" -> (
("$schema" -> "http://json-schema.org/draft-07/schema#") ~
typeToJsonSchema(inboundType)
)) ~
("outbound_schema" -> typeToJsonSchema(outboundType)) ~
("inbound_schema" -> typeToJsonSchema(inboundType)) ~
("adapter_implementation" -> messageDoc.adapterImplementation.map { impl =>
("group" -> impl.group) ~
("suggested_order" -> JInt(BigInt(impl.suggestedOrder)))

View File

@ -4,13 +4,36 @@ import code.api.util.APIUtil.OAuth._
import code.api.util.ApiRole
import code.api.util.ErrorMessages.InvalidConnector
import code.api.v6_0_0.OBPAPI6_0_0.Implementations6_0_0
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.dwickern.macros.NameOf.nameOf
import com.networknt.schema.{JsonSchemaFactory, SpecVersion}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json._
import org.scalatest.Tag
/**
* Tests for the Message Docs JSON Schema endpoint (v6.0.0)
*
* This endpoint returns message documentation as JSON Schema format for code generation.
* The schema follows JSON Schema draft-07 specification and is validated using the
* networknt/json-schema-validator library (https://github.com/networknt/json-schema-validator).
*
* Schema structure:
* - Root level: $schema, title, description, type, properties, definitions
* - Each message includes: process, description, outbound_schema, inbound_schema
* - Type definitions use $ref references to the definitions section
* - All definitions have: type: "object", properties, required (for non-Option fields)
*
* Industry Standard Compliance:
* - Validated against JSON Schema draft-07 meta-schema
* - Uses standard $ref for type references
* - Compatible with code generation tools like quicktype
*/
class MessageDocsJsonSchemaTest extends V600ServerSetup {
// Jackson ObjectMapper for converting between Lift JSON and Jackson JsonNode
private val mapper = new ObjectMapper()
override def beforeAll(): Unit = {
super.beforeAll()
}
@ -77,25 +100,17 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
And("Outbound schema should be valid JSON Schema")
val outboundSchema = (firstMessage \ "outbound_schema").extract[JObject]
val outboundSchemaVersion = (outboundSchema \ "$schema").extractOpt[String]
outboundSchemaVersion shouldBe defined
// Schema can have either a direct "type" or a "$ref" to definitions for case classes
val outboundType = (outboundSchema \ "type").extractOpt[String]
outboundType shouldBe Some("object")
val outboundProperties = (outboundSchema \ "properties").extractOpt[JObject]
outboundProperties shouldBe defined
val outboundRef = (outboundSchema \ "$ref").extractOpt[String]
(outboundType.isDefined || outboundRef.isDefined) shouldBe true
And("Inbound schema should be valid JSON Schema")
val inboundSchema = (firstMessage \ "inbound_schema").extract[JObject]
val inboundSchemaVersion = (inboundSchema \ "$schema").extractOpt[String]
inboundSchemaVersion shouldBe defined
// Schema can have either a direct "type" or a "$ref" to definitions for case classes
val inboundType = (inboundSchema \ "type").extractOpt[String]
inboundType shouldBe Some("object")
val inboundProperties = (inboundSchema \ "properties").extractOpt[JObject]
inboundProperties shouldBe defined
val inboundRef = (inboundSchema \ "$ref").extractOpt[String]
(inboundType.isDefined || inboundRef.isDefined) shouldBe true
}
scenario("We get JSON Schema for rest_vMar2019 connector", ApiEndpoint1, VersionOfApi) {
@ -130,14 +145,14 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
When("We make a request with invalid connector name")
val request = (v6_0_0_Request / "message-docs" / "invalid_connector" / "json-schema").GET
val response = makeGetRequest(request)
Then("We should get a 400 Bad Request response")
response.code should equal(400)
And("Error message should mention invalid connector")
val errorMessage = (response.body \ "message").extractOpt[String]
errorMessage shouldBe defined
errorMessage.get should include("InvalidConnector")
errorMessage.get should include("Invalid Connector")
}
scenario("We verify schema includes nested type definitions", ApiEndpoint1, VersionOfApi) {
@ -201,19 +216,64 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
When("We make a request to get message docs as JSON Schema")
val request = (v6_0_0_Request / "message-docs" / "rabbitmq_vOct2024" / "json-schema").GET
val response = makeGetRequest(request)
Then("We should get a 200 OK response")
response.code should equal(200)
And("Process names should follow obp.methodName pattern")
val json = response.body.extract[JValue]
val messages = (json \ "properties" \ "messages" \ "items").extract[List[JValue]]
messages.foreach { message =>
val process = (message \ "process").extract[String]
process should startWith("obp.")
process.length should be > 4
}
}
scenario("We validate schema is industry-standard JSON Schema draft-07 using networknt validator", ApiEndpoint1, VersionOfApi) {
When("We make a request to get message docs as JSON Schema")
val request = (v6_0_0_Request / "message-docs" / "rabbitmq_vOct2024" / "json-schema").GET
val response = makeGetRequest(request)
Then("We should get a 200 OK response")
response.code should equal(200)
And("Schema should be valid according to JSON Schema draft-07 specification")
val schemaString = compactRender(response.body)
val schemaNode = mapper.readTree(schemaString)
// Use networknt JSON Schema validator with draft-07
val factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)
val jsonSchema = factory.getSchema(schemaNode)
// The schema should load without errors (this validates the schema structure)
jsonSchema should not be null
And("Schema should have valid definitions that can be resolved")
val definitions = (response.body \ "definitions").extract[JObject]
definitions.obj.length should be > 100 // Should have many type definitions
And("Each definition should be valid JSON Schema")
definitions.obj.foreach { case JField(name, defn) =>
val defnString = compactRender(defn)
val defnNode = mapper.readTree(defnString)
// Create a schema from each definition to validate it
val defnSchema = factory.getSchema(defnNode)
defnSchema should not be null
}
And("$ref references should resolve correctly within the schema")
val messages = (response.body \ "properties" \ "messages" \ "items").extract[List[JValue]]
val firstMessage = messages.head
val outboundRef = (firstMessage \ "outbound_schema" \ "$ref").extractOpt[String]
outboundRef shouldBe defined
outboundRef.get should startWith("#/definitions/")
// Extract the referenced definition name and verify it exists
val refName = outboundRef.get.replace("#/definitions/", "")
val definitionNames = definitions.obj.map(_.name)
definitionNames should contain(refName)
}
}
}