|
| 1 | +package cloud.mindbox.mobile_sdk.models |
| 2 | + |
| 3 | +import com.google.gson.Gson |
| 4 | +import org.junit.Assert.assertEquals |
| 5 | +import org.junit.Assert.assertFalse |
| 6 | +import org.junit.Assert.assertNotNull |
| 7 | +import org.junit.Assert.assertNull |
| 8 | +import org.junit.Assert.assertTrue |
| 9 | +import org.junit.Test |
| 10 | + |
| 11 | +/** |
| 12 | + * Tests for [MindboxErrorAdapter] via the public [MindboxError.toJson] API. |
| 13 | + * |
| 14 | + * The adapter handles two directions: |
| 15 | + * - write (toJson) — used by SDK clients to log/pass errors around — fully implemented. |
| 16 | + * - read (fromJson) — parsing is effectively non-functional in the current implementation |
| 17 | + * (the read() method reads the JSON key name instead of key value, causing it to always |
| 18 | + * fall through to `else -> null`). Tests below document this known behavior. |
| 19 | + */ |
| 20 | +class MindboxErrorAdapterTest { |
| 21 | + |
| 22 | + // MindboxError subtypes have @JsonAdapter(MindboxErrorAdapter::class) |
| 23 | + private val gson = Gson() |
| 24 | + |
| 25 | + // region write / toJson — Validation |
| 26 | + |
| 27 | + @Test |
| 28 | + fun `toJson Validation - contains type MindboxError`() { |
| 29 | + val error = MindboxError.Validation( |
| 30 | + statusCode = 200, |
| 31 | + status = "ValidationError", |
| 32 | + validationMessages = emptyList(), |
| 33 | + ) |
| 34 | + val json = error.toJson() |
| 35 | + assertTrue(json.contains(""""type":"MindboxError"""")) |
| 36 | + } |
| 37 | + |
| 38 | + @Test |
| 39 | + fun `toJson Validation - contains statusCode`() { |
| 40 | + val error = MindboxError.Validation(200, "ValidationError", emptyList()) |
| 41 | + val json = error.toJson() |
| 42 | + assertTrue(json.contains(""""statusCode":200""")) |
| 43 | + } |
| 44 | + |
| 45 | + @Test |
| 46 | + fun `toJson Validation - contains status`() { |
| 47 | + val error = MindboxError.Validation(200, "ValidationError", emptyList()) |
| 48 | + val json = error.toJson() |
| 49 | + assertTrue(json.contains(""""status":"ValidationError"""")) |
| 50 | + } |
| 51 | + |
| 52 | + @Test |
| 53 | + fun `toJson Validation - contains empty validationMessages array`() { |
| 54 | + val error = MindboxError.Validation(200, "ValidationError", emptyList()) |
| 55 | + val json = error.toJson() |
| 56 | + assertTrue(json.contains(""""validationMessages":[]""")) |
| 57 | + } |
| 58 | + |
| 59 | + @Test |
| 60 | + fun `toJson Validation - contains validationMessages with entries`() { |
| 61 | + val error = MindboxError.Validation( |
| 62 | + statusCode = 200, |
| 63 | + status = "ValidationError", |
| 64 | + validationMessages = listOf( |
| 65 | + ValidationMessage(message = "field required", location = "email"), |
| 66 | + ), |
| 67 | + ) |
| 68 | + val json = error.toJson() |
| 69 | + assertTrue(json.contains(""""message":"field required"""")) |
| 70 | + assertTrue(json.contains(""""location":"email"""")) |
| 71 | + } |
| 72 | + |
| 73 | + @Test |
| 74 | + fun `toJson Validation - full JSON structure`() { |
| 75 | + val error = MindboxError.Validation(200, "Ok", emptyList()) |
| 76 | + val json = error.toJson() |
| 77 | + assertEquals( |
| 78 | + """{"type":"MindboxError","data":{"statusCode":200,"status":"Ok","validationMessages":[]}}""", |
| 79 | + json, |
| 80 | + ) |
| 81 | + } |
| 82 | + |
| 83 | + // endregion |
| 84 | + |
| 85 | + // region write / toJson — Protocol |
| 86 | + |
| 87 | + @Test |
| 88 | + fun `toJson Protocol - contains type MindboxError`() { |
| 89 | + val error = MindboxError.Protocol( |
| 90 | + statusCode = 400, |
| 91 | + status = "Error", |
| 92 | + errorMessage = "Bad request", |
| 93 | + errorId = "err-1", |
| 94 | + httpStatusCode = 400, |
| 95 | + ) |
| 96 | + val json = error.toJson() |
| 97 | + assertTrue(json.contains(""""type":"MindboxError"""")) |
| 98 | + } |
| 99 | + |
| 100 | + @Test |
| 101 | + fun `toJson Protocol - full JSON structure`() { |
| 102 | + val error = MindboxError.Protocol(400, "Error", "Bad request", "err-1", 400) |
| 103 | + val json = error.toJson() |
| 104 | + assertEquals( |
| 105 | + """{"type":"MindboxError","data":{"statusCode":400,"status":"Error","errorMessage":"Bad request","errorId":"err-1","httpStatusCode":400}}""", |
| 106 | + json, |
| 107 | + ) |
| 108 | + } |
| 109 | + |
| 110 | + @Test |
| 111 | + fun `toJson Protocol - null optional fields omitted from JSON`() { |
| 112 | + // GSON's nullValue() silently skips a name+value pair when serializeNulls = false |
| 113 | + // (the default). MindboxErrorAdapter does not override this, so null fields are |
| 114 | + // absent from the output — not present as "null". This is the current behavior. |
| 115 | + val error = MindboxError.Protocol(403, "Forbidden", null, null, null) |
| 116 | + val json = error.toJson() |
| 117 | + assertFalse("null errorMessage should be omitted", json.contains("errorMessage")) |
| 118 | + assertFalse("null errorId should be omitted", json.contains("errorId")) |
| 119 | + assertFalse("null httpStatusCode should be omitted", json.contains("httpStatusCode")) |
| 120 | + } |
| 121 | + |
| 122 | + // endregion |
| 123 | + |
| 124 | + // region write / toJson — InternalServer |
| 125 | + |
| 126 | + @Test |
| 127 | + fun `toJson InternalServer - contains type MindboxError`() { |
| 128 | + val error = MindboxError.InternalServer(500, "ServerError", "Internal error", "id-1", 500) |
| 129 | + val json = error.toJson() |
| 130 | + assertTrue(json.contains(""""type":"MindboxError"""")) |
| 131 | + } |
| 132 | + |
| 133 | + @Test |
| 134 | + fun `toJson InternalServer - full JSON structure`() { |
| 135 | + val error = MindboxError.InternalServer(500, "ServerError", "Internal error", "id-1", 500) |
| 136 | + val json = error.toJson() |
| 137 | + assertEquals( |
| 138 | + """{"type":"MindboxError","data":{"statusCode":500,"status":"ServerError","errorMessage":"Internal error","errorId":"id-1","httpStatusCode":500}}""", |
| 139 | + json, |
| 140 | + ) |
| 141 | + } |
| 142 | + |
| 143 | + // endregion |
| 144 | + |
| 145 | + // region write / toJson — UnknownServer |
| 146 | + |
| 147 | + @Test |
| 148 | + fun `toJson UnknownServer - contains type NetworkError`() { |
| 149 | + val error = MindboxError.UnknownServer() |
| 150 | + val json = error.toJson() |
| 151 | + assertTrue(json.contains(""""type":"NetworkError"""")) |
| 152 | + } |
| 153 | + |
| 154 | + @Test |
| 155 | + fun `toJson UnknownServer - default constructor full JSON`() { |
| 156 | + val error = MindboxError.UnknownServer() |
| 157 | + val json = error.toJson() |
| 158 | + // Default constructor sets errorMessage = "Cannot reach server", all else null |
| 159 | + assertTrue(json.contains(""""errorMessage":"Cannot reach server"""")) |
| 160 | + } |
| 161 | + |
| 162 | + @Test |
| 163 | + fun `toJson UnknownServer - with all fields`() { |
| 164 | + val error = MindboxError.UnknownServer(503, "Unavailable", "Service down", "id-2", 503) |
| 165 | + val json = error.toJson() |
| 166 | + assertEquals( |
| 167 | + """{"type":"NetworkError","data":{"statusCode":503,"status":"Unavailable","errorMessage":"Service down","errorId":"id-2","httpStatusCode":503}}""", |
| 168 | + json, |
| 169 | + ) |
| 170 | + } |
| 171 | + |
| 172 | + // endregion |
| 173 | + |
| 174 | + // region write / toJson — Unknown |
| 175 | + |
| 176 | + @Test |
| 177 | + fun `toJson Unknown - contains type InternalError`() { |
| 178 | + val error = MindboxError.Unknown() |
| 179 | + val json = error.toJson() |
| 180 | + assertTrue(json.contains(""""type":"InternalError"""")) |
| 181 | + } |
| 182 | + |
| 183 | + @Test |
| 184 | + fun `toJson Unknown - null throwable produces empty data object`() { |
| 185 | + // Both errorName and errorMessage are null → both name+null pairs are silently |
| 186 | + // dropped by GSON (serializeNulls = false). The data object is empty. |
| 187 | + val error = MindboxError.Unknown(throwable = null) |
| 188 | + val json = error.toJson() |
| 189 | + assertFalse("null errorName should be omitted", json.contains("errorName")) |
| 190 | + assertFalse("null errorMessage should be omitted", json.contains("errorMessage")) |
| 191 | + assertTrue("data object should be present but empty", json.contains(""""data":{}""")) |
| 192 | + } |
| 193 | + |
| 194 | + @Test |
| 195 | + fun `toJson Unknown - throwable class name and message included`() { |
| 196 | + val throwable = RuntimeException("something went wrong") |
| 197 | + val error = MindboxError.Unknown(throwable) |
| 198 | + val json = error.toJson() |
| 199 | + assertTrue(json.contains("RuntimeException")) |
| 200 | + assertTrue(json.contains("something went wrong")) |
| 201 | + } |
| 202 | + |
| 203 | + // endregion |
| 204 | + |
| 205 | + // region read / fromJson — current behavior documentation |
| 206 | + |
| 207 | + @Test |
| 208 | + fun `fromJson Validation - current behavior returns null (read is not implemented)`() { |
| 209 | + // MindboxErrorAdapter.read() calls nextString() after beginObject() which reads |
| 210 | + // the key name "type" instead of its value. None of the when-branches match "type", |
| 211 | + // so the method always returns null. This test documents that known limitation so |
| 212 | + // that a future fix or migration will be noticed immediately. |
| 213 | + val json = MindboxError.Validation(200, "Ok", emptyList()).toJson() |
| 214 | + val result = gson.fromJson(json, MindboxError.Validation::class.java) |
| 215 | + assertNull(result) |
| 216 | + } |
| 217 | + |
| 218 | + @Test |
| 219 | + fun `fromJson Protocol - current behavior returns null`() { |
| 220 | + val json = MindboxError.Protocol(400, "Error", null, null, null).toJson() |
| 221 | + val result = gson.fromJson(json, MindboxError.Protocol::class.java) |
| 222 | + assertNull(result) |
| 223 | + } |
| 224 | + |
| 225 | + @Test |
| 226 | + fun `fromJson UnknownServer - current behavior returns null`() { |
| 227 | + val json = MindboxError.UnknownServer().toJson() |
| 228 | + val result = gson.fromJson(json, MindboxError.UnknownServer::class.java) |
| 229 | + assertNull(result) |
| 230 | + } |
| 231 | + |
| 232 | + // endregion |
| 233 | + |
| 234 | + // region toJson output is valid JSON |
| 235 | + |
| 236 | + @Test |
| 237 | + fun `toJson output is parseable by Gson as JsonObject`() { |
| 238 | + listOf( |
| 239 | + MindboxError.Validation(200, "Ok", emptyList()), |
| 240 | + MindboxError.Protocol(400, "Error", null, null, null), |
| 241 | + MindboxError.InternalServer(500, "Err", null, null, null), |
| 242 | + MindboxError.UnknownServer(), |
| 243 | + MindboxError.Unknown(), |
| 244 | + ).forEach { error -> |
| 245 | + val json = error.toJson() |
| 246 | + val parsed = gson.fromJson(json, com.google.gson.JsonObject::class.java) |
| 247 | + assertNotNull("toJson() should produce valid JSON for ${error::class.simpleName}", parsed) |
| 248 | + assertTrue( |
| 249 | + "${error::class.simpleName} JSON should have 'type' key", |
| 250 | + parsed.has("type"), |
| 251 | + ) |
| 252 | + assertTrue( |
| 253 | + "${error::class.simpleName} JSON should have 'data' key", |
| 254 | + parsed.has("data"), |
| 255 | + ) |
| 256 | + } |
| 257 | + } |
| 258 | + |
| 259 | + // endregion |
| 260 | +} |
0 commit comments