diff --git a/.rubocop.yml b/.rubocop.yml index 81319d1..c96a2be 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,3 +10,9 @@ AllCops: Exclude: - db/schema.rb - bin/**/* + +RSpec/ExampleLength: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false diff --git a/Gemfile b/Gemfile index e824bb2..5a8db7b 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "2.7.4" -gem "jbuilder", "~> 2.7" gem "pg", "~> 1.1" gem "puma", "~> 5.0" gem "rails", "~> 6.1.4" @@ -13,14 +12,19 @@ gem "sass-rails", ">= 6" gem "turbolinks", "~> 5" gem "webpacker", "~> 5.0" gem "bootsnap", ">= 1.4.4", require: false +gem "image_processing", "~> 1.12" gem "devise" -gem "devise-bootstrap-views" gem "devise-i18n" -gem "administrate" -gem "graphql" +gem "devise-bootstrap-views" + +gem "administrate-field-active_storage" gem "tailwindcss-rails" +gem "administrate" +gem "enumerize" gem "httparty" +gem "graphql" +gem "pundit" group :development, :test do gem "dotenv-rails" @@ -34,9 +38,15 @@ group :development, :test do end group :development do + gem "annotate" gem "graphql_playground-rails" gem "web-console", ">= 4.1.0" gem "listen", "~> 3.3" gem "spring" end + +group :test do + gem "shoulda-matchers", "~> 5.0" + gem "rspec-graphql_matchers", "~> 1.3" +end diff --git a/Gemfile.lock b/Gemfile.lock index 2fd4fc7..bc77765 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,12 @@ GEM momentjs-rails (~> 2.8) sassc-rails (~> 2.1) selectize-rails (~> 0.6) + administrate-field-active_storage (0.3.7) + administrate (>= 0.2.2) + rails (>= 6.0) + annotate (3.1.1) + activerecord (>= 3.2, < 7.0) + rake (>= 10.4, < 14.0) ast (2.4.2) bcrypt (3.1.16) bindex (0.8.1) @@ -106,6 +112,8 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) + enumerize (2.4.0) + activesupport (>= 3.2) erubi (1.10.0) ffi (1.15.3) globalid (0.5.2) @@ -118,8 +126,9 @@ GEM multi_xml (>= 0.5.2) i18n (1.8.10) concurrent-ruby (~> 1.0) - jbuilder (2.11.2) - activesupport (>= 5.0.0) + image_processing (1.12.1) + mini_magick (>= 4.9.5, < 5) + ruby-vips (>= 2.0.17, < 3) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -149,6 +158,7 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2021.0704) + mini_magick (4.11.0) mini_mime (1.1.0) mini_portile2 (2.6.1) minitest (5.14.4) @@ -174,6 +184,8 @@ GEM public_suffix (4.0.6) puma (5.4.0) nio4r (~> 2.0) + pundit (2.1.0) + activesupport (>= 3.0.0) racc (1.5.2) rack (2.2.3) rack-proxy (0.7.0) @@ -221,6 +233,8 @@ GEM rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) + rspec-graphql_matchers (1.3.0) + graphql (>= 1.8, < 2.0) rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) @@ -254,6 +268,8 @@ GEM rubocop-shopify (2.2.0) rubocop (~> 1.18) ruby-progressbar (1.11.0) + ruby-vips (2.1.2) + ffi (~> 1.12) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -266,6 +282,8 @@ GEM tilt selectize-rails (0.12.6) semantic_range (3.0.0) + shoulda-matchers (5.0.0) + activesupport (>= 5.2.0) spring (2.1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) @@ -308,26 +326,32 @@ PLATFORMS DEPENDENCIES administrate + administrate-field-active_storage + annotate bootsnap (>= 1.4.4) capybara devise devise-bootstrap-views devise-i18n dotenv-rails + enumerize graphql graphql_playground-rails httparty - jbuilder (~> 2.7) + image_processing (~> 1.12) listen (~> 3.3) pg (~> 1.1) pry-byebug puma (~> 5.0) + pundit rails (~> 6.1.4) + rspec-graphql_matchers (~> 1.3) rspec-rails rubocop-rails rubocop-rspec rubocop-shopify sass-rails (>= 6) + shoulda-matchers (~> 5.0) spring tailwindcss-rails turbolinks (~> 5) diff --git a/app/controllers/admin/user_documents_controller.rb b/app/controllers/admin/user_documents_controller.rb new file mode 100644 index 0000000..6fc8f77 --- /dev/null +++ b/app/controllers/admin/user_documents_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Admin + class UserDocumentsController < Admin::ApplicationController + def valid_action?(name, resource = resource_class) + ["destroy"].exclude?(name.to_s) && super + end + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb new file mode 100644 index 0000000..b8cac1c --- /dev/null +++ b/app/controllers/admin/users_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Admin + class UsersController < Admin::ApplicationController + def valid_action?(name, resource = resource_class) + ["new", "destroy"].exclude?(name.to_s) && super + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e049134..396f23e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ # frozen_string_literal: true class ApplicationController < ActionController::Base + include Pundit end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 8f9e23c..b729732 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -9,8 +9,8 @@ class GraphqlController < ApplicationController query = params[:query] operation_name = params[:operationName] context = { - current_user: current_admin_user, # || current_auth.current_user, current_auth: current_auth, + current_user: current_admin_user, # || current_auth.current_user, } result = XStakeSchema.execute(query, variables: variables, context: context, operation_name: operation_name) render(json: result) diff --git a/app/dashboards/user_dashboard.rb b/app/dashboards/user_dashboard.rb new file mode 100644 index 0000000..41e33f7 --- /dev/null +++ b/app/dashboards/user_dashboard.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require "administrate/base_dashboard" + +class UserDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + full_name: Field::String, + first_name: Field::String, + last_name: Field::String, + email: Field::String, + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [:full_name, :email].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [:id, :first_name, :last_name, :email, :created_at, :updated_at].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [:first_name, :last_name].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how users are displayed + # across all pages of the admin dashboard. + # + # def display_resource(user) + # "User ##{user.id}" + # end +end diff --git a/app/dashboards/user_document_dashboard.rb b/app/dashboards/user_document_dashboard.rb new file mode 100644 index 0000000..29d7a1d --- /dev/null +++ b/app/dashboards/user_document_dashboard.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require "administrate/base_dashboard" + +class UserDocumentDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + user: Field::BelongsTo, + image: Field::ActiveStorage, + status: Field::Select.with_options(collection: UserDocument.status.values), + created_at: Field::DateTime, + updated_at: Field::DateTime, + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = [:user, :status].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = [:id, :user, :status, :image, :created_at, :updated_at].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = [:status].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how user documents are displayed + # across all pages of the admin dashboard. + # + # def display_resource(user_document) + # "UserDocument ##{user_document.id}" + # end +end diff --git a/app/graphql/inputs/user_attributes_input.rb b/app/graphql/inputs/user_attributes_input.rb new file mode 100644 index 0000000..61dc56a --- /dev/null +++ b/app/graphql/inputs/user_attributes_input.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Inputs + class UserAttributesInput < Types::BaseInputObject + graphql_name "UserAttributesInput" + + argument :first_name, String, required: true + argument :last_name, String, required: true + end +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index a6576af..e589946 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -5,5 +5,9 @@ module Mutations field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject + + field :errors, [String], + null: true, + description: "Errors encountered during execution of the mutation." end end diff --git a/app/graphql/mutations/create_user.rb b/app/graphql/mutations/create_user.rb new file mode 100644 index 0000000..c314e14 --- /dev/null +++ b/app/graphql/mutations/create_user.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Mutations + class CreateUser < BaseMutation + field :success, Boolean, null: false + + argument :user, Inputs::UserAttributesInput, required: true + + def resolve(user:) + User.create!({ **user, email: context[:current_auth].email }) + + { success: true } + rescue ActiveRecord::RecordInvalid => e + { success: false, errors: [e.message] } + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 1c6dc93..7712f72 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true module Types class MutationType < Types::BaseObject - # TODO: remove me - field :test_field, String, null: false, - description: "An example field added by the generator" - def test_field - "Hello World" - end + field :create_user, mutation: Mutations::CreateUser end end diff --git a/app/models/admin_user.rb b/app/models/admin_user.rb index 9380387..3d62e8d 100644 --- a/app/models/admin_user.rb +++ b/app/models/admin_user.rb @@ -1,4 +1,23 @@ # frozen_string_literal: true + +# == Schema Information +# +# Table name: admin_users +# +# id :bigint not null, primary key +# email :string default(""), not null +# encrypted_password :string default(""), not null +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_admin_users_on_email (email) UNIQUE +# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE +# class AdminUser < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..42f639c --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# email :string not null +# first_name :string not null +# last_name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_email (email) UNIQUE +# +class User < ApplicationRecord + has_many :documents, class_name: "UserDocument", dependent: :destroy + + validates :first_name, :last_name, :email, presence: true + validates :email, uniqueness: true + + def full_name + "#{first_name} #{last_name}" + end +end diff --git a/app/models/user_document.rb b/app/models/user_document.rb new file mode 100644 index 0000000..df5578c --- /dev/null +++ b/app/models/user_document.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_documents +# +# id :bigint not null, primary key +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :bigint not null +# +# Indexes +# +# index_user_documents_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +class UserDocument < ApplicationRecord + extend Enumerize + + belongs_to :user + has_one_attached :image + + enumerize :status, + in: [:pending_review, :approved, :refused], + default: :pending_review +end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 0000000..f527406 --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +class ApplicationPolicy + attr_reader :user, :record + + def initialize(user, record) + @user = user + @record = record + end + + def index? + false + end + + def show? + false + end + + def create? + false + end + + def new? + create? + end + + def update? + false + end + + def edit? + update? + end + + def destroy? + false + end + + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + scope.all + end + end +end diff --git a/app/services/auth/authenticate.rb b/app/services/auth/authenticate.rb index dce1225..b6d8838 100644 --- a/app/services/auth/authenticate.rb +++ b/app/services/auth/authenticate.rb @@ -8,6 +8,8 @@ module Auth end def profile + return nil if jwt_token.blank? + Auth0Client.find_profile(jwt_token) end end diff --git a/config/routes.rb b/config/routes.rb index b85e8d8..ccf16ca 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,14 +3,16 @@ Rails.application.routes.draw do devise_for :admin_users namespace :admin do + resources :users + resources :user_documents resources :admin_users - root to: "admin_users#index" + root to: "users#index" end root to: "home#index" get "*all" => "home#index", constraints: lambda { |req| - Rails.env.development? ? req.path.exclude?("playground") : req + req.path.exclude?("playground") && req.path.exclude?("rails") } post "/graphql", to: "graphql#execute" diff --git a/db/migrate/20210804230323_create_users.rb b/db/migrate/20210804230323_create_users.rb new file mode 100644 index 0000000..55ca6e0 --- /dev/null +++ b/db/migrate/20210804230323_create_users.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +class CreateUsers < ActiveRecord::Migration[6.1] + def change + create_table(:users) do |t| + t.string(:first_name, null: false) + t.string(:last_name, null: false) + t.string(:email, null: false) + + t.timestamps + end + + add_index(:users, :email, unique: true) + end +end diff --git a/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb b/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..08c3ae3 --- /dev/null +++ b/db/migrate/20210804233306_create_active_storage_tables.active_storage.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table(:active_storage_blobs) do |t| + t.string(:key, null: false) + t.string(:filename, null: false) + t.string(:content_type) + t.text(:metadata) + t.string(:service_name, null: false) + t.bigint(:byte_size, null: false) + t.string(:checksum, null: false) + t.datetime(:created_at, null: false) + + t.index([:key], unique: true) + end + + create_table(:active_storage_attachments) do |t| + t.string(:name, null: false) + t.references(:record, null: false, polymorphic: true, index: false) + t.references(:blob, null: false) + + t.datetime(:created_at, null: false) + + t.index( + [:record_type, :record_id, :name, :blob_id], + name: "index_active_storage_attachments_uniqueness", + unique: true + ) + t.foreign_key(:active_storage_blobs, column: :blob_id) + end + + # rubocop:disable Rails/CreateTableWithTimestamps + create_table(:active_storage_variant_records) do |t| + t.belongs_to(:blob, null: false, index: false) + t.string(:variation_digest, null: false) + + t.index([:blob_id, :variation_digest], name: "index_active_storage_variant_records_uniqueness", unique: true) + t.foreign_key(:active_storage_blobs, column: :blob_id) + end + # rubocop:enable Rails/CreateTableWithTimestamps + end +end diff --git a/db/migrate/20210805000225_create_user_documents.rb b/db/migrate/20210805000225_create_user_documents.rb new file mode 100644 index 0000000..8d663a3 --- /dev/null +++ b/db/migrate/20210805000225_create_user_documents.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +class CreateUserDocuments < ActiveRecord::Migration[6.1] + def change + create_table(:user_documents) do |t| + t.string(:status, null: false) + t.references(:user, null: false, foreign_key: true) + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 70fae2b..edd001a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,11 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_03_222524) do +ActiveRecord::Schema.define(version: 2021_08_05_000225) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.string "service_name", null: false + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + create_table "active_storage_variant_records", force: :cascade do |t| + t.bigint "blob_id", null: false + t.string "variation_digest", null: false + t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true + end + create_table "admin_users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false @@ -27,10 +55,24 @@ ActiveRecord::Schema.define(version: 2021_08_03_222524) do t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true end - create_table "customers", force: :cascade do |t| - t.string "name" + create_table "user_documents", force: :cascade do |t| + t.string "status", null: false + t.bigint "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.index ["user_id"], name: "index_user_documents_on_user_id" end + create_table "users", force: :cascade do |t| + t.string "first_name", null: false + t.string "last_name", null: false + t.string "email", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["email"], name: "index_users_on_email", unique: true + end + + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "user_documents", "users" end diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake new file mode 100644 index 0000000..9e736fc --- /dev/null +++ b/lib/tasks/auto_annotate_models.rake @@ -0,0 +1,60 @@ +# frozen_string_literal: true +# NOTE: only doing this in development as some production environments (Heroku) +# NOTE: are sensitive to local FS writes, and besides -- it's just not proper +# NOTE: to have a dev-mode tool do its thing in production. +if Rails.env.development? + require "annotate" + task set_annotation_options: :environment do + # You can override any of these by setting an environment variable of the + # same name. + Annotate.set_defaults( + "active_admin" => "false", + "additional_file_patterns" => [], + "routes" => "false", + "models" => "true", + "position_in_routes" => "before", + "position_in_class" => "before", + "position_in_test" => "before", + "position_in_fixture" => "before", + "position_in_factory" => "before", + "position_in_serializer" => "before", + "show_foreign_keys" => "true", + "show_complete_foreign_keys" => "false", + "show_indexes" => "true", + "simple_indexes" => "false", + "model_dir" => "app/models", + "root_dir" => "", + "include_version" => "false", + "require" => "", + "exclude_tests" => "false", + "exclude_fixtures" => "false", + "exclude_factories" => "false", + "exclude_serializers" => "false", + "exclude_scaffolds" => "true", + "exclude_controllers" => "true", + "exclude_helpers" => "true", + "exclude_sti_subclasses" => "false", + "ignore_model_sub_dir" => "false", + "ignore_columns" => nil, + "ignore_routes" => nil, + "ignore_unknown_models" => "false", + "hide_limit_column_types" => "integer,bigint,boolean", + "hide_default_column_types" => "json,jsonb,hstore", + "skip_on_db_migrate" => "false", + "format_bare" => "true", + "format_rdoc" => "false", + "format_yard" => "false", + "format_markdown" => "false", + "sort" => "false", + "force" => "false", + "frozen" => "false", + "classified_sort" => "true", + "trace" => "false", + "wrapper_open" => nil, + "wrapper_close" => nil, + "with_comment" => "true" + ) + end + + Annotate.load_tasks +end diff --git a/spec/graphql/inputs/user_attributes_input_spec.rb b/spec/graphql/inputs/user_attributes_input_spec.rb new file mode 100644 index 0000000..d3fe858 --- /dev/null +++ b/spec/graphql/inputs/user_attributes_input_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +require "rails_helper" + +RSpec.describe(Inputs::UserAttributesInput) do + subject { described_class } + + describe "arguments" do + it { is_expected.to(accept_argument(:first_name).of_type("String!")) } + it { is_expected.to(accept_argument(:last_name).of_type("String!")) } + end +end diff --git a/spec/graphql/mutations/create_user_spec.rb b/spec/graphql/mutations/create_user_spec.rb new file mode 100644 index 0000000..ef8f711 --- /dev/null +++ b/spec/graphql/mutations/create_user_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true +require "rails_helper" + +RSpec.describe(Mutations::CreateUser) do + describe "#resolve" do + let(:mutation_string) do + <<~GQL + mutation($input: CreateUserInput!) { + createUser(input: $input) { + success + errors + } + } + GQL + end + + let(:context) do + { + current_auth: Auth::Profile.new({ + id: "_", + email: "user@example.com", + }), + } + end + + let(:variables) do + { + input: { user: { + firstName: "First Name", + lastName: "Last Name", + } }, + } + end + + context "when current_auth is not being used by any user" do + it "create a user to auth" do + result = XStakeSchema.execute( + mutation_string, + variables: variables, + context: context + ).to_h + + expect(result["data"]["createUser"]["success"]).to(eq(true)) + end + end + + context "when auth is being used by no users" do + it "returns error" do + User.create( + first_name: "First Name", + last_name: "Last Name", + email: "user@example.com" + ) + + result = XStakeSchema.execute( + mutation_string, + variables: variables, + context: context + ).to_h + + expect(result["data"]["createUser"]["success"]).to(eq(false)) + expect(result["data"]["createUser"]["errors"]).to(eq(["Validation failed: Email has already been taken"])) + end + end + end +end diff --git a/spec/models/admin_user_spec.rb b/spec/models/admin_user_spec.rb index cea59fd..a6218ff 100644 --- a/spec/models/admin_user_spec.rb +++ b/spec/models/admin_user_spec.rb @@ -1,4 +1,23 @@ # frozen_string_literal: true + +# == Schema Information +# +# Table name: admin_users +# +# id :bigint not null, primary key +# email :string default(""), not null +# encrypted_password :string default(""), not null +# remember_created_at :datetime +# reset_password_sent_at :datetime +# reset_password_token :string +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_admin_users_on_email (email) UNIQUE +# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE +# require "rails_helper" RSpec.describe(AdminUser, type: :model) do diff --git a/spec/models/user_document_spec.rb b/spec/models/user_document_spec.rb new file mode 100644 index 0000000..d13bd1a --- /dev/null +++ b/spec/models/user_document_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: user_documents +# +# id :bigint not null, primary key +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# user_id :bigint not null +# +# Indexes +# +# index_user_documents_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +require "rails_helper" + +RSpec.describe(UserDocument, type: :model) do + describe "associations" do + it { is_expected.to(belong_to(:user)) } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..d1af9d9 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: users +# +# id :bigint not null, primary key +# email :string not null +# first_name :string not null +# last_name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_users_on_email (email) UNIQUE +# +require "rails_helper" + +RSpec.describe(User, type: :model) do + describe "validations" do + it { is_expected.to(validate_presence_of(:first_name)) } + it { is_expected.to(validate_presence_of(:last_name)) } + it { is_expected.to(validate_presence_of(:email)) } + end + + describe "associations" do + it { is_expected.to(have_many(:documents)) } + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 262e28b..990e786 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -67,3 +67,10 @@ RSpec.configure do |config| # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework(:rspec) + with.library(:rails) + end +end