From ec03adb97947181423d6e3efc9f2bec0fb069966 Mon Sep 17 00:00:00 2001 From: Peter Baranyi Date: Sun, 1 Mar 2026 22:42:13 +0100 Subject: [PATCH 1/9] add e2e tests to GitHub actions --- .github/workflows/action.yml | 45 +++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 8c9134b..f9ee537 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -1,4 +1,4 @@ -name: Mailnesia Perl unittests +name: Mailnesia Perl tests on: push: @@ -7,27 +7,30 @@ on: branches: [master] jobs: - build: + test: runs-on: ubuntu-latest steps: - - name: checkout code + - name: Checkout code uses: actions/checkout@v4 - - name: set up Perl + + - name: Set up Perl uses: shogo82148/actions-setup-perl@v1 with: perl-version: "5.32" - - name: print working directory - run: pwd - - name: install dependencies + - name: Install dependencies run: cpanm --installdeps . --skip-satisfied - - name: create tables in postgres + shell: bash + + - name: Create tables in postgres run: bash ./tools/psql-create-tables.sh env: postgres_host: localhost postgres_user: mailnesia PGPASSWORD: test - - name: execute unit/function tests + shell: bash + + - name: Execute unit / function tests run: prove -lv t env: postgres_host: localhost @@ -35,14 +38,34 @@ jobs: postgres_password: test redis_host: localhost + - name: Execute e2e tests + uses: MohamedRaslan/background_run_and_test@v1 + with: + start: | + morbo --listen http://*:3000 ./script/website-pages.pl + morbo --listen http://*:3001 ./script/website.pl + morbo --listen http://*:3002 ./script/api.pl + morbo --listen http://*:3003 ./script/rss.pl + perl ./script/clicker.pl + + wait-on: | + http://localhost:3000 + http://localhost:3001/mailbox/test + http://localhost:3002/api/mailbox/test + http://localhost:3003/rss/test + command: perl tools/test-mailnesia.pl + env: + postgres_host: localhost + postgres_user: mailnesia + postgres_password: test + redis_host: localhost + # Service containers to start services: # Label used to access the service container redis: # Docker Hub image image: redis - # TODO: password? - # ports: # Opens tcp port 6379 on the host and service container - 6379:6379 From 6f5b197d9035ef5425c37e6edde08d08bfa77ae5 Mon Sep 17 00:00:00 2001 From: Peter Baranyi Date: Mon, 2 Mar 2026 00:14:18 +0100 Subject: [PATCH 2/9] add FindBin to clicker This way it works without setting PERL5LIB=lib, as it can find ./lib automatically. --- script/clicker.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/script/clicker.pl b/script/clicker.pl index 423be0c..7e84019 100644 --- a/script/clicker.pl +++ b/script/clicker.pl @@ -2,6 +2,8 @@ # ZeroMQ version used is 4.2.1 +use FindBin; +use lib "$FindBin::Bin/../lib/"; use ZMQ::FFI qw(ZMQ_PUSH ZMQ_PULL); use AnyEvent; use EV; From 858ddab5e9f110c0582cbe7e31e66395634efe3e Mon Sep 17 00:00:00 2001 From: Peter Baranyi Date: Mon, 2 Mar 2026 23:17:29 +0100 Subject: [PATCH 3/9] remove unused Redis from e2e test --- tools/test-mailnesia.pl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/test-mailnesia.pl b/tools/test-mailnesia.pl index 0bda077..573d42d 100755 --- a/tools/test-mailnesia.pl +++ b/tools/test-mailnesia.pl @@ -7,7 +7,6 @@ use HTML::Lint::Pluggable; use DBI; use XML::LibXML; -use Redis; use IO::Socket qw(AF_INET); use FindBin; @@ -46,11 +45,6 @@ my $parser = XML::LibXML->new(); -my $redis = Redis->new( - encoding => undef, - sock => '/var/run/redis/redis.sock' - ); - my $mailbox_to_ban = 'ban-this-mailbox-as-a-test'; # tests: From 64e66278af713579b3da183b861437ca80a84821 Mon Sep 17 00:00:00 2001 From: Peter Baranyi Date: Sun, 22 Mar 2026 23:26:46 +0100 Subject: [PATCH 4/9] add app specific base urls to the e2e test This way the test can be executed without Nginx. TODO: main::visitor_test --- tools/test-mailnesia.pl | 171 +++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 81 deletions(-) diff --git a/tools/test-mailnesia.pl b/tools/test-mailnesia.pl index 573d42d..b3ff941 100755 --- a/tools/test-mailnesia.pl +++ b/tools/test-mailnesia.pl @@ -28,6 +28,12 @@ my $sender_domain = q{gmail.com}; my $project_directory = $mailnesia->get_project_directory(); my $baseurl = $mailnesia->{devel} ? "http://" . $config->{siteurl_devel} : "http://" . $config->{siteurl}; +# base URLs per service, can be set to a different value than $baseurl for testing without Nginx: +my $website_baseurl = $ENV{website_baseurl} || $baseurl; +my $website_pages_baseurl = $ENV{website_pages_baseurl} || $baseurl; +my $api_baseurl = $ENV{api_baseurl} || $baseurl; +my $rss_baseurl = $ENV{rss_baseurl} || $baseurl; + # language pages to test: my @languages = qw!/ /hu /it /lv /fi /pt /de /ru /pl /zh /fr /es /cs /es-ar /ms /id /pt-br!; @@ -96,12 +102,12 @@ sub webpage_tests { for (@languages) { - $url = $baseurl.$_; + $url = $website_pages_baseurl.$_; print_testcase_header($category . " " . $_); $numof_tests = webpage_tests_internal($url); - $url = $baseurl.$_."/features.html"; + $url = $website_pages_baseurl.$_."/features.html"; $numof_tests += webpage_tests_internal($url); } return scalar @languages * $numof_tests; @@ -139,7 +145,7 @@ sub mailbox_settings_page_tests { my $mailbox_url_encoded = $mailnesia->get_url_encoded_mailbox ( $mailbox ); my $tests = 0; - $url = $baseurl. "/settings/$mailbox_url_encoded"; + $url = $website_baseurl. "/settings/$mailbox_url_encoded"; if ( $mech->get_ok( $url, "GET $url" ) ) { $mech->text_contains( qq{Welcome to the preferences page of mailbox $mailbox_lowercase!}, "page contains the mailbox name"); @@ -170,7 +176,7 @@ sub mailbox_tests { my $mailbox_url_encoded = $mailnesia->get_url_encoded_mailbox ( $mailbox ); my $tests = 0; - $url = $baseurl. "/mailbox/$mailbox_url_encoded"; + $url = $website_baseurl. "/mailbox/$mailbox_url_encoded"; $mech->get_ok( $url, "GET $url" ); $tests += 1 + check_mailbox_header(); @@ -187,7 +193,7 @@ sub mailbox_tests { $tests += 1 + check_mailbox_header(); my $valid_part = "wfef8yudl8sylisgyhsldigalf8e"; my $invalid_part = ",1"; - $url = $baseurl . "/mailbox/" . $valid_part . $invalid_part; + $url = $website_baseurl . "/mailbox/" . $valid_part . $invalid_part; if ( $mech->get_ok( $url, "test invalid mailbox: $url" ) ) { @@ -219,7 +225,7 @@ sub alias_negative_tests { $alias_fail = $mailnesia->random_name_for_testing(); # try to set an alias for an alias - $mech->post("$baseurl/settings/$_/alias/set", + $mech->post("$website_baseurl/settings/$_/alias/set", { alias=>$alias_fail } @@ -239,7 +245,7 @@ sub alias_negative_tests { #try to set an alias that is already set - $mech->post("$baseurl/settings/$alias_fail/alias/set", + $mech->post("$website_baseurl/settings/$alias_fail/alias/set", { alias=>$_ } @@ -262,7 +268,7 @@ sub alias_negative_tests { if (@aliases) { #try to set an alias that is a mailbox (has alias) - $mech->post("$baseurl/settings/$alias_fail/alias/set", + $mech->post("$website_baseurl/settings/$alias_fail/alias/set", { alias=>$global_mailbox } @@ -298,9 +304,9 @@ sub alias_positive_tests { my $mailbox_url_encoded = $mailnesia->get_url_encoded_mailbox ( lc $global_mailbox ); my $alias_url_encoded; - $url = "$baseurl/mailbox/$mailbox_url_encoded" ; + $url = "$website_baseurl/mailbox/$mailbox_url_encoded" ; $mech->get_ok( $url, "GET $url" ); - $url = "$baseurl/settings/$mailbox_url_encoded"; + $url = "$website_baseurl/settings/$mailbox_url_encoded"; if ($mech->follow_link_ok( { url => "/settings/$mailbox_url_encoded" }, "follow settings link" )) { @@ -331,7 +337,7 @@ sub alias_positive_tests { { my $alias = $mailnesia->random_name_for_testing(); - if ( $mech->post_ok("$baseurl/settings/$mailbox_url_encoded/alias/set", + if ( $mech->post_ok("$website_baseurl/settings/$mailbox_url_encoded/alias/set", { alias=>$alias }, @@ -374,7 +380,7 @@ sub alias_positive_tests { #test the first alias $alias_url_encoded = $mailnesia->get_url_encoded_mailbox ( $aliases[0] ); - $url = $baseurl. "/mailbox/$alias_url_encoded" ; + $url = $website_baseurl. "/mailbox/$alias_url_encoded" ; $mech->get_ok( $url, "open alias $aliases[0] at $url" ); $mech->content_lacks( '
get_url_encoded_mailbox ( $mailbox ); + my $mailbox = lc shift; + my $tests = 0; + my $url = "/rss/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); + + my $link_found = $mech->find_link( url => $url ); + ok ( $link_found, "follow RSS link on current page: $url" ); - if ( $mech->follow_link_ok( {url_abs => $url}, "follow RSS link on current page: $url" ) ) - { + if ( $link_found ) { + $mech->get_ok( $rss_baseurl . $url, "GET $rss_baseurl$url" ); - my $content_type = $mech->response()->header( 'Content-Type' ); - is ( $content_type, 'application/xml', "Content-Type is application/xml"); + my $content_type = $mech->response()->header( 'Content-Type' ); + is ( $content_type, 'application/xml', "Content-Type is application/xml"); - eval { - my $parser = XML::LibXML->load_xml - ( - string => $mech->content - ); - }; + eval { + my $parser = XML::LibXML->load_xml + ( + string => $mech->content + ); + }; - ok (! $@, "RSS valid") ; - my $url_encoded_mailbox = $mailnesia->get_url_encoded_mailbox($mailbox); - $mech->content_contains ('' . lc $mailbox, "RSS title contains " . lc $mailbox); - $mech->content_contains ("<link>$baseurl/mailbox/" . $url_encoded_mailbox, "RSS link contains " . $url_encoded_mailbox); + ok (! $@, "RSS valid") ; + my $url_encoded_mailbox = $mailnesia->get_url_encoded_mailbox($mailbox); + $mech->content_contains ('<title>' . lc $mailbox, "RSS title contains " . lc $mailbox); + $mech->content_contains ("<link>$baseurl/mailbox/" . $url_encoded_mailbox, "RSS link contains " . $url_encoded_mailbox); - $mech->back(); # going back to page so next test can operate on current page - $tests = 4; + $mech->back(); # going back to page so next test can operate on current page + $tests = 5; - } + } - return $tests + 1; + return $tests + 1; } @@ -444,7 +453,7 @@ =head1 check if rss request is forbidden sub rss_forbidden_tests { my $mailbox = shift; print "Checking forbidden RSS for $mailbox\n"; - my $url = $baseurl . "/rss/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); + my $url = $rss_baseurl . "/rss/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); ok ( $mech->get( $url ), "GET $url" ); is ( $mech->status(), 403, "Status is 403 Forbidden"); @@ -465,7 +474,7 @@ =head1 check if API request for mailbox is forbidden sub api_forbidden_tests { my $mailbox = shift; print "Checking forbidden API request for $mailbox\n"; - my $url = $baseurl . "/api/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); + my $url = $api_baseurl . "/api/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); ok ( $mech->get( $url ), "GET $url" ); is ( $mech->status(), 403, "Status is 403 Forbidden"); @@ -591,7 +600,7 @@ sub negative_delete_test { print_test_category_header( ); my $mailbox = $mailnesia->random_name_for_testing(); my $id = int(rand(1_000_000)); - my $url = "$baseurl/mailbox/" . lc $mailbox . "/$id"; + my $url = "$website_baseurl/mailbox/" . lc $mailbox . "/$id"; $mech->post( $url, {delete => 1} ); @@ -610,17 +619,17 @@ sub visitor_test { my $visitor_list = $config->get_formatted_visitor_list($mailbox); is(scalar @$visitor_list, 0, 'visitor list should be empty'); - my $url = "$baseurl/mailbox/" . $mailbox; - $mech->get($url); + my $url = "$website_baseurl/mailbox/" . $mailbox; + $mech->get_ok($url); $visitor_list = $config->get_formatted_visitor_list($mailbox); is(scalar @$visitor_list, 1, 'visitor list should contain 1 item'); # one visitor is only logged once in each hour - $mech->get($url); + $mech->get_ok($url); $visitor_list = $config->get_formatted_visitor_list($mailbox); - is(scalar @$visitor_list, 1, 'visitor list should contain 1 items'); + is(scalar @$visitor_list, 1, 'visitor list should still only contain 1 item'); - return 3; + return 5; } sub email_sending_and_deleting { @@ -657,7 +666,7 @@ sub email_sending_and_deleting { $tests += wipe_mailbox_test($global_mailbox) ; } - $tests += check_empty_mailbox("$baseurl/mailbox/$global_mailbox"); + $tests += check_empty_mailbox("$website_baseurl/mailbox/$global_mailbox"); kill 15, $pid ; waitpid ( $pid, 0 ); @@ -692,7 +701,7 @@ sub banned_recipient_test { my $banned_mailbox = $config->get_banned_mailbox(); ok( send_mail ( $banned_mailbox, $mailnesia->random_name_for_testing() ."@". $sender_domain ) != 0, "sending email to banned mailbox $banned_mailbox fails" ); - $url = $baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $banned_mailbox ); + $url = $website_baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $banned_mailbox ); $mech->get( $url ); is ($mech->status, 403, "open a banned mailbox: GET $url" ); $mech->text_lacks( qq{Mail for } . $banned_mailbox ); @@ -717,12 +726,12 @@ sub invalid_recipient_test { ok( send_mail ( $invalid_mailbox, $mailnesia->random_name_for_testing() ."@". $sender_domain ) != 0, "sending email to invalid mailbox $invalid_mailbox fails" ); - $url = $baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $invalid_mailbox ); + $url = $website_baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $invalid_mailbox ); $mech->get_ok( $url, "open an invalid mailbox (will show warning only): GET $url" ); $mech->text_contains( q{Invalid characters entered! (valid: asd)} ); $mech->text_unlike ( qr/\bnil\b/, "Text does not contain 'nil' as separate word" ); $mech->text_unlike ( qr/�/, "Text does not contain an invalid utf8 character" ); - my $api_tests = api_check_bad_request($baseurl. "/api/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $invalid_mailbox )); + my $api_tests = api_check_bad_request($api_baseurl. "/api/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $invalid_mailbox )); return 5 + $api_tests; } @@ -741,7 +750,7 @@ sub invalid_sender_test { ok( send_mail ( $mailbox , $mailnesia->random_name_for_testing() ."@". $_ ) != 0, "sending email from invalid domain $_ fails" ); - $url = $baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); + $url = $website_baseurl. "/mailbox/" . $mailnesia->get_url_encoded_mailbox ( $mailbox ); $mech->get_ok( $url, "open mailbox (should be empty): GET $url" ); $mech->text_contains( qq{Mail for } . lc $mailbox ); $mech->text_contains( qq{No e-mail message for } . lc $mailbox ); @@ -771,7 +780,7 @@ sub banned_sender_test { (my $recipient_url_encoded = $mailnesia->get_url_encoded_mailbox ($recipient)) =~ s/@.*//; # do not use @ in URL - $url = $baseurl. "/mailbox/$recipient_url_encoded"; + $url = $website_baseurl. "/mailbox/$recipient_url_encoded"; $mech->get_ok( $url, "GET $url" ); $mech->text_contains( 'No e-mail message for' ); @@ -802,7 +811,7 @@ sub send_mail_test { my $check_here_url_encoded = $mailnesia->get_url_encoded_mailbox ($check_here); - $url = $baseurl. "/mailbox/$check_here_url_encoded"; + $url = $website_baseurl. "/mailbox/$check_here_url_encoded"; $mech->get_ok( $url, "GET $url" ); $tests += check_mailbox_header( $check_here ); @@ -811,7 +820,7 @@ sub send_mail_test { my $mail_link_regex = qr{/mailbox/$lc_check_here_url_encoded/\d+}; $mech->content_like ($mail_link_regex, "mailbox view contains a link to open email"); # also get mailbox using API - $tests += api_check_mailbox($baseurl. "/api/mailbox/$check_here_url_encoded"); + $tests += api_check_mailbox($api_baseurl. "/api/mailbox/$check_here_url_encoded"); if ( ok ( my $first_email = $mech->find_link ( url_regex => $mail_link_regex ), 'find first email' ) ) { @@ -825,18 +834,18 @@ sub send_mail_test { # also get email using API $email_id = $1 if $first_email->url() =~ m^/(\d+)$^; ok ($email_id, "Found ID of first email on page"); - $tests += 1 + api_check_email($baseurl. "/api/mailbox/$check_here_url_encoded/$email_id"); + $tests += 1 + api_check_email($api_baseurl. "/api/mailbox/$check_here_url_encoded/$email_id"); - $tests += api_check_mailbox($baseurl. "/api/mailbox/$check_here_url_encoded?newerthan=1"); - $tests += api_check_mailbox_204($baseurl. "/api/mailbox/$check_here_url_encoded?newerthan=9999999"); - $tests += api_check_mailbox($baseurl. "/api/mailbox/$check_here_url_encoded?page=5"); + $tests += api_check_mailbox($api_baseurl. "/api/mailbox/$check_here_url_encoded?newerthan=1"); + $tests += api_check_mailbox_204($api_baseurl. "/api/mailbox/$check_here_url_encoded?newerthan=9999999"); + $tests += api_check_mailbox($api_baseurl. "/api/mailbox/$check_here_url_encoded?page=5"); } $tests += 3 + rss_tests($check_here); my $alias_fail = $mailnesia->random_name_for_testing(); - $mech->post("$baseurl/settings/$alias_fail/alias/set", + $mech->post("$website_baseurl/settings/$alias_fail/alias/set", { alias=>$check_here } @@ -905,7 +914,7 @@ sub send_complete_email_test { { ok ( send_mail($send_to,"test\@$sender_domain", $_) == 0, "sending $_ to $send_to" ); - $url = $baseurl. "/mailbox/$check_here_url_encoded"; + $url = $website_baseurl. "/mailbox/$check_here_url_encoded"; # disable HTML validation, since the page contains the email which can be invalid my $old_status = $mech->autolint (0); @@ -916,7 +925,7 @@ sub send_complete_email_test { $mech->text_contains( "Mail for ". lc $send_to ); # TODO: also get mailbox using API $tests += rss_tests($send_to); - $tests += api_check_email($baseurl. "/api/mailbox/$check_here_url_encoded"); + $tests += api_check_email($api_baseurl. "/api/mailbox/$check_here_url_encoded"); my $lc_check_here_url_encoded = $mailnesia->get_url_encoded_mailbox ( lc $send_to ); @@ -938,7 +947,7 @@ sub send_complete_email_test { # also get email using API my $email_id = $1 if $first_email->url() =~ m^/(\d+)$^; ok ($email_id, "Found ID of first email on page"); - $tests += 1 + api_check_email($baseurl. "/api/mailbox/$lc_check_here_url_encoded/$email_id"); + $tests += 1 + api_check_email($api_baseurl. "/api/mailbox/$lc_check_here_url_encoded/$email_id"); #test original email view (raw) @@ -953,7 +962,7 @@ sub send_complete_email_test { } # also get raw email using API - $tests += api_check_email($baseurl. "/api/mailbox/$lc_check_here_url_encoded/$email_id/raw"); + $tests += api_check_email($api_baseurl. "/api/mailbox/$lc_check_here_url_encoded/$email_id/raw"); #test URL clicker button if ( $mech->follow_link_ok( {text_regex => qr/test URL clicker/i }, "open 'test URL clicker' link on current page" ) ) @@ -979,7 +988,7 @@ sub send_complete_email_test { $tests += 4; - $tests += api_check_empty_mailbox($baseurl. "/api/mailbox/$lc_check_here_url_encoded"); + $tests += api_check_empty_mailbox($api_baseurl. "/api/mailbox/$lc_check_here_url_encoded"); } $tests += 7; @@ -1021,7 +1030,7 @@ sub delete_mail_test { print_test_category_header( ); my $mailbox = shift; my $lower_mailbox = $mailnesia->get_url_encoded_mailbox ( lc $mailbox ); - my $email_link_regex = qr{$baseurl/mailbox/$lower_mailbox/(\d+)}; + my $email_link_regex = qr{$website_baseurl/mailbox/$lower_mailbox/(\d+)}; if ($mech->follow_link_ok( {url_regex => $email_link_regex }, "open an email to delete" )) { @@ -1058,7 +1067,7 @@ sub wipe_mailbox_test { my $lower_esc_mailbox = $mailnesia->get_url_encoded_mailbox ( $lower_mailbox ); $mech->post_ok( - $baseurl . qq{/mailbox/$lower_esc_mailbox}, { + $website_baseurl . qq{/mailbox/$lower_esc_mailbox}, { delete => 1 }, "try to wipe mailbox $mailbox" @@ -1077,14 +1086,14 @@ sub restoration { print_test_category_header( ); my $mailbox_url_encoded = $mailnesia->get_url_encoded_mailbox ( lc $global_mailbox ); - my $url = "$baseurl/settings/$global_mailbox"; + my $url = "$website_baseurl/settings/$global_mailbox"; if (@aliases) { while ( my $alias = shift @aliases ) { - if ( $mech->post_ok("$baseurl/settings/$mailbox_url_encoded/alias/remove", + if ( $mech->post_ok("$website_baseurl/settings/$mailbox_url_encoded/alias/remove", { remove_alias=>$alias }, @@ -1156,7 +1165,7 @@ sub api_alias_tests { sub test_empty_alias_list { my $mailbox = shift; - my $url = $baseurl . "/api/alias/$mailbox"; + my $url = $api_baseurl . "/api/alias/$mailbox"; $mech->get_ok( $url, "GET $url" ); $mech->header_is('Content-Type', 'application/json'); $mech->content_is( '[]' ) or warn $mech->content(); @@ -1166,7 +1175,7 @@ sub test_empty_alias_list { sub add_alias_to_mailbox { my $mailbox = shift; my $alias = shift; - my $url = $baseurl . "/api/alias/$mailbox/$alias"; + my $url = $api_baseurl . "/api/alias/$mailbox/$alias"; $mech->post_ok( $url, "POST $url" ); $mech->header_is('Content-Type', 'application/json'); $mech->content_is( lc "\"$alias\"" ) or warn $mech->content(); @@ -1176,7 +1185,7 @@ sub add_alias_to_mailbox { sub test_alias_list { my $mailbox = shift; my $alias_list = shift; - my $url = $baseurl . "/api/alias/$mailbox"; + my $url = $api_baseurl . "/api/alias/$mailbox"; $mech->get_ok( $url, "GET $url" ); $mech->header_is('Content-Type', 'application/json'); $mech->content_is( lc arrayref_to_json($alias_list) ) or warn $mech->content(); @@ -1187,7 +1196,7 @@ sub modify_alias { my $mailbox = shift; my $alias = shift; my $new_alias = shift; - my $url = $baseurl . "/api/alias/$mailbox/$alias/$new_alias"; + my $url = $api_baseurl . "/api/alias/$mailbox/$alias/$new_alias"; $mech->put_ok( $url, "PUT $url" ); $mech->header_is('Content-Type', 'application/json'); my $result = $mech->content; @@ -1199,7 +1208,7 @@ sub modify_alias { sub delete_alias { my $mailbox = shift; my $alias = shift; - my $url = $baseurl . "/api/alias/$mailbox/$alias"; + my $url = $api_baseurl . "/api/alias/$mailbox/$alias"; $mech->delete_ok( $url, "DELETE $url" ); $mech->header_is('Content-Type', 'text/plain;charset=UTF-8'); $mech->content_is( "" ) or warn $mech->content(); @@ -1220,7 +1229,7 @@ sub mailbox_delete_tests_via_api { sub delete_mail { my $mailbox = shift; my $id = shift; - my $url = $baseurl . "/api/mailbox/$mailbox/$id"; + my $url = $api_baseurl . "/api/mailbox/$mailbox/$id"; $mech->delete_ok( $url, "DELETE $url" ); $mech->header_is('Content-Type', 'text/plain;charset=UTF-8'); $mech->content_is( "" ) or warn $mech->content(); @@ -1229,7 +1238,7 @@ sub delete_mail { sub delete_mailbox { my $mailbox = shift; - my $url = $baseurl . "/api/mailbox/$mailbox"; + my $url = $api_baseurl . "/api/mailbox/$mailbox"; $mech->delete_ok( $url, "DELETE $url" ); $mech->header_is('Content-Type', 'text/plain;charset=UTF-8'); $mech->content_is( "" ) or warn $mech->content(); @@ -1335,15 +1344,15 @@ =head1 done_testing done_testing( check_config() + visitor_test() + - mailbox_tests() + - alias_positive_tests() + - alias_negative_tests() + - email_sending_and_deleting() + - random_mailbox() + - webpage_tests() + - negative_delete_test() + - api_alias_tests() + - mailbox_delete_tests_via_api() + - mailbox_settings_page_tests() + + # mailbox_tests() + + # alias_positive_tests() + + # alias_negative_tests() + + # email_sending_and_deleting() + + # random_mailbox() + + # webpage_tests() + + # negative_delete_test() + + # api_alias_tests() + + # mailbox_delete_tests_via_api() + + # mailbox_settings_page_tests() + restoration() ); From 65f511eec2e90d9dd133cf2db27b13b3ee0c89fb Mon Sep 17 00:00:00 2001 From: Peter Baranyi <peter.baranyi@protonmail.com> Date: Mon, 23 Mar 2026 22:49:45 +0100 Subject: [PATCH 5/9] e2e passes with&without nginx --- .github/workflows/action.yml | 4 ++++ script/website.pl | 2 +- tools/test-mailnesia.pl | 20 ++++++++++---------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index f9ee537..0038996 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -59,6 +59,10 @@ jobs: postgres_user: mailnesia postgres_password: test redis_host: localhost + website_pages_baseurl: http://localhost:3000 + website_baseurl: http://localhost:3001 + api_baseurl: http://localhost:3002 + rss_baseurl: http://localhost:3003 # Service containers to start services: diff --git a/script/website.pl b/script/website.pl index 1a1c666..d011f8e 100755 --- a/script/website.pl +++ b/script/website.pl @@ -52,7 +52,7 @@ =head1 website.pl # check SQL connection on each pageload, try to reconnect if fails $mailnesia->connect_sql(); - my $ip = $self->req->headers->header('X-Forwarded-For'); + my $ip = $self->req->headers->header('X-Forwarded-For') || $self->tx->remote_address; if ($ip) { # redirect to captcha if too many mailbox requests diff --git a/tools/test-mailnesia.pl b/tools/test-mailnesia.pl index b3ff941..c306a5e 100755 --- a/tools/test-mailnesia.pl +++ b/tools/test-mailnesia.pl @@ -1344,15 +1344,15 @@ =head1 done_testing done_testing( check_config() + visitor_test() + - # mailbox_tests() + - # alias_positive_tests() + - # alias_negative_tests() + - # email_sending_and_deleting() + - # random_mailbox() + - # webpage_tests() + - # negative_delete_test() + - # api_alias_tests() + - # mailbox_delete_tests_via_api() + - # mailbox_settings_page_tests() + + mailbox_tests() + + alias_positive_tests() + + alias_negative_tests() + + email_sending_and_deleting() + + random_mailbox() + + webpage_tests() + + negative_delete_test() + + api_alias_tests() + + mailbox_delete_tests_via_api() + + mailbox_settings_page_tests() + restoration() ); From b504a3d215fde4f37fa59cd216a22d6e0e57f4cf Mon Sep 17 00:00:00 2001 From: Peter Baranyi <peter.baranyi@protonmail.com> Date: Sat, 28 Mar 2026 17:24:20 +0100 Subject: [PATCH 6/9] print INC --- .github/workflows/action.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 0038996..225f6aa 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -18,6 +18,14 @@ jobs: with: perl-version: "5.32" + - name: print INC + run: perl -e 'print join("\n", @INC)' + shell: bash + + - name: fail with nonexistent module + run: perl -MNonExistentModule -e1 + shell: bash + - name: Install dependencies run: cpanm --installdeps . --skip-satisfied shell: bash From 809444e41d7097c73a1b30c6b21c44b4dfc74c0c Mon Sep 17 00:00:00 2001 From: Peter Baranyi <peter.baranyi@protonmail.com> Date: Sun, 29 Mar 2026 13:31:38 +0200 Subject: [PATCH 7/9] e2e test: start SMTP server separately --- .github/workflows/action.yml | 13 ++--- tools/test-mailnesia.pl | 98 +++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 225f6aa..91b0238 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -18,14 +18,6 @@ jobs: with: perl-version: "5.32" - - name: print INC - run: perl -e 'print join("\n", @INC)' - shell: bash - - - name: fail with nonexistent module - run: perl -MNonExistentModule -e1 - shell: bash - - name: Install dependencies run: cpanm --installdeps . --skip-satisfied shell: bash @@ -55,13 +47,16 @@ jobs: morbo --listen http://*:3002 ./script/api.pl morbo --listen http://*:3003 ./script/rss.pl perl ./script/clicker.pl + perl ./script/AnyEvent-SMTP-Server.pl -d wait-on: | http://localhost:3000 http://localhost:3001/mailbox/test http://localhost:3002/api/mailbox/test http://localhost:3003/rss/test - command: perl tools/test-mailnesia.pl + tcp:5000 + tcp:2525 + command: perl tools/test-mailnesia.pl --dont-start-smtp-server env: postgres_host: localhost postgres_user: mailnesia diff --git a/tools/test-mailnesia.pl b/tools/test-mailnesia.pl index c306a5e..496827b 100755 --- a/tools/test-mailnesia.pl +++ b/tools/test-mailnesia.pl @@ -53,6 +53,11 @@ my $mailbox_to_ban = 'ban-this-mailbox-as-a-test'; +my $start_smtp_server = 1; +if ($ARGV[0] eq "--dont-start-smtp-server") { + print "Not starting SMTP server, expecting it to already be running\n"; + $start_smtp_server = 0; +} # tests: =head1 webpage tests @@ -632,60 +637,68 @@ sub visitor_test { return 5; } -sub email_sending_and_deleting { - print_test_category_header( ); - #starting smtp server - if (my $pid = fork()) - { - #parent, sending email - print "waiting for SMTP server to start...\n"; - sleep 2; - my $tests; - my $wipeTest = scalar @aliases; +sub run_email_tests { + my $tests; + my $wipeTest = scalar @aliases; - while (my $alias = shift @aliases) - { - $tests += send_mail_test($alias,$global_mailbox,$mailnesia->random_name_for_testing()) - }; + while (my $alias = shift @aliases) + { + $tests += send_mail_test($alias,$global_mailbox,$mailnesia->random_name_for_testing()) + }; - $tests += send_mail_test($mailbox_for_api_test, $mailbox_for_api_test); - $tests += send_mail_test($mailbox_for_api_test, $mailbox_for_api_test); + $tests += send_mail_test($mailbox_for_api_test, $mailbox_for_api_test); + $tests += send_mail_test($mailbox_for_api_test, $mailbox_for_api_test); - # test disabled, feature not enabled - # invalid_sender_test() + + # test disabled, feature not enabled + # invalid_sender_test() + - $tests += invalid_recipient_test() + - banned_sender_test() + - banned_recipient_test() + - send_complete_email_test() + - test_url_clicker(); + $tests += invalid_recipient_test() + + banned_sender_test() + + banned_recipient_test() + + send_complete_email_test() + + test_url_clicker(); - # wipe $global_mailbox if there were alias tests - if ($wipeTest) { + # wipe $global_mailbox if there were alias tests + if ($wipeTest) { $tests += wipe_mailbox_test($global_mailbox) ; - } + } - $tests += check_empty_mailbox("$website_baseurl/mailbox/$global_mailbox"); + $tests += check_empty_mailbox("$website_baseurl/mailbox/$global_mailbox"); - kill 15, $pid ; - waitpid ( $pid, 0 ); + return $tests; +} - return $tests; - } - elsif ($pid == 0) - { - #child, start smtp szerver - my $server = "$project_directory/script/AnyEvent-SMTP-Server.pl"; - exec ('/usr/bin/perl', $server, '-d'); - } - else - { - die "error forking\n"; +sub email_sending_and_deleting { + print_test_category_header( ); + + if ($start_smtp_server) { + #starting smtp server + if (my $pid = fork()) { + #parent, sending email + print "waiting for SMTP server to start...\n"; + sleep 2; + my $tests = run_email_tests(); + kill 15, $pid ; + waitpid ( $pid, 0 ); + + return $tests; + } elsif ($pid == 0) { + #child, start smtp server + my $server = "$project_directory/script/AnyEvent-SMTP-Server.pl"; + + exec ('/usr/bin/perl', $server, '-d'); + } else { + die "error forking\n"; + } + } else { + print "Skipping starting SMTP server, running email tests assuming server is already running\n"; + return run_email_tests(); } } + =head1 banned recipient tests =cut @@ -1216,10 +1229,13 @@ sub delete_alias { } =head1 mailbox delete tests via api -Delete all mail that was sent to $mailbox_for_api_test +Delete all mail that was sent to $mailbox_for_api_test. First delete $email_id, then wipe all mail in the mailbox. +Check that the API returns the expected responses. Note that this will only work if there are at least two emails +in the mailbox. =cut sub mailbox_delete_tests_via_api { + print_test_category_header(); my $tests = 0; $tests += delete_mail($mailbox_for_api_test, $email_id); $tests += delete_mailbox($mailbox_for_api_test); From 29f0b9841ef03608de65eef6ff27e2f2a40eff86 Mon Sep 17 00:00:00 2001 From: Peter Baranyi <peter.baranyi@protonmail.com> Date: Sun, 29 Mar 2026 14:17:05 +0200 Subject: [PATCH 8/9] try to start the SMTP server in the workflow --- .github/workflows/action.yml | 8 ++++++++ script/AnyEvent-SMTP-Server.pl | 1 + 2 files changed, 9 insertions(+) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 91b0238..9ccabfb 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -30,6 +30,14 @@ jobs: PGPASSWORD: test shell: bash + - name: Execute smtp server to see what's going on + run: perl ./script/AnyEvent-SMTP-Server.pl -d + env: + postgres_host: localhost + postgres_user: mailnesia + postgres_password: test + redis_host: localhost + - name: Execute unit / function tests run: prove -lv t env: diff --git a/script/AnyEvent-SMTP-Server.pl b/script/AnyEvent-SMTP-Server.pl index 7e39334..05b8064 100755 --- a/script/AnyEvent-SMTP-Server.pl +++ b/script/AnyEvent-SMTP-Server.pl @@ -395,6 +395,7 @@ sub add_received_header { print &display_time()." $0 started on port $server_port\n"; open_log(); +terminate() if $debugging_mode; AnyEvent->condvar->recv; From 0c82adaa52c1d4a0a7c960d7763318bac042a058 Mon Sep 17 00:00:00 2001 From: Peter Baranyi <peter.baranyi@protonmail.com> Date: Sun, 29 Mar 2026 22:57:21 +0200 Subject: [PATCH 9/9] E2E: install swaks --- .github/workflows/action.yml | 11 +++-------- README.md | 23 +++++++++++++++++++++-- script/AnyEvent-SMTP-Server.pl | 19 ++++++------------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 9ccabfb..a7614ae 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -30,14 +30,6 @@ jobs: PGPASSWORD: test shell: bash - - name: Execute smtp server to see what's going on - run: perl ./script/AnyEvent-SMTP-Server.pl -d - env: - postgres_host: localhost - postgres_user: mailnesia - postgres_password: test - redis_host: localhost - - name: Execute unit / function tests run: prove -lv t env: @@ -46,6 +38,9 @@ jobs: postgres_password: test redis_host: localhost + - name: Install swaks for email testing + run: sudo apt-get install -y swaks + - name: Execute e2e tests uses: MohamedRaslan/background_run_and_test@v1 with: diff --git a/README.md b/README.md index 5ed4c22..258353b 100644 --- a/README.md +++ b/README.md @@ -135,8 +135,27 @@ upon save in Emacs. ## Testing -Test running website and mail server by sending test emails: +### End-to-end tests +#### Description +Test running website and mail server by sending test emails. + +#### Requirements +All services must be running, as well as Postgres and Redis. Email sending +requires the `swaks` tool which can be installed with: + + sudo apt-get install swaks -y + +#### Execution + tools/test-mailnesia.pl -Execute function tests under t/ (these don't require the website to be up): +### Unit & function tests +#### Description +Execute function tests under t/. + +#### Requirements +These don't require the website to be up, but running Postgres and Redis are necessary. + +#### Execution + prove diff --git a/script/AnyEvent-SMTP-Server.pl b/script/AnyEvent-SMTP-Server.pl index 05b8064..533d35f 100755 --- a/script/AnyEvent-SMTP-Server.pl +++ b/script/AnyEvent-SMTP-Server.pl @@ -70,10 +70,15 @@ sub nextOne { } } +my $debugging_mode = 0; +if ($ARGV[0] and ($ARGV[0] eq '-d' or $ARGV[0] eq '--debug')) { + $debugging_mode = 1; +} + my $email_count=0; my $email_bandwidth=0; -my $config = Mailnesia::Config->new; +my $config = Mailnesia::Config->new($debugging_mode); my $mailnesia = Mailnesia->new; #maximum size the clicker will download @@ -87,7 +92,6 @@ sub nextOne { #pid file: my $pidfile = $config->{pidfile}; -my $debugging_mode; # do not save email / click logs my $logging_disabled = 1; @@ -111,16 +115,6 @@ sub nextOne { # %cookie_jar will contain the cookies to use after redirects. Once the request is done, cookies are discarded. my %cookie_jar ; - -if ($ARGV[0]) -{ - $debugging_mode = ($ARGV[0] eq '-d' or $ARGV[0] eq '--debug') ? 1 : 0; -} -else -{ - $debugging_mode = 0; -} - my $server_port = $debugging_mode || $mailnesia->{devel} ? $config->{smtp_port_devel} : $config->{smtp_port}; my $server_ip = $debugging_mode || $mailnesia->{devel} ? $config->{smtp_host_devel} : $config->{smtp_host}; my $dbh = $mailnesia->{dbh}; @@ -395,7 +389,6 @@ sub add_received_header { print &display_time()." $0 started on port $server_port\n"; open_log(); -terminate() if $debugging_mode; AnyEvent->condvar->recv;