Skip to content

Commit de8724f

Browse files
committed
Schema Validation tests
1 parent 6e2d7cb commit de8724f

2 files changed

Lines changed: 84 additions & 30 deletions

File tree

obp-api/src/main/scala/code/api/util/JsonSchemaGenerator.scala

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,8 @@ object JsonSchemaGenerator {
3333
("message_format" -> messageDoc.messageFormat) ~
3434
("outbound_topic" -> messageDoc.outboundTopic) ~
3535
("inbound_topic" -> messageDoc.inboundTopic) ~
36-
("outbound_schema" -> (
37-
("$schema" -> "http://json-schema.org/draft-07/schema#") ~
38-
typeToJsonSchema(outboundType)
39-
)) ~
40-
("inbound_schema" -> (
41-
("$schema" -> "http://json-schema.org/draft-07/schema#") ~
42-
typeToJsonSchema(inboundType)
43-
)) ~
36+
("outbound_schema" -> typeToJsonSchema(outboundType)) ~
37+
("inbound_schema" -> typeToJsonSchema(inboundType)) ~
4438
("adapter_implementation" -> messageDoc.adapterImplementation.map { impl =>
4539
("group" -> impl.group) ~
4640
("suggested_order" -> JInt(BigInt(impl.suggestedOrder)))

obp-api/src/test/scala/code/api/v6_0_0/MessageDocsJsonSchemaTest.scala

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,36 @@ import code.api.util.APIUtil.OAuth._
44
import code.api.util.ApiRole
55
import code.api.util.ErrorMessages.InvalidConnector
66
import code.api.v6_0_0.OBPAPI6_0_0.Implementations6_0_0
7+
import com.fasterxml.jackson.databind.ObjectMapper
78
import com.github.dwickern.macros.NameOf.nameOf
9+
import com.networknt.schema.{JsonSchemaFactory, SpecVersion}
810
import com.openbankproject.commons.util.ApiVersion
911
import net.liftweb.json._
1012
import org.scalatest.Tag
1113

14+
/**
15+
* Tests for the Message Docs JSON Schema endpoint (v6.0.0)
16+
*
17+
* This endpoint returns message documentation as JSON Schema format for code generation.
18+
* The schema follows JSON Schema draft-07 specification and is validated using the
19+
* networknt/json-schema-validator library (https://github.com/networknt/json-schema-validator).
20+
*
21+
* Schema structure:
22+
* - Root level: $schema, title, description, type, properties, definitions
23+
* - Each message includes: process, description, outbound_schema, inbound_schema
24+
* - Type definitions use $ref references to the definitions section
25+
* - All definitions have: type: "object", properties, required (for non-Option fields)
26+
*
27+
* Industry Standard Compliance:
28+
* - Validated against JSON Schema draft-07 meta-schema
29+
* - Uses standard $ref for type references
30+
* - Compatible with code generation tools like quicktype
31+
*/
1232
class MessageDocsJsonSchemaTest extends V600ServerSetup {
13-
33+
34+
// Jackson ObjectMapper for converting between Lift JSON and Jackson JsonNode
35+
private val mapper = new ObjectMapper()
36+
1437
override def beforeAll(): Unit = {
1538
super.beforeAll()
1639
}
@@ -77,25 +100,17 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
77100

78101
And("Outbound schema should be valid JSON Schema")
79102
val outboundSchema = (firstMessage \ "outbound_schema").extract[JObject]
80-
val outboundSchemaVersion = (outboundSchema \ "$schema").extractOpt[String]
81-
outboundSchemaVersion shouldBe defined
82-
103+
// Schema can have either a direct "type" or a "$ref" to definitions for case classes
83104
val outboundType = (outboundSchema \ "type").extractOpt[String]
84-
outboundType shouldBe Some("object")
85-
86-
val outboundProperties = (outboundSchema \ "properties").extractOpt[JObject]
87-
outboundProperties shouldBe defined
88-
105+
val outboundRef = (outboundSchema \ "$ref").extractOpt[String]
106+
(outboundType.isDefined || outboundRef.isDefined) shouldBe true
107+
89108
And("Inbound schema should be valid JSON Schema")
90109
val inboundSchema = (firstMessage \ "inbound_schema").extract[JObject]
91-
val inboundSchemaVersion = (inboundSchema \ "$schema").extractOpt[String]
92-
inboundSchemaVersion shouldBe defined
93-
110+
// Schema can have either a direct "type" or a "$ref" to definitions for case classes
94111
val inboundType = (inboundSchema \ "type").extractOpt[String]
95-
inboundType shouldBe Some("object")
96-
97-
val inboundProperties = (inboundSchema \ "properties").extractOpt[JObject]
98-
inboundProperties shouldBe defined
112+
val inboundRef = (inboundSchema \ "$ref").extractOpt[String]
113+
(inboundType.isDefined || inboundRef.isDefined) shouldBe true
99114
}
100115

101116
scenario("We get JSON Schema for rest_vMar2019 connector", ApiEndpoint1, VersionOfApi) {
@@ -130,14 +145,14 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
130145
When("We make a request with invalid connector name")
131146
val request = (v6_0_0_Request / "message-docs" / "invalid_connector" / "json-schema").GET
132147
val response = makeGetRequest(request)
133-
148+
134149
Then("We should get a 400 Bad Request response")
135150
response.code should equal(400)
136-
151+
137152
And("Error message should mention invalid connector")
138153
val errorMessage = (response.body \ "message").extractOpt[String]
139154
errorMessage shouldBe defined
140-
errorMessage.get should include("InvalidConnector")
155+
errorMessage.get should include("Invalid Connector")
141156
}
142157

143158
scenario("We verify schema includes nested type definitions", ApiEndpoint1, VersionOfApi) {
@@ -201,19 +216,64 @@ class MessageDocsJsonSchemaTest extends V600ServerSetup {
201216
When("We make a request to get message docs as JSON Schema")
202217
val request = (v6_0_0_Request / "message-docs" / "rabbitmq_vOct2024" / "json-schema").GET
203218
val response = makeGetRequest(request)
204-
219+
205220
Then("We should get a 200 OK response")
206221
response.code should equal(200)
207-
222+
208223
And("Process names should follow obp.methodName pattern")
209224
val json = response.body.extract[JValue]
210225
val messages = (json \ "properties" \ "messages" \ "items").extract[List[JValue]]
211-
226+
212227
messages.foreach { message =>
213228
val process = (message \ "process").extract[String]
214229
process should startWith("obp.")
215230
process.length should be > 4
216231
}
217232
}
233+
234+
scenario("We validate schema is industry-standard JSON Schema draft-07 using networknt validator", ApiEndpoint1, VersionOfApi) {
235+
When("We make a request to get message docs as JSON Schema")
236+
val request = (v6_0_0_Request / "message-docs" / "rabbitmq_vOct2024" / "json-schema").GET
237+
val response = makeGetRequest(request)
238+
239+
Then("We should get a 200 OK response")
240+
response.code should equal(200)
241+
242+
And("Schema should be valid according to JSON Schema draft-07 specification")
243+
val schemaString = compactRender(response.body)
244+
val schemaNode = mapper.readTree(schemaString)
245+
246+
// Use networknt JSON Schema validator with draft-07
247+
val factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)
248+
val jsonSchema = factory.getSchema(schemaNode)
249+
250+
// The schema should load without errors (this validates the schema structure)
251+
jsonSchema should not be null
252+
253+
And("Schema should have valid definitions that can be resolved")
254+
val definitions = (response.body \ "definitions").extract[JObject]
255+
definitions.obj.length should be > 100 // Should have many type definitions
256+
257+
And("Each definition should be valid JSON Schema")
258+
definitions.obj.foreach { case JField(name, defn) =>
259+
val defnString = compactRender(defn)
260+
val defnNode = mapper.readTree(defnString)
261+
// Create a schema from each definition to validate it
262+
val defnSchema = factory.getSchema(defnNode)
263+
defnSchema should not be null
264+
}
265+
266+
And("$ref references should resolve correctly within the schema")
267+
val messages = (response.body \ "properties" \ "messages" \ "items").extract[List[JValue]]
268+
val firstMessage = messages.head
269+
val outboundRef = (firstMessage \ "outbound_schema" \ "$ref").extractOpt[String]
270+
outboundRef shouldBe defined
271+
outboundRef.get should startWith("#/definitions/")
272+
273+
// Extract the referenced definition name and verify it exists
274+
val refName = outboundRef.get.replace("#/definitions/", "")
275+
val definitionNames = definitions.obj.map(_.name)
276+
definitionNames should contain(refName)
277+
}
218278
}
219279
}

0 commit comments

Comments
 (0)