The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Test::Webservice::InterMine::Query::Template;

use strict;

use base ('Test::Webservice::InterMine::Query::Core');
use Test::MockObject;
use Test::MockObject::Extends;
use Test::More;
use Test::XML;
use Test::Exception;
use Data::Dumper;

sub class {'Webservice::InterMine::Query::Template'}
sub template_xml {q|
<template name="employeesFromCompanyAndDepartment" description="View all the employees that work within a certain department of the specified company" >
     <query name="employeesFromCompanyAndDepartment" model="testmodel" view="Employee.name Employee.age" constraintLogic="A and B">
     <constraint description="" identifier="" path="Employee.department.company.name" op="=" value="CompanyA" code="A" />
     <constraint description="Choose a name" identifier="name choice" path="Employee.department.name" op="=" value="FOO" code="B" editable="true" switchable="on"/>
   </query>
</template>
|}
sub exp_xml {q|<template comment="" longDescription="" name="employeesFromCompanyAndDepartment" title="View all the employees that work within a certain department of the specified company">
   <query constraintLogic="A and B" model="testmodel" name="employeesFromCompanyAndDepartment" sortOrder="Employee.name asc" view="Employee.name Employee.age">
     <constraint code="A" editable="false" op="=" path="Employee.department.company.name" switchable="locked" value="CompanyA"/>
     <constraint description="Choose a name" identifier="name choice" code="B" editable="true" op="=" path="Employee.department.name" switchable="on" value="FOO"/>
   </query>
 </template>

|}

sub exp_shown_con {
    return q|B) Employee.department.name = "FOO" (on)|;
}

sub exp_head {
    return (
	name  => "employeesFromCompanyAndDepartment",
	title => "View all the employees that work within a certain department of the specified company",
	longDescription => '',
	comment => '',
    );
}
sub exp_url {'FAKEROOTFAKEPATH'}

sub args {
    my $test = shift;
    return (
	model => $test->model,
	source_string => $test->template_xml,
	service => $test->{service},
    );
}
sub extra_constraint_args {(is_editable => 1)}
sub test_paths {
    my $test = shift;
    my @paths = $test->SUPER::test_paths;
    return (@paths, 'Employee.department.company.name');
}

sub logic_string1 {'A and B and C and D and E'}
sub logic_string2 {'(C or D) and E'}

sub startup {
    my $test = shift;

    my $service = Test::MockObject->new;
    $service->fake_module(
	'Webservice::InterMine::Service',
	new => sub {
	    return $service;
	},
    );
    $service->set_isa('Webservice::InterMine::Service');
    $service->mock(
	model => sub {
	    return $test->model;
	},
    );
    $service->mock(
	get_results_iterator => sub {
	    my $self = shift;
	    return @_;
	},
    );
    $service->mock(
	root => sub {
	    return 'FAKEROOT';
	},
    );
    $service->mock(
	TEMPLATE_QUERY_PATH => sub {
	    return 'FAKEPATH';
	},
    );
    $service->mock(
	get_results_iterator => sub {
	    return $test->{iterator};
	},
    );
    $test->{service} = $service;

    my $iterator = Test::MockObject->new;
    $iterator->mock(
	all_lines => sub {
	    my $self = shift;
	    return @_, @_, @_; #repeated so we get a list back
	},
    );
    $test->{iterator} = $iterator;
    $test->SUPER::startup;
}


sub _methods : Test(2) {
    my $test = shift;
    $test->SUPER::_methods;
    my @methods = (
	qw/to_xml source_string source_file url
	  results_with service_root 
	  editable_constraints show_constraints
	  comment title head insertion/
    );
    can_ok($test->class, @methods);
}

sub _inheritance : Test {
    my $test = shift;
    isa_ok($test->class, 'Webservice::InterMine::Query::Core');
}

sub head : Test(2) {
    my $test = shift;
    my $obj = $test->{object};
    my %exp_head = $test->exp_head;
    is_deeply(
	$obj->head, \%exp_head,
	"Gets the head correctly",
    );
    $obj->comment("a very nice template");
    $obj->description("something kind of templatey");
    $exp_head{comment} = "a very nice template";
    $exp_head{longDescription} = "something kind of templatey";
    is_deeply(
	$obj->head, \%exp_head,
	"Gets the head correctly with changes",
    );
}

sub to_xml : Test {
    my $test = shift;
    my $obj = $test->{object};
    is_xml(
	$obj->to_xml, $test->exp_xml,
	"Serialises to xml ok",
    );
}
sub sort_order_initial_state : Test {
    my $test = shift;
    my $obj = $test->{object};
    is(
        $obj->sort_order, 'Employee.name asc',
        "Sets the sort order correctly",
    );
}

sub view : Test(9) {
    my $test = shift;
    my $obj  = $test->{object};
    my @initial_view = ('Employee.name', 'Employee.age');
    is_deeply(
	[$obj->views], \@initial_view, "Has a good initial view",
    );
    $obj->clear_view;
    $test->SUPER::view;
}

