Skip to content

Commit 173e0fb

Browse files
authored
Merge pull request #770 from commercetools/Add-json-type-switch-tests
Add JsonTypeSwitch tests
2 parents e55da37 + ccfb418 commit 173e0fb

3 files changed

Lines changed: 215 additions & 34 deletions

File tree

json/json-derivation/src/test/scala/io/sphere/json/TypesSwitchSpec.scala renamed to json/json-derivation/src/test/scala/io/sphere/json/TypeSelectorContainerSpec.scala

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,11 @@
11
package io.sphere.json
22

33
import io.sphere.json.generic.{TypeSelectorContainer, deriveJSON, jsonTypeSwitch}
4-
import org.json4s._
54
import org.scalatest.matchers.must.Matchers
65
import org.scalatest.wordspec.AnyWordSpec
76

8-
class TypesSwitchSpec extends AnyWordSpec with Matchers {
9-
import TypesSwitchSpec._
10-
11-
"jsonTypeSwitch" must {
12-
"combine different sum types tree" in {
13-
val m: Seq[Message] = List(
14-
TypeA.ClassA1(23),
15-
TypeA.ClassA2("world"),
16-
TypeB.ClassB1(valid = false),
17-
TypeB.ClassB2(Seq("a23", "c62")))
18-
19-
val jsons = m.map(Message.json.write)
20-
jsons must be(
21-
List(
22-
JObject("number" -> JLong(23), "type" -> JString("ClassA1")),
23-
JObject("name" -> JString("world"), "type" -> JString("ClassA2")),
24-
JObject("valid" -> JBool(false), "type" -> JString("ClassB1")),
25-
JObject(
26-
"references" -> JArray(List(JString("a23"), JString("c62"))),
27-
"type" -> JString("ClassB2"))
28-
))
29-
30-
val messages = jsons.map(Message.json.read).map(_.toOption.get)
31-
messages must be(m)
32-
}
33-
}
7+
class TypeSelectorContainerSpec extends AnyWordSpec with Matchers {
8+
import TypeSelectorContainerSpec._
349

3510
"TypeSelectorContainer" must {
3611
"have information about type value discriminators" in {
@@ -48,19 +23,19 @@ class TypesSwitchSpec extends AnyWordSpec with Matchers {
4823
selectors.map(_.typeField) must be(List("type", "type", "type", "type", "type", "type"))
4924

5025
selectors.map(_.clazz.getName) must contain.allOf(
51-
"io.sphere.json.TypesSwitchSpec$TypeA$ClassA1",
52-
"io.sphere.json.TypesSwitchSpec$TypeA$ClassA2",
53-
"io.sphere.json.TypesSwitchSpec$TypeA",
54-
"io.sphere.json.TypesSwitchSpec$TypeB$ClassB1",
55-
"io.sphere.json.TypesSwitchSpec$TypeB$ClassB2",
56-
"io.sphere.json.TypesSwitchSpec$TypeB"
26+
"io.sphere.json.TypeSelectorContainerSpec$TypeA$ClassA1",
27+
"io.sphere.json.TypeSelectorContainerSpec$TypeA$ClassA2",
28+
"io.sphere.json.TypeSelectorContainerSpec$TypeA",
29+
"io.sphere.json.TypeSelectorContainerSpec$TypeB$ClassB1",
30+
"io.sphere.json.TypeSelectorContainerSpec$TypeB$ClassB2",
31+
"io.sphere.json.TypeSelectorContainerSpec$TypeB"
5732
)
5833
}
5934
}
6035

6136
}
6237

63-
object TypesSwitchSpec {
38+
object TypeSelectorContainerSpec {
6439

6540
trait Message
6641
object Message {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.sphere.json.generic
2+
3+
import io.sphere.json.JSON
4+
import org.scalatest.matchers.must.Matchers
5+
import org.scalatest.wordspec.AnyWordSpec
6+
7+
class JsonTypeSwitchSpec extends AnyWordSpec with Matchers with JsonTypeSwitchSpecCommon {
8+
import JsonTypeSwitchModels._
9+
10+
"jsonTypeSwitch" must {
11+
12+
"derive a subset of a sealed trait" in {
13+
val format: JSON[A] = jsonTypeSwitch[A, B, C](Nil)
14+
testDeriveASubsetOfASealedTrait(format)
15+
}
16+
17+
"derive a subset of a sealed trait with a mongoKey" in {
18+
val format: JSON[A] = jsonTypeSwitch[A, B, D](Nil)
19+
testDeriveSubsetWithMongoKey(format)
20+
}
21+
22+
"combine different sum types tree" in {
23+
val format: JSON[Message] = jsonTypeSwitch[Message, TypeA, TypeB](Nil)
24+
testCombineSumTypes(format)
25+
}
26+
27+
"handle custom implementations for subtypes" in {
28+
implicit val jsonB: JSON[B] = customJsonB
29+
val format: JSON[A] = jsonTypeSwitch[A, B, D, C](Nil)
30+
testCustomSubtypeImpl(format)
31+
}
32+
33+
"handle the PlatformFormattedNotification case" in {
34+
testPlatformFormattedNotificationCase()
35+
}
36+
}
37+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package io.sphere.json.generic
2+
3+
import cats.data.Validated.Valid
4+
import cats.implicits._
5+
import io.sphere.json.{JSON, JValidation, parseJSON}
6+
import io.sphere.util.test._
7+
import org.json4s.DefaultReaders.StringReader
8+
import org.json4s._
9+
import org.scalatest.matchers.must.Matchers
10+
import org.scalatest.wordspec.AnyWordSpec
11+
12+
/** Shared test case logic for jsonTypeSwitch, because the scala 3 tests has a new syntax for
13+
* jsonTypeSwitch (using tuples, instead of fixed parameter lists) This way we can tests both
14+
* syntaxes
15+
*/
16+
trait JsonTypeSwitchSpecCommon { self: AnyWordSpec with Matchers =>
17+
import JsonTypeSwitchModels._
18+
19+
def testDeriveASubsetOfASealedTrait(format: JSON[A]): Unit = {
20+
val b = B(123)
21+
val jsonB = format.write(b)
22+
val b2 = format.read(jsonB).expectValid
23+
b2 must be(b)
24+
25+
val c = C(2345345)
26+
val jsonC = format.write(c)
27+
val c2 = format.read(jsonC).expectValid
28+
c2 must be(c)
29+
}
30+
31+
def testDeriveSubsetWithMongoKey(format: JSON[A]): Unit = {
32+
val d = D(123)
33+
val json = format.write(d)
34+
val d2 = format.read(json)
35+
36+
(json \ "type").as[String] must be("D2")
37+
d2 must be(Valid(d))
38+
}
39+
40+
def testCombineSumTypes(format: JSON[Message]): Unit = {
41+
val m: Seq[Message] = List(
42+
TypeA.ClassA1(23),
43+
TypeA.ClassA2("world"),
44+
TypeB.ClassB1(valid = false),
45+
TypeB.ClassB2(Seq("a23", "c62")))
46+
47+
val jsons = m.map(format.write)
48+
jsons must be(
49+
List(
50+
JObject("number" -> JLong(23), "type" -> JString("ClassA1")),
51+
JObject("name" -> JString("world"), "type" -> JString("ClassA2")),
52+
JObject("valid" -> JBool(false), "type" -> JString("ClassB1")),
53+
JObject(
54+
"references" -> JArray(List(JString("a23"), JString("c62"))),
55+
"type" -> JString("ClassB2"))
56+
))
57+
58+
val messages = jsons.map(format.read).map(_.toOption.get)
59+
messages must be(m)
60+
}
61+
62+
def testCustomSubtypeImpl(format: JSON[A]): Unit = {
63+
check[A](D(2345), """ {"type": "D2", "int": 2345 } """)(format)
64+
check[A](C(4), """ {"type": "C", "int": 4 } """)(format)
65+
check[A](B(34), """ {"type": "B", "field": "Custom-B-34" } """)(format)
66+
}
67+
68+
def testPlatformFormattedNotificationCase(): Unit = {
69+
val formatSub2 = jsonTypeSwitch[SubTrait2, SubTrait2.O3.type, SubTrait2.O4.type](Nil)
70+
val formatSub3 = jsonTypeSwitch[SubTrait3, SubTrait3.O5.type, SubTrait3.O6.type](Nil)
71+
72+
val typeSelectors = formatSub2.typeSelectors ++ formatSub3.typeSelectors
73+
val formatSuper: JSON[SuperTrait] = jsonTypeSwitch[SuperTrait, SubTrait1](typeSelectors)
74+
75+
val objs =
76+
List[SuperTrait](
77+
SubTrait1.O1,
78+
SubTrait1.O2,
79+
SubTrait2.O3,
80+
SubTrait2.O4,
81+
SubTrait3.O5,
82+
SubTrait3.O6)
83+
84+
val res = objs.map(formatSuper.write).map(formatSuper.read).sequence.expectValid
85+
86+
res must be(objs)
87+
}
88+
89+
private def check[T](a: T, json: String)(format: JSON[T]): Unit = {
90+
val parsedJson = parseJSON(json).expectValid
91+
val json2 = format.write(a)
92+
json2 must be(parsedJson)
93+
format.read(json2).expectValid must be(a)
94+
}
95+
}
96+
97+
object JsonTypeSwitchModels {
98+
sealed trait A
99+
case class B(int: Int) extends A
100+
object B { implicit val json: JSON[B] = deriveJSON }
101+
102+
case class C(int: Int) extends A
103+
object C { implicit val json: JSON[C] = deriveJSON }
104+
105+
@JSONTypeHint("D2") case class D(int: Int) extends A
106+
object D { implicit val json: JSON[D] = deriveJSON }
107+
108+
trait Message
109+
110+
sealed trait TypeA extends Message
111+
object TypeA {
112+
case class ClassA1(number: Int) extends TypeA
113+
case class ClassA2(name: String) extends TypeA
114+
implicit val json: JSON[TypeA] = deriveJSON[TypeA]
115+
}
116+
117+
sealed trait TypeB extends Message
118+
object TypeB {
119+
case class ClassB1(valid: Boolean) extends TypeB
120+
case class ClassB2(references: Seq[String]) extends TypeB
121+
implicit val json: JSON[TypeB] = deriveJSON[TypeB]
122+
}
123+
124+
trait SuperTrait
125+
126+
sealed trait SubTrait1 extends SuperTrait
127+
object SubTrait1 {
128+
case object O1 extends SubTrait1
129+
case object O2 extends SubTrait1
130+
implicit val json: JSON[SubTrait1] = deriveJSON
131+
}
132+
133+
sealed trait SubTrait2 extends SuperTrait
134+
object SubTrait2 {
135+
case object O3 extends SubTrait2 { implicit val json: JSON[O3.type] = deriveJSON }
136+
case object O4 extends SubTrait2 { implicit val json: JSON[O4.type] = deriveJSON }
137+
implicit val json: JSON[SubTrait2] = deriveJSON
138+
}
139+
140+
sealed trait SubTrait3 extends SuperTrait
141+
object SubTrait3 {
142+
case object O5 extends SubTrait3 { implicit val json: JSON[O5.type] = deriveJSON }
143+
case object O6 extends SubTrait3 { implicit val json: JSON[O6.type] = deriveJSON }
144+
implicit val json: JSON[SubTrait3] = deriveJSON
145+
}
146+
147+
sealed trait SubTrait4 extends SuperTrait
148+
object SubTrait4 {
149+
case object O7 extends SubTrait4 { implicit val json: JSON[O7.type] = deriveJSON }
150+
case object O8 extends SubTrait4 { implicit val json: JSON[O8.type] = deriveJSON }
151+
implicit val json: JSON[SubTrait4] = deriveJSON
152+
}
153+
154+
/** A custom JSON[B] for testing custom subtype implementations */
155+
val customJsonB: JSON[B] = new JSON[B] {
156+
override def read(jval: JValue): JValidation[B] = jval match {
157+
// JObject(List((field,JString(Custom-B-34)), (type,JString(B))))
158+
case JObject(fields) =>
159+
fields.collectFirst { case ("field", JString(s)) if s.startsWith("Custom-B-") => s } match {
160+
case Some(s) => Valid(B(s.stripPrefix("Custom-B-").toInt))
161+
case None => ???
162+
}
163+
case _ => ???
164+
}
165+
166+
override def write(value: B): JValue =
167+
JObject(List("field" -> JString(s"Custom-B-${value.int}")))
168+
}
169+
}

0 commit comments

Comments
 (0)