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