diff --git a/Gemfile b/Gemfile index 04a4443..07f4bc2 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,5 @@ source 'https://rubygems.org' # gem "rails" gem 'rspec' +gem 'sorbet-static-and-runtime', '>= 0.5.11511' + diff --git a/Gemfile.lock b/Gemfile.lock index cd47bb7..8064893 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,6 +15,13 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) + sorbet (0.5.11531) + sorbet-static (= 0.5.11531) + sorbet-runtime (0.5.11531) + sorbet-static (0.5.11531-x86_64-linux) + sorbet-static-and-runtime (0.5.11531) + sorbet (= 0.5.11531) + sorbet-runtime (= 0.5.11531) PLATFORMS ruby @@ -22,6 +29,7 @@ PLATFORMS DEPENDENCIES rspec + sorbet-static-and-runtime (>= 0.5.11511) BUNDLED WITH 2.5.11 diff --git a/lib/public/relationship.rb b/lib/public/relationship.rb index 5af3f72..d7c5e8b 100644 --- a/lib/public/relationship.rb +++ b/lib/public/relationship.rb @@ -17,7 +17,9 @@ class Relationship < T::Struct const :optional, T::Boolean # Are there multiple resources in the other side of the relationship? - delegate :many_cardinality?, to: :type + def many_cardinality? + type.many_cardinality? + end sig { returns(T::Hash[String, T.untyped]) } def dump diff --git a/spec/i18n_keys_for_resource_spec.rb b/spec/i18n_keys_for_resource_spec.rb index a817de0..56d382f 100644 --- a/spec/i18n_keys_for_resource_spec.rb +++ b/spec/i18n_keys_for_resource_spec.rb @@ -1,6 +1,9 @@ # typed: strict # frozen_string_literal: true +require 'spec_helper' +require_relative '../lib/public/i18n_keys_for_resource' + RSpec.describe ResourceRegistry::I18nKeysForResource do subject { described_class.new(resource) } diff --git a/spec/openapi_specification_generator_spec.rb b/spec/openapi_specification_generator_spec.rb deleted file mode 100644 index 8fded30..0000000 --- a/spec/openapi_specification_generator_spec.rb +++ /dev/null @@ -1,655 +0,0 @@ -# typed: strict - -RSpec.describe ResourceRegistry::OpenapiSpecificationGenerator do - let(:schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: schema_properties - ) - end - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'foo', - types: [SchemaRegistry::PropertyType::String], - required: true - ) - ] - end - let(:dummy_dto) { Class.new(T::Struct) } - let(:verbs) do - { read: ResourceRegistry::Verb.new(id: :read, dto_raw: dummy_dto.to_s, schema: verb_schema) } - end - let(:capabilities) do - { rest: ResourceRegistry::Capabilities::Rest.new(is_public: true, private_verbs: []) } - end - let(:prop) do - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - description: 'It does something', - required: true - ) - end - - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - required: true - ), - SchemaRegistry::Property.new( - name: 'name', - types: [SchemaRegistry::PropertyType::String], - required: true - ) - ], - raw_json_schema: { - 'Create' => { - 'type' => 'object', - 'properties' => { - 'id' => { - 'type' => 'integer', - 'typedef' => Integer, - 'description' => 'id of the resource', - 'example' => 1 - }, - 'name' => { - 'type' => 'string', - 'typedef' => String, - 'description' => 'name of the resource', - 'example' => 'John Doe' - } - } - } - } - ) - end - let(:resource) do - ResourceRegistry::Resource.new( - repository_raw: ApiPublic::Repositories::Core::ApiKeys.to_s, - verbs: verbs, - schema: schema, - capabilities: capabilities - ) - end - - let(:output) { subject.call } - - subject { described_class.new(resources: [resource]) } - - describe '#call' do - it 'pluralizes the resource' do - expect(subject.call.to_s).to match('/v2/resources/api_public/api_keys') - end - - it 'gives disctinct names for read operations' do - paths = subject.call['paths'] - index = paths['/api/v2/resources/api_public/api_keys']['get'] - show = paths['/api/v2/resources/api_public/api_keys/{id}']['get'] - - expect(index['description']).to eq('Reads all Api keys') - expect(index['description']).to eq(index['summary']) - - expect(show['description']).to eq('Reads a single Api key') - expect(show['description']).to eq(show['summary']) - end - - context 'when create' do - let(:verbs) do - { - create: - ResourceRegistry::Verb.new(id: :create, dto_raw: dummy_dto.to_s, schema: verb_schema) - } - end - - it 'gives disctinct names for create operations' do - paths = subject.call['paths'] - create = paths['/api/v2/resources/api_public/api_keys']['post'] - - expect(create['description']).to eq('Creates a/an Api key') - expect(create['description']).to eq(create['summary']) - end - - it 'generates request body properties' do - paths = subject.call['paths'] - create = paths['/api/v2/resources/api_public/api_keys']['post'] - properties = create['requestBody']['content']['application/json']['schema']['properties'] - id = properties['id'] - name = properties['name'] - - expect(id['description']).to eq('id of the resource') - expect(id['example']).to eq(1) - expect(name['description']).to eq('name of the resource') - expect(name['example']).to eq('John Doe') - end - end - - context 'when update' do - let(:verbs) do - { - update: - ResourceRegistry::Verb.new(id: :update, dto_raw: dummy_dto.to_s, schema: verb_schema) - } - end - - it 'gives disctinct names for update operations' do - paths = subject.call['paths'] - update = paths['/api/v2/resources/api_public/api_keys/{id}']['put'] - - expect(update['description']).to eq('Updates a/an Api key') - expect(update['description']).to eq(update['summary']) - end - end - - context 'when delete' do - let(:verbs) do - { - delete: - ResourceRegistry::Verb.new(id: :delete, dto_raw: dummy_dto.to_s, schema: verb_schema) - } - end - - it 'gives disctinct names for delete operations' do - paths = subject.call['paths'] - delete = paths['/api/v2/resources/api_public/api_keys/{id}']['delete'] - - expect(delete['description']).to eq('Deletes a/an Api key') - expect(delete['description']).to eq(delete['summary']) - end - end - - context 'parameters types' do - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.first['schema']['type']).to eq 'integer' - expect(parameters.last['schema']['type']).to eq 'string' - end - end - - context 'parameter description' do - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - description: 'It does something', - required: true - ), - SchemaRegistry::Property.new( - name: 'name', - types: [SchemaRegistry::PropertyType::String], - description: 'It does something', - required: true - ) - ] - ) - end - - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.last['description']).to eq 'It does something' - end - end - - context 'parameter type when array' do - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'goals', - namespace: 'goals', - properties: [ - SchemaRegistry::Property.new( - name: 'ids', - types: [SchemaRegistry::PropertyType::Array], - description: 'It fetches the ids of all goals', - required: true, - items: [prop] - ) - ] - ) - end - - xit do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.first['schema']).to have_key('items') - end - end - - context 'required parameters' do - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - description: 'It does something', - required: true - ), - SchemaRegistry::Property.new( - name: 'name', - types: [SchemaRegistry::PropertyType::Null, SchemaRegistry::PropertyType::String], - description: 'It does something', - required: true - ) - ] - ) - end - - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.first['required']).to be_truthy - expect(parameters.last['required']).to be_falsey - end - end - - context 'deprecated parameters' do - around { |example| Timecop.freeze(current_date) { example.run } } - - let(:current_date) { Date.new(2022, 10, 28) } - let(:deprecated_on) { Date.new(2022, 10, 28) } - - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - deprecated_on: deprecated_on, - required: true - ), - SchemaRegistry::Property.new( - name: 'name', - types: [SchemaRegistry::PropertyType::Null, SchemaRegistry::PropertyType::String], - deprecated_on: deprecated_on, - required: true - ) - ] - ) - end - - context 'deprecated_on date is nil' do - let(:deprecated_on) { nil } - - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.last['deprecated']).to be_falsey - end - end - - context 'before expiration date' do - let(:current_date) { Date.new(2022, 10, 28) } - - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.last['deprecated']).to be_falsey - end - end - - context 'after expiration date' do - let(:current_date) { Date.new(2022, 10, 29) } - - it do - parameters = output['paths'].values.first['get']['parameters'] - - expect(parameters.last['deprecated']).to be_truthy - end - end - end - - context 'private parameters' do - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - description: 'It does something', - required: true - ), - SchemaRegistry::Property.new( - name: 'private_field_name', - types: [SchemaRegistry::PropertyType::String], - description: 'It does something', - required: true, - serialization_groups: Set[:private] - ) - ] - ) - end - - it 'non-private fields are included' do - parameters = output['paths'].values.first['get']['parameters'] - expect(parameters).to(be_any { |p| p['name'] == 'id' }) - end - - it 'private fields are excluded' do - parameters = output['paths'].values.first['get']['parameters'] - expect(parameters).not_to(be_any { |p| p['name'] == 'private_field_name' }) - end - end - - context 'private body fields' do - let(:verb_schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - description: 'It does something', - required: true - ), - SchemaRegistry::Property.new( - name: 'private_field_name', - types: [SchemaRegistry::PropertyType::String], - description: 'It does something', - required: true, - serialization_groups: Set[:private] - ) - ] - ) - end - - let(:verbs) do - { - create: - ResourceRegistry::Verb.new(id: :create, dto_raw: dummy_dto.to_s, schema: verb_schema) - } - end - - it 'non-private fields are included' do - body = output['paths'].values.first['post']['requestBody']['content']['application/json'] - expect(body['schema']['properties'].keys).to include('id') - end - - it 'private fields are excluded' do - body = output['paths'].values.first['post']['requestBody']['content']['application/json'] - expect(body['schema']['properties'].keys).not_to include('private_field_name') - end - end - - context 'schema prop is nilable' do - let(:schema) do - SchemaRegistry::Schema.new( - name: 'employees', - namespace: 'employees', - properties: [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - required: true - ), - SchemaRegistry::Property.new( - name: 'dummy_property', - types: [SchemaRegistry::PropertyType::Null, SchemaRegistry::PropertyType::String], - required: true - ), - SchemaRegistry::Property.new( - name: 'another_dummy_property', - types: [SchemaRegistry::PropertyType::Integer, SchemaRegistry::PropertyType::Null], - required: true - ) - ], - raw_json_schema: { - 'employees' => { - 'type' => 'object', - 'properties' => { - 'id' => { - 'type' => 'integer' - }, - 'dummy_property' => { - 'type' => %w[null string] - }, - 'another_dummy_property' => { - 'type' => %w[integer null] - } - } - } - } - ) - end - - it do - schema = output['components']['schemas']['employees'] - - expect(schema['required']).to eq(['id']) - expect(schema['properties']).to include( - 'id' => include('type' => 'integer'), - 'dummy_property' => include('type' => 'string'), - 'another_dummy_property' => include('type' => 'integer') - ) - end - end - - context 'schema prop not is nilable' do - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - required: true - ), - SchemaRegistry::Property.new( - name: 'dummy_property', - types: [SchemaRegistry::PropertyType::String], - required: true - ) - ] - end - - it do - expect(output['components']['schemas']['employees']['required']).to include 'dummy_property' - end - end - - context 'schema prop is private' do - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'id', - types: [SchemaRegistry::PropertyType::Integer], - required: true - ), - SchemaRegistry::Property.new( - name: 'dummy_property', - types: [SchemaRegistry::PropertyType::String], - required: true, - serialization_groups: Set[:foo] - ), - SchemaRegistry::Property.new( - name: 'private_property', - types: [SchemaRegistry::PropertyType::String], - required: true, - serialization_groups: Set[:private] - ) - ] - end - - it 'non-private fields are included' do - properties = output['components']['schemas']['employees']['properties'] - expect(properties).to be_key('id') - expect(properties).to be_key('dummy_property') - end - - it 'private fields are excluded' do - properties = output['components']['schemas']['employees']['properties'] - expect(properties).not_to be_key('private_property') - end - end - - context 'schema has an example' do - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'dummy_property', - types: [SchemaRegistry::PropertyType::String], - example: 'I am a dummy example', - required: true - ) - ] - end - - it 'has an example' do - expect(output['components']['schemas']['employees']['example']).to eq [ - { 'dummy_property' => 'I am a dummy example' } - ] - end - end - - context 'schema deprecated' do - around { |example| Timecop.freeze(current_date) { example.run } } - - let(:current_date) { Date.new(2022, 10, 28) } - let(:deprecated_on) { Date.new(2022, 10, 28) } - - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'dummy_property', - types: [SchemaRegistry::PropertyType::String], - deprecated_on: deprecated_on, - required: true - ) - ] - end - - context 'expiration date is nil' do - let(:deprecated_on) { nil } - - it do - expect( - output['components']['schemas']['employees']['properties']['dummy_property'][ - 'deprecated' - ] - ).to be_falsey - end - end - - context 'before expiration date' do - let(:current_date) { Date.new(2022, 10, 28) } - - it do - expect( - output['components']['schemas']['employees']['properties']['dummy_property'][ - 'deprecated' - ] - ).to be_falsey - end - end - - context 'after expiration date' do - let(:current_date) { Date.new(2022, 10, 29) } - - it do - expect( - output['components']['schemas']['employees']['properties']['dummy_property'][ - 'deprecated' - ] - ).to be_truthy - end - end - end - - describe 'schema property enum' do - it 'does not exist' do - property = output.dig('components', 'schemas', 'employees', 'properties', 'foo') - - keys = property.keys - expect(keys).to be_present - expect(keys).not_to include('enum') - end - - context 'with a property that has a enum' do - let(:schema_properties) do - [ - SchemaRegistry::Property.new( - name: 'foo', - types: [SchemaRegistry::PropertyType::String], - required: true, - enum_values: %w[one two] - ) - ] - it do - property = output.dig('components', 'schemas', 'employees', 'properties', 'foo') - - expect(property['enum']).to eq(%w[one two]) - end - end - end - end - - context 'verb has summary' do - let(:verbs) do - { - read: - ResourceRegistry::Verb.new( - id: :read, - dto_raw: dummy_dto.to_s, - summary: 'Example summary', - schema: verb_schema - ) - } - end - - it { expect(output['paths'].values.first['get']['summary']).to eq 'Example summary' } - end - - context 'verb deprecated' do - around { |example| Timecop.freeze(current_date) { example.run } } - - let(:current_date) { Date.new(2022, 10, 28) } - let(:deprecated_on) { Date.new(2022, 10, 28) } - - let(:verbs) do - { - read: - ResourceRegistry::Verb.new( - id: :read, - dto_raw: dummy_dto.to_s, - deprecated_on: deprecated_on, - schema: verb_schema - ) - } - end - - context 'deprecated on is nil' do - let(:deprecated_on) { nil } - - it { expect(output['paths'].values.first['get']['deprecated']).to be_falsey } - end - - context 'before expiration date' do - let(:current_date) { Date.new(2022, 10, 28) } - - it { expect(output['paths'].values.first['get']['deprecated']).to be_falsey } - end - - context 'after expiration date' do - let(:current_date) { Date.new(2022, 10, 29) } - - it { expect(output['paths'].values.first['get']['deprecated']).to be_truthy } - end - end - end -end diff --git a/spec/public/registry_spec.rb b/spec/public/registry_spec.rb index 07825b9..47fd681 100644 --- a/spec/public/registry_spec.rb +++ b/spec/public/registry_spec.rb @@ -1,5 +1,7 @@ # typed: strict +require_relative '../../lib/public/registry' + RSpec.describe ResourceRegistry::Registry do let(:resources) { [resource] } let(:registry) { described_class.new(resources: resources) } diff --git a/spec/public/relationship_spec.rb b/spec/public/relationship_spec.rb index 73253a2..5437f0a 100644 --- a/spec/public/relationship_spec.rb +++ b/spec/public/relationship_spec.rb @@ -1,5 +1,7 @@ # typed: strict +require_relative '../../lib/public/relationship' + RSpec.describe ResourceRegistry::Relationship do describe '#load' do let(:type) { ResourceRegistry::RelationshipTypes::HasOne.new({}) } diff --git a/spec/public/resource_spec.rb b/spec/public/resource_spec.rb index 26c6f03..a8f5140 100644 --- a/spec/public/resource_spec.rb +++ b/spec/public/resource_spec.rb @@ -1,5 +1,8 @@ # typed: strict +require 'spec_helper' +require_relative '../../lib/public/resource' + RSpec.describe ResourceRegistry::Resource do let(:capability) { Graphql::Capability.new } let(:dummy_struct) { T::Struct } diff --git a/spec/public/versions_spec.rb b/spec/public/versions_spec.rb index f487884..e641a3b 100644 --- a/spec/public/versions_spec.rb +++ b/spec/public/versions_spec.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true # typed: strict +require 'spec_helper' +require_relative '../../lib/public/versions' + RSpec.describe ResourceRegistry::Versions do subject do described_class.new( diff --git a/spec/resource_struct_builder_spec.rb b/spec/resource_struct_builder_spec.rb index 8c64ac2..4f0c671 100644 --- a/spec/resource_struct_builder_spec.rb +++ b/spec/resource_struct_builder_spec.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true # typed: strict +require 'spec_helper' +require_relative '../lib/public/resource_struct_builder' + module ::ResourceRegistry module DtoBuilderSpec module Dtos diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..4a24c5c --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'sorbet-runtime'