The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
use strict;
use warnings;

use Test::More;
use Smolder::TestScript;
use Smolder::TestData qw(
  base_url
  is_smolder_running
  create_developer
  delete_developers
  create_project
  delete_projects
  db_field_value
  create_preference
  delete_preferences
);
use Smolder::DB::ProjectDeveloper;
use Smolder::Mech;

if (is_smolder_running) {
    plan(tests => 98);
} else {
    plan(skip_all => 'Smolder not running');
}

my $mech     = Smolder::Mech->new();
my $BASE_URL = base_url() . '/admin_projects';
my $pw       = 's3cr3t';
my $admin    = create_developer(admin => 1, password => $pw);

END {
    delete_developers();
    delete_preferences();
}
my %data = (
    project_name => "Im A Test Project",
    start_date   => '01/01/2006',
    public       => 0,
    enable_feed  => 0,
    max_reports  => 20,
    extra_css    => '#header_extra { color: red }',
);
my $proj;

# 1
use_ok('Smolder::Control::Admin::Projects');

# 2..5
$mech->login(username => $admin->username, password => $pw);
ok($mech->success);
$mech->get_ok($BASE_URL);
$mech->content_contains('Admin');
$mech->content_contains('Projects');

# 6..31
# add
{

    # empty form
    $mech->follow_link_ok({text => 'Add New Project'});
    $mech->form_name('add');
    $mech->set_fields(max_reports => '');
    $mech->submit();
    ok($mech->success);
    $mech->content_contains('missing required fields');
    $mech->content_contains('class="required warn">Project Name');
    $mech->content_contains('class="required warn">Start Date');
    $mech->content_contains('class="required warn">Public Project');
    $mech->content_contains('class="required warn">Data Feeds');
    $mech->content_contains('class="required warn">Number of Full Reports Saved');

    # invalid form
    my $other_proj = create_project();
    END { delete_projects() }

    # invalid form
    # set these fields like this since WWW::Mechanize won't let us set them to non-existant values
    my $request = HTTP::Request::Common::POST(
        $mech->uri,
        {
            project_name => $other_proj->name,
            start_date   => '01/01/06',
            public       => 'abc',
            enable_feed  => 'efg',
            max_reports  => 'asd',
        }
    );
    $mech->request($request);

    ok($mech->success);
    $mech->content_contains('class="required warn">Project Name');
    $mech->content_contains('name already exists');

    $mech->content_contains('class="required warn">Start Date');
    $mech->content_contains('Invalid Start Date');
    $mech->content_contains('class="required warn">Public Project');
    $mech->content_contains('class="required warn">Data Feeds');
    $mech->content_contains('class="required warn">Number of Full Reports Saved');
    $mech->content_contains('Invalid Number of Full Reports Saved');

    # complete form
    $mech->form_name('add');
    $mech->set_fields(%data);
    $mech->submit();
    ok($mech->success);
    $mech->contains_message("New project '$data{project_name}' successfully created");
    ($proj) = Smolder::DB::Project->search(name => $data{project_name});
    isa_ok($proj, 'Smolder::DB::Project');
    foreach my $field (keys %data) {
        if( $field eq 'start_date' ) {
            isa_ok($proj->start_date, 'DateTime');
            is($proj->start_date->mdy('/'), $data{start_date}, 'start_date was saved correctly');
        } elsif($field eq 'project_name') {
            is($proj->name, $data{project_name}, 'project name was saved correctly');
        } else {
            is($proj->$field, $data{$field}, "$field field was saved correctly");
        }
    }

    END { $proj->delete() if ($proj) };    # make sure it's not left over after the tests
}

# 32..36
# details
{
    $mech->get_ok("$BASE_URL/details/$proj");
    $mech->content_contains($proj->name);
    $mech->content_contains($proj->start_date->strftime('%d/%m/%Y'));
    $mech->content_like(qr|Public Project\?</label>\s*</td>\s*<td>\s*No\s*|);
    $mech->content_like(qr|Data Feed[^<]*</label>\s*</td>\s*<td>\s*No\s*|);
}

