+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['package', 'dist']
+});
+% end
+% end
+
+% } else {
+
Sorry, <%= $pause->{HiddenUser}{userid} || $pause->{User}{userid} %> does not seem to be co-maintainer of any module.
+% }
diff --git a/lib/pause_2025/templates/user/perms/make_comaint.html.ep b/lib/pause_2025/templates/user/perms/make_comaint.html.ep
new file mode 100644
index 000000000..8fe4a3ff1
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/make_comaint.html.ep
@@ -0,0 +1,90 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
+% if (param("SUBMIT_pause99_share_perms_makeco")) {
+
+% if ($pause->{error}) {
+
<%= $pause->{error} %>
+% } elsif (@{$pause->{results} || []}) {
+
+% for (@{$pause->{results}}) {
+% if ($_->{error}) {
+Error trying to add <%= $_->{user} %> to co-maintainers of <%= $_->{mod} %>: <%= $_->{error} %>
+% } elsif ($_->{duplicated}) {
+<%= $_->{user} %> was already a co-maintainer of <%= $_->{mod} %>: skipping
+% } else {
+Added <%= $_->{user} %> to co-maintainers of <%= $_->{mod} %>.
+% }
+% }
+
+% } else {
+
You need to select one or more packages and enter a userid.
+ Nothing done.
+% }
+
+
+% }
+
+% if (@{$pause->{mods} || []}) {
+
+
Select a co-maintainer
+
Please select one or
+more namespaces for which you want to select a
+co-maintainer, enter the CPAN userid of the co-maintainer
+into the text field and press Make Co-Maintainer
+
+
If you are open to someone else asking for your first-come
+permissions, but you wish to decide on any such request, you
+can give a co-maint to a special user called
+HANDOFF .
+
+
You can also grant co-maint to
+NEEDHELP
+if you would like additional volunteers to help you work on a particular module.
+
+
If you want to add comaintainers for all the modules in a
+distribution, visit
+Add Comaintainers per distribution page.
+
+
Select one or more namespaces:
+
+
+
+
+
+
+ Package
+ Indexed Distribution
+
+
+
+ % for (@{$pause->{mods}}) {
+
+ <%= check_box "pause99_share_perms_makeco_m" => $_ %>
+ <%= $_ %>
+ <%= $pause->{dist_for_package}{$_} %>
+
+ % }
+
+
+
Select a userid:
+<%= text_field "pause99_share_perms_makeco_a", size => 15, maxlength => 9 %>
+
+
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['package', 'dist']
+});
+% end
+% end
+
+% } else {
+
Sorry, there are no modules registered belonging to <%= $pause->{HiddenUser}{userid} || $pause->{User}{userid} %>.
+% }
diff --git a/lib/pause_2025/templates/user/perms/move_primary.html.ep b/lib/pause_2025/templates/user/perms/move_primary.html.ep
new file mode 100644
index 000000000..5172c4290
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/move_primary.html.ep
@@ -0,0 +1,81 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
+% if (param("SUBMIT_pause99_share_perms_movepr")) {
+
+% if ($pause->{error}) {
+
<%= $pause->{error} %>
+% } elsif (@{$pause->{results} || []}) {
+
+% for my $res (@{$pause->{results}}) {
+% if ($res->{error}) {
+Error trying to make <%= $res->{user} %> primary maintainer of <%= $res->{mod} %>: <%= $res->{error} %> ";
+% } else {
+Made <%= $res->{user} %> primary maintainer of <%= $res->{mod} %>.
+% }
+% }
+
+% } else {
+
You need to select one or more packages and enter a userid.
+Nothing done.
+% }
+
+
+% }
+
+% if (@{$pause->{mods}}) {
+
Pass maintainership status Please select one
+or more namespaces for which you want to pass primary
+maintainership status, enter the CPAN userid of the new
+maintainer into the text field and press Pass Maintainership
+Status . Note: you keep co-maintainer status after this move.
+If you want to get rid of that too, please visit 'remove_primary') %>">Give up
+co-maintainership status next.
+
+
If you want to transfer all the modules in a distribution, visit
+
+Transfer Primary Permissions per distribution page.
+
+
Select one or more namespaces:
+
+
+
+
+
+
+ Package
+ Indexed Distribution
+
+
+
+ % for (@{$pause->{mods}}) {
+
+ <%= check_box "pause99_share_perms_pr_m" => $_ %>
+ <%= $_ %>
+ <%= $pause->{dist_for_package}{$_} // '' %>
+
+ % }
+
+
+
Select a userid:
+<%= text_field "pause99_share_perms_movepr_a", size => 15, maxlength => 9 =%>
+
+
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['package', 'dist']
+});
+% end
+% end
+
+% } else {
+
Sorry, there are no modules registered belonging to <%= $pause->{HiddenUser}{userid} || $pause->{User}{userid} %>.
+% }
+
diff --git a/lib/pause_2025/templates/user/perms/peek.html.ep b/lib/pause_2025/templates/user/perms/peek.html.ep
new file mode 100644
index 000000000..6e169b00e
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/peek.html.ep
@@ -0,0 +1,94 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
Select the option and fill in a module name or
+user ID as appropriate. The answer is all modules that an
+user ID is registered for or all user IDs registered for a
+module, as appropriate.
+
+
Registration comes in one of two types: type
+first-come is the automatic registration on a
+first-come-first-serve basis that happens on the initial
+upload. And type co-maint is the registration as
+co-maintainer which means that the primary maintainer of
+the namespace has granted permission to upload this module
+to other userid(s). Per namespace there can only be one
+primary maintainer (userid in the
+first-come category) and any number of userids in
+the co-maint category. Being registered in any of
+the categories means that a user is able not only to
+upload a module in that namespace but also be accepted by
+the indexer. In other words, the indexer will not ignore
+uploads for that namespace by that person.
+
+
If the list is too long, visit
+View permission per distribution page.
+
+
The
+contents of the tables presented on this page are mostly
+generated automatically, so please report any errors you
+observe to <%= "@{$PAUSE::Config->{ADMINS}}" %> so that the tables
+can be corrected.--Thank you!
+
+<%= select_field 'pause99_peek_perms_by' => [
+ ["for a module--exact match" => "me"],
+ [qq{for a module--SQL "LIKE" match} => "ml"],
+ ["of an author" => "a"],
+], size => 1 =%>
+
+<%= text_field 'pause99_peek_perms_query', size => 44, maxlength => 112, =%>
+
+
+
+
+% if (@{$pause->{rows} || []}) {
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('perms', {
+ valueNames: ['module', 'userid', 'type', 'owner']
+});
+% end
+% end
+
+% } else {
+No records found.
+% }
diff --git a/lib/pause_2025/templates/user/perms/remove_comaint.html.ep b/lib/pause_2025/templates/user/perms/remove_comaint.html.ep
new file mode 100644
index 000000000..19a3c4f16
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/remove_comaint.html.ep
@@ -0,0 +1,80 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
+% if (param("SUBMIT_pause99_share_perms_remocos")) {
+
+% if ($pause->{error}) {
+
<%= $pause->{error} %>
+% } elsif (@{$pause->{results} || []}) {
+
+% for (@{$pause->{results}}) {
+% if ($_->{not_exists}) {
+Cannot handle tuple <%= $_->{mod} %> . If you believe, this is a bug, please complain.
+% } elsif ($_->{error}) {
+Error trying to remove <%= $_->{user} %> from co-maintainers of <%= $_->{mod} %>: <%= $_->{error} %>
+% } else {
+Removed <%= $_->{user} %> from co-maintainers of <%= $_->{mod} %>.
+% }
+% }
+
+% } else {
+
You need to select one or more packages. Nothing done.
+% }
+
+
+% }
+
+% if (@{$pause->{mods} || []}) {
+
Remove co-maintainer status
+
The scrolling
+list shows you, which packages are associated with other
+maintainers besides yourself. Every line denotes a tuple
+of a namespace and a userid. Select those that you want to
+remove and press Remove
+
+
If you want to remove comaintainers from all the modules
+in a distribution, visit
+
+Remove Comaintainers per distribution page.
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['pacakge', 'dist', 'userid']
+});
+% end
+% end
+
+% } else {
+
There are no co-maintainers registered to any of <%= $pause->{HiddenUser}{userid} || $pause->{User}{userid} %>'s modules.
+% }
diff --git a/lib/pause_2025/templates/user/perms/remove_primary.html.ep b/lib/pause_2025/templates/user/perms/remove_primary.html.ep
new file mode 100644
index 000000000..2e56d5274
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/remove_primary.html.ep
@@ -0,0 +1,92 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
+% if (param("SUBMIT_pause99_share_perms_remopr")) {
+
+% if ($pause->{error}) {
+
<%= $pause->{error} %>
+% } elsif (@{$pause->{results} || []}) {
+
+% for (@{$pause->{results}}) {
+% if ($_->{error}) {
+Error trying to remove primary maintainership of <%= $_->{user} %> from <%= $_->{mod} %>: <%= $_->{error} %>
+% } else {
+Removed primary maintainership of <%= $_->{user} %> from <%= $_->{mod} %>.
+% }
+% }
+
+% } else {
+
You need to select one or more packages. Nothing done.
+% }
+
+
+% }
+
+% if (@{$pause->{mods} || []}) {
+
+
Give up maintainership status
+
Please select one or more namespaces for which you
+want to give up primary maintainership status and press
+Give Up Maintainership Status . Note: you keep co-maintainer
+status after this move. If you want to get rid of that too,
+please visit ">Give up
+co-maintainership status next.
+
+
Giving up primary permissions now means that the permissions are
+transferred to a special user called
+ADOPTME .
+
+
If multiple owners are listed, those owners keep their primary
+maintainership for those modules. In this case, you are strongly advised to
+">transfer your primary permissions
+to one of the other owners.
+
+
+If you have are unsure about what to do, or have any questions,
+please email the PAUSE admins at modules@perl.org .
+
+
+
If you want to give up all the modules in a distribution, visit
+
+<%= $c->app->pause->config->action('remove_dist_primary')->{verb} %> page.
+
+
Select one or more namespaces:
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['package', 'dist']
+});
+% end
+% end
+
+% } else {
+
Sorry, there are no modules registered belonging to <%= $pause->{HiddenUser}{userid} || $pause->{User}{userid} %>.
+% }
+
diff --git a/lib/pause_2025/templates/user/perms/share.html.ep b/lib/pause_2025/templates/user/perms/share.html.ep
new file mode 100644
index 000000000..061ed849c
--- /dev/null
+++ b/lib/pause_2025/templates/user/perms/share.html.ep
@@ -0,0 +1,159 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+% my $subaction = $pause->{subaction} || "";
+
+
+
+
+% unless ($subaction) {
+
Permissions on PAUSE come in three flavors:
+
+
+ only one user per module can be either
+
+
+
+ registered in modulelist or
+
+
+ primary maintainer on a first-come-first-serve
+ basis;
+
+
+
+
+ many users can get granted permissions as co-maintainers ,
+ which means their uploads for the given module are honoured by
+ the indexer.
+
+
+
+
You can view your current set of permissions on the View Permissions page. To
+ change permissions, select one of the following submit
+ buttons, each of which leads you to a different page:
+
+
+% } else {
+%= include "user/perms/_share_$subaction";
+% }
diff --git a/lib/pause_2025/templates/user/reindex.html.ep b/lib/pause_2025/templates/user/reindex.html.ep
new file mode 100644
index 000000000..31befcf4f
--- /dev/null
+++ b/lib/pause_2025/templates/user/reindex.html.ep
@@ -0,0 +1,58 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+% my $files = $pause->{files} || {};
+
+
+
Indexing normally happens only once, shortly after the upload takes place. Sometimes it is necessary to reindex a file. The reason is typically one of the following:
+
+
+A file that contained a current version of a module got deleted, now an older file should be considered current.
+
+The perms
table got altered, now a file should be visited again to overrule the previous indexing decision.
+
+At the time of uploading PAUSE had a bug and made a wrong indexing decision.
+
+
+
+
With this form you can tell the indexer to index selected files again. As it is done by a cron job, it may take up to an hour until the indexer actually executes the command. If this doesn't repair the index, please email me .
+
+% if (%$files) {
+% if ($pause->{mailbody}) {
+
<%= $pause->{mailbody} %>
+% }
+
+
Files in directory authors/id/<%= $pause->{userhome} %>
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('files', {
+ valueNames: ['file']
+});
+% end
+% end
+
+% } else {
+No files found in authors/id/<%= $pause->{userhome} %>
+% }
diff --git a/lib/pause_2025/templates/user/reset_version.html.ep b/lib/pause_2025/templates/user/reset_version.html.ep
new file mode 100644
index 000000000..32faca254
--- /dev/null
+++ b/lib/pause_2025/templates/user/reset_version.html.ep
@@ -0,0 +1,71 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
Note: resetting versions is a major inconvenience for
+module users. This page will probably be withdrawn from PAUSE if
+the perl community does not want to allow decreasing version numbers
+on the CPAN. For now: use with care. Thanks.
+
+
Below you see the packages and version numbers that
+the indexer considers the current and highest version number that
+it has seen so far. By selecting an item in the list and clicking
+Forget , this value is set to undef . This opens the
+way for a Force Reindexing run in which the version of the
+package in the reindexed distribution can become the current.
+
+
Did I say, this operation should not be done lightly? Because
+users of the module out there may still have that higher version
+installed and so will not notice the newer but lower-numbered
+release. Let me repeat: please make responsible use of this
+page.
+
+
Q: So why is this page up at all?
+
+
A: Combine a multi-module-distro with a small mistake in an
+older release or a bug in the PAUSE indexer. In such a case you
+will be happy to use this page and nobody else will ever notice
+there was a problem.
+% if ($pause->{mailbody}) {
+
<%= $pause->{mailbody} %>
+% }
+% if (%{$pause->{packages} || {}}) {
+
<%= scalar keys %{$pause->{packages}} %> <%= keys %{$pause->{packages}} == 1 ? "package" : "packages" %> associated with <%= $pause->{User}{userid} %>
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var List = new List('packages', {
+ valueNames: ['package', 'version', 'dist']
+});
+% end
+% end
+
+% } else {
+
No packages associated with <%= $pause->{User}{userid} %>
+% }
diff --git a/lib/pause_2025/templates/user/show_ml_repr.html.ep b/lib/pause_2025/templates/user/show_ml_repr.html.ep
new file mode 100644
index 000000000..aad827860
--- /dev/null
+++ b/lib/pause_2025/templates/user/show_ml_repr.html.ep
@@ -0,0 +1,2 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
diff --git a/lib/pause_2025/templates/user/tail_logfile.html.ep b/lib/pause_2025/templates/user/tail_logfile.html.ep
new file mode 100644
index 000000000..84bdab3bd
--- /dev/null
+++ b/lib/pause_2025/templates/user/tail_logfile.html.ep
@@ -0,0 +1,21 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+% content_for javascript => begin
+%= javascript "/pause/list.min.js"
+%= javascript begin
+var logList = new List('logs', {valueNames: ['log']});
+% end
+% end
diff --git a/lib/pause_2025/templates/user/uri/_continued.html.ep b/lib/pause_2025/templates/user/uri/_continued.html.ep
new file mode 100644
index 000000000..132845051
--- /dev/null
+++ b/lib/pause_2025/templates/user/uri/_continued.html.ep
@@ -0,0 +1,64 @@
+% my $pause = stash(".pause") || {};
+
+
+
+
+% if ($pause->{invalid_uri}) {
+% } else {
+
+Submitting query
+
+% if ($pause->{query_succeeded}) {
+
+Query succeeded. Thank you for your contribution
+
+As it is done by a separate process, it may take a few minutes to
+complete the upload. The processing of your file is going on while you
+read this. There's no need for you to retry. The form below is only
+here in case you want to upload further files.
+
+Please tidy up your homedir: CPAN is getting larger every day which
+is nice but usually there is no need to keep old an outdated version
+of a module on several hundred mirrors. Please consider "delete_files") %>">removing old versions of
+your module from PAUSE and CPAN. If you are worried that someone might
+need an old version, it can always be found on the {userhome} %>/">backpan
+
+
+Debugging: your submission should show up soon at {usrdir} %>"><%= $pause->{usrdir} %> . If something's wrong, please
+check the logfile of the daemon: see the tail of it with {tailurl} %>"><%= $pause->{tailurl} %> . If you already know what's going wrong, you
+may wish to visit the ">repair
+tool for pending uploads.
+
+% } else {
+
+Could not enter the URL into the database.
+Reason:
<%= $pause->{errmsg} %>
+
+% if ($pause->{duplicate}) {
+This indicates that you probably tried to upload a file that is
+already in the database. You will most probably have to rename your
+file and try again, because PAUSE doesn't let you upload a file
+twice .
+
+This seems to be the record causing the conflict:
+
+% if (my $rec = $pause->{rec}) {
+% for my $k (sort keys %$rec) {
+<%= $k %> <%= $rec->{$k} || b(" ") %>
+% }
+% }
+
+
+
+% }
+% }
+% }
+
+
+
+
+
diff --git a/lib/pause_2025/templates/user/uri/add.html.ep b/lib/pause_2025/templates/user/uri/add.html.ep
new file mode 100644
index 000000000..d2d59be85
--- /dev/null
+++ b/lib/pause_2025/templates/user/uri/add.html.ep
@@ -0,0 +1,132 @@
+% layout 'layout';
+% my $pause = stash(".pause") || {};
+
+
+
<%# just for compatibility; will be removed eventually =%>
+
+
Add a file for <%= $pause->{HiddenUser}{userid} %>
+
+% if (my $to = $pause->{successfully_copied_to}) {
+
File successfully copied to '<%= $to %>'
+% }
+
+% if (my $renamed = $pause->{upload_is_renamed}) {
+
+
Your filename has been altered as it contained characters besides
+the class [A-Za-z0-9_\\-\\.\\@\\+]. DEBUG: your filename[<%= $renamed->{from} %>] corrected
+filename[<%= $renamed->{to} %>].
+
+% }
+
+% if (! $pause->{uploaded_uri} ) {
+
+% } else {
+
+%= include "user/uri/_continued";
+
+% }
+
+
+
+
This form enables you to enter one file at a time
+into CPAN in one of these ways:
+
+% if ($pause->{tryupload}) {
+
+
HTTP Upload: As an
+HTTP upload: enter the filename in the lower text field.
+Hint: If you encounter problems processing this form,
+it may be due to the fact that your browser can't handle
+multipart/form-data
forms that support file
+upload. In such a case, please retry to access this "add_uri", CAN_MULTIPART => 0) %>">file-upload-disabled form .
+
+% } else {
+
+
HTTP Upload: As
+you do not seem to want HTTP upload enabled, we do
+not offer it. If this is not what you want, try to
+ 1) %>">explicitly enable HTTP upload .
+
+% }
+
+
GET URL: PAUSE fetches any http or ftp
+URL that can be handled by LWP: use the text field (please specify the complete
+URL ).
+
+
Please, make sure your filename
+contains a version number. For security reasons you will never
+be able to upload a file with the same name again (not even
+after deleting it). Thank you.
+
+
There is no need to upload README files separately. The
+upload server will unwrap your files (.tar.gz or .zip files
+only) within a few hours after uploading and will put the
+topmost README file as, say, Foo-Bar-3.14.readme into your
+directory. Hint: if you're looking for an even more
+convenient way to upload files than this form, you can try the
+cpan-upload script.
+
+
+
Target Directory If you want to load the
+file into a directory below your CPAN directory,
+please specify the directory name here. Any number of
+subdirectory levels is allowed, they all will be
+created on the fly if they don't exist yet. Only sane
+directory names are allowed and the number of
+characters for the whole path is limited.
+NOTE : To upload a Perl6 distribution a target
+directory whose top level subdirectory is "Perl6" must
+be specified. In addition, a Perl6 distribution must
+contain a META6.json. Pause will only consider it a
+Perl6 dist if these two conditions are satisfied.
+
+
+
+
+<%= text_field "pause99_add_uri_subdirtext",
+ size => 32,
+ maxlength => 128,
+%>
+
+% if (@{$pause->{subdirs} || []}) {
+
+<%= select_field "pause99_add_uri_subdirscrl" => $pause->{subdirs},
+ size => (@{$pause->{subdirs}} > 18 ? 15 : scalar @{$pause->{subdirs}}),
+%>
+% }
+
+
+
+
Upload Material
+
+% if ($pause->{tryupload}) {
+
+
+
If your browser can handle
+file upload , enter the filename here and I'll transfer it
+to your homedirectory:
+
+
<%= file_field "pause99_add_uri_httpupload",
+ size => 50,
+%>
+
+
+
+% }
+
+
+
If you want me to fetch a file from an URL, enter the full URL here.
+
+
<%= text_field "pause99_add_uri_uri",
+ size => 64,
+ maxlength => 255,
+%>
+
+
diff --git a/one-off-utils/schemachange-2025-04.sql b/one-off-utils/schemachange-2025-04.sql
new file mode 100644
index 000000000..b981d9722
--- /dev/null
+++ b/one-off-utils/schemachange-2025-04.sql
@@ -0,0 +1,3 @@
+ALTER TABLE usertable ADD COLUMN mfa tinyint(1) DEFAULT 0;
+ALTER TABLE usertable ADD COLUMN mfa_secret32 varchar(16);
+ALTER TABLE usertable ADD COLUMN mfa_recovery_codes text;
diff --git a/one-off-utils/schemachange-2025.sql b/one-off-utils/schemachange-2025.sql
new file mode 100644
index 000000000..b981d9722
--- /dev/null
+++ b/one-off-utils/schemachange-2025.sql
@@ -0,0 +1,3 @@
+ALTER TABLE usertable ADD COLUMN mfa tinyint(1) DEFAULT 0;
+ALTER TABLE usertable ADD COLUMN mfa_secret32 varchar(16);
+ALTER TABLE usertable ADD COLUMN mfa_recovery_codes text;
diff --git a/t/pause_2025/00_load.t b/t/pause_2025/00_load.t
new file mode 100644
index 000000000..be3dfbf20
--- /dev/null
+++ b/t/pause_2025/00_load.t
@@ -0,0 +1,20 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use Test::PAUSE::Web;
+use Test::More;
+use File::Find;
+use Path::Tiny;
+
+note "AppRoot: $Test::PAUSE::Web::AppRoot";
+
+find({wanted => sub {
+ my $file = path($File::Find::name);
+ my $path = $file->relative("$Test::PAUSE::Web::AppRoot/lib/pause_2025");
+ $path =~ s|\.pm$|| or return;
+ $path =~ s|/|::|g;
+ use_ok($path);
+}, no_chdir => 1}, "$Test::PAUSE::Web::AppRoot/lib/pause_2025/PAUSE");
+
+done_testing;
+
diff --git a/t/pause_2025/action_2017/add_uri.t b/t/pause_2025/action_2017/add_uri.t
new file mode 100644
index 000000000..5d1c22f66
--- /dev/null
+++ b/t/pause_2025/action_2017/add_uri.t
@@ -0,0 +1,296 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use File::Path qw/rmtree mkpath/;
+use File::Spec;
+use Mojo::File qw/path/;
+use utf8;
+
+my $http_upload = {
+ pause99_add_uri_httpupload => ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "index.html"],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $uri_upload = {
+ pause99_add_uri_uri => "file://".File::Spec->rel2abs(__FILE__),
+ SUBMIT_pause99_add_uri_uri => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=add_uri");
+ # note $t->content;
+ }
+};
+
+subtest 'get: user with subdirs' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $user_home = $PAUSE::Config->{MLROOT}."/".PAUSE::user2dir($user);
+ my $subdir = path("$user_home/test");
+ $subdir->make_path;
+ $subdir->child("stuff.txt")->spew("Foo");
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=add_uri");
+ $t->text_is('select[name="pause99_add_uri_subdirscrl"] option[value="."]', "."); # default
+ $t->text_is('select[name="pause99_add_uri_subdirscrl"] option[value="test"]', "test");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: under a new subdir' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_subdirtext} = "new_dir";
+
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ like $rows->[0]{uriid} => qr!/new_dir/!, "uriid contains /new_dir/";
+ }
+};
+
+subtest 'post: under a Perl6 subdir' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_subdirscrl} = "Perl6";
+
+ my $user_home = $PAUSE::Config->{MLROOT}."/".PAUSE::user2dir($user);
+ my $subdir = path("$user_home/Perl6");
+ $subdir->make_path;
+ $subdir->child("stuff.txt")->spew("Foo");
+
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ like $rows->[0]{uriid} => qr!/Perl6/!, "uriid contains /Perl6/";
+ ok $rows->[0]{is_perl6};
+ }
+};
+
+subtest 'post: empty' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = [undef, 'index.html'];
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: renamed' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", 'html/index.html'];
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/index.html";
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ # renamed file exists
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => "index.html",
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: uri' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$uri_upload;
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_uri},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: CHECKSUMS' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "CHECKSUMS"],
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ $t->text_like('.error_message' => qr/Files with the name CHECKSUMS cannot be/);
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: allow overwrite' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exists";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ for (0 .. 1) {
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ # uploaded file exists
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+ }
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: duplicate' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "index.tar.gz"],
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+
+ my $res = $t->post("$path?ACTION=add_uri", \%form, "Content-Type" => "form-data");
+ is $res->code => 409;
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: to the site top, as various CPAN uploaders do/did' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("$path", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/add_user.t b/t/pause_2025/action_2017/add_user.t
new file mode 100644
index 000000000..0912d03a3
--- /dev/null
+++ b/t/pause_2025/action_2017/add_user.t
@@ -0,0 +1,276 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $new_user = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "NEWUSER",
+ pause99_add_user_fullname => "new user",
+ pause99_add_user_email => "new_user\@localhost.localdomain",
+ pause99_add_user_homepage => "http://home.page",
+};
+
+my $new_mailing_list = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "MAILLIST",
+ pause99_add_user_fullname => "Mailing List",
+ pause99_add_user_email => "ml\@localhost.localdomain",
+ pause99_add_user_subscribe => "how to subscribe",
+};
+
+my $default = {
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "edit_ml",
+ pause99_select_ml_action_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=add_user");
+ # note $t->content;
+ }
+};
+
+subtest 'post: ordinary user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", $new_user);
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ for my $key (qw/userid homepage fullname/) {
+ is $rows->[0]{$key} => $new_user->{"pause99_add_user_$key"}, "$key is stored correctly";
+ }
+ is $rows->[0]{email} => 'CENSORED'; # email in the user table is always CENSORED
+
+ # email tests; censored email shouldn't be disclosed to admins
+ my @deliveries = $t->deliveries;
+ my @welcome_emails = grep { $_->header('Subject') =~ /Welcome/ } @deliveries;
+ is @welcome_emails => 2;
+ my ($welcome_for_user) = grep { $_->header('To') =~ /new_user/ } @welcome_emails;
+ like $welcome_for_user->body => qr/email:\s+new_user\@localhost/;
+
+ my ($welcome_for_admins) = grep { $_->header('To') =~ /admin/ } @welcome_emails;
+ unlike $welcome_for_admins->body => qr/email:\s+new_user\@localhost/;
+ like $welcome_for_admins->body => qr/email:\s+CENSORED/;
+ }
+};
+
+subtest 'post: user with an accent in their name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %$new_user,
+ pause99_add_user_fullname => "T\xc3\xa9st Name",
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ SKIP: {
+ skip "FIXME: seems not so stable; probably needs more explicit configuration", 1;
+ is $rows->[0]{fullname} => "T\xc3\xa9st Name";
+ }
+ }
+};
+
+subtest 'post: soundex' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new user';
+ $copied_user{SUBMIT_pause99_add_user_Soundex} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: soundex error: similar name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new nome';
+ $copied_user{SUBMIT_pause99_add_user_Soundex} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+
+ # new user does not exist
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: metaphone' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new user';
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: metaphone error: similar name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new nome';
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+
+ # new user does not exist
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: metaphone error: completely duplicated' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+
+ $t->post_ok("$path?ACTION=add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+ }
+};
+
+subtest 'post: mailing list' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=add_user", $new_mailing_list);
+ # note $t->content;
+
+ # new mailing list exists
+ my $rows = $t->mod_db->select('maillists', ['*'], {
+ maillistid => $new_mailing_list->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+
+ # new user also exists
+ $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_mailing_list->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'get: retrieve a stored session' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %requested_user;
+ for my $key (keys %$new_user) {
+ next if $key =~ /SUBMIT/;
+ my $new_key = $key =~ s/add_user/request_id/r;
+ $requested_user{$new_key} = $new_user->{$key};
+ }
+ $requested_user{pause99_request_id_rationale} = 'Rational to request PAUSE ID';
+ $requested_user{SUBMIT_pause99_request_id_sub} = 1;
+
+ $t->reset_fixture;
+ $t->post_ok("$path?ACTION=request_id", \%requested_user);
+ my ($email) = map {$_->body} $t->deliveries;
+ my ($userid) = $email =~ m!https://.+?/pause/authenquery.+?USERID=([^&\s]+)!;
+ like $userid => qr/\A\d+_\w+\z/;
+ $t->clear_deliveries;
+
+ $t->get_ok("$path?ACTION=add_user&USERID=$userid");
+ # note $t->content;
+
+ for my $key (keys %$new_user) {
+ next if $key =~ /SUBMIT/;
+ is $t->dom->at("input[name=$key]")->attr('value') => $new_user->{$key}, "$key is set correctly";
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/change_passwd.t b/t/pause_2025/action_2017/change_passwd.t
new file mode 100644
index 000000000..c447fb14c
--- /dev/null
+++ b/t/pause_2025/action_2017/change_passwd.t
@@ -0,0 +1,227 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use Time::Piece;
+use utf8;
+
+my $default = {
+ pause99_change_passwd_pw1 => "new_pass",
+ pause99_change_passwd_pw2 => "new_pass",
+ pause99_change_passwd_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=change_passwd");
+ # note $t->content;
+ }
+};
+
+subtest 'get: public without ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ my $res = $t->get("$path?ACTION=change_passwd");
+ is $res->code => 403;
+ # note $t->content;
+ }
+};
+
+subtest 'get: public with ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ $t->get_ok("$path?ACTION=change_passwd&ABRA=$chuser.$chpass");
+ # note $t->content;
+
+ # No links should keep ABRA (71a745d)
+ my @links = map {$_->attr('href')} $t->dom->at('a');
+ ok !grep {$_ =~ /ABRA=/} @links;
+ }
+};
+
+subtest 'post: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my $res = $t->post("$path?ACTION=change_passwd", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_passwd", \%form)
+ ->text_like("p.password_stored", qr/New password stored/);
+ is $t->deliveries => 1, "one delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: user with CENSORED email' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ $user = "TESTCNSRD" if $user eq "TESTUSER";
+
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_passwd", \%form)
+ ->text_like("p.password_stored", qr/New password stored/);
+ my @deliveries = $t->deliveries;
+ is @deliveries => 1, "one delivery for admin";
+ my $email = $deliveries[0]->as_string;
+ unlike $email => qr/CENSORED/;
+ like $email => qr/testcnsrd\@localhost/;
+ note $email;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: public without ABRA' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+
+ my %form = %$default;
+ my $res = $t->post_with_token("$path?ACTION=change_passwd", \%form);
+ is $res->code => 403;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: public with ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ my %form = %$default;
+ $t->post_with_token_ok("$path?ACTION=change_passwd&ABRA=$chuser.$chpass", \%form);
+ $t->text_like("p.password_stored", qr/New password stored/);
+ # note $t->content;
+
+ # No links should keep ABRA (71a745d)
+ my @links = map {$_->attr('href')} $t->dom->at('a');
+ ok !grep {$_ =~ /ABRA=/} @links;
+
+ # Used ABRA is gone (8234a6a)
+ my $res = $t->post_with_token("$path?ACTION=change_passwd&ABRA=$chuser.$chpass", \%form);
+ ok !$res->is_success;
+ is $res->code => 401;
+ }
+};
+
+subtest 'post_with_token: public with incorrect ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ my %form = %$default;
+ my $res = $t->post_with_token("$path?ACTION=change_passwd&ABRA=$chuser.wrong$chpass", \%form);
+ is $res->code => 401;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: passwords mismatch' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw2 => "wrong_pass",
+ );
+ $t->post_with_token_ok("$path?ACTION=change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/The two passwords didn't match./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: only one password' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw2 => undef,
+ );
+ $t->post_with_token_ok("$path?ACTION=change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/You need to fill in the same password in both fields./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: no password' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw1 => undef,
+ pause99_change_passwd_pw2 => undef,
+ );
+ $t->post_with_token_ok("$path?ACTION=change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/Please fill in the form with passwords./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/change_user_status.t b/t/pause_2025/action_2017/change_user_status.t
new file mode 100644
index 000000000..f5a6fbaa1
--- /dev/null
+++ b/t/pause_2025/action_2017/change_user_status.t
@@ -0,0 +1,98 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+
+my $default = {
+ pause99_change_user_status_user => "TESTUSER",
+ pause99_change_user_status_new_ustatus => "nologin",
+ pause99_change_user_status_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=change_user_status");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my $res = $t->post("$path?ACTION=change_user_status", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/);
+ is $t->deliveries => 2, "two deliveries for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: user not found' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = (
+ %$default,
+ pause99_change_user_status_user => 'UNKNOWN',
+ );
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/User UNKNOWN is not found/);
+ is $t->deliveries => 0, "no deliveries for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: ustatus not changed' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/);
+ is $t->deliveries => 2, "two deliveries for admin";
+ # note $t->content;
+
+ # nologin to nologin
+ $t->post_with_token_ok("$path?ACTION=change_user_status", \%form)
+ ->dom_not_found("div.messagebox p");
+ is $t->deliveries => 0, "no deliveries for admin";
+ }
+};
+
+subtest 'post_with_token: unknown ustatus' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = (
+ %$default,
+ pause99_change_user_status_new_ustatus => 'unknown',
+ );
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->post_with_token_ok("$path?ACTION=change_user_status", \%form)
+ ->dom_not_found("div.messagebox p");
+ is $t->deliveries => 0, "no deliveries for admin";
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/delete_files.t b/t/pause_2025/action_2017/delete_files.t
new file mode 100644
index 000000000..07ea06525
--- /dev/null
+++ b/t/pause_2025/action_2017/delete_files.t
@@ -0,0 +1,244 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => [Test::PAUSE::Web->file_to_upload],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_delete_files_FILE => ["Hash-RenameKey-0.02.tar.gz"],
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=delete_files");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/ACTION=delete_files/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 1;
+ like $rows->[0]{deleteid} => qr!/$form{pause99_delete_files_FILE}[0]$!;
+
+ # undelete
+ delete $form{SUBMIT_pause99_delete_files_delete};
+ $form{SUBMIT_pause99_delete_files_undelete} = 1;
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ ok $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: absolute path' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ ok(File::Spec->file_name_is_absolute($copied));
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => [$copied],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: illegal filename/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: file not found' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => ['Something-Else-0.02.tar.gz'],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: file not found/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: CHECKSUMS' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ $t->save_to_authors_dir($user, "CHECKSUMS", "CHECKSUMS");
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => ['CHECKSUMS'],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: CHECKSUMS not erasable/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: readme' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ $t->save_to_authors_dir($user, "Hash-RenameKey-0.02.readme", "README");
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ # .readme is deleted when a related tarball is removed
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/\.readme/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 2;
+ ok grep {$_->{deleteid} =~ /\.readme$/} @$rows;
+ }
+};
+
+subtest 'post: delete by admin using select_user' => sub {
+ {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ }
+ {
+ my $test = Test::PAUSE::Web->tests_for('admin');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %action_form = (
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "delete_files",
+ pause99_select_user_sub => 1,
+ );
+ $t->post_ok("$path?ACTION=select_user", \%action_form);
+ # note $t->content;
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $form{HIDDENNAME} = "TESTUSER";
+ $t->post_ok("$path?ACTION=delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 3; # for TESTUSER, TESTADMIN, pause_admin
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/ACTION=delete_files/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 1;
+ like $rows->[0]{deleteid} => qr!/$form{pause99_delete_files_FILE}[0]$!;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/edit_cred.t b/t/pause_2025/action_2017/edit_cred.t
new file mode 100644
index 000000000..507859f8f
--- /dev/null
+++ b/t/pause_2025/action_2017/edit_cred.t
@@ -0,0 +1,65 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_edit_cred_fullname => "new fullname",
+ pause99_edit_cred_asciiname => "new ascii name",
+ pause99_edit_cred_email => "new_email\@localhost.localdomain",
+ pause99_edit_cred_homepage => "none",
+ pause99_edit_cred_cpan_mail_alias => "none",
+ pause99_edit_cred_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=edit_cred");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ plan skip_all => 'SKIP for now';
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=edit_cred", \%form);
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = %$default;
+ $t->post_with_token_ok("$path?ACTION=edit_cred", \%form);
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: edit with CENSORED email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ Test::PAUSE::Web->setup;
+ $t->mod_db->update('users', { email => 'CENSORED' }, { userid => $user });
+ my %form = (%$default, pause99_edit_cred_email => 'CENSORED');
+ $t->post_with_token_ok("$path?ACTION=edit_cred", \%form);
+ my @deliveries = $t->deliveries;
+ like $deliveries[0]->as_string => qr/\[CENSORED\]/;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/edit_ml.t b/t/pause_2025/action_2017/edit_ml.t
new file mode 100644
index 000000000..535653cd9
--- /dev/null
+++ b/t/pause_2025/action_2017/edit_ml.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=edit_ml");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/edit_uris.t b/t/pause_2025/action_2017/edit_uris.t
new file mode 100644
index 000000000..80c882fdc
--- /dev/null
+++ b/t/pause_2025/action_2017/edit_uris.t
@@ -0,0 +1,45 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => ["$Test::PAUSE::Web::AppRoot/t/staging/Hash-RenameKey-0.02.tar.gz", "Hash-RenameKey-0.02.tar.gz"],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_edit_uris_3 => "T/TE/TESTUSER/Hash-RenameKey-0.02.tar.gz",
+ pause99_edit_uris_2 => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=edit_uris");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my %form = %$default;
+ $form{pause99_edit_uris_3} =~ s/TESTUSER/$user/;
+ $t->post_ok("$path?ACTION=edit_uris", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/email_for_admin.t b/t/pause_2025/action_2017/email_for_admin.t
new file mode 100644
index 000000000..4d86b2991
--- /dev/null
+++ b/t/pause_2025/action_2017/email_for_admin.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=email_for_admin");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/giveup_comaint.t b/t/pause_2025/action_2017/giveup_comaint.t
new file mode 100644
index 000000000..c1cb6e4f0
--- /dev/null
+++ b/t/pause_2025/action_2017/giveup_comaint.t
@@ -0,0 +1,112 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_remome_m => "Module::Comaint",
+ SUBMIT_pause99_share_perms_remome => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=giveup_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case (comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remome_m => [qw/Module::Comaint Module::Comaint::Foo/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=giveup_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint.',
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint::Foo.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER from co-maintainers of Module::Comaint.',
+ 'Removed TESTUSER from co-maintainers of Module::Comaint::Foo.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remome_m => [qw/Module::Unrelated Module::Unrelated::Foo/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=giveup_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be co-maintainer of Module::Unrelated'
+ ]) or note explain \@errors;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/giveup_dist_comaint.t b/t/pause_2025/action_2017/giveup_dist_comaint.t
new file mode 100644
index 000000000..7e4e97a91
--- /dev/null
+++ b/t/pause_2025/action_2017/giveup_dist_comaint.t
@@ -0,0 +1,109 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_giveup_dist_comaint_d => "Module-Comaint",
+ SUBMIT_pause99_giveup_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ Test::PAUSE::Web->reset_module_fixture;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=giveup_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case (comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_giveup_dist_comaint_d => [qw/Module-Comaint/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=giveup_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint (Module-Comaint).',
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint::Foo (Module-Comaint).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER from co-maintainers of Module::Comaint (Module-Comaint).',
+ 'Removed TESTUSER from co-maintainers of Module::Comaint::Foo (Module-Comaint).',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_giveup_dist_comaint_d => [qw/Module-Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=giveup_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be co-maintainer of Module-Unrelated'
+ ]) or note explain \@errors;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/mailpw.t b/t/pause_2025/action_2017/mailpw.t
new file mode 100644
index 000000000..5aadcfe7e
--- /dev/null
+++ b/t/pause_2025/action_2017/mailpw.t
@@ -0,0 +1,175 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_mailpw_1 => "TESTUSER",
+ pause99_mailpw_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=mailpw");
+ #note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ my $res = $t->post("$path?ACTION=mailpw", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ # note $t->content;
+ }
+};
+
+subtest 'got an email instead of a userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'INV@LID',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/Please supply a userid/s);
+ }
+};
+
+subtest 'invalid userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'INV#LID',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A userid of INV#LID is not allowed/s);
+ }
+};
+
+subtest 'cannot find a userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'NOTFOUND',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/Cannot find a userid.+NOTFOUND/s);
+ # note $t->content;
+ }
+};
+
+subtest 'no secretmail' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->authen_db->update('usertable', {secretemail => undef}, {user => "TESTUSER"});
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ # note $t->content;
+ }
+
+ Test::PAUSE::Web->setup; # restore the original state
+};
+
+subtest 'requested recently' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A token for TESTUSER that allows/s);
+ # note $t->content;
+ }
+};
+
+subtest 'user without an entry in usertable: has email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => "OTHERUSER",
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->mod_db->insert('users', {
+ userid => 'OTHERUSER',
+ email => 'foo@localhost',
+ }, {replace => 1});
+ $t->authen_db->delete('usertable', {user => 'OTHERUSER'});
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+
+ # new usertable entry is created
+ ok @{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ #note $t->content;
+ }
+};
+
+subtest 'user without an entry in usertable: without email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => "OTHERUSER",
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->mod_db->insert('users', {
+ userid => 'OTHERUSER',
+ email => '',
+ }, {replace => 1});
+ $t->authen_db->delete('usertable', {user => 'OTHERUSER'});
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ $t->post_with_token_ok("$path?ACTION=mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A userid of OTHERUSER\s+is not known/s);
+
+ # new usertable entry is not created
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ #note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/make_comaint.t b/t/pause_2025/action_2017/make_comaint.t
new file mode 100644
index 000000000..42483c71c
--- /dev/null
+++ b/t/pause_2025/action_2017/make_comaint.t
@@ -0,0 +1,169 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_makeco_m => [],
+ pause99_share_perms_makeco_a => "TESTUSER2",
+ SUBMIT_pause99_share_perms_makeco => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=make_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => \@packages,
+ pause99_share_perms_makeco_a => "TESTUSER4",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::User::Bar.',
+ ]);
+ }
+ note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => \@packages,
+ pause99_share_perms_makeco_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => [qw/Module::Unrelated/],
+ pause99_share_perms_makeco_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module::Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/make_dist_comaint.t b/t/pause_2025/action_2017/make_dist_comaint.t
new file mode 100644
index 000000000..2bd1cc9d3
--- /dev/null
+++ b/t/pause_2025/action_2017/make_dist_comaint.t
@@ -0,0 +1,165 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_make_dist_comaint_d => [],
+ pause99_make_dist_comaint_a => "TESTUSER2",
+ SUBMIT_pause99_make_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=make_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => \@dists,
+ pause99_make_dist_comaint_a => "TESTUSER4",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_dist_comaint", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Bar (Module-Admin).',
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::User::Bar (Module-User).',
+ 'Added TESTUSER4 to co-maintainers of Module::User::Foo (Module-User).',
+ ]);
+ }
+
+ # note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => \@dists,
+ pause99_make_dist_comaint_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_dist_comaint", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => [qw/Module-Unrelated/],
+ pause99_make_dist_comaint_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=make_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module-Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/manage_id_requests.t b/t/pause_2025/action_2017/manage_id_requests.t
new file mode 100644
index 000000000..9113a3a83
--- /dev/null
+++ b/t/pause_2025/action_2017/manage_id_requests.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=manage_id_requests");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/move_dist_primary.t b/t/pause_2025/action_2017/move_dist_primary.t
new file mode 100644
index 000000000..bcbd2132f
--- /dev/null
+++ b/t/pause_2025/action_2017/move_dist_primary.t
@@ -0,0 +1,161 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_move_dist_primary_d => [],
+ pause99_move_dist_primary_a => "TESTUSER2",
+ SUBMIT_pause99_move_dist_primary => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=move_dist_primary");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => \@dists,
+ pause99_move_dist_primary_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_dist_primary", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Bar (Module-Admin).',
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::User::Bar (Module-User).',
+ 'Made TESTUSER2 primary maintainer of Module::User::Foo (Module-User).',
+ ]);
+ }
+ note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => \@dists,
+ pause99_move_dist_primary_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_dist_primary", \%form);
+ my @new_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@new_dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@new_dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => [qw/Module-Unrelated/],
+ pause99_move_dist_primary_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_dist_primary", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module-Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/move_primary.t b/t/pause_2025/action_2017/move_primary.t
new file mode 100644
index 000000000..e9a739229
--- /dev/null
+++ b/t/pause_2025/action_2017/move_primary.t
@@ -0,0 +1,168 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_pr_m => [],
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ SUBMIT_pause99_share_perms_movepr => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=move_primary");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::User::Bar.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ pause99_share_perms_movepr_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => [qw/Module::Unrelated/],
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module::Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_04about.t b/t/pause_2025/action_2017/pause_04about.t
new file mode 100644
index 000000000..05773616f
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_04about.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_04about");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_04imprint.t b/t/pause_2025/action_2017/pause_04imprint.t
new file mode 100644
index 000000000..780605e78
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_04imprint.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_04imprint");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_05news.t b/t/pause_2025/action_2017/pause_05news.t
new file mode 100644
index 000000000..e033387c2
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_05news.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_05news");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_06history.t b/t/pause_2025/action_2017/pause_06history.t
new file mode 100644
index 000000000..79aed17ab
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_06history.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_06history");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_logout.t b/t/pause_2025/action_2017/pause_logout.t
new file mode 100644
index 000000000..2459230d3
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_logout.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_logout");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_namingmodules.t b/t/pause_2025/action_2017/pause_namingmodules.t
new file mode 100644
index 000000000..443064d5d
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_namingmodules.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_namingmodules");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_operating_model.t b/t/pause_2025/action_2017/pause_operating_model.t
new file mode 100644
index 000000000..c502086ba
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_operating_model.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_operating_model");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/pause_privacy_policy.t b/t/pause_2025/action_2017/pause_privacy_policy.t
new file mode 100644
index 000000000..d33518728
--- /dev/null
+++ b/t/pause_2025/action_2017/pause_privacy_policy.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_privacy_policy");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/peek_dist_perms.t b/t/pause_2025/action_2017/peek_dist_perms.t
new file mode 100644
index 000000000..52532ea24
--- /dev/null
+++ b/t/pause_2025/action_2017/peek_dist_perms.t
@@ -0,0 +1,172 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use YAML::Syck;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_peek_dist_perms_query => "TESTUSER",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=peek_dist_perms");
+ # note $t->content;
+ }
+};
+
+subtest 'search by author' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => $user,
+ );
+ $t->$method("$path?ACTION=peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ ok grep(/^Module-Comaint/, @dists), 'Module-Comaint is also listed';
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_dist_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ if ($user eq 'TESTADMIN') {
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-Admin',
+ 'owner' => 'TESTADMIN',
+ 'comaint' => 'TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-Comaint',
+ 'owner' => 'TESTUSER2',
+ 'comaint' => 'TESTADMIN',
+ },
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN',
+ },
+ ] );
+ }
+ if ($user eq 'TESTUSER') {
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-Comaint',
+ 'owner' => 'TESTUSER2',
+ 'comaint' => 'TESTUSER',
+ },
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-User-Foo-Baz',
+ 'owner' => 'TESTUSER',
+ 'comaint' => undef,
+ },
+ ] );
+ }
+ }
+ }
+};
+
+subtest 'search by dist (exact)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => 'Module-User',
+ pause99_peek_dist_perms_by => 'de',
+ );
+ $t->$method("$path?ACTION=peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_dist_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ ]);
+ }
+ }
+};
+
+subtest 'search by module (sql-like)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => 'Module-User%',
+ pause99_peek_dist_perms_by => 'dl',
+ );
+ $t->$method("$path?ACTION=peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_dist_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-User-Foo-Baz',
+ 'owner' => 'TESTUSER',
+ 'comaint' => undef,
+ },
+ ]);
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/peek_perms.t b/t/pause_2025/action_2017/peek_perms.t
new file mode 100644
index 000000000..74f816693
--- /dev/null
+++ b/t/pause_2025/action_2017/peek_perms.t
@@ -0,0 +1,240 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use YAML::Syck;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_peek_perms_query => "TESTUSER",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=peek_perms");
+ # note $t->content;
+ }
+};
+
+subtest 'search by author' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => $user,
+ );
+ $t->$method("$path?ACTION=peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'No co-maint';
+ }
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ if ($user eq 'TESTADMIN') {
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::Admin::Bar',
+ 'owner' => 'TESTADMIN',
+ 'type' => 'first-come',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Admin::Foo',
+ 'owner' => 'TESTADMIN',
+ 'type' => 'first-come',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Comaint',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Comaint::Foo',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ] );
+ }
+ if ($user eq 'TESTUSER') {
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo::Baz',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::Comaint',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::Comaint::Foo',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER'
+ },
+ ] );
+ }
+ }
+ }
+};
+
+subtest 'search by module (exact)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => 'Module::User::Foo',
+ pause99_peek_perms_by => 'me',
+ );
+ $t->$method("$path?ACTION=peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ cmp_set(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ]);
+ }
+ }
+};
+
+subtest 'search by module (sql-like)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => 'Module::User::%',
+ pause99_peek_perms_by => 'ml',
+ );
+ $t->$method("$path?ACTION=peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ # note $t->content;
+
+ $t->$method("$path?ACTION=peek_perms&OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo::Baz',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER2'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ]);
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/reindex.t b/t/pause_2025/action_2017/reindex.t
new file mode 100644
index 000000000..3d0df8430
--- /dev/null
+++ b/t/pause_2025/action_2017/reindex.t
@@ -0,0 +1,47 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => [Test::PAUSE::Web->file_to_upload],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_reindex_FILE => ["Hash-RemoteKey-0.02.tar.gz"],
+ SUBMIT_pause99_reindex_delete => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=reindex");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+
+ # prepare distribution
+ $t->post_ok("$path?ACTION=add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=reindex", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/remove_comaint.t b/t/pause_2025/action_2017/remove_comaint.t
new file mode 100644
index 000000000..752dcba37
--- /dev/null
+++ b/t/pause_2025/action_2017/remove_comaint.t
@@ -0,0 +1,172 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_remocos_tuples => [],
+ SUBMIT_pause99_share_perms_remocos => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=remove_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module::Admin::Bar -- TESTUSER2',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module::User::Bar -- TESTUSER2',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Bar.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not the owner)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => ['Module::Unrelated -- TESTUSER2'],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be owner of Module::Unrelated.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not the comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module::Admin::Bar -- TESTUSER4',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module::User::Bar -- TESTUSER4',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module::Admin::Bar -- TESTUSER4. If you believe, this is a bug, please complain.'
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module::User::Bar -- TESTUSER4. If you believe, this is a bug, please complain.'
+ ]);
+ }
+ ok !@results;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/remove_dist_comaint.t b/t/pause_2025/action_2017/remove_dist_comaint.t
new file mode 100644
index 000000000..db476fde3
--- /dev/null
+++ b/t/pause_2025/action_2017/remove_dist_comaint.t
@@ -0,0 +1,166 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_remove_dist_comaint_tuples => [],
+ SUBMIT_pause99_remove_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=remove_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module-Admin -- TESTUSER2',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module-User -- TESTUSER2',
+ );
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Bar (Module-Admin).',
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Bar (Module-User).',
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Foo (Module-User).',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not an owner)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => ['Module-Unrelated -- TESTUSER2'],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be owner of Module-Unrelated.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not a comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module-Admin -- TESTUSER4',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module-User -- TESTUSER4',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module-Admin -- TESTUSER4. If you believe, this is a bug, please complain.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module-User -- TESTUSER4. If you believe, this is a bug, please complain.',
+ ]);
+ }
+ ok !@results;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/remove_dist_primary.t b/t/pause_2025/action_2017/remove_dist_primary.t
new file mode 100644
index 000000000..06d7c3342
--- /dev/null
+++ b/t/pause_2025/action_2017/remove_dist_primary.t
@@ -0,0 +1,134 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_remove_dist_primary_d => [],
+ SUBMIT_pause99_remove_dist_primary => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=remove_dist_primary");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_primary_d => \@dists,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_dist_primary", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Bar (Module-Admin).',
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Foo (Module-Admin).',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("$path?ACTION=peek_dist_perms", {
+ pause99_peek_dist_perms_query => "ADOPTME",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+ });
+ my @adoptme_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@adoptme_dists, [qw/Module-Admin/]) or note explain \@adoptme_dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTUSER from Module::User::Bar (Module-User).',
+ 'Removed primary maintainership of TESTUSER from Module::User::Foo (Module-User).',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("$path?ACTION=peek_dist_perms", {
+ pause99_peek_dist_perms_query => "ADOPTME",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+ });
+ my @adoptme_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@adoptme_dists, [qw/Module-User/]) or note explain \@adoptme_dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_primary_d => [qw/Module-Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_dist_primary", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @warnings = map {$_->all_text} $t->dom->find('.warning')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@warnings, [
+ 'You need to select one or more distributions. Nothing done.',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/remove_primary.t b/t/pause_2025/action_2017/remove_primary.t
new file mode 100644
index 000000000..e6f82d58d
--- /dev/null
+++ b/t/pause_2025/action_2017/remove_primary.t
@@ -0,0 +1,138 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_pr_m => [],
+ SUBMIT_pause99_share_perms_remopr => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=remove_primary");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Bar.',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("$path?ACTION=peek_perms", {
+ pause99_peek_perms_query => "ADOPTME",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+ });
+ my @adoptme_modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ cmp_set(\@adoptme_modules, [qw/Module::Admin::Bar/]) or note explain \@adoptme_modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTUSER from Module::User::Bar.',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("$path?ACTION=peek_perms", {
+ pause99_peek_perms_query => "ADOPTME",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+ });
+ my @adoptme_modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ cmp_set(\@adoptme_modules, [qw/Module::User::Bar/]) or note explain \@adoptme_modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => [qw/Module::Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("$path?ACTION=remove_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @warnings = map {$_->all_text} $t->dom->find('.warning')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@warnings, [
+ 'You need to select one or more packages. Nothing done.',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/request_id.t b/t/pause_2025/action_2017/request_id.t
new file mode 100644
index 000000000..8936d08bb
--- /dev/null
+++ b/t/pause_2025/action_2017/request_id.t
@@ -0,0 +1,273 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_request_id_fullname => 'full name',
+ pause99_request_id_email => 'test@localhost.localdomain',
+ pause99_request_id_homepage => 'none',
+ pause99_request_id_userid => 'NEWUSER',
+ pause99_request_id_rationale => 'Hello, my ratoinale is to test PAUSE',
+ SUBMIT_pause99_request_id_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=request_id");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_like("pre.email_sent", qr/Subject: PAUSE ID request \(NEWUSER/);
+ is $t->deliveries => 2, "two deliveries (one for admin, one for requester)";
+ # note $t->content;
+ }
+};
+
+subtest 'post: thank you, bot' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ url => 'http://host/path',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form);
+ is $t->content => "Thank you!";
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no space in full name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_fullname => 'FULLNAME',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Name does not look like a full civil name/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no full name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_fullname => '',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a name/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_email => '',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply an email address/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: invalid email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_email => 'no email',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Your email address doesn't look like valid email address./);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: rational is too short' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => 'rationale',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/this looks a\s+bit too short/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+# XXX: might be better to ignore other attributes (or YAGNI)
+subtest 'post: rational has html links' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => '
',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Please do not use HTML links/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: multiple links' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => <<'SPAM',
+http://spam/path
+http://spam/path
+SPAM
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Please do not include more than one URL/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no rationale' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => '',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a short description/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: userid is taken' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => 'TESTUSER',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/The userid TESTUSER is already taken/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: invalid userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => 'INV#LID',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/The userid INV#LID does not match/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => '',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a desired user-ID/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: lots of .info' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => <<'SPAM',
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+SPAM
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/rationale looks like spam/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: interesting .cn homepage' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_homepage => 'http://some.cn/index.htm',
+ pause99_request_id_rationale => 'interesting site',
+ );
+ $t->post_ok("$path?ACTION=request_id", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/rationale looks like spam/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/reset_version.t b/t/pause_2025/action_2017/reset_version.t
new file mode 100644
index 000000000..45c37dc89
--- /dev/null
+++ b/t/pause_2025/action_2017/reset_version.t
@@ -0,0 +1,48 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_reset_version_PKG => ["Foo"],
+ SUBMIT_pause99_reset_version_forget => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=reset_version");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE packages");
+ $t->mod_db->insert('packages', {
+ package => "Foo",
+ version => "0.01",
+ dist => "T/TE/$user/Foo-0.01.tar.gz",
+ file => "Foo-0.01.tar.gz",
+ });
+ $t->mod_db->insert('packages', {
+ package => "Bar",
+ version => "0.02",
+ dist => "T/TE/$user/Bar-0.02.tar.gz",
+ file => "Bar-0.02.tar.gz",
+ });
+
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=reset_version", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/select_ml_action.t b/t/pause_2025/action_2017/select_ml_action.t
new file mode 100644
index 000000000..75bda972b
--- /dev/null
+++ b/t/pause_2025/action_2017/select_ml_action.t
@@ -0,0 +1,49 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $mailing_list = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "MAILLIST",
+ pause99_add_user_email => "ml\@localhost.localdomain",
+ pause99_add_user_subscribe => "how to subscribe",
+};
+
+my $default = {
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "edit_ml",
+ pause99_select_ml_action_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=select_ml_action");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->post_ok("$path?ACTION=add_user", $mailing_list);
+
+ $t->mod_db->insert("list2user", {
+ maillistid => "MAILLIST",
+ userid => "TESTUSER",
+ }, {ignore => 1});
+
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=select_ml_action", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/select_user.t b/t/pause_2025/action_2017/select_user.t
new file mode 100644
index 000000000..3d6ac280e
--- /dev/null
+++ b/t/pause_2025/action_2017/select_user.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=select_user");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/share_perms.t b/t/pause_2025/action_2017/share_perms.t
new file mode 100644
index 000000000..76c4be7bf
--- /dev/null
+++ b/t/pause_2025/action_2017/share_perms.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=share_perms");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/show_files.t b/t/pause_2025/action_2017/show_files.t
new file mode 100644
index 000000000..9c6b61797
--- /dev/null
+++ b/t/pause_2025/action_2017/show_files.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=show_files");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/show_ml_repr.t b/t/pause_2025/action_2017/show_ml_repr.t
new file mode 100644
index 000000000..2fc8bce41
--- /dev/null
+++ b/t/pause_2025/action_2017/show_ml_repr.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=show_ml_repr");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/tail_logfile.t b/t/pause_2025/action_2017/tail_logfile.t
new file mode 100644
index 000000000..49bb91888
--- /dev/null
+++ b/t/pause_2025/action_2017/tail_logfile.t
@@ -0,0 +1,43 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_tail_logfile_1 => 5000,
+ pause99_tail_logfile_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+{
+ open my $fh, '>', $PAUSE::Config->{PAUSE_LOG};
+ say $fh < sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("/pause/authenquery?ACTION=tail_logfile");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ my %form = %$default;
+ $t->post_ok("$path?ACTION=tail_logfile", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/who_admin.t b/t/pause_2025/action_2017/who_admin.t
new file mode 100644
index 000000000..f8a41cb07
--- /dev/null
+++ b/t/pause_2025/action_2017/who_admin.t
@@ -0,0 +1,44 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use YAML::Syck ();
+
+Test::PAUSE::Web->setup;
+
+# SELECT user FROM grouptable WHERE ugroup='admin' order by user");
+subtest 'get' => sub {
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "FOO",
+ ugroup => "admin",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAR",
+ ugroup => "admin",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAZ",
+ ugroup => "bar",
+ });
+
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->get_ok("$path?ACTION=who_admin")
+ ->text_like('body', qr/Registered admins:\s+BAR, FOO/);
+
+ $t->get_ok("$path?ACTION=who_admin&OF=YAML");
+ my $list_amp = YAML::Syck::Load( $t->content );
+ is_deeply( $list_amp, [qw/BAR FOO TESTADMIN/], "YAML output works" );
+
+ SKIP: {
+ skip "; is not supported anymore", 1;
+ $t->get_ok("$path?ACTION=who_admin;OF=YAML");
+ my $list_sem = YAML::Syck::Load( $t->content );
+ is_deeply( $list_sem, [qw/BAR FOO TESTADMIN/], "YAML output works" );
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2017/who_pumpkin.t b/t/pause_2025/action_2017/who_pumpkin.t
new file mode 100644
index 000000000..4e60d2bc5
--- /dev/null
+++ b/t/pause_2025/action_2017/who_pumpkin.t
@@ -0,0 +1,44 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use YAML::Syck ();
+
+Test::PAUSE::Web->setup;
+
+# SELECT user FROM grouptable WHERE ugroup='pumpking' order by user");
+subtest 'get' => sub {
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "FOO",
+ ugroup => "pumpking",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAR",
+ ugroup => "pumpking",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAZ",
+ ugroup => "baz",
+ });
+
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+
+ $t->get_ok("$path?ACTION=who_pumpkin")
+ ->text_like("body", qr/Registered pumpkins:\s+BAR, FOO/);
+
+ $t->get_ok("$path?ACTION=who_pumpkin&OF=YAML");
+ my $list_amp = YAML::Syck::Load( $t->content );
+ is_deeply( $list_amp, [qw/BAR FOO/], "YAML output works" );
+
+ SKIP: {
+ skip "; is not supported anymore", 1;
+ $t->get_ok("$path?ACTION=who_pumpkin;OF=YAML");
+ my $list_sem = YAML::Syck::Load( $t->content );
+ is_deeply( $list_sem, [qw/BAR FOO/], "YAML output works" );
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/add_uri.t b/t/pause_2025/action_2025/add_uri.t
new file mode 100644
index 000000000..e911c50e0
--- /dev/null
+++ b/t/pause_2025/action_2025/add_uri.t
@@ -0,0 +1,308 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use File::Path qw/rmtree mkpath/;
+use File::Spec;
+use Mojo::File qw/path/;
+use utf8;
+
+my $http_upload = {
+ pause99_add_uri_httpupload => ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "index.html"],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $uri_upload = {
+ pause99_add_uri_uri => "file://".File::Spec->rel2abs(__FILE__),
+ SUBMIT_pause99_add_uri_uri => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("user/add_uri");
+ # note $t->content;
+ }
+};
+
+subtest 'get: user with subdirs' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $user_home = $PAUSE::Config->{MLROOT}."/".PAUSE::user2dir($user);
+ my $subdir = path("$user_home/test");
+ $subdir->make_path;
+ $subdir->child("stuff.txt")->spew("Foo");
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/add_uri");
+ $t->text_is('select[name="pause99_add_uri_subdirscrl"] option[value="."]', "."); # default
+ $t->text_is('select[name="pause99_add_uri_subdirscrl"] option[value="test"]', "test");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: under a new subdir' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_subdirtext} = "new_dir";
+
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ like $rows->[0]{uriid} => qr!/new_dir/!, "uriid contains /new_dir/";
+ }
+};
+
+subtest 'post: under a Perl6 subdir' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_subdirscrl} = "Perl6";
+
+ my $user_home = $PAUSE::Config->{MLROOT}."/".PAUSE::user2dir($user);
+ my $subdir = path("$user_home/Perl6");
+ $subdir->make_path;
+ $subdir->child("stuff.txt")->spew("Foo");
+
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ like $rows->[0]{uriid} => qr!/Perl6/!, "uriid contains /Perl6/";
+ ok $rows->[0]{is_perl6};
+ }
+};
+
+subtest 'post: empty' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = [undef, 'index.html'];
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: renamed' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", 'html/index.html'];
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/index.html";
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ # renamed file exists
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => "index.html",
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: uri' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$uri_upload;
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_uri},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: CHECKSUMS' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "CHECKSUMS"],
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ $t->text_like('.error_message' => qr/Files with the name CHECKSUMS cannot be/);
+ # note $t->content;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: allow overwrite' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exists";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ for (0 .. 1) {
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ # uploaded file exists
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+ }
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: duplicate' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ $form{pause99_add_uri_httpupload} = ["$Test::PAUSE::Web::AppRoot/htdocs/index.html", "index.tar.gz"],
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/user/add_uri", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+
+ my $res = $t->post("/user/add_uri", \%form, "Content-Type" => "form-data");
+ is $res->code => 409;
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: to the site top, as various CPAN uploaders do/did' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$http_upload;
+ my $file = $PAUSE::Config->{INCOMING_LOC}."/".$form{pause99_add_uri_httpupload}[1];
+ ok !-f $file, "file to upload does not exist";
+
+ $t->mod_dbh->do('TRUNCATE uris');
+ $t->post_ok("/", \%form, "Content-Type" => "form-data");
+ # note $t->content;
+
+ ok -f $file, "uploaded file exists";
+ unlink $file;
+
+ my $rows = $t->mod_db->select('uris', ['*'], {
+ userid => $user,
+ uri => $form{pause99_add_uri_httpupload}[1],
+ });
+ is @$rows => 1;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/add_user.t b/t/pause_2025/action_2025/add_user.t
new file mode 100644
index 000000000..55c21afd2
--- /dev/null
+++ b/t/pause_2025/action_2025/add_user.t
@@ -0,0 +1,286 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $new_user = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "NEWUSER",
+ pause99_add_user_fullname => "new user",
+ pause99_add_user_email => "new_user\@localhost.localdomain",
+ pause99_add_user_homepage => "http://home.page",
+};
+
+my $new_mailing_list = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "MAILLIST",
+ pause99_add_user_fullname => "Mailing List",
+ pause99_add_user_email => "ml\@localhost.localdomain",
+ pause99_add_user_subscribe => "how to subscribe",
+};
+
+my $default = {
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "edit_ml",
+ pause99_select_ml_action_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/add_user");
+ # note $t->content;
+ }
+};
+
+subtest 'post: ordinary user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", $new_user);
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ for my $key (qw/userid homepage fullname/) {
+ is $rows->[0]{$key} => $new_user->{"pause99_add_user_$key"}, "$key is stored correctly";
+ }
+ is $rows->[0]{email} => 'CENSORED'; # email in the user table is always CENSORED
+
+ # email tests; censored email shouldn't be disclosed to admins
+ my @deliveries = $t->deliveries;
+ my @welcome_emails = grep { $_->header('Subject') =~ /Welcome/ } @deliveries;
+ is @welcome_emails => 2;
+ my ($welcome_for_user) = grep { $_->header('To') =~ /new_user/ } @welcome_emails;
+ like $welcome_for_user->body => qr/email:\s+new_user\@localhost/;
+
+ my ($welcome_for_admins) = grep { $_->header('To') =~ /admin/ } @welcome_emails;
+ unlike $welcome_for_admins->body => qr/email:\s+new_user\@localhost/;
+ like $welcome_for_admins->body => qr/email:\s+CENSORED/;
+ }
+};
+
+subtest 'post: user with an accent in their name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %$new_user,
+ pause99_add_user_fullname => "T\xc3\xa9st Name",
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ SKIP: {
+ skip "FIXME: seems not so stable; probably needs more explicit configuration", 1;
+ is $rows->[0]{fullname} => "T\xc3\xa9st Name";
+ }
+ }
+};
+
+subtest 'post: soundex' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new user';
+ $copied_user{SUBMIT_pause99_add_user_Soundex} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: soundex error: similar name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new nome';
+ $copied_user{SUBMIT_pause99_add_user_Soundex} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+
+ # new user does not exist
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: metaphone' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new user';
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+ # note $t->content;
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'post: metaphone error: similar name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{pause99_add_user_fullname} = 'new nome';
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+
+ # new user does not exist
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 0;
+ }
+};
+
+subtest 'post: metaphone error: completely duplicated' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %copied_user = %$new_user;
+ $copied_user{SUBMIT_pause99_add_user_Metaphone} = 1;
+ delete $copied_user{SUBMIT_pause99_add_user_Definitely};
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+
+ # new user exists
+ my $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_user->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+
+ $t->post_ok("/admin/add_user", {
+ %copied_user,
+ });
+ $t->text_like('h3', qr/Not submitting NEWUSER, maybe we have a duplicate/);
+ # note $t->content;
+ }
+};
+
+subtest 'post: mailing list' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->reset_fixture;
+ $t->post_ok("/admin/add_user", $new_mailing_list);
+ # note $t->content;
+
+ # new mailing list exists
+ my $rows = $t->mod_db->select('maillists', ['*'], {
+ maillistid => $new_mailing_list->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+
+ # new user also exists
+ $rows = $t->mod_db->select('users', ['*'], {
+ userid => $new_mailing_list->{pause99_add_user_userid},
+ });
+ is @$rows => 1;
+ }
+};
+
+subtest 'get: retrieve a stored session' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %requested_user;
+ for my $key (keys %$new_user) {
+ next if $key =~ /SUBMIT/;
+ my $new_key = $key =~ s/add_user/request_id/r;
+ $requested_user{$new_key} = $new_user->{$key};
+ }
+ $requested_user{pause99_request_id_rationale} = 'Rational to request PAUSE ID';
+ $requested_user{SUBMIT_pause99_request_id_sub} = 1;
+
+ $t->reset_fixture;
+ $t->post_ok("/public/request_id", \%requested_user);
+ my ($email) = map {$_->body} $t->deliveries;
+ my ($userid) = $email =~ m!https://.+?/admin/add_user\?USERID=([^&\s]+)!;
+ like $userid => qr/\A\d+_\w+\z/;
+ $t->clear_deliveries;
+
+ $t->get_ok("/admin/add_user?USERID=$userid");
+ # note $t->content;
+
+ for my $key (keys %$new_user) {
+ next if $key =~ /SUBMIT/;
+ is $t->dom->at("input[name=$key]")->attr('value') => $new_user->{$key}, "$key is set correctly";
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/change_passwd.t b/t/pause_2025/action_2025/change_passwd.t
new file mode 100644
index 000000000..95168ecc2
--- /dev/null
+++ b/t/pause_2025/action_2025/change_passwd.t
@@ -0,0 +1,239 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use Time::Piece;
+use utf8;
+
+my $default = {
+ pause99_change_passwd_pw1 => "new_pass",
+ pause99_change_passwd_pw2 => "new_pass",
+ pause99_change_passwd_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/change_passwd");
+ # note $t->content;
+ }
+};
+
+subtest 'get: public without ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ my $res = $t->get("/public/change_passwd");
+ is $res->code => 403;
+ # note $t->content;
+ }
+};
+
+subtest 'get: public with ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ $t->get_ok("/public/change_passwd?ABRA=$chuser.$chpass");
+ # note $t->content;
+
+ # No links should keep ABRA (71a745d)
+ my @links = map {$_->attr('href')} $t->dom->at('a');
+ ok !grep {$_ =~ /ABRA=/} @links;
+ }
+};
+
+subtest 'post: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my $res = $t->post("/user/change_passwd", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/user/change_passwd", \%form)
+ ->text_like("p.password_stored", qr/New password stored/);
+ is $t->deliveries => 1, "one delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: user with CENSORED email' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ $user = "TESTCNSRD" if $user eq "TESTUSER";
+
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/user/change_passwd", \%form)
+ ->text_like("p.password_stored", qr/New password stored/);
+ my @deliveries = $t->deliveries;
+ is @deliveries => 1, "one delivery for admin";
+ my $email = $deliveries[0]->as_string;
+ unlike $email => qr/CENSORED/;
+ like $email => qr/testcnsrd\@localhost/;
+ note $email;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: public without ABRA' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+
+ my %form = %$default;
+ my $res = $t->post_with_token("/public/change_passwd", \%form);
+ is $res->code => 403;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: public with ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ my %form = %$default;
+ $t->post_with_token_ok("/public/change_passwd?ABRA=$chuser.$chpass", \%form);
+ $t->text_like("p.password_stored", qr/New password stored/);
+ # note $t->content;
+
+ # No links should keep ABRA (71a745d)
+ my @links = map {$_->attr('href')} $t->dom->at('a');
+ ok !grep {$_ =~ /ABRA=/} @links;
+
+ # Used ABRA is gone (8234a6a)
+ my $res = $t->post_with_token("/public/change_passwd?ABRA=$chuser.$chpass", \%form);
+ ok !$res->is_success;
+ is $res->code => 401;
+ }
+};
+
+subtest 'post_with_token: public with incorrect ABRA' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ next if $user; # public only
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my $chuser = 'TESTUSER';
+ my $chpass = 'testpassword';
+ $t->authen_dbh->do('TRUNCATE abrakadabra');
+ ok $t->authen_db->insert('abrakadabra', {
+ user => $chuser,
+ chpasswd => $chpass,
+ expires => Time::Piece->new(time + 3600)->strftime('%Y-%m-%d %H:%M:%S'),
+ });
+
+ my %form = %$default;
+ my $res = $t->post_with_token("/public/change_passwd?ABRA=$chuser.wrong$chpass", \%form);
+ is $res->code => 401;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: passwords mismatch' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw2 => "wrong_pass",
+ );
+ $t->post_with_token_ok("/user/change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/The two passwords didn't match./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: only one password' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw2 => undef,
+ );
+ $t->post_with_token_ok("/user/change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/You need to fill in the same password in both fields./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: no password' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_change_passwd_pw1 => undef,
+ pause99_change_passwd_pw2 => undef,
+ );
+ $t->post_with_token_ok("/user/change_passwd", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/Please fill in the form with passwords./);
+ ok !$t->deliveries, "no delivery for admin";
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/change_user_status.t b/t/pause_2025/action_2025/change_user_status.t
new file mode 100644
index 000000000..ae24f8ac4
--- /dev/null
+++ b/t/pause_2025/action_2025/change_user_status.t
@@ -0,0 +1,104 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+
+my $default = {
+ pause99_change_user_status_user => "TESTUSER",
+ pause99_change_user_status_new_ustatus => "nologin",
+ pause99_change_user_status_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/change_user_status");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my $res = $t->post("/admin/change_user_status", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/admin/change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/);
+ is $t->deliveries => 2, "two deliveries for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: user not found' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = (
+ %$default,
+ pause99_change_user_status_user => 'UNKNOWN',
+ );
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/admin/change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/User UNKNOWN is not found/);
+ is $t->deliveries => 0, "no deliveries for admin";
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: ustatus not changed' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = %$default;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/admin/change_user_status", \%form)
+ ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/);
+ is $t->deliveries => 2, "two deliveries for admin";
+ # note $t->content;
+
+ # nologin to nologin
+ $t->post_with_token_ok("/admin/change_user_status", \%form)
+ ->dom_not_found("div.messagebox p");
+ is $t->deliveries => 0, "no deliveries for admin";
+ }
+};
+
+subtest 'post_with_token: unknown ustatus' => sub {
+ Test::PAUSE::Web->setup;
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my %form = (
+ %$default,
+ pause99_change_user_status_new_ustatus => 'unknown',
+ );
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->post_with_token_ok("/admin/change_user_status", \%form)
+ ->dom_not_found("div.messagebox p");
+ is $t->deliveries => 0, "no deliveries for admin";
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/delete_files.t b/t/pause_2025/action_2025/delete_files.t
new file mode 100644
index 000000000..7c6e03197
--- /dev/null
+++ b/t/pause_2025/action_2025/delete_files.t
@@ -0,0 +1,252 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => [Test::PAUSE::Web->file_to_upload],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_delete_files_FILE => ["Hash-RenameKey-0.02.tar.gz"],
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/delete_files");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr!/user/delete_files!;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 1;
+ like $rows->[0]{deleteid} => qr!/$form{pause99_delete_files_FILE}[0]$!;
+
+ # undelete
+ delete $form{SUBMIT_pause99_delete_files_delete};
+ $form{SUBMIT_pause99_delete_files_undelete} = 1;
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ ok $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: absolute path' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ ok(File::Spec->file_name_is_absolute($copied));
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => [$copied],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: illegal filename/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: file not found' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => ['Something-Else-0.02.tar.gz'],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: file not found/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: CHECKSUMS' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ $t->save_to_authors_dir($user, "CHECKSUMS", "CHECKSUMS");
+
+ # delete
+ my %form = (
+ pause99_delete_files_FILE => ['CHECKSUMS'],
+ SUBMIT_pause99_delete_files_delete => 1
+ );
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/WARNING: CHECKSUMS not erasable/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ ok !@$rows;
+ }
+};
+
+subtest 'post: readme' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ $t->save_to_authors_dir($user, "Hash-RenameKey-0.02.readme", "README");
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ # .readme is deleted when a related tarball is removed
+ my @deliveries = $t->deliveries;
+ is @deliveries => 2;
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr/\.readme/;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 2;
+ ok grep {$_->{deleteid} =~ /\.readme$/} @$rows;
+ }
+};
+
+subtest 'post: delete by admin using select_user' => sub {
+ {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+ $t->mod_dbh->do("TRUNCATE deletes");
+ $t->remove_authors_dir($user);
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my $copied = $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+ }
+ {
+ my $test = Test::PAUSE::Web->tests_for('admin');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %action_form = (
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "delete_files",
+ pause99_select_user_sub => 1,
+ );
+ $t->post_ok("/admin/select_user", \%action_form);
+ # note $t->content;
+
+ # delete
+ my %form = %$default;
+ $form{SUBMIT_pause99_delete_files_delete} = 1;
+ $form{HIDDENNAME} = "TESTUSER";
+ $t->post_ok("/user/delete_files", \%form);
+ # note $t->content;
+
+ my @deliveries = $t->deliveries;
+ is @deliveries => 3; # for TESTUSER, TESTADMIN, pause_admin
+ my ($mail_body) = map {$_->body} @deliveries;
+ like $mail_body => qr!/user/delete_files!;
+
+ my $rows = $t->mod_db->select('deletes', ['*']);
+ is @$rows => 1;
+ like $rows->[0]{deleteid} => qr!/$form{pause99_delete_files_FILE}[0]$!;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/edit_cred.t b/t/pause_2025/action_2025/edit_cred.t
new file mode 100644
index 000000000..83d9ae42e
--- /dev/null
+++ b/t/pause_2025/action_2025/edit_cred.t
@@ -0,0 +1,69 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_edit_cred_fullname => "new fullname",
+ pause99_edit_cred_asciiname => "new ascii name",
+ pause99_edit_cred_email => "new_email\@localhost.localdomain",
+ pause99_edit_cred_homepage => "none",
+ pause99_edit_cred_cpan_mail_alias => "none",
+ pause99_edit_cred_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/edit_cred");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ plan skip_all => 'SKIP for now';
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = %$default;
+ $t->post_ok("/user/edit_cred", \%form);
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = %$default;
+ $t->post_with_token_ok("/user/edit_cred", \%form);
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: edit with CENSORED email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ Test::PAUSE::Web->setup;
+ $t->mod_db->update('users', { email => 'CENSORED' }, { userid => $user });
+ my %form = (%$default, pause99_edit_cred_email => 'CENSORED');
+ $t->post_with_token_ok("/user/edit_cred", \%form);
+ my @deliveries = $t->deliveries;
+ like $deliveries[0]->as_string => qr/\[CENSORED\]/;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/edit_ml.t b/t/pause_2025/action_2025/edit_ml.t
new file mode 100644
index 000000000..ec8187fa9
--- /dev/null
+++ b/t/pause_2025/action_2025/edit_ml.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/edit_ml");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/edit_uris.t b/t/pause_2025/action_2025/edit_uris.t
new file mode 100644
index 000000000..be74f58d8
--- /dev/null
+++ b/t/pause_2025/action_2025/edit_uris.t
@@ -0,0 +1,47 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => ["$Test::PAUSE::Web::AppRoot/t/staging/Hash-RenameKey-0.02.tar.gz", "Hash-RenameKey-0.02.tar.gz"],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_edit_uris_3 => "T/TE/TESTUSER/Hash-RenameKey-0.02.tar.gz",
+ pause99_edit_uris_2 => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/edit_uris");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ my %form = %$default;
+ $form{pause99_edit_uris_3} =~ s/TESTUSER/$user/;
+ $t->post_ok("/user/edit_uris", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/email_for_admin.t b/t/pause_2025/action_2025/email_for_admin.t
new file mode 100644
index 000000000..9c6cf5999
--- /dev/null
+++ b/t/pause_2025/action_2025/email_for_admin.t
@@ -0,0 +1,33 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/email_for_admin")
+ ->text_like("body", qr/TESTADMIN\s+testadmin\@localhost/)
+ ->text_like("body", qr/TESTUSER\s+testuser\@localhost/);
+ # note $t->content;
+
+ $t->get_ok("/admin/email_for_admin?OF=YAML");
+ my $list_amp = YAML::Syck::Load( $t->content );
+ is_deeply( $list_amp, {
+ TESTADMIN => 'testadmin@localhost',
+ TESTCNSRD => 'testcnsrd@localhost',
+ TESTUSER => 'testuser@localhost',
+ TESTUSER2 => 'testuser2@localhost',
+ TESTUSER3 => 'testuser3@localhost',
+ TESTUSER4 => 'testuser4@localhost',
+ }, "YAML output works" );
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/giveup_comaint.t b/t/pause_2025/action_2025/giveup_comaint.t
new file mode 100644
index 000000000..76e13b9c3
--- /dev/null
+++ b/t/pause_2025/action_2025/giveup_comaint.t
@@ -0,0 +1,115 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_remome_m => "Module::Comaint",
+ SUBMIT_pause99_share_perms_remome => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/giveup_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case (comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remome_m => [qw/Module::Comaint Module::Comaint::Foo/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/giveup_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint.',
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint::Foo.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER from co-maintainers of Module::Comaint.',
+ 'Removed TESTUSER from co-maintainers of Module::Comaint::Foo.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remome_m => [qw/Module::Unrelated Module::Unrelated::Foo/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/giveup_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be co-maintainer of Module::Unrelated'
+ ]) or note explain \@errors;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/giveup_dist_comaint.t b/t/pause_2025/action_2025/giveup_dist_comaint.t
new file mode 100644
index 000000000..e6dd81ca1
--- /dev/null
+++ b/t/pause_2025/action_2025/giveup_dist_comaint.t
@@ -0,0 +1,112 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_giveup_dist_comaint_d => "Module-Comaint",
+ SUBMIT_pause99_giveup_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ Test::PAUSE::Web->reset_module_fixture;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/giveup_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case (comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_giveup_dist_comaint_d => [qw/Module-Comaint/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/giveup_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint (Module-Comaint).',
+ 'Removed TESTADMIN from co-maintainers of Module::Comaint::Foo (Module-Comaint).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER from co-maintainers of Module::Comaint (Module-Comaint).',
+ 'Removed TESTUSER from co-maintainers of Module::Comaint::Foo (Module-Comaint).',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_giveup_dist_comaint_d => [qw/Module-Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/giveup_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be co-maintainer of Module-Unrelated'
+ ]) or note explain \@errors;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/logout.t b/t/pause_2025/action_2025/logout.t
new file mode 100644
index 000000000..4c706dc4f
--- /dev/null
+++ b/t/pause_2025/action_2025/logout.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/logout");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/mailpw.t b/t/pause_2025/action_2025/mailpw.t
new file mode 100644
index 000000000..16e73ecdf
--- /dev/null
+++ b/t/pause_2025/action_2025/mailpw.t
@@ -0,0 +1,185 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_mailpw_1 => "TESTUSER",
+ pause99_mailpw_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/mailpw");
+ #note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ my $res = $t->post("/public/mailpw", \%form);
+ ok !$res->is_success && $res->code == 403, "Forbidden";
+ like $res->content => qr/Failed CSRF check/;
+ # note $t->content;
+ }
+};
+
+subtest 'post_with_token: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ # note $t->content;
+ }
+};
+
+subtest 'got an email instead of a userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'INV@LID',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/Please supply a userid/s);
+ }
+};
+
+subtest 'invalid userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'INV#LID',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A userid of INV#LID is not allowed/s);
+ }
+};
+
+subtest 'cannot find a userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => 'NOTFOUND',
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/Cannot find a userid.+NOTFOUND/s);
+ # note $t->content;
+ }
+};
+
+subtest 'no secretmail' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->authen_db->update('usertable', {secretemail => undef}, {user => "TESTUSER"});
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ # note $t->content;
+ }
+
+ Test::PAUSE::Web->setup; # restore the original state
+};
+
+subtest 'requested recently' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$default;
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A token for TESTUSER that allows/s);
+ # note $t->content;
+ }
+};
+
+subtest 'user without an entry in usertable: has email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => "OTHERUSER",
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->mod_db->insert('users', {
+ userid => 'OTHERUSER',
+ email => 'foo@localhost',
+ }, {replace => 1});
+ $t->authen_db->delete('usertable', {user => 'OTHERUSER'});
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_like("p.form_response", qr/A token to change the password/);
+
+ # new usertable entry is created
+ ok @{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ #note $t->content;
+ }
+};
+
+subtest 'user without an entry in usertable: without email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_mailpw_1 => "OTHERUSER",
+ );
+ $t->authen_dbh->do("TRUNCATE abrakadabra");
+ $t->mod_db->insert('users', {
+ userid => 'OTHERUSER',
+ email => '',
+ }, {replace => 1});
+ $t->authen_db->delete('usertable', {user => 'OTHERUSER'});
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ $t->post_with_token_ok("/public/mailpw", \%form)
+ ->text_is('h2', 'Error')
+ ->text_like('p.error_message', qr/A userid of OTHERUSER\s+is not known/s);
+
+ # new usertable entry is not created
+ ok !@{ $t->authen_db->select('usertable', ['user'], {user => 'OTHERUSER'}) // [] };
+ #note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/make_comaint.t b/t/pause_2025/action_2025/make_comaint.t
new file mode 100644
index 000000000..1c3350be6
--- /dev/null
+++ b/t/pause_2025/action_2025/make_comaint.t
@@ -0,0 +1,173 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_makeco_m => [],
+ pause99_share_perms_makeco_a => "TESTUSER2",
+ SUBMIT_pause99_share_perms_makeco => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/make_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => \@packages,
+ pause99_share_perms_makeco_a => "TESTUSER4",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::User::Bar.',
+ ]);
+ }
+ note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => \@packages,
+ pause99_share_perms_makeco_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_makeco_m => [qw/Module::Unrelated/],
+ pause99_share_perms_makeco_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module::Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/make_dist_comaint.t b/t/pause_2025/action_2025/make_dist_comaint.t
new file mode 100644
index 000000000..f9295e7b7
--- /dev/null
+++ b/t/pause_2025/action_2025/make_dist_comaint.t
@@ -0,0 +1,169 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_make_dist_comaint_d => [],
+ pause99_make_dist_comaint_a => "TESTUSER2",
+ SUBMIT_pause99_make_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/make_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => \@dists,
+ pause99_make_dist_comaint_a => "TESTUSER4",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_dist_comaint", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Bar (Module-Admin).',
+ 'Added TESTUSER4 to co-maintainers of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Added TESTUSER4 to co-maintainers of Module::User::Bar (Module-User).',
+ 'Added TESTUSER4 to co-maintainers of Module::User::Foo (Module-User).',
+ ]);
+ }
+
+ # note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => \@dists,
+ pause99_make_dist_comaint_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_dist_comaint", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_make_dist_comaint_d => [qw/Module-Unrelated/],
+ pause99_make_dist_comaint_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/make_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module-Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/manage_id_requests.t b/t/pause_2025/action_2025/manage_id_requests.t
new file mode 100644
index 000000000..0b9a69e8f
--- /dev/null
+++ b/t/pause_2025/action_2025/manage_id_requests.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/manage_id_requests");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/move_dist_primary.t b/t/pause_2025/action_2025/move_dist_primary.t
new file mode 100644
index 000000000..f38564aee
--- /dev/null
+++ b/t/pause_2025/action_2025/move_dist_primary.t
@@ -0,0 +1,165 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_move_dist_primary_d => [],
+ pause99_move_dist_primary_a => "TESTUSER2",
+ SUBMIT_pause99_move_dist_primary => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/move_dist_primary");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => \@dists,
+ pause99_move_dist_primary_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_dist_primary", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Bar (Module-Admin).',
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::User::Bar (Module-User).',
+ 'Made TESTUSER2 primary maintainer of Module::User::Foo (Module-User).',
+ ]);
+ }
+ note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => \@dists,
+ pause99_move_dist_primary_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_dist_primary", \%form);
+ my @new_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@new_dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@new_dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_move_dist_primary_d => [qw/Module-Unrelated/],
+ pause99_move_dist_primary_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_dist_primary", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module-Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/move_primary.t b/t/pause_2025/action_2025/move_primary.t
new file mode 100644
index 000000000..2512a73c7
--- /dev/null
+++ b/t/pause_2025/action_2025/move_primary.t
@@ -0,0 +1,172 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_pr_m => [],
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ SUBMIT_pause99_share_perms_movepr => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/move_primary");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Made TESTUSER2 primary maintainer of Module::User::Bar.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unknown user' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ pause99_share_perms_movepr_a => "UNKNOWN",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'UNKNOWN is not a valid userid.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => [qw/Module::Unrelated/],
+ pause99_share_perms_movepr_a => "TESTUSER2",
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/move_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be maintainer of Module::Unrelated',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_04about.t b/t/pause_2025/action_2025/pause_04about.t
new file mode 100644
index 000000000..c13621fed
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_04about.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_04about");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_04imprint.t b/t/pause_2025/action_2025/pause_04imprint.t
new file mode 100644
index 000000000..c2f01cdd8
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_04imprint.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_04imprint");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_05news.t b/t/pause_2025/action_2025/pause_05news.t
new file mode 100644
index 000000000..48a6aa23a
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_05news.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_05news");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_06history.t b/t/pause_2025/action_2025/pause_06history.t
new file mode 100644
index 000000000..f71e80b1d
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_06history.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_06history");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_logout.t b/t/pause_2025/action_2025/pause_logout.t
new file mode 100644
index 000000000..2459230d3
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_logout.t
@@ -0,0 +1,18 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path?ACTION=pause_logout");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_namingmodules.t b/t/pause_2025/action_2025/pause_namingmodules.t
new file mode 100644
index 000000000..62de8f0d4
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_namingmodules.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_namingmodules");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_operating_model.t b/t/pause_2025/action_2025/pause_operating_model.t
new file mode 100644
index 000000000..8ce328fe8
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_operating_model.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_operating_model");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/pause_privacy_policy.t b/t/pause_2025/action_2025/pause_privacy_policy.t
new file mode 100644
index 000000000..5f017f975
--- /dev/null
+++ b/t/pause_2025/action_2025/pause_privacy_policy.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/pause_privacy_policy");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/peek_dist_perms.t b/t/pause_2025/action_2025/peek_dist_perms.t
new file mode 100644
index 000000000..74ab30552
--- /dev/null
+++ b/t/pause_2025/action_2025/peek_dist_perms.t
@@ -0,0 +1,176 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use YAML::Syck;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_peek_dist_perms_query => "TESTUSER",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/peek_dist_perms");
+ # note $t->content;
+ }
+};
+
+subtest 'search by author' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => $user,
+ );
+ $t->$method("/user/peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ Module-Comaint
+ Module-User
+ /]) or note explain \@dists;
+ ok grep(/^Module-Comaint/, @dists), 'Module-Comaint is also listed';
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-Comaint
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+
+ $t->$method("/user/peek_dist_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ if ($user eq 'TESTADMIN') {
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-Admin',
+ 'owner' => 'TESTADMIN',
+ 'comaint' => 'TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-Comaint',
+ 'owner' => 'TESTUSER2',
+ 'comaint' => 'TESTADMIN',
+ },
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN',
+ },
+ ] );
+ }
+ if ($user eq 'TESTUSER') {
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-Comaint',
+ 'owner' => 'TESTUSER2',
+ 'comaint' => 'TESTUSER',
+ },
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-User-Foo-Baz',
+ 'owner' => 'TESTUSER',
+ 'comaint' => undef,
+ },
+ ] );
+ }
+ }
+ }
+};
+
+subtest 'search by dist (exact)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => 'Module-User',
+ pause99_peek_dist_perms_by => 'de',
+ );
+ $t->$method("/user/peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ # note $t->content;
+
+ $t->$method("/user/peek_dist_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ ]);
+ }
+ }
+};
+
+subtest 'search by module (sql-like)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_dist_perms_query => 'Module-User%',
+ pause99_peek_dist_perms_by => 'dl',
+ );
+ $t->$method("/user/peek_dist_perms", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ # note $t->content;
+
+ $t->$method("/user/peek_dist_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'dist' => 'Module-User',
+ 'owner' => 'TESTUSER',
+ 'comaint' => 'TESTADMIN,TESTUSER2',
+ },
+ {
+ 'dist' => 'Module-User-Foo-Baz',
+ 'owner' => 'TESTUSER',
+ 'comaint' => undef,
+ },
+ ]);
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/peek_perms.t b/t/pause_2025/action_2025/peek_perms.t
new file mode 100644
index 000000000..f25f1c6b9
--- /dev/null
+++ b/t/pause_2025/action_2025/peek_perms.t
@@ -0,0 +1,244 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use YAML::Syck;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_peek_perms_query => "TESTUSER",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/peek_perms");
+ # note $t->content;
+ }
+};
+
+subtest 'search by author' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => $user,
+ );
+ $t->$method("/user/peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Foo
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'No co-maint';
+ }
+ # note $t->content;
+
+ $t->$method("/user/peek_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ if ($user eq 'TESTADMIN') {
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::Admin::Bar',
+ 'owner' => 'TESTADMIN',
+ 'type' => 'first-come',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Admin::Foo',
+ 'owner' => 'TESTADMIN',
+ 'type' => 'first-come',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Comaint',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ {
+ 'module' => 'Module::Comaint::Foo',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ] );
+ }
+ if ($user eq 'TESTUSER') {
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo::Baz',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::Comaint',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::Comaint::Foo',
+ 'owner' => 'TESTUSER2',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER'
+ },
+ ] );
+ }
+ }
+ }
+};
+
+subtest 'search by module (exact)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => 'Module::User::Foo',
+ pause99_peek_perms_by => 'me',
+ );
+ $t->$method("/user/peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ cmp_set(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ # note $t->content;
+
+ $t->$method("/user/peek_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ]);
+ }
+ }
+};
+
+subtest 'search by module (sql-like)' => sub {
+ for my $method (qw/get_ok post_ok/) {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_peek_perms_query => 'Module::User::%',
+ pause99_peek_perms_by => 'ml',
+ );
+ $t->$method("/user/peek_perms", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ my @types = map {$_->all_text} $t->dom->find('td.type')->each;
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ ok grep(/co-maint/, @types), 'Has co-maint';
+ # note $t->content;
+
+ $t->$method("/user/peek_perms?OF=YAML", \%form);
+ my $list = YAML::Syck::Load( $t->content );
+ eq_or_diff( $list => [
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Foo::Baz',
+ 'owner' => 'TESTUSER',
+ 'type' => 'first-come',
+ 'userid' => 'TESTUSER'
+ },
+ {
+ 'module' => 'Module::User::Bar',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTUSER2'
+ },
+ {
+ 'module' => 'Module::User::Foo',
+ 'owner' => 'TESTUSER',
+ 'type' => 'co-maint',
+ 'userid' => 'TESTADMIN'
+ },
+ ]);
+ }
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/reindex.t b/t/pause_2025/action_2025/reindex.t
new file mode 100644
index 000000000..21b1e4999
--- /dev/null
+++ b/t/pause_2025/action_2025/reindex.t
@@ -0,0 +1,49 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default_for_add_uri = {
+ pause99_add_uri_httpupload => [Test::PAUSE::Web->file_to_upload],
+ SUBMIT_pause99_add_uri_httpupload => 1,
+};
+
+my $default = {
+ pause99_reindex_FILE => ["Hash-RemoteKey-0.02.tar.gz"],
+ SUBMIT_pause99_reindex_delete => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/reindex");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE uris");
+
+ # prepare distribution
+ $t->post_ok("/user/add_uri", $default_for_add_uri, "Content-Type" => "form-data");
+
+ $t->copy_to_authors_dir($user, scalar Test::PAUSE::Web->file_to_upload);
+
+ my %form = %$default;
+ $t->post_ok("/user/reindex", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/remove_comaint.t b/t/pause_2025/action_2025/remove_comaint.t
new file mode 100644
index 000000000..4d5ed74cb
--- /dev/null
+++ b/t/pause_2025/action_2025/remove_comaint.t
@@ -0,0 +1,176 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_remocos_tuples => [],
+ SUBMIT_pause99_share_perms_remocos => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/remove_comaint");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module::Admin::Bar -- TESTUSER2',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module::User::Bar -- TESTUSER2',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Bar.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Bar.',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not the owner)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => ['Module::Unrelated -- TESTUSER2'],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be owner of Module::Unrelated.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not the comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module::Admin::Bar -- TESTUSER4',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module::User::Bar -- TESTUSER4',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_remocos_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_comaint", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module::Admin::Bar -- TESTUSER4. If you believe, this is a bug, please complain.'
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module::User::Bar -- TESTUSER4. If you believe, this is a bug, please complain.'
+ ]);
+ }
+ ok !@results;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/remove_dist_comaint.t b/t/pause_2025/action_2025/remove_dist_comaint.t
new file mode 100644
index 000000000..4cc161f71
--- /dev/null
+++ b/t/pause_2025/action_2025/remove_dist_comaint.t
@@ -0,0 +1,170 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_remove_dist_comaint_tuples => [],
+ SUBMIT_pause99_remove_dist_comaint => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/remove_dist_comaint");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module-Admin -- TESTUSER2',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module-User -- TESTUSER2',
+ );
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Bar (Module-Admin).',
+ 'Removed TESTUSER2 from co-maintainers of Module::Admin::Foo (Module-Admin).',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Bar (Module-User).',
+ 'Removed TESTUSER2 from co-maintainers of Module::User::Foo (Module-User).',
+ ]);
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not an owner)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => ['Module-Unrelated -- TESTUSER2'],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@errors, [
+ 'You do not seem to be owner of Module-Unrelated.',
+ ]);
+ # note $t->content;
+ }
+};
+
+subtest 'broken tuple (not a comaint)' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @tuples;
+ if ($user eq 'TESTADMIN') {
+ @tuples = (
+ 'Module-Admin -- TESTUSER4',
+ );
+ }
+ if ($user eq 'TESTUSER') {
+ @tuples = (
+ 'Module-User -- TESTUSER4',
+ );
+
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_comaint_tuples => \@tuples,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_dist_comaint", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @errors = map {$_->all_text} $t->dom->find('.error')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_set(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module-Admin -- TESTUSER4. If you believe, this is a bug, please complain.',
+ ]);
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_set(\@dists, [qw/
+ Module-User
+ /]) or note explain \@dists;
+ eq_or_diff(\@errors, [
+ 'Cannot handle tuple Module-User -- TESTUSER4. If you believe, this is a bug, please complain.',
+ ]);
+ }
+ ok !@results;
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/remove_dist_primary.t b/t/pause_2025/action_2025/remove_dist_primary.t
new file mode 100644
index 000000000..ef8bd6866
--- /dev/null
+++ b/t/pause_2025/action_2025/remove_dist_primary.t
@@ -0,0 +1,137 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_remove_dist_primary_d => [],
+ SUBMIT_pause99_remove_dist_primary => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/remove_dist_primary");
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @dists;
+ if ($user eq 'TESTADMIN') {
+ @dists = qw/Module-Admin/;
+ }
+ if ($user eq 'TESTUSER') {
+ @dists = qw/Module-User/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_primary_d => \@dists,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_dist_primary", \%form);
+ @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Bar (Module-Admin).',
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Foo (Module-Admin).',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("/user/peek_dist_perms", {
+ pause99_peek_dist_perms_query => "ADOPTME",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+ });
+ my @adoptme_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@adoptme_dists, [qw/Module-Admin/]) or note explain \@adoptme_dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTUSER from Module::User::Bar (Module-User).',
+ 'Removed primary maintainership of TESTUSER from Module::User::Foo (Module-User).',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("/user/peek_dist_perms", {
+ pause99_peek_dist_perms_query => "ADOPTME",
+ pause99_peek_dist_perms_by => "a",
+ pause99_peek_dist_perms_sub => 1,
+ });
+ my @adoptme_dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ cmp_set(\@adoptme_dists, [qw/Module-User/]) or note explain \@adoptme_dists;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated dists' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_remove_dist_primary_d => [qw/Module-Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_dist_primary", \%form);
+ my @dists = map {$_->all_text} $t->dom->find('td.dist')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @warnings = map {$_->all_text} $t->dom->find('.warning')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@dists, [qw/
+ Module-Admin
+ /]) or note explain \@dists;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@dists, [qw/
+ Module-User
+ Module-User-Foo-Baz
+ /]) or note explain \@dists;
+ }
+ ok !@results;
+ eq_or_diff(\@warnings, [
+ 'You need to select one or more distributions. Nothing done.',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/remove_primary.t b/t/pause_2025/action_2025/remove_primary.t
new file mode 100644
index 000000000..32b28a2d6
--- /dev/null
+++ b/t/pause_2025/action_2025/remove_primary.t
@@ -0,0 +1,141 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+use Test::Deep;
+use Test::Differences;
+
+my $default = {
+ pause99_share_perms_pr_m => [],
+ SUBMIT_pause99_share_perms_remopr => 1,
+};
+
+Test::PAUSE::Web->setup;
+Test::PAUSE::Web->reset_module_fixture;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/remove_primary");
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'normal case' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my @packages;
+ if ($user eq 'TESTADMIN') {
+ @packages = qw/Module::Admin::Bar/;
+ }
+ if ($user eq 'TESTUSER') {
+ @packages = qw/Module::User::Bar/;
+ }
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => \@packages,
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTADMIN from Module::Admin::Bar.',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("/user/peek_perms", {
+ pause99_peek_perms_query => "ADOPTME",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+ });
+ my @adoptme_modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ cmp_set(\@adoptme_modules, [qw/Module::Admin::Bar/]) or note explain \@adoptme_modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ eq_or_diff(\@results, [
+ 'Removed primary maintainership of TESTUSER from Module::User::Bar.',
+ ]);
+
+ # really transferred to ADOPTME?
+ $t->get_ok("/user/peek_perms", {
+ pause99_peek_perms_query => "ADOPTME",
+ pause99_peek_perms_by => "a",
+ pause99_peek_perms_sub => 1,
+ });
+ my @adoptme_modules = map {$_->all_text} $t->dom->find('td.module')->each;
+ cmp_set(\@adoptme_modules, [qw/Module::User::Bar/]) or note explain \@adoptme_modules;
+ }
+ # note $t->content;
+ }
+};
+
+subtest 'unrelated modules' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = (
+ %$default,
+ pause99_share_perms_pr_m => [qw/Module::Unrelated/],
+ );
+
+ Test::PAUSE::Web->reset_module_fixture;
+ $t->post_ok("/user/remove_primary", \%form);
+ my @modules = map {$_->all_text} $t->dom->find('td.package')->each;
+ my @results = map {$_->all_text} $t->dom->find('.result')->each;
+ my @warnings = map {$_->all_text} $t->dom->find('.warning')->each;
+ if ($user eq 'TESTADMIN') {
+ cmp_bag(\@modules, [qw/
+ Module::Admin::Bar
+ Module::Admin::Foo
+ /]) or note explain \@modules;
+ }
+ if ($user eq 'TESTUSER') {
+ cmp_bag(\@modules, [qw/
+ Module::User::Bar
+ Module::User::Foo
+ Module::User::Foo::Baz
+ /]) or note explain \@modules;
+ }
+ ok !@results;
+ eq_or_diff(\@warnings, [
+ 'You need to select one or more packages. Nothing done.',
+ ]);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/request_id.t b/t/pause_2025/action_2025/request_id.t
new file mode 100644
index 000000000..f1257556e
--- /dev/null
+++ b/t/pause_2025/action_2025/request_id.t
@@ -0,0 +1,289 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_request_id_fullname => 'full name',
+ pause99_request_id_email => 'test@localhost.localdomain',
+ pause99_request_id_homepage => 'none',
+ pause99_request_id_userid => 'NEWUSER',
+ pause99_request_id_rationale => 'Hello, my ratoinale is to test PAUSE',
+ SUBMIT_pause99_request_id_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/public/request_id");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = %$default;
+ $t->post_ok("/public/request_id", \%form)
+ ->text_like("pre.email_sent", qr/Subject: PAUSE ID request \(NEWUSER/);
+ is $t->deliveries => 2, "two deliveries (one for admin, one for requester)";
+ # note $t->content;
+ }
+};
+
+subtest 'post: thank you, bot' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ url => 'http://host/path',
+ );
+ $t->post_ok("/public/request_id", \%form);
+ is $t->content => "Thank you!";
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no space in full name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_fullname => 'FULLNAME',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Name does not look like a full civil name/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no full name' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_fullname => '',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a name/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_email => '',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply an email address/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: invalid email' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_email => 'no email',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Your email address doesn't look like valid email address./);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: rational is too short' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => 'rationale',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/this looks a\s+bit too short/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+# XXX: might be better to ignore other attributes (or YAGNI)
+subtest 'post: rational has html links' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => '',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Please do not use HTML links/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: multiple links' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => <<'SPAM',
+http://spam/path
+http://spam/path
+SPAM
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/Please do not include more than one URL/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no rationale' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => '',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a short description/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: userid is taken' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => 'TESTUSER',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/The userid TESTUSER is already taken/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: invalid userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => 'INV#LID',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/The userid INV#LID does not match/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: no userid' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_userid => '',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h3", "Error processing form")
+ ->text_like("ul.errors li", qr/You must supply a desired user-ID/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: lots of .info' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_rationale => <<'SPAM',
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+ttp://spam.info
+SPAM
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/rationale looks like spam/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+subtest 'post: interesting .cn homepage' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my %form = (
+ %$default,
+ pause99_request_id_homepage => 'http://some.cn/index.htm',
+ pause99_request_id_rationale => 'interesting site',
+ );
+ $t->post_ok("/public/request_id", \%form)
+ ->text_is("h2", "Error")
+ ->text_like("p.error_message", qr/rationale looks like spam/);
+ ok !$t->deliveries, "no deliveries";
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/reset_version.t b/t/pause_2025/action_2025/reset_version.t
new file mode 100644
index 000000000..60f96653e
--- /dev/null
+++ b/t/pause_2025/action_2025/reset_version.t
@@ -0,0 +1,50 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_reset_version_PKG => ["Foo"],
+ SUBMIT_pause99_reset_version_forget => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/reset_version");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->mod_dbh->do("TRUNCATE packages");
+ $t->mod_db->insert('packages', {
+ package => "Foo",
+ version => "0.01",
+ dist => "T/TE/$user/Foo-0.01.tar.gz",
+ file => "Foo-0.01.tar.gz",
+ });
+ $t->mod_db->insert('packages', {
+ package => "Bar",
+ version => "0.02",
+ dist => "T/TE/$user/Bar-0.02.tar.gz",
+ file => "Bar-0.02.tar.gz",
+ });
+
+ my %form = %$default;
+ $t->post_ok("/user/reset_version", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/select_ml_action.t b/t/pause_2025/action_2025/select_ml_action.t
new file mode 100644
index 000000000..5440333dd
--- /dev/null
+++ b/t/pause_2025/action_2025/select_ml_action.t
@@ -0,0 +1,51 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $mailing_list = {
+ SUBMIT_pause99_add_user_Definitely => 1,
+ pause99_add_user_userid => "MAILLIST",
+ pause99_add_user_email => "ml\@localhost.localdomain",
+ pause99_add_user_subscribe => "how to subscribe",
+};
+
+my $default = {
+ HIDDENNAME => "TESTUSER",
+ ACTIONREQ => "edit_ml",
+ pause99_select_ml_action_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/mlrepr/select_ml_action");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->post_ok("/admin/add_user", $mailing_list);
+
+ $t->mod_db->insert("list2user", {
+ maillistid => "MAILLIST",
+ userid => "TESTUSER",
+ }, {ignore => 1});
+
+ my %form = %$default;
+ $t->post_ok("/mlrepr/select_ml_action", \%form);
+ note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/select_user.t b/t/pause_2025/action_2025/select_user.t
new file mode 100644
index 000000000..5ed844b67
--- /dev/null
+++ b/t/pause_2025/action_2025/select_user.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/admin/select_user");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/share_perms.t b/t/pause_2025/action_2025/share_perms.t
new file mode 100644
index 000000000..1af2a33b3
--- /dev/null
+++ b/t/pause_2025/action_2025/share_perms.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/share_perms");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/show_files.t b/t/pause_2025/action_2025/show_files.t
new file mode 100644
index 000000000..8960bc84b
--- /dev/null
+++ b/t/pause_2025/action_2025/show_files.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/show_files");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/show_ml_repr.t b/t/pause_2025/action_2025/show_ml_repr.t
new file mode 100644
index 000000000..b9ced8642
--- /dev/null
+++ b/t/pause_2025/action_2025/show_ml_repr.t
@@ -0,0 +1,19 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'get' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('admin')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/mlrepr/show_ml_repr");
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/tail_logfile.t b/t/pause_2025/action_2025/tail_logfile.t
new file mode 100644
index 000000000..f49f812ca
--- /dev/null
+++ b/t/pause_2025/action_2025/tail_logfile.t
@@ -0,0 +1,45 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use utf8;
+
+my $default = {
+ pause99_tail_logfile_1 => 5000,
+ pause99_tail_logfile_sub => 1,
+};
+
+Test::PAUSE::Web->setup;
+
+{
+ open my $fh, '>', $PAUSE::Config->{PAUSE_LOG};
+ say $fh < sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/tail_logfile");
+ # note $t->content;
+ }
+};
+
+subtest 'post: basic' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ my %form = %$default;
+ $t->post_ok("/user/tail_logfile", \%form);
+ # note $t->content;
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/who_admin.t b/t/pause_2025/action_2025/who_admin.t
new file mode 100644
index 000000000..b7918d74c
--- /dev/null
+++ b/t/pause_2025/action_2025/who_admin.t
@@ -0,0 +1,38 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use YAML::Syck ();
+
+Test::PAUSE::Web->setup;
+
+# SELECT user FROM grouptable WHERE ugroup='admin' order by user");
+subtest 'get' => sub {
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "FOO",
+ ugroup => "admin",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAR",
+ ugroup => "admin",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAZ",
+ ugroup => "bar",
+ });
+
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->get_ok("/public/who_admin")
+ ->text_like('body', qr/Registered admins:\s+BAR, FOO/);
+
+ $t->get_ok("/public/who_admin?OF=YAML");
+ my $list_amp = YAML::Syck::Load( $t->content );
+ is_deeply( $list_amp, [qw/BAR FOO TESTADMIN/], "YAML output works" );
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/action_2025/who_pumpkin.t b/t/pause_2025/action_2025/who_pumpkin.t
new file mode 100644
index 000000000..05f5d576a
--- /dev/null
+++ b/t/pause_2025/action_2025/who_pumpkin.t
@@ -0,0 +1,38 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/../lib";
+use Test::PAUSE::Web;
+use YAML::Syck ();
+
+Test::PAUSE::Web->setup;
+
+# SELECT user FROM grouptable WHERE ugroup='pumpking' order by user");
+subtest 'get' => sub {
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "FOO",
+ ugroup => "pumpking",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAR",
+ ugroup => "pumpking",
+ });
+ Test::PAUSE::Web->authen_db->insert('grouptable', {
+ user => "BAZ",
+ ugroup => "baz",
+ });
+
+ for my $test (Test::PAUSE::Web->tests_for('public')) {
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+
+ $t->get_ok("/public/who_pumpkin")
+ ->text_like("body", qr/Registered pumpkins:\s+BAR, FOO/);
+
+ $t->get_ok("/public/who_pumpkin?OF=YAML");
+ my $list_amp = YAML::Syck::Load( $t->content );
+ is_deeply( $list_amp, [qw/BAR FOO/], "YAML output works" );
+ }
+};
+
+done_testing;
diff --git a/t/pause_2025/auth.t b/t/pause_2025/auth.t
new file mode 100644
index 000000000..96b9c16b3
--- /dev/null
+++ b/t/pause_2025/auth.t
@@ -0,0 +1,121 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use Test::PAUSE::Web;
+use HTTP::Status qw/:constants/;
+use utf8;
+
+Test::PAUSE::Web->setup;
+
+subtest 'for 2017 app' => sub {
+ subtest 'basic' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my $res = $t->get("$path");
+ ok $res->is_success;
+ };
+
+ subtest 'lower case' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => lc $user);
+ my $res = $t->get("$path");
+ ok $res->is_success;
+ };
+
+ subtest 'wrong password' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user, pass => "WRONG PASS");
+ my $res = $t->get("$path");
+ ok !$res->is_success;
+ is $res->code => HTTP_UNAUTHORIZED;
+ };
+
+ subtest 'unknown user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => "UNKNOWN");
+ my $res = $t->get("$path");
+ ok !$res->is_success;
+ is $res->code => HTTP_UNAUTHORIZED;
+ };
+
+ subtest 'disallowed action for an anonymous user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ my $res = $t->get("/pause/authenquery/?ACTION=add_user");
+ ok !$res->is_success;
+ is $res->code => HTTP_UNAUTHORIZED;
+ };
+
+ subtest 'disallowed action for a user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ my $res = $t->get("/pause/authenquery/?ACTION=add_user");
+ ok !$res->is_success;
+ is $res->code => HTTP_FORBIDDEN;
+ };
+};
+
+subtest 'for 2025 app' => sub {
+ subtest 'basic' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ my $res = $t->login(user => $user);
+ ok $res->is_success;
+ ok my @redirects = $res->redirects, "login succeeded and redirected";
+ is $redirects[0]->header('Location')->path => '/';
+ };
+
+ subtest 'lower case' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new(user => lc $user);
+ my $res = $t->login(user => lc $user);
+ ok $res->is_success;
+ ok my @redirects = $res->redirects, "login succeeded and redirected";
+ is $redirects[0]->header('Location')->path => '/';
+ };
+
+ subtest 'wrong password' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ my $res = $t->login(user => $user, pass => "WRONG PASS");
+ ok !(my @redirects = $res->redirects), "login failed and not redirected";
+ };
+
+ subtest 'unknown user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ my $res = $t->login(user => "UNKNOWN");
+ ok !(my @redirects = $res->redirects), "login failed and not redirected";
+ };
+
+ subtest 'disallowed action for an anonymous user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ my $res = $t->get("/admin/add_user");
+ ok !$res->is_success;
+ is $res->code => HTTP_FORBIDDEN;
+ };
+
+ subtest 'disallowed action for a user' => sub {
+ my $test = Test::PAUSE::Web->tests_for('user');
+ my ($path, $user) = @$test;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ my $res = $t->get("/admin/add_user");
+ ok !$res->is_success;
+ is $res->code => HTTP_FORBIDDEN;
+ };
+};
+
+done_testing;
diff --git a/t/pause_2025/lib/Test/PAUSE/MySQL.pm b/t/pause_2025/lib/Test/PAUSE/MySQL.pm
new file mode 100644
index 000000000..13ae4873a
--- /dev/null
+++ b/t/pause_2025/lib/Test/PAUSE/MySQL.pm
@@ -0,0 +1,230 @@
+package Test::PAUSE::MySQL;
+
+use Test::Builder ();
+use Test::Requires qw(Test::mysqld);
+use Test::Requires qw(File::Which);
+
+BEGIN {
+ unless (File::Which::which 'mysql') {
+ Test::Builder->new->skip_all("no mysql found, needed for this test")
+ }
+}
+
+use Moose;
+use Test::mysqld;
+use Test::More;
+use DBI;
+use File::Temp qw/tempfile/;
+use Capture::Tiny qw/capture_merged/;
+use SQL::Maker;
+use Path::Tiny;
+
+$SIG{INT} = sub { die "caught SIGINT, shutting down mysql\n" };
+
+# These are the only caller-configurable parts
+
+# SQL to load at instantiation
+has 'schemas' => (
+ is => 'ro',
+ isa => 'ArrayRef[Str]',
+ default => sub {[]},
+);
+
+# Location of the mysql client binary
+has 'mysql_client' => (
+ is => 'ro',
+ isa => 'Str',
+ default => ($ENV{'PAUSE_MYSQL_CLIENT'} || 'mysql'),
+);
+
+# These are the public methods
+
+# DBH
+has 'dbh' => (
+ is => 'ro',
+ isa => 'DBI::db',
+ lazy_build => 1,
+);
+
+has 'sql_maker' => (
+ is => 'ro',
+ isa => 'SQL::Maker',
+ lazy_build => 1,
+);
+
+# Drops you in to `mysql` connected to the database
+sub debug_console {
+ my $self = shift;
+ $self->run_mysql();
+}
+
+sub dsn {
+ my $self = shift;
+ return $self->mysqld->dsn( dbname => $self->_db_name );
+}
+
+# Private attributes
+
+# Object-specific database name
+has '_db_name' => (
+ is => 'ro',
+ isa => 'Str',
+ lazy_build => 1,
+);
+
+sub _build__db_name {
+ my $self = shift;
+ return 'db_' . ( $self + 0 ) . int(rand 999_999);
+}
+
+# Location of the config file for the mysql client
+has '_auth_file' => (
+ is => 'ro',
+ isa => 'Str',
+ lazy_build => 1,
+);
+
+sub _build__auth_file {
+ my $self = shift;
+ my ($fh, $filename) = tempfile();
+ my $args = $self->dsn;
+ $args =~ s/DBI:mysql://;
+
+ my %options = map { split /=/ } split( /;/, $args );
+ $options{'database'} = delete $options{'dbname'};
+ $options{'socket'} = delete $options{'mysql_socket'};
+ $options{'default-character-set'} = 'utf8';
+
+ my $auth_content = join "\n", "[client]",
+ map { "$_=" . $options{$_} } keys %options;
+
+ print $fh $auth_content;
+ close $fh;
+ return $filename;
+}
+
+sub BUILD {
+ my $self = shift;
+ my $dbh = $self->dbh;
+
+ for my $schema ( @{$self->schemas} ) {
+ note("Loading schema: $schema");
+ my $body = path($schema)->slurp;
+ for (grep $_, split /;\n/s, $body) {
+ $dbh->do($_);
+ }
+ }
+}
+
+
+
+sub _build_dbh {
+ my $self = shift;
+ my $dbname = $self->_db_name;
+
+ my $master_dbh = DBI->connect(
+ $self->mysqld->dsn(
+ dbname => 'test',
+ 'default-character-set' => 'utf8'
+ )
+ );
+
+ note("Creating new MySQL database: $dbname");
+ $master_dbh->do( 'CREATE DATABASE ' . $dbname )
+ or die $master_dbh->errstr;
+
+ # Connect to it
+ my $dbh = DBI->connect( $self->mysqld->dsn( dbname => $dbname ),
+ '', '', { RaiseError => 1 } );
+
+ return $dbh;
+}
+
+sub _build_sql_maker {
+ my $self = shift;
+ SQL::Maker->new(driver => 'mysql');
+}
+
+sub run_mysql {
+ my $self = shift;
+ my $cmd = shift || '';
+ my $exe = $self->mysql_client;
+ system(sprintf("%s --defaults-extra-file=%s %s", $exe, $self->_auth_file, $cmd));
+}
+
+# mysqld singleton. We might have different tests that want to execute in
+# seperate DBs, but I can't see why we'd want to be running more than one
+# mysqld, so we do a singleton here
+our $mysqld;
+
+sub mysqld {
+ my $self = shift;
+ return $mysqld if $mysqld;
+
+ note("Starting a test mysqld");
+ note(
+ capture_merged(
+ sub { $mysqld = Test::mysqld->new(
+ my_cnf => { 'skip-networking' => '' }
+ );
+ }
+ )
+ );
+ die $Test::mysqld::errstr unless $mysqld;
+ note("mysqld started");
+
+ return $mysqld;
+}
+
+my %DefaultValues = (
+ # authen_pause
+ # mod
+ packages => {
+ filemtime => time,
+ pause_reg => 'TESTUSER',
+ comment => '',
+ status => 'index',
+ },
+ users => {
+ fullname => 'test',
+ homepage => '',
+ isa_list => '',
+ introduced => time,
+ changed => time,
+ changedby => 'TESTADMIN',
+ },
+);
+
+sub insert {
+ my ($self, $table, $values, $opt) = @_;
+ if (my $default = $DefaultValues{$table}) {
+ for my $key (keys %$default) {
+ $values->{$key} //= $default->{$key};
+ }
+ }
+ if ($opt and delete $opt->{replace}) {
+ $opt->{prefix} = 'REPLACE';
+ }
+ my ($sql, @bind) = $self->sql_maker->insert($table, $values, $opt);
+ $self->dbh->do($sql, undef, @bind);
+}
+
+sub update {
+ my ($self, $table, $set, $where) = @_;
+ my ($sql, @bind) = $self->sql_maker->update($table, $set, $where);
+ $self->dbh->do($sql, undef, @bind);
+}
+
+sub delete {
+ my ($self, $table, $where) = @_;
+ my ($sql, @bind) = $self->sql_maker->delete($table, $where);
+ $self->dbh->do($sql, undef, @bind);
+}
+
+sub select {
+ my ($self, $table, $fields, $where, $opt) = @_;
+ my ($sql, @bind) = $self->sql_maker->select($table, $fields, $where, $opt);
+ $self->dbh->selectall_arrayref($sql, {Slice => +{}}, @bind);
+}
+
+1;
diff --git a/t/pause_2025/lib/Test/PAUSE/Web.pm b/t/pause_2025/lib/Test/PAUSE/Web.pm
new file mode 100644
index 000000000..0704514de
--- /dev/null
+++ b/t/pause_2025/lib/Test/PAUSE/Web.pm
@@ -0,0 +1,490 @@
+package Test::PAUSE::Web;
+
+use strict;
+use warnings;
+use FindBin;
+use JSON::PP; # just to avoid redefine warnings
+use Path::Tiny;
+use DBI;
+use Plack::Test;
+use Test::WWW::Mechanize::PSGI;
+use Test::More;
+use Exporter qw/import/;
+use Test::PAUSE::MySQL;
+use Email::Sender::Simple;
+use Mojo::DOM;
+use URI;
+use URI::QueryParam;
+
+our $AppRoot = path(__FILE__)->parent->parent->parent->parent->parent->parent->realpath;
+#our $AppRoot = path(__FILE__)->parent->parent->parent->parent->parent->parent->parent->realpath;
+our $TmpDir = Path::Tiny->tempdir(TEMPLATE => "pause_web_XXXXXXXX");
+our $TestRoot = path($TmpDir)->realpath;
+our $TestEmail = 'pause_admin@localhost.localdomain';
+our @EXPORT = @Test::More::EXPORT;
+
+our $FilenameToUpload = "Hash-RenameKey-0.02.tar.gz";
+our $FileToUpload = "$AppRoot/t/staging/$FilenameToUpload";
+
+push @INC, "$AppRoot/lib", "$AppRoot/lib/pause_2025", "$AppRoot/lib/pause_2017", "$AppRoot/privatelib";
+
+$TmpDir->child($_)->mkpath for qw/rundata incoming etc public log/;
+$TmpDir->child('log')->child('paused.log')->touch();
+
+$INC{"PrivatePAUSE.pm"} = 1;
+$ENV{EMAIL_SENDER_TRANSPORT} = "Test";
+
+require PAUSE;
+require PAUSE::Web2025::Config;
+
+$PAUSE::Config->{DOCUMENT_ROOT} = "$AppRoot/htdocs";
+$PAUSE::Config->{PID_DIR} = $TestRoot;
+$PAUSE::Config->{ADMIN} = $TestEmail;
+$PAUSE::Config->{ADMINS} = [$TestEmail];
+$PAUSE::Config->{CPAN_TESTERS} = $TestEmail;
+$PAUSE::Config->{TO_CPAN_TESTERS} = $TestEmail;
+$PAUSE::Config->{REPLY_TO_CPAN_TESTERS} = $TestEmail;
+$PAUSE::Config->{GONERS_NOTIFY} = $TestEmail;
+$PAUSE::Config->{P5P} = $TestEmail;
+$PAUSE::Config->{MLROOT} = "$TestRoot/public/authors/id";
+$PAUSE::Config->{ML_CHOWN_USER} = 'ishigaki';
+$PAUSE::Config->{ML_CHOWN_GROUP} = 'ishigaki';
+$PAUSE::Config->{ML_MIN_INDEX_LINES} = 0;
+$PAUSE::Config->{ML_MIN_FILES} = 0;
+$PAUSE::Config->{RUNDATA} = "$TestRoot/rundata";
+$PAUSE::Config->{UPLOAD} = $TestEmail;
+$PAUSE::Config->{HAVE_PERLBAL} = 0;
+$PAUSE::Config->{SLEEP} = 1;
+$PAUSE::Config->{INCOMING} = "file://$TestRoot/incoming/";
+$PAUSE::Config->{INCOMING_LOC} = "$TestRoot/incoming/";
+$PAUSE::Config->{PAUSE_LOG} = "$TestRoot/log/paused.log";
+$PAUSE::Config->{PAUSE_LOG_DIR} = "$TestRoot/log";
+$PAUSE::Config->{RECAPTCHA_ENABLED} = 0;
+
+# These will get changed every time you run setup()
+$PAUSE::Config->{AUTHEN_DATA_SOURCE_NAME} = "";
+$PAUSE::Config->{MOD_DATA_SOURCE_NAME} = "";
+
+$ENV{TEST_PAUSE_WEB} = 1;
+
+our $AuthDBH;
+our $ModDBH;
+
+my $dbh_attr = {ShowErrorStatement => 1};
+
+sub authen_dbh { $AuthDBH ||= authen_db()->dbh }
+sub mod_dbh { $ModDBH ||= mod_db()->dbh }
+
+our $AuthDB;
+sub authen_db {
+ my $db = $AuthDB ||= Test::PAUSE::MySQL->new(
+ schemas => ["$AppRoot/doc/authen_pause.schema.txt"]
+ );
+ $PAUSE::Config->{AUTHEN_DATA_SOURCE_NAME} = $db->dsn;
+ $db;
+}
+
+our $ModDB;
+sub mod_db {
+ my $db = $ModDB ||= Test::PAUSE::MySQL->new(
+ schemas => ["$AppRoot/doc/mod.schema.txt"]
+ );
+ $PAUSE::Config->{MOD_DATA_SOURCE_NAME} = $db->dsn;
+ $db;
+}
+
+sub setup { # better to use Test::mysqld
+ my $class = shift;
+
+ require PAUSE::Crypt;
+
+ # Remove old DB handles and objects
+ undef $AuthDBH;
+ undef $AuthDB;
+ undef $ModDBH;
+ undef $ModDB;
+
+ $class->reset_fixture;
+}
+
+sub reset_fixture {
+ my $self = shift;
+
+ # test fixture
+ { # authen_pause.usertable
+ $self->authen_dbh->do(qq{TRUNCATE usertable});
+ for my $user ("TESTUSER", "TESTUSER2", "TESTUSER3", "TESTUSER4", "TESTADMIN", "TESTCNSRD") {
+ $self->authen_db->insert('usertable', {
+ user => $user,
+ password => PAUSE::Crypt::hash_password("test"),
+ secretemail => lc($user) . '@localhost',
+ });
+ my $user_dir = join "/", $PAUSE::Config->{MLROOT}, PAUSE::user2dir($user);
+ path($user_dir)->mkpath;
+ }
+ }
+ { # authen_pause.grouptable
+ $self->authen_dbh->do(qq{TRUNCATE grouptable});
+ $self->authen_db->insert('grouptable', {user => "TESTADMIN", ugroup => "admin"});
+ }
+ { # mod.users
+ $self->mod_dbh->do(qq{TRUNCATE users});
+ for my $user ("TESTUSER", "TESTUSER2", "TESTUSER3", "TESTUSER4", "TESTADMIN", "TESTCNSRD") {
+ $self->mod_db->insert('users', {
+ userid => $user,
+ fullname => "$user Name",
+ email => ($user eq "TESTCNSRD" ? "CENSORED" : (lc($user) . '@localhost')),
+ cpan_mail_alias => 'secr',
+ isa_list => '',
+ });
+ }
+ }
+ $self;
+}
+
+sub new {
+ my ($class, %args) = @_;
+
+ my $psgi = $ENV{TEST_PAUSE_WEB_PSGI} // "app_2025.psgi";
+ my $app = do "$AppRoot/$psgi";
+
+ $args{mech} = Test::WWW::Mechanize::PSGI->new(app => $app, cookie_jar => {});
+ if (!$INC{'Devel/Cover.pm'} and !$ENV{TRAVIS} and $ENV{HARNESS_VERBOSE} and eval {require LWP::ConsoleLogger::Easy; 1}) {
+ LWP::ConsoleLogger::Easy::debug_ua($args{mech});
+ }
+ $args{pass} ||= "test" if $args{user};
+
+ $class->clear_deliveries;
+
+ bless \%args, $class;
+}
+
+sub set_credentials {
+ my $self = shift;
+ return unless $self->{user};
+ note "log in as ".$self->{user};
+ $self->{mech}->credentials($self->{user}, $self->{pass});
+ $self->{mech}->{env}{REMOTE_USER} = $self->{user};
+}
+
+sub login {
+ my ($self, %args) = @_;
+ my $user = $args{user} or return;
+ my $pass = $args{pass} || 'test';
+ note "log in as $user";
+ $self->{mech}->get('/login');
+ $self->{mech}->submit_form(fields => {pause_id => $user, password => $pass});
+}
+
+sub get {
+ my ($self, $url, @args) = @_;
+
+ $self->set_credentials if $self->{user};
+ if (@args and ref $args[0] eq 'HASH') {
+ my $params = shift @args;
+ $url = URI->new($url);
+ $url->query_param($_ => $params->{$_}) for keys %$params;
+ }
+ my $res = $self->{mech}->get($url, @args);
+ unlike $res->decoded_content => qr/(?:HASH|ARRAY|SCALAR|CODE)\(/; # most likely stringified reference
+ ok !grep /(?:HASH|ARRAY|SCALAR|CODE)\(/, map {$_->as_string} $self->deliveries;
+ $res;
+}
+
+sub get_ok {
+ my ($self, $url, @args) = @_;
+
+ $self->clear_deliveries;
+ my $res = $self->get($url, @args);
+ ok $res->is_success, "GET $url";
+ $self->title_is_ok($url);
+ $self->note_deliveries;
+ $self;
+}
+
+sub post {
+ my ($self, $url, @args) = @_;
+
+ $self->set_credentials if $self->{user};
+ my $res = $self->{mech}->post($url, @args);
+ unlike $res->decoded_content => qr/(?:HASH|ARRAY|SCALAR|CODE)\(/; # most likely stringified reference
+ ok !grep /(?:HASH|ARRAY|SCALAR|CODE)\(/, map {$_->as_string} $self->deliveries;
+ $res;
+}
+
+sub post_ok {
+ my ($self, $url, @args) = @_;
+
+ $self->clear_deliveries;
+ my $res = $self->post($url, @args);
+ ok $res->is_success, "POST $url";
+ $self->title_is_ok($url);
+ $self->note_deliveries;
+ $self;
+}
+
+sub post_with_token {
+ my ($self, $url, @args) = @_;
+
+ my $res = $self->get($url);
+ return $res unless $res->is_success;
+ my $input = Mojo::DOM->new($res->decoded_content)->at('input[name="csrf_token"]');
+ my $token = $input ? $input->attr('value') : '';
+ ok $token, "Got a CSRF token";
+ @args = {} if !@args;
+ $args[0]->{csrf_token} = $token if @args and ref $args[0] eq 'HASH';
+
+ $res = $self->post($url, @args);
+}
+
+sub post_with_token_ok {
+ my ($self, $url, @args) = @_;
+
+ $self->clear_deliveries;
+ my $res = $self->post_with_token($url, @args);
+ ok $res->is_success, "POST $url";
+ $self->title_is_ok($url);
+ $self->note_deliveries;
+ $self;
+}
+
+sub tests_for {
+ my ($self, $permission) = @_;
+ my @tests;
+ if ($permission eq "public") {
+ push @tests, (
+ ["/pause/query"],
+ ["/pause/query", "TESTUSER"],
+ ["/pause/query", "TESTADMIN"],
+ );
+ }
+ if ($permission ne "admin") {
+ push @tests, ["/pause/authenquery", "TESTUSER"];
+ }
+ push @tests, ["/pause/authenquery", "TESTADMIN"];
+ $ENV{PAUSE_WEB_TEST_ALL} && wantarray ? @tests : $tests[0];
+}
+
+sub content {
+ my $self = shift;
+ $self->{mech}->content;
+}
+
+sub dom {
+ my $self = shift;
+ Mojo::DOM->new($self->content);
+}
+
+sub text_is {
+ my ($self, $selector, $expects) = @_;
+ my $at = $self->dom->at($selector);
+ if ($at) {
+ my $text = $at->all_text // '';
+ is $text => $expects, "$selector is $expects";
+ } else {
+ fail "'$selector' is not found";
+ }
+ $self;
+}
+
+sub text_like {
+ my ($self, $selector, $expects) = @_;
+ my $at = $self->dom->at($selector);
+ if ($at) {
+ my $text = $at->all_text // '';
+ like $text => $expects, "$selector like $expects";
+ } else {
+ fail "'$selector' is not found";
+ }
+ $self;
+}
+
+sub text_unlike {
+ my ($self, $selector, $expects) = @_;
+ my $at = $self->dom->at($selector);
+ if ($at) {
+ my $text = $at->all_text // '';
+ unlike $text => $expects, "$selector unlike $expects";
+ } else {
+ fail "'$selector' is not found";
+ }
+ $self;
+}
+
+sub dom_not_found {
+ my ($self, $selector) = @_;
+ my $at = $self->dom->at($selector);
+ if ($at) {
+ fail "'$selector' is found";
+ } else {
+ pass "'$selector' is not found";
+ }
+ $self;
+}
+
+sub title_is_ok {
+ my ($self, $url) = @_;
+ return if $self->dom->at('p.error_message'); # ignore if error
+ return if $self->{mech}->content_type !~ /html/i;
+
+ my ($action) = $url =~ /ACTION=(\w+)/;
+ $action ||= $url; # in case action is passed as url
+ return if $action =~ /^select_(user|ml_action)$/;
+ my $conf = PAUSE::Web2025::Config->action($action);
+ return if $conf->{has_title}; # uses different title from its data source
+
+ my $title = $conf->{verb};
+ return unless $title; # maybe top page
+
+ $self->text_is("h2.firstheader", $title);
+}
+
+sub file_to_upload {
+ wantarray ? ($FileToUpload, $FilenameToUpload) : $FileToUpload;
+}
+
+sub copy_to_authors_dir {
+ my ($self, $user, $file) = @_;
+ my $userhome = PAUSE::user2dir($user);
+ my $destination = path("$PAUSE::Config->{MLROOT}/$userhome");
+ $destination->mkpath;
+ note "copy $file to $destination";
+ path($file)->copy($destination);
+}
+
+sub save_to_authors_dir {
+ my ($self, $user, $file, $body) = @_;
+ my $userhome = PAUSE::user2dir($user);
+ my $destination = path("$PAUSE::Config->{MLROOT}/$userhome");
+ $destination->mkpath;
+ note "save $file to $destination";
+ path("$destination/$file")->spew($body);
+}
+
+sub remove_authors_dir {
+ my ($self, $user) = @_;
+ my $userhome = PAUSE::user2dir($user);
+ my $destination = path("$PAUSE::Config->{MLROOT}/$userhome");
+ $destination->remove_tree;
+}
+
+sub deliveries { map { $_->{email}->cast('Email::MIME') } Email::Sender::Simple->default_transport->deliveries }
+sub clear_deliveries { Email::Sender::Simple->default_transport->clear_deliveries }
+sub note_deliveries { note "-- email begin --\n".$_->as_string."\n-- email end --\n\n" for shift->deliveries }
+
+END { $TmpDir->remove_tree if $TmpDir }
+
+sub reset_module_fixture {
+ my $self = shift;
+
+ $self->mod_dbh->do("TRUNCATE primeur");
+ $self->mod_dbh->do("TRUNCATE perms");
+ $self->mod_dbh->do("TRUNCATE packages");
+
+ my @dists = (
+ {
+ name => 'Module-Admin',
+ owner => 'TESTADMIN',
+ packages => [qw/
+ Module::Admin::Foo
+ Module::Admin::Bar
+ /],
+ comaints => [qw/TESTUSER2/],
+ },
+ {
+ name => 'Module-User',
+ owner => 'TESTUSER',
+ packages => [qw/
+ Module::User::Foo
+ Module::User::Bar
+ /],
+ comaints => [
+ [TESTADMIN => [qw/Module::User::Foo/]],
+ [TESTUSER2 => [qw/Module::User::Bar/]],
+ ],
+ },
+ {
+ name => 'Module-User-Foo-Baz',
+ owner => 'TESTUSER',
+ packages => [qw/
+ Module::User::Foo::Baz
+ /],
+ },
+ {
+ name => 'Module-Comaint',
+ owner => 'TESTUSER2',
+ packages => [qw/
+ Module::Comaint
+ Module::Comaint::Foo
+ /],
+ comaints => [qw/TESTADMIN TESTUSER/],
+ },
+ {
+ name => 'Module-Managed',
+ owner => 'TESTUSER2',
+ packages => [qw/
+ Module::Managed
+ Module::Managed::Foo
+ /],
+ comaints => [
+ [TESTUSER3 => [qw/Module::Managed/]],
+ ],
+ },
+ {
+ name => 'Module-Unrelated',
+ owner => 'TESTUSER3',
+ packages => [qw/
+ Module::Unrelated
+ Module::Unrelated::Foo
+ /],
+ },
+ );
+
+ for my $dist (@dists) {
+ for my $package (@{$dist->{packages}}) {
+ my $userdir = _userdir($dist->{owner});
+ $self->mod_db->insert("packages", {
+ package => $package,
+ lc_package => lc $package,
+ version => '0.01',
+ dist => "$userdir/$dist->{name}-0.01.tar.gz",
+ distname => $dist->{name},
+ filemtime => time,
+ pause_reg => time,
+ status => 'index',
+ });
+ $self->mod_db->insert("primeur", {
+ package => $package,
+ lc_package => lc $package,
+ userid => $dist->{owner},
+ });
+ }
+ for my $comaint (@{$dist->{comaints} // []}) {
+ if (ref $comaint eq 'ARRAY') {
+ my ($id, $packages) = @$comaint;
+ for my $package (@$packages) {
+ $self->mod_db->insert("perms", {
+ package => $package,
+ lc_package => lc $package,
+ userid => $id,
+ });
+ }
+ } else {
+ for my $package (@{$dist->{packages}}) {
+ $self->mod_db->insert("perms", {
+ package => $package,
+ lc_package => lc $package,
+ userid => $comaint,
+ });
+ }
+ }
+ }
+ }
+}
+
+sub _userdir {
+ my $user = shift;
+ join '/', substr($user, 0, 1), substr($user, 0, 2), $user;
+}
+
+1;
diff --git a/t/pause_2025/logout.t b/t/pause_2025/logout.t
new file mode 100644
index 000000000..008ec23e9
--- /dev/null
+++ b/t/pause_2025/logout.t
@@ -0,0 +1,62 @@
+use Mojo::Base -strict;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use Test::PAUSE::Web;
+use utf8;
+use HTTP::Status qw/:constants/;
+
+Test::PAUSE::Web->setup;
+
+subtest 'for 2017 app' => sub {
+ subtest 'logout 1: redirect with Cookie' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $rand = rand 1;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path");
+ my $res = $t->get("$path?logout=1$rand");
+ is $res->code => HTTP_UNAUTHORIZED;
+ }
+ };
+
+ subtest 'logout 2: redirect to Badname:Badpass@Server URL' => sub {
+ plan skip_all => "WWW::Mechanize/LWP::UserAgent currently ignores userinfo";
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $rand = rand 1;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path");
+ my $res = $t->get("$path?logout=2$rand");
+ is $res->code => HTTP_UNAUTHORIZED;
+ }
+ };
+
+ subtest 'logout 3: Quick direct 401' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $rand = rand 1;
+ my $t = Test::PAUSE::Web->new(user => $user);
+ $t->get_ok("$path");
+ my $res = $t->get("$path?logout=3$rand");
+ is $res->code => HTTP_UNAUTHORIZED;
+ }
+ };
+};
+
+subtest 'for 2025 app' => sub {
+ # there's only one way to logout
+ subtest 'logout' => sub {
+ for my $test (Test::PAUSE::Web->tests_for('user')) {
+ my ($path, $user) = @$test;
+ my $rand = rand 1;
+ my $t = Test::PAUSE::Web->new;
+ $t->login(user => $user);
+ $t->get_ok("/user/logout");
+ $t->post_with_token_ok("/user/logout", {SUBMIT => 'Logout'});
+ my $res = $t->get("/user/logout");
+ is $res->code => HTTP_FORBIDDEN;
+ }
+ };
+};
+
+done_testing;