diff --git a/Makefile b/Makefile index 3c3063a..623ddf1 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ logs: build: @docker build --build-arg RAILS_MASTER_KEY=$(cat config/master.key) -f Dockerfile -t simplesave-api:latest . test: - @rails test + @bin/rails test run: - @rails server + @bin/rails server migrate: - @rails db:migrate \ No newline at end of file + @bin/rails db:migrate \ No newline at end of file diff --git a/app/controllers/api/v1/auth_controller.rb b/app/controllers/api/v1/auth_controller.rb new file mode 100644 index 0000000..462897c --- /dev/null +++ b/app/controllers/api/v1/auth_controller.rb @@ -0,0 +1,30 @@ +module Api + module V1 + class AuthController < ApplicationController + def register + user = User.new(user_params) + + if user.save + session = Session.create!(user_id: user.id) + + render json: { + token: session.token, + user: { + id: user.id, + email: user.email + } + }, status: :created + else + render json: { errors: user.errors.full_messages }, status: :unprocessable_entity + end + end + + private + def user_params + params.require(:user).permit(:email, :password, :password_confirmation) + rescue ActionController::ParameterMissing + {} + end + end + end +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000..330d071 --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,6 @@ +class UserMailer < ApplicationMailer + def account_activation(user) + @user = user + mail to: @user.email, subject: "Account activation" + end +end diff --git a/app/models/session.rb b/app/models/session.rb index 7cd717e..39aa064 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -8,6 +8,7 @@ class Session < ApplicationRecord validates :token, presence: true, uniqueness: true validates :expires_at, presence: true + before_validation :generate_token, on: :create before_validation :set_expiration, on: :create def revoked? diff --git a/app/models/user.rb b/app/models/user.rb index 527cea1..8a93fb9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,7 @@ class User < ApplicationRecord has_secure_password - validates :email, presence: true, uniqueness: true + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i + validates :email, presence: true, uniqueness: true, format: { with: VALID_EMAIL_REGEX } + validates :password, presence: { on: :create }, length: { minimum: 8 }, if: -> { password.present? } end diff --git a/config/routes.rb b/config/routes.rb index c7e7197..542ed34 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -7,6 +7,11 @@ # Application resources :application + # V1 Namespace + namespace :auth do + post :register + end + # Root root to: "root#index" end diff --git a/test/controllers/api/v1/auth_controller_test.rb b/test/controllers/api/v1/auth_controller_test.rb new file mode 100644 index 0000000..4cae377 --- /dev/null +++ b/test/controllers/api/v1/auth_controller_test.rb @@ -0,0 +1,5 @@ +require "test_helper" + +class Api::V1::AuthControllerTest < ActionDispatch::IntegrationTest + # TODO: Add unit tests for registration endpoint here. +end diff --git a/test/integration/api/v1/api_v1_auth_register_test.rb b/test/integration/api/v1/api_v1_auth_register_test.rb new file mode 100644 index 0000000..d317c90 --- /dev/null +++ b/test/integration/api/v1/api_v1_auth_register_test.rb @@ -0,0 +1,99 @@ +require "test_helper" + +class ApiV1AuthRegisterTest < ActionDispatch::IntegrationTest + def setup + # Start from a clean state before each test, each test does its setup. + Session.delete_all + User.delete_all + end + + test "register creates a user and a session" do + assert_difference [ "User.count", "Session.count" ], +1 do + post "/api/v1/auth/register", params: { + user: { + email: "alice@example.com", + password: "securepassword", + password_confirmation: "securepassword" + } + } + end + + assert_response :created + + body = JSON.parse(response.body) + + assert body["token"].present?, "expected response to include a session token" + assert_equal "alice@example.com", body.dig("user", "email") + end + + test "register with existing email returns error" do + existing_user = User.create!( + email: "alice@example.com", + password: "securepassword", + password_confirmation: "securepassword" + ) + + assert_no_difference [ "User.count", "Session.count" ] do + post "/api/v1/auth/register", params: { + user: { + email: existing_user.email, + password: "anotherpassword", + password_confirmation: "anotherpassword" + } + } + end + + assert_response :unprocessable_entity + assert JSON.parse(response.body)["errors"].present?, "expected response to include errors" + end + + test "register with mismatched password confirmation returns error" do + post "/api/v1/auth/register", params: { + user: { + email: "alice@example.com", + password: "securepassword", + password_confirmation: "differentpassword" + } + } + + assert_response :unprocessable_entity + assert JSON.parse(response.body)["errors"].present?, "expected response to include errors" + end + + test "register with invalid email returns error" do + post "/api/v1/auth/register", params: { + user: { + email: "invalid-email", + password: "securepassword", + password_confirmation: "securepassword" + } + } + + assert_response :unprocessable_entity + assert JSON.parse(response.body)["errors"].present?, "expected response to include errors" + end + + test "register with short password returns error" do + post "/api/v1/auth/register", params: { + user: { + email: "alice@example.com", + password: "short", + password_confirmation: "short" + } + } + + assert_response :unprocessable_entity + assert JSON.parse(response.body)["errors"].present?, "expected response to include errors" + end + + test "register with missing parameters returns error" do + post "/api/v1/auth/register", params: {} + assert_response :unprocessable_entity + assert JSON.parse(response.body)["errors"].present?, "expected response to include errors" + end + + test "register with non-POST method returns method not allowed" do + get "/api/v1/auth/register" + assert_response :not_found + end +end