Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 12 search api #70

Merged
merged 10 commits into from
Mar 20, 2017
1 change: 1 addition & 0 deletions src/main/scala/codecheck/github/api/GitHubAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class GitHubAPI(token: String, client: Transport, tokenType: String = "token", d
with WebhookOp
with CollaboratorOp
with BranchOp
with SearchOp
{

private val endpoint = "https://api.github.com"
Expand Down
5 changes: 4 additions & 1 deletion src/main/scala/codecheck/github/models/Repository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ case class Repository(value: JValue) extends AbstractJson(value) {
def name = get("name")
def full_name = get("full_name")
def url = get("url")
def language = get("language")
def stargazers_count = get("stargazers_count").toLong

def description = opt("description")
def open_issues_count = get("open_issues_count").toInt
def open_issues_count = get("open_issues_count").toLong
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the only compatibility change in this pull request.

There was no test case to catch it. The RepositoryCommand uses it, but the generic PrintList.build function doesn't care about the type.

def `private` = boolean("private")

lazy val permissions = Permissions(value \ "permissions")
lazy val owner = User(value \ "owner")
Expand Down
133 changes: 133 additions & 0 deletions src/main/scala/codecheck/github/models/Search.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package codecheck.github.models

import org.json4s.JValue
import org.json4s.JArray

sealed trait SearchSort {
def name: String
override def toString = name
}

sealed abstract class SearchRepositorySort(val name: String) extends SearchSort

object SearchRepositorySort {
case object stars extends SearchRepositorySort("stars")
case object forks extends SearchRepositorySort("forks")
case object updated extends SearchRepositorySort("updated")

val values = Array(stars, forks, updated)

def fromString(str: String) = values.filter(_.name == str).head
}

sealed abstract class SearchCodeSort(val name: String) extends SearchSort

object SearchCodeSort {
case object indexed extends SearchCodeSort("indexed")

val values = Array(indexed)

def fromString(str: String) = values.filter(_.name == str).head
}

sealed abstract class SearchIssueSort(val name: String) extends SearchSort

object SearchIssueSort {
case object created extends SearchIssueSort("created")
case object updated extends SearchIssueSort("updated")
case object comments extends SearchIssueSort("comments")

val values = Array(created, updated, comments)

def fromString(str: String) = values.filter(_.name == str).head
}

sealed abstract class SearchUserSort(val name: String) extends SearchSort

object SearchUserSort {
case object followers extends SearchUserSort("followers")
case object repositories extends SearchUserSort("repositories")
case object joined extends SearchUserSort("joined")

val values = Array(followers, repositories, joined)

def fromString(str: String) = values.filter(_.name == str).head
}

sealed trait SearchInput extends AbstractInput {
def q: String
def sort: Option[SearchSort]
def order: SortDirection
def query = s"?q=$q" + sort.map(sortBy => s"&sort=$sortBy&order=$order").getOrElse("")
}

case class SearchRepositoryInput (
val q: String,
val sort: Option[SearchRepositorySort] = None,
val order: SortDirection = SortDirection.desc
) extends SearchInput

case class SearchRepositoryResult(value: JValue) extends AbstractJson(value) {
def total_count: Long = get("total_count").toLong
def incomplete_results: Boolean = boolean("incomplete_results")
lazy val items = (value \ "items") match {
case JArray(arr) => arr.map(Repository(_))
case _ => Nil
}
}

case class SearchCodeInput (
q: String,
sort: Option[SearchCodeSort] = None,
order: SortDirection = SortDirection.desc
) extends SearchInput

case class SearchCodeItem(value: JValue) extends AbstractJson(value) {
def name: String = get("name")
def path: String = get("path")
def sha: String = get("sha")
def url: String = get("url")
def git_url: String = get("git_url")
def html_url: String = get("html_url")
def score: Double = get("score").toDouble
lazy val repository = Repository(value \ "repository")
}

case class SearchCodeResult(value: JValue) extends AbstractJson(value) {
def total_count: Long = get("total_count").toLong
def incomplete_results: Boolean = boolean("incomplete_results")
lazy val items = (value \ "items") match {
case JArray(arr) => arr.map(SearchCodeItem(_))
case _ => Nil
}
}

case class SearchIssueInput (
q: String,
sort: Option[SearchIssueSort] = None,
order: SortDirection = SortDirection.desc
) extends SearchInput

case class SearchIssueResult(value: JValue) extends AbstractJson(value) {
def total_count: Long = get("total_count").toLong
def incomplete_results: Boolean = boolean("incomplete_results")
lazy val items = (value \ "items") match {
case JArray(arr) => arr.map(Issue(_))
case _ => Nil
}
}

case class SearchUserInput (
q: String,
sort: Option[SearchUserSort] = None,
order: SortDirection = SortDirection.desc
) extends SearchInput

case class SearchUserResult(value: JValue) extends AbstractJson(value) {
def total_count: Long = get("total_count").toLong
def incomplete_results: Boolean = boolean("incomplete_results")
lazy val items = (value \ "items") match {
case JArray(arr) => arr.map(User(_))
case _ => Nil
}
}
43 changes: 43 additions & 0 deletions src/main/scala/codecheck/github/operations/SearchOp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package codecheck.github.operations

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import codecheck.github.api.GitHubAPI
import codecheck.github.models.SearchInput
import codecheck.github.models.SearchRepositoryResult
import codecheck.github.models.SearchCodeResult
import codecheck.github.models.SearchIssueResult
import codecheck.github.models.SearchUserResult

trait SearchOp {
self: GitHubAPI =>

def searchRepositories(input: SearchInput): Future[SearchRepositoryResult] = {
val path = s"/search/repositories${input.query}"
exec("GET", path ).map { res =>
SearchRepositoryResult(res.body)
}
}

def searchCode(input: SearchInput): Future[SearchCodeResult] = {
val path = s"/search/code${input.query}"
exec("GET", path ).map { res =>
SearchCodeResult(res.body)
}
}

def searchIssues(input: SearchInput): Future[SearchIssueResult] = {
val path = s"/search/issues${input.query}"
exec("GET", path ).map { res =>
SearchIssueResult(res.body)
}
}

def searchUser(input: SearchInput): Future[SearchUserResult] = {
val path = s"/search/users${input.query}"
exec("GET", path ).map { res =>
SearchUserResult(res.body)
}
}
}
93 changes: 93 additions & 0 deletions src/test/scala/SearchOpSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import org.scalatest.path.FunSpec
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import codecheck.github.models.SortDirection
import codecheck.github.models.SearchRepositoryInput
import codecheck.github.models.SearchCodeInput
import codecheck.github.models.SearchIssueInput
import codecheck.github.models.SearchUserInput
import codecheck.github.models.SearchRepositorySort
import codecheck.github.models.SearchCodeSort
import codecheck.github.models.SearchIssueSort
import codecheck.github.models.SearchUserSort
import codecheck.github.models.SearchRepositoryResult
import codecheck.github.models.SearchCodeResult
import codecheck.github.exceptions.GitHubAPIException

class SearchOpSpec extends FunSpec
with Constants
{

describe("searchRepositories") {
it("with valid SearchInput should succeed") {
val q = "tetris language:assembly".trim.replaceAll(" ","+")
val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc)
val res = Await.result(api.searchRepositories(input), TIMEOUT)
assert(res.total_count >= 1)
assert(res.items(0).id >= 1 )
assert(res.items(0).name.length >= 1)
assert(res.items(0).full_name.length >= 1)
assert(res.items(0).description.isDefined)
assert(res.items(0).open_issues_count >= 0)
assert(res.items(0).language == "Assembly")
assert(res.items(0).stargazers_count > res.items(1).stargazers_count)
}
it("with valid changed query(q) SearchInput should succeed") {
val q = "jquery in:name,description".trim.replaceAll(" ","+")
val input = SearchRepositoryInput(q,sort=Some(SearchRepositorySort.stars),order=SortDirection.desc)
val res = Await.result(api.searchRepositories(input), TIMEOUT)
assert(res.total_count >= 1)
assert(res.items(0).id >= 1 )
assert(res.items(0).name.length >= 1)
assert(res.items(0).full_name.length >= 1)
assert(res.items(0).description.isDefined)
assert(res.items(0).open_issues_count >= 0)
}
}
describe("searchCode") {
it("with valid SearchInput q,no SortOrder should succeed") {
val q = "addClass in:file language:js repo:jquery/jquery".trim.replaceAll(" ","+")
val input = SearchCodeInput(q,sort=None,order=SortDirection.desc)
val res = Await.result(api.searchCode(input), TIMEOUT)
assert(res.total_count >= 1)
assert(res.items(0).repository.id >= 1 )
assert(res.items(0).sha.length >= 40)
assert(res.items(0).score >= 0d)
assert(res.items(0).repository.full_name == "jquery/jquery")
}
it("with valid SearchInput it should succeed") {
val q = "function size:10000 language:python".trim.replaceAll(" ","+")
val input = SearchCodeInput(q,sort=Some(SearchCodeSort.indexed),order=SortDirection.asc)
val res = Await.result(api.searchCode(input), TIMEOUT)
assert(res.total_count >= 1)
assert(res.items(0).repository.id >= 1 )
assert(res.items(0).path.endsWith(".py"))
assert(res.items(0).sha.length >= 40)
assert(res.items(0).score >= 0d)
assert(res.items(0).repository.`private` == false)
}
}
describe("searchIssues") {
it("with valid SearchInput should succeed") {
val q = "windows label:bug language:python state:open".trim.replaceAll(" ","+")
val input = SearchIssueInput(q,sort=Some(SearchIssueSort.created),order=SortDirection.desc)
val res = Await.result(api.searchIssues(input), TIMEOUT)
assert(res.total_count >= 1)
assert(res.items(0).labels(0).name == "bug" )
assert(res.items(0).state == "open")
assert(((res.items(0).created_at).compareTo(res.items(1).created_at)) > 0)
}
}
describe("searchUser") {
it("with valid SearchInput should succeed") {
val q = "tom repos:>42 followers:>1000"
.trim.replaceAll(" ","+")
.replaceAll(">","%3E")
val input = SearchUserInput(q,sort=None,order=SortDirection.desc)
val res = Await.result(api.searchUser(input), TIMEOUT)
assert(res.total_count >= 0)
assert(res.items(0).login.length >= 0)
assert(res.items(0).id >= 0)
}
}
}