chromium/chrome/test/data/extensions/api_test/file_system_provider/write_file/test.js

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/**
 * Map from a file path to contents of the file.
 * @type {Object<string, string>}
 */
var fileContents = {};

/**
 * Initial contents of testing files.
 * @type {string}
 * @const
 */
var TESTING_INITIAL_TEXT = 'Hello world. How are you today?';

/**
 * Initial contents of testing files.
 * @type {string}
 * @const
 */
var TESTING_TEXT_TO_WRITE = 'Vanilla ice creams are the best.';

/**
 * @type {string}
 * @const
 */
var TESTING_NEW_FILE_NAME = 'perfume.txt';

/**
 * @type {string}
 * @const
 */
var TESTING_TIRAMISU_FILE_NAME = 'tiramisu.txt';

/**
 * @type {string}
 * @const
 */
var TESTING_BROKEN_TIRAMISU_FILE_NAME = 'broken-tiramisu.txt';

/**
 * @type {string}
 * @const
 */
var TESTING_CHOCOLATE_FILE_NAME = 'chocolate.txt';

/**
 * List of callbacks to be called when a file write is requested.
 * @type {Array<function(string)>}
 */
var writeFileRequestedCallbacks = [];

/**
 * Requests writing contents to a file, previously opened with <code>
 * openRequestId</code>.
 *
 * @param {ReadFileRequestedOptions} options Options.
 * @param {function()} onSuccess Success callback.
 * @param {function(string)} onError Error callback.
 */
function onWriteFileRequested(options, onSuccess, onError) {
  var filePath = test_util.openedFiles[options.openRequestId];
  writeFileRequestedCallbacks.forEach(function(callback) {
    callback(filePath);
  });

  if (options.fileSystemId !== test_util.FILE_SYSTEM_ID || !filePath) {
    onError('SECURITY');  // enum ProviderError.
    return;
  }

  if (!(filePath in test_util.defaultMetadata)) {
    onError('INVALID_OPERATION');  // enum ProviderError.
    return;
  }

  var metadata = test_util.defaultMetadata[filePath];

  if (filePath === '/' + TESTING_BROKEN_TIRAMISU_FILE_NAME) {
    onError('FAILED');
    return;
  }

  if (filePath === '/' + TESTING_CHOCOLATE_FILE_NAME) {
    // Do not call any callback to simulate a very slow network connection.
    return;
  }

  // Writing beyond the end of the file.
  if (options.offset > metadata.size) {
    onError('INVALID_OPERATION');
    return;
  }

  // Create an array with enough space for new data.
  const oldArray = new TextEncoder().encode(fileContents[filePath] || '');
  const newLength =
      Math.max(oldArray.length, options.offset + options.data.byteLength);
  const newArray = new Uint8Array(new ArrayBuffer(newLength));
  // Write existing data and new data.
  newArray.set(oldArray, 0);
  newArray.set(new Uint8Array(options.data), options.offset);
  // Save the new file as text.
  const newContents = new TextDecoder().decode(newArray);
  fileContents[filePath] = newContents;
  metadata.size = newContents.length;
  onSuccess();
}

/**
 * Sets up the tests. Called once per all test cases. In case of a failure,
 * the callback is not called.
 *
 * @param {function()} callback Success callback.
 */
