File Coverage

lib/Net/FTP/Mock.pm
Criterion Covered Total %
statement 8 10 80.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 14 85.7


line stmt bran cond sub pod time code
1 1     1   804 use strict;
  1         3  
  1         43  
2 1     1   7 use warnings;
  1         2  
  1         68  
3              
4             package Net::FTP::Mock;
5             BEGIN {
6 1     1   27 $Net::FTP::Mock::VERSION = '0.103300';
7             }
8              
9             # ABSTRACT: test code using Net::FTP without having an FTP server
10              
11              
12 1     1   438 use Moose;
  0            
  0            
13              
14             use File::Copy 'copy';
15              
16             {
17             my $servers;
18             sub servers {
19             return $servers if @_ < 2;
20             $servers = $_[1];
21             }
22             }
23              
24             has host => ( isa => 'Str', is => 'ro', required => 1, initializer => '_check_host' );
25             has user => ( is => 'rw', isa => 'Str' );
26             has pass => ( is => 'rw', isa => 'Str' );
27             has message => ( is => 'rw', isa => 'Str' );
28             has _account => ( is => 'rw', isa => 'HashRef', lazy => 1, builder => '_get_account' );
29             has root => ( is => 'rw', isa => 'Str', lazy => 1, default => sub { $_[0]->_account->{root} } );
30             has code => ( is => 'rw', isa => 'Int' );
31              
32              
33             sub Net::FTP::new {
34             my ( undef, @args ) = @_;
35              
36             my $ftp = Net::FTP::Mock->new( host => @args );
37             return $ftp if !$ftp->message;
38              
39             $@ = $ftp->message;
40             return;
41             }
42              
43              
44             sub import {
45             my ( $self , %args ) = @_;
46              
47             $INC{'Net/FTP.pm'} = $INC{'Net/FTP/Mock.pm'};
48             $self->servers( \%args );
49              
50             return;
51             }
52              
53              
54             sub isa {
55             return 1 if $_[1] eq 'Net::FTP';
56             return $_[0]->UNIVERSAL::isa($_[1]);
57             }
58              
59             sub _check_host {
60             my ( $self, $host, $set_function ) = @_;
61              
62             $self->message( "Net::FTP: Bad hostname '$host'" ) if !$self->servers->{$host};
63              
64             return $set_function->( $host );
65             }
66              
67             sub _server {
68             my ( $self ) = @_;
69             return if !$self->servers;
70             return $self->servers->{$self->host};
71             }
72              
73             sub _get_account {
74             my ( $self ) = @_;
75             return $self->_bad_host if !$self->_server;
76             my $acc = $self->_server->{$self->user}{$self->pass} || $self->_bad_account;
77             return $acc;
78             }
79              
80             sub _bad_host {
81             my ( $self ) = @_;
82             $self->message( "Cannot connect to ".$self->host.": Net::FTP: Bad hostname '".$self->host."'" );
83             return {};
84             }
85              
86             sub _bad_account {
87             my ( $self ) = @_;
88             $self->message( "Login or password incorrect!\n" );
89             return {};
90             }
91              
92             sub _file_missing {
93             my ( $self, $file, $target ) = @_;
94             $self->code( 550 );
95             return;
96             }
97              
98             sub _full_filename {
99             my ( $self, $file ) = @_;
100              
101             $file = $self->root.$file;
102              
103             return $file;
104             }
105              
106              
107              
108             sub login {
109             my ( $self, $user, $pass ) = @_;
110              
111             $self->user( $user );
112             $self->pass( $pass );
113              
114             return 1 if $self->_account->{active};
115             return;
116             }
117              
118             sub binary {}
119              
120             sub get {
121             my ( $self, $file, $target ) = @_;
122              
123             $file = $self->_full_filename( $file );
124             return $self->_file_missing if !-e $file;
125              
126             copy $file, $target or return;
127              
128             $self->code( 226 );
129             return $file;
130             }
131              
132             sub quit {}
133              
134             sub mdtm {
135             my ( $self, $file ) = @_;
136              
137             $file = $self->_full_filename( $file );
138             return if !-e $file;
139              
140             return (stat $file)[9];
141             }
142              
143             sub size {
144             my ( $self, $file ) = @_;
145              
146             $file = $self->_full_filename( $file );
147             return if !-e $file;
148              
149             return -s $file;
150             }
151              
152              
153             1;
154              
155             __END__
156             =pod
157              
158             =head1 NAME
159              
160             Net::FTP::Mock - test code using Net::FTP without having an FTP server
161              
162             =head1 VERSION
163              
164             version 0.103300
165              
166             =head1 SYNOPSIS
167              
168             use Net::FTP::Mock (
169             localhost => {
170             username => { password => {
171             active => 1,
172             root => "t/remote_ftp/"
173             }},
174             },
175             ftp.work.com => {
176             harry => { god => {
177             active => 1,
178             root => "t/other_remote_ftp/"
179             }},
180             },
181             );
182              
183             use Net::FTP; # will do nothing, since Mock already blocked it
184              
185             # $ftp here actually is a Net::FTP::Mock object,
186             # but when inspected with isa() it happily claims to be Net::FTP
187             my $ftp = Net::FTP->new("ftp.work.com", Debug => 0) or die "Cannot connect to some.host.name: $@";
188              
189             # all of these do what you'd think they do, only instead of acting
190             # on a real ftp server, they act no the data provided via import
191             # and the local harddisk
192             $ftp->login( "harry",'god' ) or die "Cannot login ", $ftp->message;
193             $ftp->get("that.file") or die "get failed ", $ftp->message;
194             $ftp->quit;
195              
196             =head1 DESCRIPTION
197              
198             Net::FTP::Mock is designed to make code using Net::FTP testable without having to set up actual FTP servers. When
199             calling its import(), usually by way of use, you can pass it a hash detailing virtual servers, their accounts, as well
200             as directories that those accounts map to on the local machine.
201              
202             You can then interact with the Net::FTP::Mock object exactly as you would with a real one.
203              
204             NOTE: This is a work in progress and much of Net::FTP's functionality is not yet emulated. If it behaves odd, look at
205             the code or yell at me. Contributions on github are very welcome.
206              
207             =head1 NAME
208              
209             test code using Net::FTP without having an FTP server
210              
211             =head1 METHODS
212              
213             =head2 Net::FTP::new
214              
215             Factory method that is implanted into Net::FTP's namespace and returns a Net::FTP::Mock object. Should behave exactly
216             like Net::FTP's new() behaves.
217              
218             =head2 servers
219              
220             Class attribute that stores the servers hashref passed when the module is used.
221              
222             =head2 Net::FTP::Mock->import( %server_details );
223              
224             Blocks Net::FTP's namespace in %INC and prepares the servers to be emulated.
225              
226             =head2 isa
227              
228             Overrides isa to ensure that Moose's type checks recognize this as a Net::FTP object.
229              
230             =head1 SUPPORTED NET::FTP METHODS
231              
232             =head2 code
233              
234             =head2 message
235              
236             =head2 binary
237              
238             =head2 get
239              
240             =head2 quit
241              
242             =head2 mdtm
243              
244             =head2 size
245              
246             =head2 login
247              
248             =head1 ACKNOWLEDGEMENTS
249              
250             Thanks to L<Tr@ffics|http://traffics.de> and especially Jens Muskewitz for granting permission to release this module.
251              
252             Many thanks to mst and rjbs who fielded my newbie questions in #moose and helped me figure out how to actually create
253             the Mock object from Net::FTP's mainspace, as well as how to get the Mock object to masquerade as Net::FTP.
254              
255             =head1 CONTRIBUTIONS
256              
257             Since I'm not sure how much time i can devote to this, I'm happy about any help. The code is up on github and i'll
258             accept any helping pull requests.
259              
260             =head1 AUTHOR
261              
262             Christian Walde <mithaldu@yahoo.de>
263              
264             =head1 COPYRIGHT AND LICENSE
265              
266             This software is Copyright (c) 2010 by Christian Walde.
267              
268             This is free software, licensed under:
269              
270             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE, Version 2, December 2004
271              
272             =cut
273