@@ -4,13 +4,36 @@ import code.api.util.APIUtil.OAuth._
44import code .api .util .ApiRole
55import code .api .util .ErrorMessages .InvalidConnector
66import code .api .v6_0_0 .OBPAPI6_0_0 .Implementations6_0_0
7+ import com .fasterxml .jackson .databind .ObjectMapper
78import com .github .dwickern .macros .NameOf .nameOf
9+ import com .networknt .schema .{JsonSchemaFactory , SpecVersion }
810import com .openbankproject .commons .util .ApiVersion
911import net .liftweb .json ._
1012import 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+ */
1232class 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