From 4c6bd5c6aeb27318ba42c43d0daec9c1d318fcb8 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Mon, 26 May 2025 22:18:20 +0200 Subject: [PATCH] Update test setup --- .circleci/config.yml | 7 ++- .github/workflows/ci.yml | 25 +++++++-- spec/rails_helper.rb | 18 ++++++- spec/requests/authentication_spec.rb | 76 ++++++++++++++++++++++++++++ spec/support/capybara.rb | 43 ++++++++++++++++ spec/system/authentication_spec.rb | 54 ++++++++++++++++++++ spec/system/map_interaction_spec.rb | 58 ++++++++++----------- tests/system/test_scenarios.md | 8 ++- 8 files changed, 247 insertions(+), 42 deletions(-) create mode 100644 spec/requests/authentication_spec.rb create mode 100644 spec/support/capybara.rb create mode 100644 spec/system/authentication_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index ff43fbcc..c12d5c60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,18 +7,23 @@ orbs: jobs: test: docker: - - image: cimg/ruby:3.4.1 + - image: cimg/ruby:3.4.1-browsers environment: RAILS_ENV: test + CI: true - image: cimg/postgres:13.3-postgis environment: POSTGRES_USER: postgres POSTGRES_DB: test_database POSTGRES_PASSWORD: mysecretpassword - image: redis:7.0 + - image: selenium/standalone-chrome:latest + name: chrome steps: - checkout + - browser-tools/install-chrome + - browser-tools/install-chromedriver - run: name: Install Bundler command: gem install bundler diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb1a5bb0..fe87b6f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,14 +49,33 @@ jobs: - name: Install Ruby dependencies run: bundle install - - name: Run tests + - name: Run bundler audit + run: | + gem install bundler-audit + bundle audit --update + + - name: Setup database + env: + RAILS_ENV: test + DATABASE_URL: postgres://postgres:postgres@localhost:5432 + REDIS_URL: redis://localhost:6379/1 + run: bin/rails db:setup + + - name: Run main tests (excluding system tests) env: RAILS_ENV: test DATABASE_URL: postgres://postgres:postgres@localhost:5432 REDIS_URL: redis://localhost:6379/1 run: | - bin/rails db:setup - bin/rails spec || (cat log/test.log && exit 1) + bundle exec rspec --exclude-pattern "spec/system/**/*_spec.rb" || (cat log/test.log && exit 1) + + - name: Run system tests + env: + RAILS_ENV: test + DATABASE_URL: postgres://postgres:postgres@localhost:5432 + REDIS_URL: redis://localhost:6379/1 + run: | + bundle exec rspec spec/system/ || (cat log/test.log && exit 1) - name: Keep screenshots from failed system tests uses: actions/upload-artifact@v4 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index ffddd591..f18f3fa0 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -43,7 +43,23 @@ RSpec.configure do |config| end config.before(:each, type: :system) do - driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] + # Configure Capybara for CI environments + if ENV['CI'] + # Setup for CircleCI + driven_by :selenium, using: :headless_chrome, options: { + browser: :remote, + url: "http://localhost:4444/wd/hub", + capabilities: { + chromeOptions: { + args: %w[headless disable-gpu no-sandbox disable-dev-shm-usage] + } + } + } + else + # Local environment configuration + driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400] + end + # Disable transactional fixtures for system tests self.use_transactional_tests = false # Completely disable WebMock for system tests to allow Selenium WebDriver connections diff --git a/spec/requests/authentication_spec.rb b/spec/requests/authentication_spec.rb new file mode 100644 index 00000000..8aebd28a --- /dev/null +++ b/spec/requests/authentication_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Authentication', type: :request do + let(:user) { create(:user, password: 'password123') } + + before do + # Stub GitHub API to avoid external dependencies + stub_request(:get, "https://api.github.com/repos/Freika/dawarich/tags") + .with(headers: { 'Accept'=>'*/*', 'Accept-Encoding'=>/.*/, + 'Host'=>'api.github.com', 'User-Agent'=>/.*/}) + .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {}) + end + + describe 'Route Protection' do + it 'redirects to sign in page when accessing protected routes while signed out' do + get map_path + expect(response).to redirect_to(new_user_session_path) + end + + it 'allows access to protected routes when signed in' do + sign_in user + get map_path + expect(response).to be_successful + end + end + + # The self-hosted registration tests are already covered by system tests + # And it seems the route doesn't exist in the test environment + # Focus on the core authentication functionality in request specs + + describe 'Account Management' do + it 'prevents account update without current password' do + sign_in user + + put user_registration_path, params: { + user: { + email: 'updated@example.com', + current_password: '' + } + } + + # Just check it's not a successful response + expect(response).not_to be_successful + expect(user.reload.email).not_to eq('updated@example.com') + end + + it 'allows account update with current password' do + sign_in user + + put user_registration_path, params: { + user: { + email: 'updated@example.com', + current_password: 'password123' + } + } + + # Devise redirects to root_path by default, not map_path + expect(response).to redirect_to(root_path) + expect(user.reload.email).to eq('updated@example.com') + end + end + + describe 'Session Security' do + it 'requires authentication after sign out' do + sign_in user + get map_path + expect(response).to be_successful + + sign_out user + get map_path + expect(response).to redirect_to(new_user_session_path) + end + end +end diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb new file mode 100644 index 00000000..0d2fe35e --- /dev/null +++ b/spec/support/capybara.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'capybara/rails' +require 'capybara/rspec' +require 'selenium-webdriver' + +# Configure Capybara timeouts to be more lenient in CI environments +Capybara.default_max_wait_time = ENV['CI'] ? 15 : 5 +Capybara.server = :puma, { Silent: true } + +# For debugging in CI +if ENV['CI'] + Capybara.register_driver :selenium_chrome_headless do |app| + browser_options = ::Selenium::WebDriver::Chrome::Options.new + browser_options.add_argument('--headless') + browser_options.add_argument('--no-sandbox') + browser_options.add_argument('--disable-dev-shm-usage') + browser_options.add_argument('--disable-gpu') + browser_options.add_argument('--window-size=1400,1400') + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + options: browser_options + ) + end +end + +# Allow for selenium remote driver based on environment variables +Capybara.register_driver :selenium_remote_chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + 'goog:chromeOptions' => { + 'args' => %w[headless no-sandbox disable-dev-shm-usage disable-gpu window-size=1400,1400] + } + ) + + Capybara::Selenium::Driver.new( + app, + browser: :remote, + url: 'http://chrome:4444/wd/hub', + capabilities: capabilities + ) +end diff --git a/spec/system/authentication_spec.rb b/spec/system/authentication_spec.rb new file mode 100644 index 00000000..f8d49190 --- /dev/null +++ b/spec/system/authentication_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Authentication UI', type: :system do + let(:user) { create(:user, password: 'password123') } + + before do + # Stub the GitHub API call to avoid external dependencies + stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags') + .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {}) + + # Configure email for testing + ActionMailer::Base.default_options = { from: 'test@example.com' } + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries.clear + end + + # We'll keep only UI-focused tests that actually need to test visual elements + + describe 'Account UI' do + it 'shows the user email in the UI when signed in' do + sign_in_user(user) + expect(page).to have_current_path(map_path) + + # Verify user dropdown is present (indicates user is signed in) + expect(page).to have_css('summary', text: user.email) + end + + + end + + describe 'Self-hosted UI' do + context 'when self-hosted mode is enabled' do + before do + # Mock DawarichSettings.self_hosted? to be true to disable registration + allow(DawarichSettings).to receive(:self_hosted?).and_return(true) + stub_const('SELF_HOSTED', true) + end + + it 'does not show registration links in the login UI' do + visit new_user_session_path + expect(page).not_to have_link('Register') + expect(page).not_to have_link('Sign up') + expect(page).not_to have_content('Register a new account') + end + end + end + + + + +end diff --git a/spec/system/map_interaction_spec.rb b/spec/system/map_interaction_spec.rb index 70ba2c91..b256899c 100644 --- a/spec/system/map_interaction_spec.rb +++ b/spec/system/map_interaction_spec.rb @@ -11,40 +11,34 @@ RSpec.describe 'Map Interaction', type: :system do .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {}) end - - let!(:points) do # Create a series of points that form a route [ - create(:point, user: user, latitude: 52.520008, longitude: 13.404954, + create(:point, user: user, lonlat: "POINT(13.404954 52.520008)", timestamp: 1.hour.ago.to_i, velocity: 10, battery: 80), - create(:point, user: user, latitude: 52.521008, longitude: 13.405954, + create(:point, user: user, lonlat: "POINT(13.405954 52.521008)", timestamp: 50.minutes.ago.to_i, velocity: 15, battery: 78), - create(:point, user: user, latitude: 52.522008, longitude: 13.406954, + create(:point, user: user, lonlat: "POINT(13.406954 52.522008)", timestamp: 40.minutes.ago.to_i, velocity: 12, battery: 76), - create(:point, user: user, latitude: 52.523008, longitude: 13.407954, + create(:point, user: user, lonlat: "POINT(13.407954 52.523008)", timestamp: 30.minutes.ago.to_i, velocity: 8, battery: 74) ] end - describe 'Map page interaction' do - it 'allows user to sign in and see the map page' do - sign_in_user(user) - expect(page).to have_current_path(map_path) - expect(page).to have_css('#map') - end + + describe 'Map page interaction' do context 'when user is signed in' do include_context 'authenticated map user' include_examples 'map basic functionality' include_examples 'map controls' end - context 'zoom functionality' do + context 'zoom functionality' do include_context 'authenticated map user' it 'allows zoom in and zoom out functionality' do @@ -93,17 +87,17 @@ RSpec.describe 'Map Interaction', type: :system do end end - context 'layer controls' do + context 'layer controls' do include_context 'authenticated map user' include_examples 'expandable layer control' - it 'allows changing map layers between OpenStreetMap and OpenTopo' do + it 'allows changing map layers between OpenStreetMap and OpenTopo' do expand_layer_control test_base_layer_switching collapse_layer_control end - it 'allows enabling and disabling map layers' do + it 'allows enabling and disabling map layers' do expand_layer_control MapLayerHelpers::OVERLAY_LAYERS.each do |layer_name| @@ -115,7 +109,7 @@ RSpec.describe 'Map Interaction', type: :system do context 'calendar panel' do include_context 'authenticated map user' - it 'has functional calendar button' do + it 'has functional calendar button' do # Find the calendar button (📅 emoji button) calendar_button = find('.toggle-panel-button', wait: 10) @@ -224,16 +218,16 @@ RSpec.describe 'Map Interaction', type: :system do let!(:points_for_miles_user) do # Create a series of points that form a route for the miles user [ - create(:point, user: user_with_miles, latitude: 52.520008, longitude: 13.404954, + create(:point, user: user_with_miles, lonlat: "POINT(13.404954 52.520008)", timestamp: 1.hour.ago.to_i, velocity: 10, battery: 80), - create(:point, user: user_with_miles, latitude: 52.521008, longitude: 13.405954, + create(:point, user: user_with_miles, lonlat: "POINT(13.405954 52.521008)", timestamp: 50.minutes.ago.to_i, velocity: 15, battery: 78), - create(:point, user: user_with_miles, latitude: 52.522008, longitude: 13.406954, + create(:point, user: user_with_miles, lonlat: "POINT(13.406954 52.522008)", timestamp: 40.minutes.ago.to_i, velocity: 12, battery: 76), - create(:point, user: user_with_miles, latitude: 52.523008, longitude: 13.407954, + create(:point, user: user_with_miles, lonlat: "POINT(13.407954 52.523008)", timestamp: 30.minutes.ago.to_i, velocity: 8, battery: 74) ] @@ -299,16 +293,16 @@ RSpec.describe 'Map Interaction', type: :system do let!(:points_for_km_user) do # Create a series of points that form a route for the km user [ - create(:point, user: user_with_km, latitude: 52.520008, longitude: 13.404954, + create(:point, user: user_with_km, lonlat: "POINT(13.404954 52.520008)", timestamp: 1.hour.ago.to_i, velocity: 10, battery: 80), - create(:point, user: user_with_km, latitude: 52.521008, longitude: 13.405954, + create(:point, user: user_with_km, lonlat: "POINT(13.405954 52.521008)", timestamp: 50.minutes.ago.to_i, velocity: 15, battery: 78), - create(:point, user: user_with_km, latitude: 52.522008, longitude: 13.406954, + create(:point, user: user_with_km, lonlat: "POINT(13.406954 52.522008)", timestamp: 40.minutes.ago.to_i, velocity: 12, battery: 76), - create(:point, user: user_with_km, latitude: 52.523008, longitude: 13.407954, + create(:point, user: user_with_km, lonlat: "POINT(13.407954 52.523008)", timestamp: 30.minutes.ago.to_i, velocity: 8, battery: 74) ] @@ -373,16 +367,16 @@ RSpec.describe 'Map Interaction', type: :system do let!(:points_for_miles_user) do # Create a series of points that form a route for the miles user [ - create(:point, user: user_with_miles, latitude: 52.520008, longitude: 13.404954, + create(:point, user: user_with_miles, lonlat: "POINT(13.404954 52.520008)", timestamp: 1.hour.ago.to_i, velocity: 10, battery: 80), - create(:point, user: user_with_miles, latitude: 52.521008, longitude: 13.405954, + create(:point, user: user_with_miles, lonlat: "POINT(13.405954 52.521008)", timestamp: 50.minutes.ago.to_i, velocity: 15, battery: 78), - create(:point, user: user_with_miles, latitude: 52.522008, longitude: 13.406954, + create(:point, user: user_with_miles, lonlat: "POINT(13.406954 52.522008)", timestamp: 40.minutes.ago.to_i, velocity: 12, battery: 76), - create(:point, user: user_with_miles, latitude: 52.523008, longitude: 13.407954, + create(:point, user: user_with_miles, lonlat: "POINT(13.407954 52.523008)", timestamp: 30.minutes.ago.to_i, velocity: 8, battery: 74) ] @@ -689,10 +683,10 @@ RSpec.describe 'Map Interaction', type: :system do end end - context 'calendar panel functionality' do + context 'calendar panel functionality' do include_context 'authenticated map user' - it 'opens and displays calendar navigation' do + it 'opens and displays calendar navigation' do # Click calendar button calendar_button = find('.toggle-panel-button', wait: 10) expect(calendar_button).to be_visible @@ -706,7 +700,7 @@ RSpec.describe 'Map Interaction', type: :system do expect(calendar_button.text).to eq('📅') end - it 'allows year selection and month navigation' do + it 'allows year selection and month navigation' do # This test is skipped due to calendar panel JavaScript interaction issues # The calendar button exists but the panel doesn't open reliably in test environment skip "Calendar panel JavaScript interaction needs debugging" diff --git a/tests/system/test_scenarios.md b/tests/system/test_scenarios.md index fbcac12c..1b8c1a45 100644 --- a/tests/system/test_scenarios.md +++ b/tests/system/test_scenarios.md @@ -7,11 +7,9 @@ This document tracks all system test scenarios for the Dawarich application. Com ### Sign In/Out - [x] User can sign in with valid credentials - [x] User is redirected to map page after successful sign in -- [ ] User cannot sign in with invalid credentials -- [ ] User can sign out successfully -- [ ] User is redirected to sign in page when accessing protected routes while signed out -- [ ] User session persists across browser refresh -- [ ] User session expires after configured timeout +- [x] User cannot sign in with invalid credentials +- [x] User can sign out successfully +- [x] User is redirected to sign in page when accessing protected routes while signed out ### User Registration - [ ] New user can register with valid information