Skip to content

Commit d205120

Browse files
authored
Added first version of fetching pull requests (#462)
* Added first version of fetching pull requests * Add visible logging for unit tests
1 parent e2dc8f1 commit d205120

File tree

9 files changed

+1006
-6
lines changed

9 files changed

+1006
-6
lines changed

feature/repo/build.gradle

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@ dependencies {
3939

4040
implementation 'androidx.lifecycle:lifecycle-reactivestreams-ktx:2.3.1'
4141

42-
testImplementation 'com.jraska.livedata:testing:1.1.2'
43-
testImplementation 'com.jraska.livedata:testing-ktx:1.1.2'
42+
testImplementation okHttpMockWebServer
43+
testImplementation okHttpLoggingInterceptor
4444
testImplementation 'junit:junit:4.13.2'
4545
testImplementation 'org.assertj:assertj-core:3.18.1'
46-
testImplementation 'androidx.arch.core:core-testing:2.1.0'
47-
testImplementation project(':core-testing')
4846
}

feature/repo/src/main/java/com/jraska/github/client/repo/model/GitHubApiRepoRepository.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,13 @@ internal class GitHubApiRepoRepository(
1010
return gitHubRepoApi.getRepo(owner, repoName)
1111
.toObservable()
1212
.map { RepoConverter.convertToDetail(it) }
13+
.concatMap { detail ->
14+
Observable.just(detail)
15+
.concatWith(
16+
gitHubRepoApi.getPulls(owner, repoName)
17+
.map { RepoConverter.convertRepos(detail, it) }
18+
.onErrorReturn { RepoConverter.convertRepos(detail, it) }
19+
)
20+
}
1321
}
1422
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.jraska.github.client.repo.model
2+
3+
import androidx.annotation.Keep
4+
import com.google.gson.annotations.Expose
5+
import com.google.gson.annotations.SerializedName
6+
7+
@Keep
8+
internal class GitHubPullRequest {
9+
10+
@SerializedName("title")
11+
lateinit var title: String
12+
13+
@SerializedName("html_url")
14+
lateinit var prUrl: String
15+
16+
@SerializedName("number")
17+
var number: Int = 0
18+
19+
@SerializedName("state")
20+
var state: String? = null
21+
22+
@SerializedName("body")
23+
var body: String = ""
24+
25+
@SerializedName("merged")
26+
var merged: Boolean = false
27+
28+
@SerializedName("additions")
29+
var additions = 0
30+
31+
@SerializedName("deletions")
32+
var deletions = 0
33+
34+
@SerializedName("changed_files")
35+
var changedFiles = 0
36+
37+
@SerializedName("comments")
38+
var comments = 0
39+
40+
@SerializedName("review_comments")
41+
var reviewComments = 0
42+
43+
@SerializedName("commits")
44+
var commits = 0
45+
}

feature/repo/src/main/java/com/jraska/github/client/repo/model/RepoConverter.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ internal object RepoConverter {
2121
repo.subscribersCount!!
2222
)
2323

24-
return RepoDetail(header, data)
24+
return RepoDetail(header, data, RepoDetail.PullRequestsState.Loading)
25+
}
26+
27+
fun convertRepos(detail: RepoDetail, prs: List<GitHubPullRequest>): RepoDetail {
28+
val pulls = prs.map { convertPr(it) }
29+
return detail.copy(pullRequests = RepoDetail.PullRequestsState.PullRequests(pulls))
30+
}
31+
32+
fun convertRepos(detail: RepoDetail, prsError: Throwable): RepoDetail {
33+
return detail.copy(pullRequests = RepoDetail.PullRequestsState.Error(prsError))
34+
}
35+
36+
private fun convertPr(gitHubPr: GitHubPullRequest): RepoDetail.PullRequest {
37+
return RepoDetail.PullRequest(gitHubPr.title)
2538
}
2639
}

feature/repo/src/main/java/com/jraska/github/client/repo/model/RepoDetail.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@ package com.jraska.github.client.repo.model
22

33
import org.threeten.bp.Instant
44

