Skip to content

Commit 3f61635

Browse files
Merge pull request #70 from ashawley/fix-12-SearchApi
Fix 12 search api
2 parents 0fddbb8 + 1cd570e commit 3f61635

File tree

5 files changed

+274
-1
lines changed

5 files changed

+274
-1
lines changed

Diff for: src/main/scala/codecheck/github/api/GitHubAPI.scala

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class GitHubAPI(token: String, client: Transport, tokenType: String = "token", d
2828
with WebhookOp
2929
with CollaboratorOp
3030
with BranchOp
31+
with SearchOp
3132
{
3233

3334
private val endpoint = "https://api.github.com"

Diff for: src/main/scala/codecheck/github/models/Repository.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ case class Repository(value: JValue) extends AbstractJson(value) {
6666
def name = get("name")
6767
def full_name = get("full_name")
6868
def url = get("url")
69+
def language = get("language")
70+
def stargazers_count = get("stargazers_count").toLong
6971

7072
def description = opt("description")
71-
def open_issues_count = get("open_issues_count").toInt
73+
def open_issues_count = get("open_issues_count").toLong
74+
def `private` = boolean("private")
7275

7376
lazy val permissions = Permissions(value \ "permissions")
7477
lazy val owner = User(value \ "owner")

Diff for: src/main/scala/codecheck/github/models/Search.scala

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package codecheck.github.models
2+
3+
import org.json4s.JValue
4+
import org.json4s.JArray
5+
6+
sealed trait SearchSort {
7+
def name: String
8+
override def toString = name
9+
}
10+
11+
sealed abstract class SearchRepositorySort(val name: String) extends SearchSort
12+
13+
object SearchRepositorySort {
14+
case object stars extends SearchRepositorySort("stars")
15+
case object forks extends SearchRepositorySort("forks")
16+
case object updated extends SearchRepositorySort("updated")
17+
18+
val values = Array(stars, forks, updated)
19+
20+
def fromString(str: String) = values.filter(_.name == str).head
21+
}
22+
23+
sealed abstract class SearchCodeSort(val name: String) extends SearchSort
24+
25+
object SearchCodeSort {
26+
case object indexed extends SearchCodeSort("indexed")
27+
28+
val values = Array(indexed)
29+
30+
def fromString(str: String) = values.filter(_.name == str).head
31+
}
32+
33+
sealed abstract class SearchIssueSort(val name: String) extends SearchSort
34+
35+
object SearchIssueSort {
36+
case object created extends SearchIssueSort("created")
37+
case object updated extends SearchIssueSort("updated")
38+
case object comments extends SearchIssueSort("comments")
39+
40+
val values = Array(created, updated, comments)
41+
42+
def fromString(str: String) = values.filter(_.name == str).head
43+
}
44+
45+
sealed abstract class SearchUserSort(val name: String) extends SearchSort
46+
47+
object SearchUserSort {
48+
case object followers extends SearchUserSort("followers")
49+
case object repositories extends SearchUserSort("repositories")
50+
case object joined extends SearchUserSort("joined")
51+
52+
val values = Array(followers, repositories, joined)
53+
54+
def fromString(str: String) = values.filter(_.name == str).head
55+
}
56+
57+
sealed trait SearchInput extends AbstractInput {
58+
def q: String
59+
def sort: Option[SearchSort]
60+
def order: SortDirection
61+
def query = s"?q=$q" + sort.map(sortBy => s"&sort=$sortBy&order=$order").getOrElse("")
62+
}
63+
64+
case class SearchRepositoryInput (
65+
val q: String,
66+
val sort: Option[SearchRepositorySort] = None,
67+
val order: SortDirection = SortDirection.desc
68+
) extends SearchInput
69+
70+
case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) {
71+
def total_count: Long = get("total_count").toLong
72+
def incomplete_results: Boolean = boolean("incomplete_results")
73+
lazy val items = (value \ "items") match {
74+
case JArray(arr) => arr.map(Repository(_))
75+
case _ => Nil
76+
}
77+
}
78+
79+
case class SearchCodeInput (
80+
q: String,
81+
sort: Option[SearchCodeSort] = None,
82+
order: SortDirection = SortDirection.desc
83+
) extends SearchInput
84+
85+
case class SearchCodeItem(value: JValue) extends AbstractJson(value) {
86+
def name: String = get("name")
87+
def path: String = get("path")
88+
def sha: String = get("sha")
89+
def url: String = get("url")
90+
def git_url: String = get("git_url")
91+
def html_url: String = get("html_url")
92+
def score: Double = get("score").toDouble
93+
lazy val repository = Repository(value \ "repository")
94+
}
95+
96+
case class SearchCodeResult(value: JValue) extends AbstractJson(value) {
97+
def total_count: Long = get("total_count").toLong
98+
def incomplete_results: Boolean = boolean("incomplete_results")
99+
lazy val items = (value \ "items") match {
100+
case JArray(arr) => arr.map(SearchCodeItem(_))
101+
case _ => Nil
102+
}
103+
}
104+
105+
case class SearchIssueInput (
106+
q: String,
107+
sort: Option[SearchIssueSort] = None,
108+
order: SortDirection = SortDirection.desc
109+
) extends SearchInput
110+
111+
case class SearchIssueResult(value: JValue) extends AbstractJson(value) {
112+
def total_count: Long = get("total_count").toLong
113+
def incomplete_results: Boolean = boolean("incomplete_results")
114+
lazy val items = (value \ "items") match {
115+
case JArray(arr) => arr.map(Issue(_))
116+
case _ => Nil
117+
}
118+
}
119+
120+
case class SearchUserInput (
121+
q: String,
122+
sort: Option[SearchUserSort] = None,
123+
order: SortDirection = SortDirection.desc
124+
) extends SearchInput
125+
126+
case class SearchUserResult(value: JValue) extends AbstractJson(value) {
127+
def total_count: Long = get("total_count").toLong
128+
def incomplete_results: Boolean = boolean("incomplete_results")
129+
lazy val items = (value \ "items") match {
130+
case JArray(arr) => arr.map(User(_))
131+
case _ => Nil
132+
}
133+
}
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package codecheck.github.operations
2+
3+
import scala.concurrent.Future
4+
import scala.concurrent.ExecutionContext.Implicits.global
5+
6+
import codecheck.github.api.GitHubAPI
7+
import codecheck.github.models.SearchInput
8+
import codecheck.github.models.SearchRepositoryResult
9+
import codecheck.github.models.SearchCodeResult
10+
import codecheck.github.models.SearchIssueResult
11+
import codecheck.github.models.SearchUserResult
12+
13+
trait SearchOp {
14+
self: GitHubAPI =>
15+
16+
def searchRepositories(input: SearchInput): Future[SearchRepositoryResult] = {
17+
val path = s"/search/repositories${input.query}"
18+
exec("GET", path ).map { res =>
19+
SearchRepositoryResult(res.body)
20+
}
21+
}
22+
23+
def searchCode(input: SearchInput): Future[SearchCodeResult] = {
24+
val path = s"/search/code${input.query}"
25+
exec("GET", path ).map { res =>
26+
SearchCodeResult(res.body)
27+
}
28+
}
29+
30+
def searchIssues(input: SearchInput): Future[SearchIssueResult] = {
31+
val path = s"/search/issues${input.query}"
32+
exec("GET", path ).map { res =>
33+
SearchIssueResult(res.body)
34+
}
35+
}
36+
37+
def searchUser(input: SearchInput): Future[SearchUserResult] = {
38+
val path = s"/search/users${input.query}"
39+
exec("GET", path ).map { res =>
40+
SearchUserResult(res.body)
41+
}
42+
}
43+
}

Diff for: src/test/scala/SearchOpSpec.scala

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import org.scalatest.path.FunSpec
2+
import scala.concurrent.Await
3+
import scala.concurrent.ExecutionContext.Implicits.global
4+
import codecheck.github.models.SortDirection
5+
import codecheck.github.models.SearchRepositoryInput
6+
import codecheck.github.models.SearchCodeInput
7+
import codecheck.github.models.SearchIssueInput
8+
import codecheck.github.models.SearchUserInput
9+
import codecheck.github.models.SearchRepositorySort
10+
import codecheck.github.models.SearchCodeSort
11+
import codecheck.github.models.SearchIssueSort
12+
import codecheck.github.models.SearchUserSort
13+
import codecheck.github.models.SearchRepositoryResult
14+
import codecheck.github.models.SearchCodeResult
15+
import codecheck.github.exceptions.GitHubAPIException
16+
17+
class SearchOpSpec extends FunSpec
18+
with Constants
19+
{
20+
21+
describe("searchRepositories") {
22+
it("with valid SearchInput should succeed") {
23+
val q = "tetris language:assembly".trim.replaceAll(" ","+")
24+
val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc)
25+
val res = Await.result(api.searchRepositories(input), TIMEOUT)
26+
assert(res.total_count >= 1)
27+
assert(res.items(0).id >= 1 )
28+
assert(res.items(0).name.length >= 1)
29+
assert(res.items(0).full_name.length >= 1)
30+
assert(res.items(0).description.isDefined)
31+
assert(res.items(0).open_issues_count >= 0)
32+
assert(res.items(0).language == "Assembly")
33+
assert(res.items(0).stargazers_count > res.items(1).stargazers_count)
34+
}
35+
it("with valid changed query(q) SearchInput should succeed") {
36+
val q = "jquery in:name,description".trim.replaceAll(" ","+")
37+
val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc)
38+
val res = Await.result(api.searchRepositories(input), TIMEOUT)
39+
assert(res.total_count >= 1)
40+
assert(res.items(0).id >= 1 )
41+
assert(res.items(0).name.length >= 1)
42+
assert(res.items(0).full_name.length >= 1)
43+
assert(res.items(0).description.isDefined)
44+
assert(res.items(0).open_issues_count >= 0)
45+
}
46+
}
47+
describe("searchCode") {
48+
it("with valid SearchInput q,no SortOrder should succeed") {
49+
val q = "addClass in:file language:js repo:jquery/jquery".trim.replaceAll(" ","+")
50+
val input = SearchCodeInput(q,sort=None,order=SortDirection.desc)
51+
val res = Await.result(api.searchCode(input), TIMEOUT)
52+
assert(res.total_count >= 1)
53+
assert(res.items(0).repository.id >= 1 )
54+
assert(res.items(0).sha.length >= 40)
55+
assert(res.items(0).score >= 0d)
56+
assert(res.items(0).repository.full_name == "jquery/jquery")
57+
}
58+
it("with valid SearchInput it should succeed") {
59+
val q = "function size:10000 language:python".trim.replaceAll(" ","+")
60+
val input = SearchCodeInput(q,sort=Some(SearchCodeSort.indexed),order=SortDirection.asc)
61+
val res = Await.result(api.searchCode(input), TIMEOUT)
62+
assert(res.total_count >= 1)
63+
assert(res.items(0).repository.id >= 1 )
64+
assert(res.items(0).path.endsWith(".py"))
65+
assert(res.items(0).sha.length >= 40)
66+
assert(res.items(0).score >= 0d)
67+
assert(res.items(0).repository.`private` == false)
68+
}
69+
}
70+
describe("searchIssues") {
71+
it("with valid SearchInput should succeed") {
72+
val q = "windows label:bug language:python state:open".trim.replaceAll(" ","+")
73+
val input = SearchIssueInput(q,sort=Some(SearchIssueSort.created),order=SortDirection.desc)
74+
val res = Await.result(api.searchIssues(input), TIMEOUT)
75+
assert(res.total_count >= 1)
76+
assert(res.items(0).labels(0).name == "bug" )
77+
assert(res.items(0).state == "open")
78+
assert(((res.items(0).created_at).compareTo(res.items(1).created_at)) > 0)
79+
}
80+
}
81+
describe("searchUser") {
82+
it("with valid SearchInput should succeed") {
83+
val q = "tom repos:>42 followers:>1000"
84+
.trim.replaceAll(" ","+")
85+
.replaceAll(">","%3E")
86+
val input = SearchUserInput(q,sort=None,order=SortDirection.desc)
87+
val res = Await.result(api.searchUser(input), TIMEOUT)
88+
assert(res.total_count >= 0)
89+
assert(res.items(0).login.length >= 0)
90+
assert(res.items(0).id >= 0)
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)