shlist

share and manage lists between multiple people
Log | Files | Refs

commit 772576f108002e75f783f94ab03c416b12298ba2
parent 5437846c47fd4d6520880868ee0d94180ad7ed0e
Author: Kyle Milz <kyle@0x30.net>
Date:   Sat, 27 Feb 2016 12:15:41 -0700

server: add new TAP harness and tests

- Test Anything Protocol (TAP) is a test runner that's pretty nice
- convert all existing tests to this
- also we now start/stop a server per test, which should make running individual
  tests easier

Diffstat:
Mgen_msgs.sh | 3---
Aserver/SL.pm | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver/t/bad_payloads.t | 14++++++++++++++
Aserver/t/device_add.t | 31+++++++++++++++++++++++++++++++
Aserver/t/device_update.t | 13+++++++++++++
Aserver/t/friend_add.t | 27+++++++++++++++++++++++++++
Aserver/t/friend_delete.t | 36++++++++++++++++++++++++++++++++++++
Aserver/t/friend_delete_unit.t | 25+++++++++++++++++++++++++
Aserver/t/get_other_lists_filters_my_lists.t | 29+++++++++++++++++++++++++++++
Aserver/t/header.t | 33+++++++++++++++++++++++++++++++++
Aserver/t/invalid_deviceid.t | 45+++++++++++++++++++++++++++++++++++++++++++++
Aserver/t/large_response.t | 27+++++++++++++++++++++++++++
Aserver/t/leave_list_your_not_in.t | 19+++++++++++++++++++
Aserver/t/list_add.t | 24++++++++++++++++++++++++
Aserver/t/list_join.t | 35+++++++++++++++++++++++++++++++++++
Aserver/t/list_join_unit.t | 18++++++++++++++++++
Aserver/t/list_leave.t | 16++++++++++++++++
Aserver/t/list_leave_unit.t | 17+++++++++++++++++
Aserver/t/list_reference_counting.t | 29+++++++++++++++++++++++++++++
Aserver/t/list_update.t | 43+++++++++++++++++++++++++++++++++++++++++++
Aserver/t/lists_get.t | 32++++++++++++++++++++++++++++++++
Aserver/t/lists_get_other.t | 26++++++++++++++++++++++++++
Aserver/t/multiple_friends_same_other_list.t | 36++++++++++++++++++++++++++++++++++++
Aserver/t/no_ssl_fails.t | 28++++++++++++++++++++++++++++
Aserver/t/response_too_large.t_ | 13+++++++++++++
Aserver/t/two_lists_same_name.t | 18++++++++++++++++++
Aserver/t/update_list_youre_not_in.t | 19+++++++++++++++++++
Aserver/t/utf8.t | 18++++++++++++++++++
Aserver/t/zero_payload.t | 23+++++++++++++++++++++++
Aserver/test.pl | 11+++++++++++
30 files changed, 965 insertions(+), 3 deletions(-)

