diff --git a/.gitignore b/.gitignore index da91d7c..43894cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ /pkg/ /spec/reports/ /tmp/ +/spec/dummy/tmp/local_secret.txt +/spec/dummy/db/test.sqlite3* # rspec failure tracking .rspec_status diff --git a/CHANGELOG.md b/CHANGELOG.md index dc1c4da..7de2b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ _none_ +## 4.0.0 (2024-05-28) + +**Changes** + +* Remove the `schema_generation_context?` attribute to the GraphQL `context` when generating the schema. Use the + already available `GraphQL::Schema::AlwaysVisible` plugin instead. + * **(Breaking)** Remove the `NulogyGraphqlApi::Schema::BaseMutation` class which introduced a new API for the + `visible?` method that took a block. This introduced a deviation from the ruby graphql gem's API only for + Mutations and so it was removed. Please ensure that any invocations of `visible?` do not take a block and use + `GraphQL::Schema::Mutation` instead. + * **(Breaking)** Change the `NulogyGraphqlApi::Tasks::SchemaGenerator#generate_schema` method to output the + stringified version of the schema instead of checking it for changes and writing it to a file. + * Expose the `#check_changes` and `#write_schema_to_file` methods on the + `NulogyGraphqlApi::Tasks::SchemaGenerator` to give the user more control over how to build their + tooling. +* Allow the user to be specified for the `request_graphql` test helper. + ## 3.0.1 (2024-01-30) * Add `include_graphql_error` RSpec matcher diff --git a/README.md b/README.md index c8a5346..c15ff39 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,6 @@ end There is a Rake task to generate the `schema.graphql` file. You need to provide the `schema_file_path` and the schema class so that the task can detect breaking changes and generate the file. If you don't have a schema file because it's your first time generating it then the rake task will just create one for you in the path provided. -There is also a third argument `context`. You'll have to configure it to be able to generate the SDL of fields or types that are only visible for more privileged users. - ```ruby namespace :graphql_api do desc "Generate the graphql schema of the api." @@ -142,47 +140,7 @@ namespace :graphql_api do NulogyGraphqlApi::Tasks::SchemaGenerator .new(schema_file_path, schema) - .generate_schema - end -end -``` - -### Node visibility - -When you customize the visibility of parts of your graph you have to make sure that all nodes are visible when the schema is being generated by the rake task. You can do so by using the `schema_generation_context?` attribute that is added to the context by the `SchemaGenerator` mentioned in the previous section. - -Here is how to use it: - -##### On fields -```ruby -field :entity, MyApp::EntityType, null: false do - description "Find an entity by ID" - argument :id, ID, required: true - - def visible?(context) - context[:schema_generation_context?] || context[:current_user].superuser? - end -end -``` - - -##### On mutations - -In this case the `schema_generation_context?` attribute is checked by the `BaseMutation` class. All you have to do is inheriting from it and override `visible?` passing a block to the base method. - -```ruby -module MyApp - class CreateEntity < NulogyGraphqlApi::Schema::BaseMutation - field :entity, MyApp::EntityType, null: false - field :errors, [NulogyGraphqlApi::Types::UserErrorType], null: false - - def self.visible?(context) - super { context[:current_user].super_user? } - end - - def resolve(args) - # ... - end + .write_schema_to_file end end ``` @@ -237,7 +195,7 @@ The `request_graphql` helper issues a POST request against the provided URL. Thi ```ruby RSpec.describe MyApp::Graphql::Query, :graphql, type: :request do it "returns 401 Unauthorized given an unauthenticated request" do - gql_response = request_graphql(url, <<~GRAPHQL, headers: { "HTTP_AUTHORIZATION" => nil }) + gql_response = request_graphql(url, <<~GRAPHQL, headers: { "HTTP_AUTHORIZATION" => nil }, user: default_user) query { entities { id diff --git a/lib/nulogy_graphql_api.rb b/lib/nulogy_graphql_api.rb index acf8d87..391935f 100644 --- a/lib/nulogy_graphql_api.rb +++ b/lib/nulogy_graphql_api.rb @@ -3,7 +3,6 @@ require "nulogy_graphql_api/error_handling" require "nulogy_graphql_api/graphql_executor" require "nulogy_graphql_api/graphql_response" -require "nulogy_graphql_api/schema/base_mutation" require "nulogy_graphql_api/transaction_service" require "nulogy_graphql_api/tasks/schema_changes_checker" require "nulogy_graphql_api/tasks/schema_generator" diff --git a/lib/nulogy_graphql_api/rspec/graphql_helpers.rb b/lib/nulogy_graphql_api/rspec/graphql_helpers.rb index 13debc8..ce405be 100644 --- a/lib/nulogy_graphql_api/rspec/graphql_helpers.rb +++ b/lib/nulogy_graphql_api/rspec/graphql_helpers.rb @@ -1,5 +1,7 @@ module NulogyGraphqlApi module GraphqlHelpers + # Prefer the `request_graphql` method over this one because it exercises more of the stack but doesn't run + # much slower. def execute_graphql(query, schema, variables: {}, context: {}) camelized_variables = variables.deep_transform_keys! { |key| key.to_s.camelize(:lower) } || {} @@ -13,11 +15,11 @@ def execute_graphql(query, schema, variables: {}, context: {}) response.to_h.deep_symbolize_keys end - def request_graphql(url, query, variables: {}, headers: {}) + def request_graphql(url, query, variables: {}, headers: {}, user: nil) params = { query: query, variables: variables }.to_json default_headers = { CONTENT_TYPE: "application/json", - HTTP_AUTHORIZATION: basic_auth_token(default_user.login) + HTTP_AUTHORIZATION: basic_auth_token((user || default_user).login) } post url, params: params, headers: default_headers.merge(headers) diff --git a/lib/nulogy_graphql_api/schema/base_mutation.rb b/lib/nulogy_graphql_api/schema/base_mutation.rb deleted file mode 100644 index d1f2db8..0000000 --- a/lib/nulogy_graphql_api/schema/base_mutation.rb +++ /dev/null @@ -1,12 +0,0 @@ -module NulogyGraphqlApi - module Schema - class BaseMutation < GraphQL::Schema::Mutation - def self.visible?(context) - return true if context[:schema_generation_context?] - return super && yield if block_given? - - super - end - end - end -end diff --git a/lib/nulogy_graphql_api/tasks/schema_generator.rb b/lib/nulogy_graphql_api/tasks/schema_generator.rb index 5702b50..88bae26 100644 --- a/lib/nulogy_graphql_api/tasks/schema_generator.rb +++ b/lib/nulogy_graphql_api/tasks/schema_generator.rb @@ -4,37 +4,32 @@ class SchemaGenerator def initialize(schema_output_path, schema, context: {}) @schema_output_path = schema_output_path @schema = schema - @context = context.merge( - schema_generation_context?: true - ) + @context = context end def generate_schema - check_changes - write_schema_to_file + visible_schema = Class.new(@schema) + visible_schema.use(GraphQL::Schema::AlwaysVisible) + visible_schema.to_definition(context: @context) end - private - def check_changes return if old_schema.blank? - SchemaChangesChecker.new.check_changes(old_schema, @schema) - end - - def old_schema - return unless File.exist?(@schema_output_path) - - File.read(@schema_output_path) + SchemaChangesChecker.new.check_changes(old_schema, generate_schema) end def write_schema_to_file - File.write(@schema_output_path, schema_definition) + File.write(@schema_output_path, generate_schema) puts Rainbow("\nSuccessfully updated #{@schema_output_path}").green end - def schema_definition - GraphQL::Schema::Printer.print_schema(@schema, context: @context) + private + + def old_schema + return unless File.exist?(@schema_output_path) + + File.read(@schema_output_path) end end end diff --git a/lib/nulogy_graphql_api/version.rb b/lib/nulogy_graphql_api/version.rb index 4e75de5..66efba6 100644 --- a/lib/nulogy_graphql_api/version.rb +++ b/lib/nulogy_graphql_api/version.rb @@ -1,3 +1,3 @@ module NulogyGraphqlApi - VERSION = "3.0.1" + VERSION = "4.0.0" end diff --git a/spec/dummy/config/initializers/assets.rb b/spec/dummy/config/initializers/assets.rb index fe48fc3..9c7a620 100644 --- a/spec/dummy/config/initializers/assets.rb +++ b/spec/dummy/config/initializers/assets.rb @@ -1,7 +1,7 @@ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = '1.0' +# Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path diff --git a/spec/dummy/db/test.sqlite3 b/spec/dummy/db/test.sqlite3 index e69de29..210e705 100644 Binary files a/spec/dummy/db/test.sqlite3 and b/spec/dummy/db/test.sqlite3 differ diff --git a/spec/integration/lib/nulogy_graphql_api/tasks/schema_generator_spec.rb b/spec/integration/lib/nulogy_graphql_api/tasks/schema_generator_spec.rb new file mode 100644 index 0000000..65c37b2 --- /dev/null +++ b/spec/integration/lib/nulogy_graphql_api/tasks/schema_generator_spec.rb @@ -0,0 +1,61 @@ +class VisibleQuery < GraphQL::Schema::Object + field :test, String, null: false +end + +class InvisibleQuery < GraphQL::Schema::Object + field :test, String, null: false + + def self.visible?(_context) + false + end +end + +RSpec.describe NulogyGraphqlApi::Tasks::SchemaGenerator do + it "stringifies the schema" do + fake_schema = Class.new(GraphQL::Schema) do + query VisibleQuery + end + task = NulogyGraphqlApi::Tasks::SchemaGenerator.new("schema_output_path", fake_schema) + + result = task.generate_schema + + expect(result).to eq(<<~GQL) + schema { + query: VisibleQuery + } + + type VisibleQuery { + test: String! + } + GQL + end + + it "stringifies invisible parts of the schema" do + fake_schema = Class.new(GraphQL::Schema) do + query InvisibleQuery + end + task = NulogyGraphqlApi::Tasks::SchemaGenerator.new("schema_output_path", fake_schema) + + result = task.generate_schema + + expect(result).to eq(<<~GQL) + schema { + query: InvisibleQuery + } + + type InvisibleQuery { + test: String! + } + GQL + end + + it "does not alter the given schema" do + fake_schema = Class.new(GraphQL::Schema) + old_warden = fake_schema.warden_class + task = NulogyGraphqlApi::Tasks::SchemaGenerator.new("schema_output_path", fake_schema) + + task.generate_schema + + expect(fake_schema.warden_class).to eq(old_warden) + end +end