Completed
Pull Request — master (#923)
by Jeroen De
61:01
created

assertErrorCauseIsLogged()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\Tests\EdgeToEdge\Routes;
6
7
use DateTime;
8
use Psr\Log\LogLevel;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpFoundation\Response;
11
use Symfony\Component\HttpKernel\Client;
12
use WMDE\Fundraising\Frontend\DonationContext\Tests\Data\ValidDonation;
13
use WMDE\Fundraising\Frontend\Factories\FunFunFactory;
14
use WMDE\Fundraising\Frontend\Tests\EdgeToEdge\WebRouteTestCase;
15
use WMDE\Fundraising\Frontend\Tests\Fixtures\FixedTokenGenerator;
16
use WMDE\PsrLogTestDoubles\LoggerSpy;
17
18
class SofortPaymentNotificationRouteTest extends WebRouteTestCase {
19
20
	private const VALID_TOKEN = 'my-secret_token';
21
	private const INVALID_TOKEN = 'fffffggggg';
22
23
	private const VALID_TRANSACTION_ID = '99999-53245-5483-4891';
24
	private const VALID_TRANSACTION_TIME = '2010-04-14T19:01:08+02:00';
25
26
	public function testGivenWrongPaymentType_applicationRefuses(): void {
27
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
28
			$donation = ValidDonation::newIncompletePayPalDonation();
29
			$factory->getDonationRepository()->storeDonation( $donation );
30
31
			$client->request(
32
				Request::METHOD_POST,
33
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::VALID_TOKEN,
34
				[
35
					'transaction' => self::VALID_TRANSACTION_ID,
36
					'time' => self::VALID_TRANSACTION_TIME
37
				]
38
			);
39
40
			$this->assertIsBadRequestResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsBadRequestResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
41
		} );
42
	}
43
44
	private function newEnvironment( callable $onEnvironmentCreated ): void {
45
		$this->createEnvironment(
46
			[],
47
			function ( Client $client, FunFunFactory $factory ) use ( $onEnvironmentCreated ) {
48
				$factory->setTokenGenerator( new FixedTokenGenerator(
49
					self::VALID_TOKEN,
50
					new DateTime( '2039-12-31 23:59:59Z' )
51
				) );
52
53
				$onEnvironmentCreated( $client, $factory );
54
			}
55
		);
56
	}
57
58
	private function assertIsBadRequestResponse( Response $response ): void {
59
		$this->assertSame( 'Bad request', $response->getContent() );
60
		$this->assertSame( Response::HTTP_BAD_REQUEST, $response->getStatusCode() );
61
	}
62
63
	public function testGivenWrongToken_applicationRefuses(): void {
64
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
65
			$donation = ValidDonation::newIncompleteSofortDonation();
66
			$factory->getDonationRepository()->storeDonation( $donation );
67
68
			$client->request(
69
				Request::METHOD_POST,
70
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::INVALID_TOKEN,
71
				[
72
					'transaction' => self::VALID_TRANSACTION_ID,
73
					'time' => self::VALID_TRANSACTION_TIME
74
				]
75
			);
76
77
			$this->assertIsBadRequestResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsBadRequestResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
78
		} );
79
	}
80
81
	public function testGivenBadTimeFormat_applicationRefuses(): void {
82
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
83
			$donation = ValidDonation::newIncompleteSofortDonation();
84
			$factory->getDonationRepository()->storeDonation( $donation );
85
86
			$client->request(
87
				Request::METHOD_POST,
88
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::VALID_TOKEN,
89
				[
90
					'transaction' => self::VALID_TRANSACTION_ID,
91
					'time' => 'now'
92
				]
93
			);
94
95
			$this->assertIsBadRequestResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsBadRequestResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
96
		} );
97
	}
98
99
	public function testGivenValidRequest_applicationIndicatesSuccess(): void {
100
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
101
			$donation = ValidDonation::newIncompleteSofortDonation();
102
			$factory->getDonationRepository()->storeDonation( $donation );
103
104
			$client->request(
105
				Request::METHOD_POST,
106
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::VALID_TOKEN,
107
				[
108
					'transaction' => self::VALID_TRANSACTION_ID,
109
					'time' => self::VALID_TRANSACTION_TIME
110
				]
111
			);
112
113
			$this->assertSame( 'Ok', $client->getResponse()->getContent() );
114
			$this->assertSame( Response::HTTP_OK, $client->getResponse()->getStatusCode() );
115
116
			$donation = $factory->getDonationRepository()->getDonationById( $donation->getId() );
117
118
			$this->assertEquals( new DateTime( self::VALID_TRANSACTION_TIME ), $donation->getPaymentMethod()->getConfirmedAt() );
119
		} );
120
	}
