Skip to content
Snippets Groups Projects
Commit 684db560 authored by shyngys's avatar shyngys Committed by kerdo
Browse files

implemented logout functionality with BDD/TDD

parent 358ef169
No related branches found
No related tags found
1 merge request!7Resolve "FR-03: User Logout"
......@@ -9,6 +9,10 @@ defmodule WhiteBreadConfig do
context: UserLoginContext,
feature_paths: ["features/user_login.feature"]
suite name: "User Logout Features",
context: UserLogoutContext,
feature_paths: ["features/user_logout.feature"]
suite name: "Password Change Features",
context: PasswordChangeContext,
feature_paths: ["features/password_change.feature"]
......
defmodule UserLogoutContext do
use WhiteBread.Context
use Hound.Helpers
alias PropTrackr.Accounts
alias PropTrackr.Repo
alias PropTrackr.Accounts.User
scenario_starting_state fn _state ->
Ecto.Adapters.SQL.Sandbox.checkout(PropTrackr.Repo)
Ecto.Adapters.SQL.Sandbox.mode(PropTrackr.Repo, {:shared, self()})
Hound.start_session()
%{}
end
scenario_finalize fn _status, _state ->
Ecto.Adapters.SQL.Sandbox.checkin(PropTrackr.Repo)
Hound.end_session()
end
given_ ~r/^there exists following accounts$/, fn state, %{table_data: table} ->
table
|> Enum.map(fn user_details -> User.changeset(%User{}, user_details) end)
|> Enum.each(fn changeset -> Repo.insert!(changeset) end)
existing_user = List.first(table)
{
:ok,
state
|> Map.put(:email, existing_user[:email])
|> Map.put(:password, existing_user[:password])
}
end
given_ ~r/^I am logged in$/, fn state ->
navigate_to("/login")
fill_field({:id, "email"}, state[:email])
fill_field({:id, "password"}, state[:password])
click({:id, "login_button"})
assert visible_in_page? ~r"Successfully logged in!"
assert current_path() == "/"
navigate_to("/users")
{:ok, state}
end
when_ ~r/^I click the logout button$/, fn state ->
click({:id, "logout_button"})
{:ok, state}
end
then_ ~r/^I should be logged out$/, fn state ->
assert visible_in_page? ~r"You are successfully logged out"
{:ok, state}
end
and_ ~r/^I should be redirected to the home page$/, fn state ->
assert current_path() == "/"
{:ok, state}
end
given_ ~r/^I am not logged in$/, fn state ->
navigate_to("/")
{:ok, state}
end
when_ ~r/^I try to access the logout functionality$/, fn state ->
navigate_to("/logout")
{:ok, state}
end
then_ ~r/^I should see an error message$/, fn state ->
assert visible_in_page? ~r"You are not logged in"
{:ok, state}
end
end
Feature: User Logout
Scenario: Authenticated user can logout successfully (AC1, AC2)
Given there exists following accounts
| name | surname | birth_date | phone_number | email | password | confirm_password |
| Existing | Account | 2000-01-01 | 000 | existing.account@gmail.com | password | password |
And I am logged in
When I click the logout button
Then I should be logged out
And I should be redirected to the home page
Scenario: Unauthenticated user attempts to logout
Given I am not logged in
When I try to access the logout functionality
Then I should see an error message
And I should be redirected to the home page
\ No newline at end of file
<header class="header">
<ol class="breadcrumb pull-right">
<%= if @conn.assigns.current_user do %>
<li>Hello <%= @conn.assigns.current_user.name %> </li>
<li>Hello <%= @conn.assigns.current_user.name %></li>
<button
id="logout_button"
phx-click={JS.navigate("/logout")}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
<.icon name="hero-arrow-left-solid" class="h-3 w-3" /> Logout
</button>
<% else %>
<% end %>
</ol>
<span class="logo"></span>
</header>
<main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
\ No newline at end of file
<div class="mx-auto max-w-2xl"><.flash_group flash={@flash} /> <%= @inner_content %></div>
</main>
......@@ -3,10 +3,6 @@ defmodule PropTrackrWeb.LoginController do
import PropTrackr.Authentication
defmodule LoginProps do
defstruct [:email, :password]
end
def index(conn, _params) do
current_user = get_session(conn, :user_id)
if current_user do
......
defmodule PropTrackrWeb.LogoutController do
use PropTrackrWeb, :controller
alias PropTrackr.Authentication
def index(conn, _params) do
current_user = get_session(conn, :user_id)
if current_user do
conn
|> Authentication.logout()
|> put_flash(:info, "You are successfully logged out")
|> redirect(to: "/")
else
conn
|> put_flash(:info, "You are not logged in")
|> redirect(to: "/")
end
end
end
defmodule PropTrackr.Authentication do
import Plug.Conn
import Phoenix.Controller, only: [put_flash: 3]
def init(opts) do
opts[:repo]
......@@ -9,7 +10,11 @@ defmodule PropTrackr.Authentication do
def call(conn, repo) do
user_id = get_session(conn, :user_id)
user = user_id && repo.get(PropTrackr.Accounts.User, user_id)
login(conn, user_id, user)
if user do
login(conn, user_id, user)
else
assign(conn, :current_user, nil)
end
end
def login(conn, user_id, user) do
......@@ -17,6 +22,12 @@ defmodule PropTrackr.Authentication do
|> put_session(:user_id, user_id)
end
def logout(conn) do
conn
|> assign(:current_user, nil)
|> delete_session(:user_id)
end
def validate_credentials(conn, email, password, [repo: repo]) do
user = repo.get_by(PropTrackr.Accounts.User, email: email)
cond do
......
......@@ -22,6 +22,7 @@ defmodule PropTrackrWeb.Router do
resources "/users", UserController, only: [:index]
resources "/register", RegisterController, only: [:index, :create]
resources "/login", LoginController, only: [:index, :create]
resources "/logout", LogoutController
resources "/me/password", PasswordController, only: [:index, :create]
end
......
# Script for populating the database. You can run it as:
#
# mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
# PropTrackr.Repo.insert!(%PropTrackr.SomeSchema{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.
alias PropTrackr.{Repo, Accounts.User}
[
%{
name: "Shyngys",
surname: "Satkan",
birth_date: "2003-09-04",
phone_number: "+77011234567",
bio: "Software engineer",
email: "shyngys.satkan@example.com",
password: "ssatkan",
confirm_password: "ssatkan"
},
%{
name: "Jotaro",
surname: "Kujo",
birth_date: "1984-04-03",
phone_number: "+1234567890",
bio: "Marine biologist and a stand user.",
email: "jotaro.kujo@example.com",
password: "oraoraora",
confirm_password: "oraoraora"
}
]
|> Enum.map(fn user_data -> User.changeset(%User{}, user_data) end)
|> Enum.each(fn changeset -> Repo.insert!(changeset) end)
defmodule PropTrackrWeb.LogoutControllerTest do
use PropTrackrWeb.ConnCase
alias PropTrackr.Accounts.User
alias PropTrackr.Repo
setup do
user = %User{
name: "Test",
surname: "User",
birth_date: "2000-01-01",
phone_number: "000",
bio: "Yo",
email: "test.user@gmail.com",
password: "testing",
confirm_password: "testing",
}
Repo.insert!(user)
:ok
end
test "Authenticated user should be able to logout and be redirected to the homepage", %{conn: conn} do
# First login the user
conn = conn
|> post("/login", %{"email" => "test.user@gmail.com", "password" => "testing"})
# Verify logged in
assert get_session(conn, :user_id)
# Perform logout
conn = get(conn, "/logout")
assert redirected_to(conn) == "/"
conn = get(conn, redirected_to(conn))
assert html_response(conn, 200)
assert get_flash(conn, :info) =~ ~r/You are successfully logged out/
refute get_session(conn, :user_id)
end
test "Unauthenticated user should be redirected to homepage with message", %{conn: conn} do
conn = get(conn, "/logout")
assert redirected_to(conn) == "/"
conn = get(conn, redirected_to(conn))
assert html_response(conn, 200)
assert get_flash(conn, :info) =~ ~r/You are not logged in/
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment