diff --git a/README.md b/README.md index 3113236..b679ca5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Il s'agit d'un ensemble de scripts, de type "tests unitaires" permettant de valider le comportement d'un service web, du point de vue d'un client web. -## Installation +# Installation Au minimum il faut Ruby pour l'exécution des scripts. @@ -17,9 +17,9 @@ Une fois Ruby installé, depuis le dossier du projet : Ça va installer localement des paquets Ruby nécessaires. -## Exécution +# Exécution -### Partie "sécurité" : +## Partie "sécurité" : bundle exec ruby -Itest test/secrity_test.rb @@ -27,34 +27,94 @@ Il est possible d'utiliser un certificat SSL racine stocké en local (`test/cert Il est possible d'utiliser un binaire `open_ssl` personnalisé, géré dans la méthode `openssl_path`. -#### `test_certificate_level` +### `test_certificate_level` Permet de vérifier la conformité avec un des niveaux normalisés proposés par Mozilla (https://wiki.mozilla.org/Security/Server_Side_TLS) -#### `test_certificate` +### `test_certificate` Vérifie la validité du certificat -#### `test_accepts_tls_v1` +### `test_accepts_tls_v1` Vérifie que le serveur accepte bien des connexions TLS v1 -#### `test_refuse_ssl_v3` +### `test_refuse_ssl_v3` Vérifie que le serveur refuse bien des connexions SSL v3 -#### `test_hsts_header` +### `test_hsts_header` Vérifie la présence de l'en-tête HTTP `HTTP-Strict-Transport-Security`. -### Partie "redirections" : +## Partie "redirections" : bundle exec ruby -Itest test/domain_redirects_test.rb -#### `test_redirections` +### `test_redirections` Vérifie que les redirections attendues aient bien lieu. -#### `test_http_codes` +### `test_http_codes` Vérifie que les codes HTTP attendus soient bien reçus. + +## Partie "assets accessibility" + + bundle exec ruby -Itest test/assets_accessibility_test.rb + +### `test_rss_feeds` + +Vérifie la présence de flux RSS sur la page et leur fonctionnement. + +### `test_head_stylelsheets` + +Vérifie la présence de CSS, qu'elles sont bien accessibles, et que leurs en-têtes HTTP les rendent cachables. + +### `test_head_scripts` + +Vérifie la présence de JS, qu'ils sont bien accessibles, et que leurs en-têtes HTTP les rendent cachables. + +### `test_images` + +Vérifie que toutes images de la page sont bien accessibles et cachables. + +### `test_https_src` + +Vérifie que toutes les balises avec attribut `src` sont bien en HTTPS. + +### `test_cors` + +Vérifie les en-têtes CORS sur la feuille de style et toutes les polices web qu'elle référence. + +## Partie "cookies" + + bundle exec ruby -Itest test/cookies_test.rb + +`test_no_session_on_pages` + +Vérifie que certaines pages ne poussent pas de cookies. + +`test_session_on_pages` + +Vérifie que certaines pages poussent bien un cookie. + +## Partie "cache HTTP" + + bundle exec ruby -Itest test/http_cache_test.rb + +`test_varnish_hit` + +Vérifie que certaines pages sont bien servies puis mises en cache par Varnish. + +`test_homepage_first_visit` + +Vérifie que certaines pages ont des en-têtes corrects pour le cache public. + +`test_homepage_second_visit` + +Vérifie que certaines pages rechargées en transmettant les infos de cache reçoivent une réponse "304". + +`test_login_first_visit` + +Vérifie que certaines pages ne soient pas mises en cache. diff --git a/test/assets_accessibility_test.rb b/test/assets_accessibility_test.rb new file mode 100644 index 0000000..927549e --- /dev/null +++ b/test/assets_accessibility_test.rb @@ -0,0 +1,110 @@ +require 'test_helper' +require "mechanize" +require "nokogiri" + +class AssetsAccessibilityTest < Minitest::Test + include WebserverHelper + + def test_rss_feeds + on_home_page do |page, doc| + feeds = doc.search("//head/link[@type='application/rss+xml']") + + # Il y a au moins 1 flux RSS + assert feeds.size >= 1, "Expected to find an RSS feed" + + feeds.each do |feed| + url = feed["href"] + assert_status_ok agent.get(url), "for #{url}" + end + end + end + + def test_head_stylelsheets + on_home_page do |page, doc| + stylesheets = doc.search("//head/link[@rel='stylesheet']") + + # Il y a au moins 1 CSS "application" + assert stylesheets.any? { |stylesheet| + path = URI.parse(stylesheet["href"]).path + %r(\A/assets/application-\w+\.css\Z) =~ path + }, "Expected to find the application CSS" + + # Tous les CSS sont accessibles et correctement configurés + stylesheets.each do |stylesheet| + url = stylesheet["href"] + if internal_url?(url) + assert_cachable_asset agent.get(url), "for #{url}" + end + end + end + end + + def test_head_scripts + on_home_page do |page, doc| + scripts = doc.search("//head/script[@src]") + + # Il y a au moins 1 JS "application" + assert scripts.any? { |script| + path = URI.parse(script["src"]).path + %r|\A/assets/application-\w+\.js\Z| =~ path + }, "Expected to find the application JS" + + # Tous les scripts externes sont accessibles + scripts.each do |script| + url = script["src"] + if internal_url?(url) + assert_cachable_asset agent.get(url), "for #{url}" + end + end + end + end + + def test_images + on_home_page do |page, doc| + doc.search("body img").each do |image| + url = image["src"] + if internal_url?(url) + assert_cachable_asset agent.get(url), "for #{url}" + end + end + end + end + + def test_https_src + on_home_page do |page, doc| + doc.search("[src]").each do |element| + assert_scheme "https", element["src"], "on page #{page.uri}" + end + end + end + + def test_cors + on_home_page do |page, doc| + stylesheets = doc.search("//head/link[@rel='stylesheet']") + + # On récupère la feuille de style qui contient les appels aux polices web + vendor_stylesheet = stylesheets.detect { |stylesheet| + path = URI.parse(stylesheet["href"]).path + %r(\A/assets/vendor_bootstrap-\w+\.css\Z) =~ path + } + refute_nil vendor_stylesheet, "Expected to find a \"vendor_bootstrap\" CSS file" + + # On extrait toutes les URL de polices web + css_url = vendor_stylesheet["href"] + pattern = /url\((?:"|')?([^\)"']+)(?:"|')?\)/ + font_urls = agent.get(css_url).body.scan(pattern).flatten + + refute font_urls.empty?, "Expected to find webfonts in #{css_url}" + + # On vérifie que les en-têtes sont présents + origin = "https://www.example.com/" + font_urls.each do |font_url| + page = agent.get(font_url, [], nil, { "Origin" => origin }) + actual = header(page, "access-control-allow-origin") + + assert_equal origin, actual, "Expected CORS header to match the request origin for #{font_url}" + end + end + end + +end diff --git a/test/cookies_test.rb b/test/cookies_test.rb new file mode 100644 index 0000000..32fc8be --- /dev/null +++ b/test/cookies_test.rb @@ -0,0 +1,47 @@ +require 'test_helper' +require 'mechanize' + +class CookiesTest < Minitest::Test + include WebserverHelper + + def setup + @agent = Mechanize.new { |a| + a.follow_redirect = false + } + end + + def test_no_session_on_pages + [ + "/", + "/faq", + ].each do |path| + url = "https://#{domain}#{path}" + page = @agent.get(url) + + refute_has_session_cookie @agent, "_example_session", "for #{url}" + end + end + + def test_session_on_pages + [ + "/login", + "/store", + ].each do |path| + url = "https://#{domain}#{path}" + page = @agent.get(url) + + assert_has_session_cookie @agent, "_example_session", "for #{url}" + end + end + + def assert_has_session_cookie(agent, cookie_name, context = nil) + message = message_with_context("Expected to find a session cookie named '#{cookie_name}'", context) + assert_includes agent.cookie_jar.map(&:name), cookie_name, message + end + + def refute_has_session_cookie(agent, cookie_name, context = nil) + message = message_with_context("Expected to not find a session cookie named '#{cookie_name}'", context) + refute_includes agent.cookie_jar.map(&:name), cookie_name, message + end + +end diff --git a/test/http_cache_test.rb b/test/http_cache_test.rb new file mode 100644 index 0000000..a60bd00 --- /dev/null +++ b/test/http_cache_test.rb @@ -0,0 +1,93 @@ +require 'test_helper' +require "mechanize" + +class HTTPCacheTest < Minitest::Test + include WebserverHelper + + def setup + @agent = Mechanize.new { |a| + a.follow_redirect = false + } + end + + def test_varnish_hit + skip("Pas de test avec Varnish") if ENV["VARNISH"] == "0" + + [ + "https://#{domain}/", + ].each do |url| + # Pour assurer la mise en cache par Varnish + page = @agent.get(url) + + assert_has_header "x-varnish", page, "for #{url}" + assert_has_header "x-cache", page, "for #{url}" + + # On laisse un peu de temps au cache pour se réchauffer + sleep(0.1) + + # On refait la requête en espérant avoir un HIT + @agent.reset + page = @agent.get(url) + + assert_x_cache_hit page, "for #{url}" + end + end + + def test_homepage_first_visit + [ + "https://#{domain}/", + ].each do |url| + page = @agent.get(url) + + assert_status_ok page, "for #{url}" + assert_has_etag page, "for #{url}" + assert_has_last_modified page, "for #{url}" + # assert_max_age "300", page, "for #{url}" + # refute_must_revalidate page, "for #{url}" + assert_public page, "for #{url}" + end + end + + def test_homepage_second_visit + [ + "https://#{domain}/", + ].each do |url| + page1 = @agent.get(url) + assert_status_ok page1, "for #{url} on 1st visit" + + if last_modified = last_modified_header(page1) + # il faut reinitialiser l'agent pour vider le cache et l'historique + @agent.reset + page2a = @agent.get(url, [], nil, { + "If-Modified-Since" => last_modified + }) + assert_status_not_modified page2a, "for #{url} on 2nd visit with Last-Modified" + else + flunk "Expected to find a Last-Modified header for #{url} on 1st visit" + end + + if etag = etag_header(page1) + # il faut reinitialiser l'agent pour vider le cache et l'historique + @agent.reset + page2b = @agent.get(url, [], nil, { + "If-None-Match" => etag_header(page1) + }) + assert_status_not_modified page2b, "for #{url} on 2nd visit with ETag" + else + flunk "Expected to fin an ETag header for #{url} on 1st visit" + end + end + end + + def test_login_first_visit + [ + "https://#{domain}/login", + ].each do |url| + page = @agent.get(url) + + assert_max_age "0", page, "for #{url}" + assert_must_revalidate page, "for #{url}" + assert_private page, "for #{url}" + end + end +end