2
2
namespace Codeception \Lib \Connector ;
3
3
4
4
use Codeception \Lib \Connector \Yii2 \Logger ;
5
- use Codeception \Lib \Connector \ Yii2 \ TestMailer ;
5
+ use Codeception \Lib \InnerBrowser ;
6
6
use Codeception \Util \Debug ;
7
7
use Symfony \Component \BrowserKit \Client ;
8
8
use Symfony \Component \BrowserKit \Cookie ;
9
9
use Symfony \Component \BrowserKit \Response ;
10
10
use Yii ;
11
11
use yii \base \ExitException ;
12
+ use yii \base \Security ;
12
13
use yii \web \HttpException ;
14
+ use yii \web \Request ;
13
15
use yii \web \Response as YiiResponse ;
14
16
15
17
class Yii2 extends Client
@@ -21,71 +23,41 @@ class Yii2 extends Client
21
23
*/
22
24
public $ configFile ;
23
25
24
- public $ defaultServerVars = [];
25
-
26
- /**
27
- * @var array
28
- */
29
- public $ headers ;
30
- public $ statusCode ;
31
-
32
- /**
33
- * @var \yii\web\Application
34
- */
35
- private $ app ;
36
-
37
- /**
38
- * @var \yii\db\Connection
39
- */
40
- public static $ db ; // remember the db instance
41
-
42
- /**
43
- * @var TestMailer
44
- */
45
- public static $ mailer ;
46
-
47
26
/**
48
27
* @return \yii\web\Application
49
28
*/
50
29
public function getApplication ()
51
30
{
52
- if (!isset ($ this -> app )) {
31
+ if (!isset (Yii:: $ app )) {
53
32
$ this ->startApp ();
54
33
}
55
- return $ this -> app ;
34
+ return Yii:: $ app ;
56
35
}
57
36
58
37
public function resetApplication ()
59
38
{
60
- $ this ->app = null ;
39
+ codecept_debug ('Destroying application ' );
40
+ Yii::$ app = null ;
41
+ \yii \web \UploadedFile::reset ();
42
+ if (method_exists (\yii \base \Event::className (), 'offAll ' )) {
43
+ \yii \base \Event::offAll ();
44
+ }
45
+ Yii::setLogger (null );
61
46
}
62
47
63
48
public function startApp ()
64
49
{
50
+ codecept_debug ('Starting application ' );
65
51
$ config = require ($ this ->configFile );
66
52
if (!isset ($ config ['class ' ])) {
67
53
$ config ['class ' ] = 'yii\web\Application ' ;
68
54
}
69
- if (static ::$ db ) {
70
- // If the DB conection already exists, make sure to pass it as early as possible
71
- // to prevent application from new connection creating during bootstrap
72
- $ config ['components ' ]['db ' ] = static ::$ db ;
73
- }
55
+
56
+ $ config = $ this ->mockMailer ($ config );
74
57
/** @var \yii\web\Application $app */
75
- $ this ->app = Yii::createObject ($ config );
76
- $ this ->persistDb ();
77
- $ this ->mockMailer ($ config );
78
- \Yii::setLogger (new Logger ());
79
- }
58
+ Yii::$ app = Yii::createObject ($ config );
80
59
81
- public function resetPersistentVars ()
82
- {
83
- static ::$ db = null ;
84
- static ::$ mailer = null ;
85
- \yii \web \UploadedFile::reset ();
86
- if (method_exists (\yii \base \Event::className (), 'offAll ' )) {
87
- \yii \base \Event::offAll ();
88
- }
60
+ Yii::setLogger (new Logger ());
89
61
}
90
62
91
63
/**
@@ -98,7 +70,6 @@ public function doRequest($request)
98
70
{
99
71
$ _COOKIE = $ request ->getCookies ();
100
72
$ _SERVER = $ request ->getServer ();
101
- $ this ->restoreServerVars ();
102
73
$ _FILES = $ this ->remapFiles ($ request ->getFiles ());
103
74
$ _REQUEST = $ this ->remapRequestParameters ($ request ->getParameters ());
104
75
$ _POST = $ _GET = [];
@@ -123,20 +94,28 @@ public function doRequest($request)
123
94
124
95
$ app = $ this ->getApplication ();
125
96
126
- $ app ->getResponse ()->on (YiiResponse::EVENT_AFTER_PREPARE , [$ this , 'processResponse ' ]);
97
+ /**
98
+ * Just before the request we set the response object so it is always fresh.
99
+ * @todo Implement some kind of check to see if someone tried to change the objects' properties and expects
100
+ * those changes to be reflected in the reponse.
101
+ */
102
+ $ app ->set ('response ' , $ app ->getComponents ()['response ' ]);
127
103
128
104
// disabling logging. Logs are slowing test execution down
129
105
foreach ($ app ->log ->targets as $ target ) {
130
106
$ target ->enabled = false ;
131
107
}
132
108
133
- $ this ->headers = [];
134
- $ this ->statusCode = null ;
135
-
136
109
ob_start ();
137
110
138
111
// recreating request object to reset headers and cookies collections
112
+ /**
113
+ * Just before the request we set the request object so it is always fresh.
114
+ * @todo Implement some kind of check to see if someone tried to change the objects' properties and expects
115
+ * those changes to be reflected in the reponse.
116
+ */
139
117
$ app ->set ('request ' , $ app ->getComponents ()['request ' ]);
118
+
140
119
$ yiiRequest = $ app ->getRequest ();
141
120
if ($ request ->getContent () !== null ) {
142
121
$ yiiRequest ->setRawBody ($ request ->getContent ());
@@ -148,35 +127,42 @@ public function doRequest($request)
148
127
$ yiiRequest ->setQueryParams ($ _GET );
149
128
150
129
try {
130
+ /*
131
+ * This is basically equivalent to $app->run() without sending the response.
132
+ * Sending the response is problematic because it tries to send headers.
133
+ */
151
134
$ app ->trigger ($ app ::EVENT_BEFORE_REQUEST );
152
-
153
- $ app ->handleRequest ($ yiiRequest )->send ();
154
-
135
+ $ response = $ app ->handleRequest ($ yiiRequest );
155
136
$ app ->trigger ($ app ::EVENT_AFTER_REQUEST );
137
+ codecept_debug ($ response ->isSent );
138
+ $ response ->send ();
156
139
} catch (\Exception $ e ) {
157
140
if ($ e instanceof HttpException) {
158
141
// Don't discard output and pass exception handling to Yii to be able
159
142
// to expect error response codes in tests.
160
143
$ app ->errorHandler ->discardExistingOutput = false ;
161
144
$ app ->errorHandler ->handleException ($ e );
145
+ $ response = $ app ->response ;
146
+
162
147
} elseif (!$ e instanceof ExitException) {
163
148
// for exceptions not related to Http, we pass them to Codeception
164
149
$ this ->resetApplication ();
165
150
throw $ e ;
166
151
}
167
152
}
168
153
169
- $ content = ob_get_clean ( );
154
+ $ this -> encodeCookies ( $ response , $ yiiRequest , $ app -> security );
170
155
171
- // catch "location" header and display it in debug, otherwise it would be handled
172
- // by symfony browser-kit and not displayed.
173
- if (isset ($ this ->headers ['location ' ])) {
174
- Debug::debug ("[Headers] " . json_encode ($ this ->headers ));
156
+ if ($ response ->isRedirection ) {
157
+ Debug::debug ("[Redirect with headers] " . print_r ($ response ->getHeaders ()->toArray (), true ));
175
158
}
176
159
177
- $ this ->resetApplication ();
160
+ $ content = ob_get_clean ();
161
+ if (empty ($ content ) && !empty ($ response ->content )) {
162
+ throw new \Exception ('No content was sent from Yii application ' );
163
+ }
178
164
179
- return new Response ($ content , $ this ->statusCode , $ this -> headers );
165
+ return new Response ($ content , $ response ->statusCode , $ response -> getHeaders ()-> toArray () );
180
166
}
181
167
182
168
protected function revertErrorHandler ()
@@ -186,36 +172,28 @@ protected function revertErrorHandler()
186
172
}
187
173
188
174
189
- public function restoreServerVars ()
190
- {
191
- $ this ->server = $ this ->defaultServerVars ;
192
- foreach ($ this ->server as $ key => $ value ) {
193
- $ _SERVER [$ key ] = $ value ;
194
- }
195
- }
196
-
197
- public function processResponse ($ event )
198
- {
199
- /** @var \yii\web\Response $response */
200
- $ response = $ event ->sender ;
201
- $ request = Yii::$ app ->getRequest ();
202
- $ this ->headers = $ response ->getHeaders ()->toArray ();
203
- $ response ->getHeaders ()->removeAll ();
204
- $ this ->statusCode = $ response ->getStatusCode ();
205
- $ cookies = $ response ->getCookies ();
206
-
175
+ /**
176
+ * Encodes the cookies and adds them to the headers.
177
+ * @param \yii\web\Response $response
178
+ * @throws \yii\base\InvalidConfigException
179
+ */
180
+ protected function encodeCookies (
181
+ YiiResponse $ response ,
182
+ Request $ request ,
183
+ Security $ security
184
+ ) {
207
185
if ($ request ->enableCookieValidation ) {
208
186
$ validationKey = $ request ->cookieValidationKey ;
209
187
}
210
188
211
- foreach ($ cookies as $ cookie ) {
189
+ foreach ($ response -> getCookies () as $ cookie ) {
212
190
/** @var \yii\web\Cookie $cookie */
213
191
$ value = $ cookie ->value ;
214
192
if ($ cookie ->expire != 1 && isset ($ validationKey )) {
215
193
$ data = version_compare (Yii::getVersion (), '2.0.2 ' , '> ' )
216
194
? [$ cookie ->name , $ cookie ->value ]
217
195
: $ cookie ->value ;
218
- $ value = Yii:: $ app -> security ->hashData (serialize ($ data ), $ validationKey );
196
+ $ value = $ security ->hashData (serialize ($ data ), $ validationKey );
219
197
}
220
198
$ c = new Cookie (
221
199
$ cookie ->name ,
@@ -228,21 +206,15 @@ public function processResponse($event)
228
206
);
229
207
$ this ->getCookieJar ()->set ($ c );
230
208
}
231
- $ cookies ->removeAll ();
232
209
}
233
210
234
211
/**
235
212
* Replace mailer with in memory mailer
236
- * @param $config
237
- * @param $app
213
+ * @param array $config Original configuration
214
+ * @return array New configuration
238
215
*/
239
- protected function mockMailer ($ config )
216
+ protected function mockMailer (array $ config )
240
217
{
241
- if (static ::$ mailer ) {
242
- $ this ->app ->set ('mailer ' , static ::$ mailer );
243
- return ;
244
- }
245
-
246
218
// options that make sense for mailer mock
247
219
$ allowedOptions = [
248
220
'htmlLayout ' ,
@@ -267,21 +239,24 @@ protected function mockMailer($config)
267
239
}
268
240
}
269
241
}
242
+ $ config ['components ' ]['mailer ' ] = $ mailerConfig ;
270
243
271
- $ this ->app ->set ('mailer ' , $ mailerConfig );
272
- static ::$ mailer = $ this ->app ->get ('mailer ' );
244
+ return $ config ;
273
245
}
274
246
275
247
/**
276
- * @param $app
248
+ * A new client is created for every test, it is destroyed after every test.
249
+ * @see InnerBrowser::_after()
250
+ *
277
251
*/
278
- protected function persistDb ()
252
+ public function __destruct ()
279
253
{
280
- // always use the same DB connection
281
- if (static ::$ db ) {
282
- $ this ->app ->set ('db ' , static ::$ db );
283
- } elseif ($ this ->app ->has ('db ' )) {
284
- static ::$ db = $ this ->app ->get ('db ' );
285
- }
254
+ $ this ->resetApplication ();
255
+ }
256
+
257
+ public function restart ()
258
+ {
259
+ parent ::restart ();
260
+ $ this ->resetApplication ();
286
261
}
287
262
}
0 commit comments