sub url : Test {
    my $test = shift;
    my $obj = $test->{object};
    is(
	$obj->url, $test->exp_url,
	"Makes a good url",
    );
}

sub results : Test(4) {
    my $test = shift;
    my $obj  = $test->{object};
    my $service = $test->{service};
    $service->mock(
	  get_results_iterator => sub {
        sub MockedResIt::get_all {return shift}
        my $self = shift;
        my $args = [@_];
        return bless $args, 'MockedResIt';
	});

    is_deeply(
        $obj->results(as => 'string'),
        [
            "FAKEROOTFAKEPATH", 
            {
                'constraint1' => 'Employee.department.name',
                'value1' => 'FOO',
                name => 'employeesFromCompanyAndDepartment',
                op1 => '=',
                code1 => 'B',
            },
            $obj->view, 'tab', 'perl', undef
        ],
        "Produces appropriate arguments for string results",
    );
    is_deeply(
        $obj->results(as => 'arrayref'),
        [
            "FAKEROOTFAKEPATH", 
            {
                'constraint1' => 'Employee.department.name',
                'value1' => 'FOO',
                name => 'employeesFromCompanyAndDepartment',
                op1 => '=',
                code1 => 'B',
            },
            $obj->view, 'arrayref', 'perl', undef
        ],
        "Produces appropriate arguments for other formats",
    );
    is_deeply(
        $obj->results(),
        [
            "FAKEROOTFAKEPATH", 
            {
                'constraint1' => 'Employee.department.name',
                'value1' => 'FOO',
                name => 'employeesFromCompanyAndDepartment',
                op1 => '=',
                code1 => 'B',
            },
            $obj->view, 'rr', 'perl', undef
        ],
        "Defaults to result-row"
    );
}

sub results_with : Test(14) {
    my $test    = shift;
    my $obj     = $test->{object};
    my $exp_xml = $test->exp_xml;
    my $before  = $obj->show_constraints;
    $obj = Test::MockObject::Extends->new($obj);
    $obj->mock(
        results => sub {
            return [@_];
        },
    );
    my $results;
    lives_ok(
        sub {$results = $obj->results_with();},
        "Runs results_with with no args OK",
    ) or diag( $obj->show_constraints );
    is_xml(
        $results->[0]->to_xml,
        $exp_xml,
        "The xml comes out as expected",
    );
    lives_ok(
        sub {$results = $obj->results_with(valueB => 'BAR');},
        "Runs results_with OK with a value",
    );
    my $after = $obj->show_constraints;
    is($after, $before, "and does not change the obj's constraints");
    $exp_xml =~ s/FOO/BAR/;
    is_xml(
        $results->[0]->to_xml,
        $exp_xml,
        "The xml comes out as expected",
    );
    is_deeply(
        [$results->[1], $results->[2]], 
        ['as', undef], 
        "the format arg is correct",
    );
    lives_ok(
        sub {$results = $obj->results_with(opB => '=');},
        "runs results with ok with an operator",
    );
    $exp_xml =~ s/BAR/FOO/;
    $exp_xml =~ s/LIKE/=/; #TODO make this test work, now that LIKE has gone
    is_xml(
        $results->[0]->to_xml,
        $exp_xml,
        "The xml comes out as expected",
    );
    lives_ok(
        sub {$results = $obj->results_with(valueB => 'QUUX', opB => '=');},
        "runs results with ok with an operator and a value",
    );
    $exp_xml =~ s/FOO/QUUX/;
    is_xml(
        $results->[0]->to_xml,
        $exp_xml,
        "The xml comes out as expected",
    ) or diag($exp_xml);

    $exp_xml =~ s/QUUX/ZOP/;
    lives_ok(
       sub {
           $results = $obj->results_with(
               valueB => 'ZOP', 
               opB => '=', 
               as => 'strings'
           );
       },
        "runs results with ok with an operator and a value and a format",
    );
    is(
        $results->[2], 
        'strings', 
        "the format arg is correct",
    );
    is_xml(
        $results->[0]->to_xml,
        $exp_xml,
        "The xml comes out as expected",
    ) or diag($exp_xml);

    throws_ok(
        sub {$obj->results_with(valueA => 'foo')},
        qr/You can only change values and operators for editable constraints/,
        "Catches attempts to apply values to non editable constraints",
    );
}

sub editable_constraints : Test {
    my $test = shift;
    my $obj  = $test->{object};
    is($obj->editable_constraints, 1, "Parses editable attribute correctly");
}

sub show_constraints : Test {
    my $test = shift;
    my $obj  = $test->{object};
    is($obj->show_constraints, $test->exp_shown_con, "Reports constraints correctly");
}

sub save : Test {
    local $TODO = "saving currently unimplemented";
}

1;