1package soy.iko.opencode
3import soy.iko.opencode.data.model.BusEvent
4import soy.iko.opencode.data.model.FileContent
5import soy.iko.opencode.data.model.FileNode
6import soy.iko.opencode.data.model.PathInfo
7import soy.iko.opencode.data.model.PermissionReplied
8import soy.iko.opencode.data.model.PermissionUpdated
9import soy.iko.opencode.data.model.Project
10import soy.iko.opencode.data.model.ProvidersResponse
11import soy.iko.opencode.data.model.Session
12import soy.iko.opencode.data.model.defaultOption
13import soy.iko.opencode.data.model.toOptions
14import soy.iko.opencode.data.network.OpencodeJson
15import kotlinx.serialization.builtins.ListSerializer
16import kotlinx.serialization.builtins.serializer
17import org.junit.Assert.assertEquals
18import org.junit.Assert.assertNull
19import org.junit.Assert.assertTrue
24 private fun <T> decode(s: kotlinx.serialization.KSerializer<T>, json: String): T =
25 OpencodeJson.decodeFromString(s, json)
28 fun decodesPermissionUpdatedWithArrayPattern() {
30 { "type": "permission.updated", "properties": {
31 "id": "perm_1", "sessionID": "ses_1", "type": "bash",
32 "pattern": ["rm -rf *", "git push"], "title": "Run bash",
33 "metadata": { "cwd": "/tmp" }, "time": { "created": 123 } } }
35 val event = decode(BusEvent.serializer(), json) as PermissionUpdated
36 assertEquals("perm_1", event.properties.id)
37 assertEquals("bash", event.properties.type)
38 assertEquals("rm -rf *, git push", event.properties.patternText)
42 fun decodesPermissionUpdatedWithStringPattern() {
44 { "type": "permission.updated", "properties": {
45 "id": "perm_2", "sessionID": "ses_1", "type": "edit", "pattern": "src/Main.kt" } }
47 val event = decode(BusEvent.serializer(), json) as PermissionUpdated
48 assertEquals("src/Main.kt", event.properties.patternText)
52 fun decodesSessionWithDirectory() {
53 // A session created in a specific worktree carries directory + projectID; older
54 // servers omit them, so both must be optional (default null).
57 """{"id":"ses_1","title":"Fix bug","directory":"/home/me/code/my-project","projectID":"prj_1"}""",
59 assertEquals("/home/me/code/my-project", withDir.directory)
60 assertEquals("prj_1", withDir.projectID)
61 assertEquals("my-project", withDir.displayDirectory)
63 val withoutDir = decode(Session.serializer(), """{"id":"ses_2","title":"No dir"}""")
64 assertNull(withoutDir.directory)
65 assertNull(withoutDir.displayDirectory)
69 fun decodesProjectsAndPath() {
70 val projects = decode(
71 ListSerializer(Project.serializer()),
72 """[{"id":"p1","worktree":"/home/me/a","vcs":"git","time":{"created":1}},{"id":"p2","worktree":"/home/me/b"}]""",
74 assertEquals(2, projects.size)
75 assertEquals("/home/me/a", projects[0].worktree)
76 assertEquals("git", projects[0].vcs)
79 PathInfo.serializer(),
80 """{"directory":"/home/me/cwd","worktree":"/home/me/cwd","state":"/s","config":"/c"}""",
82 assertEquals("/home/me/cwd", path.directory)
86 fun decodesPermissionReplied() {
87 val json = """{"type":"permission.replied","properties":{"sessionID":"s","permissionID":"perm_1","response":"once"}}"""
88 val event = decode(BusEvent.serializer(), json) as PermissionReplied
89 assertEquals("perm_1", event.properties.permissionID)
90 assertEquals("once", event.properties.response)
94 fun decodesRichProvidersAndFlattens() {
95 // Mirrors the real (richer) /config/providers shape; unknown fields must be ignored.
99 { "id": "anthropic", "name": "Anthropic", "source": "env", "env": ["ANTHROPIC_API_KEY"],
102 "claude-sonnet": { "id": "claude-sonnet", "providerID": "anthropic", "name": "Claude Sonnet",
103 "capabilities": { "reasoning": true }, "cost": { "input": 3, "output": 15 },
104 "limit": { "context": 200000, "output": 8192 }, "status": "active" }
107 "default": { "anthropic": "claude-sonnet" }
110 val resp = decode(ProvidersResponse.serializer(), json)
111 val options = resp.toOptions()
112 assertEquals(1, options.size)
113 assertEquals("Claude Sonnet", options.single().modelLabel)
114 assertEquals("claude-sonnet", resp.defaultOption(options)?.modelID)
118 fun decodesFileContentAndDirectoryListing() {
119 val content = decode(FileContent.serializer(), """{"type":"text","content":"hello\nworld"}""")
120 assertEquals("hello\nworld", content.content)
121 assertTrue(!content.isBinary)
124 ListSerializer(FileNode.serializer()),
125 """[{"name":"src","path":"src","type":"directory"},{"name":"a.kt","path":"src/a.kt","type":"file"}]""",
127 assertEquals(2, nodes.size)
128 assertTrue(nodes[0].isDirectory)
129 assertTrue(!nodes[1].isDirectory)
133 fun decodesFindFilesAsStringArray() {
134 val paths = decode(ListSerializer(String.serializer()), """["src/a.kt","src/b.kt"]""")
135 assertEquals(listOf("src/a.kt", "src/b.kt"), paths)
139 fun binaryFileContentIsFlagged() {
140 val content = decode(FileContent.serializer(), """{"type":"binary","content":"AAAA","encoding":"base64","mimeType":"image/png"}""")
141 assertTrue(content.isBinary)
142 assertNull(content.diff)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list