diff --git a/Gemfile b/Gemfile index c72bd8326..9296044c0 100644 --- a/Gemfile +++ b/Gemfile @@ -71,7 +71,7 @@ group :test do end require_relative "lib/bootstrap" -unless Bootstrap.local_authentication? +unless Bootstrap.oss_config? eval_gemfile "gems/fizzy-saas/Gemfile" gem "fizzy-saas", path: "gems/fizzy-saas" end diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index 89871a9f5..2ede3c108 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -37,7 +37,9 @@ module Authentication end def require_tenant - ApplicationRecord.current_tenant.present? || request_authentication + unless ApplicationRecord.current_tenant.present? + render "sessions/login_menu" + end end def require_authentication diff --git a/app/models/account.rb b/app/models/account.rb index 950c0a124..c29f61005 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -4,7 +4,7 @@ class Account < ApplicationRecord has_many_attached :uploads def slug - "/#{tenant_id}" + "/#{tenant}" end def setup_basic_template diff --git a/app/views/layouts/shared/_head.html.erb b/app/views/layouts/shared/_head.html.erb index 0922c2de4..25b06cae9 100644 --- a/app/views/layouts/shared/_head.html.erb +++ b/app/views/layouts/shared/_head.html.erb @@ -19,7 +19,9 @@ <%= tenanted_action_cable_meta_tag %> <%= yield :head %> - + <% if ApplicationRecord.current_tenant %> + + <% end %> diff --git a/app/views/sessions/login_menu.html.erb b/app/views/sessions/login_menu.html.erb new file mode 100644 index 000000000..e8c78fc61 --- /dev/null +++ b/app/views/sessions/login_menu.html.erb @@ -0,0 +1,15 @@ +<% cache ApplicationRecord.tenants do %> +

We're migrating Fizzy authentication

+

+ While we migrate Fizzy away from Launchpad and 37id, please login at one of the following URLs: + +

+

+

+ Mike should have sent you your temporary password by now. If you haven't received it, please ping him! +

