def test_makedirs_race_handling(tmpdir, monkeypatch):
    """
    File storage should be robust against directory creation race conditions.
    """
    real_makedirs = os.makedirs

    # Monkey-patch os.makedirs, to simulate a normal call, a raced call,
    # and an error.
    def fake_makedirs(path):
        if path == os.path.join(str(tmpdir), "normal"):
            real_makedirs(path)
        elif path == os.path.join(str(tmpdir), "raced"):
            real_makedirs(path)
            raise OSError(errno.EEXIST, "simulated EEXIST")
        elif path == os.path.join(str(tmpdir), "error"):
            raise OSError(errno.EACCES, "simulated EACCES")
        else:
            pytest.fail("unexpected argument %r" % path)

    monkeypatch.setattr(os, "makedirs", fake_makedirs)

    storage = FileSystemStorage(location=str(tmpdir))

    storage.save("normal/test.file", io.StringIO("saved normally"))
    assert storage.open("normal/test.file").read() == "saved normally"

    storage.save("raced/test.file", io.StringIO("saved with race"))
    assert storage.open("raced/test.file").read() == "saved with race"

    # Check that OSErrors aside from EEXIST are still raised.
    with pytest.raises(OSError):
        storage.save("error/test.file", io.StringIO("not saved"))
def test_file_storage_preserves_filename_case(tmpdir):
    """
    The storage backend should preserve case of filenames.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    f = storage.open("CaSe_SeNsItIvE", "w")
    f.write("storage contents")
    f.close()

    assert os.path.join(str(tmpdir), "CaSe_SeNsItIvE") == storage.path("CaSe_SeNsItIvE")
def test_file_storage_prevents_directory_traversal(tmpdir):
    """
    File storage prevents directory traversal (files can only be accessed if
    they're below the storage location).
    """
    storage = FileSystemStorage(location=str(tmpdir))

    with pytest.raises(SuspiciousOperation):
        storage.exists("..")

    with pytest.raises(SuspiciousOperation):
        storage.exists("/etc/passwd")
def test_file_path(tmpdir):
    """
    File storage returns the full path of a file
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("test.file")

    f = io.StringIO("custom contents")
    f_name = storage.save("test.file", f)

    assert storage.path(f_name) == os.path.join(str(tmpdir), f_name)
def test_file_save_without_name(tmpdir):
    """
    File storage extracts the filename from the content object if no
    name is given explicitly.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("test.file")

    class NamedStringIO(io.StringIO):
        name = "test.file"

    f = NamedStringIO("custom contents")

    storage_f_name = storage.save(None, f)

    assert storage_f_name == f.name
    assert os.path.exists(os.path.join(str(tmpdir), f.name))
def test_file_access_options(tmpdir):
    """
    Standard file access options are available, and work as expected.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("storage_test")

    f = storage.open("storage_test", "w")
    f.write("storage contents")
    f.close()

    assert storage.exists("storage_test")

    f = storage.open("storage_test", "r")
    assert f.read() == "storage contents"
    f.close()

    storage.delete("storage_test")
    assert not storage.exists("storage_test")
def test_file_save_with_path(tmpdir):
    """
    Saving a pathname should create intermediate directories as necessary.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("path/to")

    storage.save("path/to/test.file", io.StringIO("file saved with path"))

    assert storage.exists("path/to")
    assert storage.open("path/to/test.file").read() == "file saved with path"
    assert os.path.exists(os.path.join(str(tmpdir), "path", "to", "test.file"))
def test_file_modified_time(tmpdir):
    """
    File storage returns a Datetime object for the last modified time of
    a file.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("test.file")

    f = io.StringIO("custom contents")
    f_name = storage.save("test.file", f)
    mtime = storage.modified_time(f_name)

    assert mtime == datetime.datetime.fromtimestamp(os.path.getmtime(storage.path(f_name)))
    assert datetime.datetime.now() - storage.modified_time(f_name) < datetime.timedelta(seconds=2)
def test_file_uri(tmpdir):
    """
    File storage returns a url to access a given file from the Web.
    """
    storage = FileSystemStorage(location=str(tmpdir), base_uri="/test_media_url/")

    assert storage.uri("test.file") == "".join([storage.base_uri, "test.file"])

    # should encode special chars except ~!*()'
    # like encodeURIComponent() JavaScript function do
    assert storage.uri(r"""~!*()'@#$%^&*abc`+ =.file""") == """/test_media_url/~!*()'%40%23%24%25%5E%26*abc%60%2B%20%3D.file"""

    # should translate os path separator(s) to the url path separator
    assert storage.uri("""a/b\\c.file""") == """/test_media_url/a/b/c.file"""

    storage.base_uri = None

    with pytest.raises(ValueError):
        storage.uri("test.file")
def test_listdir(tmpdir):
    """
    File storage returns a tuple containing directories and files.
    """
    storage = FileSystemStorage(location=str(tmpdir))

    assert not storage.exists("storage_test_1")
    assert not storage.exists("storage_test_2")
    assert not storage.exists("storage_dir_1")

    storage.save("storage_test_1", io.StringIO("custom content"))
    storage.save("storage_test_2", io.StringIO("custom content"))

    os.mkdir(os.path.join(str(tmpdir), 'storage_dir_1'))

    dirs, files = storage.listdir("")

    assert set(dirs) == set(["storage_dir_1"])
    assert set(files) == set(["storage_test_1", "storage_test_2"])
def test_remove_race_handling(tmpdir, monkeypatch):
    """
    File storage should be robust against file removal race conditions.
    """
    real_remove = os.remove

    # Monkey-patch os.remove, to simulate a normal call, a raced call,
    # and an error.
    def fake_remove(path):
        if path == os.path.join(str(tmpdir), "normal.file"):
            real_remove(path)
        elif path == os.path.join(str(tmpdir), "raced.file"):
            real_remove(path)
            raise OSError(errno.ENOENT, "simulated ENOENT")
        elif path == os.path.join(str(tmpdir), "error.file"):
            raise OSError(errno.EACCES, "simulated EACCES")
        else:
            pytest.fail("unexpected argument %r" % path)

    monkeypatch.setattr(os, "remove", fake_remove)

    storage = FileSystemStorage(location=str(tmpdir))

    storage.save("normal.file", io.StringIO("delete normally"))
    storage.delete("normal.file")

    assert not storage.exists("normal.file")

    storage.save("raced.file", io.StringIO("delete with race"))
    storage.delete("raced.file")

    assert not storage.exists("normal.file")

    # Check that OSErrors aside from ENOENT are still raised.
    storage.save("error.file", io.StringIO("delete with error"))

    with pytest.raises(OSError):
        storage.delete("error.file")