function setUp(callback) {
  chrome.fileSystemProvider.onGetMetadataRequested.addListener(
      test_util.onGetMetadataRequestedDefault);
  chrome.fileSystemProvider.onOpenFileRequested.addListener(
      test_util.onOpenFileRequested);
  chrome.fileSystemProvider.onCloseFileRequested.addListener(
      test_util.onCloseFileRequested);
  chrome.fileSystemProvider.onCreateFileRequested.addListener(
      test_util.onCreateFileRequested);

  test_util.defaultMetadata['/' + TESTING_TIRAMISU_FILE_NAME] = {
    isDirectory: false,
    name: TESTING_TIRAMISU_FILE_NAME,
    size: TESTING_INITIAL_TEXT.length,
    modificationTime: new Date(2014, 1, 24, 6, 35, 11)
  };

  test_util.defaultMetadata['/' + TESTING_BROKEN_TIRAMISU_FILE_NAME] = {
    isDirectory: false,
    name: TESTING_BROKEN_TIRAMISU_FILE_NAME,
    size: TESTING_INITIAL_TEXT.length,
    modificationTime: new Date(2014, 1, 25, 7, 36, 12)
  };

  test_util.defaultMetadata['/' + TESTING_CHOCOLATE_FILE_NAME] = {
    isDirectory: false,
    name: TESTING_CHOCOLATE_FILE_NAME,
    size: TESTING_INITIAL_TEXT.length,
    modificationTime: new Date(2014, 1, 26, 8, 37, 13)
  };

  fileContents['/' + TESTING_TIRAMISU_FILE_NAME] = TESTING_INITIAL_TEXT;
  fileContents['/' + TESTING_BROKEN_TIRAMISU_FILE_NAME] = TESTING_INITIAL_TEXT;
  fileContents['/' + TESTING_CHOCOLATE_FILE_NAME] = TESTING_INITIAL_TEXT;

  chrome.fileSystemProvider.onWriteFileRequested.addListener(
      onWriteFileRequested);

  test_util.mountFileSystem(callback);
}

/**
 * Runs all of the test cases, one by one.
 */
