summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/01-static.t5
-rw-r--r--t/02-registration.t124
-rw-r--r--t/11-journey-stats.t178
-rw-r--r--t/12-journey-edit.t206
-rw-r--r--t/21-relations.t855
-rw-r--r--t/22-transit-visibility.t487
-rw-r--r--t/23-journey-visibility.t461
-rw-r--r--t/24-past-visibility.t558
-rw-r--r--t/r-negative-delay.t101
9 files changed, 2873 insertions, 102 deletions
diff --git a/t/01-static.t b/t/01-static.t
index 1d80ee0..3727f1e 100644
--- a/t/01-static.t
+++ b/t/01-static.t
@@ -1,4 +1,9 @@
#!/usr/bin/env perl
+
+# Copyright (C) 2020 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
use Mojo::Base -strict;
use Test::More;
diff --git a/t/02-registration.t b/t/02-registration.t
index 2258d9a..799022f 100644
--- a/t/02-registration.t
+++ b/t/02-registration.t
@@ -1,4 +1,9 @@
#!/usr/bin/env perl
+
+# Copyright (C) 2020 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
use Mojo::Base -strict;
# Tests the standard registration -> verification -> successful login flow
@@ -28,6 +33,7 @@ $t->app->pg->on(
$t->app->config->{mail}->{disabled} = 1;
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 1;
$t->app->start( 'database', 'migrate' );
my $csrf_token
@@ -38,6 +44,7 @@ my $csrf_token
$t->post_ok(
'/register' => form => {
csrf_token => $csrf_token,
+ dt => 1,
user => 'someone',
email => 'foo@example.org',
password => 'foofoofoo',
@@ -46,10 +53,23 @@ $t->post_ok(
);
$t->status_is(200)->content_like(qr{Verifizierungslink});
+# Failed registration (CSRF)
+$t->post_ok(
+ '/register' => form => {
+ csrf_token => $csrf_token,
+ user => 'noone',
+ email => 'foo2@example.org',
+ password => 'foofoofoo',
+ password2 => 'foofoofoo',
+ }
+);
+$t->status_is(400)->content_like(qr{CSRF});
+
# Failed registration (user name not available)
$t->post_ok(
'/register' => form => {
csrf_token => $csrf_token,
+ dt => 1,
user => 'someone',
email => 'foo@example.org',
password => 'foofoofoo',
@@ -69,7 +89,7 @@ $t->post_ok(
password => 'foofoofoo',
}
);
-$t->status_is(200)->content_like(qr{nicht freigeschaltet});
+$t->status_is(400)->content_like(qr{nicht freigeschaltet});
my $res = $t->app->pg->db->select( 'users', ['id'], { name => 'someone' } );
my $uid = $res->hash->{id};
@@ -89,7 +109,7 @@ $t->post_ok(
password => 'definitely invalid',
}
);
-$t->status_is(200)->content_like(qr{falsches Passwort});
+$t->status_is(400)->content_like(qr{falsches Passwort});
# Successful login
$t->post_ok(
@@ -201,105 +221,5 @@ $t->post_ok(
);
$t->status_is(302)->header_is( location => '/account' );
-$csrf_token
- = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
- ->attr('value');
-$t->post_ok(
- '/journey/add' => form => {
- csrf_token => $csrf_token,
- action => 'save',
- train => 'RE 42 11238',
- dep_station => 'EMST',
- sched_departure => '16.10.2018 17:36',
- rt_departure => '16.10.2018 17:36',
- arr_station => 'EG',
- sched_arrival => '16.10.2018 18:34',
- rt_arrival => '16.10.2018 18:34',
- comment => 'Passierschein A38',
- }
-);
-$t->status_is(302)->header_is( location => '/journey/1' );
-
-$t->get_ok('/journey/1')->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
- ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
- ->content_like(qr{Linie 42})->content_like(qr{..:36})
- ->content_like(qr{..:34})->content_like(qr{ca[.] 62 km})
- ->content_like(qr{Luftlinie: 62 km})->content_like(qr{64 km/h})
- ->content_like(qr{Passierschein A38});
-
-$t->get_ok('/history/2018/10')->status_is(200)->content_like(qr{62 km})
- ->content_like(qr{00:58 Stunden})->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:00 Stunden})
- ->content_like(qr{Bei Ankunft: 00:00 Stunden});
-
-$t->get_ok('/history/2018')->status_is(200)->content_like(qr{62 km})
- ->content_like(qr{00:58 Stunden})->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:00 Stunden})
- ->content_like(qr{Bei Ankunft: 00:00 Stunden});
-
-$t->get_ok('/history/map')->status_is(200)
- ->content_like(qr{\[\[51.956[^,]*,7.635[^]]*\],'M.nster\(Westf\)Hbf'\],})
- ->content_like(qr{\[\[51.504[^,]*,7.102[^]]*\],'Gelsenkirchen Hbf'\]});
-
-$csrf_token
- = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
- ->attr('value');
-$t->post_ok(
- '/journey/add' => form => {
- csrf_token => $csrf_token,
- action => 'save',
- train => 'RE 42 11238',
- dep_station => 'EMST',
- sched_departure => '16.11.2018 17:36',
- rt_departure => '16.11.2018 17:45',
- arr_station => 'EG',
- sched_arrival => '16.11.2018 18:34',
- rt_arrival => '16.11.2018 19:00',
- }
-);
-$t->status_is(302)->header_is( location => '/journey/2' );
-
-$t->get_ok('/history/2018/11')->status_is(200)->content_like(qr{62 km})
- ->content_like(qr{01:15 Stunden})->content_like(qr{nach Fahrplan: 00:58})
- ->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
- ->content_like(qr{Bei Ankunft: 00:26 Stunden});
-
-$t->get_ok('/history/2018')->status_is(200)->content_like(qr{124 km})
- ->content_like(qr{02:13 Stunden})->content_like(qr{nach Fahrplan: 01:56})
- ->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
- ->content_like(qr{Bei Ankunft: 00:26 Stunden});
-
-$csrf_token
- = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
- ->attr('value');
-$t->post_ok(
- '/journey/add' => form => {
- csrf_token => $csrf_token,
- action => 'save',
- train => 'ICE 1',
- dep_station => 'EE',
- sched_departure => '17.11.2018 15:42',
- rt_departure => '',
- arr_station => 'BL',
- sched_arrival => '17.11.2018 19:42',
- rt_arrival => '',
- }
-);
-$t->status_is(302)->header_is( location => '/journey/3' );
-
-$t->get_ok('/history/2018/11')->status_is(200)->content_like(qr{513 km})
- ->content_like(qr{05:15 Stunden})->content_like(qr{nach Fahrplan: 04:58})
- ->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
- ->content_like(qr{Bei Ankunft: 00:26 Stunden});
-
-$t->get_ok('/history/2018')->status_is(200)->content_like(qr{576 km})
- ->content_like(qr{06:13 Stunden})->content_like(qr{nach Fahrplan: 05:56})
- ->content_like(qr{00:00 Stunden})
- ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
- ->content_like(qr{Bei Ankunft: 00:26 Stunden});
-
$t->app->pg->db->query('drop schema travelynx_test_02 cascade');
done_testing();
diff --git a/t/11-journey-stats.t b/t/11-journey-stats.t
new file mode 100644
index 0000000..9853b85
--- /dev/null
+++ b/t/11-journey-stats.t
@@ -0,0 +1,178 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2020 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_11 cascade');
+$t->app->pg->db->query('create schema travelynx_test_11');
+$t->app->pg->db->query('set search_path to travelynx_test_11');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_11');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 0;
+$t->app->start( 'database', 'migrate' );
+
+my $csrf_token
+ = $t->ua->get('/register')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+
+# Successful registration
+$t->post_ok(
+ '/register' => form => {
+ csrf_token => $csrf_token,
+ dt => 1,
+ user => 'someone',
+ email => 'foo@example.org',
+ password => 'foofoofoo',
+ password2 => 'foofoofoo',
+ }
+);
+$t->status_is(200)->content_like(qr{Verifizierungslink});
+
+my $res = $t->app->pg->db->select( 'users', ['id'], { name => 'someone' } );
+my $uid = $res->hash->{id};
+$res = $t->app->pg->db->select( 'pending_registrations', ['token'],
+ { user_id => $uid } );
+my $token = $res->hash->{token};
+
+# Successful verification
+$t->get_ok("/reg/${uid}/${token}");
+$t->status_is(200)->content_like(qr{freigeschaltet});
+
+# Successful login
+$t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => 'someone',
+ password => 'foofoofoo',
+ }
+);
+$t->status_is(302)->header_is( location => '/' );
+
+$csrf_token
+ = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+$t->post_ok(
+ '/journey/add' => form => {
+ csrf_token => $csrf_token,
+ action => 'save',
+ train => 'RE 42 11238',
+ dep_station => 'EMST',
+ sched_departure => '16.10.2018 17:36',
+ rt_departure => '16.10.2018 17:36',
+ arr_station => 'EG',
+ sched_arrival => '16.10.2018 18:34',
+ rt_arrival => '16.10.2018 18:34',
+ comment => 'Passierschein A38',
+ }
+);
+$t->status_is(302)->header_is( location => '/journey/1' );
+
+$t->get_ok('/journey/1')->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})->content_like(qr{..:36})
+ ->content_like(qr{..:34})->content_like(qr{ca[.] 62 km})
+ ->content_like(qr{Luftlinie: 62 km})->content_like(qr{64 km/h})
+ ->content_like(qr{Passierschein A38})
+ ->content_like(qr{Daten wurden manuell eingetragen});
+
+$t->get_ok('/history/2018/10')->status_is(200)->content_like(qr{62 km})
+ ->content_like(qr{00:58 Stunden})->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:00 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:00 Stunden});
+
+$t->get_ok('/history/2018')->status_is(200)->content_like(qr{62 km})
+ ->content_like(qr{00:58 Stunden})->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:00 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:00 Stunden});
+
+$t->get_ok('/history/map')->status_is(200)
+ ->content_like(qr{\[\[51.956[^,]*,7.635[^]]*\],'M.nster\(Westf\)Hbf'\],})
+ ->content_like(qr{\[\[51.504[^,]*,7.102[^]]*\],'Gelsenkirchen Hbf'\]});
+
+$csrf_token
+ = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+$t->post_ok(
+ '/journey/add' => form => {
+ csrf_token => $csrf_token,
+ action => 'save',
+ train => 'RE 42 11238',
+ dep_station => 'EMST',
+ sched_departure => '16.11.2018 17:36',
+ rt_departure => '16.11.2018 17:45',
+ arr_station => 'EG',
+ sched_arrival => '16.11.2018 18:34',
+ rt_arrival => '16.11.2018 19:00',
+ }
+);
+$t->status_is(302)->header_is( location => '/journey/2' );
+
+$t->get_ok('/history/2018/11')->status_is(200)->content_like(qr{62 km})
+ ->content_like(qr{01:15 Stunden})->content_like(qr{nach Fahrplan: 00:58})
+ ->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:26 Stunden});
+
+$t->get_ok('/history/2018')->status_is(200)->content_like(qr{124 km})
+ ->content_like(qr{02:13 Stunden})->content_like(qr{nach Fahrplan: 01:56})
+ ->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:26 Stunden});
+
+$csrf_token
+ = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+$t->post_ok(
+ '/journey/add' => form => {
+ csrf_token => $csrf_token,
+ action => 'save',
+ train => 'ICE 1',
+ dep_station => 'EE',
+ sched_departure => '17.11.2018 15:42',
+ rt_departure => '',
+ arr_station => 'BL',
+ sched_arrival => '17.11.2018 19:42',
+ rt_arrival => '',
+ }
+);
+$t->status_is(302)->header_is( location => '/journey/3' );
+
+$t->get_ok('/history/2018/11')->status_is(200)->content_like(qr{513 km})
+ ->content_like(qr{05:15 Stunden})->content_like(qr{nach Fahrplan: 04:58})
+ ->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:26 Stunden});
+
+$t->get_ok('/history/2018')->status_is(200)->content_like(qr{576 km})
+ ->content_like(qr{06:13 Stunden})->content_like(qr{nach Fahrplan: 05:56})
+ ->content_like(qr{00:00 Stunden})
+ ->content_like(qr{Bei Abfahrt: 00:09 Stunden})
+ ->content_like(qr{Bei Ankunft: 00:26 Stunden});
+
+$t->app->pg->db->query('drop schema travelynx_test_11 cascade');
+done_testing();
diff --git a/t/12-journey-edit.t b/t/12-journey-edit.t
new file mode 100644
index 0000000..27e309b
--- /dev/null
+++ b/t/12-journey-edit.t
@@ -0,0 +1,206 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2020 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+use DateTime;
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_12 cascade');
+$t->app->pg->db->query('create schema travelynx_test_12');
+$t->app->pg->db->query('set search_path to travelynx_test_12');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_12');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 0;
+$t->app->start( 'database', 'migrate' );
+
+my $csrf_token
+ = $t->ua->get('/register')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+
+# Successful registration
+$t->post_ok(
+ '/register' => form => {
+ csrf_token => $csrf_token,
+ dt => 1,
+ user => 'someone',
+ email => 'foo@example.org',
+ password => 'foofoofoo',
+ password2 => 'foofoofoo',
+ }
+);
+$t->status_is(200)->content_like(qr{Verifizierungslink});
+
+my $res = $t->app->pg->db->select( 'users', ['id'], { name => 'someone' } );
+my $uid = $res->hash->{id};
+$res = $t->app->pg->db->select( 'pending_registrations', ['token'],
+ { user_id => $uid } );
+my $token = $res->hash->{token};
+
+# Successful verification
+$t->get_ok("/reg/${uid}/${token}");
+$t->status_is(200)->content_like(qr{freigeschaltet});
+
+# Successful login
+$t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => 'someone',
+ password => 'foofoofoo',
+ }
+);
+$t->status_is(302)->header_is( location => '/' );
+
+$t->app->journeys->add(
+ db => $t->app->pg->db,
+ uid => $uid,
+ dep_station => 'EMST',
+ arr_station => 'EG',
+ sched_departure => DateTime->new(
+ year => 2018,
+ month => 10,
+ day => 16,
+ hour => 17,
+ minute => 36,
+ time_zone => 'Europe/Berlin'
+ ),
+ rt_departure => DateTime->new(
+ year => 2018,
+ month => 10,
+ day => 16,
+ hour => 17,
+ minute => 36,
+ time_zone => 'Europe/Berlin'
+ ),
+ sched_arrival => DateTime->new(
+ year => 2018,
+ month => 10,
+ day => 16,
+ hour => 18,
+ minute => 34,
+ time_zone => 'Europe/Berlin'
+ ),
+ rt_arrival => DateTime->new(
+ year => 2018,
+ month => 10,
+ day => 16,
+ hour => 18,
+ minute => 34,
+ time_zone => 'Europe/Berlin'
+ ),
+ cancelled => 0,
+ train_type => 'RE',
+ train_line => '42',
+ train_no => '11238',
+ comment => 'Huhu'
+);
+
+$t->get_ok('/journey/1')->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})->content_like(qr{..:36})
+ ->content_like(qr{..:34})->content_like(qr{ca[.] 62 km})
+ ->content_like(qr{Luftlinie: 62 km})->content_like(qr{64 km/h})
+ ->content_like(qr{Huhu})->content_like(qr{Daten wurden manuell eingetragen});
+
+$t->post_ok(
+ '/journey/edit' => form => {
+ action => 'edit',
+ journey_id => 1,
+ }
+);
+
+$t->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})->content_like(qr{16.10.2018 ..:36})
+ ->content_like(qr{16.10.2018 ..:34})->content_like(qr{Huhu});
+
+$csrf_token = $t->tx->res->dom->at('input[name=csrf_token]')->attr('value');
+
+$t->post_ok(
+ '/journey/edit' => form => {
+ action => 'save',
+ journey_id => 1,
+ csrf_token => $csrf_token,
+ from_name => 'Münster(Westf)Hbf',
+ to_name => 'Gelsenkirchen Hbf',
+ sched_departure => '16.10.2018 17:36',
+ rt_departure => '16.10.2018 17:36',
+ sched_arrival => '16.10.2018 18:34',
+ rt_arrival => '16.10.2018 18:34',
+ }
+);
+
+$t->status_is(302)->header_is( location => '/journey/1' );
+
+$t->get_ok('/journey/1')->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})->content_like(qr{..:36})
+ ->content_like(qr{..:34})->content_like(qr{ca[.] 62 km})
+ ->content_like(qr{Luftlinie: 62 km})->content_like(qr{64 km/h})
+ ->content_like(qr{Huhu})->content_like(qr{Daten wurden manuell eingetragen});
+
+$t->post_ok(
+ '/journey/edit' => form => {
+ action => 'edit',
+ journey_id => 1,
+ }
+);
+
+$t->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})->content_like(qr{16.10.2018 ..:36})
+ ->content_like(qr{16.10.2018 ..:34})->content_like(qr{Huhu});
+
+$csrf_token = $t->tx->res->dom->at('input[name=csrf_token]')->attr('value');
+
+$t->post_ok(
+ '/journey/edit' => form => {
+ action => 'save',
+ journey_id => 1,
+ csrf_token => $csrf_token,
+ from_name => 'Münster(Westf)Hbf',
+ to_name => 'Gelsenkirchen Hbf',
+ sched_departure => '16.10.2018 17:36',
+ rt_departure => '16.10.2018 17:42',
+ sched_arrival => '16.10.2018 18:34',
+ rt_arrival => '16.10.2018 18:33',
+ }
+);
+
+$t->status_is(302)->header_is( location => '/journey/1' );
+
+$t->get_ok('/journey/1')->status_is(200)->content_like(qr{M.nster\(Westf\)Hbf})
+ ->content_like(qr{Gelsenkirchen Hbf})->content_like(qr{RE 11238})
+ ->content_like(qr{Linie 42})
+ ->content_like(qr{..:42\s*\(\+6,\s*Plan: ..:36\)})
+ ->content_like(qr{..:33\s*\(-1,\s*Plan: ..:34\)})
+ ->content_like(qr{ca[.] 62 km})->content_like(qr{Luftlinie: 62 km})
+ ->content_like(qr{73 km/h})->content_like(qr{Huhu})
+ ->content_like(qr{Daten wurden manuell eingetragen});
+
+$t->app->pg->db->query('drop schema travelynx_test_12 cascade');
+done_testing();
diff --git a/t/21-relations.t b/t/21-relations.t
new file mode 100644
index 0000000..857d20d
--- /dev/null
+++ b/t/21-relations.t
@@ -0,0 +1,855 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2023 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_21 cascade');
+$t->app->pg->db->query('create schema travelynx_test_21');
+$t->app->pg->db->query('set search_path to travelynx_test_21');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_21');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 1;
+$t->app->start( 'database', 'migrate' );
+
+my $u = $t->app->users;
+
+my $uid1 = $u->add(
+ name => 'test1',
+ email => 'test1@example.org',
+ token => 'abcd',
+ password => q{},
+);
+
+my $uid2 = $u->add(
+ name => 'test2',
+ email => 'test2@example.org',
+ token => 'efgh',
+ password => q{},
+);
+
+$u->verify_registration_token(
+ uid => $uid1,
+ token => 'abcd'
+);
+$u->verify_registration_token(
+ uid => $uid2,
+ token => 'efgh'
+);
+
+$u->set_social(
+ uid => $uid1,
+ accept_follow_requests => 1
+);
+$u->set_social(
+ uid => $uid2,
+ accept_follow_requests => 1
+);
+
+is(
+ $u->get_relation(
+ uid => $uid1,
+ target => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ uid => $uid2,
+ target => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$u->request_follow(
+ uid => $uid1,
+ target => $uid2
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'requests_follow'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 1 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 1
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 1 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 1
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 1 );
+is_deeply(
+ [ $u->get_follow_requests( uid => $uid2 ) ],
+ [ { id => $uid1, name => 'test1' } ]
+);
+is_deeply(
+ [ $u->get_follow_requests( uid => $uid1, sent => 1 ) ],
+ [ { id => $uid2, name => 'test2' } ]
+);
+
+$u->reject_follow_request(
+ uid => $uid2,
+ applicant => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$u->request_follow(
+ uid => $uid1,
+ target => $uid2
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'requests_follow'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 1 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 1
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 1 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 1
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 1 );
+is_deeply(
+ [ $u->get_follow_requests( uid => $uid2 ) ],
+ [ { id => $uid1, name => 'test1' } ]
+);
+is_deeply(
+ [ $u->get_follow_requests( uid => $uid1, sent => 1 ) ],
+ [ { id => $uid2, name => 'test2' } ]
+);
+
+$u->accept_follow_request(
+ uid => $uid2,
+ applicant => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'follows'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 1 );
+is( scalar $u->get_followees( uid => $uid1 ), 1 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+is_deeply(
+ [ $u->get_followers( uid => $uid2 ) ],
+ [
+ {
+ id => $uid1,
+ name => 'test1',
+ following_back => 0,
+ followback_requested => 0,
+ can_follow_back => 0,
+ can_request_follow_back => 1
+ }
+ ]
+);
+is_deeply(
+ [ $u->get_followees( uid => $uid1 ) ],
+ [ { id => $uid2, name => 'test2', following_back => 0 } ]
+);
+
+$u->remove_follower(
+ uid => $uid2,
+ follower => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$u->request_follow(
+ uid => $uid1,
+ target => $uid2
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'requests_follow'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+
+$u->block(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'is_blocked_by'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 1 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+is_deeply(
+ [ $u->get_blocked_users( uid => $uid2 ) ],
+ [ { id => $uid1, name => 'test1' } ]
+);
+
+$u->unblock(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$u->block(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'is_blocked_by'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 1 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+is_deeply(
+ [ $u->get_blocked_users( uid => $uid2 ) ],
+ [ { id => $uid1, name => 'test1' } ]
+);
+
+$u->unblock(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$u->request_follow(
+ uid => $uid1,
+ target => $uid2
+);
+$u->accept_follow_request(
+ uid => $uid2,
+ applicant => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ 'follows'
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 1 );
+is( scalar $u->get_followees( uid => $uid1 ), 1 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+is_deeply(
+ [ $u->get_followers( uid => $uid2 ) ],
+ [
+ {
+ id => $uid1,
+ name => 'test1',
+ following_back => 0,
+ followback_requested => 0,
+ can_follow_back => 0,
+ can_request_follow_back => 1
+ }
+ ]
+);
+is_deeply(
+ [ $u->get_followees( uid => $uid1 ) ],
+ [ { id => $uid2, name => 'test2', following_back => 0 } ]
+);
+
+$u->unfollow(
+ uid => $uid1,
+ target => $uid2
+);
+
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ undef
+);
+is( scalar $u->get_followers( uid => $uid1 ), 0 );
+is( scalar $u->get_followers( uid => $uid2 ), 0 );
+is( scalar $u->get_followees( uid => $uid1 ), 0 );
+is( scalar $u->get_followees( uid => $uid2 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid1 ), 0 );
+is( scalar $u->get_follow_requests( uid => $uid2 ), 0 );
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ scalar $u->get_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( scalar $u->get_blocked_users( uid => $uid1 ), 0 );
+is( scalar $u->get_blocked_users( uid => $uid2 ), 0 );
+is( $u->has_follow_requests( uid => $uid1 ), 0 );
+is( $u->has_follow_requests( uid => $uid2 ), 0 );
+is(
+ $u->has_follow_requests(
+ uid => $uid1,
+ sent => 1
+ ),
+ 0
+);
+is(
+ $u->has_follow_requests(
+ uid => $uid2,
+ sent => 1
+ ),
+ 0
+);
+is( $u->get( uid => $uid1 )->{notifications}, 0 );
+is( $u->get( uid => $uid2 )->{notifications}, 0 );
+
+$t->app->pg->db->query('drop schema travelynx_test_21 cascade');
+done_testing();
diff --git a/t/22-transit-visibility.t b/t/22-transit-visibility.t
new file mode 100644
index 0000000..7e995c5
--- /dev/null
+++ b/t/22-transit-visibility.t
@@ -0,0 +1,487 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2023 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+use DateTime;
+use Travel::Status::DE::IRIS::Result;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_22 cascade');
+$t->app->pg->db->query('create schema travelynx_test_22');
+$t->app->pg->db->query('set search_path to travelynx_test_22');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_22');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 1;
+$t->app->start( 'database', 'migrate' );
+
+my $u = $t->app->users;
+
+sub login {
+ my %opt = @_;
+ my $csrf_token
+ = $t->ua->get('/login')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => $opt{user},
+ password => $opt{password},
+ }
+ );
+ $t->status_is(302)->header_is( location => '/' );
+}
+
+sub logout {
+ my $csrf_token
+ = $t->ua->get('/account')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/logout' => form => {
+ csrf_token => $csrf_token,
+ }
+ );
+ $t->status_is(302)->header_is( location => '/login' );
+}
+
+sub test_intransit_visibility {
+ my %opt = @_;
+
+ if ( $opt{set_default_visibility} ) {
+ my %p = %{ $u->get_privacy_by( uid => $opt{uid} ) };
+ $p{default_visibility} = $opt{set_default_visibility};
+ $u->set_privacy(
+ uid => $opt{uid},
+ %p
+ );
+ }
+
+ if ( $opt{set_visibility} ) {
+ $t->app->in_transit->update_visibility(
+ uid => $opt{uid},
+ visibility => $opt{set_visibility}
+ );
+ }
+
+ my $status = $t->app->get_user_status( $opt{uid} );
+ my $token
+ = $status->{sched_departure}->epoch
+ . q{?token=}
+ . $status->{dep_eva} . q{-}
+ . $status->{timestamp}->epoch % 337;
+ my $j_token
+ = $status->{dep_eva} . q{-}
+ . $status->{timestamp}->epoch % 337 . q{-}
+ . $status->{sched_departure}->epoch;
+
+ my $desc
+ = "in_transit vis=$opt{effective_visibility_str} (from $opt{visibility_str})";
+
+ is( $status->{visibility}, $opt{visibility}, $desc );
+ is( $status->{visibility_str}, $opt{visibility_str}, $desc );
+ is( $status->{effective_visibility}, $opt{effective_visibility}, $desc );
+ is( $status->{effective_visibility_str},
+ $opt{effective_visibility_str}, $desc );
+
+ if ( $opt{public} ) {
+ $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok('/status/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ if ( $opt{with_token} ) {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ login(
+ user => 'test1',
+ password => 'password1'
+ );
+
+ # users can see their own status if visibility is >= followrs
+ if ( $opt{effective_visibility} >= 60 ) {
+ $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok('/status/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ # users can see their own status with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ logout();
+ login(
+ user => 'test2',
+ password => 'password2'
+ );
+
+ # uid2 can see uid1 if visibility is >= followers
+ if ( $opt{effective_visibility} >= 60 ) {
+ $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok('/status/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ # uid2 can see uid1 with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ logout();
+ login(
+ user => 'test3',
+ password => 'password3'
+ );
+
+ # uid3 can see uid1 if visibility is >= travelynx
+ if ( $opt{effective_visibility} >= 80 ) {
+ $t->get_ok('/status/test1')->status_is(200)->content_like(qr{DPN 667});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok('/p/test1')->status_is(200)->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok('/status/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/ajax/status/test1.html')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ # uid3 can see uid1 with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/status/test1/$token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ $t->get_ok("/ajax/status/test1.html?token=$j_token")->status_is(200)
+ ->content_like(qr{nicht eingecheckt});
+ }
+
+ logout();
+}
+
+my $uid1 = $u->add(
+ name => 'test1',
+ email => 'test1@example.org',
+ token => 'abcd',
+ password => 'password1',
+);
+
+my $uid2 = $u->add(
+ name => 'test2',
+ email => 'test2@example.org',
+ token => 'efgh',
+ password => 'password2',
+);
+
+my $uid3 = $u->add(
+ name => 'test3',
+ email => 'test3@example.org',
+ token => 'ijkl',
+ password => 'password3',
+);
+
+$u->verify_registration_token(
+ uid => $uid1,
+ token => 'abcd'
+);
+$u->verify_registration_token(
+ uid => $uid2,
+ token => 'efgh'
+);
+$u->verify_registration_token(
+ uid => $uid3,
+ token => 'ijkl'
+);
+
+$u->set_social(
+ uid => $uid1,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid2,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid3,
+ accept_follows => 1
+);
+
+$u->follow(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ 'follows'
+);
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+
+my $dep = DateTime->now;
+my $arr = $dep->clone->add( hours => 1 );
+my $train_dep = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-1',
+ departure_ts => '2306251312',
+ platform => 8,
+ station => 'Aachen Hbf',
+ station_uic => 8000001,
+ route_post => 'Mainz Hbf|Aalen Hbf',
+);
+my $train_arr = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-3',
+ arrival_ts => '2306252000',
+ platform => 1,
+ station => 'Aalen Hbf',
+ station_uic => 8000002,
+ route_pre => 'Aachen Hbf|Mainz Hbf',
+);
+$t->app->in_transit->add(
+ uid => $uid1,
+ departure_eva => 8000001,
+ train => $train_dep,
+ route => [],
+);
+$t->app->in_transit->set_arrival_eva(
+ uid => $uid1,
+ arrival_eva => 8000002,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 10,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 10,
+ effective_visibility_str => 'private',
+ public => 0,
+ with_token => 0,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 30,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 60,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 60,
+ effective_visibility_str => 'followers',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 80,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 80,
+ effective_visibility_str => 'travelynx',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 100,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 100,
+ effective_visibility_str => 'public',
+ public => 1,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_visibility => 'private',
+ visibility => 10,
+ visibility_str => 'private',
+ effective_visibility => 10,
+ effective_visibility_str => 'private',
+ public => 0,
+ with_token => 0,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_visibility => 'unlisted',
+ visibility => 30,
+ visibility_str => 'unlisted',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_visibility => 'followers',
+ visibility => 60,
+ visibility_str => 'followers',
+ effective_visibility => 60,
+ effective_visibility_str => 'followers',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_visibility => 'travelynx',
+ visibility => 80,
+ visibility_str => 'travelynx',
+ effective_visibility => 80,
+ effective_visibility_str => 'travelynx',
+ public => 0,
+ with_token => 1,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_visibility => 'public',
+ visibility => 100,
+ visibility_str => 'public',
+ effective_visibility => 100,
+ effective_visibility_str => 'public',
+ public => 1,
+ with_token => 1,
+);
+
+$t->app->in_transit->update_visibility(
+ uid => $uid1,
+ visibility => undef,
+);
+
+test_intransit_visibility(
+ uid => $uid1,
+ set_default_visibility => 10,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 10,
+ effective_visibility_str => 'private',
+ public => 0,
+ with_token => 0,
+);
+
+$t->app->pg->db->query('drop schema travelynx_test_22 cascade');
+done_testing();
diff --git a/t/23-journey-visibility.t b/t/23-journey-visibility.t
new file mode 100644
index 0000000..2124940
--- /dev/null
+++ b/t/23-journey-visibility.t
@@ -0,0 +1,461 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2023 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+use DateTime;
+use Travel::Status::DE::IRIS::Result;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_23 cascade');
+$t->app->pg->db->query('create schema travelynx_test_23');
+$t->app->pg->db->query('set search_path to travelynx_test_23');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_23');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 1;
+$t->app->start( 'database', 'migrate' );
+
+my $u = $t->app->users;
+
+sub login {
+ my %opt = @_;
+ my $csrf_token
+ = $t->ua->get('/login')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => $opt{user},
+ password => $opt{password},
+ }
+ );
+ $t->status_is(302)->header_is( location => '/' );
+}
+
+sub logout {
+ my $csrf_token
+ = $t->ua->get('/account')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/logout' => form => {
+ csrf_token => $csrf_token,
+ }
+ );
+ $t->status_is(302)->header_is( location => '/login' );
+}
+
+sub test_journey_visibility {
+ my %opt = @_;
+ my $jid = $opt{journey_id};
+
+ if ( $opt{set_default_visibility} ) {
+ my %p = %{ $u->get_privacy_by( uid => $opt{uid} ) };
+ $p{default_visibility} = $opt{set_default_visibility};
+ $u->set_privacy(
+ uid => $opt{uid},
+ %p
+ );
+ }
+
+ if ( $opt{set_visibility} ) {
+ $t->app->journeys->update_visibility(
+ uid => $opt{uid},
+ id => $jid,
+ visibility => $opt{set_visibility}
+ );
+ }
+
+ my $status = $t->app->get_user_status( $opt{uid} );
+ my $journey = $t->app->journeys->get_single(
+ uid => $opt{uid},
+ journey_id => $jid
+ );
+ my $token
+ = q{?token=}
+ . $status->{dep_eva} . q{-}
+ . $journey->{checkin_ts} % 337 . q{-}
+ . $status->{sched_departure}->epoch;
+
+ my $desc
+ = "journey=$jid vis=$opt{effective_visibility_str} (from $opt{visibility_str})";
+
+ is( $status->{visibility}, $opt{visibility}, $desc );
+ is( $status->{visibility_str}, $opt{visibility_str}, $desc );
+ is( $status->{effective_visibility}, $opt{effective_visibility}, $desc );
+ is( $status->{effective_visibility_str},
+ $opt{effective_visibility_str}, $desc );
+
+ if ( $opt{public} ) {
+ $t->get_ok("/p/test1/j/$jid")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ if ( $opt{with_token} ) {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ login(
+ user => 'test1',
+ password => 'password1'
+ );
+
+ # users can see their own status if visibility is >= followrs
+ if ( $opt{effective_visibility} >= 60 ) {
+ $t->get_ok("/p/test1/j/$jid")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ # users can see their own status with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ logout();
+ login(
+ user => 'test2',
+ password => 'password2'
+ );
+
+ # uid2 can see uid1 if visibility is >= followers
+ if ( $opt{effective_visibility} >= 60 ) {
+ $t->get_ok("/p/test1/j/$jid")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ # uid2 can see uid1 with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ logout();
+ login(
+ user => 'test3',
+ password => 'password3'
+ );
+
+ # uid3 can see uid1 if visibility is >= travelynx
+ if ( $opt{effective_visibility} >= 80 ) {
+ $t->get_ok("/p/test1/j/$jid")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ # uid3 can see uid1 with token if visibility is >= unlisted
+ if ( $opt{effective_visibility} >= 30 ) {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(200)
+ ->content_like(qr{DPN 667});
+ }
+ else {
+ $t->get_ok("/p/test1/j/$jid$token")->status_is(404)
+ ->content_like(qr{Fahrt nicht gefunden.});
+ }
+
+ logout();
+}
+
+my $uid1 = $u->add(
+ name => 'test1',
+ email => 'test1@example.org',
+ token => 'abcd',
+ password => 'password1',
+);
+
+my $uid2 = $u->add(
+ name => 'test2',
+ email => 'test2@example.org',
+ token => 'efgh',
+ password => 'password2',
+);
+
+my $uid3 = $u->add(
+ name => 'test3',
+ email => 'test3@example.org',
+ token => 'ijkl',
+ password => 'password3',
+);
+
+$u->verify_registration_token(
+ uid => $uid1,
+ token => 'abcd'
+);
+$u->verify_registration_token(
+ uid => $uid2,
+ token => 'efgh'
+);
+$u->verify_registration_token(
+ uid => $uid3,
+ token => 'ijkl'
+);
+
+$u->set_social(
+ uid => $uid1,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid2,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid3,
+ accept_follows => 1
+);
+
+$u->follow(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ 'follows'
+);
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+
+my $dep = DateTime->now->subtract( hours => 2 );
+my $arr = DateTime->now->subtract( hours => 1 );
+my $train_dep = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-1',
+ departure_ts => $dep->strftime('%y%m%d%H%M'),
+ platform => 8,
+ station => 'Aachen Hbf',
+ station_uic => 8000001,
+ route_post => 'Mainz Hbf|Aalen Hbf',
+);
+my $train_arr = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-3',
+ arrival_ts => $arr->strftime('%y%m%d%H%M'),
+ platform => 1,
+ station => 'Aalen Hbf',
+ station_uic => 8000002,
+ route_pre => 'Aachen Hbf|Mainz Hbf',
+);
+$t->app->in_transit->add(
+ uid => $uid1,
+ departure_eva => 8000001,
+ train => $train_dep,
+ route => [],
+);
+$t->app->in_transit->set_arrival_eva(
+ uid => $uid1,
+ arrival_eva => 8000002,
+);
+
+my $db = $t->app->pg->db;
+my $tx = $db->begin;
+
+my $journey = $t->app->in_transit->get(
+ uid => $uid1,
+ db => $db,
+);
+my $jid = $t->app->journeys->add_from_in_transit(
+ journey => $journey,
+ db => $db
+);
+$t->app->in_transit->delete(
+ uid => $uid1,
+ db => $db
+);
+$tx->commit;
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_default_visibility => 10,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 10,
+ effective_visibility_str => 'private',
+ public => 0,
+ with_token => 0,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_default_visibility => 30,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_default_visibility => 60,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 60,
+ effective_visibility_str => 'followers',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_default_visibility => 80,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 80,
+ effective_visibility_str => 'travelynx',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_default_visibility => 100,
+ visibility => undef,
+ visibility_str => 'default',
+ effective_visibility => 100,
+ effective_visibility_str => 'public',
+ public => 1,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'private',
+ visibility => 10,
+ visibility_str => 'private',
+ effective_visibility => 10,
+ effective_visibility_str => 'private',
+ public => 0,
+ with_token => 0,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'unlisted',
+ visibility => 30,
+ visibility_str => 'unlisted',
+ effective_visibility => 30,
+ effective_visibility_str => 'unlisted',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'followers',
+ visibility => 60,
+ visibility_str => 'followers',
+ effective_visibility => 60,
+ effective_visibility_str => 'followers',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'travelynx',
+ visibility => 80,
+ visibility_str => 'travelynx',
+ effective_visibility => 80,
+ effective_visibility_str => 'travelynx',
+ public => 0,
+ with_token => 1,
+);
+
+test_journey_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'public',
+ visibility => 100,
+ visibility_str => 'public',
+ effective_visibility => 100,
+ effective_visibility_str => 'public',
+ public => 1,
+ with_token => 1,
+);
+
+$t->app->pg->db->query('drop schema travelynx_test_23 cascade');
+done_testing();
diff --git a/t/24-past-visibility.t b/t/24-past-visibility.t
new file mode 100644
index 0000000..51c8081
--- /dev/null
+++ b/t/24-past-visibility.t
@@ -0,0 +1,558 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2023 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Tests journey entry and statistics
+
+use Test::More;
+use Test::Mojo;
+
+use DateTime;
+use Travel::Status::DE::IRIS::Result;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query('drop schema if exists travelynx_test_24 cascade');
+$t->app->pg->db->query('create schema travelynx_test_24');
+$t->app->pg->db->query('set search_path to travelynx_test_24');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_test_24');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 1;
+$t->app->start( 'database', 'migrate' );
+
+my $u = $t->app->users;
+
+sub login {
+ my %opt = @_;
+ my $csrf_token
+ = $t->ua->get('/login')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => $opt{user},
+ password => $opt{password},
+ }
+ );
+ $t->status_is(302)->header_is( location => '/' );
+}
+
+sub logout {
+ my $csrf_token
+ = $t->ua->get('/account')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+ $t->post_ok(
+ '/logout' => form => {
+ csrf_token => $csrf_token,
+ }
+ );
+ $t->status_is(302)->header_is( location => '/login' );
+}
+
+sub test_history_visibility {
+ my %opt = @_;
+ my $jid = $opt{journey_id};
+
+ if ( $opt{set_default_visibility} ) {
+ my %p = %{ $u->get_privacy_by( uid => $opt{uid} ) };
+ $p{default_visibility} = $opt{set_default_visibility};
+ $u->set_privacy(
+ uid => $opt{uid},
+ %p
+ );
+ }
+
+ if ( $opt{set_past_visibility} ) {
+ my %p = %{ $u->get_privacy_by( uid => $opt{uid} ) };
+ $p{past_visibility} = $opt{set_past_visibility};
+ $u->set_privacy(
+ uid => $opt{uid},
+ %p
+ );
+ }
+
+ if ( $opt{set_visibility} ) {
+ $t->app->journeys->update_visibility(
+ uid => $opt{uid},
+ id => $jid,
+ visibility => $opt{set_visibility}
+ );
+ }
+
+ my $status = $t->app->get_user_status( $opt{uid} );
+ my $journey = $t->app->journeys->get_single(
+ uid => $opt{uid},
+ journey_id => $jid,
+ with_visibility => 1,
+ );
+ my $token
+ = q{?token=}
+ . $status->{dep_eva} . q{-}
+ . $journey->{checkin_ts} % 337 . q{-}
+ . $status->{sched_departure}->epoch;
+
+ $opt{set_past_visibility} //= q{};
+ my $desc
+ = "history vis=$opt{set_past_visibility} journey=$jid vis=$journey->{effective_visibility_str}";
+
+ if ( $opt{public} ) {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like( qr{DPN 667}, "public $desc" );
+ }
+ else {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_unlike( qr{DPN 667}, "public $desc" );
+ }
+
+ login(
+ user => 'test1',
+ password => 'password1'
+ );
+
+ if ( $opt{self} ) {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like( qr{DPN 667}, "self $desc" );
+ }
+ else {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_unlike( qr{DPN 667}, "self $desc" );
+ }
+
+ logout();
+ login(
+ user => 'test2',
+ password => 'password2'
+ );
+
+ if ( $opt{followers} ) {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like( qr{DPN 667}, "follower $desc" );
+ }
+ else {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_unlike( qr{DPN 667}, "follower $desc" );
+ }
+
+ logout();
+ login(
+ user => 'test3',
+ password => 'password3'
+ );
+
+ if ( $opt{travelynx} ) {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_like( qr{DPN 667}, "travelynx $desc" );
+ }
+ else {
+ $t->get_ok('/p/test1')->status_is(200)
+ ->content_unlike( qr{DPN 667}, "travelynx $desc" );
+ }
+
+ logout();
+}
+
+my $uid1 = $u->add(
+ name => 'test1',
+ email => 'test1@example.org',
+ token => 'abcd',
+ password => 'password1',
+);
+
+my $uid2 = $u->add(
+ name => 'test2',
+ email => 'test2@example.org',
+ token => 'efgh',
+ password => 'password2',
+);
+
+my $uid3 = $u->add(
+ name => 'test3',
+ email => 'test3@example.org',
+ token => 'ijkl',
+ password => 'password3',
+);
+
+$u->verify_registration_token(
+ uid => $uid1,
+ token => 'abcd'
+);
+$u->verify_registration_token(
+ uid => $uid2,
+ token => 'efgh'
+);
+$u->verify_registration_token(
+ uid => $uid3,
+ token => 'ijkl'
+);
+
+$u->set_social(
+ uid => $uid1,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid2,
+ accept_follows => 1
+);
+$u->set_social(
+ uid => $uid3,
+ accept_follows => 1
+);
+
+$u->follow(
+ uid => $uid2,
+ target => $uid1
+);
+
+is(
+ $u->get_relation(
+ subject => $uid2,
+ object => $uid1
+ ),
+ 'follows'
+);
+is(
+ $u->get_relation(
+ subject => $uid1,
+ object => $uid2
+ ),
+ undef
+);
+
+my $dep = DateTime->now->subtract( hours => 2 );
+my $arr = DateTime->now->subtract( hours => 1 );
+my $train_dep = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-1',
+ departure_ts => $dep->strftime('%y%m%d%H%M'),
+ platform => 8,
+ station => 'Aachen Hbf',
+ station_uic => 8000001,
+ route_post => 'Mainz Hbf|Aalen Hbf',
+);
+my $train_arr = Travel::Status::DE::IRIS::Result->new(
+ classes => 'N',
+ type => 'DPN',
+ train_no => '667',
+ raw_id => '1234-2306251312-3',
+ arrival_ts => $arr->strftime('%y%m%d%H%M'),
+ platform => 1,
+ station => 'Aalen Hbf',
+ station_uic => 8000002,
+ route_pre => 'Aachen Hbf|Mainz Hbf',
+);
+$t->app->in_transit->add(
+ uid => $uid1,
+ departure_eva => 8000001,
+ train => $train_dep,
+ route => [],
+);
+$t->app->in_transit->set_arrival_eva(
+ uid => $uid1,
+ arrival_eva => 8000002,
+);
+
+$t->app->in_transit->update_visibility(
+ uid => $uid1,
+ visibility => 'public',
+);
+
+my $db = $t->app->pg->db;
+my $tx = $db->begin;
+
+my $journey = $t->app->in_transit->get(
+ uid => $uid1,
+ db => $db,
+);
+my $jid = $t->app->journeys->add_from_in_transit(
+ journey => $journey,
+ db => $db
+);
+$t->app->in_transit->delete(
+ uid => $uid1,
+ db => $db
+);
+$tx->commit;
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_past_visibility => 60,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_past_visibility => 80,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_past_visibility => 100,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 1,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'private',
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'private',
+ set_past_visibility => 60,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'private',
+ set_past_visibility => 80,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'private',
+ set_past_visibility => 100,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'unlisted',
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'unlisted',
+ set_past_visibility => 60,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'unlisted',
+ set_past_visibility => 80,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'unlisted',
+ set_past_visibility => 100,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'followers',
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'followers',
+ set_past_visibility => 60,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'followers',
+ set_past_visibility => 80,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'followers',
+ set_past_visibility => 100,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'travelynx',
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'travelynx',
+ set_past_visibility => 60,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'travelynx',
+ set_past_visibility => 80,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'travelynx',
+ set_past_visibility => 100,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'public',
+ set_past_visibility => 10,
+ self => 0,
+ followers => 0,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'public',
+ set_past_visibility => 60,
+ self => 1,
+ followers => 1,
+ travelynx => 0,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'public',
+ set_past_visibility => 80,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 0,
+);
+
+test_history_visibility(
+ uid => $uid1,
+ journey_id => $jid,
+ set_visibility => 'public',
+ set_past_visibility => 100,
+ self => 1,
+ followers => 1,
+ travelynx => 1,
+ public => 1,
+);
+
+$t->app->pg->db->query('drop schema travelynx_test_24 cascade');
+done_testing();
diff --git a/t/r-negative-delay.t b/t/r-negative-delay.t
new file mode 100644
index 0000000..78bd6e0
--- /dev/null
+++ b/t/r-negative-delay.t
@@ -0,0 +1,101 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2020 Birte Kristina Friesel <derf@finalrewind.org>
+#
+# SPDX-License-Identifier: MIT
+
+use Mojo::Base -strict;
+
+# Regression test: handle negative cumulative arrival / departure delay
+
+use Test::More;
+use Test::Mojo;
+
+# Include application
+use FindBin;
+require "$FindBin::Bin/../index.pl";
+
+my $t = Test::Mojo->new('Travelynx');
+
+if ( not $t->app->config->{db} ) {
+ plan( skip_all => 'No database configured' );
+}
+
+$t->app->pg->db->query(
+ 'drop schema if exists travelynx_regr_negative_delay cascade');
+$t->app->pg->db->query('create schema travelynx_regr_negative_delay');
+$t->app->pg->db->query('set search_path to travelynx_regr_negative_delay');
+$t->app->pg->on(
+ connection => sub {
+ my ( $pg, $dbh ) = @_;
+ $dbh->do('set search_path to travelynx_regr_negative_delay');
+ }
+);
+
+$t->app->config->{mail}->{disabled} = 1;
+
+$ENV{__TRAVELYNX_TEST_MINI_IRIS} = 0;
+$t->app->start( 'database', 'migrate' );
+
+my $csrf_token
+ = $t->ua->get('/register')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+
+# Successful registration
+$t->post_ok(
+ '/register' => form => {
+ csrf_token => $csrf_token,
+ dt => 1,
+ user => 'someone',
+ email => 'foo@example.org',
+ password => 'foofoofoo',
+ password2 => 'foofoofoo',
+ }
+);
+$t->status_is(200)->content_like(qr{Verifizierungslink});
+
+my $res = $t->app->pg->db->select( 'users', ['id'], { name => 'someone' } );
+my $uid = $res->hash->{id};
+$res = $t->app->pg->db->select( 'pending_registrations', ['token'],
+ { user_id => $uid } );
+my $token = $res->hash->{token};
+
+# Successful verification
+$t->get_ok("/reg/${uid}/${token}");
+$t->status_is(200)->content_like(qr{freigeschaltet});
+
+# Successful login
+$t->post_ok(
+ '/login' => form => {
+ csrf_token => $csrf_token,
+ user => 'someone',
+ password => 'foofoofoo',
+ }
+);
+$t->status_is(302)->header_is( location => '/' );
+
+$csrf_token
+ = $t->ua->get('/journey/add')->res->dom->at('input[name=csrf_token]')
+ ->attr('value');
+$t->post_ok(
+ '/journey/add' => form => {
+ csrf_token => $csrf_token,
+ action => 'save',
+ train => 'RE 42 11238',
+ dep_station => 'EMST',
+ sched_departure => '16.10.2018 17:36',
+ rt_departure => '16.10.2018 17:35',
+ arr_station => 'EG',
+ sched_arrival => '16.10.2018 18:34',
+ rt_arrival => '16.10.2018 18:32',
+ }
+);
+$t->status_is(302)->header_is( location => '/journey/1' );
+
+$t->get_ok('/history/2018/10')->status_is(200)->content_like(qr{62 km})
+ ->content_like(qr{00:57 Stunden})->content_like(qr{nach Fahrplan: 00:58})
+ ->content_like(qr{Bei Abfahrt: -00:01 Stunden})
+ ->content_like(qr{Bei Ankunft: -00:02 Stunden});
+
+$t->app->pg->db->query('drop schema travelynx_regr_negative_delay cascade');
+done_testing();