#!/usr/bin/perl
#
# End-to-end tests for the wallet client.
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2008, 2010, 2014
#     The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.

use strict;
use warnings;

# Point to our server configuration.  This must be done before Wallet::Config
# is loaded, and it's pulled in as a prerequisite for Wallet::Admin.
BEGIN { $ENV{WALLET_CONFIG} = "$ENV{SOURCE}/data/wallet.conf" }

use Test::More tests => 59;

use lib "$ENV{SOURCE}/../perl/lib";
use Wallet::Admin;

use lib "$ENV{SOURCE}/../perl/t/lib";
use Util;

# Make a call to the wallet client.  Takes the principal used by the server
# and the command.  Returns the standard output, the standard error, and the
# exit status as a list.
sub wallet {
    my ($principal, @command) = @_;
    my $pid = fork;
    if (not defined $pid) {
        die "cannot fork: $!\n";
    } elsif ($pid == 0) {
        open (STDOUT, '>', 'wallet.out')
            or die "cannot create wallet.out: $!\n";
        open (STDERR, '>', 'wallet.err')
            or die "cannot create wallet.err: $!\n";
        exec ("$ENV{BUILD}/../client/wallet", '-k', $principal, '-p',
              '14373', '-s', 'localhost', @command)
            or die "cannot run $ENV{BUILD}/client/wallet: $!\n";
    } else {
        waitpid ($pid, 0);
    }
    my $status = $?;
    local $/;
    open (OUT, '<', 'wallet.out') or die "cannot open wallet.out: $!\n";
    my $output = <OUT>;
    close OUT;
    open (ERR, '<', 'wallet.err') or die "cannot open wallet.err: $!\n";
    my $error = <ERR>;
    close ERR;
    unlink ('wallet.out', 'wallet.err');
    return ($output, $error, $status);
}

# cd to the correct directory.
chdir "$ENV{SOURCE}" or die "Cannot chdir to $ENV{SOURCE}: $!\n";

SKIP: {
    skip 'no keytab configuration', 59
        unless -f "$ENV{BUILD}/config/keytab";
    my $remctld = '@REMCTLD@';
    skip 'remctld not found', 59 unless $remctld;

    # Spawn remctld and get local tickets.  Don't destroy the user's Kerberos
    # ticket cache.
    unlink ('krb5cc_test', 'test-pid');
    my $principal = contents ("$ENV{BUILD}/config/principal");
    remctld_spawn ($remctld, $principal,
                   "$ENV{BUILD}/config/keytab",
                   "$ENV{SOURCE}/data/full.conf");
    $ENV{KRB5CCNAME} = 'krb5cc_test';
    getcreds ("$ENV{BUILD}/config/keytab", $principal);

    # Use Wallet::Admin to set up the database.
    db_setup;
    my $admin = eval { Wallet::Admin->new };
    is ($@, '', 'Database connection succeeded');
    is ($admin->reinitialize ($principal), 1,
        'Database initialization succeeded');
    my $dbh = $admin->dbh;

    # Create the file bucket.
    if (-d 'test-files') {
        system ('rm', '-r', 'test-files');
    }
    mkdir ('test-files', 0777) or die "cannot create file-bucket: $!\n";

    # Now, start testing.  First, create an object, create an ACL, assign an
    # owner, store the contents, and then get the contents and make sure all
    # of that works as expected.
    my ($out, $err, $status) = wallet ($principal, 'create', 'file', 'test');
    is ($status, 0, 'Object creation succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'acl', 'create', 'user/test');
    is ($status, 0, 'ACL creation succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'acl', 'add', 'user/test',
                                    'krb5', $principal);
    is ($status, 0, 'ACL population succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'acl', 'show', 'user/test');
    is ($status, 0, 'ACL show succeeds');
    is ($out, "Members of ACL user/test (id: 2) are:\n  krb5 $principal\n",
        ' with the right output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'owner', 'file', 'test',
                                    'user/test');
    is ($status, 0, 'Object owner succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'store', 'file', 'test',
                                    "foo\nbar\n");
    is ($status, 0, 'Object store succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'test');
    is ($status, 0, 'Object get succeeds');
    is ($out, "foo\nbar\n", ' and returns the right data');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'store', 'file', 'test', '');
    is ($status, 0, 'Store of empty object succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'test');
    is ($status, 0, 'Get of empty object succeeds');
    is ($out, '', ' and returns an empty object');
    is ($err, '', ' with no error');
    ($out, $err, $status) = wallet ($principal, 'destroy', 'file', 'test');
    is ($status, 0, 'Object destroy succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'test');
    isnt ($status, 0, 'Object get now fails');
    is ($out, '', ' with no output');
    is ($err, "wallet: $principal not authorized to create file:test\n",
        ' and the right error');

    # Try auto-creation.
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'auto');
    isnt ($status, 0, 'Object autocreation get fails');
    is ($out, '', ' with no output');
    is ($err, "wallet: cannot get file:auto: object has not been stored\n",
        ' and the right error');
    ($out, $err, $status) = wallet ($principal, 'destroy', 'file', 'auto');
    is ($status, 0, 'Object destroy succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'store', 'file', 'auto',
                                    "baz\nboo\n");
    is ($status, 0, 'Object autocreation store succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'auto');
    is ($status, 0, 'Object get now succeeds');
    is ($out, "baz\nboo\n", ' with the right output');
    is ($err, '', ' and no error');

    # Store data beginning with a dash.
    ($out, $err, $status) = wallet ($principal, 'store', 'file', 'auto', '--',
                                    '-q');
    is ($status, 0, 'Storing data that looks like an option succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'auto');
    is ($status, 0, 'Object get succeeds');
    is ($out, '-q', ' with the right output');
    is ($err, '', ' and no error');

    # Store data containing nul characters.
    my $data = "Some data\000with a nul";
    open (IN, '>', 'tmp-file') or BAIL_OUT ("cannot create tmp-file: $!");
    print IN $data;
    close IN;
    ($out, $err, $status) = wallet ($principal, '-f', 'tmp-file', 'store',
                                    'file', 'auto');
    unlink ('tmp-file');
    is ($status, 0, 'Storing data with a nul succeeds');
    is ($out, '', ' with no output');
    is ($err, '', ' and no error');
    ($out, $err, $status) = wallet ($principal, 'get', 'file', 'auto');
    is ($status, 0, 'Object get succeeds');
    is ($out, $data, ' with the right output');
    is ($err, '', ' and no error');

    # All done.
    remctld_stop;
    $admin->destroy;
    if (-d 'test-files') {
        system ('rm', '-r', 'test-files');
    }
}

# Clean up the database and other test files at the end of the test.
END {
    unlink ('wallet-db', 'krb5cc_test', 'test-pid');
}