summaryrefslogtreecommitdiff
path: root/modules/file/tests/file.test
blob: 8c4a75b0a04ddd8892e563d3145c4dc33a801708 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
<?php
// $Id$

/**
 * @file
 * Tests file uploading through the File module.
 */

/**
 * This class provides methods specifically for testing File's field handling.
 */
class FileFieldTestCase extends DrupalWebTestCase {
  protected $admin_user;

  function setUp() {
    parent::setUp('file');
    $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content', 'administer files'));
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Get a sample file of the specified type.
   */
  function getTestFile($type_name, $size = NULL) {
    // Get a file to upload.
    $file = current($this->drupalGetTestFiles($type_name, $size));

    // Add a filesize property to files as would be read by file_load().
    $file->filesize = filesize($file->uri);

    return $file;
  }

  /**
   * Create a new file field.
   *
   * @param $name
   *   The name of the new field (all lowercase), exclude the "field_" prefix.
   * @param $type_name
   *   The node type that this field will be added to.
   * @param $field_settings
   *   A list of field settings that will be added to the defaults.
   * @param $instance_settings
   *   A list of instance settings that will be added to the instance defaults.
   * @param $widget_settings
   *   A list of widget settings that will be added to the widget defaults.
   */
  function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) {
    $field = array(
      'field_name' => $name,
      'type' => 'file',
      'settings' => array(),
      'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1,
    );
    $field['settings'] = array_merge($field['settings'], $field_settings);
    field_create_field($field);

    $instance = array(
      'field_name' => $field['field_name'],
      'object_type' => 'node',
      'label' => $name,
      'bundle' => $type_name,
      'required' => !empty($instance_settings['required']),
      'settings' => array(),
      'widget' => array(
        'type' => 'file_generic',
        'settings' => array(),
      ),
    );
    $instance['settings'] = array_merge($instance['settings'], $instance_settings);
    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);
    field_create_instance($instance);
  }

  /**
   * Update an existing file field with new settings.
   */
  function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) {
    $field = field_info_field($name);
    $instance = field_info_instance('node', $name, $type_name);
    $instance['settings'] = array_merge($instance['settings'], $instance_settings);
    $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings);

    field_update_instance($instance);
  }

  /**
   * Upload a file to a node.
   */
  function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE) {
    $langcode = FIELD_LANGUAGE_NONE;
    $edit = array(
      "title[$langcode][0][value]" => $this->randomName(),
      'revision' => (string) (int) $new_revision,
    );

    if (is_numeric($nid_or_type)) {
      $node = node_load($nid_or_type);
      $delta = isset($node->$field_name) ? count($node->$field_name) : 0;
      $edit['files[' . $field_name . '_' . FIELD_LANGUAGE_NONE . '_' . $delta . ']'] = realpath($file->uri);
      $this->drupalPost('node/' . $nid_or_type . '/edit', $edit, t('Save'));
    }
    else {
      $edit['files[' . $field_name . '_' . FIELD_LANGUAGE_NONE . '_0]'] = realpath($file->uri);
      $type_name = str_replace('_', '-', $nid_or_type);
      $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    }

    $matches = array();
    preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
    return isset($matches[1]) ? $matches[1] : FALSE;
  }

  /**
   * Remove a file from a node.
   *
   * Note that if replacing a file, it must first be removed then added again.
   */
  function removeNodeFile($nid, $new_revision = TRUE) {
    $edit = array(
      'revision' => (string) (int) $new_revision,
    );

    $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
    $this->drupalPost(NULL, $edit, t('Save'));
  }

  /**
   * Replace a file within a node.
   */
  function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
    $edit = array(
      'files[' . $field_name . '_' . FIELD_LANGUAGE_NONE . '_0]' => realpath($file->uri),
      'revision' => (string) (int) $new_revision,
    );

    $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
    $this->drupalPost(NULL, $edit, t('Save'));
  }

  /**
   * Assert that a file exists physically on disk.
   */
  function assertFileExists($file, $message = NULL) {
    $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri));
    $this->assertTrue(is_file($file->uri), $message);
  }

  /**
   * Assert that a file exists in the database.
   */
  function assertFileEntryExists($file, $message = NULL) {
    drupal_static_reset('file_load_multiple');
    $db_file = file_load($file->fid);
    $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
    $this->assertEqual($db_file->uri, $file->uri, $message);
  }

  /**
   * Assert that a file does not exist on disk.
   */
  function assertFileNotExists($file, $message = NULL) {
    $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri));
    $this->assertFalse(is_file($file->uri), $message);
  }

  /**
   * Assert that a file does not exist in the database.
   */
  function assertFileEntryNotExists($file, $message) {
    drupal_static_reset('file_load_multiple');
    $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri));
    $this->assertFalse(file_load($file->fid), $message);
  }
}

