artemis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +36 -0
- data/Appraisals +32 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +181 -0
- data/Rakefile +14 -0
- data/artemis.gemspec +28 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_42.gemfile +12 -0
- data/gemfiles/rails_50.gemfile +11 -0
- data/gemfiles/rails_51.gemfile +11 -0
- data/gemfiles/rails_52.gemfile +11 -0
- data/gemfiles/rails_edge.gemfile +14 -0
- data/lib/artemis.rb +3 -0
- data/lib/artemis/adapters.rb +25 -0
- data/lib/artemis/adapters/abstract_adapter.rb +46 -0
- data/lib/artemis/adapters/curb_adapter.rb +56 -0
- data/lib/artemis/adapters/net_http_adapter.rb +51 -0
- data/lib/artemis/adapters/net_http_persistent_adapter.rb +46 -0
- data/lib/artemis/adapters/test_adapter.rb +29 -0
- data/lib/artemis/client.rb +245 -0
- data/lib/artemis/exceptions.rb +19 -0
- data/lib/artemis/graphql_endpoint.rb +54 -0
- data/lib/artemis/railtie.rb +50 -0
- data/lib/artemis/version.rb +3 -0
- data/lib/generators/artemis/USAGE +11 -0
- data/lib/generators/artemis/install_generator.rb +59 -0
- data/lib/generators/artemis/templates/.gitkeep +0 -0
- data/lib/generators/artemis/templates/client.rb +10 -0
- data/lib/generators/artemis/templates/graphql.yml +19 -0
- data/lib/tasks/artemis.rake +47 -0
- data/spec/adapters_spec.rb +106 -0
- data/spec/autoloading_spec.rb +146 -0
- data/spec/callbacks_spec.rb +46 -0
- data/spec/client_spec.rb +161 -0
- data/spec/endpoint_spec.rb +45 -0
- data/spec/fixtures/metaphysics.rb +2 -0
- data/spec/fixtures/metaphysics/_artist_fragment.graphql +4 -0
- data/spec/fixtures/metaphysics/artist.graphql +7 -0
- data/spec/fixtures/metaphysics/artists.graphql +8 -0
- data/spec/fixtures/metaphysics/artwork.graphql +8 -0
- data/spec/fixtures/metaphysics/schema.json +49811 -0
- data/spec/spec_helper.rb +48 -0
- metadata +219 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Artemis
|
2
|
+
class Error < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class EndpointNotFound < Error
|
6
|
+
end
|
7
|
+
|
8
|
+
class ConfigurationError < Error
|
9
|
+
end
|
10
|
+
|
11
|
+
class GraphQLFileNotFound < Error
|
12
|
+
end
|
13
|
+
|
14
|
+
class GraphQLError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class GraphQLServerError < GraphQLError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
require 'active_support/core_ext/object/blank'
|
6
|
+
require 'active_support/core_ext/string/inflections'
|
7
|
+
require 'graphql/client'
|
8
|
+
|
9
|
+
require 'artemis/adapters'
|
10
|
+
require 'artemis/exceptions'
|
11
|
+
|
12
|
+
module Artemis
|
13
|
+
class GraphQLEndpoint
|
14
|
+
# Hash object that holds references to adapter instances.
|
15
|
+
ENDPOINT_INSTANCES = {}
|
16
|
+
|
17
|
+
private_constant :ENDPOINT_INSTANCES
|
18
|
+
|
19
|
+
class << self
|
20
|
+
##
|
21
|
+
# Provides an endpoint instance specified in the +configuration+. If the endpoint is not found in
|
22
|
+
# +ENDPOINT_INSTANCES+, it'll raise an exception.
|
23
|
+
def lookup(service_name)
|
24
|
+
ENDPOINT_INSTANCES[service_name.to_s.underscore] || raise(Artemis::EndpointNotFound, "Service `#{service_name}' not registered.")
|
25
|
+
end
|
26
|
+
|
27
|
+
def register!(service_name, configurations)
|
28
|
+
ENDPOINT_INSTANCES[service_name.to_s.underscore] = new(service_name.to_s, configurations.symbolize_keys)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :name, :url, :adapter, :timeout, :schema_path, :pool_size
|
33
|
+
|
34
|
+
def initialize(name, url: , adapter: :net_http, timeout: 30, schema_path: nil, pool_size: 5)
|
35
|
+
@name, @url, @adapter, @timeout, @schema_path, @pool_size = name.to_s, url, adapter, timeout, schema_path, pool_size
|
36
|
+
|
37
|
+
@mutex_for_schema = Mutex.new
|
38
|
+
@mutex_for_connection = Mutex.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def schema
|
42
|
+
@schema || @mutex_for_schema.synchronize do
|
43
|
+
@schema ||= ::GraphQL::Client.load_schema(schema_path.presence || connection)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias load_schema! schema
|
47
|
+
|
48
|
+
def connection
|
49
|
+
@connection || @mutex_for_connection.synchronize do
|
50
|
+
@connection ||= ::Artemis::Adapters.lookup(adapter).new(url, service_name: name, timeout: timeout, pool_size: pool_size)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_support/file_update_checker'
|
2
|
+
|
3
|
+
module Artemis
|
4
|
+
class Railtie < ::Rails::Railtie #:nodoc:
|
5
|
+
initializer 'graphql.client.attach_log_subscriber' do
|
6
|
+
if !defined?(GraphQL::Client::LogSubscriber)
|
7
|
+
require "graphql/client/log_subscriber"
|
8
|
+
GraphQL::Client::LogSubscriber.attach_to :graphql
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'graphql.client.set_query_paths' do |app|
|
13
|
+
app.paths.add "app/operations"
|
14
|
+
|
15
|
+
Artemis::Client.query_paths = app.paths["app/operations"].existent
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer 'graphql.client.set_reloader', after: 'graphql.client.set_query_paths' do |app|
|
19
|
+
files_to_watch = Artemis::Client.query_paths.map {|path| [path, ["graphql"]] }.to_h
|
20
|
+
|
21
|
+
app.reloaders << ActiveSupport::FileUpdateChecker.new([], files_to_watch) do
|
22
|
+
endpoint_names = app.config_for(:graphql).keys
|
23
|
+
endpoint_names.each do |endpoint_name|
|
24
|
+
Artemis::Client.query_paths.each do |path|
|
25
|
+
FileUtils.touch("#{path}/#{endpoint_name}.rb")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer 'graphql.client.load_config' do |app|
|
32
|
+
# TODO: Remove the +rescue+ call
|
33
|
+
(app.config_for(:graphql) rescue {}).each do |endpoint_name, options|
|
34
|
+
Artemis::GraphQLEndpoint.register!(endpoint_name, { 'schema_path' => app.root.join("vendor/graphql/schema/#{endpoint_name}.json").to_s }.merge(options))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
initializer 'graphql.client.preload', after: 'graphql.client.load_config' do |app|
|
39
|
+
if app.config.eager_load
|
40
|
+
app.config_for(:graphql).keys.each do |endpoint_name|
|
41
|
+
endpoint_name.to_s.camelize.constantize.preload!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
rake_tasks do
|
47
|
+
load "tasks/artemis.rake"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Description:
|
2
|
+
Generates a client stub and config files and downloads the schema from the given endpoint.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
rails generate install Artsy https://metaphysics-production.artsy.net
|
6
|
+
|
7
|
+
This will create:
|
8
|
+
app/operations/artsy.rb
|
9
|
+
app/operations/artsy/.gitkeep
|
10
|
+
config/graphql.yml
|
11
|
+
vendor/graphql/schema/artsy.json
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'graphql/client'
|
2
|
+
require 'graphql/client/http'
|
3
|
+
|
4
|
+
class Artemis::InstallGenerator < Rails::Generators::NamedBase
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
argument :endpoint_url, type: :string, banner: "The endpoint URL for a GraphQL service"
|
8
|
+
|
9
|
+
class_option :authorization, type: :string, default: nil, aliases: "-A"
|
10
|
+
|
11
|
+
def generate_client
|
12
|
+
template "client.rb", client_file_name
|
13
|
+
create_file query_dir_gitkeep, ""
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate_config
|
17
|
+
in_root do
|
18
|
+
if behavior == :invoke && !File.exist?(config_file_name)
|
19
|
+
template "graphql.yml", config_file_name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def download_schema
|
25
|
+
say " downloading GraphQL schema from #{endpoint_url}..."
|
26
|
+
|
27
|
+
if options['authorization'].present?
|
28
|
+
rake "graphql:schema:update SERVICE=#{file_name} AUTHORIZATION='#{options['authorization']}'"
|
29
|
+
else
|
30
|
+
rake "graphql:schema:update SERVICE=#{file_name}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def file_name # :doc:
|
37
|
+
@_file_name ||= super.underscore
|
38
|
+
end
|
39
|
+
|
40
|
+
def client_file_name
|
41
|
+
if mountable_engine?
|
42
|
+
"app/operations/#{namespaced_path}/#{file_name}.rb"
|
43
|
+
else
|
44
|
+
"app/operations/#{file_name}.rb"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def query_dir_gitkeep
|
49
|
+
if mountable_engine?
|
50
|
+
"app/operations/#{namespaced_path}/#{file_name}/.gitkeep"
|
51
|
+
else
|
52
|
+
"app/operations/#{file_name}/.gitkeep"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def config_file_name
|
57
|
+
"config/graphql.yml"
|
58
|
+
end
|
59
|
+
end
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
default: &default
|
2
|
+
adapter: :net_http
|
3
|
+
timeout: 10
|
4
|
+
pool_size: 25
|
5
|
+
|
6
|
+
development:
|
7
|
+
<%= file_name %>:
|
8
|
+
<<: *default
|
9
|
+
url: <%= endpoint_url %>
|
10
|
+
|
11
|
+
test:
|
12
|
+
<%= file_name %>:
|
13
|
+
<<: *default
|
14
|
+
url: <%= endpoint_url %>
|
15
|
+
|
16
|
+
production:
|
17
|
+
<%= file_name %>:
|
18
|
+
<<: *default
|
19
|
+
url: <%= endpoint_url %>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
require 'graphql/client'
|
7
|
+
|
8
|
+
namespace :graphql do
|
9
|
+
namespace :schema do
|
10
|
+
desc "Downloads and saves the GraphQL schema (options: SERVICE=service_name AUTHORIZATION='token ...')"
|
11
|
+
task update: :environment do
|
12
|
+
service = if ENV['SERVICE']
|
13
|
+
ENV['SERVICE']
|
14
|
+
else
|
15
|
+
services = Rails.application.config_for(:graphql).keys
|
16
|
+
|
17
|
+
if services.size == 1
|
18
|
+
services.first
|
19
|
+
else
|
20
|
+
raise "Please specify a service name (available services: #{services.join(", ")}): rake graphql:schema:update SERVICE=service"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
headers = ENV['AUTHORIZATION'] ? { Authorization: ENV['AUTHORIZATION'] } : {}
|
25
|
+
service_class = service.camelize.constantize
|
26
|
+
schema_path = service_class.endpoint.schema_path
|
27
|
+
schema = service_class.connection
|
28
|
+
.execute(
|
29
|
+
document: GraphQL::Client::IntrospectionDocument,
|
30
|
+
operation_name: "IntrospectionQuery",
|
31
|
+
variables: {},
|
32
|
+
context: { headers: headers }
|
33
|
+
).to_h
|
34
|
+
|
35
|
+
if schema['errors'].nil? || schema['errors'].empty?
|
36
|
+
FileUtils.mkdir_p(File.dirname(schema_path))
|
37
|
+
File.open(schema_path, 'w') do |file|
|
38
|
+
file.write(JSON.pretty_generate(schema))
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "saved schema to: #{schema_path.gsub("#{Dir.pwd}/", '')}"
|
42
|
+
else
|
43
|
+
raise "received error from server: #{schema}\n\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
describe 'Adapters' do
|
5
|
+
FakeServer = ->(env) {
|
6
|
+
case env['PATH_INFO']
|
7
|
+
when '/slow_server'
|
8
|
+
sleep 2.1
|
9
|
+
|
10
|
+
[200, {}, ['{}']]
|
11
|
+
when '/500'
|
12
|
+
[500, {}, ['Server error']]
|
13
|
+
else
|
14
|
+
body = {
|
15
|
+
data: {
|
16
|
+
body: JSON.parse(env['rack.input'].read),
|
17
|
+
headers: env.select {|key, val| key.start_with?('HTTP_') }
|
18
|
+
.collect {|key, val| [key.gsub(/^HTTP_/, ''), val.downcase] }
|
19
|
+
.to_h,
|
20
|
+
},
|
21
|
+
errors: [],
|
22
|
+
extensions: {}
|
23
|
+
}.to_json
|
24
|
+
|
25
|
+
[200, {}, [body]]
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
before :all do
|
30
|
+
Artemis::Adapters::AbstractAdapter.send(:attr_writer, :uri, :timeout)
|
31
|
+
|
32
|
+
@server_thread = Thread.new do
|
33
|
+
Rack::Handler::WEBrick.run(FakeServer, Port: 8000, Logger: WEBrick::Log.new('/dev/null'), AccessLog: [])
|
34
|
+
end
|
35
|
+
|
36
|
+
loop do
|
37
|
+
begin
|
38
|
+
TCPSocket.open('localhost', 8000)
|
39
|
+
break
|
40
|
+
rescue Errno::ECONNREFUSED
|
41
|
+
# no-op
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
after :all do
|
47
|
+
Rack::Handler::WEBrick.shutdown
|
48
|
+
@server_thread.terminate
|
49
|
+
end
|
50
|
+
|
51
|
+
shared_examples 'an adapter' do
|
52
|
+
describe '#execute' do
|
53
|
+
it 'makes an actual HTTP request' do
|
54
|
+
response = adapter.execute(
|
55
|
+
document: GraphQL::Client::IntrospectionDocument,
|
56
|
+
operation_name: 'IntrospectionQuery',
|
57
|
+
variables: { id: 'yayoi-kusama' },
|
58
|
+
context: { user_id: 1 }
|
59
|
+
)
|
60
|
+
|
61
|
+
expect(response['data']['body']['query']).to eq(GraphQL::Client::IntrospectionDocument.to_query_string)
|
62
|
+
expect(response['data']['body']['variables']).to eq('id' => 'yayoi-kusama')
|
63
|
+
expect(response['data']['body']['operationName']).to eq('IntrospectionQuery')
|
64
|
+
expect(response['errors']).to eq([])
|
65
|
+
expect(response['extensions']).to eq({})
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'raises an error when it receives a server error' do
|
69
|
+
adapter.uri = URI.parse('http://localhost:8000/500')
|
70
|
+
|
71
|
+
expect do
|
72
|
+
adapter.execute(document: GraphQL::Client::IntrospectionDocument, operation_name: 'IntrospectionQuery')
|
73
|
+
end.to raise_error(Artemis::GraphQLServerError, "Received server error status 500: Server error")
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'allows for overriding timeout' do
|
77
|
+
adapter.uri = URI.parse('http://localhost:8000/slow_server')
|
78
|
+
|
79
|
+
expect do
|
80
|
+
adapter.execute(document: GraphQL::Client::IntrospectionDocument, operation_name: 'IntrospectionQuery')
|
81
|
+
end.to raise_error(timeout_error)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe Artemis::Adapters::NetHttpAdapter do
|
87
|
+
let(:adapter) { Artemis::Adapters::NetHttpAdapter.new('http://localhost:8000', service_name: nil, timeout: 2, pool_size: 5) }
|
88
|
+
let(:timeout_error) { Net::ReadTimeout }
|
89
|
+
|
90
|
+
it_behaves_like 'an adapter'
|
91
|
+
end
|
92
|
+
|
93
|
+
describe Artemis::Adapters::NetHttpPersistentAdapter do
|
94
|
+
let(:adapter) { Artemis::Adapters::NetHttpPersistentAdapter.new('http://localhost:8000', service_name: nil, timeout: 2, pool_size: 5) }
|
95
|
+
let(:timeout_error) { Net::HTTP::Persistent::Error }
|
96
|
+
|
97
|
+
it_behaves_like 'an adapter'
|
98
|
+
end
|
99
|
+
|
100
|
+
describe Artemis::Adapters::CurbAdapter do
|
101
|
+
let(:adapter) { Artemis::Adapters::CurbAdapter.new('http://localhost:8000', service_name: nil, timeout: 2, pool_size: 5) }
|
102
|
+
let(:timeout_error) { Curl::Err::TimeoutError }
|
103
|
+
|
104
|
+
it_behaves_like 'an adapter'
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
describe "#{GraphQL::Client} Autoloading" do
|
2
|
+
describe ".load_constant" do
|
3
|
+
it "loads the specified constant if there is a matching graphql file" do
|
4
|
+
Metaphysics.send(:remove_const, :Artist) if Metaphysics.constants.include?(:Artist)
|
5
|
+
|
6
|
+
Metaphysics.load_constant(:Artist)
|
7
|
+
|
8
|
+
expect(defined?(Metaphysics::Artist)).to eq('constant')
|
9
|
+
end
|
10
|
+
|
11
|
+
it "does nothing and returns nil if there is no matching file" do
|
12
|
+
expect(Metaphysics.load_constant(:DoesNotExist)).to be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".preload!" do
|
17
|
+
it "preloads all the graphQL files in the query paths" do
|
18
|
+
%i(Artist Artwork ArtistFragment)
|
19
|
+
.select {|const_name| Metaphysics.constants.include?(const_name) }
|
20
|
+
.each {|const_name| Metaphysics.send(:remove_const, const_name) }
|
21
|
+
|
22
|
+
Metaphysics.preload!
|
23
|
+
|
24
|
+
expect(defined?(Metaphysics::Artist)).to eq('constant')
|
25
|
+
expect(defined?(Metaphysics::Artwork)).to eq('constant')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "dynamically loads the matching GraphQL query and sets it to a constant" do
|
30
|
+
Metaphysics.send(:remove_const, :Artist) if Metaphysics.constants.include?(:Artist)
|
31
|
+
|
32
|
+
query = Metaphysics::Artist
|
33
|
+
|
34
|
+
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
35
|
+
query Metaphysics__Artist($id: String!) {
|
36
|
+
artist(id: $id) {
|
37
|
+
name
|
38
|
+
bio
|
39
|
+
birthday
|
40
|
+
}
|
41
|
+
}
|
42
|
+
GRAPHQL
|
43
|
+
end
|
44
|
+
|
45
|
+
it "dynamically loads the matching GraphQL fragment and sets it to a constant" do
|
46
|
+
Metaphysics.send(:remove_const, :ArtistFragment) if Metaphysics.constants.include?(:ArtistFragment)
|
47
|
+
|
48
|
+
query = Metaphysics::ArtistFragment
|
49
|
+
|
50
|
+
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
51
|
+
fragment Metaphysics__ArtistFragment on Artist {
|
52
|
+
hometown
|
53
|
+
deathday
|
54
|
+
}
|
55
|
+
GRAPHQL
|
56
|
+
end
|
57
|
+
|
58
|
+
it "correctly loads the matching GraphQL query even when the top-level constant with the same name exists" do
|
59
|
+
# In Ruby <= 2.4 top-level constants can be looked up through a namespace, which turned out to be a bad practice.
|
60
|
+
# This has been removed in 2.5, but in earlier versions still suffer from this behaviour.
|
61
|
+
Metaphysics.send(:remove_const, :Artist) if Metaphysics.constants.include?(:Artist)
|
62
|
+
Object.send(:remove_const, :Artist) if Object.constants.include?(:Artist)
|
63
|
+
|
64
|
+
begin
|
65
|
+
Object.send(:const_set, :Artist, 1)
|
66
|
+
|
67
|
+
Metaphysics.artist
|
68
|
+
ensure
|
69
|
+
Object.send(:remove_const, :Artist)
|
70
|
+
end
|
71
|
+
|
72
|
+
query = Metaphysics::Artist
|
73
|
+
|
74
|
+
expect(query.document.to_query_string).to eq(<<~GRAPHQL.strip)
|
75
|
+
query Metaphysics__Artist($id: String!) {
|
76
|
+
artist(id: $id) {
|
77
|
+
name
|
78
|
+
bio
|
79
|
+
birthday
|
80
|
+
}
|
81
|
+
}
|
82
|
+
GRAPHQL
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises an exception when the path was resolved but the file does not exist" do
|
86
|
+
begin
|
87
|
+
Metaphysics.graphql_file_paths << "metaphysics/removed.graphql"
|
88
|
+
|
89
|
+
expect { Metaphysics::Removed }.to raise_error(Errno::ENOENT)
|
90
|
+
ensure
|
91
|
+
Metaphysics.graphql_file_paths.delete("metaphysics/removed.graphql")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "raises an NameError when there is no graphql file that matches the const name" do
|
96
|
+
expect { Metaphysics::DoesNotExist }.to raise_error(NameError)
|
97
|
+
end
|
98
|
+
|
99
|
+
xit "defines the query method when the matching class method gets called for the first time" do
|
100
|
+
Metaphysics.undef_method(:artwork) if Metaphysics.public_instance_methods.include?(:artwork)
|
101
|
+
|
102
|
+
Metaphysics.artwork
|
103
|
+
|
104
|
+
expect(Metaphysics.public_instance_methods).to include(:artwork)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "raises an NameError when there is no graphql file that matches the class method name" do
|
108
|
+
expect { Metaphysics.does_not_exist }.to raise_error(NameError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "raises an NameError when the class method name matches a fragment name" do
|
112
|
+
expect { Metaphysics.artist_fragment }.to raise_error(NameError)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "responds to a class method that has a matching graphQL file" do
|
116
|
+
expect(Metaphysics).to respond_to(:artwork)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "does not respond to class methods that do not have a matching graphQL file" do
|
120
|
+
expect(Metaphysics).not_to respond_to(:does_not_exist)
|
121
|
+
end
|
122
|
+
|
123
|
+
xit "defines the query method when the matching instance method gets called for the first time" do
|
124
|
+
Metaphysics.undef_method(:artwork) if Metaphysics.public_instance_methods.include?(:artwork)
|
125
|
+
|
126
|
+
Metaphysics.new.artwork
|
127
|
+
|
128
|
+
expect(Metaphysics.public_instance_methods).to include(:artwork)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "raises an NameError when there is no graphql file that matches the instance method name" do
|
132
|
+
expect { Metaphysics.new.does_not_exist }.to raise_error(NameError)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "raises an NameError when the instance method name matches a fragment name" do
|
136
|
+
expect { Metaphysics.new.artist_fragment }.to raise_error(NameError)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "responds to the method that has a matching graphQL file" do
|
140
|
+
expect(Metaphysics.new).to respond_to(:artwork)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "does not respond to methods that do not have a matching graphQL file" do
|
144
|
+
expect(Metaphysics.new).not_to respond_to(:does_not_exist)
|
145
|
+
end
|
146
|
+
end
|