Completed
Branch proxy (a8ed88)
by leo
08:29
created

TicketRepositoryTest::testApplyTicket()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 89
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 74
nc 4
nop 0
dl 0
loc 89
rs 8.5731
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: leo108
5
 * Date: 2016/9/29
6
 * Time: 09:53
7
 */
8
namespace Leo108\CAS\Repositories;
9
10
use Exception;
11
use Leo108\CAS\Exceptions\CAS\CasException;
12
use Leo108\CAS\Models\Service;
13
use Leo108\CAS\Models\Ticket;
14
use Leo108\CAS\Services\TicketGenerator;
15
use Mockery;
16
use ReflectionClass;
17
use TestCase;
18
use User;
19
20
class TicketRepositoryTest extends TestCase
21
{
22
    public function testApplyTicket()
23
    {
24
        //test if service url is not valid
25
        $user = Mockery::mock(User::class)
0 ignored issues
show
Bug introduced by
The method shouldReceive() does not seem to exist on object<Mockery\Expectation>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
26
            ->shouldReceive('getAttribute')
27
            ->withArgs(['id'])
28
            ->andReturn(1)
29
            ->shouldReceive('getEloquentModel')
30
            ->andReturn(Mockery::self())
31
            ->getMock();
32
33
        $serviceRepository = Mockery::mock(ServiceRepository::class)
34
            ->shouldReceive('getServiceByUrl')
35
            ->andReturn(false)
36
            ->getMock();
37
        app()->instance(ServiceRepository::class, $serviceRepository);
38
        try {
39
            app()->make(TicketRepository::class)->applyTicket($user, 'what ever');
40
        } catch (Exception $e) {
41
            $this->assertInstanceOf(CasException::class, $e);
42
            $this->assertEquals($e->getCasErrorCode(), CasException::INVALID_SERVICE);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getCasErrorCode() does only exist in the following sub-classes of Exception: Leo108\CAS\Exceptions\CAS\CasException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
43
        }
44
45
        //test if get available ticket failed
46
        $service           = Mockery::mock(Service::class)
47
            ->shouldReceive('getAttribute')
48
            ->withArgs(['id'])
49
            ->andReturn(1)
50
            ->getMock();
51
        $serviceRepository = Mockery::mock(ServiceRepository::class)
52
            ->shouldReceive('getServiceByUrl')
53
            ->andReturn($service)
54
            ->getMock();
55
        app()->instance(ServiceRepository::class, $serviceRepository);
56
        $ticketRepository = $this->initTicketRepository()
57
            ->makePartial()
58
            ->shouldAllowMockingProtectedMethods()
59
            ->shouldReceive('getAvailableTicket')
60
            ->andReturn(false)
61
            ->getMock();
62
63
        try {
64
            $ticketRepository->applyTicket($user, 'what ever');
65
        } catch (Exception $e) {
66
            $this->assertInstanceOf(CasException::class, $e);
67
            $this->assertEquals($e->getCasErrorCode(), CasException::INTERNAL_ERROR);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getCasErrorCode() does only exist in the following sub-classes of Exception: Leo108\CAS\Exceptions\CAS\CasException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
68
            $this->assertEquals($e->getMessage(), 'apply ticket failed');
69
        }
70
71
        //normal
72
        $ticketStr         = 'ST-abc';
73
        $serviceUrl        = 'what ever';
74
        $service           = Mockery::mock(Service::class)
75
            ->shouldReceive('getAttribute')
76
            ->withArgs(['id'])
77
            ->andReturn(1)
78
            ->getMock();
79
        $serviceRepository = Mockery::mock(ServiceRepository::class)
80
            ->shouldReceive('getServiceByUrl')
81
            ->andReturn($service)
82
            ->getMock();
83
        app()->instance(ServiceRepository::class, $serviceRepository);
84
        $ticket = Mockery::mock(Ticket::class)
85
            ->shouldReceive('newInstance')
86
            ->andReturnUsing(
87
                function ($param) {
88
                    $obj = Mockery::mock();
89
                    $obj->shouldReceive('user->associate');
90
                    $obj->shouldReceive('service->associate');
91
                    $obj->shouldReceive('save');
92
                    $obj->ticket      = $param['ticket'];
0 ignored issues
show
Bug introduced by
Accessing ticket on the interface Mockery\MockInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
93
                    $obj->service_url = $param['service_url'];
0 ignored issues
show
Bug introduced by
Accessing service_url on the interface Mockery\MockInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
94
95
                    return $obj;
96
                }
97
            )
98
            ->getMock();
99
        app()->instance(Ticket::class, $ticket);
100
        $ticketRepository = $this->initTicketRepository()
101
            ->makePartial()
102
            ->shouldAllowMockingProtectedMethods()
103
            ->shouldReceive('getAvailableTicket')
104
            ->andReturn($ticketStr)
105
            ->getMock();
106
107
        $record = $ticketRepository->applyTicket($user, $serviceUrl);
108
        $this->assertEquals($ticketStr, $record->ticket);
109
        $this->assertEquals($serviceUrl, $record->service_url);
110
    }
111
112
    public function testGetByTicket()
113
    {
114
        $ticket = Mockery::mock(Ticket::class);
115
        $ticket->shouldReceive('where->first')->andReturn(null);
116
        app()->instance(Ticket::class, $ticket);
117
        $this->assertNull(app()->make(TicketRepository::class)->getByTicket('what ever'));
118
119
        $mockTicket = Mockery::mock(Ticket::class)
120
            ->shouldReceive('isExpired')
121
            ->andReturnValues([false, true])
122
            ->getMock();
123
124
        $ticket = Mockery::mock(Ticket::class);
125
        $ticket->shouldReceive('where->first')->andReturn($mockTicket);
126
        app()->instance(Ticket::class, $ticket);
127
        $this->assertNotNull(app()->make(TicketRepository::class)->getByTicket('what ever', false));
128
        $this->assertNotNull(app()->make(TicketRepository::class)->getByTicket('what ever'));
129
        $this->assertNull(app()->make(TicketRepository::class)->getByTicket('what ever'));
130
    }
131
132
    public function testInvalidTicket()
133
    {
134
        $mockTicket = Mockery::mock(Ticket::class)
135
            ->shouldReceive('delete')
136
            ->andReturn(true)
137
            ->getMock();
138
        $this->assertTrue(app()->make(TicketRepository::class)->invalidTicket($mockTicket));
139
    }
140
141
    public function testGetAvailableTicket()
142
    {
143
        $length          = 32;
144
        $prefix          = 'ST-';
145
        $ticket          = 'ticket string';
146
        $ticketGenerator = Mockery::mock(TicketGenerator::class)
147
            ->shouldReceive('generate')
148
            ->andReturnUsing(
149
                function ($totalLength, $paramPrefix, callable $checkFunc, $maxRetry) use ($length, $prefix, $ticket) {
150
                    $this->assertEquals($length, $totalLength);
151
                    $this->assertEquals($prefix, $paramPrefix);
152
                    $this->assertEquals('getByTicket called', call_user_func_array($checkFunc, [$ticket]));
153
                    $this->assertEquals(10, $maxRetry);
154
155
                    return 'generate called';
156
                }
157
            )
158
            ->once()
159
            ->getMock();
160
        app()->instance(TicketGenerator::class, $ticketGenerator);
161
        $ticketRepository = $this->initTicketRepository()
162
            ->makePartial()
163
            ->shouldReceive('getByTicket')
164
            ->with($ticket, false)
165
            ->andReturn('getByTicket called')
166
            ->once()
167
            ->getMock();
168
169
        $this->assertEquals(
170
            'generate called',
171
            self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke(
172
                $ticketRepository,
173
                $length,
174
                $prefix
175
            )
176
        );
177
    }
178
179
    /**
180
     * @return Mockery\MockInterface
181
     */
182
    protected function initTicketRepository()
183
    {
184
        return Mockery::mock(
185
            TicketRepository::class,
186
            [app(Ticket::class), app(ServiceRepository::class), app(TicketGenerator::class)]
187
        );
188
    }
189
}
190