Skip to content

Commit

Permalink
Refactor fetch_metadata service
Browse files Browse the repository at this point in the history
  • Loading branch information
straight-shoota committed May 6, 2020
1 parent 0a0eda5 commit 2a00a53
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 216 deletions.
54 changes: 54 additions & 0 deletions spec/fetchers/github_api_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require "spec"
require "../../src/fetchers/github_api"
require "webmock"

describe Shardbox::GitHubAPI do
describe "#fetch_repo_metadata" do
it "queries github graphql" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return do |request|
body = request.body.not_nil!.gets_to_end
body.should contain %("variables":{"owner":"foo","name":"bar"})

HTTP::Client::Response.new(:ok, <<-JSON)
{
"data": {
"repository": {
"description": "foo bar baz"
}
}
}
JSON
end
api = Shardbox::GitHubAPI.new("")
repo_info = api.fetch_repo_metadata("foo", "bar")

repo_info.should eq Repo::Metadata.new(description: "foo bar baz")
end
end

it "raises when endpoint unavailable" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return(status: 401)

api = Shardbox::GitHubAPI.new("")

expect_raises(Shardbox::FetchError, "Repository unavailable") do
api.fetch_repo_metadata("foo", "bar")
end
end
end

it "raises when response is invalid" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return(status: 200)

api = Shardbox::GitHubAPI.new("")

expect_raises(Shardbox::FetchError, "Invalid response") do
api.fetch_repo_metadata("foo", "bar")
end
end
end
end
end
68 changes: 20 additions & 48 deletions spec/service/fetch_metadata_spec.cr
Original file line number Diff line number Diff line change
@@ -1,60 +1,32 @@
require "spec"
require "../../src/service/fetch_metadata"
require "webmock"

struct Shardbox::GitHubAPI
property mock_repo_metadata : Repo::Metadata?

def fetch_repo_metadata(owner : String, name : String)
if mock = mock_repo_metadata
return mock
else
previous_def
end
end
end

describe Service::FetchMetadata do
describe "#fetch_metadata" do
describe "#fetch_repo_metadata" do
it "returns nil for git" do
service = Service::FetchMetadata.new(Repo::Ref.new("git", "foo"))
service.fetch_metadata.should be_nil
service.fetch_repo_metadata.should be_nil
end

it "queries github graphql" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return do |request|
body = request.body.not_nil!.gets_to_end
body.should contain %("variables":{"owner":"foo","name":"bar"})

HTTP::Client::Response.new(:ok, <<-JSON)
{
"data": {
"repository": {
"description": "foo bar baz"
}
}
}
JSON
end
service = Service::FetchMetadata.new(Repo::Ref.new("github", "foo/bar"))
service.api_token = ""
service.fetch_metadata.should eq Repo::Metadata.new(description: "foo bar baz")
end
end

it "raises when endpoint unavailable" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return(status: 401)

service = Service::FetchMetadata.new(Repo::Ref.new("github", "foo/bar"))
service.api_token = ""

expect_raises(Service::FetchMetadata::Error, "Repository unavailable") do
service.fetch_metadata
end
end
end

it "raises when response is invalid" do
WebMock.wrap do
WebMock.stub(:post, "https://api.github.com/graphql").to_return(status: 200)

service = Service::FetchMetadata.new(Repo::Ref.new("github", "foo/bar"))
service.api_token = ""

expect_raises(Service::FetchMetadata::Error, "Invalid response") do
service.fetch_metadata
end
end
metadata = Repo::Metadata.new(created_at: Time.utc)
service = Service::FetchMetadata.new(Repo::Ref.new("github", "foo/bar"))
api = Shardbox::GitHubAPI.new("")
api.mock_repo_metadata = metadata
service.github_api = api
service.fetch_repo_metadata.should eq metadata
end
end
end
4 changes: 2 additions & 2 deletions spec/support/mock_resolver.cr
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ struct MockFetchMetadata
def initialize(@metadata : Repo::Metadata?)
end

