OXYGENHUB
# 0414607973cb oxygenhub-android / app / src / test / java / io / oxygenhub / android / api / HubClientTest.kt
8.6 KiB · last changed The phone as an OxygenFlow runner, first part (refs #8) 10 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 aRunnerTokenIsAskedForWithThePost() = runTest {
101
        server.enqueue(MockResponse().setResponseCode(201).setHeader("Content-Type", "application/json").setBody("""{"token":"flowreg_abc","owner":"andymccall"}"""))
102
        val t = client().runnerToken()
103
        assertEquals("andymccall", t.owner)
104
        assertEquals("flowreg_abc", t.token)
105
        assertFalse("the token is not printed", t.toString().contains("flowreg_abc"))
106
        val req = server.takeRequest()
107
        assertEquals("POST" to "/api/v1/user/runner-token", req.method to req.path)
108
        assertEquals("Bearer oxy_secret", req.getHeader("Authorization"))
109
    }
110
111
    @Test
112
    fun repositories() = runTest {
113
        server.enqueue(json("repos.json"))
114
        assertEquals(3, client().repositories().size)
115
        assertEquals("/api/v1/repos", server.takeRequest().path)
116
    }
117
118
    @Test
119
    fun issuesAskForTheState() = runTest {
120
        server.enqueue(json("issues.json"))
121
        val issues = client().issues("andymccall", "oxygenhub-android", IssueState.All)
122
        assertEquals(3, issues.size)
123
        assertEquals("/api/v1/repos/andymccall/oxygenhub-android/issues?state=all", server.takeRequest().path)
124
    }
125
126
    @Test
127
    fun pathSegmentsAreEncoded() = runTest {
128
        server.enqueue(MockResponse().setBody("[]"))
129
        client().issues("a b", "c/d")
130
        assertEquals("/api/v1/repos/a%20b/c%2Fd/issues?state=open", server.takeRequest().path)
131
    }
132
133
    @Test
134
    fun inboxWithALimit() = runTest {
135
        server.enqueue(json("inbox.json"))
136
        assertEquals(2, client().inbox(500).unread)
137
        assertEquals("/api/v1/notifications?limit=200", server.takeRequest().path)
138
    }
139
140
    @Test
141
    fun markingRead() = runTest {
142
        server.enqueue(MockResponse().setResponseCode(204))
143
        server.enqueue(MockResponse().setResponseCode(204))
144
        client().markRead(41)
145
        client().markAllRead()
146
        val one = server.takeRequest()
147
        assertEquals("POST", one.method)
148
        assertEquals("/api/v1/notifications/41/read", one.path)
149
        val all = server.takeRequest()
150
        assertEquals("POST", all.method)
151
        assertEquals("/api/v1/notifications/read", all.path)
152
    }
153
154
    @Test
155
    fun revokingDeletesTheOwnToken() = runTest {
156
        server.enqueue(MockResponse().setResponseCode(204))
157
        client().revokeToken()
158
        val req = server.takeRequest()
159
        assertEquals("DELETE", req.method)
160
        assertEquals("/api/v1/user/token", req.path)
161
        assertEquals("Bearer oxy_secret", req.getHeader("Authorization"))
162
    }
163
164
    @Test
165
    fun aProblemBecomesAnHttpError() = runTest {
166
        server.enqueue(json("problem_403.json", 403).setHeader("Content-Type", "application/problem+json"))
167
        try {
168
            client().user()
169
            fail("expected an error")
170
        } catch (e: HubException.Http) {
171
            assertEquals(403, e.status)
172
            assertEquals("this token lacks the user:read scope", e.detail)
173
            assertEquals("Not allowed: this token lacks the user:read scope", e.userMessage())
174
        }
175
        assertEquals(0, unauthorized)
176
    }
177
178
    @Test
179
    fun aRefusedTokenEndsTheSession() = runTest {
180
        server.enqueue(json("problem_401.json", 401))
181
        try {
182
            client().repositories()
183
            fail("expected an error")
184
        } catch (e: HubException.Http) {
185
            assertEquals(401, e.status)
186
        }
187
        assertEquals(1, unauthorized)
188
    }
189
190
    @Test
191
    fun revokingARefusedTokenDoesNotCallBack() = runTest {
192
        server.enqueue(json("problem_401.json", 401))
193
        try {
194
            client().revokeToken()
195
            fail("expected an error")
196
        } catch (e: HubException.Http) {
197
            assertEquals(401, e.status)
198
        }
199
        assertEquals(0, unauthorized)
200
    }
201
202
    @Test
203
    fun anUnreadableAnswer() = runTest {
204
        server.enqueue(MockResponse().setBody("<html>a proxy's page</html>"))
205
        try {
206
            client().repositories()
207
            fail("expected an error")
208
        } catch (e: HubException.Unreadable) {
209
            assertTrue(e.message!!.contains("/api/v1/repos"))
210
        }
211
    }
212
213
    @Test
214
    fun aBrokenConnection() = runTest {
215
        server.enqueue(MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START))
216
        try {
217
            client().user()
218
            fail("expected an error")
219
        } catch (e: HubException.Network) {
220
            assertEquals("Can't reach the hub. Check your connection and try again.", e.userMessage())
221
        }
222
    }
223
224
    @Test
225
    fun theTokenIsNotInToString() {
226
        assertFalse(client().toString().contains("oxy_secret"))
227
    }
228
}