diff --git a/gen_msgs.sh b/gen_msgs.sh @@ -19,7 +19,6 @@ msg_types=" objc_path="ios/shlist/MsgTypes.h" java_path="android/shlist/app/src/main/java/drsocto/shlist/MsgTypes.java" perl_path="server/msgs.pl" -test_path="server/tests/msgs.pl" generated_at="generated `date`" @@ -81,8 +80,6 @@ EOF print_table $perl_path "our %msg_num = (" "\$msg => \$i," ");" print_table $perl_path "our @msg_str = (" "'\$msg'," ");" print_table $perl_path "our @msg_func = (" "\\&msg_\$msg," ");" - - cp $perl_path $test_path } gen_objc diff --git a/server/SL.pm b/server/SL.pm @@ -0,0 +1,260 @@ +package SL::Server; +use strict; + +use IPC::Open2; + +sub new { + my $class = shift; + + my $self = {}; + bless ($self, $class); + + # perl -MDevel::Cover sl -p $PORT -t & + my $pid = open2(\*CHLD_OUT, undef, "perl -T sl -t -p 4729"); + + $self->{pid} = $pid; + $self->{CHLD_OUT} = \*CHLD_OUT; + return $self; +} + +sub readline { + my $self = shift; + + return readline $self->{CHLD_OUT}; +} + +sub DESTROY { + my $self = shift; + + kill 'TERM', $self->{pid}; + waitpid( $self->{pid}, 0 ); +} + +1; + +package SL::Client; +use strict; +use warnings; + +use Carp; +use IO::Socket::SSL; +use JSON::XS; +use String::Random; +use Test; +use Time::HiRes qw(usleep); +use Try::Tiny; + +require "msgs.pl"; +our (%msg_num, @msg_str); + +sub new { + my $class = shift; + my $dont_register = shift || 0; + + my $self = {}; + bless ($self, $class); + + my $socket = undef; + while (! $socket) { + $socket = IO::Socket::SSL->new( + PeerHost => 'localhost', + PeerPort => 4729, + # this is needed because PeerHost is localhost and our + # SSL certificates are signed with + # absentmindedproductions.ca + SSL_verifycn_name => "absentmindedproductions.ca", + ) or usleep(50 * 1000); + } + die "failed connect or ssl handshake: $!,$SSL_ERROR" unless ($socket); + + $self->{sock} = $socket; + $self->{device_id} = undef; + + if ($dont_register == 0) { + my $string_gen = String::Random->new; + $self->{phnum} = '403' . $string_gen->randpattern('nnnnnnn'); + + my $args = { phone_number => $self->{phnum}, os => 'unix' }; + $self->{device_id} = $self->device_add($args); + + $self->device_update({ pushtoken_hex => "token_$self->{phnum}" }, 'ok'); + } + + return $self; +} + +sub device_add { + my ($self, $args, $status) = @_; + return $self->communicate('device_add', $status, $args); +} + +sub device_update { + my ($self, $args, $status) = @_; + return $self->communicate('device_update', $status, $args); +} + +sub list_add { + my ($self, $args, $status) = @_; + return $self->communicate('list_add', $status, $args); +} + +sub list_update { + my ($self, $args, $status) = @_; + return $self->communicate('list_update', $status, $args); +} + +sub list_join { + my ($self, $args, $status) = @_; + return $self->communicate('list_join', $status, $args); +} + +sub list_leave { + my ($self, $args, $status) = @_; + return $self->communicate('list_leave', $status, $args); +} + +sub friend_add { + my ($self, $args, $status) = @_; + return $self->communicate('friend_add', $status, $args); +} + +sub friend_delete { + my ($self, $args, $status) = @_; + return $self->communicate('friend_delete', $status, $args); +} + +sub lists_get { + my ($self, $status) = @_; + return $self->communicate('lists_get', $status); +} + +sub lists_get_other { + my ($self, $status) = @_; + return $self->communicate('lists_get_other', $status); +} + +sub communicate { + my ($self, $msg_type, $exp_status, $msg_data) = @_; + + # If no expected status was passed in assume 'ok' + $exp_status = 'ok' if (! defined $exp_status); + + my $msg_args->{data} = $msg_data; + + # device_add is the only message type that does not require device_id as + # a mandatory argument + $msg_args->{device_id} = $self->{device_id} if ($msg_type ne 'device_add'); + + $self->send_msg($msg_type, $msg_args); + my $resp = $self->recv_msg($msg_type); + + # Check that the received status was the same as the expected status + my $status = $resp->{status}; + ok($status, $exp_status); + + # Response indicated error, return the reason + return $resp->{reason} if ($status eq 'err'); + + # Everything looks good, return the response data + return $resp->{data}; +} + +sub send_msg { + my ($self, $msg_type, $request) = @_; + + # Request comes in as a hash ref, do this now to figure out length + my $payload = encode_json($request); + + confess "invalid message type $msg_type" unless (grep { $_ eq $msg_type } @msg_str); + + my $version = 0; + my $payload_len = length($payload); + my $header = pack("nnn", $version, $msg_num{$msg_type}, $payload_len); + + my $sent_bytes = 0; + $sent_bytes += $self->send_all($header, length($header)); + $sent_bytes += $self->send_all($payload, $payload_len); + + return $sent_bytes; +} + +sub send_all { + my ($self, $bytes, $bytes_total) = @_; + + my $bytes_written = $self->{sock}->syswrite($bytes); + + confess "write failed: $!" if (!defined $bytes_written); + confess "wrote $bytes_written instead of $bytes_total bytes" if ($bytes_written != $bytes_total); + + return $bytes_total; +} + +sub recv_msg { + my ($self, $exp_msg_type) = @_; + + # Read header + my $header = $self->read_all(6); + my ($version, $msg_type, $payload_size) = unpack("nnn", $header); + + # Check some things + confess "unsupported protocol version $version" if ($version != 0); + confess "unknown message type $msg_type" if ($msg_type >= @msg_str); + confess "0 byte payload" if ($payload_size == 0); + confess "unexpected message type $msg_type" if ($msg_num{$exp_msg_type} != $msg_type); + + # Read again for payload, $payload_size > 0 + my $payload = $self->read_all($payload_size); + + my $response; + try { + $response = decode_json($payload); + } catch { + confess "server sent invalid json"; + }; + + # Don't accept messages without an object root (ie array roots) + if (ref($response) ne "HASH") { + confess "server didn't send back object root element"; + } + + return $response; +} + +sub read_all { + my ($self, $bytes_total) = @_; + + my $data; + my $bytes_read = 0; + while ($bytes_total > 0) { + my $read = $self->{sock}->sysread($data, $bytes_total, $bytes_read); + + confess "read failed: $!" unless (defined $read); + confess "read EOF on socket" if ($read == 0); + + $bytes_total -= $read; + $bytes_read += $read; + } + + return $data; +} + +sub phnum { + my ($self) = @_; + return $self->{phnum}; +} + +sub device_id { + my ($self) = @_; + return $self->{device_id}; +} + +sub set_device_id { + my ($self, $new_id) = @_; + $self->{device_id} = $new_id; +} + +sub msg_str { + return @msg_str; +} + +1; diff --git a/server/t/bad_payloads.t b/server/t/bad_payloads.t @@ -0,0 +1,14 @@ +use strict; +use Test::More tests => 1; + +use_ok( 'SL' ); + +my $server = SL::Server->new(); + +# Send a straight up unparsable json string +my $client = SL::Client->new(1); +$client->send_all(pack('nnnZ*', 0, 0, 2, "{"), 8); + +# Send an empty array back (which is valid json but we don't use this) +$client = SL::Client->new(1); +$client->send_all(pack('nnnZ*', 0, 0, 2, "[]"), 9); diff --git a/server/t/device_add.t b/server/t/device_add.t @@ -0,0 +1,31 @@ +use strict; +use Test; + +BEGIN { plan tests => 12 } + +use SL; + +my $server = SL::Server->new(); +my $A = SL::Client->new(); + +# Constructor automatically calls device_add so no need to do it here +my $devid = $A->device_id(); +my $length = length($devid); +ok($devid, 'm/^[a-zA-Z0-9+\/=]+$/'); +ok($length, 43); + +# Duplicate phone number +my $err = $A->device_add({ phone_number => $A->phnum, os => 'unix' }, 'err'); +ok($err, 'the sent phone number already exists'); + +# Bad phone number +$err = $A->device_add({ phone_number => '403867530&', os => 'unix' }, 'err'); +ok($err, 'the sent phone number is not a number'); + +# Bad operating system +$err = $A->device_add({ phone_number => 12345, os => 'bados' }, 'err'); +ok($err, 'operating system not supported'); + +# Good operating systems +$A->device_add({ phone_number => 678910, os => 'android' }); +$A->device_add({ phone_number => 231455, os => 'ios' }); diff --git a/server/t/device_update.t b/server/t/device_update.t @@ -0,0 +1,13 @@ +use strict; +use Test; + +BEGIN { plan tests => 4 } + +use SL; + +my $server = SL::Server->new(); +my $A = SL::Client->new(); + +$A->device_update({ pushtoken_hex => "AD34A9EF72DC714CED" }); + +ok(1) diff --git a/server/t/friend_add.t b/server/t/friend_add.t @@ -0,0 +1,27 @@ +use strict; +use Test; + +BEGIN { plan tests => 10 } + +use SL; + +my $server = SL::Server->new(); +my $A = SL::Client->new(); + +# Normal message +$A->friend_add('54321'); + +# Re-add same friend +$A->friend_add('54321'); + +# Non numeric phone number +my $err = $A->friend_add('123asdf', 'err'); +ok($err, 'friends phone number is not a valid phone number'); + +# Empty phone number +$err = $A->friend_add('', 'err'); +ok($err, 'friends phone number is not a valid phone number'); + +# Friending yourself +$err = $A->friend_add($A->phnum(), 'err'); +ok($err, 'device cannot add itself as a friend'); diff --git a/server/t/friend_delete.t b/server/t/friend_delete.t @@ -0,0 +1,36 @@ +use strict; +use Test; + +BEGIN { plan tests => 19 } + +use SL; + +my $server = SL::Server->new(); +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# A and B are mutual friends +$A->friend_add($B->phnum()); +$B->friend_add($A->phnum()); + +# A creates 2 lists +my $As_first_list = $A->list_add({ name => "this is a's first list", date => 0 }); +$A->list_add({ name => "this is a's second list", date => 0 }); +# B creates 1 list +$B->list_add({ name => "this is b's first list", date => 0}); + +# B joins A's first list +$B->list_join($As_first_list->{num}); + +# A deletes B's friendship +$A->friend_delete($B->phnum()); + +# Check that: +# - A and B are both in A's first list +# - B can't see A's other list +# - A can't see B's other list +ok(scalar @{ $A->lists_get_other() }, 0); +ok(scalar @{ $B->lists_get_other() }, 0); + +ok(scalar @{ $A->lists_get() }, 2); +ok(scalar @{ $B->lists_get() }, 2); diff --git a/server/t/friend_delete_unit.t b/server/t/friend_delete_unit.t @@ -0,0 +1,25 @@ +use strict; +use Test; + +BEGIN { plan tests => 10 } + +use SL; + +my $server = SL::Server->new(); +my $A = SL::Client->new(); + +# Someone who is not your friend +my $err = $A->friend_delete('12345', 'err'); +ok($err, 'friend sent for deletion was not a friend'); + +# Non numeric friends phone number +$err = $A->friend_delete('asdf123', 'err'); +ok($err, 'friends phone number is not a valid phone number'); + +# Empty phone number +$err = $A->friend_delete('', 'err'); +ok($err, 'friends phone number is not a valid phone number'); + +# Add/delete cycle works +$A->friend_add('12345'); +$A->friend_delete('12345'); diff --git a/server/t/get_other_lists_filters_my_lists.t b/server/t/get_other_lists_filters_my_lists.t @@ -0,0 +1,29 @@ +use strict; +use Test; + +BEGIN { plan tests => 11 } + +use SL; + +# Check that when your mutual friends are in your own lists that you don't get +# your own lists back when doing a lists_get_other request +my $server = SL::Server->new(); + +# Create A and B +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# B adds a new list +$B->list_add({ name => 'bs new list', date => 0 }); + +# A and B become mutual friends +$A->friend_add($B->phnum()); +$B->friend_add($A->phnum()); + +# A adds a new list, B joins A's new list +my $list = $A->list_add({ name => 'as new list', date => 0 }); +$B->list_join($list->{num}); + +# A should only see B's list that it never joined +my $other = $A->lists_get_other(); +ok(scalar(@$other), 1); diff --git a/server/t/header.t b/server/t/header.t @@ -0,0 +1,33 @@ +use strict; +use Test; + +# XXX: This test isn't very good +# - needs to check that server disconnects on these messages +BEGIN { plan tests => 1 } + +use SL; + +# Need a new connection every time because server disconnects on header errors. +my $server = SL::Server->new(); + +# Invalid message number +my $client = SL::Client->new(1); +$client->send_all(pack('nnn', 0, 47837, 0), 6); + +# Bad protocol version +$client = SL::Client->new(1); +$client->send_all(pack('nnn', 101, 0, 0), 6); + +# Payload length that's too long +$client = SL::Client->new(1); +$client->send_all(pack('nnn', 0, 0, 25143), 6); + +# Advertised payload length longer than actual data length +$client = SL::Client->new(1); +$client->send_all(pack('nnnZ*', 0, 0, 5, 'ab'), 9); + +# Truncated header +$client = SL::Client->new(1); +$client->send_all(pack('nn', 101, 69), 4); + +ok(1); diff --git a/server/t/invalid_deviceid.t b/server/t/invalid_deviceid.t @@ -0,0 +1,45 @@ +use strict; +use Test; + +BEGIN { plan tests => 42 } + +use SL; + +# Test that sending invalid device id's results in errors +my $server = SL::Server->new(); + +# Don't register +my $A = SL::Client->new(1); + +my @device_ids = ('' , 'somebull$hit', 'legit'); +my @good_msgs = ('the client sent a device id that was not base64', + 'the client sent a device id that was not base64', + 'the client sent an unknown device id' +); + +for (0..2) { + $A->set_device_id($device_ids[$_]); + + # for messages that send 2 arguments, send an empty 2nd argument + my $err = $A->friend_add('', 'err'); + ok( $err, $good_msgs[$_] ); + + $err = $A->friend_delete('', 'err'); + ok( $err, $good_msgs[$_] ); + + $err = $A->list_add('', 'err'); + ok( $err, $good_msgs[$_] ); + + $err = $A->list_join('', 'err'); + ok( $good_msgs[$_], $err ); + + $err = $A->list_leave('', 'err'); + ok( $good_msgs[$_], $err ); + + # messages that send 1 argument + $err = $A->lists_get('err'); + ok( $good_msgs[$_], $err ); + + $err = $A->lists_get_other('err'); + ok( $good_msgs[$_], $err ); +} diff --git a/server/t/large_response.t b/server/t/large_response.t @@ -0,0 +1,27 @@ +use strict; +use Test; + +BEGIN { plan tests => 14 } + +use SL; + +# XXX: Test is broken +# - for some reason sending more than 5 list_add's in a row screws something up +# - lists_get() also seems to choke sometimes +# - causes unknown + +# Test that large responses > 16384 bytes work as the underlying ssl layer can +# only handle that much data at a time +my $s = SL::Server->new(); + +my $A = SL::Client->new(); +$A->list_add({ name => "$_" x 1000, date => 0}) for (1..5); + +# The response to this lists_get request clocks in at ~24 KB +my $count = 0; +for my $list (@{ $A->lists_get() }) { + $count += 1; + ok("$count" x 1000, $list->{name}); +} + +ok($count, 5); diff --git a/server/t/leave_list_your_not_in.t b/server/t/leave_list_your_not_in.t @@ -0,0 +1,19 @@ +use strict; +use Test; + +BEGIN { plan tests => 7 } + +use SL; + +# Send a leave_list message that contains a valid list id but the requesting +# device is not currently a member of. +my $server = SL::Server->new(); + +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +my $list = $A->list_add({ name => 'only a can see this list', date => 0 }); + +# Who knows how B got this list id, but he did +my $err = $B->list_leave($list->{num}, 'err'); +ok($err, 'the client was not a member of the list'); diff --git a/server/t/list_add.t b/server/t/list_add.t @@ -0,0 +1,24 @@ +use strict; +use Test; + +BEGIN { plan tests => 10 } + +use SL; +use Scalar::Util qw(looks_like_number); + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# make sure normal list_add works +my $name = 'this is a new list'; +my $list = $A->list_add({ name => $name, date => 0 }); + +ok(looks_like_number($list->{num})); +ok($list->{name}, $name); +ok($list->{num_members}, 1); +ok($list->{members}->[0], $A->phnum()); + +# verify a new_list request with an empty list name succeeds +$A->list_add({ name => '', date => 0 }); + +ok(scalar( @{ $A->lists_get() } ), 2); diff --git a/server/t/list_join.t b/server/t/list_join.t @@ -0,0 +1,35 @@ +use strict; +use Test; + +BEGIN { plan tests => 18 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# make A and B mutual friends +$A->friend_add($B->phnum()); +$B->friend_add($A->phnum()); + +# A creates a new list +my $list_name = "this is a new list"; +my $As_list = $A->list_add({ name => $list_name, date => 0 }); + +# B joins A's list +my $list = $B->list_join($As_list->{num}); +ok( $list->{num}, $As_list->{num} ); +ok( $list->{name}, 'this is a new list' ); +ok( $list->{date}, 0 ); +ok( $list->{items_complete}, 0 ); +ok( $list->{items_total}, 0 ); +ok( $list->{num_members}, 2 ); + +# B requests its lists to make sure its committed to the list +($list) = @{ $B->lists_get() }; + +# Verify what we get from server +for ('num', 'name', 'date') { + ok( $As_list->{$_}, $list->{$_} ); +} diff --git a/server/t/list_join_unit.t b/server/t/list_join_unit.t @@ -0,0 +1,18 @@ +use strict; +use Test; + +BEGIN { plan tests => 7 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# Try joining a list that doesn't exist +my $err = $A->list_join('12345678', 'err'); +ok($err, 'the client sent an unknown list number'); + +# Test joining a list your already in +my $list = $A->list_add({ name => 'my new test list', date => 0 }); +$err = $A->list_join($list->{num}, 'err'); +ok($err, 'the device is already part of this list'); diff --git a/server/t/list_leave.t b/server/t/list_leave.t @@ -0,0 +1,16 @@ +use strict; +use Test; + +BEGIN { plan tests => 8 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +my $list = $A->list_add({ name => 'this list was made for leaving', date => 0 }); +$A->list_leave($list->{num}); + +# verify we don't get this list back when requesting all lists +ok( scalar( @{ $A->lists_get() } ), 0 ); +ok( scalar(@{ $A->lists_get_other() }), 0 ); diff --git a/server/t/list_leave_unit.t b/server/t/list_leave_unit.t @@ -0,0 +1,17 @@ +use strict; +use Test; + +BEGIN { plan tests => 6 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# Try leaving a list your not in +my $err = $A->list_leave('1234567', 'err'); +ok($err, 'the client sent an unknown list number'); + +# Try leaving the empty list +$err = $A->list_leave('', 'err'); +ok($err, 'the client sent a list number that was not a number'); diff --git a/server/t/list_reference_counting.t b/server/t/list_reference_counting.t @@ -0,0 +1,29 @@ +use strict; +use Test; + +BEGIN { plan tests => 10 } + +use SL; + +# Test list reference counting to make sure they stay alive when needed +my $s = SL::Server->new(); +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# A creates a new list +my $list = $A->list_add({ name => 'this list will belong to B soon enough', date => 0 }); + +# XXX: missing steps +# - A and B become mutual friends +# - B requests his other lists +# - B joins A's list + +# B joins A's list, A leaves its own list +$B->list_join($list->{num}); +$A->list_leave($list->{num}); + +# B verifies its still in the list +ok( scalar(@{ $B->lists_get() }), 1 ); + +# B also leaves the list +$B->list_leave($list->{num}); diff --git a/server/t/list_update.t b/server/t/list_update.t @@ -0,0 +1,43 @@ +use strict; +use Test; + +BEGIN { plan tests => 19 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# Test sending a request with no 'num' key +my $err = $A->list_update({ name => 'some name' }, 'err'); +ok( $err, 'the client did not send a list number' ); + +# Try and update a list that doesn't exist +$err = $A->list_update({ num => 123456, name => 'some name' }, 'err'); +ok( $err, 'the client sent an unknown list number' ); + +# All checks after this require a valid list, create one now +my $list = $A->list_add({ name => 'this is a new list', date => 0 }); + +# Update only the list name first +$A->list_update({ num => $list->{num}, name => 'this is an updated name' }); + +# Verify the name change persisted +my @lists = @{ $A->lists_get() }; +ok( $lists[0]->{name}, 'this is an updated name' ) ; +ok( $lists[0]->{date}, 0 ); + +# Update only the date +$A->list_update({ num => $list->{num}, date => 12345 }); + +# Verify the date change persisted +@lists = @{ $A->lists_get() }; +ok( $lists[0]->{name}, 'this is an updated name' ); +ok( $lists[0]->{date}, 12345 ); + +# Now update both the name and date +$A->list_update({ num => $list->{num}, date => 54321, name => 'updated again' }); + +@lists = @{ $A->lists_get() }; +ok( $lists[0]->{name}, 'updated again' ); +ok( $lists[0]->{date}, 54321 ); diff --git a/server/t/lists_get.t b/server/t/lists_get.t @@ -0,0 +1,32 @@ +use strict; +use Test; + +BEGIN { plan tests => 28 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# Create 3 new lists +my @stored_lists; +for ('new list 1', 'new list 2', 'new list 3') { + push @stored_lists, $A->list_add({ name => $_, date => 0 }); +} + +my $i = 0; +# Verify the information from lists_get matches what we know is true +for my $list (@{ $A->lists_get() }) { + my $num = $list->{num}; + my $stored_list = $stored_lists[$i]; + + ok( $list->{num}, $stored_list->{num} ); + ok( $list->{num_members}, $stored_list->{num_members} ); + ok( $list->{members}->[0], $A->phnum ); + ok( $list->{name}, $stored_list->{name} ); + ok( $list->{date}, $stored_list->{date} ); + ok( $list->{items_total}, 0 ); + ok( $list->{items_complete}, 0 ); + $i++; +} +ok( $i, 3 ); diff --git a/server/t/lists_get_other.t b/server/t/lists_get_other.t @@ -0,0 +1,26 @@ +use strict; +use Test; + +BEGIN { plan tests => 13 } + +use SL; + +# Create A and B +my $s = SL::Server->new(); +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# A and B become mutual friends +$A->friend_add($B->phnum()); +$B->friend_add($A->phnum()); + +# A adds a new list +my $as_list = $A->list_add({ name => 'this is a new list that B can see', date => 0 }); + +# Check that B can see As list +my @other_lists = @{ $B->lists_get_other() }; +ok( $other_lists[0]->{name}, $as_list->{'name'} ); +ok( $other_lists[0]->{num}, $as_list->{'num'} ); +ok( $other_lists[0]->{num_members}, 1 ); +ok( $other_lists[0]->{members}->[0], $A->phnum() ); +ok( scalar(@other_lists), 1 ); diff --git a/server/t/multiple_friends_same_other_list.t b/server/t/multiple_friends_same_other_list.t @@ -0,0 +1,36 @@ +use strict; +use Test; + +BEGIN { plan tests => 19 } + +use SL; + +# this test makes sure that when 2 friends of yours are in the same list that +# your not in, that the list doesn't show up twice in your list_get_other +# request. +my $s = SL::Server->new(); + +my $A = SL::Client->new(); +my $B = SL::Client->new(); +my $C = SL::Client->new(); + +# A and B are mutual friends +$A->friend_add($B->phnum()); +$B->friend_add($A->phnum()); + +# A and C are also mutual friends +$A->friend_add($C->phnum()); +$C->friend_add($A->phnum()); + +# B and C need to be in the same list +my $list = $B->list_add({ name => 'this is Bs new list', date => 0 }); +$C->list_join($list->{num}); + +# A makes sure he got a single list +my @other = @{ $A->lists_get_other() }; +ok( $other[0]->{num_members}, 2 ); +ok( $other[0]->{num}, $list->{num} ); +ok( scalar(@other), 1 ); +ok( ! grep {$_ eq $A->phnum()} @{$other[0]->{members}} ); +ok( grep {$_ eq $B->phnum()} @{$other[0]->{members}} ); +ok( grep {$_ eq $C->phnum()} @{$other[0]->{members}} ); diff --git a/server/t/no_ssl_fails.t b/server/t/no_ssl_fails.t @@ -0,0 +1,28 @@ +use strict; +use Test::More tests => 2; + +use_ok( 'SL' ); +use IO::Socket::INET; +use Time::HiRes qw(usleep); + +# Check that a non-ssl connection isn't accepted + +my $server = SL::Server->new(); + +my $socket = undef; +while (!defined $socket) { + $socket = new IO::Socket::INET( + PeerHost => 'localhost', + PeerPort => 4729, + ); + usleep(100 * 1000); +} + +my $good_errno = 'Illegal seek'; +$socket->syswrite("a\0\0\0" x 787); +#my $ret = $socket->sysread(my $buf, 6); +ok(1); +#fail "expected errno '$good_errno' but got '$!'" if ($! ne $good_errno); +#fail "sysread returned '$ret', expected '0'" if ($ret != 0); + +#print STDERR $server->readline(); diff --git a/server/t/response_too_large.t_ b/server/t/response_too_large.t_ @@ -0,0 +1,13 @@ +#!/usr/bin/perl -I../ +use strict; +use warnings; +use client; +use test; + +# Test that a message greater than 65KB doesn't get sent + +my $A = client->new(); +$A->list_add({ name => $_, date => 0 }) for (1..600); + +my $err = $A->lists_get('err'); +fail_msg_ne 'response too large', $err; diff --git a/server/t/two_lists_same_name.t b/server/t/two_lists_same_name.t @@ -0,0 +1,18 @@ +use strict; +use Test; + +BEGIN { plan tests => 6 } + +use SL; + +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# check that adding the same list twice works +my $name = 'some list thats going to be added twice'; +$A->list_add({ name => $name, date => 0 }); +$A->list_add({ name => $name, date => 0 }); + +my $num_lists = scalar(@{ $A->lists_get() }); +ok( $num_lists, 2 ); +# XXX: add validation this gives back 2 independent lists diff --git a/server/t/update_list_youre_not_in.t b/server/t/update_list_youre_not_in.t @@ -0,0 +1,19 @@ +use strict; +use Test; + +BEGIN { plan tests => 7 } + +use SL; +my $s = SL::Server->new(); + +# Create A and B +my $A = SL::Client->new(); +my $B = SL::Client->new(); + +# A adds a new list +my $list = $A->list_add({ name => 'this is a new list for a', date => 0 }); + +# B tries to update A's list without joining it first +my $request = { num => $list->{num}, name => 'some new name', date => 1 }; +my $err = $B->list_update($request, 'err'); +ok($err, 'client tried to update a list it was not in'); diff --git a/server/t/utf8.t b/server/t/utf8.t @@ -0,0 +1,18 @@ +use strict; +use Test; + +BEGIN { plan tests => 5 } + +use SL; +my $s = SL::Server->new(); +my $A = SL::Client->new(); + +# Create a new list with a name composed of 3 valid Unicode characters +# - a left double quotation mark and +# - ae sorta character thing but where they touch +# - face with medical mask +$A->list_add({ name => "\xE2\x80\x9C \xC3\xA6 \xF0\x9F\x98\xB8", date => 0 }); +my ($list) = @{ $A->lists_get() }; + +# Check the list name we get back hasn't been mangled in the round trip +ok( "\xE2\x80\x9C \xC3\xA6 \xF0\x9F\x98\xB8", $list->{name} ); diff --git a/server/t/zero_payload.t b/server/t/zero_payload.t @@ -0,0 +1,23 @@ +use strict; +use Test; + +BEGIN { plan tests => 12 } + +use SL; + +# Create new device, turn off automatic device_add +my $s = SL::Server->new(); +my $A = SL::Client->new(1); + +# Send size zero payload to all message types +for ( $A->msg_str() ) { + my $msg_good = 'a missing message argument was required'; + if ($_ eq 'device_add') { + $msg_good = 'the sent phone number is not a number'; + } + + # Send empty dictionary + $A->send_msg($_, {} ); + my $response = $A->recv_msg($_); + ok( $response->{reason}, $msg_good ); +} diff --git a/server/test.pl b/server/test.pl @@ -0,0 +1,11 @@ +use strict; +use warnings; + +use TAP::Harness; +use Getopt::Std; + +my %args; +getopt("c", \%args); + +my $harness = TAP::Harness->new({ color => 1 }); +$harness->runtests(glob("t/*.t"));