function runTests() {
  chrome.test.runTests([
    // Write contents to a non-existing file. It should succeed.
    function writeNewFileSuccess() {
      test_util.fileSystem.root.getFile(
          TESTING_NEW_FILE_NAME,
          {create: true, exclusive: true},
          chrome.test.callbackPass(function(fileEntry) {
            fileEntry.createWriter(
                chrome.test.callbackPass(function(fileWriter) {
                  fileWriter.onwriteend = chrome.test.callbackPass(function(e) {
                    // Note that onwriteend() is called even if an error
                    // happened.
                    if (fileWriter.error)
                      return;
                    chrome.test.assertEq(
                        TESTING_TEXT_TO_WRITE,
                        fileContents['/' + TESTING_NEW_FILE_NAME]);
                  });
                  fileWriter.onerror = function(e) {
                    chrome.test.fail(fileWriter.error.name);
                  };
                  var blob = new Blob(
                      [TESTING_TEXT_TO_WRITE], {type: 'text/plain'});
                  fileWriter.write(blob);
                }),
                function(error) {
                  chrome.test.fail(error.name);
                });
          }),
          function(error) {
            chrome.test.fail(error.name);
          });
    },

    // Overwrite contents in an existing file. It should succeed.
    function overwriteFileSuccess() {
      test_util.fileSystem.root.getFile(
          TESTING_TIRAMISU_FILE_NAME,
          {create: true, exclusive: false},
          chrome.test.callbackPass(function(fileEntry) {
            fileEntry.createWriter(
                chrome.test.callbackPass(function(fileWriter) {
                fileWriter.onwriteend = chrome.test.callbackPass(function(e) {
                  if (fileWriter.error)
                    return;
                  chrome.test.assertEq(
                      TESTING_TEXT_TO_WRITE,
                      fileContents['/' + TESTING_TIRAMISU_FILE_NAME]);
                });
                fileWriter.onerror = function(e) {
                  chrome.test.fail(fileWriter.error.name);
                };
                var blob = new Blob(
                    [TESTING_TEXT_TO_WRITE], {type: 'text/plain'});
                fileWriter.write(blob);
              }),
              function(error) {
                chrome.test.fail(error.name);
              });
          }),
          function(error) {
            chrome.test.fail(error.name);
          });
    },

    // Append contents to an existing file. It should succeed.
    function appendFileSuccess() {
      var onTestSuccess = chrome.test.callbackPass();
      test_util.fileSystem.root.getFile(
          TESTING_TIRAMISU_FILE_NAME,
          {create: false, exclusive: false},
          function(fileEntry) {
            fileEntry.createWriter(function(fileWriter) {
              fileWriter.seek(TESTING_TEXT_TO_WRITE.length);
              fileWriter.onwriteend = function(e) {
                if (fileWriter.error)
                  return;
                chrome.test.assertEq(
                    TESTING_TEXT_TO_WRITE + TESTING_TEXT_TO_WRITE,
                    fileContents['/' + TESTING_TIRAMISU_FILE_NAME]);
                onTestSuccess();
              };
              fileWriter.onerror = function(e) {
                chrome.test.fail(fileWriter.error.name);
              };
              var blob = new Blob(
                  [TESTING_TEXT_TO_WRITE], {type: 'text/plain'});
              fileWriter.write(blob);
            },
            function(error) {
              chrome.test.fail(error.name);
            });
          },
          function(error) {
            chrome.test.fail(error.name);
          });
    },

    // Replace contents in an existing file. It should succeed.
    function replaceFileSuccess() {
      var onTestSuccess = chrome.test.callbackPass();
      test_util.fileSystem.root.getFile(
          TESTING_TIRAMISU_FILE_NAME,
          {create: false, exclusive: false},
          function(fileEntry) {
            fileEntry.createWriter(function(fileWriter) {
              fileWriter.seek(TESTING_TEXT_TO_WRITE.indexOf('creams'));
              fileWriter.onwriteend = function(e) {
                if (fileWriter.error)
                  return;
                var expectedContents = TESTING_TEXT_TO_WRITE.replace(
                    'creams', 'skates') + TESTING_TEXT_TO_WRITE;
                chrome.test.assertEq(
                    expectedContents,
                    fileContents['/' + TESTING_TIRAMISU_FILE_NAME]);
                onTestSuccess();
              };
              fileWriter.onerror = function(e) {
                chrome.test.fail(fileWriter.error.name);
              };
              var blob = new Blob(['skates'], {type: 'text/plain'});
              fileWriter.write(blob);
            },
            function(error) {
              chrome.test.fail(error.name);
            });
          },
          function(error) {
            chrome.test.fail(error.name);
          });
    },

    // Write bytes to a broken file. This should result in an error.
    function writeBrokenFileError() {
      var onTestSuccess = chrome.test.callbackPass();
      test_util.fileSystem.root.getFile(
          TESTING_BROKEN_TIRAMISU_FILE_NAME,
          {create: false, exclusive: false},
          function(fileEntry) {
            fileEntry.createWriter(function(fileWriter) {
              fileWriter.onwriteend = function(e) {
                if (fileWriter.error)
                  return;
                chrome.test.fail(
                    'Unexpectedly succeeded to write to a broken file.');
              };
              fileWriter.onerror = function(e) {
                chrome.test.assertEq(
                    'InvalidStateError', fileWriter.error.name);
                onTestSuccess();
              };
              var blob = new Blob(['A lot of flowers.'], {type: 'text/plain'});
              fileWriter.write(blob);
            },
            function(error) {
              chrome.test.fail();
            });
          },
          function(error) {
            chrome.test.fail(error.name);
          });
    },

    // Abort writing to a valid file with a registered abort handler. Should
    // result in a gracefully terminated writing operation.
    function abortWritingSuccess() {
      var onTestSuccess = chrome.test.callbackPass();

      var onAbortRequested = function(options, onSuccess, onError) {
        chrome.fileSystemProvider.onAbortRequested.removeListener(
            onAbortRequested);
        onSuccess();
        onTestSuccess();
      };

      chrome.fileSystemProvider.onAbortRequested.addListener(
          onAbortRequested);

      test_util.fileSystem.root.getFile(
          TESTING_CHOCOLATE_FILE_NAME,
          {create: false, exclusive: false},
          function(fileEntry) {
            fileEntry.createWriter(function(fileWriter) {
              fileWriter.onwriteend = function(e) {
                if (fileWriter.error) {
                  chrome.test.assertEq(
                      'AbortError', fileWriter.error.name);
                } else {
                  chrome.test.fail(
                      'Unexpectedly succeeded writing, despite aborting.');
                }
              };
              writeFileRequestedCallbacks.push(
                  function(filePath) {
                    // Abort the operation after it's started.
                    if (filePath === '/' + TESTING_CHOCOLATE_FILE_NAME)
                      fileWriter.abort();
                  });
              var blob = new Blob(['A lot of cherries.'], {type: 'text/plain'});
              fileWriter.write(blob);
            },
            function(error) {
              chrome.test.fail();
            });
          },
          function(error) {
            chrome.test.fail(error.name);
          });
    }
  ]);
}

// Setup and run all of the test cases.
setUp(runTests);