/**
 * Test class to test file handling with node revisions.
 */
class FileFieldRevisionTestCase extends FileFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'File field revision test',
      'description' => 'Test creating and deleting revisions with files attached.',
      'group' => 'File',
    );
  }

  /**
   * Test creating multiple revisions of a node and managing the attached files.
   *
   * Expected behaviors:
   *  - Adding a new revision will make another entry in the field table, but
   *    the original file will not be duplicated.
   *  - Deleting a revision should not delete the original file if the file
   *    is in use by another revision.
   *  - When the last revision that uses a file is deleted, the original file
   *    should be deleted also.
   */
  function testRevisions() {
    $type_name = 'article';
    $field_name = 'field_' . strtolower($this->randomName());
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $test_file = $this->getTestFile('text');

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file exists on disk and in the database.
    $node = node_load($nid, NULL, TRUE);
    $node_file_r1 = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $node_vid_r1 = $node->vid;
    $this->assertFileExists($node_file_r1, t('New file saved to disk on node creation.'));
    $this->assertFileEntryExists($node_file_r1, t('File entry exists in database on node creation.'));

    // Upload another file to the same node in a new revision.
    $this->replaceNodeFile($test_file, $field_name, $nid);
    $node = node_load($nid, NULL, TRUE);
    $node_file_r2 = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $node_vid_r2 = $node->vid;
    $this->assertFileExists($node_file_r2, t('Replacement file exists on disk after creating new revision.'));
    $this->assertFileEntryExists($node_file_r2, t('Replacement file entry exists in database after creating new revision.'));

    // Check that the original file is still in place on the first revision.
    $node = node_load($nid, $node_vid_r1, TRUE);
    $this->assertEqual($node_file_r1, (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0], t('Original file still in place after replacing file in new revision.'));
    $this->assertFileExists($node_file_r1, t('Original file still in place after replacing file in new revision.'));
    $this->assertFileEntryExists($node_file_r1, t('Original file entry still in place after replacing file in new revision'));

    // Save a new version of the node without any changes.
    // Check that the file is still the same as the previous revision.
    $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save'));
    $node = node_load($nid, NULL, TRUE);
    $node_file_r3 = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $node_vid_r3 = $node->vid;
    $this->assertEqual($node_file_r2, $node_file_r3, t('Previous revision file still in place after creating a new revision without a new file.'));

    // Revert to the first revision and check that the original file is active.
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert'));
    $node = node_load($nid, NULL, TRUE);
    $node_file_r4 = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $node_vid_r4 = $node->vid;
    $this->assertEqual($node_file_r1, $node_file_r4, t('Original revision file still in place after reverting to the original revision.'));

    // Delete the second revision and check that the file is kept (since it is
    // still being used by the third revision).
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete'));
    $this->assertFileExists($node_file_r3, t('Second file is still available after deleting second revision, since it is being used by the third revision.'));
    $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.'));

    // Delete the third revision and check that the file is deleted also.
    $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
    // TODO: This seems like a bug in File API. Clearing the stat cache should
    // not be necessary here. The file really is deleted, but stream wrappers
    // doesn't seem to think so unless we clear the PHP file stat() cache.
    clearstatcache();
    $this->assertFileNotExists($node_file_r3, t('Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));
    $this->assertFileEntryNotExists($node_file_r3, t('Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));

    // Delete the entire node and check that the original file is deleted.
    $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete'));
    $this->assertFileNotExists($node_file_r1, t('Original file is deleted after deleting the entire node with two revisions remaining.'));
    $this->assertFileEntryNotExists($node_file_r1, t('Original file entry is deleted after deleting the entire node with two revisions remaining.'));
  }
}

/**
 * Test class to check that formatters are working properly.
 */
class FileFieldDisplayTestCase extends FileFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'File field display tests',
      'description' => 'Test the display of file fields in node and views.',
      'group' => 'File',
    );
  }

  /**
   * Test normal formatter display on node display.
   */
  function testNodeDisplay() {
    $field_name = 'field_' . strtolower($this->randomName());
    $type_name = 'article';
    $field_settings = array(
      'display_field' => '1',
      'display_default' => '1',
    );
    $instance_settings = array(
      'description_field' => '1',
    );
    $widget_settings = array();
    $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings);
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $test_file = $this->getTestFile('text');

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $this->drupalGet('node/' . $nid . '/edit');

    // Check that the default formatter is displaying with the file name.
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $default_output = theme('file_link', array('file' => $node_file));
    $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.'));

    // Turn the "display" option off and check that the file is no longer displayed.
    $edit = array($field_name . '[' . FIELD_LANGUAGE_NONE . '][0][display]' => FALSE);
    $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));

    $this->assertNoRaw($default_output, t('Field is hidden when "display" option is unchecked.'));

  }
}

