File Coverage

lib/Concierge/Sessions/Session.pm
Criterion Covered Total %
statement 59 62 95.1
branch 14 18 77.7
condition 10 14 71.4
subroutine 17 17 100.0
pod 14 14 100.0
total 114 125 91.2


line stmt bran cond sub pod time code
1             package Concierge::Sessions::Session v0.11.0;
2 4     4   45 use v5.36;
  4         25  
3              
4             # ABSTRACT: Individual session objects created by Concierge::Sessions
5              
6 4     4   26 use Time::HiRes qw(time);
  4         7  
  4         56  
7 4     4   337 use Carp qw(croak);
  4         7  
  4         3775  
8              
9             sub new {
10 62     62 1 243 my ($class, %args) = @_;
11              
12 62         133 my $backend = $args{storage};
13              
14 62         288 my $create_result = $backend->create_session( %args );
15 62 50       454 unless ($create_result->{success}) {
16 0         0 return { success => 0, message => "new_session failure: " . $create_result->{message} };
17             }
18              
19             # Retrieve the full created session info with timestamps, etc.
20 62         503 my $get_result = $backend->get_session_info( $create_result->{session_id} );
21 62 50       379 unless ($get_result->{success}) {
22 0         0 return { success => 0, message => "new_session info failure: " . $get_result->{message} };
23             }
24              
25             # Create Session object
26             my $self = bless {
27             $get_result->{info}->%*,
28 62         643 storage => $backend,
29             }, __PACKAGE__;
30              
31 62         758 return { success => 1, session => $self };
32             }
33              
34             # Data access methods - work with entire data field
35             sub get_data {
36 25     25 1 4161 my ($self) = @_;
37              
38             # Return entire data field
39 25         88 my $value = $self->{data};
40              
41 25         109 return { success => 1, value => $value };
42             }
43              
44             sub set_data {
45 23     23 1 26907 my ($self, $value) = @_;
46              
47             # $value replaces entire data field
48 23         58 $self->{data} = $value;
49              
50             # Only changed in memory, not in storage
51 23         54 $self->{status}{dirty} = 1;
52              
53 23         70 return { success => 1 };
54             }
55              
56             # Persistence method for app data, timestamped
57             sub save {
58 18     18 1 89 my ($self) = @_;
59              
60             # Check if dirty
61 18   100     77 my $dirty = $self->{status}{dirty} || 0;
62              
63 18 100       47 unless ($dirty) {
64 4         17 return { success => 1 }; # Fine if not dirty
65             }
66              
67             # Calculate new expiration time (sliding window extension)
68 14         29 my $timeout = $self->{session_timeout};
69 14         24 my $new_expires_at;
70 14 100 66     84 if (defined $timeout && $timeout eq 'indefinite') {
71 1         4 $new_expires_at = 'indefinite';
72             } else {
73 13         76 $new_expires_at = time() + $timeout;
74             }
75              
76             # Update internal expires_at
77 14         63 $self->{expires_at} = $new_expires_at;
78              
79             # Save session data changes and new expiration time
80             my $result = $self->{storage}->update_session(
81             $self->{session_id},
82             {
83             data => $self->{data},
84 14         111 expires_at => $new_expires_at,
85             }
86             );
87              
88 14 50       153 unless ($result->{success}) {
89 0         0 return { success => 0, message => "save: " . $result->{message} };
90             }
91              
92             # Clear dirty flag
93 14         54 $self->{status}{dirty} = 0;
94              
95 14         99 return { success => 1 };
96             }
97              
98             # Read-only Status booleans
99             sub is_valid {
100 5     5 1 32 my ($self) = @_;
101 5 100 66     16 return ($self->is_active() && !$self->is_expired()) ? 1 : 0;
102             }
103              
104              
105             sub is_active {
106 9     9 1 42 my ($self) = @_;
107 9   50     83 my $state = $self->{status}{state} || '';
108 9 50       108 return $state eq 'active' ? 1 : 0;
109             }
110              
111             sub is_expired {
112 13     13 1 1234 my ($self) = @_;
113             # Indefinite sessions never expire
114 13 100       88 return 0 if $self->{expires_at} eq 'indefinite';
115 9 100       119 return (time() > $self->{expires_at}) ? 1 : 0;
116             }
117              
118             sub is_dirty {
119 16     16 1 1391 my ($self) = @_;
120 16   100     201 return $self->{status}{dirty} || 0;
121             }
122              
123             # Read-only system info
124             sub session_id {
125 25     25 1 5465 my ($self) = @_;
126 25         149 return $self->{session_id};
127             }
128              
129             sub storage_backend {
130 1     1 1 10 my ($self) = @_;
131 1         6 return ref($self->{storage});
132             }
133              
134             sub created_at {
135 5     5 1 2835 my ($self) = @_;
136 5         26 $self->{created_at};
137             }
138              
139             sub expires_at {
140 10     10 1 840 my ($self) = @_;
141 10         60 $self->{expires_at};
142             }
143              
144             sub last_updated {
145 3     3 1 26 my ($self) = @_;
146 3         11 return $self->{last_updated};
147             }
148              
149             sub status {
150 4     4 1 21 my ($self) = @_;
151 4   50     54 return $self->{status} || { state => 'active', dirty => 0 };
152             }
153              
154             1;
155              
156             __END__