OXYGENHUB
# d1f635be9442 oxygenhub-android / app / src / test / java / io / oxygenhub / android / api / HubClientTest.kt
8.0 KiB · last changed Land proposals from the phone (closes #17) 11 days ago source blame history raw
1
package io.oxygenhub.android.api
2
3
import io.oxygenhub.android.Fixtures
4
import kotlinx.coroutines.test.runTest
5
import okhttp3.OkHttpClient
6
import okhttp3.mockwebserver.MockResponse
7
import okhttp3.mockwebserver.MockWebServer
8
import okhttp3.mockwebserver.SocketPolicy
9
import org.junit.After
10
import org.junit.Assert.assertEquals
11
import org.junit.Assert.assertNull
12
import org.junit.Assert.assertFalse
13
import org.junit.Assert.assertTrue
14
import org.junit.Assert.fail
15
import org.junit.Before
16
import org.junit.Test
17
18
class HubClientTest {
19
    private lateinit var server: MockWebServer
20
    private val http = OkHttpClient()
21
    private var unauthorized = 0
22
23
    @Before
24
    fun setUp() {
25
        server = MockWebServer()
26
        server.start()
27
        unauthorized = 0
28
    }
29
30
    @After
31
    fun tearDown() {
32
        server.shutdown()
33
    }
34
35
    // MockWebServer speaks plain HTTP; the client takes any base it is given.
36
    private fun client() = HubClient(server.url("/").toString().trimEnd('/'), "oxy_secret", http) { unauthorized++ }
37
38
    private fun json(name: String, code: Int = 200) =
39
        MockResponse().setResponseCode(code).setHeader("Content-Type", "application/json").setBody(Fixtures.text(name))
40
41
    @Test
42
    fun userSendsTheBearerToken() = runTest {
43
        server.enqueue(json("user.json"))
44
        val u = client().user()
45
        assertEquals("andymccall", u.username)
46
        val req = server.takeRequest()
47
        assertEquals("GET", req.method)
48
        assertEquals("/api/v1/user", req.path)
49
        assertEquals("Bearer oxy_secret", req.getHeader("Authorization"))
50
        assertEquals("application/json", req.getHeader("Accept"))
51
    }
52
53
    @Test
54
    fun anAvatarIsFetchedWithoutTheToken() = runTest {
55
        server.enqueue(MockResponse().setHeader("Content-Type", "image/svg+xml").setBody(Fixtures.text("identicon-bob.svg")))
56
        val a = client().avatar("bob")
57
        assertEquals(196, (a as Avatar.Identicon).hue)
58
        val req = server.takeRequest()
59
        assertEquals("/avatar/bob", req.path)
60
        assertNull(req.getHeader("Authorization"))
61
        server.enqueue(MockResponse().setResponseCode(404))
62
        assertNull(client().avatar("nobody"))
63
        assertEquals(0, unauthorized)
64
    }
65
66
    @Test
67
    fun proposalsAreListedReadAndLanded() = runTest {
68
        server.enqueue(json("proposals.json"))
69
        assertEquals(3, client().proposals("andymccall", "oxygenhub", ProposalState.All).size)
70
        assertEquals("/api/v1/repos/andymccall/oxygenhub/proposals?state=all", server.takeRequest().path)
71
        server.enqueue(json("checks.json"))
72
        assertEquals("failed", client().checks("andymccall", "oxygenhub", 300)[1].status)
73
        assertEquals("/api/v1/repos/andymccall/oxygenhub/proposals/300/checks", server.takeRequest().path)
74
        server.enqueue(json("proposal.json"))
75
        assertEquals(300L, client().land("andymccall", "oxygenhub", 300).number)
76
        val land = server.takeRequest()
77
        assertEquals("POST", land.method)
78
        assertEquals("/api/v1/repos/andymccall/oxygenhub/proposals/300/land", land.path)
79
        assertEquals("{}", land.body.readUtf8())
80
        server.enqueue(json("proposal.json"))
81
        client().setAutoLand("andymccall", "oxygenhub", 300, on = false)
82
        val off = server.takeRequest()
83
        assertEquals("DELETE" to "/api/v1/repos/andymccall/oxygenhub/proposals/300/auto-land", off.method to off.path)
84
    }
85
86
    @Test
87
    fun aRefusedLandingCarriesTheHubsReason() = runTest {
88
        server.enqueue(
89
            MockResponse().setResponseCode(409).setHeader("Content-Type", "application/problem+json")
90
                .setBody("""{"status":409,"title":"Conflict","detail":"0 of 1 required sealed approvals"}"""),
91
        )
92
        val e = runCatching { client().land("andymccall", "oxygenhub", 301) }.exceptionOrNull() as HubException.Http
93
        assertEquals(409 to "0 of 1 required sealed approvals", e.status to e.detail)
94
        server.enqueue(json("problem_403.json", 403))
95
        val scope = runCatching { client().land("andymccall", "oxygenhub", 301) }.exceptionOrNull()
96
        assertEquals("user:read", scope?.lackedScope)
97
    }
98
99
    @Test
100
    fun repositories() = runTest {
101
        server.enqueue(json("repos.json"))
102
        assertEquals(3, client().repositories().size)
103
        assertEquals("/api/v1/repos", server.takeRequest().path)
104
    }
105
106
    @Test
107
    fun issuesAskForTheState() = runTest {
108
        server.enqueue(json("issues.json"))
109
        val issues = client().issues("andymccall", "oxygenhub-android", IssueState.All)
110
        assertEquals(3, issues.size)
111
        assertEquals("/api/v1/repos/andymccall/oxygenhub-android/issues?state=all", server.takeRequest().path)
112
    }
113
114
    @Test
115
    fun pathSegmentsAreEncoded() = runTest {
116
        server.enqueue(MockResponse().setBody("[]"))
117
        client().issues("a b", "c/d")
118
        assertEquals("/api/v1/repos/a%20b/c%2Fd/issues?state=open", server.takeRequest().path)
119
    }
120
121
    @Test
122
    fun inboxWithALimit() = runTest {
123
        server.enqueue(json("inbox.json"))
124
        assertEquals(2, client().inbox(500).unread)
125
        assertEquals("/api/v1/notifications?limit=200", server.takeRequest().path)
126
    }
127
128
    @Test
129
    fun markingRead() = runTest {
130
        server.enqueue(MockResponse().setResponseCode(204))
131
        server.enqueue(MockResponse().setResponseCode(204))
132
        client().markRead(41)
133
        client().markAllRead()
134
        val one = server.takeRequest()
135
        assertEquals("POST", one.method)
136
        assertEquals("/api/v1/notifications/41/read", one.path)
137
        val all = server.takeRequest()
138
        assertEquals("POST", all.method)
139
        assertEquals("/api/v1/notifications/read", all.path)
140
    }
141
142
    @Test
143
    fun revokingDeletesTheOwnToken() = runTest {
144
        server.enqueue(MockResponse().setResponseCode(204))
145
        client().revokeToken()
146
        val req = server.takeRequest()
147
        assertEquals("DELETE", req.method)
148
        assertEquals("/api/v1/user/token", req.path)
149
        assertEquals("Bearer oxy_secret", req.getHeader("Authorization"))
150
    }
151
152
    @Test
153
    fun aProblemBecomesAnHttpError() = runTest {
154
        server.enqueue(json("problem_403.json", 403).setHeader("Content-Type", "application/problem+json"))
155
        try {
156
            client().user()
157
            fail("expected an error")
158
        } catch (e: HubException.Http) {
159
            assertEquals(403, e.status)
160
            assertEquals("this token lacks the user:read scope", e.detail)
161
            assertEquals("Not allowed: this token lacks the user:read scope", e.userMessage())
162
        }
163
        assertEquals(0, unauthorized)
164
    }
165
166
    @Test
167
    fun aRefusedTokenEndsTheSession() = runTest {
168
        server.enqueue(json("problem_401.json", 401))
169
        try {
170
            client().repositories()
171
            fail("expected an error")
172
        } catch (e: HubException.Http) {
173
            assertEquals(401, e.status)
174
        }
175
        assertEquals(1, unauthorized)
176
    }
177
178
    @Test
179
    fun revokingARefusedTokenDoesNotCallBack() = runTest {
180
        server.enqueue(json("problem_401.json", 401))
181
        try {
182
            client().revokeToken()
183
            fail("expected an error")
184
        } catch (e: HubException.Http) {
185
            assertEquals(401, e.status)
186
        }
187
        assertEquals(0, unauthorized)
188
    }
189
190
    @Test
191
    fun anUnreadableAnswer() = runTest {
192
        server.enqueue(MockResponse().setBody("<html>a proxy's page</html>"))
193
        try {
194
            client().repositories()
195
            fail("expected an error")
196
        } catch (e: HubException.Unreadable) {
197
            assertTrue(e.message!!.contains("/api/v1/repos"))
198
        }
199
    }
200
201
    @Test
202
    fun aBrokenConnection() = runTest {
203
        server.enqueue(MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START))
204
        try {
205
            client().user()
206
            fail("expected an error")
207
        } catch (e: HubException.Network) {
208
            assertEquals("Can't reach the hub. Check your connection and try again.", e.userMessage())
209
        }
210
    }
211
212
    @Test
213
    fun theTokenIsNotInToString() {
214
        assertFalse(client().toString().contains("oxy_secret"))
215
    }
216
}