# 37..53
# edit
{
    $mech->follow_link_ok({text => 'Edit'});

    # make sure it's prefilled
    my $form = $mech->form_name('edit');
    is($form->value('project_name'), $proj->name, 'name prefilled');
    is($form->value('start_date'), $proj->start_date->strftime('%d/%m/%Y'), 'start_date prefilled');
    is($form->value('public'), $proj->public, 'public prefilled');
    is($form->value('enable_feed'), $proj->enable_feed, 'enable_feed prefilled');

    # invalid form
    my $other_proj = create_project();
    my $url        = "$BASE_URL/process_add/$proj";
    my $request    = HTTP::Request::Common::POST(
        $url,
        {
            project_name => $other_proj->name,
            start_date   => '01/01/06',
            public       => 'abc',
            enable_feed  => 'def',
        }
    );
    $mech->request($request);
    ok($mech->success);

    $mech->content_contains('class="required warn">Project Name');
    $mech->content_contains('name already exists');
    $mech->content_contains('class="required warn">Start Date');
    $mech->content_contains('Invalid Start Date');
    $mech->content_contains('class="required warn">Public Project');
    $mech->content_contains('class="required warn">Data Feed');

    # valid
    $mech->form_name('edit');
    my %new_data = %data;
    $new_data{public}      = 1;
    $new_data{enable_feed} = 1;
    $mech->set_fields(%new_data);
    $mech->submit();
    ok($mech->success);
    $mech->contains_message("Project '$new_data{project_name}' successfully updated");
    $mech->get_ok("$BASE_URL/details/$proj");
    $mech->content_like(qr|Public Project\?</label>\s*</td>\s*<td>\s*Yes\s*|);
    $mech->content_like(qr|Data Feeds[^<]*</label>\s*</td>\s*<td>\s*Yes\s*|);
}

# 54..57
# list
{
    $mech->follow_link_ok({text => 'All Projects'});
    $mech->content_contains($proj->name);
    $mech->content_contains('Yes');
    $mech->follow_link_ok({text => '[Edit]', n => -1});
}

# 58..92
# devs, add_dev, change_admin and remove_dev
{

    # first 'devs'
    $mech->get_ok("$BASE_URL/devs/$proj");
    $mech->content_contains('No users are currently assigned to this project');

    my $dev1 = create_developer();
    my $dev2 = create_developer();
    my $dev3 = create_developer();
    my $url  = "$BASE_URL/add_dev";

    # add admin, dev1, dev2 and dev3 to proj
    # and try to add dev1 twice to make sure it doesn't cause an error
    foreach my $developer ($admin, $dev1, $dev2, $dev3, $dev1) {
        my $request = HTTP::Request::Common::POST(
            $url,
            {
                project   => $proj->id,
                developer => $developer->id,
            }
        );
        $mech->request($request);
        ok($mech->success);

        # make sure this developer is in this project
        my $proj_dev = Smolder::DB::ProjectDeveloper->retrieve(
            project   => $proj,
            developer => $developer,
        );
        isa_ok($proj_dev, 'Smolder::DB::ProjectDeveloper');
    }

    # make sure that all are listed under this proj's details
    $mech->get_ok("$BASE_URL/details/$proj");
    $mech->content_contains($proj->name);
    $mech->content_contains($dev1->username);
    $mech->content_contains($dev2->username);
    $mech->content_contains($dev3->username);
    $mech->follow_link_ok({text => $dev1->username});

    # get the admins for this project
    my @admins = $proj->admins();
    is(scalar @admins, 0, 'no admins currently');

    # set dev2 as the admin
    $mech->get_ok("$BASE_URL/change_admin?project=$proj&developer=$dev2");
    @admins = $proj->admins();
    is(scalar @admins, 1,         'now with 1 admin');
    is($admins[0]->id, $dev2->id, 'dev2 is now an admin');

    # set dev1 as an admin
    $mech->get_ok("$BASE_URL/change_admin?project=$proj&developer=$dev1");
    @admins = $proj->admins();
    is(scalar @admins, 2, 'now with 2 admin');
    is_deeply(
        [sort { $a->id <=> $b->id } @admins],
        [$dev1, $dev2],
        '2 correct devs are now admins',
    );

    # now unset dev2 as an admin
    $mech->get_ok("$BASE_URL/change_admin?project=$proj&developer=$dev2&remove=1");
    @admins = $proj->admins();
    is(scalar @admins, 1,         'now with 1 admin');
    is($admins[0]->id, $dev1->id, 'dev1 is now the only admin');

    # now remove_developer for $dev2
    $url = "$BASE_URL/remove_dev";
    my $request = HTTP::Request::Common::POST(
        $url,
        {
            project   => $proj->id,
            developer => $dev2->id,
        }
    );
    $mech->request($request);
    ok($mech->success);

    # make sure this developer is not in this project
    my $proj_dev = Smolder::DB::ProjectDeveloper->retrieve(
        project   => $proj->id,
        developer => $dev2->id,
    );
    ok(!defined $proj_dev);

    # make sure that dev2 is not listed under this proj's details
    $mech->get_ok("$BASE_URL/details/$proj");
    $mech->content_contains($proj->name);
    $mech->content_contains($dev1->username);
    $mech->content_lacks($dev2->username);
    $mech->content_contains($dev3->username);
}

# 93..97
# delete
{
    $mech->follow_link_ok({text => 'All Projects'});
    ok($mech->form_name("delete_$proj"));
    $mech->submit();
    ok($mech->success);
    $mech->content_contains('project_list');
    $mech->content_lacks($proj->name);
}