/**
 * Test class to check for various validations.
 */
class FileFieldValidateTestCase extends FileFieldTestCase {
  protected $field;
  protected $node_type;

  public static function getInfo() {
    return array(
      'name' => 'File field validation tests',
      'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.',
      'group' => 'File',
    );
  }

  /**
   * Test required property on file fields.
   */
  function testRequired() {
    $type_name = 'article';
    $field_name = 'field_' . strtolower($this->randomName());
    $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $test_file = $this->getTestFile('text');

    // Try to post a new node without uploading a file.
    $langcode = FIELD_LANGUAGE_NONE;
    $edit = array("title[$langcode][0][value]" => $this->randomName());
    $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required file field was empty.'));

    // Create a new node with the uploaded file.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);

    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertFileExists($node_file, t('File exists after uploading to the required field.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required field.'));

    // Try again with a multiple value field.
    field_delete_field($field_name);
    $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1'));

    // Try to post a new node without uploading a file in the multivalue field.
    $edit = array("title[$langcode][0][value]" => $this->randomName());
    $this->drupalPost('node/add/' . $type_name, $edit, t('Save'));
    $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required multiple value file field was empty.'));

    // Create a new node with the uploaded file into the multivalue field.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertFileExists($node_file, t('File exists after uploading to the required multiple value field.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required multipel value field.'));

    // Remove our file field.
    field_delete_field($field_name);
  }

  /**
   * Test the max file size validator.
   */
  function testFileMaxSize() {
    $type_name = 'article';
    $field_name = 'field_' . strtolower($this->randomName());
    $this->createFileField($field_name, $type_name, array(), array('required' => '1'));
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    $small_file = $this->getTestFile('text', 131072); // 128KB.
    $large_file = $this->getTestFile('text', 1310720); // 1.2MB

    // Test uploading both a large and small file with different increments.
    $sizes = array(
      '1M' => 1048576,
      '1024K' => 1048576,
      '1048576' => 1048576,
    );

    foreach ($sizes as $max_filesize => $file_limit) {
      // Set the max file upload size.
      $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize));
      $instance = field_info_instance('node', $field_name, $type_name);

