From 160a5b5269cbece1f0fbbb2c585d4dbbc7caad71 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 7 Oct 2025 20:35:20 -0400 Subject: [PATCH] Signup user flow --- .../app/controllers/signups_controller.rb | 31 +++++++ .../fizzy-saas/app/views/signups/new.html.erb | 60 +++++++++++++ gems/fizzy-saas/config/routes.rb | 1 + .../controllers/signups_controller_test.rb | 87 +++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 gems/fizzy-saas/app/controllers/signups_controller.rb create mode 100644 gems/fizzy-saas/app/views/signups/new.html.erb create mode 100644 gems/fizzy-saas/test/controllers/signups_controller_test.rb diff --git a/gems/fizzy-saas/app/controllers/signups_controller.rb b/gems/fizzy-saas/app/controllers/signups_controller.rb new file mode 100644 index 000000000..73bcdeada --- /dev/null +++ b/gems/fizzy-saas/app/controllers/signups_controller.rb @@ -0,0 +1,31 @@ +class SignupsController < ApplicationController + require_untenanted_access + + rate_limit only: :create, name: "short-term", to: 5, within: 3.minutes, + with: -> { redirect_to saas.new_signup_path, alert: "Try again later." } + rate_limit only: :create, name: "long-term", to: 10, within: 30.minutes, + with: -> { redirect_to saas.new_signup_path, alert: "Try again later." } + + http_basic_authenticate_with \ + name: Rails.env.test? ? "testname" : Rails.application.credentials.account_signup_http_basic_auth.name, + password: Rails.env.test? ? "testpassword" : Rails.application.credentials.account_signup_http_basic_auth.password + + def new + @signup = Signup.new + end + + def create + @signup = Signup.new(signup_params) + + if @signup.process + redirect_to root_url(script_name: @signup.account.slug) + else + render :new, status: :unprocessable_entity + end + end + + private + def signup_params + params.require(:signup).permit(*Signup::PERMITTED_KEYS) + end +end diff --git a/gems/fizzy-saas/app/views/signups/new.html.erb b/gems/fizzy-saas/app/views/signups/new.html.erb new file mode 100644 index 000000000..c357d3c0f --- /dev/null +++ b/gems/fizzy-saas/app/views/signups/new.html.erb @@ -0,0 +1,60 @@ +<% @page_title = "Sign up for Fizzy" %> + +
" style="--panel-size: 65ch;"> +

Fizzy

+

Create your account

+ + <%= form_with model: @signup, url: saas.signup_path, scope: "signup", class: "flex flex-column gap txt-large", data: { turbo: false } do |form| %> +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + <% if @signup.errors.any? %> +
+
    + <% @signup.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + + + <% end %> + + +
diff --git a/gems/fizzy-saas/config/routes.rb b/gems/fizzy-saas/config/routes.rb index cb130af4f..698095350 100644 --- a/gems/fizzy-saas/config/routes.rb +++ b/gems/fizzy-saas/config/routes.rb @@ -1,3 +1,4 @@ Fizzy::Saas::Engine.routes.draw do + resource :signup, only: [ :new, :create ] Queenbee.routes(self) end diff --git a/gems/fizzy-saas/test/controllers/signups_controller_test.rb b/gems/fizzy-saas/test/controllers/signups_controller_test.rb new file mode 100644 index 000000000..8cbe8ec9f --- /dev/null +++ b/gems/fizzy-saas/test/controllers/signups_controller_test.rb @@ -0,0 +1,87 @@ +require "test_helper" + +class SignupsControllerTest < ActionDispatch::IntegrationTest + setup do + @signup_params = { + full_name: "Brian Wilson", + email_address: "brian@example.com", + company_name: "Beach Boys", + password: SecureRandom.hex(16) + } + @starting_tenants = ApplicationRecord.tenants + + # Clear script_name for untenanted signup tests + integration_session.default_url_options[:script_name] = nil + end + + test "should require http basic authentication" do + get saas.new_signup_url + + assert_response :unauthorized + end + + test "should get new" do + get saas.new_signup_url, headers: http_basic_auth_headers + + assert_response :success + assert_select "h2", "Create your account" + assert_select "input[name='signup[full_name]']" + assert_select "input[name='signup[email_address]']" + assert_select "input[name='signup[company_name]']" + assert_select "input[name='signup[password]']" + end + + test "should create signup and redirect to tenant root on success" do + Account.any_instance.expects(:setup_basic_template).once + + assert_difference -> { ApplicationRecord.tenants.count }, 1 do + post saas.signup_url, params: { signup: @signup_params }, headers: http_basic_auth_headers + end + + assert_response :redirect + + new_tenant = (ApplicationRecord.tenants - @starting_tenants).first + ApplicationRecord.with_tenant(new_tenant) do + account = Account.sole + assert account, "Account should have been created" + assert_equal @signup_params[:company_name], account.name + + user = User.find_by(email_address: @signup_params[:email_address]) + assert user, "User should have been created" + assert_equal @signup_params[:full_name], user.name + assert_equal @signup_params[:email_address], user.email_address + + assert_redirected_to root_url(script_name: account.slug) + end + end + + test "should render new with errors when signup fails validation" do + invalid_params = @signup_params.merge(password: "") + + assert_no_difference -> { ApplicationRecord.tenants.count } do + post saas.signup_url, params: { signup: invalid_params }, headers: http_basic_auth_headers + end + + assert_response :unprocessable_entity + assert_select ".alert--error" + assert_select ".alert--error li", /Password can't be blank/ + end + + test "should render new with errors when signup processing fails" do + Queenbee::Remote::Account.stubs(:create!).raises(RuntimeError, "Invalid account data") + + assert_no_difference -> { ApplicationRecord.tenants.count } do + post saas.signup_url, params: { signup: @signup_params }, headers: http_basic_auth_headers + end + + assert_response :unprocessable_entity + assert_select ".alert--error" + assert_select ".alert--error li", /An error occurred during signup/ + end + + private + def http_basic_auth_headers + credentials = ActionController::HttpAuthentication::Basic.encode_credentials("testname", "testpassword") + { "HTTP_AUTHORIZATION" => credentials } + end +end