Introduce a Signup model and integrate with QB/37id
- Creates a Queenbee::Account - SignalId peer classes work properly - Set up a basic template for the account (closure reasons, workflow, collection) - Load signal_id fixtures during `bin/setup --reset` - Update signal_id to pull in the Fizzy product
This commit is contained in:
@@ -39,6 +39,8 @@ gem "queuety", bc: "queuety", branch: "rails4" # needed by signal_id
|
||||
gem "service_concurrency_prevention", bc: "service_concurrency_prevention" # needed by queuety
|
||||
gem "portfolio", ">= 4.6", bc: "portfolio" # needed by signal_id
|
||||
gem "file_repository", "~> 1.4.5", bc: "file_repository" # needed by portfolio
|
||||
gem "queenbee", bc: "queenbee-plugin"
|
||||
gem "activeresource", require: "active_resource" # needed by queenbee
|
||||
|
||||
# Telemetry, logging, and operations
|
||||
gem "mission_control-jobs"
|
||||
@@ -63,4 +65,5 @@ group :test do
|
||||
gem "selenium-webdriver"
|
||||
gem "webmock"
|
||||
gem "vcr"
|
||||
gem "mocha"
|
||||
end
|
||||
|
||||
+21
-1
@@ -46,6 +46,15 @@ GIT
|
||||
activesupport
|
||||
file_repository (>= 1.4.4)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queenbee-plugin
|
||||
revision: bf887b5c128570d1b19a0140146ae9aa4ae2915f
|
||||
specs:
|
||||
queenbee (3.2.0)
|
||||
activeresource
|
||||
builder
|
||||
rexml
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/queuety
|
||||
revision: bf7e2a552fb674533d3e092f06dbf9b5d1ebc753
|
||||
@@ -72,7 +81,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/basecamp/signal_id
|
||||
revision: 7090b0d518b6656d8c8792da8301146da40f3eae
|
||||
revision: 129483ccc1d9452fa7713080fcbf0c012b2f4107
|
||||
branch: rails4
|
||||
specs:
|
||||
signal_id (4.3.2)
|
||||
@@ -205,6 +214,14 @@ GEM
|
||||
specs:
|
||||
action_text-trix (2.1.15)
|
||||
railties
|
||||
activemodel-serializers-xml (1.0.3)
|
||||
activemodel (>= 5.0.0.a)
|
||||
activesupport (>= 5.0.0.a)
|
||||
builder (~> 3.1)
|
||||
activeresource (6.1.4)
|
||||
activemodel (>= 6.0)
|
||||
activemodel-serializers-xml (~> 1.0)
|
||||
activesupport (>= 6.0)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
ast (2.4.3)
|
||||
@@ -568,6 +585,7 @@ PLATFORMS
|
||||
DEPENDENCIES
|
||||
actiontext-lexical!
|
||||
active_record-tenanted!
|
||||
activeresource
|
||||
aws-sdk-s3
|
||||
bcrypt (~> 3.1.7)
|
||||
bootsnap
|
||||
@@ -582,11 +600,13 @@ DEPENDENCIES
|
||||
jbuilder
|
||||
kamal!
|
||||
mission_control-jobs
|
||||
mocha
|
||||
mysql2
|
||||
platform_agent
|
||||
portfolio (>= 4.6)!
|
||||
propshaft
|
||||
puma (>= 5.0)
|
||||
queenbee!
|
||||
queuety!
|
||||
rails!
|
||||
rails_structured_logging!
|
||||
|
||||
@@ -2,4 +2,13 @@ class Account < ApplicationRecord
|
||||
include Entropic, Joinable, SignalAccount
|
||||
|
||||
has_many_attached :uploads
|
||||
|
||||
def setup_basic_template
|
||||
user = User.first
|
||||
|
||||
Closure::Reason.create_defaults
|
||||
Collection.create!(name: "Cards", creator: user, all_access: true)
|
||||
workflow = Workflow.create!(name: "Basic")
|
||||
Collection.first.update!(workflow: workflow)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,4 +5,23 @@ module Account::SignalAccount
|
||||
# TODO: remove the "optional: true" once we've populated the accounts properly
|
||||
belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id, optional: true
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def create_with_admin_user(queenbee_id:)
|
||||
new(queenbee_id: queenbee_id).tap do |account|
|
||||
SignalId::Database.on_master do
|
||||
account.name = account.signal_account.name
|
||||
account.save!
|
||||
|
||||
User.create!(
|
||||
name: account.signal_account.owner.name,
|
||||
email_address: account.signal_account.owner.email_address,
|
||||
signal_user_id: account.signal_account.owner.id,
|
||||
role: "admin",
|
||||
password: SecureRandom.hex(36) # TODO: remove password column?
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
class Signup
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
include ActiveModel::Validations
|
||||
|
||||
attribute :full_name, :string
|
||||
attribute :email_address, :string
|
||||
attribute :password, :string
|
||||
attribute :company_name, :string
|
||||
|
||||
attr_reader :signal_identity, :queenbee_account, :signal_account, :account, :user
|
||||
|
||||
validate :validate_new_identity
|
||||
|
||||
def initialize(...)
|
||||
super
|
||||
|
||||
@signal_identity = nil
|
||||
@queenbee_account = nil
|
||||
@account = nil
|
||||
@user = nil
|
||||
end
|
||||
|
||||
def process
|
||||
return false unless valid?
|
||||
|
||||
create_signal_identity
|
||||
create_queenbee_account
|
||||
create_tenant
|
||||
|
||||
true
|
||||
rescue => error
|
||||
destroy_tenant
|
||||
destroy_queenbee_account
|
||||
destroy_signal_identity
|
||||
|
||||
errors.add(:base, "An error occurred during signup: #{error.message}")
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
def create_signal_identity
|
||||
SignalId::Database.on_master do
|
||||
@signal_identity = build_new_identity.tap(&:save!)
|
||||
end
|
||||
end
|
||||
|
||||
def create_queenbee_account
|
||||
@queenbee_account = Queenbee::Remote::Account.create!(queenbee_account_attributes)
|
||||
SignalId::Database.on_master do
|
||||
@signal_account = SignalId::Account.find_by(queenbee_id: @queenbee_account.id)
|
||||
end
|
||||
end
|
||||
|
||||
def create_tenant(&block)
|
||||
ApplicationRecord.create_tenant(tenant_name) do
|
||||
@account = Account.create_with_admin_user(queenbee_id: queenbee_account.id)
|
||||
@account.setup_basic_template
|
||||
@user = User.first
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_tenant
|
||||
if queenbee_account && ApplicationRecord.tenant_exist?(tenant_name)
|
||||
ApplicationRecord.destroy_tenant(tenant_name)
|
||||
@account = nil
|
||||
@user = nil
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_queenbee_account
|
||||
queenbee_account&.cancel
|
||||
@queenbee_account = nil
|
||||
end
|
||||
|
||||
def destroy_signal_identity
|
||||
SignalId::Database.on_master do
|
||||
signal_identity&.destroy
|
||||
end
|
||||
@signal_identity = nil
|
||||
end
|
||||
|
||||
def validate_new_identity
|
||||
build_new_identity.tap do |identity|
|
||||
unless identity.valid?
|
||||
identity.errors.each { |error| errors.add(error.attribute, error.message) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_new_identity
|
||||
SignalId::Identity.new do |identity|
|
||||
identity.name = full_name || email_address
|
||||
identity.email_address = email_address
|
||||
identity.username = email_address
|
||||
identity.password = password
|
||||
end
|
||||
end
|
||||
|
||||
def queenbee_account_attributes
|
||||
{
|
||||
skip_remote: true, # Fizzy creates its own local account
|
||||
product_name: "fizzy",
|
||||
name: company_name.presence || signal_identity.name,
|
||||
owner_identity_id: signal_identity.id,
|
||||
trial: false,
|
||||
subscription: subscription_attributes,
|
||||
remote_request: request_attributes
|
||||
}
|
||||
end
|
||||
|
||||
def subscription_attributes
|
||||
{ name: "FreeV1", price: 0 }
|
||||
end
|
||||
|
||||
def request_attributes
|
||||
{ remote_address: Current.ip_address, user_agent: Current.user_agent, referrer: Current.referrer }
|
||||
end
|
||||
|
||||
def tenant_name
|
||||
@tenant_name ||= signal_account.subdomain
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,15 @@
|
||||
require "signal_id"
|
||||
|
||||
Rails.application.config.to_prepare do
|
||||
SignalId.product = "fizzy"
|
||||
|
||||
SignalId::Database.load_configuration SignalId::Database.default_configuration
|
||||
SignalId::Database.enable_rw_splitting!
|
||||
|
||||
silence_warnings do
|
||||
SignalId::Account::Peer = Account
|
||||
SignalId::User::Peer = User
|
||||
end
|
||||
end
|
||||
|
||||
Rails.application.config.after_initialize do
|
||||
|
||||
@@ -10,4 +10,27 @@ class Account::SignalAccountTest < ActiveSupport::TestCase
|
||||
assert_equal @account.queenbee_id, Account.new(signal_account: @account.signal_account).queenbee_id
|
||||
assert_equal @account.signal_account, Account.new(queenbee_id: @account.queenbee_id).signal_account
|
||||
end
|
||||
|
||||
test ".create_with_admin_user creates a new local account and user peers" do
|
||||
ApplicationRecord.create_tenant("account-create-with-dependents") do
|
||||
signal_account = signal_accounts(:honcho_fizzy)
|
||||
account = Account.create_with_admin_user(queenbee_id: signal_account.queenbee_id)
|
||||
|
||||
assert_not_nil account
|
||||
assert account.persisted?
|
||||
assert_equal 1, Account.count
|
||||
assert_equal signal_account.queenbee_id, account.queenbee_id
|
||||
assert_equal signal_account.name, account.name
|
||||
assert_equal account, signal_account.peer
|
||||
|
||||
assert_equal 1, User.count
|
||||
User.first.tap do |user|
|
||||
assert signal_account.owner.name, user.name
|
||||
assert signal_account.owner.email_address, user.email_address
|
||||
assert signal_account.owner.id, user.signal_user_id
|
||||
assert_equal "admin", user.role
|
||||
assert_equal user, signal_account.owner.peer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
require "test_helper"
|
||||
|
||||
class SignupTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@signup = Signup.new(
|
||||
email_address: "brian@example.com",
|
||||
full_name: "Brian Wilson",
|
||||
company_name: "Beach Boys",
|
||||
password: SecureRandom.hex(16)
|
||||
)
|
||||
end
|
||||
|
||||
test "#process creates all the necessary objects" do
|
||||
Account.any_instance.expects(:setup_basic_template).once
|
||||
|
||||
assert @signup.process, @signup.errors.full_messages.to_sentence(words_connector: ". ")
|
||||
assert_empty @signup.errors
|
||||
|
||||
assert @signup.signal_identity
|
||||
assert @signup.signal_identity.persisted?
|
||||
|
||||
assert @signup.queenbee_account
|
||||
assert @signup.queenbee_account.id
|
||||
|
||||
assert @signup.signal_account
|
||||
assert @signup.signal_account.persisted?
|
||||
|
||||
assert @signup.account
|
||||
assert @signup.account.persisted?
|
||||
|
||||
assert @signup.user
|
||||
assert @signup.user.persisted?
|
||||
|
||||
assert_includes ApplicationRecord.tenants, @signup.signal_account.subdomain
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating identity" do
|
||||
@signup.password = ""
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:password]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the queenbee account" do
|
||||
Queenbee::Remote::Account.stubs(:create!).raises(RuntimeError, "Invalid account data")
|
||||
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the tenant" do
|
||||
ApplicationRecord.stubs(:create_tenant).raises(RuntimeError, "Tenant already exists")
|
||||
|
||||
Queenbee::Remote::Account.any_instance.expects(:cancel).once
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
|
||||
test "#process does nothing if a validation error occurs creating the account" do
|
||||
Account.stubs(:create_with_admin_user).raises(RuntimeError, "Account creation failed")
|
||||
|
||||
ApplicationRecord.expects(:destroy_tenant).once
|
||||
Queenbee::Remote::Account.any_instance.expects(:cancel).once
|
||||
SignalId::Identity.any_instance.expects(:destroy).once
|
||||
|
||||
assert_not @signup.process
|
||||
assert_not_empty @signup.errors[:base]
|
||||
|
||||
assert_nil @signup.signal_identity
|
||||
assert_nil @signup.queenbee_account
|
||||
assert_nil @signup.account
|
||||
assert_nil @signup.user
|
||||
end
|
||||
end
|
||||
@@ -4,6 +4,8 @@ require "rails/test_help"
|
||||
require "webmock/minitest"
|
||||
require "vcr"
|
||||
require "signal_id/testing"
|
||||
require "queenbee/testing/mocks"
|
||||
require "mocha/minitest"
|
||||
|
||||
WebMock.allow_net_connect!
|
||||
|
||||
@@ -34,3 +36,11 @@ end
|
||||
RubyLLM.configure do |config|
|
||||
config.openai_api_key ||= "DUMMY-TEST-KEY" # Run tests with VCR without having to configure OpenAI API key locally.
|
||||
end
|
||||
|
||||
Queenbee::Remote::Account.class_eval do
|
||||
# because we use the account ID as the tenant name, we need it to be unique in each test to avoid
|
||||
# parallelized tests clobbering each other.
|
||||
def next_id
|
||||
super + Random.rand(1000000)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user