5-
internal class RepoDetail(val header: RepoHeader, val data: Data) {
5+
internal data class RepoDetail(
6+
val header: RepoHeader,
7+
val data: Data,
8+
val pullRequests: PullRequestsState
9+
) {
610
class Data(val created: Instant, val issuesCount: Int, val language: String, val subscribersCount: Int)
11+
12+
sealed class PullRequestsState {
13+
object Loading : PullRequestsState()
14+
class PullRequests(val pulls: List<PullRequest>) : PullRequestsState()
15+
class Error(error: Throwable) : PullRequestsState()
16+
}
17+
18+
class PullRequest(val title: String)
719
}

feature/repo/src/main/java/com/jraska/github/client/repo/model/RepoGitHubApi.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ import retrofit2.http.Query
88
internal interface RepoGitHubApi {
99
@GET("/repos/{owner}/{name}")
1010
fun getRepo(@Path("owner") path: String, @Path("name") name: String): Single<GitHubRepo>
11+
12+
@GET("/repos/{owner}/{name}/pulls?state=all")
13+
fun getPulls(@Path("owner") path: String, @Path("name") name: String): Single<List<GitHubPullRequest>>
1114
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.jraska.github.client.repo.model
2+
3+
import okhttp3.OkHttpClient
4+
import okhttp3.logging.HttpLoggingInterceptor
5+
import okhttp3.mockwebserver.MockResponse
6+
import okhttp3.mockwebserver.MockWebServer
7+
import org.assertj.core.api.Assertions.assertThat
8+
import org.junit.Rule
9+
import org.junit.Test
10+
import org.threeten.bp.Instant
11+
import retrofit2.Retrofit
12+
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
13+
import retrofit2.converter.gson.GsonConverterFactory
14+
import java.io.File
15+
16+
class GitHubApiRepoRepositoryTest {
17+
18+
@get:Rule
19+
val mockWebServer = MockWebServer()
20+
21+
@Test
22+
fun getsTheDetailProperly() {
23+
val gitHubApiRepoRepository = GitHubApiRepoRepository(repoGitHubApi())
24+
mockWebServer.enqueue("response/repo_detail.json")
25+
mockWebServer.enqueue("response/repo_pulls.json")
26+
27+
val repoDetail = gitHubApiRepoRepository.getRepoDetail("jraska", "github-client")
28+
.test()
29+
.assertValueCount(2)
30+
.values()
31+
.last()
32+
33+
assertThat(repoDetail).usingRecursiveComparison().isEqualTo(expectedRepoDetail())
34+
}
35+
36+
// This needs to be easier to create in tests
37+
private fun repoGitHubApi() = Retrofit.Builder()
38+
.baseUrl(mockWebServer.url("/"))
39+
.client(OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor { println(it) }.apply {
40+
level = HttpLoggingInterceptor.Level.BASIC
41+
}).build())
42+
.addConverterFactory(GsonConverterFactory.create())
43+
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
44+
.build()
45+
.create(RepoGitHubApi::class.java)
46+
47+
private fun expectedRepoDetail(): RepoDetail {
48+
return RepoDetail(
49+
RepoHeader(
50+
"jraska",
51+
"github-client",
52+
"Experimental architecture app with example usage intended to be a showcase, test and skeleton app.",
53+
102,
54+
14
55+
),
56+
RepoDetail.Data(Instant.parse("2016-03-01T23:38:14Z"), 4, "Kotlin", 3),
57+
RepoDetail.PullRequestsState.PullRequests(
58+
listOf(
59+
RepoDetail.PullRequest("Bump epoxy from 4.4.3 to 4.4.4"),
60+
RepoDetail.PullRequest("Bump kotlin_version from 1.4.31 to 1.4.32")
61+
)
62+
)
63+
)
64+
}
65+
66+
fun MockWebServer.enqueue(path: String) {
67+
enqueue(MockResponse().setBody(json(path)))
68+
}
69+
70+
@Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
71+
private fun MockWebServer.json(path: String): String {
72+
val uri = this.javaClass.classLoader.getResource(path)
73+
val file = File(uri?.path!!)
74+
return String(file.readBytes())
75+
}
76+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"id": 52918490,
3+
"node_id": "MDEwOlJlcG9zaXRvcnk1MjkxODQ5MA==",
4+
"name": "github-client",
5+
"full_name": "jraska/github-client",
6+
"private": false,
7+
"owner": {
8+
"login": "jraska",
9+
"id": 6277721,
10+
"node_id": "MDQ6VXNlcjYyNzc3MjE=",
11+
"avatar_url": "https://avatars.githubusercontent.com/u/6277721?v=4",
12+
"gravatar_id": "",
13+
"url": "https://api.github.com/users/jraska",
14+
"html_url": "https://github.com/jraska",
15+
"followers_url": "https://api.github.com/users/jraska/followers",
16+
"following_url": "https://api.github.com/users/jraska/following{/other_user}",
17+
"gists_url": "https://api.github.com/users/jraska/gists{/gist_id}",
18+
"starred_url": "https://api.github.com/users/jraska/starred{/owner}{/repo}",
19+
"subscriptions_url": "https://api.github.com/users/jraska/subscriptions",
20+
"organizations_url": "https://api.github.com/users/jraska/orgs",
21+
"repos_url": "https://api.github.com/users/jraska/repos",
22+
"events_url": "https://api.github.com/users/jraska/events{/privacy}",
23+
"received_events_url": "https://api.github.com/users/jraska/received_events",
24+
"type": "User",
25+
"site_admin": false
26+
},
27+
"html_url": "https://github.com/jraska/github-client",
28+
"description": "Experimental architecture app with example usage intended to be a showcase, test and skeleton app.",
29+
"fork": false,
30+
"url": "https://api.github.com/repos/jraska/github-client",
31+
"forks_url": "https://api.github.com/repos/jraska/github-client/forks",
32+
"keys_url": "https://api.github.com/repos/jraska/github-client/keys{/key_id}",
33+
"collaborators_url": "https://api.github.com/repos/jraska/github-client/collaborators{/collaborator}",
34+
"teams_url": "https://api.github.com/repos/jraska/github-client/teams",
35+
"hooks_url": "https://api.github.com/repos/jraska/github-client/hooks",
36+
"issue_events_url": "https://api.github.com/repos/jraska/github-client/issues/events{/number}",
37+
"events_url": "https://api.github.com/repos/jraska/github-client/events",
38+
"assignees_url": "https://api.github.com/repos/jraska/github-client/assignees{/user}",
39+
"branches_url": "https://api.github.com/repos/jraska/github-client/branches{/branch}",
40+
"tags_url": "https://api.github.com/repos/jraska/github-client/tags",
41+
"blobs_url": "https://api.github.com/repos/jraska/github-client/git/blobs{/sha}",
42+
"git_tags_url": "https://api.github.com/repos/jraska/github-client/git/tags{/sha}",
43+
"git_refs_url": "https://api.github.com/repos/jraska/github-client/git/refs{/sha}",
44+
"trees_url": "https://api.github.com/repos/jraska/github-client/git/trees{/sha}",
45+
"statuses_url": "https://api.github.com/repos/jraska/github-client/statuses/{sha}",
46+
"languages_url": "https://api.github.com/repos/jraska/github-client/languages",
47+
"stargazers_url": "https://api.github.com/repos/jraska/github-client/stargazers",
48+
"contributors_url": "https://api.github.com/repos/jraska/github-client/contributors",
49+
"subscribers_url": "https://api.github.com/repos/jraska/github-client/subscribers",
50+
"subscription_url": "https://api.github.com/repos/jraska/github-client/subscription",
51+
"commits_url": "https://api.github.com/repos/jraska/github-client/commits{/sha}",
52+
"git_commits_url": "https://api.github.com/repos/jraska/github-client/git/commits{/sha}",
53+
"comments_url": "https://api.github.com/repos/jraska/github-client/comments{/number}",
54+
"issue_comment_url": "https://api.github.com/repos/jraska/github-client/issues/comments{/number}",
55+
"contents_url": "https://api.github.com/repos/jraska/github-client/contents/{+path}",
56+
"compare_url": "https://api.github.com/repos/jraska/github-client/compare/{base}...{head}",
57+
"merges_url": "https://api.github.com/repos/jraska/github-client/merges",
58+
"archive_url": "https://api.github.com/repos/jraska/github-client/{archive_format}{/ref}",
59+
"downloads_url": "https://api.github.com/repos/jraska/github-client/downloads",
60+
"issues_url": "https://api.github.com/repos/jraska/github-client/issues{/number}",
61+
"pulls_url": "https://api.github.com/repos/jraska/github-client/pulls{/number}",
62+
"milestones_url": "https://api.github.com/repos/jraska/github-client/milestones{/number}",
63+
"notifications_url": "https://api.github.com/repos/jraska/github-client/notifications{?since,all,participating}",
64+
"labels_url": "https://api.github.com/repos/jraska/github-client/labels{/name}",
65+
"releases_url": "https://api.github.com/repos/jraska/github-client/releases{/id}",
66+
"deployments_url": "https://api.github.com/repos/jraska/github-client/deployments",
67+
"created_at": "2016-03-01T23:38:14Z",
68+
"updated_at": "2021-03-29T16:05:30Z",
69+
"pushed_at": "2021-03-29T16:05:27Z",
70+
"git_url": "git://github.com/jraska/github-client.git",
71+
"ssh_url": "git@github.com:jraska/github-client.git",
72+
"clone_url": "https://github.com/jraska/github-client.git",
73+
"svn_url": "https://github.com/jraska/github-client",
74+
"homepage": "",
75+
"size": 1481,
76+
"stargazers_count": 102,
77+
"watchers_count": 102,
78+
"language": "Kotlin",
79+
"has_issues": true,
80+
"has_projects": true,
81+
"has_downloads": true,
82+
"has_wiki": true,
83+
"has_pages": false,
84+
"forks_count": 14,
85+
"mirror_url": null,
86+
"archived": false,
87+
"disabled": false,
88+
"open_issues_count": 4,
89+
"license": {
90+
"key": "apache-2.0",
91+
"name": "Apache License 2.0",
92+
"spdx_id": "Apache-2.0",
93+
"url": "https://api.github.com/licenses/apache-2.0",
94+
"node_id": "MDc6TGljZW5zZTI="
95+
},
96+
"forks": 14,
97+
"open_issues": 4,
98+
"watchers": 102,
99+
"default_branch": "master",
100+
"temp_clone_token": null,
101+
"network_count": 14,
102+
"subscribers_count": 3
103+
}

0 commit comments

Comments
 (0)