



























@@ -0,0 +1,291 @@
1+package ai.openclaw.app.node
2+3+import android.content.Context
4+import kotlinx.serialization.json.Json
5+import kotlinx.serialization.json.jsonArray
6+import kotlinx.serialization.json.jsonObject
7+import kotlinx.serialization.json.jsonPrimitive
8+import org.junit.Assert.assertEquals
9+import org.junit.Assert.assertFalse
10+import org.junit.Assert.assertTrue
11+import org.junit.Test
12+13+class CallLogHandlerTest : NodeHandlerRobolectricTest() {
14+ @Test
15+fun handleCallLogSearch_requiresPermission() {
16+val handler = CallLogHandler.forTesting(appContext(), FakeCallLogDataSource(canRead = false))
17+18+val result = handler.handleCallLogSearch(null)
19+20+ assertFalse(result.ok)
21+ assertEquals("CALL_LOG_PERMISSION_REQUIRED", result.error?.code)
22+ }
23+24+ @Test
25+fun handleCallLogSearch_rejectsInvalidJson() {
26+val handler = CallLogHandler.forTesting(appContext(), FakeCallLogDataSource(canRead = true))
27+28+val result = handler.handleCallLogSearch("invalid json")
29+30+ assertFalse(result.ok)
31+ assertEquals("INVALID_REQUEST", result.error?.code)
32+ }
33+34+ @Test
35+fun handleCallLogSearch_returnsCallLogs() {
36+val callLog =
37+CallLogRecord(
38+ number = "+123456",
39+ cachedName = "lixuankai",
40+ date = 1709280000000L,
41+ duration = 60L,
42+ type = 1,
43+ )
44+val handler =
45+CallLogHandler.forTesting(
46+ appContext(),
47+FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)),
48+ )
49+50+val result = handler.handleCallLogSearch("""{"limit":1}""")
51+52+ assertTrue(result.ok)
53+val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject
54+val callLogs = payload.getValue("callLogs").jsonArray
55+ assertEquals(1, callLogs.size)
56+ assertEquals(
57+"+123456",
58+ callLogs
59+ .first()
60+ .jsonObject
61+ .getValue("number")
62+ .jsonPrimitive.content,
63+ )
64+ assertEquals(
65+"lixuankai",
66+ callLogs
67+ .first()
68+ .jsonObject
69+ .getValue("cachedName")
70+ .jsonPrimitive.content,
71+ )
72+ assertEquals(
73+1709280000000L,
74+ callLogs
75+ .first()
76+ .jsonObject
77+ .getValue("date")
78+ .jsonPrimitive.content
79+ .toLong(),
80+ )
81+ assertEquals(
82+60L,
83+ callLogs
84+ .first()
85+ .jsonObject
86+ .getValue("duration")
87+ .jsonPrimitive.content
88+ .toLong(),
89+ )
90+ assertEquals(
91+1,
92+ callLogs
93+ .first()
94+ .jsonObject
95+ .getValue("type")
96+ .jsonPrimitive.content
97+ .toInt(),
98+ )
99+ }
100+101+ @Test
102+fun handleCallLogSearch_withFilters() {
103+val callLog =
104+CallLogRecord(
105+ number = "+123456",
106+ cachedName = "lixuankai",
107+ date = 1709280000000L,
108+ duration = 120L,
109+ type = 2,
110+ )
111+val handler =
112+CallLogHandler.forTesting(
113+ appContext(),
114+FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)),
115+ )
116+117+val result =
118+ handler.handleCallLogSearch(
119+"""{"number":"123456","cachedName":"lixuankai","dateStart":1709270000000,"dateEnd":1709290000000,"duration":120,"type":2}""",
120+ )
121+122+ assertTrue(result.ok)
123+val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject
124+val callLogs = payload.getValue("callLogs").jsonArray
125+ assertEquals(1, callLogs.size)
126+ assertEquals(
127+"lixuankai",
128+ callLogs
129+ .first()
130+ .jsonObject
131+ .getValue("cachedName")
132+ .jsonPrimitive.content,
133+ )
134+ }
135+136+ @Test
137+fun handleCallLogSearch_withPagination() {
138+val callLogs =
139+listOf(
140+CallLogRecord(
141+ number = "+123456",
142+ cachedName = "lixuankai",
143+ date = 1709280000000L,
144+ duration = 60L,
145+ type = 1,
146+ ),
147+CallLogRecord(
148+ number = "+654321",
149+ cachedName = "lixuankai2",
150+ date = 1709280001000L,
151+ duration = 120L,
152+ type = 2,
153+ ),
154+ )
155+val handler =
156+CallLogHandler.forTesting(
157+ appContext(),
158+FakeCallLogDataSource(canRead = true, searchResults = callLogs),
159+ )
160+161+val result = handler.handleCallLogSearch("""{"limit":1,"offset":1}""")
162+163+ assertTrue(result.ok)
164+val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject
165+val callLogsResult = payload.getValue("callLogs").jsonArray
166+ assertEquals(1, callLogsResult.size)
167+ assertEquals(
168+"lixuankai2",
169+ callLogsResult
170+ .first()
171+ .jsonObject
172+ .getValue("cachedName")
173+ .jsonPrimitive.content,
174+ )
175+ }
176+177+ @Test
178+fun handleCallLogSearch_withDefaultParams() {
179+val callLog =
180+CallLogRecord(
181+ number = "+123456",
182+ cachedName = "lixuankai",
183+ date = 1709280000000L,
184+ duration = 60L,
185+ type = 1,
186+ )
187+val handler =
188+CallLogHandler.forTesting(
189+ appContext(),
190+FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)),
191+ )
192+193+val result = handler.handleCallLogSearch(null)
194+195+ assertTrue(result.ok)
196+val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject
197+val callLogs = payload.getValue("callLogs").jsonArray
198+ assertEquals(1, callLogs.size)
199+ assertEquals(
200+"+123456",
201+ callLogs
202+ .first()
203+ .jsonObject
204+ .getValue("number")
205+ .jsonPrimitive.content,
206+ )
207+ }
208+209+ @Test
210+fun handleCallLogSearch_withNullFields() {
211+val callLog =
212+CallLogRecord(
213+ number = null,
214+ cachedName = null,
215+ date = 1709280000000L,
216+ duration = 60L,
217+ type = 1,
218+ )
219+val handler =
220+CallLogHandler.forTesting(
221+ appContext(),
222+FakeCallLogDataSource(canRead = true, searchResults = listOf(callLog)),
223+ )
224+225+val result = handler.handleCallLogSearch("""{"limit":1}""")
226+227+ assertTrue(result.ok)
228+val payload = Json.parseToJsonElement(result.payloadJson ?: error("missing payload")).jsonObject
229+val callLogs = payload.getValue("callLogs").jsonArray
230+ assertEquals(1, callLogs.size)
231+// Verify null values are properly serialized
232+val callLogObj = callLogs.first().jsonObject
233+ assertTrue(callLogObj.containsKey("number"))
234+ assertTrue(callLogObj.containsKey("cachedName"))
235+ }
236+237+ @Test
238+fun handleCallLogSearch_clampsLimitAndOffsetBeforeSearch() {
239+val source = FakeCallLogDataSource(canRead = true)
240+val handler = CallLogHandler.forTesting(appContext(), source)
241+242+val result = handler.handleCallLogSearch("""{"limit":999,"offset":-5}""")
243+244+ assertTrue(result.ok)
245+ assertEquals(200, source.lastRequest?.limit)
246+ assertEquals(0, source.lastRequest?.offset)
247+ }
248+249+ @Test
250+fun handleCallLogSearch_mapsSearchFailuresToUnavailable() {
251+val handler =
252+CallLogHandler.forTesting(
253+ appContext(),
254+FakeCallLogDataSource(
255+ canRead = true,
256+ failure = IllegalStateException("provider down"),
257+ ),
258+ )
259+260+val result = handler.handleCallLogSearch(null)
261+262+ assertFalse(result.ok)
263+ assertEquals("CALL_LOG_UNAVAILABLE", result.error?.code)
264+ assertEquals("CALL_LOG_UNAVAILABLE: provider down", result.error?.message)
265+ }
266+}
267+268+private class FakeCallLogDataSource(
269+private val canRead: Boolean,
270+private val searchResults: List<CallLogRecord> = emptyList(),
271+private val failure: Throwable? = null,
272+) : CallLogDataSource {
273+var lastRequest: CallLogSearchRequest? = null
274+275+override fun hasReadPermission(context: Context): Boolean = canRead
276+277+override fun search(
278+context: Context,
279+request: CallLogSearchRequest,
280+ ): List<CallLogRecord> {
281+ lastRequest = request
282+ failure?.let { throw it }
283+val startIndex = request.offset.coerceAtLeast(0)
284+val endIndex = (startIndex + request.limit).coerceAtMost(searchResults.size)
285+return if (startIndex < searchResults.size) {
286+ searchResults.subList(startIndex, endIndex)
287+ } else {
288+ emptyList()
289+ }
290+ }
291+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。