121
122
	public function testGivenAlreadyConfirmedPayment_requestDataIsLogged(): void {
123
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
124
			$logger = new LoggerSpy();
125
			$factory->setSofortLogger( $logger );
126
127
			$donation = ValidDonation::newCompletedSofortDonation();
128
			$factory->getDonationRepository()->storeDonation( $donation );
129
130
			$client->request(
131
				Request::METHOD_POST,
132
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::VALID_TOKEN,
133
				[
134
					'transaction' => self::VALID_TRANSACTION_ID,
135
					'time' => self::VALID_TRANSACTION_TIME
136
				]
137
			);
138
139
			$this->assertIsBadRequestResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsBadRequestResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
140
			$this->assertErrorCauseIsLogged( $logger, 'Duplicate notification' );
141
			$this->assertRequestVarsAreLogged( $logger );
142
			$this->assertLogLevel( $logger, LogLevel::INFO );
143
		} );
144
	}
145
146
	private function assertErrorCauseIsLogged( LoggerSpy $logger, string $expectedMessage ): void {
147
		$this->assertSame(
148
			[ $expectedMessage ],
149
			$logger->getLogCalls()->getMessages()
150
		);
151
	}
152
153
	private function assertRequestVarsAreLogged( LoggerSpy $logger ): void {
154
		$this->assertSame(
155
			self::VALID_TRANSACTION_ID,
156
			$logger->getLogCalls()->getFirstCall()->getContext()['post_vars']['transaction']
0 ignored issues
show
Bug introduced by
The method getContext cannot be called on $logger->getLogCalls()->getFirstCall() (of type array<integer,object<WMD...ogTestDoubles\LogCall>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
157
		);
158
159
		$this->assertEquals(
160
			self::VALID_TOKEN,
161
			$logger->getLogCalls()->getFirstCall()->getContext()['query_vars']['updateToken']
0 ignored issues
show
Bug introduced by
The method getContext cannot be called on $logger->getLogCalls()->getFirstCall() (of type array<integer,object<WMD...ogTestDoubles\LogCall>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
162
		);
163
	}
164
165
	private function assertLogLevel( LoggerSpy $logger, string $expectedLevel ): void {
166
		$this->assertSame( $expectedLevel, $logger->getLogCalls()->getFirstCall()->getLevel() );
0 ignored issues
show
Bug introduced by
The method getLevel cannot be called on $logger->getLogCalls()->getFirstCall() (of type array<integer,object<WMD...ogTestDoubles\LogCall>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
167
	}
168
169
	public function testGivenUnknownDonation_requestDataIsLogged(): void {
170
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
171
			$logger = new LoggerSpy();
172
			$factory->setSofortLogger( $logger );
173
174
			$donation = ValidDonation::newCompletedSofortDonation();
175
			$factory->getDonationRepository()->storeDonation( $donation );
176
177
			$client->request(
178
				Request::METHOD_POST,
179
				'/sofort-payment-notification?id=' . ( $donation->getId() + 1 ) . '&updateToken=' . self::VALID_TOKEN,
180
				[
181
					'transaction' => self::VALID_TRANSACTION_ID,
182
					'time' => self::VALID_TRANSACTION_TIME
183
				]
184
			);
185
186
			$this->assertIsErrorResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsErrorResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
187
			$this->assertErrorCauseIsLogged( $logger, 'Donation not found' );
188
			$this->assertRequestVarsAreLogged( $logger );
189
			$this->assertLogLevel( $logger, LogLevel::ERROR );
190
		} );
191
	}
192
193
	private function assertIsErrorResponse( Response $response ): void {
194
		$this->assertSame( 'Error', $response->getContent() );
195
		$this->assertSame( Response::HTTP_INTERNAL_SERVER_ERROR, $response->getStatusCode() );
196
	}
197
198
	public function testGivenBadTime_requestDataIsLogged(): void {
199
		$this->newEnvironment( function ( Client $client, FunFunFactory $factory ) {
200
			$logger = new LoggerSpy();
201
			$factory->setSofortLogger( $logger );
202
203
			$donation = ValidDonation::newCompletedSofortDonation();
204
			$factory->getDonationRepository()->storeDonation( $donation );
205
206
			$client->request(
207
				Request::METHOD_POST,
208
				'/sofort-payment-notification?id=' . $donation->getId() . '&updateToken=' . self::VALID_TOKEN,
209
				[
210
					'transaction' => self::VALID_TRANSACTION_ID,
211
					'time' => 'now'
212
				]
213
			);
214
215
			$this->assertIsBadRequestResponse( $client->getResponse() );
0 ignored issues
show
Bug introduced by
It seems like $client->getResponse() can be null; however, assertIsBadRequestResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
216
			$this->assertErrorCauseIsLogged( $logger, 'Bad notification time' );
217
			$this->assertRequestVarsAreLogged( $logger );
218
			$this->assertLogLevel( $logger, LogLevel::ERROR );
219
		} );
220
	}
221
222
}
223