+<% end %> diff --git a/bin/dev b/bin/dev index 9fe70d369..1c77800e3 100755 --- a/bin/dev +++ b/bin/dev @@ -1,12 +1,10 @@ #!/usr/bin/env sh -echo "Login with david@37signals.com / secret123456 to:" bin/rails runner - < "accounts#new" resources :accounts, only: %i[ new create ] diff --git a/gems/fizzy-saas/lib/fizzy/saas/engine.rb b/gems/fizzy-saas/lib/fizzy/saas/engine.rb index 2729e1db3..4e5e93489 100644 --- a/gems/fizzy-saas/lib/fizzy/saas/engine.rb +++ b/gems/fizzy-saas/lib/fizzy/saas/engine.rb @@ -5,10 +5,7 @@ module Fizzy class Engine < ::Rails::Engine # extend application models config.to_prepare do - User.prepend User::SignalUser Account.prepend Account::SignalAccount - LoginHelper.prepend LoginHelper::SignalLogin - SessionsController.include Sessions::SignalSessions end # moved from config/initializers/queenbee.rb @@ -39,7 +36,6 @@ module Fizzy silence_warnings do SignalId::Account::Peer = Account - SignalId::User::Peer = User end end diff --git a/gems/fizzy-saas/test/controllers/controller_authentication_test.rb b/gems/fizzy-saas/test/controllers/controller_authentication_test.rb deleted file mode 100644 index bf1fc21aa..000000000 --- a/gems/fizzy-saas/test/controllers/controller_authentication_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "test_helper" - -class ControllerAuthenticationTest < ActionDispatch::IntegrationTest - test "access without an account slug redirects to launchpad" do - integration_session.default_url_options[:script_name] = "" # no tenant - - get cards_path - - assert_redirected_to Launchpad.login_url(product: true) - end - - test "access with an account slug but no session redirects to launchpad" do - get cards_path - - assert_redirected_to Launchpad.login_url(product: true, account: Account.sole) - end -end diff --git a/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb b/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb deleted file mode 100644 index 309f6ec1a..000000000 --- a/gems/fizzy-saas/test/controllers/sessions/launchpad_controller_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require "test_helper" - -class Sessions::LaunchpadControllerTest < ActionDispatch::IntegrationTest - test "show renders when not signed in" do - get saas.session_launchpad_path(params: { sig: "test-sig" }) - - assert_response :success - - assert_select "form input#sig" do |node| - assert_equal node.length, 1 - assert_equal node.first["value"], "test-sig" - end - end - - test "create establishes a session when the sig is valid" do - user = users(:david) - - put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature }) - - assert_redirected_to root_url - assert parsed_cookies.signed[:session_token] - end - - test "create checks user.active?" do - user = users(:david) - user.update! active: false - - put saas.session_launchpad_path(params: { sig: user.external_user.perishable_signature }) - - assert_response :unauthorized - end - - test "returns 401 when the sig is invalid" do - put saas.session_launchpad_path(params: { sig: "invalid" }) - - assert_response :unauthorized - end -end diff --git a/gems/fizzy-saas/test/controllers/sessions_controller_test.rb b/gems/fizzy-saas/test/controllers/sessions_controller_test.rb deleted file mode 100644 index 98df25bdf..000000000 --- a/gems/fizzy-saas/test/controllers/sessions_controller_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "test_helper" - -class SessionsControllerTest < ActionDispatch::IntegrationTest - test "destroy" do - sign_in_as :kevin - - delete session_path - - assert_redirected_to Launchpad.logout_url - assert_not cookies[:session_token].present? - end - - test "new" do - get new_session_path - - assert_response :forbidden - end - - test "create" do - post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } - - assert_response :forbidden - end -end diff --git a/gems/fizzy-saas/test/models/account/signal_account_test.rb b/gems/fizzy-saas/test/models/account/signal_account_test.rb index 623f95610..ad43e45cb 100644 --- a/gems/fizzy-saas/test/models/account/signal_account_test.rb +++ b/gems/fizzy-saas/test/models/account/signal_account_test.rb @@ -18,7 +18,7 @@ class Account::SignalAccountTest < ActiveSupport::TestCase assert_equal @account, @account.external_account.peer end - test ".create_with_admin_user creates a new local account and user peers" do + test ".create_with_admin_user creates a new local account" do ApplicationRecord.create_tenant("account-create-with-dependents") do signal_account = signal_accounts(:honcho_fizzy) account = Account.create_with_admin_user(tenant_id: signal_account.queenbee_id) @@ -32,11 +32,9 @@ class Account::SignalAccountTest < ActiveSupport::TestCase 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.external_user_id + assert_equal signal_account.owner.name, user.name + assert_equal signal_account.owner.email_address, user.email_address assert_equal "admin", user.role - assert_equal user, signal_account.owner.peer end end end diff --git a/gems/fizzy-saas/test/models/signup_test.rb b/gems/fizzy-saas/test/models/signup_test.rb index 96e432840..e64dbf801 100644 --- a/gems/fizzy-saas/test/models/signup_test.rb +++ b/gems/fizzy-saas/test/models/signup_test.rb @@ -42,7 +42,6 @@ class SignupTest < ActiveSupport::TestCase assert @signup.user assert @signup.user.persisted? - assert_equal @signup.user.external_user, @signup.signal_account.owner assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name assert_includes ApplicationRecord.tenants, @signup.tenant_name @@ -86,7 +85,6 @@ class SignupTest < ActiveSupport::TestCase assert @signup.user assert @signup.user.persisted? - assert_equal @signup.user.external_user, @signup.signal_account.owner assert_equal @signup.queenbee_account.id.to_s, @signup.tenant_name assert_includes ApplicationRecord.tenants, @signup.tenant_name diff --git a/gems/fizzy-saas/test/models/user/signal_user_test.rb b/gems/fizzy-saas/test/models/user/signal_user_test.rb deleted file mode 100644 index b244a587e..000000000 --- a/gems/fizzy-saas/test/models/user/signal_user_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "test_helper" - -class User::SignalUserTest < ActiveSupport::TestCase - setup do - @user = users(:david) - end - - test "belongs to a Signal::User" do - assert_not_nil @user.external_user_id - assert_equal signal_users("37s_fizzy_david"), @user.external_user - end - - test "peering" do - assert_equal @user, @user.external_user.peer - end - - test "deactivate clears signal user" do - users(:jz).deactivate - - assert_nil users(:jz).reload.external_user - end -end diff --git a/gems/fizzy-saas/test/test_helper.rb b/gems/fizzy-saas/test/test_helper.rb index 22d468a6c..648afa3ee 100644 --- a/gems/fizzy-saas/test/test_helper.rb +++ b/gems/fizzy-saas/test/test_helper.rb @@ -4,10 +4,6 @@ require "queenbee/testing/mocks" module ActiveSupport class TestCase include SignalId::Testing - - def saas_extension_sign_in_as(user) - put saas.session_launchpad_path, params: { sig: user.external_user.perishable_signature } - end end end diff --git a/lib/bootstrap.rb b/lib/bootstrap.rb index 1cfe4042c..6d52f3f49 100644 --- a/lib/bootstrap.rb +++ b/lib/bootstrap.rb @@ -1,5 +1,5 @@ module Bootstrap - def self.local_authentication? - ENV.fetch("LOCAL_AUTHENTICATION", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) + def self.oss_config? + ENV.fetch("OSS_CONFIG", "") != "" || !File.directory?(File.expand_path("../gems/fizzy-saas", __dir__)) end end diff --git a/script/create-local-user.rb b/script/create-local-user.rb deleted file mode 100755 index 7b303774f..000000000 --- a/script/create-local-user.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby -# -# This script creates a new user that can be logged into via LOCAL_AUTHENTICATION, avoiding Launchpad/37id integration. -# In order to login as this user, you must set the `LOCAL_AUTHENTICATION` environment variable when running the Rails server: -# -# LOCAL_AUTHENTICATION=1 bin/dev -# - -require_relative "../config/environment" - -unless Rails.env.development? - puts "ERROR: This script is intended to be run in development mode only." - exit 1 -end - -if ARGV.length < 2 - puts "Usage: #{$0} " - exit 1 -end - -email_address = ARGV[0] -tenant = ARGV[1] - -ApplicationRecord.with_tenant(tenant) do - user = User.create!( - name: email_address.split("@").first, - email_address: email_address, - password: "secret123456" - ) - - puts "Created: " - pp user -end diff --git a/script/create-new-user.rb b/script/create-new-user.rb index 934719447..ab30c585e 100755 --- a/script/create-new-user.rb +++ b/script/create-new-user.rb @@ -2,41 +2,25 @@ require_relative "../config/environment" -if ARGV.length < 2 - puts "Usage: #{$0} " +if ARGV.length < 3 + puts "Usage: #{$0} " exit 1 end -email_address = ARGV[0] -tenant = ARGV[1] +tenant = ARGV.shift +email_address = ARGV.shift +name = ARGV.join(" ") -def confirm(noun) - print "Is this the correct #{noun}? (y/n) " - response = $stdin.gets.chomp.downcase - exit 0 unless response == "y" - puts -end - -signal_identity = SignalId::Identity.find_by!(email_address: email_address) -pp signal_identity -confirm "identity" - -ApplicationRecord.with_tenant(tenant) do - signal_account = Account.sole.external_account - pp signal_account - confirm "account" - - SignalId::Database.on_master do - signal_user = SignalId::User.create!(identity: signal_identity, account: signal_account) - - user = User.create!( - name: signal_user.name, - email_address: signal_user.email_address, - external_user_id: signal_user.id, - password: SecureRandom.hex(36) # TODO: remove password column? - ) +begin + ApplicationRecord.with_tenant(tenant) do + password = SecureRandom.hex(16) + user = User.create!(name:, email_address:, password:) puts "Created: " - pp [ user, signal_user ] + pp user + + puts "Password is: #{password.inspect}" end +rescue Exception => e + puts "Failed with error: #{e.inspect}" end diff --git a/script/load-prod-db-in-dev.rb b/script/load-prod-db-in-dev.rb index a619b1f8d..da0db12ba 100755 --- a/script/load-prod-db-in-dev.rb +++ b/script/load-prod-db-in-dev.rb @@ -34,7 +34,6 @@ ActiveRecord::Tenanted::DatabaseTasks.migrate_all ApplicationRecord.with_tenant(signup.tenant_name) do |tenant| Account.sole.update! external_account: signup.signal_account - User.first.update! external_user: signup.signal_account.owner puts "\n\nLogin to http://launchpad.localhost:3011/ as #{signup.email_address} / #{signup.password}" end diff --git a/script/migrations/202510-migrate-to-local-authentication.rb b/script/migrations/202510-migrate-to-local-authentication.rb new file mode 100755 index 000000000..cd8788092 --- /dev/null +++ b/script/migrations/202510-migrate-to-local-authentication.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +# +# set up a temporary password for all users +# while we migrate away from Launchpad and 37id. +# +# the password is unguessable but generated based on the email address and a seed, +# so each user will have the same password across all tenants. +# +require_relative "../../config/environment" + +seed = ARGV[0] || "temporary-password-seed" +puts "Using seed: #{seed.inspect}" + +TENANTS = Hash.new +USERS = Hash.new { |h, k| h[k] = Hash.new } + +ApplicationRecord.with_each_tenant do |tenant| + TENANTS[tenant] = Account.sole.name + + User.find_each do |user| + putc "." + next if user.system? + + if user.external_user_id + suser = SignalId::User.find_by_id(user.external_user_id) + if suser && suser.email_address != user.email_address + puts "\nWarning: fixing email address for user #{user.id} in tenant #{tenant}:" + puts " local: #{user.email_address}" + puts " signal: #{suser.email_address}" + user.update! email_address: suser.email_address + end + end + + password = Digest::SHA256.hexdigest("#{seed}-#{user.email_address}")[0..16] + user.update! password: password + + USERS[user.email_address][tenant] = password + end +end + +puts + +USERS.each do |email, hash| + puts "\n#{email}:" + puts " password: #{hash.first.last}" + puts " fizzies:" + hash.each do |tenant, _| + url = Rails.application.routes.url_helpers.root_url(Rails.application.config.action_controller.default_url_options.merge(script_name: "/#{tenant}")) + + puts " #{TENANTS[tenant]}: #{url}" + end +end diff --git a/script/migrations/create-accounts-in-staging.rb b/script/migrations/create-accounts-in-staging.rb deleted file mode 100755 index d0279d608..000000000 --- a/script/migrations/create-accounts-in-staging.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env ruby - -require_relative "../config/environment" - -ApplicationRecord.with_each_tenant do |tenant| - account = Account.sole - signal_account = account.signal_account - - signal_users = SignalId::User.where(account_id: signal_account.id) - - signal_users.each do |signal_user| - unless User.find_by(signal_user_id: signal_user.id) - User.create!( - name: signal_user.identity.name, - email_address: signal_user.identity.email_address, - signal_user_id: signal_user.id, - password: SecureRandom.hex(36) # TODO: remove password column? - ) - end - end -end diff --git a/script/stitch-signal-id-accounts.rb b/script/stitch-signal-id-accounts.rb deleted file mode 100755 index e08f594ae..000000000 --- a/script/stitch-signal-id-accounts.rb +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env ruby - -# -# this is intended to copy a production database into beta, change the account id, and stitch the -# user accounts together properly. -# - -require_relative "../config/environment" - -ActiveRecord::Base.logger = Logger.new(File::NULL) - -ApplicationRecord.with_each_tenant do |tenant| - puts "\n# tenant: #{tenant}" - - signal_account = SignalId::Account.find_by!(queenbee_id: tenant) - puts "Found signal account #{signal_account.inspect}" - - account = Account.sole - if account.tenant_id != tenant - puts "setting account tenant_id to #{tenant}" - account.update!(tenant_id: tenant, name: account.name + " (Beta)") - end - - User.find_each do |user| - next if user.system? || user.external_user_id.nil? - - signal_user = user.external_user - next if signal_user.nil? - next if signal_user.account == account.external_account - - signal_identity = signal_user.identity - pp signal_identity - - SignalId::Database.on_master do - signal_user = SignalId::User.find_or_create_by!(identity: signal_identity, account: signal_account) - puts "Created signal user #{signal_user.inspect} for identity #{signal_identity.inspect}" - user.external_user_id = signal_user.id - user.save! - end - end -end diff --git a/test/controllers/controller_authentication_test.rb b/test/controllers/controller_authentication_test.rb index 1dfc7a3fd..72e0df995 100644 --- a/test/controllers/controller_authentication_test.rb +++ b/test/controllers/controller_authentication_test.rb @@ -1,20 +1,19 @@ require "test_helper" class ControllerAuthenticationTest < ActionDispatch::IntegrationTest - if Rails.application.config.x.local_authentication - test "access without an account slug redirects to new session" do - integration_session.default_url_options[:script_name] = "" # no tenant + test "access without an account slug redirects to new session" do + integration_session.default_url_options[:script_name] = "" # no tenant - get cards_path + get cards_path - assert_redirected_to new_session_path - end + assert_response :success + assert_dom "h2", text: "We're migrating Fizzy authentication" + end - test "access with an account slug but no session redirects to new session" do - get cards_path + test "access with an account slug but no session redirects to new session" do + get cards_path - assert_redirected_to new_session_path - end + assert_redirected_to new_session_path end test "access with an account slug and a session allows functional access" do diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb index 9a8cf0caf..0c85ad83c 100644 --- a/test/controllers/sessions_controller_test.rb +++ b/test/controllers/sessions_controller_test.rb @@ -1,34 +1,32 @@ require "test_helper" class SessionsControllerTest < ActionDispatch::IntegrationTest - if Rails.application.config.x.local_authentication - test "destroy" do - sign_in_as :kevin + test "destroy" do + sign_in_as :kevin - delete session_path + delete session_path - assert_redirected_to new_session_path - assert_not cookies[:session_token].present? - end + assert_redirected_to new_session_path + assert_not cookies[:session_token].present? + end - test "new" do - get new_session_path + test "new" do + get new_session_path - assert_response :success - end + assert_response :success + end - test "create with valid credentials" do - post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } + test "create with valid credentials" do + post session_path, params: { email_address: "david@37signals.com", password: "secret123456" } - assert_redirected_to root_path - assert cookies[:session_token].present? - end + assert_redirected_to root_path + assert cookies[:session_token].present? + end - test "create with invalid credentials" do - post session_path, params: { email_address: "david@37signals.com", password: "wrong" } + test "create with invalid credentials" do + post session_path, params: { email_address: "david@37signals.com", password: "wrong" } - assert_redirected_to new_session_path - assert_not cookies[:session_token].present? - end + assert_redirected_to new_session_path + assert_not cookies[:session_token].present? end end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 73c15ea65..d12c1a4b3 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -5,27 +5,18 @@ david: email_address: david@37signals.com password_digest: <%= digest %> role: member -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_david -<% end %> jz: name: JZ email_address: jz@37signals.com password_digest: <%= digest %> role: member -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_jzimdars -<% end %> kevin: name: Kevin email_address: kevin@37signals.com password_digest: <%= digest %> role: admin -<% if User.reflect_on_association :external_user %> - external_user: 37s_fizzy_kevin -<% end %> system: name: System diff --git a/test/models/account_test.rb b/test/models/account_test.rb index dbede16f2..78554b997 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -3,6 +3,6 @@ require "test_helper" class AccountTest < ActiveSupport::TestCase test "slug" do account = Account.sole - assert_equal "/#{account.tenant_id}", account.slug + assert_equal "/#{ApplicationRecord.current_tenant}", account.slug end end diff --git a/test/models/period_highlights_test.rb b/test/models/period_highlights_test.rb index cf7627201..5bcfca99a 100644 --- a/test/models/period_highlights_test.rb +++ b/test/models/period_highlights_test.rb @@ -1,30 +1,27 @@ require "test_helper" class PeriodHighlightTest < ActiveSupport::TestCase - # Skipping when locally authenticating because the VCR cassettes would need to be re-recorded. - unless Rails.application.config.x.local_authentication - include VcrTestHelper + include VcrTestHelper - setup do - @user = users(:david) + setup do + @user = users(:david) + end + + test "generate period highlights" do + period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do + PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) end - test "generate period highlights" do - period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do - PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - end + assert_match /logo/i, period_highlights.to_html + end - assert_match /logo/i, period_highlights.to_html + test "don't generate highlights for existing periods" do + new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) + + existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do + PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) end - test "don't generate highlights for existing periods" do - new_period_highlights = PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - - existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do - PeriodHighlights.create_or_find_for(@user.collections, starts_at: 1.month.ago, duration: 2.months) - end - - assert_equal new_period_highlights, existing_period_highlights - end + assert_equal new_period_highlights, existing_period_highlights end end diff --git a/test/models/user/highlights_test.rb b/test/models/user/highlights_test.rb index 0344d02b8..7123f3bc2 100644 --- a/test/models/user/highlights_test.rb +++ b/test/models/user/highlights_test.rb @@ -1,67 +1,64 @@ require "test_helper" class User::HighlightsTest < ActiveSupport::TestCase - # Skipping when locally authenticating because the VCR cassettes would need to be re-recorded. - unless Rails.application.config.x.local_authentication - include VcrTestHelper + include VcrTestHelper - setup do - @user = users(:david) - travel_to 1.week.ago + 2.days - Current.session = sessions(:david) - end + setup do + @user = users(:david) + travel_to 1.week.ago + 2.days + Current.session = sessions(:david) + end - test "generate weekly highlights" do - stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do - period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do - @user.generate_weekly_highlights - end - - assert_match /logo/i, period_highlights.to_html - end - end - - test "don't generate highlights for existing periods" do - stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do - new_period_highlights = @user.generate_weekly_highlights - assert_not_nil new_period_highlights - - existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do - @user.generate_weekly_highlights - end - - assert_equal new_period_highlights, existing_period_highlights - end - end - - test "periods respect user timezone for week boundaries" do - @user.settings.update!(timezone_name: "America/New_York") - - # Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC - sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC") - - # Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC - saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC") - - # Event on Saturday evening in NYC (but Sunday in UTC) - saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") } - - # Events throughout the week starting Sunday in NYC - 7.times do |i| - travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") } + test "generate weekly highlights" do + stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do + period_highlights = assert_difference -> { PeriodHighlights.count }, 1 do + @user.generate_weekly_highlights end - wednesday = sunday_in_nyc + 3.days - - # The period should start at Sunday in NYC timezone - period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday) - period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week) - - # The Saturday event should NOT be included (it's in the previous week in NYC time) - assert_not_includes period.events, saturday_event - - # Should include 7 events from the current week - assert_equal 7, period.events.count + assert_match /logo/i, period_highlights.to_html end end + + test "don't generate highlights for existing periods" do + stub_const(PeriodHighlights::Period, :MIN_EVENTS_TO_BE_INTERESTING, 3) do + new_period_highlights = @user.generate_weekly_highlights + assert_not_nil new_period_highlights + + existing_period_highlights = assert_no_difference -> { PeriodHighlights.count } do + @user.generate_weekly_highlights + end + + assert_equal new_period_highlights, existing_period_highlights + end + end + + test "periods respect user timezone for week boundaries" do + @user.settings.update!(timezone_name: "America/New_York") + + # Sunday Jan 7, 2024 at 2am EST (7am UTC) - this is Sunday in NYC + sunday_in_nyc = Time.zone.parse("2024-01-07 07:00:00 UTC") + + # Saturday Jan 6, 2024 at 11pm EST (Jan 7 4am UTC) - still Saturday in NYC but Sunday in UTC + saturday_in_nyc = Time.zone.parse("2024-01-07 04:00:00 UTC") + + # Event on Saturday evening in NYC (but Sunday in UTC) + saturday_event = travel_to(saturday_in_nyc) { cards(:logo).track_event("Saturday event") } + + # Events throughout the week starting Sunday in NYC + 7.times do |i| + travel_to(sunday_in_nyc + i.days) { cards(:logo).track_event("Event #{i}") } + end + + wednesday = sunday_in_nyc + 3.days + + # The period should start at Sunday in NYC timezone + period_start = wednesday.in_time_zone("America/New_York").beginning_of_week(:sunday) + period = PeriodHighlights::Period.new(@user.collections, starts_at: period_start, duration: 1.week) + + # The Saturday event should NOT be included (it's in the previous week in NYC time) + assert_not_includes period.events, saturday_event + + # Should include 7 events from the current week + assert_equal 7, period.events.count + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index a71063f10..fbb2b1149 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -65,6 +65,6 @@ RubyLLM.configure do |config| config.openai_api_key ||= "DUMMY-TEST-KEY" # Run tests with VCR without having to configure OpenAI API key locally. end -unless Rails.application.config.x.local_authentication +unless Rails.application.config.x.oss_config load File.expand_path("../gems/fizzy-saas/test/test_helper.rb", __dir__) end diff --git a/test/test_helpers/session_test_helper.rb b/test/test_helpers/session_test_helper.rb index b04b06962..e857da327 100644 --- a/test/test_helpers/session_test_helper.rb +++ b/test/test_helpers/session_test_helper.rb @@ -7,11 +7,7 @@ module SessionTestHelper cookies.delete :session_token user = users(user) unless user.is_a? User - if Rails.application.config.x.local_authentication - post session_path, params: { email_address: user.email_address, password: "secret123456" } - else - saas_extension_sign_in_as(user) - end + post session_path, params: { email_address: user.email_address, password: "secret123456" } cookie = cookies.get_cookie "session_token" assert_not_nil cookie, "Expected session_token cookie to be set after sign in"