From 7542330d61e52178902f59cdd048e387b74501dd Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 14 Jan 2021 20:35:22 +0530 Subject: [PATCH] feat: Add Platform APIs (#1456) --- .rubocop.yml | 1 + .rubocop_todo.yml | 7 - app/controllers/application_controller.rb | 2 +- .../api/v1/account_users_controller.rb | 29 ++++ .../platform/api/v1/accounts_controller.rb | 32 ++++ .../platform/api/v1/users_controller.rb | 43 +++++ app/controllers/platform_controller.rb | 37 +++++ app/models/inbox.rb | 1 - app/models/platform_app.rb | 16 ++ app/models/platform_app_permissible.rb | 26 +++ config/routes.rb | 43 +++-- .../20201123195011_create_platform_apps.rb | 8 + ...101124_create_platform_app_permissibles.rb | 11 ++ db/schema.rb | 18 ++ lib/current.rb | 6 + .../api/v1/account_users_controller_spec.rb | 93 +++++++++++ .../api/v1/accounts_controller_spec.rb | 107 ++++++++++++ .../platform/api/v1/users_controller_spec.rb | 154 ++++++++++++++++++ spec/factories/platform_apps.rb | 5 + spec/factories/platform_apps_permissibles.rb | 6 + spec/models/agent_bot_spec.rb | 5 + spec/models/concerns/access_tokenable_spec.rb | 9 + spec/models/platform_app_permissible_spec.rb | 20 +++ spec/models/platform_app_spec.rb | 24 +++ spec/models/user_spec.rb | 5 + 25 files changed, 688 insertions(+), 20 deletions(-) create mode 100644 app/controllers/platform/api/v1/account_users_controller.rb create mode 100644 app/controllers/platform/api/v1/accounts_controller.rb create mode 100644 app/controllers/platform/api/v1/users_controller.rb create mode 100644 app/controllers/platform_controller.rb create mode 100644 app/models/platform_app.rb create mode 100644 app/models/platform_app_permissible.rb create mode 100644 db/migrate/20201123195011_create_platform_apps.rb create mode 100644 db/migrate/20201124101124_create_platform_app_permissibles.rb create mode 100644 spec/controllers/platform/api/v1/account_users_controller_spec.rb create mode 100644 spec/controllers/platform/api/v1/accounts_controller_spec.rb create mode 100644 spec/controllers/platform/api/v1/users_controller_spec.rb create mode 100644 spec/factories/platform_apps.rb create mode 100644 spec/factories/platform_apps_permissibles.rb create mode 100644 spec/models/concerns/access_tokenable_spec.rb create mode 100644 spec/models/platform_app_permissible_spec.rb create mode 100644 spec/models/platform_app_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 9cd50a44f..65a5a1308 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -48,6 +48,7 @@ Rails/ApplicationController: - 'app/controllers/dashboard_controller.rb' - 'app/controllers/widget_tests_controller.rb' - 'app/controllers/widgets_controller.rb' + - 'app/controllers/platform_controller.rb' Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2d3abda7f..4d66828c3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,13 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -# Configuration parameters: EnforcedStyle. -# SupportedStyles: native, lf, crlf -Layout/EndOfLine: - Exclude: - - 'deploy/after_restart.rb' - # Offense count: 1 Lint/DuplicateMethods: Exclude: diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8fbc8ff45..4fe48998d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base render_unauthorized('You are not authorized to do this action') ensure # to address the thread variable leak issues in Puma/Thin webserver - Current.user = nil + Current.reset end def set_current_user diff --git a/app/controllers/platform/api/v1/account_users_controller.rb b/app/controllers/platform/api/v1/account_users_controller.rb new file mode 100644 index 000000000..8f651cfd9 --- /dev/null +++ b/app/controllers/platform/api/v1/account_users_controller.rb @@ -0,0 +1,29 @@ +class Platform::Api::V1::AccountUsersController < PlatformController + before_action :set_resource + before_action :validate_platform_app_permissible + + def index + render json: @resource.account_users + end + + def create + @account_user = @resource.account_users.find_or_initialize_by(user_id: account_user_params[:user_id]) + @account_user.update!(account_user_params) + render json: @account_user + end + + def destroy + @resource.account_users.find_by(user_id: account_user_params[:user_id])&.destroy + head :ok + end + + private + + def set_resource + @resource = Account.find(params[:account_id]) + end + + def account_user_params + params.permit(:user_id, :role) + end +end diff --git a/app/controllers/platform/api/v1/accounts_controller.rb b/app/controllers/platform/api/v1/accounts_controller.rb new file mode 100644 index 000000000..24b39aa3e --- /dev/null +++ b/app/controllers/platform/api/v1/accounts_controller.rb @@ -0,0 +1,32 @@ +class Platform::Api::V1::AccountsController < PlatformController + def create + @resource = Account.new(account_params) + @resource.save! + @platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource) + render json: @resource + end + + def show + render json: @resource + end + + def update + @resource.update!(account_params) + render json: @resource + end + + def destroy + # TODO: obfusicate account + head :ok + end + + private + + def set_resource + @resource = Account.find(params[:id]) + end + + def account_params + params.permit(:name) + end +end diff --git a/app/controllers/platform/api/v1/users_controller.rb b/app/controllers/platform/api/v1/users_controller.rb new file mode 100644 index 000000000..81b526c8e --- /dev/null +++ b/app/controllers/platform/api/v1/users_controller.rb @@ -0,0 +1,43 @@ +class Platform::Api::V1::UsersController < PlatformController + # ref: https://stackoverflow.com/a/45190318/939299 + # set resource is called for other actions already in platform controller + # we want to add login to that chain as well + before_action(only: [:login]) { set_resource } + before_action(only: [:login]) { validate_platform_app_permissible } + + def create + @resource = (User.find_by(email: user_params[:email]) || User.new(user_params)) + @resource.confirm + @resource.save! + @platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource) + render json: @resource + end + + def login + render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{@resource.email}&sso_auth_token=#{@resource.generate_sso_auth_token}" } + end + + def show + render json: @resource + end + + def update + @resource.update!(user_params) + render json: @resource + end + + def destroy + # TODO: obfusicate user + head :ok + end + + private + + def set_resource + @resource = User.find(params[:id]) + end + + def user_params + params.permit(:name, :email, :password) + end +end diff --git a/app/controllers/platform_controller.rb b/app/controllers/platform_controller.rb new file mode 100644 index 000000000..612f86d27 --- /dev/null +++ b/app/controllers/platform_controller.rb @@ -0,0 +1,37 @@ +class PlatformController < ActionController::Base + protect_from_forgery with: :null_session + + before_action :ensure_access_token + before_action :set_platform_app + before_action :set_resource, only: [:update, :show, :destroy] + before_action :validate_platform_app_permissible, only: [:update, :show, :destroy] + + def show; end + + def update; end + + def destroy; end + + private + + def ensure_access_token + token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN] + @access_token = AccessToken.find_by(token: token) if token.present? + end + + def set_platform_app + @platform_app = @access_token.owner if @access_token && @access_token.owner.is_a?(PlatformApp) + render json: { error: 'Invalid access_token' }, status: :unauthorized if @platform_app.blank? + end + + def set_resource + # set @resource in your controller + raise 'Overwrite this method your controller' + end + + def validate_platform_app_permissible + return if @platform_app.platform_app_permissibles.find_by(permissible: @resource) + + render json: { error: 'Non permissible resource' }, status: :unauthorized + end +end diff --git a/app/models/inbox.rb b/app/models/inbox.rb index 663078e70..0e5fd490f 100644 --- a/app/models/inbox.rb +++ b/app/models/inbox.rb @@ -32,7 +32,6 @@ class Inbox < ApplicationRecord belongs_to :account - # TODO: should add associations for the channel types belongs_to :channel, polymorphic: true, dependent: :destroy has_many :contact_inboxes, dependent: :destroy diff --git a/app/models/platform_app.rb b/app/models/platform_app.rb new file mode 100644 index 000000000..2f7ec8e42 --- /dev/null +++ b/app/models/platform_app.rb @@ -0,0 +1,16 @@ +# == Schema Information +# +# Table name: platform_apps +# +# id :bigint not null, primary key +# name :string not null +# created_at :datetime not null +# updated_at :datetime not null +# +class PlatformApp < ApplicationRecord + include AccessTokenable + + validates :name, presence: true + + has_many :platform_app_permissibles, dependent: :destroy +end diff --git a/app/models/platform_app_permissible.rb b/app/models/platform_app_permissible.rb new file mode 100644 index 000000000..c8efc59b4 --- /dev/null +++ b/app/models/platform_app_permissible.rb @@ -0,0 +1,26 @@ +# == Schema Information +# +# Table name: platform_app_permissibles +# +# id :bigint not null, primary key +# permissible_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# permissible_id :bigint not null +# platform_app_id :bigint not null +# +# Indexes +# +# index_platform_app_permissibles_on_permissibles (permissible_type,permissible_id) +# index_platform_app_permissibles_on_platform_app_id (platform_app_id) +# unique_permissibles_index (platform_app_id,permissible_id,permissible_type) UNIQUE +# +class PlatformAppPermissible < ApplicationRecord + include AccessTokenable + + validates :platform_app, presence: true + validates :platform_app_id, uniqueness: { scope: [:permissible_id, :permissible_type] } + + belongs_to :platform_app + belongs_to :permissible, polymorphic: true, dependent: :destroy +end diff --git a/config/routes.rb b/config/routes.rb index 3d74fe877..aa888627a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -155,13 +155,25 @@ Rails.application.routes.draw do end end - namespace :twitter do - resource :authorization, only: [:create] - resource :callback, only: [:show] - end - - namespace :twilio do - resources :callback, only: [:create] + # ---------------------------------------------------------------------- + # Routes for platform APIs + namespace :platform, defaults: { format: 'json' } do + namespace :api do + namespace :v1 do + resources :users, only: [:create, :show, :update, :destroy] do + member do + get :login + end + end + resources :accounts, only: [:create, :show, :update, :destroy] do + resources :account_users, only: [:index, :create] do + collection do + delete :destroy + end + end + end + end + end end # ---------------------------------------------------------------------- @@ -173,14 +185,19 @@ Rails.application.routes.draw do end # ---------------------------------------------------------------------- - # Routes for social integrations + # Routes for channel integrations mount Facebook::Messenger::Server, at: 'bot' get 'webhooks/twitter', to: 'api/v1/webhooks#twitter_crc' post 'webhooks/twitter', to: 'api/v1/webhooks#twitter_events' - # ---------------------------------------------------------------------- - # Routes for testing - resources :widget_tests, only: [:index] unless Rails.env.production? + namespace :twitter do + resource :authorization, only: [:create] + resource :callback, only: [:show] + end + + namespace :twilio do + resources :callback, only: [:create] + end # ---------------------------------------------------------------------- # Routes for external service verifications @@ -216,4 +233,8 @@ Rails.application.routes.draw do # Routes for swagger docs get '/swagger/*path', to: 'swagger#respond' get '/swagger', to: 'swagger#respond' + + # ---------------------------------------------------------------------- + # Routes for testing + resources :widget_tests, only: [:index] unless Rails.env.production? end diff --git a/db/migrate/20201123195011_create_platform_apps.rb b/db/migrate/20201123195011_create_platform_apps.rb new file mode 100644 index 000000000..bfa36fc89 --- /dev/null +++ b/db/migrate/20201123195011_create_platform_apps.rb @@ -0,0 +1,8 @@ +class CreatePlatformApps < ActiveRecord::Migration[6.0] + def change + create_table :platform_apps do |t| + t.string :name, null: false + t.timestamps + end + end +end diff --git a/db/migrate/20201124101124_create_platform_app_permissibles.rb b/db/migrate/20201124101124_create_platform_app_permissibles.rb new file mode 100644 index 000000000..309d4f36a --- /dev/null +++ b/db/migrate/20201124101124_create_platform_app_permissibles.rb @@ -0,0 +1,11 @@ +class CreatePlatformAppPermissibles < ActiveRecord::Migration[6.0] + def change + create_table :platform_app_permissibles do |t| + t.references :platform_app, index: true, null: false + t.references :permissible, null: false, polymorphic: true, index: { name: :index_platform_app_permissibles_on_permissibles } + t.timestamps + end + + add_index :platform_app_permissibles, [:platform_app_id, :permissible_id, :permissible_type], unique: true, name: 'unique_permissibles_index' + end +end diff --git a/db/schema.rb b/db/schema.rb index 8c92821dc..eafa19f6e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,6 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. + ActiveRecord::Schema.define(version: 2021_01_09_211805) do # These are extensions that must be enabled in order to support this database @@ -428,6 +429,23 @@ ActiveRecord::Schema.define(version: 2021_01_09_211805) do t.index ["user_id"], name: "index_notifications_on_user_id" end + create_table "platform_app_permissibles", force: :cascade do |t| + t.bigint "platform_app_id", null: false + t.string "permissible_type", null: false + t.bigint "permissible_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["permissible_type", "permissible_id"], name: "index_platform_app_permissibles_on_permissibles" + t.index ["platform_app_id", "permissible_id", "permissible_type"], name: "unique_permissibles_index", unique: true + t.index ["platform_app_id"], name: "index_platform_app_permissibles_on_platform_app_id" + end + + create_table "platform_apps", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + create_table "super_admins", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false diff --git a/lib/current.rb b/lib/current.rb index cbc07cbed..4490932f0 100644 --- a/lib/current.rb +++ b/lib/current.rb @@ -7,4 +7,10 @@ module Current super Time.zone = account.timezone end + + def self.reset + Current.user = nil + Current.account = nil + Current.account_user = nil + end end diff --git a/spec/controllers/platform/api/v1/account_users_controller_spec.rb b/spec/controllers/platform/api/v1/account_users_controller_spec.rb new file mode 100644 index 000000000..9ab36b5b2 --- /dev/null +++ b/spec/controllers/platform/api/v1/account_users_controller_spec.rb @@ -0,0 +1,93 @@ +require 'rails_helper' + +RSpec.describe 'Platform Account Users API', type: :request do + let!(:account) { create(:account) } + + describe 'GET /platform/api/v1/accounts/{account_id}/account_users' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + get "/platform/api/v1/accounts/#{account.id}/account_users" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + let!(:account_user) { create(:account_user, account: account) } + + it 'returns all the account users for the account' do + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + + get "/platform/api/v1/accounts/#{account.id}/account_users", + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(account_user.id.to_s) + end + end + end + + describe 'POST /platform/api/v1/accounts/{account_id}/account_users' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + post "/platform/api/v1/accounts/#{account.id}/account_users" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'creates a new account user for the account' do + user = create(:user) + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + + post "/platform/api/v1/accounts/#{account.id}/account_users", + params: { user_id: user.id, role: 'administrator' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['user_id']).to eq(user.id) + end + + it 'updates the new account user for the account' do + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + account_user = create(:account_user, account: account, role: 'agent') + + post "/platform/api/v1/accounts/#{account.id}/account_users", + params: { user_id: account_user.user_id, role: 'administrator' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['role']).to eq('administrator') + end + end + end + + describe 'DELETE /platform/api/v1/accounts/{account_id}/account_users' do + let(:account_user) { create(:account_user, account: account, role: 'agent') } + + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + delete "/platform/api/v1/accounts/#{account.id}/account_users", params: { user_id: account_user.user_id } + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns deletes the account user' do + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + + delete "/platform/api/v1/accounts/#{account.id}/account_users", params: { user_id: account_user.user_id }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + expect(account.account_users.count).to eq 0 + end + end + end +end diff --git a/spec/controllers/platform/api/v1/accounts_controller_spec.rb b/spec/controllers/platform/api/v1/accounts_controller_spec.rb new file mode 100644 index 000000000..0b1daf622 --- /dev/null +++ b/spec/controllers/platform/api/v1/accounts_controller_spec.rb @@ -0,0 +1,107 @@ +require 'rails_helper' + +RSpec.describe 'Platform Accounts API', type: :request do + let!(:account) { create(:account) } + + describe 'POST /platform/api/v1/accounts' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + post '/platform/api/v1/accounts' + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + post '/platform/api/v1/accounts', params: { name: 'Test Account' }, + headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'creates an account when and its permissible relationship' do + post '/platform/api/v1/accounts', params: { name: 'Test Account' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include('Test Account') + expect(platform_app.platform_app_permissibles.first.permissible.name).to eq('Test Account') + end + end + end + + describe 'GET /platform/api/v1/accounts/{account_id}' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + get "/platform/api/v1/accounts/#{account.id}" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + get "/platform/api/v1/accounts/#{account.id}", headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + get "/platform/api/v1/accounts/#{account.id}", headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'shows an account when its permissible object' do + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + + get "/platform/api/v1/accounts/#{account.id}", + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(account.name) + end + end + end + + describe 'PATCH /platform/api/v1/accounts/{account_id}' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + patch "/platform/api/v1/accounts/#{account.id}" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + patch "/platform/api/v1/accounts/#{account.id}", params: { name: 'Test Account' }, + headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + patch "/platform/api/v1/accounts/#{account.id}", params: { name: 'Test Account' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'updates an account when its permissible object' do + create(:platform_app_permissible, platform_app: platform_app, permissible: account) + + patch "/platform/api/v1/accounts/#{account.id}", params: { name: 'Test Account' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + expect(account.reload.name).to eq('Test Account') + end + end + end +end diff --git a/spec/controllers/platform/api/v1/users_controller_spec.rb b/spec/controllers/platform/api/v1/users_controller_spec.rb new file mode 100644 index 000000000..7f9167b14 --- /dev/null +++ b/spec/controllers/platform/api/v1/users_controller_spec.rb @@ -0,0 +1,154 @@ +require 'rails_helper' + +RSpec.describe 'Platform Users API', type: :request do + let!(:user) { create(:user) } + + describe 'GET /platform/api/v1/users/{user_id}' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + get "/platform/api/v1/users/#{user.id}" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + get "/platform/api/v1/users/#{user.id}", headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + get "/platform/api/v1/users/#{user.id}", headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'shows a user when its permissible object' do + create(:platform_app_permissible, platform_app: platform_app, permissible: user) + + get "/platform/api/v1/users/#{user.id}", + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['email']).to eq(user.email) + end + end + end + + describe 'GET /platform/api/v1/users/{user_id}/login' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + get "/platform/api/v1/users/#{user.id}/login" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + get "/platform/api/v1/users/#{user.id}/login", headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + get "/platform/api/v1/users/#{user.id}/login", headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'return login link for user' do + create(:platform_app_permissible, platform_app: platform_app, permissible: user) + + get "/platform/api/v1/users/#{user.id}/login", + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['url']).to include('sso_auth_token') + end + end + end + + describe 'POST /platform/api/v1/users/' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + post '/platform/api/v1/users' + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + post '/platform/api/v1/users/', headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'creates a new user and permissible for the user' do + post '/platform/api/v1/users/', params: { name: 'test', email: 'test@test.com', password: 'password123' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['email']).to eq('test@test.com') + expect(platform_app.platform_app_permissibles.first.permissible_id).to eq data['id'] + end + + it 'fetch existing user and creates permissible for the user' do + create(:user, name: 'old test', email: 'test@test.com') + post '/platform/api/v1/users/', params: { name: 'test', email: 'test@test.com', password: 'password123' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['name']).to eq('old test') + expect(platform_app.platform_app_permissibles.first.permissible_id).to eq data['id'] + end + end + end + + describe 'PATCH /platform/api/v1/users/{user_id}' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + patch "/platform/api/v1/users/#{user.id}" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + patch "/platform/api/v1/users/#{user.id}", headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + patch "/platform/api/v1/users/#{user.id}", params: { name: 'test' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'updates the user' do + create(:platform_app_permissible, platform_app: platform_app, permissible: user) + patch "/platform/api/v1/users/#{user.id}", params: { name: 'test123' }, + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = JSON.parse(response.body) + expect(data['name']).to eq('test123') + end + end + end +end diff --git a/spec/factories/platform_apps.rb b/spec/factories/platform_apps.rb new file mode 100644 index 000000000..3c0fb1990 --- /dev/null +++ b/spec/factories/platform_apps.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :platform_app do + name { Faker::Book.name } + end +end diff --git a/spec/factories/platform_apps_permissibles.rb b/spec/factories/platform_apps_permissibles.rb new file mode 100644 index 000000000..b85245d29 --- /dev/null +++ b/spec/factories/platform_apps_permissibles.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :platform_app_permissible do + platform_app + permissible { create(:user) } + end +end diff --git a/spec/models/agent_bot_spec.rb b/spec/models/agent_bot_spec.rb index d6f1500c4..be71a36c9 100644 --- a/spec/models/agent_bot_spec.rb +++ b/spec/models/agent_bot_spec.rb @@ -1,8 +1,13 @@ require 'rails_helper' +require Rails.root.join 'spec/models/concerns/access_tokenable_spec.rb' RSpec.describe AgentBot, type: :model do describe 'associations' do it { is_expected.to have_many(:agent_bot_inboxes) } it { is_expected.to have_many(:inboxes) } end + + describe 'concerns' do + it_behaves_like 'access_tokenable' + end end diff --git a/spec/models/concerns/access_tokenable_spec.rb b/spec/models/concerns/access_tokenable_spec.rb new file mode 100644 index 000000000..c2b504dbf --- /dev/null +++ b/spec/models/concerns/access_tokenable_spec.rb @@ -0,0 +1,9 @@ +require 'rails_helper' + +shared_examples_for 'access_tokenable' do + let(:obj) { create(described_class.to_s.underscore) } + + it 'creates access token on create' do + expect(obj.access_token).not_to eq(nil) + end +end diff --git a/spec/models/platform_app_permissible_spec.rb b/spec/models/platform_app_permissible_spec.rb new file mode 100644 index 000000000..a6c230d3c --- /dev/null +++ b/spec/models/platform_app_permissible_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PlatformAppPermissible do + let!(:platform_app_permissible) { create(:platform_app_permissible) } + + context 'with validations' do + it { is_expected.to validate_presence_of(:platform_app) } + end + + context 'with associations' do + it { is_expected.to belong_to(:platform_app) } + it { is_expected.to belong_to(:permissible) } + end + + describe 'with factories' do + it { expect(platform_app_permissible).present? } + end +end diff --git a/spec/models/platform_app_spec.rb b/spec/models/platform_app_spec.rb new file mode 100644 index 000000000..1841929bb --- /dev/null +++ b/spec/models/platform_app_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require Rails.root.join 'spec/models/concerns/access_tokenable_spec.rb' + +RSpec.describe PlatformApp do + let(:platform_app) { create(:platform_app) } + + context 'with validations' do + it { is_expected.to validate_presence_of(:name) } + end + + context 'with associations' do + it { is_expected.to have_many(:platform_app_permissibles) } + end + + describe 'with concerns' do + it_behaves_like 'access_tokenable' + end + + describe 'with factories' do + it { expect(platform_app).present? } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 225eedeb7..2d23e0f8d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rails_helper' +require Rails.root.join 'spec/models/concerns/access_tokenable_spec.rb' RSpec.describe User do let!(:user) { create(:user) } @@ -20,6 +21,10 @@ RSpec.describe User do it { is_expected.to have_many(:events) } end + describe 'concerns' do + it_behaves_like 'access_tokenable' + end + describe 'pubsub_token' do before { user.update(name: Faker::Name.name) }