@@ -0,0 +1,167 @@
1+package com.parseable.android
2+
3+import com.parseable.android.ui.screens.logviewer.LogSeverity
4+import com.parseable.android.ui.screens.logviewer.detectSeverity
5+import kotlinx.serialization.json.JsonObject
6+import kotlinx.serialization.json.JsonPrimitive
7+import org.junit.Assert.assertEquals
8+import org.junit.Test
9+
10+class SeverityDetectionTest {
11+
12+ private fun logWith(vararg pairs: Pair<String, String>): JsonObject {
13+ return JsonObject(pairs.associate { (k, v) -> k to JsonPrimitive(v) })
14+ }
15+
16+ // --- Field name detection ---
17+
18+ @Test
19+ fun `detects level field`() {
20+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("level" to "error")))
21+ }
22+
23+ @Test
24+ fun `detects severity field`() {
25+ assertEquals(LogSeverity.WARNING, detectSeverity(logWith("severity" to "warn")))
26+ }
27+
28+ @Test
29+ fun `detects loglevel field`() {
30+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("loglevel" to "info")))
31+ }
32+
33+ @Test
34+ fun `detects log_level field`() {
35+ assertEquals(LogSeverity.DEBUG, detectSeverity(logWith("log_level" to "debug")))
36+ }
37+
38+ @Test
39+ fun `field name matching is case-insensitive`() {
40+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("Level" to "ERROR")))
41+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("SEVERITY" to "error")))
42+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("LogLevel" to "ERR")))
43+ }
44+
45+ @Test
46+ fun `returns UNKNOWN when no severity field present`() {
47+ assertEquals(LogSeverity.UNKNOWN, detectSeverity(logWith("message" to "hello", "host" to "server1")))
48+ }
49+
50+ @Test
51+ fun `returns UNKNOWN for empty log entry`() {
52+ assertEquals(LogSeverity.UNKNOWN, detectSeverity(JsonObject(emptyMap())))
53+ }
54+
55+ // --- String value matching ---
56+
57+ @Test
58+ fun `matches fatal variants`() {
59+ for (value in listOf("fatal", "FATAL", "F", "crit", "critical", "emerg", "emergency", "alert", "panic")) {
60+ assertEquals("Expected FATAL for '$value'", LogSeverity.FATAL, detectSeverity(logWith("level" to value)))
61+ }
62+ }
63+
64+ @Test
65+ fun `matches error variants`() {
66+ for (value in listOf("error", "ERROR", "err", "E", "severe", "failure", "fail")) {
67+ assertEquals("Expected ERROR for '$value'", LogSeverity.ERROR, detectSeverity(logWith("level" to value)))
68+ }
69+ }
70+
71+ @Test
72+ fun `matches warning variants`() {
73+ for (value in listOf("warn", "WARNING", "W", "warning")) {
74+ assertEquals("Expected WARNING for '$value'", LogSeverity.WARNING, detectSeverity(logWith("level" to value)))
75+ }
76+ }
77+
78+ @Test
79+ fun `matches info variants`() {
80+ for (value in listOf("info", "INFO", "I", "information", "informational", "notice", "ok", "success")) {
81+ assertEquals("Expected INFO for '$value'", LogSeverity.INFO, detectSeverity(logWith("level" to value)))
82+ }
83+ }
84+
85+ @Test
86+ fun `matches debug variants`() {
87+ for (value in listOf("debug", "DEBUG", "D", "fine", "config", "finer")) {
88+ assertEquals("Expected DEBUG for '$value'", LogSeverity.DEBUG, detectSeverity(logWith("level" to value)))
89+ }
90+ }
91+
92+ @Test
93+ fun `matches trace variants`() {
94+ for (value in listOf("trace", "TRACE", "T", "V", "verbose", "finest", "all")) {
95+ assertEquals("Expected TRACE for '$value'", LogSeverity.TRACE, detectSeverity(logWith("level" to value)))
96+ }
97+ }
98+
99+ @Test
100+ fun `value matching is case-insensitive`() {
101+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("level" to "Error")))
102+ assertEquals(LogSeverity.WARNING, detectSeverity(logWith("level" to "WaRnInG")))
103+ }
104+
105+ @Test
106+ fun `trims whitespace from values`() {
107+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("level" to " error ")))
108+ }
109+
110+ @Test
111+ fun `returns UNKNOWN for unrecognized values`() {
112+ assertEquals(LogSeverity.UNKNOWN, detectSeverity(logWith("level" to "custom_level")))
113+ }
114+
115+ // --- Numeric values ---
116+
117+ @Test
118+ fun `syslog severity numeric values`() {
119+ assertEquals(LogSeverity.FATAL, detectSeverity(logWith("syslog_severity" to "0"))) // emergency
120+ assertEquals(LogSeverity.FATAL, detectSeverity(logWith("syslog_severity" to "1"))) // alert
121+ assertEquals(LogSeverity.FATAL, detectSeverity(logWith("syslog_severity" to "2"))) // critical
122+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("syslog_severity" to "3"))) // error
123+ assertEquals(LogSeverity.WARNING, detectSeverity(logWith("syslog_severity" to "4"))) // warning
124+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("syslog_severity" to "5"))) // notice
125+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("syslog_severity" to "6"))) // informational
126+ assertEquals(LogSeverity.DEBUG, detectSeverity(logWith("syslog_severity" to "7"))) // debug
127+ }
128+
129+ @Test
130+ fun `HTTP status code mapping`() {
131+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("status" to "200")))
132+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("status" to "301")))
133+ assertEquals(LogSeverity.WARNING, detectSeverity(logWith("status" to "404")))
134+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("status" to "500")))
135+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("status" to "503")))
136+ }
137+
138+ @Test
139+ fun `generic numeric level mapping`() {
140+ assertEquals(LogSeverity.FATAL, detectSeverity(logWith("level" to "1000")))
141+ assertEquals(LogSeverity.ERROR, detectSeverity(logWith("level" to "900")))
142+ assertEquals(LogSeverity.WARNING, detectSeverity(logWith("level" to "800")))
143+ assertEquals(LogSeverity.INFO, detectSeverity(logWith("level" to "700")))
144+ assertEquals(LogSeverity.DEBUG, detectSeverity(logWith("level" to "500")))
145+ assertEquals(LogSeverity.TRACE, detectSeverity(logWith("level" to "100")))
146+ }
147+
148+ // --- Priority among fields ---
149+
150+ @Test
151+ fun `level field takes priority over status`() {
152+ val log = JsonObject(mapOf(
153+ "level" to JsonPrimitive("error"),
154+ "status" to JsonPrimitive("200"),
155+ ))
156+ assertEquals(LogSeverity.ERROR, detectSeverity(log))
157+ }
158+
159+ @Test
160+ fun `first matching field with recognized value wins`() {
161+ val log = JsonObject(mapOf(
162+ "level" to JsonPrimitive("unknown_value"),
163+ "severity" to JsonPrimitive("warn"),
164+ ))
165+ assertEquals(LogSeverity.WARNING, detectSeverity(log))
166+ }
167+}