      // Create a new node with the small file, which should pass.
      $nid = $this->uploadNodeFile($small_file, $field_name, $type_name);
      $node = node_load($nid, NULL, TRUE);
      $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
      $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));
      $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize)));

      // Check that uploading the large file fails (1M limit).
      $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
      $error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit)));
      $this->assertRaw($error_message, t('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize)));
    }

    // Turn off the max filesize.
    $this->updateFileField($field_name, $type_name, array('max_filesize' => ''));

    // Upload the big file successfully.
    $nid = $this->uploadNodeFile($large_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));

    // Remove our file field.
    field_delete_field($field_name);
  }

  /**
   * Test the file extension, do additional checks if mimedetect is installed.
   */
  function testFileExtension() {
    $type_name = 'article';
    $field_name = 'field_' . strtolower($this->randomName());
    $this->createFileField($field_name, $type_name);
    $field = field_info_field($field_name);
    $instance = field_info_instance('node', $field_name, $type_name);

    // Get the test file (a GIF image).
    $test_file = $this->getTestFile('image');

    // Disable extension checking.
    $this->updateFileField($field_name, $type_name, array('file_extensions' => ''));

    // Check that the file can be uploaded with no extension checking.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertFileExists($node_file, t('File exists after uploading a file with no extension checking.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with no extension checking.'));

    // Enable extension checking for text files.
    $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt'));

    // Check that the file with the wrong extension cannot be uploaded.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt'));
    $this->assertRaw($error_message, t('Node save failed when file uploaded with the wrong extension.'));

    // Enable extension checking for text and image files.
    $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt gif'));

    // Check that the file can be uploaded with extension checking.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertFileExists($node_file, t('File exists after uploading a file with extension checking.'));
    $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with extension checking.'));

    // Remove our file field.
    field_delete_field($field_name);
  }
}

/**
 * Test class to check that files are uploaded to proper locations.
 */
class FileFieldPathTestCase extends FileFieldTestCase {
  public static function getInfo() {
    return array(
      'name' => 'File field file path tests',
      'description' => 'Test that files are uploaded to the proper location with token support.',
      'group' => 'File',
    );
  }

  /**
   * Test normal formatter display on node display.
   */
  function testUploadPath() {
    $field_name = 'field_' . strtolower($this->randomName());
    $type_name = 'article';
    $field = $this->createFileField($field_name, $type_name);
    $test_file = $this->getTestFile('text');

    // Create a new node.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded to the file root.
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));

    // Change the path to contain multiple subdirectories.
    $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz'));

    // Upload a new file into the subdirectories.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded into the subdirectory.
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri)));

    // Check the path when used with tokens.
    // Change the path to contain multiple token directories.
    $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[user:uid]/[user:name]'));

    // Upload a new file into the token subdirectories.
    $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);

    // Check that the file was uploaded into the subdirectory.
    $node = node_load($nid, NULL, TRUE);
    $node_file = (object) $node->{$field_name}[FIELD_LANGUAGE_NONE][0];
    $data = array('user' => $this->admin_user);
    $subdirectory = token_replace('[user:uid]/[user:name]', $data);
    $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri)));
  }

  /**
   * A loose assertion to check that a file is uploaded to the right location.
   *  
   * @param $expected_path
   *   The location where the file is expected to be uploaded. Duplicate file
   *   names to not need to be taken into account.
   * @param $actual_path
   *   Where the file was actually uploaded.
   * @param $message
   *   The message to display with this assertion.
   */
  function assertPathMatch($expected_path, $actual_path, $message) {
    // Strip off the extension of the expected path to allow for _0, _1, etc.
    // suffixes when the file hits a duplicate name.
    $pos = strrpos($expected_path, '.');
    $base_path = substr($expected_path, 0, $pos);
    $extension = substr($expected_path, $pos + 1);

    $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
    $this->assertTrue($result, $message);
  }
}