graphql-coverage 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/.rspec +3 -0
- data/.rubocop.yml +19 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/Rakefile +16 -0
- data/Steepfile +8 -0
- data/exe/graphql-coverage +8 -0
- data/lib/graphql/coverage/call.rb +15 -0
- data/lib/graphql/coverage/cli.rb +50 -0
- data/lib/graphql/coverage/errors.rb +16 -0
- data/lib/graphql/coverage/result.rb +55 -0
- data/lib/graphql/coverage/store.rb +28 -0
- data/lib/graphql/coverage/trace.rb +22 -0
- data/lib/graphql/coverage/version.rb +7 -0
- data/lib/graphql/coverage.rb +99 -0
- data/rbs_collection.lock.yaml +24 -0
- data/rbs_collection.yaml +17 -0
- data/sig/graphql/coverage/call.rbs +14 -0
- data/sig/graphql/coverage/cli.rbs +19 -0
- data/sig/graphql/coverage/errors.rbs +14 -0
- data/sig/graphql/coverage/result.rbs +27 -0
- data/sig/graphql/coverage/store.rbs +20 -0
- data/sig/graphql/coverage/trace.rbs +10 -0
- data/sig/graphql/coverage/version.rbs +5 -0
- data/sig/graphql/coverage.rbs +39 -0
- data/sig/shim.rbs +39 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0460ae052196100bcc288337d4c214628a6f083d318c28123b452132ac5e6d59
|
4
|
+
data.tar.gz: 884dc07d6864682bf2646e2302f13dff6bf132fe6cd8238746a1e8c684285f66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80b3b3953f3a40e36efc205895a6337c128e80085255142352b2435b7fab245129c68528373199414ca686f0b0c47b3b1044d07c6abc045565837ff519c9a90e
|
7
|
+
data.tar.gz: 0c6d597a081d44bd34093c5a874ba7d8a30199ac941eb70474d9ad2e0fe5e4888f611e0abe558745d673d8d68b3cfe93999377ebb5550c6f477fa1f3ca8f20f2
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Style:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
Metrics:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Layout/LineLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Layout/FirstHashElementIndentation:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Naming/FileName:
|
18
|
+
Exclude:
|
19
|
+
- spec/exe/graphql-coverage_spec.rb
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Masataka Pocke Kuwabara
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# GraphQL::Coverage
|
2
|
+
|
3
|
+
`GraphQL::Coverage` is a gem that calculates the coverage of GraphQL queries.
|
4
|
+
|
5
|
+
## Motivation
|
6
|
+
|
7
|
+
The motivation of this gem is to enforce the coverage of GraphQL queries in testing.
|
8
|
+
|
9
|
+
In our projects, we have a rule that the test cases must cover all GraphQL fields. However, it is difficult to enforce this rule because we have no tools to check the coverage of GraphQL fields.
|
10
|
+
|
11
|
+
You may think that we can check the coverage of GraphQL fields with ordinary coverage tools such as simplecov. However, it is not enough because GraphQL fields are often defined without method definition. For example:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# without `def id`
|
15
|
+
field :id, String, null: false
|
16
|
+
```
|
17
|
+
|
18
|
+
So I need to develop a tool that can check the coverage of GraphQL fields.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Install the gem and add to the application's Gemfile by executing:
|
23
|
+
|
24
|
+
```console
|
25
|
+
$ bundle add graphql-coverage --require false --group test
|
26
|
+
```
|
27
|
+
|
28
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
29
|
+
|
30
|
+
```console
|
31
|
+
$ gem install graphql-coverage
|
32
|
+
```
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
**This gem is not designed to be used in a production environment.**
|
37
|
+
|
38
|
+
This gem is designed to be used in a test environment. Here is an example of using this gem with RSpec.
|
39
|
+
|
40
|
+
### On a single process
|
41
|
+
|
42
|
+
If your RSpec runs on a single process, add the following code to `spec_helper.rb`:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
RSpec.configure do |config|
|
46
|
+
config.before(:suite) do
|
47
|
+
require 'graphql/coverage'
|
48
|
+
# Pass a class that inherits `GraphQL::Schema`.
|
49
|
+
GraphQL::Coverage.enable(YourSchema)
|
50
|
+
end
|
51
|
+
|
52
|
+
config.after(:suite) do
|
53
|
+
GraphQL::Coverage.report!
|
54
|
+
# You can also use `GraphQL::Coverage.report` if you just want to display the report without failure.
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
### On multiple processes
|
60
|
+
|
61
|
+
You run RSpec on multiple processes in many cases. For example, you may use [parallel_tests](https://github.com/grosser/parallel_tests), [test-queue](https://github.com/tmm1/test-queue), or [CircleCI's parallelism](https://circleci.com/docs/parallelism-faster-jobs/).
|
62
|
+
In such cases, you need to use `GraphQL::Coverage` in a different way to aggregate coverage results.
|
63
|
+
|
64
|
+
The following code is an example of using `GraphQL::Coverage` with CircleCI's parallelism on a Rails application.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
# spec_helper.rb
|
68
|
+
RSpec.configure do |config|
|
69
|
+
config.before(:suite) do
|
70
|
+
# Pass a class that inherits `GraphQL::Schema`.
|
71
|
+
GraphQL::Coverage.enable(YourSchema)
|
72
|
+
end
|
73
|
+
|
74
|
+
config.after(:suite) do
|
75
|
+
# Instead of `GraphQL::Coverage.report!`, use `GraphQL::Coverage.dump` to dump the coverage result to a file.
|
76
|
+
GraphQL::Coverage.dump(Rails.root.join("tmp/graphql-coverage-#{ENV['CIRCLE_NODE_INDEX']}.json"))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
After running RSpec, you can aggregate the coverage results and display the coverage with the following command:
|
82
|
+
|
83
|
+
```console
|
84
|
+
# It displays the same report as `GraphQL::Coverage.report!`.
|
85
|
+
$ graphql-coverage --require ./config/environment.rb tmp/graphql-coverage-*.json
|
86
|
+
```
|
87
|
+
|
88
|
+
You must specify `--require` (or `-r`) option to load the schema. If you use Rails, you can specify `./config/environment.rb` as the argument of `--require` option.
|
89
|
+
|
90
|
+
You can also use `--no-fail-on-uncovered` option to display the coverage without failure.
|
91
|
+
|
92
|
+
### Configuration
|
93
|
+
|
94
|
+
You can specify `ignored_fields` option to ignore some fields.
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# spec_helper.rb
|
98
|
+
RSpec.configure do |config|
|
99
|
+
config.before(:suite) do
|
100
|
+
GraphQL::Coverage.enable(YourSchema)
|
101
|
+
GraphQL::Coverage.ignored_fields = [
|
102
|
+
# GraphQL::Coverage does not complain about the coverage of `Article`'s `title` field.
|
103
|
+
{ type: 'Article', field: 'title' },
|
104
|
+
# You can use `*` as a wildcard.
|
105
|
+
{ type: '*', field: 'id' },
|
106
|
+
]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
I recommend specifying the following configuration in most cases to ignore fields for Relay Connection.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
GraphQL::Coverage.ignored_fields = [
|
115
|
+
{ type: '*', field: 'edges' },
|
116
|
+
{ type: '*', field: 'node' },
|
117
|
+
{ type: '*', field: 'cursor' },
|
118
|
+
]
|
119
|
+
```
|
120
|
+
|
121
|
+
## Development
|
122
|
+
|
123
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
124
|
+
|
125
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
126
|
+
|
127
|
+
## Contributing
|
128
|
+
|
129
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/pocke/graphql-coverage.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
require "rubocop/rake_task"
|
9
|
+
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
task :steep do
|
13
|
+
sh 'steep', 'check'
|
14
|
+
end
|
15
|
+
|
16
|
+
task default: %i[rubocop steep spec]
|
data/Steepfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Coverage
|
5
|
+
# rubocop:disable Naming/ConstantName
|
6
|
+
# @api private
|
7
|
+
Call = _ = Struct.new(:type, :field, :result_type, keyword_init: true)
|
8
|
+
# rubocop:enable Naming/ConstantName
|
9
|
+
class Call
|
10
|
+
def self.from_graphql_object(field:, result_type:)
|
11
|
+
new(type: field.owner.graphql_name, field: field.graphql_name, result_type: result_type)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Coverage
|
7
|
+
class CLI
|
8
|
+
def initialize(stdout:, stderr:)
|
9
|
+
@stdout = stdout
|
10
|
+
@stderr = stderr
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(argv)
|
14
|
+
# @type var params: params
|
15
|
+
params = {
|
16
|
+
'fail-on-uncovered': true,
|
17
|
+
}
|
18
|
+
|
19
|
+
args = OptionParser.new do |opts|
|
20
|
+
opts.banner = <<~TXT
|
21
|
+
Usage: graphql-coverage [options] [result file paths]
|
22
|
+
|
23
|
+
Display the coverage result from multiple result files.
|
24
|
+
|
25
|
+
Options:
|
26
|
+
TXT
|
27
|
+
opts.version = VERSION
|
28
|
+
opts.on('-r', '--require PATH', 'Require a file.') do |path|
|
29
|
+
require path
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('--[no-]fail-on-uncovered', 'Fail when there are uncovered fields (default: true).')
|
33
|
+
end.parse(argv, into: _ = params)
|
34
|
+
|
35
|
+
Coverage.load(*args)
|
36
|
+
|
37
|
+
ok = Coverage.report(output: stdout)
|
38
|
+
if ok || !params[:'fail-on-uncovered']
|
39
|
+
return 0
|
40
|
+
else
|
41
|
+
1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :stdout, :stderr
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Coverage
|
5
|
+
module Errors
|
6
|
+
class SchemaMismatch < StandardError
|
7
|
+
def initialize(expected:, got:)
|
8
|
+
super("Schema mismatch: expected #{expected}, got #{got}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class UncoveredFields < StandardError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Coverage
|
5
|
+
# @api private
|
6
|
+
class Result
|
7
|
+
def initialize(calls:, schema:, ignored_fields:)
|
8
|
+
@calls = calls.uniq
|
9
|
+
@schema = schema
|
10
|
+
@ignored_field_patterns = ignored_fields
|
11
|
+
end
|
12
|
+
|
13
|
+
def covered_fields
|
14
|
+
@calls
|
15
|
+
end
|
16
|
+
|
17
|
+
def uncovered_fields
|
18
|
+
@uncovered_fields ||= reject_ignored_fields(available_fields - @calls)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ignored_fields
|
22
|
+
available_fields - uncovered_fields - @calls
|
23
|
+
end
|
24
|
+
|
25
|
+
def available_fields
|
26
|
+
# @type var target_types: Array[singleton(GraphQL::Schema::Object)]
|
27
|
+
target_types = _ = @schema.types.select { |name, klass| klass < GraphQL::Schema::Object && !name.start_with?('__') }.values
|
28
|
+
|
29
|
+
target_types.flat_map do |klass|
|
30
|
+
klass.fields.values.map do |field|
|
31
|
+
Call.from_graphql_object(field: field, result_type: nil)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def reject_ignored_fields(calls)
|
37
|
+
calls.reject do |call|
|
38
|
+
@ignored_field_patterns.any? do |ignored_field|
|
39
|
+
type = __skip__ = ignored_field[:type] || ignored_field['type']
|
40
|
+
field = __skip__ = ignored_field[:field] || ignored_field['field']
|
41
|
+
match_pattern?(type, call.type) && match_pattern?(field, call.field)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def match_pattern?(pat, str)
|
47
|
+
if pat == '*'
|
48
|
+
true
|
49
|
+
else
|
50
|
+
pat == str
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Coverage
|
5
|
+
# @api private
|
6
|
+
class Store
|
7
|
+
def self.current
|
8
|
+
@current
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reset!
|
12
|
+
@current = new
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :calls
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@calls = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def append(call)
|
22
|
+
@calls << call
|
23
|
+
end
|
24
|
+
|
25
|
+
reset!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Coverage
|
5
|
+
# @api private
|
6
|
+
module Trace
|
7
|
+
def execute_field(field:, query:, ast_node:, arguments:, object:)
|
8
|
+
result = super
|
9
|
+
# TODO: result type
|
10
|
+
Store.current.append(Call.from_graphql_object(field: field, result_type: nil))
|
11
|
+
result
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute_field_lazy(field:, query:, ast_node:, arguments:, object:)
|
15
|
+
result = super
|
16
|
+
# TODO: result type
|
17
|
+
Store.current.append(Call.from_graphql_object(field: field, result_type: nil))
|
18
|
+
result
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require_relative "coverage/version"
|
7
|
+
require_relative "coverage/trace"
|
8
|
+
require_relative "coverage/store"
|
9
|
+
require_relative "coverage/call"
|
10
|
+
require_relative "coverage/errors"
|
11
|
+
require_relative "coverage/result"
|
12
|
+
|
13
|
+
module GraphQL
|
14
|
+
module Coverage
|
15
|
+
class << self
|
16
|
+
attr_accessor :ignored_fields
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.enable(schema)
|
20
|
+
self.schema = schema
|
21
|
+
schema.trace_with(Trace)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.dump(file_path)
|
25
|
+
calls = Store.current.calls.map(&:to_h)
|
26
|
+
content = JSON.generate({ calls: calls, schema: @schema.name, ignored_fields: ignored_fields })
|
27
|
+
File.write(_ = file_path, content)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.load(*file_paths)
|
31
|
+
file_paths.each do |file_path|
|
32
|
+
content = JSON.parse(File.read(_ = file_path))
|
33
|
+
self.schema = Object.const_get(content['schema'])
|
34
|
+
self.ignored_fields = content['ignored_fields']
|
35
|
+
|
36
|
+
content['calls'].each do |call_hash|
|
37
|
+
call = Call.new(type: call_hash['type'], field: call_hash['field'], result_type: call_hash['result_type'])
|
38
|
+
Store.current.append(call)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.report(output: $stdout)
|
44
|
+
res = result
|
45
|
+
|
46
|
+
puts_rate = proc do
|
47
|
+
available_size = res.available_fields.size
|
48
|
+
covered_size = res.covered_fields.size
|
49
|
+
ignored_size = res.ignored_fields.size
|
50
|
+
|
51
|
+
cover_rate = sprintf("%.2f", covered_size.to_f / available_size * 100)
|
52
|
+
ignore_rate = sprintf("%.2f", ignored_size.to_f / available_size * 100)
|
53
|
+
|
54
|
+
output.puts "#{covered_size} / #{available_size} fields covered (#{cover_rate}%)"
|
55
|
+
output.puts "#{ignored_size} / #{available_size} fields ignored (#{ignore_rate}%)" if 0 < ignored_size
|
56
|
+
end
|
57
|
+
|
58
|
+
if res.uncovered_fields.empty?
|
59
|
+
output.puts "All fields are covered"
|
60
|
+
puts_rate.call
|
61
|
+
true
|
62
|
+
else
|
63
|
+
output.puts "There are uncovered fields"
|
64
|
+
puts_rate.call
|
65
|
+
output.puts "Missing fields:"
|
66
|
+
res.uncovered_fields.each do |call|
|
67
|
+
output.puts " #{call.type}.#{call.field}"
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.report!(output: $stdout)
|
74
|
+
report(output: output) or raise Errors::UncoveredFields
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.result
|
78
|
+
Result.new(calls: Store.current.calls, schema: @schema, ignored_fields: ignored_fields)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
def self.reset!
|
83
|
+
__skip__ = @schema = nil
|
84
|
+
self.ignored_fields = []
|
85
|
+
Store.reset!
|
86
|
+
end
|
87
|
+
|
88
|
+
# @api private
|
89
|
+
private_class_method def self.schema=(schema)
|
90
|
+
if @schema && @schema != schema
|
91
|
+
raise Errors::SchemaMismatch.new(expected: @schema, got: schema)
|
92
|
+
end
|
93
|
+
|
94
|
+
@schema = schema
|
95
|
+
end
|
96
|
+
|
97
|
+
reset!
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
path: ".gem_rbs_collection"
|
3
|
+
gems:
|
4
|
+
- name: graphql
|
5
|
+
version: '1.12'
|
6
|
+
source:
|
7
|
+
type: git
|
8
|
+
name: ruby/gem_rbs_collection
|
9
|
+
revision: 4b0d2f72e63b6c3e92dc54e19ce23dd24524f9a7
|
10
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
11
|
+
repo_dir: gems
|
12
|
+
- name: json
|
13
|
+
version: '0'
|
14
|
+
source:
|
15
|
+
type: stdlib
|
16
|
+
- name: optparse
|
17
|
+
version: '0'
|
18
|
+
source:
|
19
|
+
type: stdlib
|
20
|
+
- name: pathname
|
21
|
+
version: '0'
|
22
|
+
source:
|
23
|
+
type: stdlib
|
24
|
+
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Download sources
|
2
|
+
sources:
|
3
|
+
- type: git
|
4
|
+
name: ruby/gem_rbs_collection
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
6
|
+
revision: main
|
7
|
+
repo_dir: gems
|
8
|
+
|
9
|
+
path: .gem_rbs_collection
|
10
|
+
|
11
|
+
gems:
|
12
|
+
- name: graphql-coverage
|
13
|
+
ignore: true
|
14
|
+
- name: graphql
|
15
|
+
- name: json
|
16
|
+
- name: pathname
|
17
|
+
- name: optparse
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
# @api private
|
4
|
+
class Call
|
5
|
+
attr_accessor type: String
|
6
|
+
attr_accessor field: String
|
7
|
+
attr_accessor result_type: nil
|
8
|
+
|
9
|
+
def initialize: (type: String, field: String, result_type: nil) -> void
|
10
|
+
|
11
|
+
def self.from_graphql_object: (field: GraphQL::Schema::Field, result_type: untyped) -> instance
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
# @api private
|
4
|
+
class CLI
|
5
|
+
type params = {
|
6
|
+
:'fail-on-uncovered' => bool,
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize: (stdout: IO, stderr: IO) -> void
|
10
|
+
|
11
|
+
def run: (Array[String] argv) -> Integer
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader stdout: IO
|
16
|
+
attr_reader stderr: IO
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
module Errors
|
4
|
+
# It occurs when the dumped files have different schemas.
|
5
|
+
class SchemaMismatch < StandardError
|
6
|
+
def initialize: (expected: untyped, got: untyped) -> void
|
7
|
+
end
|
8
|
+
|
9
|
+
# It occurs when the schema is not fully covered by the tests.
|
10
|
+
class UncoveredFields < StandardError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
# @api private
|
4
|
+
class Result
|
5
|
+
@calls: Array[Call]
|
6
|
+
@schema: singleton(GraphQL::Schema)
|
7
|
+
@ignored_field_patterns: Array[ignored_field]
|
8
|
+
@uncovered_fields: Array[Call]
|
9
|
+
|
10
|
+
def initialize: (calls: Array[Call], schema: singleton(GraphQL::Schema), ignored_fields: Array[ignored_field]) -> void
|
11
|
+
|
12
|
+
def covered_fields: () -> Array[Call]
|
13
|
+
|
14
|
+
def uncovered_fields: () -> Array[Call]
|
15
|
+
|
16
|
+
def ignored_fields: () -> Array[Call]
|
17
|
+
|
18
|
+
def available_fields: () -> Array[Call]
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def reject_ignored_fields: (Array[Call]) -> Array[Call]
|
23
|
+
|
24
|
+
def match_pattern?: (String, String) -> bool
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
# @api private
|
4
|
+
class Store
|
5
|
+
self.@current: untyped
|
6
|
+
|
7
|
+
@calls: untyped
|
8
|
+
|
9
|
+
def self.current: () -> untyped
|
10
|
+
|
11
|
+
def self.reset!: () -> untyped
|
12
|
+
|
13
|
+
attr_reader calls: untyped
|
14
|
+
|
15
|
+
def initialize: () -> void
|
16
|
+
|
17
|
+
def append: (untyped call) -> untyped
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
# @api private
|
4
|
+
module Trace : GraphQL::Tracing::Trace
|
5
|
+
def execute_field: (field: GraphQL::Schema::Field, query: untyped, ast_node: untyped, arguments: untyped, object: untyped) -> untyped
|
6
|
+
|
7
|
+
alias execute_field_lazy execute_field
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Coverage
|
3
|
+
type ignored_field = { type: String, field: String }
|
4
|
+
| { "type" => String, "field" => String }
|
5
|
+
|
6
|
+
# Specify fields to ignore in the coverage report.
|
7
|
+
# default: []
|
8
|
+
attr_accessor self.ignored_fields: Array[ignored_field]
|
9
|
+
|
10
|
+
self.@schema: singleton(GraphQL::Schema)
|
11
|
+
|
12
|
+
# Enable coverage tracking for the given schema.
|
13
|
+
def self.enable: (singleton(GraphQL::Schema) schema) -> void
|
14
|
+
|
15
|
+
# Dump the current coverage data to a file.
|
16
|
+
def self.dump: (String | Pathname file_path) -> void
|
17
|
+
|
18
|
+
# Load coverage data from specified files.
|
19
|
+
def self.load: (*String | Pathname file_paths) -> void
|
20
|
+
|
21
|
+
# Report coverage result to the `output`.
|
22
|
+
#
|
23
|
+
# It reports to the `STDOUT` by default.
|
24
|
+
# It returns `true` if the coverage is 100%.
|
25
|
+
def self.report: (?output: IO) -> bool
|
26
|
+
|
27
|
+
# Same as `report` but it raises an error if the coverage is not 100%.
|
28
|
+
def self.report!: (?output: IO) -> void
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def self.result: () -> Result
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def self.reset!: () -> untyped
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
private def self.schema=: (untyped schema) -> untyped
|
38
|
+
end
|
39
|
+
end
|
data/sig/shim.rbs
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module GraphQL
|
2
|
+
interface _GraphQLName
|
3
|
+
def graphql_name: () -> String
|
4
|
+
end
|
5
|
+
|
6
|
+
class Schema
|
7
|
+
def self.types: (?untyped context) -> Hash[String, Class]
|
8
|
+
|
9
|
+
def self.trace_with: (Module trace_mod, ?mode: Symbol | Array[Symbol], **untyped) -> void
|
10
|
+
|
11
|
+
class Field
|
12
|
+
attr_accessor owner: Class & _GraphQLName
|
13
|
+
attr_reader name: String
|
14
|
+
|
15
|
+
alias graphql_name name
|
16
|
+
end
|
17
|
+
|
18
|
+
class Member
|
19
|
+
module HashFields
|
20
|
+
module ObjectMethods
|
21
|
+
def fields: (?untyped context) -> Hash[String, Field]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Object < Member
|
27
|
+
extend Member::HashFields
|
28
|
+
extend Member::HashFields::ObjectMethods
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Tracing
|
33
|
+
class Trace
|
34
|
+
def execute_field: (field: GraphQL::Schema::Field, query: untyped, ast_node: untyped, arguments: untyped, object: untyped) -> untyped
|
35
|
+
|
36
|
+
def execute_field_lazy: (field: GraphQL::Schema::Field, query: untyped, ast_node: untyped, arguments: untyped, object: untyped) -> untyped
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphql-coverage
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Masataka Pocke Kuwabara
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: graphql
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
description: Coverage for GraphQL
|
28
|
+
email:
|
29
|
+
- kuwabara@pocke.me
|
30
|
+
executables:
|
31
|
+
- graphql-coverage
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".rspec"
|
36
|
+
- ".rubocop.yml"
|
37
|
+
- CHANGELOG.md
|
38
|
+
- LICENSE
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- Steepfile
|
42
|
+
- exe/graphql-coverage
|
43
|
+
- lib/graphql/coverage.rb
|
44
|
+
- lib/graphql/coverage/call.rb
|
45
|
+
- lib/graphql/coverage/cli.rb
|
46
|
+
- lib/graphql/coverage/errors.rb
|
47
|
+
- lib/graphql/coverage/result.rb
|
48
|
+
- lib/graphql/coverage/store.rb
|
49
|
+
- lib/graphql/coverage/trace.rb
|
50
|
+
- lib/graphql/coverage/version.rb
|
51
|
+
- rbs_collection.lock.yaml
|
52
|
+
- rbs_collection.yaml
|
53
|
+
- sig/graphql/coverage.rbs
|
54
|
+
- sig/graphql/coverage/call.rbs
|
55
|
+
- sig/graphql/coverage/cli.rbs
|
56
|
+
- sig/graphql/coverage/errors.rbs
|
57
|
+
- sig/graphql/coverage/result.rbs
|
58
|
+
- sig/graphql/coverage/store.rbs
|
59
|
+
- sig/graphql/coverage/trace.rbs
|
60
|
+
- sig/graphql/coverage/version.rbs
|
61
|
+
- sig/shim.rbs
|
62
|
+
homepage: https://github.com/pocke/graphql-coverage
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
homepage_uri: https://github.com/pocke/graphql-coverage
|
67
|
+
source_code_uri: https://github.com/pocke/graphql-coverage
|
68
|
+
changelog_uri: https://github.com/pocke/graphql-coverage/blob/master/CHANGELOG.md
|
69
|
+
rubygems_mfa_required: 'true'
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '3.0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubygems_version: 3.5.0.dev
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Coverage for GraphQL
|
89
|
+
test_files: []
|