OXYGENHUB
◆ v0.1.3 oxygenhub-android / app / src / test / java / io / oxygenhub / android / FakeHubApi.kt
14.3 KiB · last changed The widget's foot: a white mark and title, counts of unread, reviews and assigned issues that open their screens, and the phone's runner job; Assigned to you on the dashboard (closes #31) 3 days ago source blame history raw
1
package io.oxygenhub.android
2
3
import io.oxygenhub.android.api.FileChange
4
import io.oxygenhub.android.api.Avatar
5
import io.oxygenhub.android.api.HubException
6
import io.oxygenhub.android.api.Check
7
import io.oxygenhub.android.api.Proposal
8
import io.oxygenhub.android.api.ProposalState
9
import io.oxygenhub.android.api.HubApi
10
import io.oxygenhub.android.api.Inbox
11
import io.oxygenhub.android.api.Issue
12
import io.oxygenhub.android.api.IssueState
13
import io.oxygenhub.android.api.Repository
14
import io.oxygenhub.android.api.User
15
16
/** A hub in memory: answers from the fixtures, or the failure set for a call. */
17
class FakeHubApi : HubApi {
18
    var user: () -> User = { Fixtures.user }
19
    var repositories: () -> List<Repository> = { Fixtures.repos }
20
    var issues: (IssueState) -> List<Issue> = { state ->
21
        Fixtures.issues.filter { state == IssueState.All || it.state == state.query }
22
    }
23
    var inbox: () -> Inbox = { Fixtures.inbox }
24
    var markRead: (Long) -> Unit = {}
25
    var markAllRead: () -> Unit = {}
26
    var revoke: () -> Unit = {}
27
    var avatar: (String) -> Avatar? = { null }
28
    var proposals: (ProposalState) -> List<Proposal> = { state ->
29
        Fixtures.proposals.filter {
30
            when (state) {
31
                ProposalState.Open -> it.isOpen
32
                ProposalState.All -> true
33
                else -> it.state == state.query
34
            }
35
        }
36
    }
37
    var proposal: (Long) -> Proposal = { n -> Fixtures.proposals.first { it.number == n } }
38
    var checks: (Long) -> List<Check> = { Fixtures.checks }
39
    var land: (Long) -> Proposal = { n -> proposal(n).copy(state = "landed", landed = io.oxygenhub.android.api.Landed("andymccall", "api (token phone)", 1)) }
40
    var userProposals: () -> io.oxygenhub.android.api.UserProposals = {
41
        io.oxygenhub.android.api.UserProposals(
42
            review = listOf(io.oxygenhub.android.api.ProposalBrief("andymccall/oxygenhub", 300, "Show a column's archived cards", "ada")),
43
        )
44
    }
45
    var userIssues: () -> List<io.oxygenhub.android.api.IssueBrief> = {
46
        listOf(io.oxygenhub.android.api.IssueBrief("andymccall/oxygenhub", 359, "GET /user/issues"))
47
    }
48
    var autoLand: (Long, Boolean) -> Proposal = { n, on -> proposal(n).copy(autoLand = if (on) "andymccall" else null) }
49
50
    val calls = mutableListOf<String>()
51
52
    override suspend fun user(): User = user.invoke().also { calls += "user" }
53
    override suspend fun repositories(): List<Repository> = repositories.invoke().also { calls += "repos" }
54
    override suspend fun issues(owner: String, name: String, state: IssueState): List<Issue> =
55
        issues.invoke(state).also { calls += "issues $owner/$name ${state.query}" }
56
    override suspend fun inbox(limit: Int): Inbox = inbox.invoke().also { calls += "inbox" }
57
    override suspend fun registerPush(fcmToken: String, publicKey: String): Boolean = true.also { calls += "push" }
58
    override suspend fun review(owner: String, name: String, number: Long, verdict: String, body: String) {
59
        calls += "review $verdict $body"
60
    }
61
    var proposalDiff: () -> List<FileChange> = { emptyList() }
62
    override suspend fun proposalDiff(owner: String, name: String, number: Long): List<FileChange> =
63
        proposalDiff.invoke().also { calls += "proposalDiff" }
64
    override suspend fun markRead(id: Long) {
65
        calls += "read $id"
66
        markRead.invoke(id)
67
    }
68
    override suspend fun markAllRead() {
69
        calls += "read all"
70
        markAllRead.invoke()
71
    }
72
    var runnerToken: () -> io.oxygenhub.android.api.RunnerToken = { io.oxygenhub.android.api.RunnerToken("flowreg_test", "andymccall") }
73
    override suspend fun runnerToken() = runnerToken.invoke().also { calls += "runner token" }
74
    var releaseList: List<io.oxygenhub.android.api.Release> = emptyList()
75
    override suspend fun releases(owner: String, name: String) = releaseList.also { calls += "releases" }
76
    var hits: (String) -> List<io.oxygenhub.android.api.SearchHit> = { emptyList() }
77
    override suspend fun search(query: String) = hits(query).also { calls += "search $query" }
78
    var runList: List<io.oxygenhub.android.api.FlowRun> = emptyList()
79
    override suspend fun runs(owner: String, name: String) = runList.also { calls += "runs" }
80
    val runStore = mutableMapOf<Long, io.oxygenhub.android.api.FlowRun>()
81
    override suspend fun run(owner: String, name: String, number: Long) = runStore.getValue(number).also { calls += "run $number" }
82
    val logs = mutableMapOf<String, MutableList<io.oxygenhub.android.api.LogLine>>()
83
    override suspend fun jobLog(owner: String, name: String, number: Long, job: String, after: Long) =
84
        logs[job].orEmpty().filter { it.seq > after }.also { calls += "log $job after $after" }
85
    override suspend fun rerunRun(owner: String, name: String, number: Long) {
86
        calls += "rerun $number"
87
    }
88
    override suspend fun rerunJob(owner: String, name: String, number: Long, job: String) {
89
        calls += "rerun $number $job"
90
    }
91
    override suspend fun decideJob(owner: String, name: String, number: Long, job: String, approve: Boolean) {
92
        calls += "${if (approve) "approve" else "reject"} $number $job"
93
    }
94
95
    override suspend fun expediteRun(owner: String, name: String, number: Long, reason: String) {
96
        calls += "expedite $number $reason"
97
    }
98
99
    override suspend fun cancelRun(owner: String, name: String, number: Long) {
100
        calls += "cancel $number"
101
        runStore[number]?.let { runStore[number] = it.copy(status = "cancelled") }
102
    }
103
    var projectsOf: (String) -> List<io.oxygenhub.android.api.Project> = { emptyList() }
104
    override suspend fun projects(owner: String) = projectsOf(owner).also { calls += "projects $owner" }
105
    var boards = mutableMapOf<Long, io.oxygenhub.android.api.Board>()
106
    override suspend fun board(owner: String, number: Long) = boards.getValue(number).also { calls += "board $owner/$number" }
107
    override suspend fun moveCard(owner: String, number: Long, card: Long, column: String) {
108
        calls += "move $card $column"
109
        val b = boards.getValue(number)
110
        val moving = b.columns.flatMap { it.cards }.first { it.id == card }
111
        boards[number] = b.copy(columns = b.columns.map { c ->
112
            val rest = c.cards.filter { it.id != card }
113
            if (c.name == column) c.copy(cards = rest + moving) else c.copy(cards = rest)
114
        })
115
    }
116
    val issueStore = mutableMapOf<Long, io.oxygenhub.android.api.Issue>()
117
    val commentStore = mutableMapOf<Long, MutableList<io.oxygenhub.android.api.Comment>>()
118
    var repoLabels = listOf(io.oxygenhub.android.api.Label("bug", "#c62d47"), io.oxygenhub.android.api.Label("ui", "#0969da"))
119
    override suspend fun issue(owner: String, name: String, number: Long) =
120
        (issueStore[number] ?: throw io.oxygenhub.android.api.HubException.Http(404, "no such issue")).also { calls += "issue $number" }
121
    override suspend fun issueComments(owner: String, name: String, number: Long) = commentStore[number].orEmpty().toList()
122
    override suspend fun comment(owner: String, name: String, number: Long, body: String): io.oxygenhub.android.api.Comment {
123
        calls += "comment $number"
124
        val c = io.oxygenhub.android.api.Comment(id = (commentStore.values.sumOf { it.size } + 1).toLong(), author = "andymccall", body = body)
125
        commentStore.getOrPut(number) { mutableListOf() } += c
126
        issueStore[number]?.let { issueStore[number] = it.copy(comments = it.comments + 1) }
127
        return c
128
    }
129
    override suspend fun setIssueState(owner: String, name: String, number: Long, open: Boolean, reason: String): io.oxygenhub.android.api.Issue {
130
        calls += "state $number ${if (open) "open" else "closed:$reason"}"
131
        val i = issueStore.getValue(number).copy(state = if (open) "open" else "closed", reason = if (open) "" else reason)
132
        issueStore[number] = i
133
        return i
134
    }
135
    override suspend fun createIssue(owner: String, name: String, title: String, body: String): io.oxygenhub.android.api.Issue {
136
        val n = (issueStore.keys.maxOrNull() ?: 0) + 1
137
        val i = io.oxygenhub.android.api.Issue(n, title, body, author = "andymccall")
138
        issueStore[n] = i
139
        calls += "create $title"
140
        return i
141
    }
142
    override suspend fun labels(owner: String, name: String) = repoLabels
143
    override suspend fun setIssueLabels(owner: String, name: String, number: Long, labels: List<String>): io.oxygenhub.android.api.Issue {
144
        val i = issueStore.getValue(number).copy(labels = repoLabels.filter { it.name in labels })
145
        issueStore[number] = i
146
        return i
147
    }
148
    override suspend fun setIssueAssignees(owner: String, name: String, number: Long, assignees: List<String>): io.oxygenhub.android.api.Issue {
149
        val i = issueStore.getValue(number).copy(assignees = assignees)
150
        issueStore[number] = i
151
        calls += "assign $number ${assignees.joinToString(",")}"
152
        return i
153
    }
154
    var repository: io.oxygenhub.android.api.Repository = io.oxygenhub.android.api.Repository("andymccall", "demo", defaultBookmark = "main")
155
    override suspend fun repository(owner: String, name: String) = repository.also { calls += "repository" }
156
    var tree: (String, String) -> List<io.oxygenhub.android.api.TreeEntry> = { _, _ -> emptyList() }
157
    override suspend fun tree(owner: String, name: String, ref: String, path: String) =
158
        tree.invoke(ref, path).also { calls += "tree $ref:$path" }
159
    var files: Map<String, String> = emptyMap()
160
    override suspend fun file(owner: String, name: String, ref: String, path: String): String {
161
        calls += "file $ref:$path"
162
        return files[path] ?: throw io.oxygenhub.android.api.HubException.Http(404, "no such file")
163
    }
164
    var history: io.oxygenhub.android.api.History = io.oxygenhub.android.api.History()
165
    override suspend fun history(owner: String, name: String, ref: String, path: String?, offset: Int, limit: Int) =
166
        history.also { calls += "history $ref:${path.orEmpty()}" }
167
    var change: (String) -> io.oxygenhub.android.api.ChangeDetail = { error("no change") }
168
    override suspend fun change(owner: String, name: String, hash: String) = change.invoke(hash).also { calls += "change $hash" }
169
    var refs: Pair<List<String>, List<String>> = listOf("main") to emptyList()
170
    override suspend fun refs(owner: String, name: String) = refs.also { calls += "refs" }
171
    var userSky: () -> io.oxygenhub.android.api.UserSky = {
172
        io.oxygenhub.android.api.UserSky(now = 10 * 86_400_000L, repos = listOf("andymccall/oxygen"), events = listOf(listOf(0L, 0L, 9 * 86_400_000L, 3L)))
173
    }
174
    override suspend fun userSky() = userSky.invoke().also { calls += "user sky" }
175
    var userOrgs: () -> List<io.oxygenhub.android.api.UserOrg> = { listOf(io.oxygenhub.android.api.UserOrg("doomsdayone", role = "owner")) }
176
    override suspend fun userOrgs() = userOrgs.invoke().also { calls += "user orgs" }
177
    var createRepository: (String, String, String, io.oxygenhub.android.api.Visibility) -> Repository = { o, n, d, v ->
178
        Repository(owner = o, name = n, description = d, visibility = v)
179
    }
180
    override suspend fun createRepository(owner: String, name: String, description: String, visibility: io.oxygenhub.android.api.Visibility) =
181
        createRepository.invoke(owner, name, description, visibility).also { calls += "create $owner/$name" }
182
    var starterAssistants: (String) -> List<io.oxygenhub.android.api.StarterAssistant> = { emptyList() }
183
    override suspend fun starterAssistants(owner: String) = starterAssistants.invoke(owner).also { calls += "starter assistants $owner" }
184
    var startStarter: (String) -> io.oxygenhub.android.api.StarterStatus = { io.oxygenhub.android.api.StarterStatus("running") }
185
    override suspend fun startStarter(owner: String, name: String, description: String) =
186
        startStarter.invoke(description).also { calls += "start starter $owner/$name" }
187
    /** Each call answers the next status in the list (the last one repeats). */
188
    var starterStatuses: MutableList<io.oxygenhub.android.api.StarterStatus> = mutableListOf()
189
    override suspend fun starterStatus(owner: String, name: String): io.oxygenhub.android.api.StarterStatus {
190
        calls += "starter status $owner/$name"
191
        return if (starterStatuses.size > 1) starterStatuses.removeAt(0) else starterStatuses.firstOrNull() ?: io.oxygenhub.android.api.StarterStatus()
192
    }
193
    var userFlowRuns: () -> List<io.oxygenhub.android.api.UserFlowRun> = { emptyList() }
194
    override suspend fun userFlowRuns(before: Long?) = userFlowRuns.invoke().also { calls += "user flow runs" }
195
    override suspend fun repoSky(owner: String, name: String) =
196
        userSky.invoke().copy(repos = listOf("$owner/$name")).also { calls += "sky $owner/$name" }
197
    override suspend fun userIssues() = userIssues.invoke().also { calls += "user issues" }
198
    override suspend fun userProposals(landable: Boolean) =
199
        userProposals.invoke().also { calls += if (landable) "user proposals, landable" else "user proposals" }
200
    override suspend fun proposals(owner: String, name: String, state: ProposalState): List<Proposal> =
201
        proposals.invoke(state).also { calls += "proposals $owner/$name ${state.query}" }
202
    override suspend fun proposal(owner: String, name: String, number: Long): Proposal =
203
        proposal.invoke(number).also { calls += "proposal $number" }
204
    override suspend fun checks(owner: String, name: String, number: Long): List<Check> =
205
        checks.invoke(number).also { calls += "checks $number" }
206
    override suspend fun land(owner: String, name: String, number: Long): Proposal {
207
        calls += "land $number"
208
        return land.invoke(number)
209
    }
210
    override suspend fun setAutoLand(owner: String, name: String, number: Long, on: Boolean): Proposal {
211
        calls += "auto-land $number $on"
212
        return autoLand.invoke(number, on)
213
    }
214
    override suspend fun avatar(name: String): Avatar? = avatar.invoke(name).also { calls += "avatar $name" }
215
    var accounts: Map<String, io.oxygenhub.android.api.User> = emptyMap()
216
    var orgs: Map<String, io.oxygenhub.android.api.Organisation> = emptyMap()
217
    override suspend fun account(name: String) =
218
        (accounts[name] ?: throw HubException.Http(404, "no such account")).also { calls += "account $name" }
219
    override suspend fun organisation(name: String) =
220
        (orgs[name] ?: throw HubException.Http(404, "no such organisation")).also { calls += "org $name" }
221
    override suspend fun revokeToken() {
222
        calls += "revoke"
223
        revoke.invoke()
224
    }
225
}