python-3.xdjango-rest-frameworkpytestpytest-django

Test fails when executing in order


I created a model in django to upload a file, it should save it in the media storage to be used by celery script. It should create a directory with the id, and each time I upload the same file, it should be deleted first (I don't know why I cant overwrite it, but it's enough if It delete it first). And when the entity is deleted, the directory should be deleted.

I'm working with python3.12, django 4.2, and django rest framework3.14.

The code works:

def upload_to(instance, filename):
    instance_id = instance.id or uuid.uuid4().hex
    directory = f'sample_{instance_id}'
    file_path = os.path.join(directory, filename)

    full_path = os.path.join(settings.MEDIA_ROOT, file_path)
    os.makedirs(os.path.dirname(full_path), exist_ok=True)

    return file_path


class Sample(models.Model):
    name = models.CharField(max_length=30)
    test_suite = models.FileField(upload_to=upload_to)

    def delete(self, *args, **kwargs):
        if self.test_suite:
            if os.path.isfile(self.test_suite.path):
                os.remove(self.test_suite.path)
        super().delete(*args, **kwargs)

    def save(self, *args, **kwargs):
        if self.pk is None:
            saved_test_suite = self.test_suite
            self.test_suite = None
            super().save(*args, **kwargs)
            self.test_suite = saved_test_suite
            if 'force_insert' in kwargs:
                kwargs.pop('force_insert')
        elif self.pk:
            try:
                old_instance = Sample.objects.get(pk=self.pk)
                if (
                    old_instance.test_suite
                    and self.test_suite
                    and old_instance.test_suite != self.test_suite
                ):
                    if os.path.isfile(old_instance.test_suite.path):
                        os.remove(old_instance.test_suite.path)
            except Sample.DoesNotExist:
                pass

        if 'uuid' in self.test_suite.name:
            new_path = self.test_suite.name.replace(
                self.test_suite.name.split('/')[0],
                f'dir_{self.pk}'
            )
            old_path = self.test_suite.path
            new_full_path = os.path.join(settings.MEDIA_ROOT, new_path)
            os.renames(old_path, new_full_path)
            self.test_suite.name = new_path
            super().save(update_fields=['test_suite'])

        super().save(*args, **kwargs)

these are the tests:

@pytest.fixture
def create_test_file():
    def _create_test_file(filename: str) -> SimpleUploadedFile:
        return SimpleUploadedFile(filename, b'test file content')
    return _create_test_file


@pytest.mark.django_db
def test_sample_creation(tmp_path, create_test_file):
    settings.MEDIA_ROOT = tmp_path

    sample = Sample.objects.create(
        name='test_sample_name',
        test_suite=create_test_file(filename='test_sample.sample'),
    )

    expected_directory = tmp_path / f'sample_{sample.pk}'
    expected_file_path = expected_directory / 'test_sample.sample'
    assert expected_directory.is_dir()
    assert expected_file_path.is_file()
    assert sample.name == 'test_sample_name'
    assert sample.test_suite.name.endswith('test_sample.sample')
    assert f'sample_{sample.pk}' in sample.test_suite.name


@pytest.mark.django_db
def test_sample_update(tmp_path, create_test_file):
    settings.MEDIA_ROOT = tmp_path

    sample = Sample.objects.create(
        name='test_sample_name',
        test_suite=create_test_file('test_sample.sample'),
    )

    new_file = create_test_file(filename='updated_test_sample.sample')
    sample.test_suite = new_file
    sample.save()
    sample.refresh_from_db()

    expected_directory = tmp_path / f'sample_{sample.pk}'
    assert expected_directory.is_dir()
    expected_file_path = expected_directory / 'updated_test_sample.sample'
    # import pdb; pdb.set_trace()
    assert expected_file_path.is_file()
    assert sample.test_suite.name == f'sample_{sample.pk}/updated_test_sample.sample'
    old_file_path = expected_directory / 'test_sample.sample'
    assert not old_file_path.exists()

And the error is:

>       assert expected_file_path.is_file()
E       AssertionError: assert False
E        +  where False = <bound method Path.is_file of PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample')>()
E        +    where <bound method Path.is_file of PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample')> = PosixPath('/tmp/pytest-of-usuername/pytest-84/test_sample_update0/sample_1/updated_test_sample.sample').is_file

The most strange part is, if I execute the 'test_sample_update' alone, works, and if I change 'test_sample_creation' with 'test_sample_update', the one that fails is 'test_sample_creation'. So it should be some kind of saved data between tests, but I can't find it.

If I debug where 'set_trace' is commented:

(Pdb++) expected_file_path
PosixPath('/tmp/pytest-of-username/pytest-75/test_sample_update0/sample_1/updated_test_sample.sample')
(Pdb++) expected_file_path.is_file()
False

when execute 'update' alone

(Pdb++) expected_file_path
PosixPath('/tmp/pytest-of-username/pytest-77/test_sample_update0/sample_1/updated_test_sample.sample')
(Pdb++) expected_file_path.is_file()
True

Solution

  • The old file is being deleted from a different directory than where the test is running. To fix it:

    old_file_path = Path(sample.test_suite.path)
    if old_file_path.exists():
        old_file_path.unlink()
    new_file_path = Path(sample.test_suite.path)
    

    This should fix the issue because:

    It uses the actual paths from Django's model instead of constructing them It properly cleans up the old file using the correct path.