def fetch_metadata
def fetch_repo_metadata
if metadata = @metadata
metadata
else
raise Service::FetchMetadata::Error.new("Repo unavailable")
raise Shardbox::FetchError.new("Repo unavailable")
end
end
end
2 changes: 2 additions & 0 deletions src/fetchers/error.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Shardbox::FetchError < Exception
end
File renamed without changes.
150 changes: 150 additions & 0 deletions src/fetchers/github_api.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
require "json"
require "http/client"
require "../repo"
require "./error"

struct Shardbox::GitHubAPI
getter graphql_client : HTTP::Client { HTTP::Client.new("api.github.com", 443, true) }

private getter query_repo_metadata : String = begin
{{ read_file("#{__DIR__}/github_api-repo_metadata.graphql") }}
end

def initialize(@api_token = ENV["GITHUB_TOKEN"])
end

def fetch_repo_metadata(owner : String, name : String)
body = {query: query_repo_metadata, variables: {owner: owner, name: name}}
response = graphql_client.post "/graphql", body: body.to_json, headers: HTTP::Headers{"Authorization" => "bearer #{@api_token}"}

raise FetchError.new("Repository unavailable") unless response.status_code == 200

begin
metadata = Repo::Metadata.from_github_graphql(response.body)
rescue exc : JSON::ParseException
raise FetchError.new("Invalid response", cause: exc)
end

raise FetchError.new("Invalid response") unless metadata

metadata
end
end

struct Repo::Metadata
def self.from_github_graphql(string)
pull = JSON::PullParser.new(string)
metadata = nil
pull.read_object do |key|
case key
when "data"
pull.read_object do |key|
if key == "repository"
pull.read_null_or do
metadata = Repo::Metadata.new(github_pull: pull)
end
else
pull.skip
end
end
when "errors"
errors = [] of String
pull.read_array do
pull.on_key!("message") do
errors << pull.read_string
end
end
raise Shardbox::FetchError.new("Repository error: #{errors.join(", ")}")
else
pull.skip
end
end

metadata
end

def initialize(github_pull pull : JSON::PullParser)
pull.read_object do |key|
case key
when "forks"
pull.on_key!("totalCount") do
@forks_count = pull.read?(Int32)
end
when "stargazers"
pull.on_key!("totalCount") do
@stargazers_count = pull.read?(Int32)
end
when "watchers"
pull.on_key!("totalCount") do
@watchers_count = pull.read?(Int32)
end
when "createdAt"
@created_at = Time.new(pull)
when "description"
@description = pull.read_string_or_null
when "hasIssuesEnabled"
@issues_enabled = pull.read_bool
when "hasWikiEnabled"
@wiki_enabled = pull.read_bool
when "homepageUrl"
@homepage_url = pull.read_string_or_null
when "isArchived"
@archived = pull.read_bool
when "isFork"
@fork = pull.read_bool
when "isMirror"
@mirror = pull.read_bool
when "licenseInfo"
pull.read_null_or do
pull.on_key!("key") do
@license = pull.read_string
end
end
when "primaryLanguage"
pull.read_null_or do
pull.on_key!("name") do
@primary_language = pull.read_string
end
end
when "pushedAt"
pull.read_null_or do
@pushed_at = Time.new(pull)
end
when "closedIssues"
pull.on_key!("totalCount") do
@closed_issues_count = pull.read?(Int32)
end
when "openIssues"
pull.on_key!("totalCount") do
@open_issues_count = pull.read?(Int32)
end
when "closedPullRequests"
pull.on_key!("totalCount") do
@closed_pull_requests_count = pull.read?(Int32)
end
when "openPullRequests"
pull.on_key!("totalCount") do
@open_pull_requests_count = pull.read?(Int32)
end
when "mergedPullRequests"
pull.on_key!("totalCount") do
@merged_pull_requests_count = pull.read?(Int32)
end
when "repositoryTopics"
topics = [] of String
@topics = topics
pull.on_key!("nodes") do
pull.read_array do
pull.on_key!("topic") do
pull.on_key!("name") do
topics << pull.read_string
end
end
end
end
else
pull.skip
end
end
end
end
Loading

0 comments on commit 2a00a53

Please sign in to comment.