File Coverage

blib/lib/PAGI/Test/ConnectionState.pm
Criterion Covered Total %
statement 54 55 98.1
branch 14 18 77.7
condition 1 2 50.0
subroutine 12 13 92.3
pod 0 7 0.0
total 81 95 85.2


line stmt bran cond sub pod time code
1             package PAGI::Test::ConnectionState;
2             $PAGI::Test::ConnectionState::VERSION = '0.002000';
3 15     15   80212 use strict;
  15         23  
  15         439  
4 15     15   47 use warnings;
  15         21  
  15         10712  
5              
6             =head1 NAME
7              
8             PAGI::Test::ConnectionState - the pagi.connection object provided by PAGI::Test
9              
10             =head1 DESCRIPTION
11              
12             PAGI::Test is a test server, so it provides the per-request C
13             object. It implements the surface L/L delegate to
14             (C, C, C, C,
15             C) plus C, mirroring production
16             C: a clean completion ends the request and fires
17             C but is not a disconnect; exactly one of C /
18             C fires.
19              
20             =cut
21              
22             sub new {
23 79     79 0 137247 my ($class) = @_;
24 79         798 return bless {
25             _connected => 1,
26             _response_started => 0,
27             _completed => 0, # explicit terminal-state flag, like production
28             _reason => undef,
29             _disc_cbs => [],
30             _comp_cbs => [],
31             }, $class;
32             }
33              
34 4 100   4 0 506 sub is_connected { return $_[0]->{_connected} ? 1 : 0 }
35 20 100   20 0 324 sub response_started { return $_[0]->{_response_started} ? 1 : 0 }
36 3     3 0 431 sub disconnect_reason { return $_[0]->{_reason} }
37 0     0 0 0 sub disconnect_future { return undef } # not supported by the test double (spec: undef = unsupported)
38              
39             # Late registration fires immediately for the terminal state that occurred —
40             # distinguished by _completed (clean) vs a set _reason (abnormal), like production.
41             # Invoke a callback the way production does: isolate failures so one bad
42             # callback does not prevent the others from running.
43             sub _fire {
44 6     6   9 my ($cb, @args) = @_;
45 6 50       8 eval { $cb->(@args); 1 } or warn "pagi.connection callback error: $@";
  6         13  
  6         22  
46 6         26 return;
47             }
48              
49             sub on_disconnect {
50 5     5 0 18 my ($self, $cb) = @_;
51 5 100       11 if (!$self->{_connected}) { # terminal: never store, fire only if abnormal
52 1 50       4 _fire($cb, $self->{_reason}) unless $self->{_completed};
53 1         2 return;
54             }
55 4         5 push @{$self->{_disc_cbs}}, $cb; # still in flight: register
  4         7  
56 4         8 return;
57             }
58              
59             sub on_complete {
60 7     7 0 31 my ($self, $cb) = @_;
61 7 100       20 if (!$self->{_connected}) { # terminal: never store, fire only if clean
62 2 100       7 _fire($cb) if $self->{_completed};
63 2         2 return;
64             }
65 5         7 push @{$self->{_comp_cbs}}, $cb;
  5         10  
66 5         9 return;
67             }
68              
69             # Server-internal (the test client, acting as server, calls these).
70 78     78   145 sub _mark_response_started { $_[0]->{_response_started} = 1; return }
  78         101  
71              
72             sub _mark_complete {
73 60     60   79 my ($self) = @_;
74 60 50       118 return unless $self->{_connected};
75 60         65 $self->{_connected} = 0;
76 60         76 $self->{_completed} = 1; # clean completion (distinguishes from disconnect)
77 60         54 _fire($_) for @{$self->{_comp_cbs}};
  60         156  
78 60         70 @{$self->{_comp_cbs}} = ();
  60         81  
79 60         52 @{$self->{_disc_cbs}} = ();
  60         67  
80 60         76 return;
81             }
82              
83             sub _mark_disconnected {
84 13     13   22 my ($self, $reason) = @_;
85 13 50       23 return unless $self->{_connected};
86 13         15 $self->{_connected} = 0;
87 13   50     28 $self->{_reason} = $reason // 'unknown'; # coerce like production
88 13         13 _fire($_, $self->{_reason}) for @{$self->{_disc_cbs}};
  13         29  
89 13         16 @{$self->{_disc_cbs}} = ();
  13         21  
90 13         13 @{$self->{_comp_cbs}} = ();
  13         16  
91 13         16 return;
92             }
93              
94             1;