diff --git a/Gemfile b/Gemfile
index 5be6fca..d21c87c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -14,14 +14,16 @@ gem "sprockets-rails"
# Start debugger with binding.b [https://github.com/ruby/debug]
# gem "debug", ">= 1.0.0"
-# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
-gem "rubocop-rails-omakase", require: false
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
# gem "brakeman", "~> 6.1"
# gem "debug", platforms: %i[mri windows]
+ # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
+ gem "rubocop-rails-omakase", require: false
+ gem 'rails-controller-testing'
+
# gem "standard", require: false
# gem "erb_lint", require: false
# gem "letter_opener_web", "~> 2.0"
diff --git a/app/views/kiqr/sessions/new.html.erb b/app/views/kiqr/sessions/new.html.erb
new file mode 100644
index 0000000..e31cc21
--- /dev/null
+++ b/app/views/kiqr/sessions/new.html.erb
@@ -0,0 +1,36 @@
+<% title "Sign in to your account" %>
+
+
<%= t(".title", app_name: Kiqr.config.app_name) %>
+<%= t(".description") %>
+
+
+<%#= render "partials/social_accounts_login" %>
+
+<%= form_with model: resource, url: session_path(resource_name), local: true do |f| %>
+
+ <%= f.label :email, t(".email.placeholder") %>
+ <%= f.email_field :email, placeholder: t(".email.placeholder"), required: true, autofocus: true, autocomplete: "email" %>
+
+
+
+ <%= f.label :password, t(".password.placeholder") %>
+ <%= f.password_field :password, placeholder: t(".password.placeholder"), required: true, autocomplete: "current-password" %>
+
+
+ <% if devise_mapping.rememberable? %>
+
+ <%= f.check_box :remember_me %>
+ <%= f.label :remember_me, t(".remember_me.label") %>
+
+ <% end %>
+
+
+ <%= f.submit t(".submit_button") %>
+
+
+
+
<%= t(".no_account") %>
+ <%= link_to t(".sign_up"), new_user_registration_path %>
+
+
+<% end %>
diff --git a/bin/rubocop b/bin/rubocop
deleted file mode 100755
index 40330c0..0000000
--- a/bin/rubocop
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env ruby
-require "rubygems"
-require "bundler/setup"
-
-# explicit rubocop config increases performance slightly while avoiding config confusion.
-ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
-
-load Gem.bin_path("rubocop", "rubocop")
diff --git a/config/locales/kiqr/en.yml b/config/locales/kiqr/en.yml
index 2a487e7..37f4eac 100644
--- a/config/locales/kiqr/en.yml
+++ b/config/locales/kiqr/en.yml
@@ -157,17 +157,15 @@ en:
confirmation_message: "Are you sure? This will DELETE all of your data."
sessions:
new:
- heading:
- title: "Sign in to your %{app_name} account"
- description: "Enter with your email address and password or select any of the other available options below."
- form:
- email:
- placeholder: "Enter your email"
- password:
- placeholder: "Enter your password"
- remember_me:
- label: "Remember me"
- submit_button: "Sign in"
+ title: "Sign in to your %{app_name} account"
+ description: "Enter with your email address and password or select any of the other available options below."
+ email:
+ placeholder: "Enter your email"
+ password:
+ placeholder: "Enter your password"
+ remember_me:
+ label: "Remember me"
+ submit_button: "Sign in"
no_account: "Don't have an account?"
sign_up: "Sign up!"
otp:
diff --git a/test/controllers/kiqr/account_users_controller_test.rb b/test/controllers/kiqr/account_users_controller_test.rb
new file mode 100644
index 0000000..abc8e51
--- /dev/null
+++ b/test/controllers/kiqr/account_users_controller_test.rb
@@ -0,0 +1,57 @@
+require "test_helper"
+
+class Kiqr::AccountUsersControllerTest < ActionDispatch::IntegrationTest
+ test "can't show members as personal account" do
+ user = create(:user)
+ sign_in user
+ get account_users_path
+
+ assert_redirected_to edit_account_path(account_id: nil)
+ end
+
+ test "can show edit member page" do
+ user = create(:user)
+ some_user = create(:user)
+
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user: user, owner: true)
+ account.account_users << AccountUser.create(user: some_user)
+
+ sign_in user
+ get edit_account_user_path(account_id: account, id: some_user.account_users.first)
+
+ assert_response :success
+ end
+
+ test "can show members as team account" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+ get account_users_path(account_id: account)
+
+ assert_response :success
+ end
+
+ test "can remove a member from team" do
+ user = create(:user)
+ some_user = create(:user)
+
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user: user, owner: true)
+ account.account_users << AccountUser.create(user: some_user)
+
+ assert_includes account.reload.users, some_user
+
+ sign_in user
+ delete account_user_path(account_id: account, id: some_user.account_users.first)
+
+ assert_redirected_to account_users_path(account_id: account)
+ assert_not_includes account.reload.users, some_user
+ end
+
+ test "can not remove the team owner" do
+ skip "This test is not implemented yet"
+ end
+end
diff --git a/test/controllers/kiqr/accounts/invitations_controller_test.rb b/test/controllers/kiqr/accounts/invitations_controller_test.rb
new file mode 100644
index 0000000..dd794a3
--- /dev/null
+++ b/test/controllers/kiqr/accounts/invitations_controller_test.rb
@@ -0,0 +1,60 @@
+require "test_helper"
+
+class InvitationsControllerTest < ActionDispatch::IntegrationTest
+ test "can invite a user" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+
+ assert_difference -> { account.account_invitations.count } do
+ post account_invitations_path(account_id: account), params: { account_invitation: { email: "foobar@agag.com" } }
+ end
+
+ assert_redirected_to account_invitations_path(account_id: account)
+ end
+
+ test "can't invite a user to someone else team" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ foreign_account = create(:account, name: "Foreign account")
+
+ sign_in user
+
+ assert_raises(PublicUid::RecordNotFound) do
+ post account_invitations_path(account_id: foreign_account), params: { account_invitation: { email: "foobar@agag.com" } }
+ end
+ end
+
+ test "can only invite the same user once" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+
+ post account_invitations_path(account_id: account), params: { account_invitation: { email: "foobar@foobar.com" } }
+ assert_redirected_to account_invitations_path(account_id: account)
+
+ assert_no_difference -> { account.account_invitations.count } do
+ post account_invitations_path(account_id: account), params: { account_invitation: { email: "foobar@foobar.com" } }
+ end
+ end
+
+ test "invitations validates email" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+
+ assert_no_difference -> { account.account_invitations.count } do
+ post account_invitations_path(account_id: account), params: { account_invitation: { email: "foo" } }
+ end
+
+ assert_response :unprocessable_entity
+ end
+end
diff --git a/test/controllers/kiqr/accounts_controller_test.rb b/test/controllers/kiqr/accounts_controller_test.rb
new file mode 100644
index 0000000..c071e91
--- /dev/null
+++ b/test/controllers/kiqr/accounts_controller_test.rb
@@ -0,0 +1,59 @@
+require "test_helper"
+
+class Kiqr::AccountsControllerTest < ActionDispatch::IntegrationTest
+ test "can render new account page" do
+ sign_in create(:user)
+ get new_account_path
+ assert_response :success
+ end
+
+ test "redirect to profile edit if personal account" do
+ sign_in create(:user)
+ get edit_account_path
+ assert_redirected_to edit_settings_path
+ end
+
+ test "can create new team account" do
+ user = create(:user)
+ sign_in user
+
+ post accounts_path, params: { account: { name: "Foobar team" } }
+ new_team = Account.find_by(name: "Foobar team")
+
+ assert_redirected_to dashboard_path(account_id: Account.last)
+ assert user.reload.accounts.include?(new_team)
+ end
+
+ test "shows error on invalid team account creation" do
+ user = create(:user)
+ sign_in user
+
+ post accounts_path, params: { account: { name: "no" } }
+ assert_response :unprocessable_entity
+ # assert_template :new
+ end
+
+ test "can update team accounts" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+ patch account_path(account_id: account), params: { account: { name: "New company name" } }
+ account.reload
+
+ assert_redirected_to edit_account_path
+ assert_equal "New company name", account.name
+ end
+
+ test "re-renders form if form data is invalid" do
+ user = create(:user)
+ account = create(:account, name: "Team account")
+ account.account_users << AccountUser.create(user:, owner: true)
+
+ sign_in user
+
+ patch account_path(account_id: account), params: { account: { name: "no" } }
+ assert_response :unprocessable_entity
+ end
+end
diff --git a/test/controllers/kiqr/dashboard_controller_test.rb b/test/controllers/kiqr/dashboard_controller_test.rb
new file mode 100644
index 0000000..d14faa1
--- /dev/null
+++ b/test/controllers/kiqr/dashboard_controller_test.rb
@@ -0,0 +1,14 @@
+require "test_helper"
+
+class Kiqr::DashboardControllerTest < ActionDispatch::IntegrationTest
+ test "should redirect unauthenticated users to sign in form" do
+ get dashboard_path
+ assert_redirected_to new_user_session_path
+ end
+
+ test "should allow authenticated users to visit" do
+ sign_in create(:user)
+ get dashboard_path
+ assert_response :success
+ end
+end
diff --git a/test/controllers/kiqr/invitations_controller_test.rb b/test/controllers/kiqr/invitations_controller_test.rb
new file mode 100644
index 0000000..a3b3e01
--- /dev/null
+++ b/test/controllers/kiqr/invitations_controller_test.rb
@@ -0,0 +1,46 @@
+require "test_helper"
+
+class Kiqr::InvitationsControllerTest < ActionDispatch::IntegrationTest
+ test "can view page without beeing signed in" do
+ invitation = create(:account_invitation)
+ get invitation_path(invitation)
+ assert_response :success
+ end
+
+ test "can accept invitation" do
+ user = create(:user)
+ invitation = create(:account_invitation)
+ sign_in user
+
+ assert_difference -> { user.accounts.count } do
+ post accept_invitation_path(invitation)
+ end
+
+ assert_redirected_to dashboard_path(account_id: invitation.account)
+ assert_not_nil invitation.reload.accepted_at
+ end
+
+ test "cant be accepted twice" do
+ user = create(:user)
+ invitation = create(:account_invitation, accepted_at: Time.now)
+ sign_in user
+
+ get invitation_path(invitation)
+ assert_redirected_to dashboard_path
+
+ assert_no_difference -> { user.accounts.count } do
+ post accept_invitation_path(invitation)
+ end
+ end
+
+ test "can reject invitation" do
+ user = create(:user)
+ invitation = create(:account_invitation)
+ sign_in user
+
+ post reject_invitation_path(invitation)
+
+ assert_redirected_to dashboard_path
+ assert_nil AccountInvitation.find_by(id: invitation.id)
+ end
+end
diff --git a/test/controllers/kiqr/onboarding_controller_test.rb b/test/controllers/kiqr/onboarding_controller_test.rb
new file mode 100644
index 0000000..7fa578f
--- /dev/null
+++ b/test/controllers/kiqr/onboarding_controller_test.rb
@@ -0,0 +1,30 @@
+require "test_helper"
+
+class Kiqr::OnboardingControllerTest < ActionDispatch::IntegrationTest
+ test "redirects to dashboard_path if user is already onboarded" do
+ sign_in create(:user)
+ get onboarding_path
+ assert_redirected_to dashboard_path
+ end
+
+ test "renders the onboarding form if user is not onboarded" do
+ sign_in create(:user, personal_account: nil)
+ get onboarding_path
+ assert_response :success
+ end
+
+ test "can onboard user" do
+ user = create(:user, personal_account: nil)
+ sign_in user
+ post onboarding_path, params: { user: { personal_account_attributes: { name: "Foobar zoo" } } }
+ assert_redirected_to dashboard_path
+ assert user.reload.personal_account.personal?
+ end
+
+ test "validates user onboarding" do
+ sign_in create(:user, personal_account: nil)
+ post onboarding_path, params: { user: { personal_account_attributes: { name: "no" } } }
+ assert_response :unprocessable_entity
+ assert_template :new
+ end
+end
diff --git a/test/controllers/kiqr/registrations_controller_test.rb b/test/controllers/kiqr/registrations_controller_test.rb
new file mode 100644
index 0000000..3e9c50d
--- /dev/null
+++ b/test/controllers/kiqr/registrations_controller_test.rb
@@ -0,0 +1,30 @@
+require "test_helper"
+
+class Kiqr::RegistrationControllerTest < ActionDispatch::IntegrationTest
+ test "can view cancellation page" do
+ sign_in create(:user)
+ get cancel_user_registration_path
+ assert_response :success
+ end
+
+ test "can delete a user without teams" do
+ user = create(:user)
+ sign_in user
+ delete user_registration_path
+
+ assert_redirected_to root_path
+ assert_nil User.find_by(id: user.id)
+ end
+
+ test "can't delete a user with owned teams" do
+ user = create(:user)
+ team_account = create(:account, name: "Team account")
+ team_account.account_users << AccountUser.create(user: user, owner: true)
+
+ sign_in user
+ delete user_registration_path
+
+ assert_redirected_to cancel_user_registration_path
+ assert User.find_by(id: user.id)
+ end
+end
diff --git a/test/controllers/kiqr/sessions_controller_test.rb b/test/controllers/kiqr/sessions_controller_test.rb
new file mode 100644
index 0000000..75f587a
--- /dev/null
+++ b/test/controllers/kiqr/sessions_controller_test.rb
@@ -0,0 +1,41 @@
+require "test_helper"
+
+class Kiqr::SessionsControllerTest < ActionDispatch::IntegrationTest
+ test "can sign in if two factor is disabled" do
+ user = create(:user)
+ post user_session_path, params: { user: { email: user.email, password: user.password } }
+ assert_response :redirect
+ assert_redirected_to dashboard_path
+ end
+
+ test "can sign in with otp if two factor is enabled" do
+ user = create(:user, :otp_enabled)
+ post user_session_path, params: { user: { email: user.email, password: user.password } }
+ assert_response :unprocessable_entity
+ assert_template "kiqr/sessions/otp"
+
+ post user_session_path, params: { user: { otp_attempt: user.current_otp } }
+ assert_redirected_to dashboard_path
+ end
+
+ test "can't sign in with invalid otp if two factor is enabled" do
+ user = create(:user, :otp_enabled)
+ post user_session_path, params: { user: { email: user.email, password: user.password } }
+ post user_session_path, params: { user: { otp_attempt: "123456" } }
+ assert_response :unprocessable_entity
+ assert_template "kiqr/sessions/otp"
+ end
+
+ test "renders form again if invalid email" do
+ post user_session_path, params: { user: { email: "unknown.email", password: "randompassword" } }
+ assert_response :unprocessable_entity
+ assert_template "kiqr/sessions/new"
+ end
+
+ test "renders form again if invalid password" do
+ user = create(:user)
+ post user_session_path, params: { user: { email: user.email, password: "randompassword" } }
+ assert_response :unprocessable_entity
+ assert_template "kiqr/sessions/new"
+ end
+end
diff --git a/test/controllers/kiqr/settings_controller_test.rb b/test/controllers/kiqr/settings_controller_test.rb
new file mode 100644
index 0000000..0ccfee7
--- /dev/null
+++ b/test/controllers/kiqr/settings_controller_test.rb
@@ -0,0 +1,31 @@
+require "test_helper"
+
+class Kiqr::PreferencesControllerTest < ActionDispatch::IntegrationTest
+ test "should get edit page" do
+ user = create(:user)
+ sign_in(user)
+ get edit_settings_path
+ assert_response :success
+ end
+
+ test "can update user fields" do
+ user = create(:user, time_zone: "UTC", locale: "en")
+ sign_in(user)
+
+ patch settings_path, params: { user: { time_zone: "Stockholm", locale: "sv" } }
+ assert_redirected_to edit_settings_path
+ assert_equal "Stockholm", user.reload.time_zone
+ assert_equal "sv", user.reload.locale
+ end
+
+ test "can update personal account fields" do
+ user = create(:user)
+ sign_in user
+
+ patch settings_path, params: { user: { personal_account_attributes: { name: "Personal account name" } } }
+ user.reload
+
+ assert_redirected_to edit_settings_path
+ assert_equal "Personal account name", user.personal_account.name
+ end
+end
diff --git a/test/controllers/kiqr/two_factor_controller_test.rb b/test/controllers/kiqr/two_factor_controller_test.rb
new file mode 100644
index 0000000..81845e8
--- /dev/null
+++ b/test/controllers/kiqr/two_factor_controller_test.rb
@@ -0,0 +1,65 @@
+require "test_helper"
+
+class Kiqr::TwoFactorControllerTest < ActionDispatch::IntegrationTest
+ test "should not be able to setup 2fa if already enabled" do
+ sign_in create(:user, :otp_enabled)
+ get setup_two_factor_path
+ assert_redirected_to edit_two_factor_path
+ end
+
+ test "secret code is refreshed on new setup" do
+ user = create(:user)
+ sign_in user
+ get new_two_factor_path
+
+ user.reload
+ assert user.otp_secret
+
+ get new_two_factor_path
+ assert_not_equal user.otp_secret, user.reload.otp_secret
+ end
+
+ test "can view setup page" do
+ sign_in create(:user)
+ get setup_two_factor_path
+ assert_response :success
+ end
+
+ test "redirects to show page if two factor is alredy disabled" do
+ user = create(:user)
+ sign_in user
+ get disable_two_factor_path
+ assert_redirected_to edit_two_factor_path
+
+ delete destroy_two_factor_path
+ assert_redirected_to edit_two_factor_path
+ end
+
+ test "does not activate 2fa with invalid verification code" do
+ user = create(:user, otp_secret: User.generate_otp_secret)
+ sign_in user
+ post verify_two_factor_path, params: { user: { otp_attempt: "123456" } }
+ assert_response :unprocessable_entity
+ end
+
+ test "activates 2fa with valid verification code" do
+ user = create(:user, otp_secret: User.generate_otp_secret)
+ sign_in user
+ post verify_two_factor_path, params: { user: { otp_attempt: user.current_otp } }
+ assert_redirected_to edit_two_factor_path
+ end
+
+ test "requires valid otp code to disable two factor authentication" do
+ user = create(:user, :otp_enabled)
+ sign_in user
+
+ delete destroy_two_factor_path, params: { user: { otp_attempt: "123456" } }
+ assert_response :unprocessable_entity
+ assert_template "two_factor/disable"
+ assert user.reload.otp_required_for_login?
+
+ delete destroy_two_factor_path, params: { user: { otp_attempt: user.current_otp } }
+ assert_redirected_to edit_two_factor_path
+ assert_not user.reload.otp_required_for_login?
+ end
+end
diff --git a/test/factories/user.rb b/test/factories/user.rb
index 883a1f3..bcb03ff 100644
--- a/test/factories/user.rb
+++ b/test/factories/user.rb
@@ -16,13 +16,14 @@
user.account_users.create(account: evaluator.with_account, owner: true)
end
end
- # trait :unconfirmed do
- # confirmed_at { nil }
- # end
- # trait :otp_enabled do
- # otp_required_for_login { true }
- # otp_secret { User.generate_otp_secret }
- # end
+ trait :unconfirmed do
+ confirmed_at { nil }
+ end
+
+ trait :otp_enabled do
+ otp_required_for_login { true }
+ otp_secret { User.generate_otp_secret }
+ end
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 9391124..fce43de 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -18,7 +18,7 @@
module ActiveSupport
class TestCase
- # include Devise::Test::IntegrationHelpers
+ include Devise::Test::IntegrationHelpers
include FactoryBot::Syntax::Methods
# Run tests in parallel with specified workers