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_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_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_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_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_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_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")