diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 4293bfc..845b1e9 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -33,25 +33,29 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{matrix.ruby}} - bundler-cache: true - - - name: Installing dependencies (ubuntu) + - name: Installing dependencies if: matrix.os == 'ubuntu' run: | sudo systemctl stop mysql sudo apt-get install libmariadb-dev + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + - name: Setup database run: | VARIANT=testing bundle exec bake migrate - - name: Run tests - timeout-minutes: 5 - run: ${{matrix.env}} bundle exec rspec - + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + + - name: Run tests... + run: bundle exec sus + deploy: needs: test runs-on: ubuntu-latest @@ -59,7 +63,6 @@ jobs: steps: - uses: actions/checkout@v1 - - name: Push to remote system env: DEPLOY_KEY: ${{secrets.deploy_key}} diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 57680af..0000000 --- a/Guardfile +++ /dev/null @@ -1,29 +0,0 @@ - -group :development do - guard :falcon, port: 9292 do - watch('Gemfile.lock') - watch('config.ru') - watch(%r{^config|lib|pages/.*}) - - notification :off - end -end - -group :test do - guard :rspec, cmd: 'rspec' do - # Notifications can get a bit tedious: - # notification :off - - # Re-run specs if they are changed: - watch(%r{^spec/.+_spec\.rb$}) - watch('spec/spec_helper.rb') {'spec'} - - # Run relevent specs if files in `lib/` or `pages/` are changed: - watch(%r{^lib/(.+)\.rb$}) {|match| "spec/lib/#{match[1]}_spec.rb" } - watch(%r{^pages/(.+)\.(rb|xnode)$}) {|match| "spec/pages/#{match[1]}_spec.rb"} - watch(%r{^pages/(.+)controller\.rb$}) {|match| Dir.glob("spec/pages/#{match[1]}*_spec.rb")} - - # If any files in pages changes, ensure the website still works: - watch(%r{^pages/.*}) {'spec/website_spec.rb'} - end -end diff --git a/config/sus.rb b/config/sus.rb new file mode 100644 index 0000000..301c3a3 --- /dev/null +++ b/config/sus.rb @@ -0,0 +1,5 @@ + +require 'variant' +Variant.force!(:testing) + +require_relative '../db/environment' diff --git a/fixtures/website_context.rb b/fixtures/website_context.rb new file mode 100644 index 0000000..c0fc06e --- /dev/null +++ b/fixtures/website_context.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rack/test' +require 'protocol/rack/adapter' +require 'protocol/http/reference' + +require 'sus/fixtures/async/http/server_context' +require 'sus/fixtures/async/webdriver/session_context' + +require 'falcon/middleware/verbose' + +module WebsiteContext + include Sus::Fixtures::Async::HTTP::ServerContext + include Sus::Fixtures::Async::WebDriver::SessionContext + + def app + Rack::Builder.parse_file( + File.expand_path('../config.ru', __dir__) + ) + end + + def middleware + Falcon::Middleware::Verbose.new( + Protocol::Rack::Adapter.new(app) + ) + end +end diff --git a/gems.locked b/gems.locked index 1ca179d..4cd1772 100644 --- a/gems.locked +++ b/gems.locked @@ -20,6 +20,8 @@ GEM console (~> 1.26) fiber-annotation io-event (~> 1.6, >= 1.6.5) + async-actor (0.1.1) + async (>= 1) async-await (0.7.0) async async-container (0.18.3) @@ -41,16 +43,24 @@ GEM async (>= 1.25) metrics traces - async-rspec (1.17.0) - rspec (~> 3.0) - rspec-files (~> 1.0) - rspec-memory (~> 1.0) async-service (0.12.0) async async-container (~> 0.16) + async-webdriver (0.6.0) + async-actor (~> 0.1) + async-http (~> 0.61) + async-pool (~> 0.4) + async-websocket (~> 0.25) + base64 (~> 0.2) + async-websocket (0.30.0) + async-http (~> 0.76) + protocol-http (~> 0.34) + protocol-rack (~> 0.7) + protocol-websocket (~> 0.17) bake (0.21.0) bigdecimal samovar (~> 2.1) + base64 (0.2.0) benchmark-http (0.17.0) async-await async-http (~> 0.54) @@ -60,7 +70,6 @@ GEM xrb-sanitize bigdecimal (3.1.8) build-files (1.9.0) - coderay (1.1.3) concurrent-ruby (1.3.4) console (1.27.0) fiber-annotation @@ -77,7 +86,6 @@ GEM async-pool bigdecimal ffi-module (~> 0.3.0) - diff-lcs (1.5.1) falcon (0.48.0) async async-container (~> 0.18) @@ -99,44 +107,19 @@ GEM fiber-local (1.1.0) fiber-storage fiber-storage (1.0.0) - formatador (1.1.0) - guard (2.18.1) - formatador (>= 0.2.4) - listen (>= 2.7, < 4.0) - lumberjack (>= 1.0.12, < 2.0) - nenv (~> 0.1) - notiffany (~> 0.0) - pry (>= 0.13.0) - shellany (~> 0.0) - thor (>= 0.18.1) - guard-compat (1.2.1) - guard-falcon (0.14.0) - console (~> 1.0) - falcon (~> 0.35) - guard - guard-compat (~> 1.2) - guard-rspec (4.7.3) - guard (~> 2.1) - guard-compat (~> 1.1) - rspec (>= 2.99.0, < 4.0) http-accept (2.2.1) io-endpoint (0.13.1) io-event (1.6.5) io-stream (0.4.1) json (2.7.2) latinum (1.8.0) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) localhost (1.3.1) - lumberjack (1.2.10) mail (2.8.1) mini_mime (>= 0.1.1) net-imap net-pop net-smtp mapping (1.1.1) - method_source (1.1.0) metrics (0.10.2) migrate (0.3.1) build-files (~> 1.7) @@ -145,7 +128,6 @@ GEM mime-types-data (3.2024.0903) mini_mime (1.1.5) msgpack (1.7.2) - nenv (0.3.0) net-imap (0.4.16) date net-protocol @@ -155,13 +137,6 @@ GEM timeout net-smtp (0.5.0) net-protocol - nokogiri (1.16.7-arm64-darwin) - racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) - racc (~> 1.4) - notiffany (0.1.3) - nenv (~> 0.1) - shellany (~> 0.0) openssl (3.2.0) process-metrics (0.3.0) console (~> 1.8) @@ -177,44 +152,25 @@ GEM protocol-rack (0.8.0) protocol-http (~> 0.27) rack (>= 1.0) - pry (0.14.2) - coderay (~> 1.1) - method_source (~> 1.0) - racc (1.8.1) + protocol-websocket (0.17.0) + protocol-http (~> 0.2) rack (3.1.7) rack-test (2.1.0) rack (>= 1.3) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) rexml (3.3.7) - rspec (3.13.0) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.1) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-files (1.1.3) - rspec (~> 3.0) - rspec-memory (1.0.4) - rspec (~> 3.0) - rspec-mocks (3.13.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.1) - rubyzip (2.3.2) samovar (2.3.0) console (~> 1.0) mapping (~> 1.0) - selenium-webdriver (4.10.0) - rexml (~> 3.2, >= 3.2.5) - rubyzip (>= 1.2.2, < 3.0) - websocket (~> 1.0) - shellany (0.0.1) - thor (1.3.2) + sus (0.31.0) + sus-fixtures-async (0.2.0) + async + sus (~> 0.10) + sus-fixtures-async-http (0.9.0) + async-http (~> 0.54) + sus (~> 0.31) + sus-fixtures-async (~> 0.1) + sus-fixtures-async-webdriver (0.2.0) + async-webdriver (~> 0.3) thread-local (1.1.0) timeout (0.4.1) traces (0.13.1) @@ -238,11 +194,6 @@ GEM xrb (~> 0.4) variant (0.1.1) thread-local - webdrivers (5.3.1) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0, < 4.11) - websocket (1.2.11) xrb (0.6.1) xrb-sanitize (0.7.1) xrb (~> 0.3) @@ -252,7 +203,6 @@ PLATFORMS x86_64-linux DEPENDENCIES - async-rspec bake benchmark-http bundler @@ -262,18 +212,17 @@ DEPENDENCIES db-migrate! db-model! falcon - guard-falcon - guard-rspec latinum (~> 1.0) mapping (~> 1.0) migrate rack-test rexml - rspec + sus + sus-fixtures-async-http + sus-fixtures-async-webdriver (~> 0.2) trenni-formatters (~> 2.0) utopia (~> 2.17) variant - webdrivers BUNDLED WITH 2.5.16 diff --git a/gems.rb b/gems.rb index 2cc6ca0..84c930f 100644 --- a/gems.rb +++ b/gems.rb @@ -25,18 +25,16 @@ gem 'bundler' gem 'rack-test' -group :development do - gem 'guard-falcon', require: false - gem 'guard-rspec', require: false +group :test do + gem "sus" + gem 'covered' + + gem "sus-fixtures-async-http" + gem "sus-fixtures-async-webdriver", "~> 0.2" - gem 'webdrivers' # Fixed in latest selenium : gem 'rexml' - gem 'rspec' - gem 'covered' - - gem 'async-rspec' gem 'benchmark-http' end diff --git a/spec/pages/login_spec.rb b/spec/pages/login_spec.rb deleted file mode 100644 index bada548..0000000 --- a/spec/pages/login_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ - -require_relative '../website_context' - -RSpec.describe "pages/login", timeout: false do - include_context "browser" - - let(:password) {"secure123"} - - before do - VMail.schema do |schema| - schema.clear! - - @domain = schema.domains.create(name: 'localhost') - - @account = @domain.accounts.new(local_part: 'test', name: 'Test', is_enabled: true, is_admin: true) - @account.password_plaintext = password - @account.save - end - end - - it "can log in" do - visit "/login" - - form = find_form - form.find_element(name: 'email').send_keys(@account.email_address) - form.find_element(name: 'password').send_keys(password) - form.submit - - expect(body.text).to include('Accounts') - expect(body.text).to include('test@localhost') - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index b14f0bc..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -require 'covered/rspec' -require 'async/rspec' -require 'variant' - -Variant.force!(:testing) - -require_relative '../db/environment' - -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end diff --git a/spec/website_context.rb b/spec/website_context.rb deleted file mode 100644 index 4291bf8..0000000 --- a/spec/website_context.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'rack/test' -require 'async/rspec/reactor' - -RSpec.shared_context "website" do - include_context Async::RSpec::Reactor - include Rack::Test::Methods - - let(:app) do - Rack::Builder.parse_file( - File.expand_path('../config.ru', __dir__) - ) - end - - before(:all) do - require 'falcon/server' - require 'falcon/endpoint' - require 'async/io/shared_endpoint' - end - - let(:endpoint) {Falcon::Endpoint.parse("https://localhost:9294")} - - before do - @bound_endpoint = Async::IO::SharedEndpoint.bound(endpoint, close_on_exec: true) - - @server_task = reactor.async do - middleware = Falcon::Server.middleware(app) - - server = Falcon::Server.new(middleware, @bound_endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme) - - server.run - end - end - - after do - @server_task.stop - @bound_endpoint.close - end -end - -RSpec.shared_context "browser" do - include_context "website" - - before(:all) do - require 'webdrivers' - require 'selenium/webdriver' - end - - before do - options = Selenium::WebDriver::Chrome::Options.new - options.add_argument('--allow-insecure-localhost') - options.add_argument('--headless') - options.add_argument('--disable-gpu') - - @driver = Selenium::WebDriver.for(:chrome, options: options) - end - - after do - @driver&.quit - end - - def visit(path) - uri = URI(endpoint.url) + path - @driver.navigate.to(uri) - end - - def find_form(**options) - if options.empty? - options = {tag_name: "form"} - end - - return @driver.find_element(**options) - end - - def body - @driver.find_element(tag_name: "body") - end -end - -RSpec.shared_examples_for "valid page" do |path| - include_context "website" - - it "can access #{path}" do - get path - - while last_response.redirect? - follow_redirect! - end - - expect(last_response.status).to be == 200 - end -end diff --git a/spec/website_spec.rb b/spec/website_spec.rb deleted file mode 100644 index e19b6d3..0000000 --- a/spec/website_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require_relative 'website_context' - -# Learn about best practice specs from http://betterspecs.org -RSpec.describe "website", timeout: 120 do - include_context "website" - - before(:all) do - require 'benchmark/http/spider' - end - - let(:spider) {Benchmark::HTTP::Spider.new(depth: 128)} - let(:statistics) {Benchmark::HTTP::Statistics.new} - - it "should be responsive" do - Async::HTTP::Client.open(endpoint, connection_limit: 8) do |client| - spider.fetch(statistics, client, endpoint.url) do |method, uri, response| - if response.failure? - Async.logger.error{"#{method} #{uri} -> #{response.status}"} - end - end.wait - end - - statistics.print - - expect(statistics.samples).to be_any - expect(statistics.failed).to be_zero - end -end diff --git a/test/pages/login.rb b/test/pages/login.rb new file mode 100644 index 0000000..c038ad2 --- /dev/null +++ b/test/pages/login.rb @@ -0,0 +1,31 @@ + +require 'website_context' + +describe "pages/login" do + include WebsiteContext + + let(:password) {"secure123"} + + before do + VMail.schema do |schema| + schema.clear! + + @domain = schema.domains.create(name: 'localhost') + + @account = @domain.accounts.new(local_part: 'test', name: 'Test', is_enabled: true, is_admin: true) + @account.password_plaintext = password + @account.save + end + end + + it "can log in" do + navigate_to "/login" + + session.fill_in "email", @account.email_address + session.fill_in "password", password + + session.click_button "Login" + rescue => error + pp error: error.inspect + end +end diff --git a/spec/lib/vmail/password_reset_spec.rb b/test/vmail/password_reset_spec.rb similarity index 96% rename from spec/lib/vmail/password_reset_spec.rb rename to test/vmail/password_reset_spec.rb index e025835..b9cc755 100644 --- a/spec/lib/vmail/password_reset_spec.rb +++ b/test/vmail/password_reset_spec.rb @@ -2,7 +2,7 @@ require 'website_context' require 'vmail' -RSpec.describe VMail::PasswordReset do +describe VMail::PasswordReset do include_context "website" let(:new_password) {"Hello World"} diff --git a/test/website.rb b/test/website.rb new file mode 100644 index 0000000..1dc2717 --- /dev/null +++ b/test/website.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'website_context' +require 'benchmark/http/spider' + +describe "website" do + include WebsiteContext + + let(:spider) {Benchmark::HTTP::Spider.new(depth: 128)} + let(:statistics) {Benchmark::HTTP::Statistics.new} + + it "should be responsive" do + spider.fetch(statistics, client, endpoint.url) do |method, uri, response| + if response.failure? + Console.error(self) {"#{method} #{uri} -> #{response.status}"} + end + end.wait + + inform(statistics.print) + + expect(statistics.samples).to be(:any?) + expect(statistics.failed).to be(:zero?) + end + + with "interactive session" do + include WebDriverContext + + it "can visit the front page" do + visit "/" + + elements = find_elements(xpath: "//*[contains(text(), 'Seamless Scalability for Ruby')]") + expect(elements).to be(:any?) + end + end +end