Merge "Allow maxlength attribute on HTMLSelectAndOtherField"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiUploadTest.php
1 <?php
2 /**
3 * @group API
4 * @group Database
5 * @group medium
6 */
7
8 /**
9 * n.b. Ensure that you can write to the images/ directory as the
10 * user that will run tests.
11 */
12
13 // Note for reviewers: this intentionally duplicates functionality already in
14 // "ApiSetup" and so on. This framework works better IMO and has less
15 // strangeness (such as test cases inheriting from "ApiSetup"...) (and in the
16 // case of the other Upload tests, this flat out just actually works... )
17
18 // @todo Port the other Upload tests, and other API tests to this framework
19
20 /**
21 * @group Database
22 * @group Broken
23 * Broken test, reports false errors from time to time.
24 * See https://bugzilla.wikimedia.org/26169
25 *
26 * This is pretty sucky... needs to be prettified.
27 */
28 class ApiUploadTest extends ApiTestCaseUpload {
29 /**
30 * Testing login
31 * XXX this is a funny way of getting session context
32 */
33 public function testLogin() {
34 $user = self::$users['uploader'];
35
36 $params = array(
37 'action' => 'login',
38 'lgname' => $user->username,
39 'lgpassword' => $user->password
40 );
41 list( $result, , $session ) = $this->doApiRequest( $params );
42 $this->assertArrayHasKey( "login", $result );
43 $this->assertArrayHasKey( "result", $result['login'] );
44 $this->assertEquals( "NeedToken", $result['login']['result'] );
45 $token = $result['login']['token'];
46
47 $params = array(
48 'action' => 'login',
49 'lgtoken' => $token,
50 'lgname' => $user->username,
51 'lgpassword' => $user->password
52 );
53 list( $result, , $session ) = $this->doApiRequest( $params, $session );
54 $this->assertArrayHasKey( "login", $result );
55 $this->assertArrayHasKey( "result", $result['login'] );
56 $this->assertEquals( "Success", $result['login']['result'] );
57 $this->assertArrayHasKey( 'lgtoken', $result['login'] );
58
59 $this->assertNotEmpty( $session, 'API Login must return a session' );
60
61 return $session;
62 }
63
64 /**
65 * @depends testLogin
66 */
67 public function testUploadRequiresToken( $session ) {
68 $exception = false;
69 try {
70 $this->doApiRequest( array(
71 'action' => 'upload'
72 ) );
73 } catch ( UsageException $e ) {
74 $exception = true;
75 $this->assertEquals( "The token parameter must be set", $e->getMessage() );
76 }
77 $this->assertTrue( $exception, "Got exception" );
78 }
79
80 /**
81 * @depends testLogin
82 */
83 public function testUploadMissingParams( $session ) {
84 $exception = false;
85 try {
86 $this->doApiRequestWithToken( array(
87 'action' => 'upload',
88 ), $session, self::$users['uploader']->user );
89 } catch ( UsageException $e ) {
90 $exception = true;
91 $this->assertEquals( "One of the parameters filekey, file, url, statuskey is required",
92 $e->getMessage() );
93 }
94 $this->assertTrue( $exception, "Got exception" );
95 }
96
97 /**
98 * @depends testLogin
99 */
100 public function testUpload( $session ) {
101 $extension = 'png';
102 $mimeType = 'image/png';
103
104 try {
105 $randomImageGenerator = new RandomImageGenerator();
106 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
107 } catch ( Exception $e ) {
108 $this->markTestIncomplete( $e->getMessage() );
109 }
110
111 /** @var array $filePaths */
112 $filePath = $filePaths[0];
113 $fileSize = filesize( $filePath );
114 $fileName = basename( $filePath );
115
116 $this->deleteFileByFileName( $fileName );
117 $this->deleteFileByContent( $filePath );
118
119 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
120 $this->markTestIncomplete( "Couldn't upload file!\n" );
121 }
122
123 $params = array(
124 'action' => 'upload',
125 'filename' => $fileName,
126 'file' => 'dummy content',
127 'comment' => 'dummy comment',
128 'text' => "This is the page text for $fileName",
129 );
130
131 $exception = false;
132 try {
133 list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
134 self::$users['uploader']->user );
135 } catch ( UsageException $e ) {
136 $exception = true;
137 }
138 $this->assertTrue( isset( $result['upload'] ) );
139 $this->assertEquals( 'Success', $result['upload']['result'] );
140 $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
141 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
142 $this->assertFalse( $exception );
143
144 // clean up
145 $this->deleteFileByFilename( $fileName );
146 unlink( $filePath );
147 }
148
149 /**
150 * @depends testLogin
151 */
152 public function testUploadZeroLength( $session ) {
153 $mimeType = 'image/png';
154
155 $filePath = tempnam( wfTempDir(), "" );
156 $fileName = "apiTestUploadZeroLength.png";
157
158 $this->deleteFileByFileName( $fileName );
159
160 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
161 $this->markTestIncomplete( "Couldn't upload file!\n" );
162 }
163
164 $params = array(
165 'action' => 'upload',
166 'filename' => $fileName,
167 'file' => 'dummy content',
168 'comment' => 'dummy comment',
169 'text' => "This is the page text for $fileName",
170 );
171
172 $exception = false;
173 try {
174 $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user );
175 } catch ( UsageException $e ) {
176 $this->assertContains( 'The file you submitted was empty', $e->getMessage() );
177 $exception = true;
178 }
179 $this->assertTrue( $exception );
180
181 // clean up
182 $this->deleteFileByFilename( $fileName );
183 unlink( $filePath );
184 }
185
186 /**
187 * @depends testLogin
188 */
189 public function testUploadSameFileName( $session ) {
190 $extension = 'png';
191 $mimeType = 'image/png';
192
193 try {
194 $randomImageGenerator = new RandomImageGenerator();
195 $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
196 } catch ( Exception $e ) {
197 $this->markTestIncomplete( $e->getMessage() );
198 }
199
200 // we'll reuse this filename
201 /** @var array $filePaths */
202 $fileName = basename( $filePaths[0] );
203
204 // clear any other files with the same name
205 $this->deleteFileByFileName( $fileName );
206
207 // we reuse these params
208 $params = array(
209 'action' => 'upload',
210 'filename' => $fileName,
211 'file' => 'dummy content',
212 'comment' => 'dummy comment',
213 'text' => "This is the page text for $fileName",
214 );
215
216 // first upload .... should succeed
217
218 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) {
219 $this->markTestIncomplete( "Couldn't upload file!\n" );
220 }
221
222 $exception = false;
223 try {
224 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
225 self::$users['uploader']->user );
226 } catch ( UsageException $e ) {
227 $exception = true;
228 }
229 $this->assertTrue( isset( $result['upload'] ) );
230 $this->assertEquals( 'Success', $result['upload']['result'] );
231 $this->assertFalse( $exception );
232
233 // second upload with the same name (but different content)
234
235 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) {
236 $this->markTestIncomplete( "Couldn't upload file!\n" );
237 }
238
239 $exception = false;
240 try {
241 list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
242 self::$users['uploader']->user ); // FIXME: leaks a temporary file
243 } catch ( UsageException $e ) {
244 $exception = true;
245 }
246 $this->assertTrue( isset( $result['upload'] ) );
247 $this->assertEquals( 'Warning', $result['upload']['result'] );
248 $this->assertTrue( isset( $result['upload']['warnings'] ) );
249 $this->assertTrue( isset( $result['upload']['warnings']['exists'] ) );
250 $this->assertFalse( $exception );
251
252 // clean up
253 $this->deleteFileByFilename( $fileName );
254 unlink( $filePaths[0] );
255 unlink( $filePaths[1] );
256 }
257
258 /**
259 * @depends testLogin
260 */
261 public function testUploadSameContent( $session ) {
262 $extension = 'png';
263 $mimeType = 'image/png';
264
265 try {
266 $randomImageGenerator = new RandomImageGenerator();
267 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
268 } catch ( Exception $e ) {
269 $this->markTestIncomplete( $e->getMessage() );
270 }
271
272 /** @var array $filePaths */
273 $fileNames[0] = basename( $filePaths[0] );
274 $fileNames[1] = "SameContentAs" . $fileNames[0];
275
276 // clear any other files with the same name or content
277 $this->deleteFileByContent( $filePaths[0] );
278 $this->deleteFileByFileName( $fileNames[0] );
279 $this->deleteFileByFileName( $fileNames[1] );
280
281 // first upload .... should succeed
282
283 $params = array(
284 'action' => 'upload',
285 'filename' => $fileNames[0],
286 'file' => 'dummy content',
287 'comment' => 'dummy comment',
288 'text' => "This is the page text for " . $fileNames[0],
289 );
290
291 if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) {
292 $this->markTestIncomplete( "Couldn't upload file!\n" );
293 }
294
295 $exception = false;
296 try {
297 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
298 self::$users['uploader']->user );
299 } catch ( UsageException $e ) {
300 $exception = true;
301 }
302 $this->assertTrue( isset( $result['upload'] ) );
303 $this->assertEquals( 'Success', $result['upload']['result'] );
304 $this->assertFalse( $exception );
305
306 // second upload with the same content (but different name)
307
308 if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) {
309 $this->markTestIncomplete( "Couldn't upload file!\n" );
310 }
311
312 $params = array(
313 'action' => 'upload',
314 'filename' => $fileNames[1],
315 'file' => 'dummy content',
316 'comment' => 'dummy comment',
317 'text' => "This is the page text for " . $fileNames[1],
318 );
319
320 $exception = false;
321 try {
322 list( $result ) = $this->doApiRequestWithToken( $params, $session,
323 self::$users['uploader']->user ); // FIXME: leaks a temporary file
324 } catch ( UsageException $e ) {
325 $exception = true;
326 }
327 $this->assertTrue( isset( $result['upload'] ) );
328 $this->assertEquals( 'Warning', $result['upload']['result'] );
329 $this->assertTrue( isset( $result['upload']['warnings'] ) );
330 $this->assertTrue( isset( $result['upload']['warnings']['duplicate'] ) );
331 $this->assertFalse( $exception );
332
333 // clean up
334 $this->deleteFileByFilename( $fileNames[0] );
335 $this->deleteFileByFilename( $fileNames[1] );
336 unlink( $filePaths[0] );
337 }
338
339 /**
340 * @depends testLogin
341 */
342 public function testUploadStash( $session ) {
343 $this->setMwGlobals( array(
344 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere
345 ) );
346
347 $extension = 'png';
348 $mimeType = 'image/png';
349
350 try {
351 $randomImageGenerator = new RandomImageGenerator();
352 $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
353 } catch ( Exception $e ) {
354 $this->markTestIncomplete( $e->getMessage() );
355 }
356
357 /** @var array $filePaths */
358 $filePath = $filePaths[0];
359 $fileSize = filesize( $filePath );
360 $fileName = basename( $filePath );
361
362 $this->deleteFileByFileName( $fileName );
363 $this->deleteFileByContent( $filePath );
364
365 if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) {
366 $this->markTestIncomplete( "Couldn't upload file!\n" );
367 }
368
369 $params = array(
370 'action' => 'upload',
371 'stash' => 1,
372 'filename' => $fileName,
373 'file' => 'dummy content',
374 'comment' => 'dummy comment',
375 'text' => "This is the page text for $fileName",
376 );
377
378 $exception = false;
379 try {
380 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
381 self::$users['uploader']->user ); // FIXME: leaks a temporary file
382 } catch ( UsageException $e ) {
383 $exception = true;
384 }
385 $this->assertFalse( $exception );
386 $this->assertTrue( isset( $result['upload'] ) );
387 $this->assertEquals( 'Success', $result['upload']['result'] );
388 $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] );
389 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
390 $this->assertTrue( isset( $result['upload']['filekey'] ) );
391 $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] );
392 $filekey = $result['upload']['filekey'];
393
394 // it should be visible from Special:UploadStash
395 // XXX ...but how to test this, with a fake WebRequest with the session?
396
397 // now we should try to release the file from stash
398 $params = array(
399 'action' => 'upload',
400 'filekey' => $filekey,
401 'filename' => $fileName,
402 'comment' => 'dummy comment',
403 'text' => "This is the page text for $fileName, altered",
404 );
405
406 $this->clearFakeUploads();
407 $exception = false;
408 try {
409 list( $result ) = $this->doApiRequestWithToken( $params, $session,
410 self::$users['uploader']->user );
411 } catch ( UsageException $e ) {
412 $exception = true;
413 }
414 $this->assertTrue( isset( $result['upload'] ) );
415 $this->assertEquals( 'Success', $result['upload']['result'] );
416 $this->assertFalse( $exception, "No UsageException exception." );
417
418 // clean up
419 $this->deleteFileByFilename( $fileName );
420 unlink( $filePath );
421 }
422
423 /**
424 * @depends testLogin
425 */
426 public function testUploadChunks( $session ) {
427 $this->setMwGlobals( array(
428 // @todo FIXME: still used somewhere
429 'wgUser' => self::$users['uploader']->user,
430 ) );
431
432 $chunkSize = 1048576;
433 // Download a large image file
434 // ( using RandomImageGenerator for large files is not stable )
435 $mimeType = 'image/jpeg';
436 $url = 'http://upload.wikimedia.org/wikipedia/commons/'
437 . 'e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
438 $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg';
439 try {
440 // Only download if the file is not avaliable in the temp location:
441 if ( !is_file( $filePath ) ) {
442 copy( $url, $filePath );
443 }
444 } catch ( Exception $e ) {
445 $this->markTestIncomplete( $e->getMessage() );
446 }
447
448 $fileSize = filesize( $filePath );
449 $fileName = basename( $filePath );
450
451 $this->deleteFileByFileName( $fileName );
452 $this->deleteFileByContent( $filePath );
453
454 // Base upload params:
455 $params = array(
456 'action' => 'upload',
457 'stash' => 1,
458 'filename' => $fileName,
459 'filesize' => $fileSize,
460 'offset' => 0,
461 );
462
463 // Upload chunks
464 $chunkSessionKey = false;
465 $resultOffset = 0;
466 // Open the file:
467 wfSuppressWarnings();
468 $handle = fopen( $filePath, "r" );
469 wfRestoreWarnings();
470
471 if ( $handle === false ) {
472 $this->markTestIncomplete( "could not open file: $filePath" );
473 }
474
475 while ( !feof( $handle ) ) {
476 // Get the current chunk
477 wfSuppressWarnings();
478 $chunkData = fread( $handle, $chunkSize );
479 wfRestoreWarnings();
480
481 // Upload the current chunk into the $_FILE object:
482 $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData );
483
484 // Check for chunkSessionKey
485 if ( !$chunkSessionKey ) {
486 // Upload fist chunk ( and get the session key )
487 try {
488 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
489 self::$users['uploader']->user );
490 } catch ( UsageException $e ) {
491 $this->markTestIncomplete( $e->getMessage() );
492 }
493 // Make sure we got a valid chunk continue:
494 $this->assertTrue( isset( $result['upload'] ) );
495 $this->assertTrue( isset( $result['upload']['filekey'] ) );
496 // If we don't get a session key mark test incomplete.
497 if ( !isset( $result['upload']['filekey'] ) ) {
498 $this->markTestIncomplete( "no filekey provided" );
499 }
500 $chunkSessionKey = $result['upload']['filekey'];
501 $this->assertEquals( 'Continue', $result['upload']['result'] );
502 // First chunk should have chunkSize == offset
503 $this->assertEquals( $chunkSize, $result['upload']['offset'] );
504 $resultOffset = $result['upload']['offset'];
505 continue;
506 }
507 // Filekey set to chunk session
508 $params['filekey'] = $chunkSessionKey;
509 // Update the offset ( always add chunkSize for subquent chunks
510 // should be in-sync with $result['upload']['offset'] )
511 $params['offset'] += $chunkSize;
512 // Make sure param offset is insync with resultOffset:
513 $this->assertEquals( $resultOffset, $params['offset'] );
514 // Upload current chunk
515 try {
516 list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
517 self::$users['uploader']->user );
518 } catch ( UsageException $e ) {
519 $this->markTestIncomplete( $e->getMessage() );
520 }
521 // Make sure we got a valid chunk continue:
522 $this->assertTrue( isset( $result['upload'] ) );
523 $this->assertTrue( isset( $result['upload']['filekey'] ) );
524
525 // Check if we were on the last chunk:
526 if ( $params['offset'] + $chunkSize >= $fileSize ) {
527 $this->assertEquals( 'Success', $result['upload']['result'] );
528 break;
529 } else {
530 $this->assertEquals( 'Continue', $result['upload']['result'] );
531 // update $resultOffset
532 $resultOffset = $result['upload']['offset'];
533 }
534 }
535 fclose( $handle );
536
537 // Check that we got a valid file result:
538 wfDebug( __METHOD__
539 . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" );
540 $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] );
541 $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] );
542 $this->assertTrue( isset( $result['upload']['filekey'] ) );
543 $filekey = $result['upload']['filekey'];
544
545 // Now we should try to release the file from stash
546 $params = array(
547 'action' => 'upload',
548 'filekey' => $filekey,
549 'filename' => $fileName,
550 'comment' => 'dummy comment',
551 'text' => "This is the page text for $fileName, altered",
552 );
553 $this->clearFakeUploads();
554 $exception = false;
555 try {
556 list( $result ) = $this->doApiRequestWithToken( $params, $session,
557 self::$users['uploader']->user );
558 } catch ( UsageException $e ) {
559 $exception = true;
560 }
561 $this->assertTrue( isset( $result['upload'] ) );
562 $this->assertEquals( 'Success', $result['upload']['result'] );
563 $this->assertFalse( $exception );
564
565 // clean up
566 $this->deleteFileByFilename( $fileName );
567 // don't remove downloaded temporary file for fast subquent tests.
568 //unlink( $filePath );
569 }
570 }