-
-
Notifications
You must be signed in to change notification settings - Fork 25
Run the entire test suite using pytest #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
load fixtures declaratively we don't need to call_command('loaddata') by hand remove unused imports
# PASSWORDS | ||
# ------------------------------------------------------------------------------ | ||
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers | ||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I've done this is because the passwords in the fixture files were encrypted using the default hasher, so this was causing all the auth-based tests to fail (the hashes didn't match). This didn't manifest when running the tests with django's own runner because it wasn't set up to use this file at all - it was using local.py
.
Another approach would have been to rebuild all the fixtures, but this was easier (and makes a minimal difference to test suite speed). If you end up switching all your tests to create test data on-the-fly using FactoryBoy
or whatever, you could re-enable this micro-optimisation at a later date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a community discussion about which hashing method we want to use for passwords in the DB - we're using Argon2 ATM (recommended in the Django docs - but not the default, which is PBKDF2), which is why the fixutres have the hashes they do. So I recommend using django.contrib.auth.hashers.Argon2PasswordHasher
so that the two match.
Hrmm. This is close, but not quite ready for review. I've marked this WIP for now as I've run out of time to fiddle with it this evening and there's clearly a bit of a hole in my docker knowledge that needs plugging to finish this off and tidy up. I'll have to come back to it in a few days now. Remaining tasks are:
@billglover - I think the issue I'm hitting is that essentially |
4806ba7
to
675dc91
Compare
Having fiddled with it a bit more, I think this is good for review now. I've ended up removing the I think my original comment in #109 (comment) was wrong - everything is running as root. Simultaneously, I feel like I've worked around an underlying issue here which stems from the containers running as root and writing to the filesystem but I think in its current form this PR isn't making that any worse than when I started. I'd still like to understand it better though 🤔 |
Hi @chris48s - I am going to dig into this (and some testing) today. I don't think I feel comfortable removing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A belated HUGE 🙏thanks for this, @chris48s -- sorry it took me a while to look at this!
I checked out your branch and tested all the updated commands. docker-compose run --rm app pytest
looks 😍! I also added a few questions/comments since I am approaching this from a newb perspective. I'm going to defer to @billglover and @BethanyG for final approval, since they've been more hands-on in Django and docker than I have.
project/config/settings/base.py
Outdated
@@ -257,7 +257,10 @@ | |||
"level": "DEBUG", | |||
"class": "logging.StreamHandler", | |||
"formatter": "verbose", | |||
} | |||
}, | |||
"null": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain what this does? (Sorry if it is obvious and I missed it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, f6eddc6 is declares a logger that throws away log messages and then says "use this logger for everything under test"
I originally put that there when I was running tests with the django runner to capture log messages under test because they were all getting printed to stdout, but didn't clock that once I switched them over to running with pytest, pytest does this for us in a more configurable way. I've backed this change out in cb766dd. We should rely on pytest's capturing: https://docs.pytest.org/en/latest/capture.html
pytest
will capture the logs by default and pytest --capture=no
(or pytest -s
) won't, which is more useful anyway.
I've done cb766dd as a revert commit to make review easier, but I can tidy up history before merge
@@ -10,6 +10,5 @@ class User(AbstractUser): | |||
# around the globe. | |||
name = CharField(_("Name of User"), blank=True, max_length=255) | |||
|
|||
@property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we need to remove this decorator, out of curiosity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is explained in the commit message for 03770f4
To work through a more concrete example of the problem this causes:
property:
- Declare
get_absolute_url()
as a property - Go to http://127.0.0.1:8000/admin/users/user/
- Click on any user
- Click the "view on site" link in the top-right
- It will throw
'str' object is not callable
: you can't invoke a string.
method:
- Now switch it to a method
- Click the "view on site" link in the top-right - it will issue a redirect.
Side note: It doesn't issue a redirect to a useful URL because of
"domain": "django.codebuddies.org", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On closer inspection, we should remove the redirect method altogether, & also remove the [view on site]
button from the Admin. (see this for how: https://stackoverflow.com/questions/8662359/django-remove-view-on-site-button-in-the-admin-user-change-form). I think this decorator + method was either left from Coookiecutter or cargo-culted in by myself when piecing together userauth. Since we are mostly building an API, the redirect for viewing user detail/user profile on the site won't ever work -- unless we point it at a react URL. If we do provide a user profile or user detail, it will be as an endpoint, and deliver JSON -- not an HTML template/form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll open an issue for that (and probably chuck it into the PR for our testing re-write)...unless there are any objections??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'view on site' button is probably not that helpful for an API project, but I picked it as an example of an internal call that expects get_absolute_url
to be a method which exists at the moment, but I think there are other internal calls in django (and possibly DRF) that expect that signature.
If you reeeally want a property version, we could also declare
@property
def absolute_url(self):
return self.get_absolute_url()
and probably chuck it into the PR for our testing re-write
It can be done as its own PR - should be no dependency/merge conflict there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, I was unable to make a call to fixtures work with my conda
setup for these tests - hence the call_command
. However, the fixture trick works in docker...go figure. Much nicer as a list of fixtures.
Still not really wanting to give up the manage.py
container here, but figure we could add it back in if needed, along with a test
container, if we are feeling fancy/ are presented with a strong argument for having them separate.
I think we can approve and merge this, and if we later change our minds, it won't be a terrible thing.
@billglover - any thoughts or concerns to add? |
No concerns with removing the Not much to add regarding |
Django's ORM internally expects this to be a method, not a property https://docs.djangoproject.com/en/3.0/ref/models/instances/#get-absolute-url Making it a property causes django to throw `TypeError: 'str' object is not callable` when creating a link to a user object
- run pytest in CI - scrap the standalone manage container - run console commands against app container - update docs
Cheers for the reviews all. @billglover - do you have any thoughts on the issue of files created inside the container being owned by root? Should we be chown-ing everything from outside the container, should the containers be running as not-root? Bit of both? |
I’m not too fussed at the moment, but would like to revisit how the containers are built and see if we can reduce the number of things that need to be installed. Perhaps this is would be a good time to look at file ownership. |
Main changes in this PR:
🔧 Configure pytest
👀 Get it to discover and run the full test suite
✔️ Get all the existing unit tests passing
📚 Update CI build and docs
Closes #107