Skip to content
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

Patch for step # #43

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open

Conversation

GayLaurent
Copy link

Delivery corresponding to step 1 of the service provision on the migration to Python3 / Django3

Copy link
Contributor

@roosemberth roosemberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Thanks for your contribution! I've made some small comments.

set -e
set +x

project_dir=$(dirname $(dirname $(readlink -f "$0")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_dir=$(dirname $(dirname $(readlink -f "$0")))
project_dir="$(dirname $(dirname $(readlink -f "$0")))"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)
But, it's a good idea to protect about this bad using

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)

Spaces are a valid POSIX path component. There's no reason to make any assumptions whatsoever about how the project may be deployed or developed.

There's no way to forsee how code will evolve, and this is precisely the kind to bugs that may have disastrous consequences on the user's machine.

cp $project_dir/tools/settingsLocal.py.test $project_dir/truffe2/app/settingsLocal.py

(
cd $project_dir/truffe2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cd $project_dir/truffe2
cd "$project_dir/truffe2"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)
But, it's a good idea to protect about this bad using

fi

rm -rf $project_dir/truffe2/db.sqlite3
cp $project_dir/tools/settingsLocal.py.test $project_dir/truffe2/app/settingsLocal.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cp $project_dir/tools/settingsLocal.py.test $project_dir/truffe2/app/settingsLocal.py
cp "$project_dir/tools/settingsLocal.py.test" "$project_dir/truffe2/app/settingsLocal.py"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)
But, it's a good idea to protect about this bad using

. $project_dir/venv/bin/activate
fi

rm -rf $project_dir/truffe2/db.sqlite3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rm -rf $project_dir/truffe2/db.sqlite3
rm -rf "$project_dir/truffe2/db.sqlite3"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)
But, it's a good idea to protect about this bad using

set +e
set +x

project_dir=$(dirname $(dirname $(readlink -f "$0")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project_dir=$(dirname $(dirname $(readlink -f "$0")))
project_dir="$(dirname $(dirname $(readlink -f "$0")))"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normaly, good developper don't use space in working directory ;)
But, it's a good idea to protect about this bad using

SubventionFile(id=1, uploader_id=1, file='uploads/files/logo_testing.png').save()


def initial_data():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a description of what this test does.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a test, it's an initalisation of data function for test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry I meant function, my bad. In particular it would be a good idea to document what its representation invariants are. (e.g. This function initialize djago's components so that creating a user is possible).

For maintainability it's better to have functions with a restricted responsibilities and not something as generic as initialize everything possible to the moment. Specially since this may lead developers to add logic to try to configure things that the function has not enough information to do, which always ends up being a source of bugs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also be argued that this function is doing too much. i.e. why would the function testing user management need to have access to configured logistics or accounting modules.

This argument is moot if the code requires this (though in such case, these kind of test function/helpers should contain comments to help newcomers understand the debts and limitations of the existing code).

def test_myunit_pdf(self):
self.call_check_pdf('/users/myunit/pdf/')

def test_ldap_search(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this test ?
The names of the test gives the impression it effectively verifies that a search is performed in the ldap. Whereas it is only testing an error condition. Regardless on whether such error condition is relevavant, this test should be renamed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use generic name of test form different URL to test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be another instance of #43 (comment), let's continue the discussions there.

TestCase.setUp(self)
initial_data()
class_name = self.__class__.__name__
if 'WithLogin' in class_name:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use constructor arguments or class attributes instead of magic class names; specially if they are not documented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not possible in the constructor of unitcase class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please develop? Can't we just do something like this?

class A:
    def __init__(self):
        pass

class B:
    def __init__(self):
        pass

class C(A, B):
    def __init__(self, *args, **kwargs):
        self.with_client = False
        if with_client in kwargs:
            self.with_client = kwargs['with_client']
            del kwargs['with_client']
        super(A, self).__init__(*args, **kwargs)

Otherwise, how about setting the attributes in the derived class' setUp method and adding a note in the class documentation?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change algorithme to select test user in "Step2"

def test_link_base(self):
self.call_check_html('/link/base')

def test_last_100_logging_entries(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this test is misleading, please rename it to reflect what it's actually testing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use generic name of test form different URL to test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use generic name of test form different URL to test.

I'm sorry, I do not understand what you mean. Could you please rephrase?

My comment concerns the fact that this verifies an exception is risen when a URL is accessed. I fail to understand the value of testing such exception is raised.

If you want to lay work for a future test, I'd recommend rather doing an issue or testing something smaller (but concrete) and adding a note on how that test should be extended.

As-is, I have the impression the test is trying to test something that's currently not testable, completely bypassing the test altoguether (or even worse, testing that the intended functionnality fails!). A developer that sees the test test_last_100_logging_entries pass is likely to assume that the last 100 requests were verified to have been correctly recorded.

# Create your tests here.
class RightsNoLoginTest(TestCase):

def test_basic(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the purpose of this test, can you please add a comment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test removed in "Step2"

Copy link
Contributor

@CedricCook CedricCook left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive that you are picking up work on this!

}
}

SECRET_KEY = "*=)2_8tuf5qym&6szbr%=xz)g8aw=w3z)ltc=+d)iy7v7grj#!"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Django secret key in test can just be set to "_" or something similar. Random keys like the one proposed may make other developers think you've leaked a key :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, of course, it's juste a random key in this case.
good idea to use only "_" letters


def call_check_redirect(self, path, data={}, method='get', redirect_url=None, target_status_code=200):
self.call(path, data, method, status_expected=302)
self.assertRedirects(self.response, '/users/login?next=%s' % path if redirect_url is None else redirect_url, target_status_code=target_status_code)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /users/login string here should maybe be compared directly to django.conf.settings.LOGIN_URL? Or do you want to keep it seperate on purpose?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this case: it's a test, so we want be sure that it's this URL to use for redirection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree: we should test that the view redirect to the LOGIN_URL, not a specifc url.

Checking that this URL is /users/login should be another specific test.

if status_expected != 0:
self.assertEqual(self.response.status_code, status_expected, "HTTP error [%s]:%s" % (self.response.status_code, self.get_div('text-center error-box')))

def call_check_redirect(self, path, data={}, method='get', redirect_url=None, target_status_code=200):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test essentially tests whether a view is decorated with @login_required. Is it efficient to test all of theses login protected functions by checking the redirect, or could you test for the presence of the decorator?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's juste a controle that no "login_required" is missing
And i have identified that "/accounting/main/accounting/import/step/0" has no decorator.
So, it's a no-regression control: check that nobody don't remove decorator in futur

@roosemberth
Copy link
Contributor

@GayLaurent There's no need to repeat the same answer in each discussion ^^

The reasoning for opening many discussions is that it would be easy to "just apply" the suggestions.

SubventionFile(id=1, uploader_id=1, file='uploads/files/logo_testing.png').save()


def initial_data():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry I meant function, my bad. In particular it would be a good idea to document what its representation invariants are. (e.g. This function initialize djago's components so that creating a user is possible).

For maintainability it's better to have functions with a restricted responsibilities and not something as generic as initialize everything possible to the moment. Specially since this may lead developers to add logic to try to configure things that the function has not enough information to do, which always ends up being a source of bugs.

SubventionFile(id=1, uploader_id=1, file='uploads/files/logo_testing.png').save()


def initial_data():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also be argued that this function is doing too much. i.e. why would the function testing user management need to have access to configured logistics or accounting modules.

This argument is moot if the code requires this (though in such case, these kind of test function/helpers should contain comments to help newcomers understand the debts and limitations of the existing code).

TestCase.setUp(self)
initial_data()
class_name = self.__class__.__name__
if 'WithLogin' in class_name:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please develop? Can't we just do something like this?

class A:
    def __init__(self):
        pass

class B:
    def __init__(self):
        pass

class C(A, B):
    def __init__(self, *args, **kwargs):
        self.with_client = False
        if with_client in kwargs:
            self.with_client = kwargs['with_client']
            del kwargs['with_client']
        super(A, self).__init__(*args, **kwargs)

Otherwise, how about setting the attributes in the derived class' setUp method and adding a note in the class documentation?

def test_link_base(self):
self.call_check_html('/link/base')

def test_last_100_logging_entries(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use generic name of test form different URL to test.

I'm sorry, I do not understand what you mean. Could you please rephrase?

My comment concerns the fact that this verifies an exception is risen when a URL is accessed. I fail to understand the value of testing such exception is raised.

If you want to lay work for a future test, I'd recommend rather doing an issue or testing something smaller (but concrete) and adding a note on how that test should be extended.

As-is, I have the impression the test is trying to test something that's currently not testable, completely bypassing the test altoguether (or even worse, testing that the intended functionnality fails!). A developer that sees the test test_last_100_logging_entries pass is likely to assume that the last 100 requests were verified to have been correctly recorded.

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': join(dirname(dirname(abspath(__file__))), 'db.sqlite3'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really a good idea to use a sqlite3 database since it's not what is used in production ?

I suggest using the same database to avoid potential issues

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO the production database sets even a higher barrier to potentially new developers. I'd rather stay with something simple and if a CI is ever implemented use the production configuration there. In any case, it should be easily replaceable (e.g. TEST_DATABASE_URL envvar). A section in the README.md about reading the tests should be added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Developers should use the correct database, especially dues to various small changes that can lead to misbehavior and/or missing features.

Tests are meaningless if they fail or not depending on the context. Developers should not use sqlite3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to find developers motivated to code truffe ! I'm against creating supplementary barrier. And Django provide a correct abstraction of the database. To my opinion, we should instead avoid database specific code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to decide on this @the-glu !

Copy link
Contributor

@roosemberth roosemberth Feb 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support @TeoGoddet's argument on this one.

def test_accounting_import_step2(self):
sess = self.session
sess.update({'T2_ACCOUNTING_IMPORT_def-123-abc': {'is_valid': True, 'has_data': True, 'year':1,
'data': {'nop':[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please use coding style that don't waste all spaces and tab only to +1?

I suggest using black on new files directly (and at somepoint we should use black for the whole projet)

from accounting_main.models import Budget
Budget(id=2, name='bad budget', unit_id=1, accounting_year_id=1, costcenter_id=1, deleted=True).save()
self.call_check_html('/accounting/main/budget/deleted', data={'upk':1})
self.call_check_redirect('/accounting/main/budget/deleted', method='post', data={'upk':1, 'pk':2}, redirect_url='/accounting/main/budget/')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, all thoses call only check redirection. Shouldn't them also check that (in that specific case) the object is deleted ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scope of the migration project was "testing that the pages doens't produce error, not that they do their job correctly"
So I would consider this out of scope

def test_budget_add(self):
self.call_check_html('/accounting/main/budget/~/edit', data={'upk':1, 'ypk':1})
self.call_check_redirect('/accounting/main/budget/~/edit', method='post',
data={'upk':1, 'ypk':1, 'tags':'', 'name':'my budget', 'costcenter':1}, redirect_url='/accounting/main/budget/2/')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To continue preivous comment, another example: shouldn't we test for the objcet creation in the databsae ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scope of the migration project was "testing that the pages doens't produce error, not that they do their job correctly"
So I would consider this out of scope

self.call_check_json('/accounting/main/accountingline/json')

def test_accountingline_deleted(self):
from accounting_main.models import AccountingLine
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

top level import ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should motivate someone to choose between TopLevenl and that kind of import ? @the-glu

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-level imports are the norm, one should really avoid in-place imports since they nuance the readability of the code, concerns and expectations: As a developer, when I open a file, I can directly see what this file depends on and make myself a great idea of what this file does. Moreover, inline imports are not necessarily executed in all codepaths of the file, which may lead to unexpected errors at runtime that could have been avoided.

ExpenseClaim(id=1, name='expense', user=user, nb_proofs=1, accounting_year_id=1, costcenter_id=1, status='6_archived').save()
ExpenseClaimLine(expense_claim_id=1, label='claim', account_id=5, value=54.65, tva=0, value_ttc=54.65).save()
ExpenseClaimLogging(object_id=1, when=now, who=user, what='created').save()
media_path = join(dirname(dirname(__file__)), 'media')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably use the django settings value for that instead of computing it again

from django.test.client import MULTIPART_CONTENT
from django.core.management import call_command

from main.test_data import initial_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import order doesn't follow the global convention in the project.

[django imports]


[project import]


[others imports]

(with double crlf)

(Yes, that non-standard, but it's better to follow existing convention for coherence :) )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This convention is not standard.
Personnaly, i used to more generic (Python) or more specific (project):

[others imports]

[django imports]

[project import]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like I said, yes it's not standard, but projet standards are more important :)

self.connect_to('admin')
elif 'WithLog' in class_name:
user_num = int(class_name[class_name.index('WithLog') + 7])
self.connect_to('user%d' % user_num)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also as a global convention, please use format instead of % syntax.


def call_check_redirect(self, path, data={}, method='get', redirect_url=None, target_status_code=200):
self.call(path, data, method, status_expected=302)
self.assertRedirects(self.response, '/users/login?next=%s' % path if redirect_url is None else redirect_url, target_status_code=target_status_code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree: we should test that the view redirect to the LOGIN_URL, not a specifc url.

Checking that this URL is /users/login should be another specific test.

if status_expected != 0:
self.assertEqual(self.response.status_code, status_expected, "HTTP error [%s]:%s" % (self.response.status_code, self.get_div('text-center error-box')))

def call_check_redirect(self, path, data={}, method='get', redirect_url=None, target_status_code=200):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the function name is missleading. It's not checking for a redirection but for a redirection to the login page.

@roosemberth
Copy link
Contributor

roosemberth commented Oct 30, 2020

@GayLaurent I think a different PR should be opened to include Django migrations. This PR should be merged before any of the other ones can proceed.

@the-glu what's blocking the merge of 59f476b?

@the-glu
Copy link
Member

the-glu commented Oct 30, 2020

@the-glu what's blocking the merge of 59f476b?

Some of my comments are still open ? Not sure about your question. Did you used the wrong hash ?

@roosemberth
Copy link
Contributor

Some of my comments are still open ? Not sure about your question. Did you used the wrong hash ?

Sorry, my client interface was incoherent. From the github.com's web interface what needs to be adressed is clear.

@GayLaurent GayLaurent closed this Nov 3, 2020
@GayLaurent
Copy link
Author

Cancel to submit a new pull request

@llann
Copy link
Member

llann commented Nov 23, 2020

Hey @GayLaurent that's not really the way to do it... Now it's hard to see if changes have been done. Can you please re-open it and apply changes here until it's validated and then merge them in your new branch and do a new PR. This is blocking the whole process.

@GayLaurent
Copy link
Author

Re-open to be apply in your master branch

@GayLaurent GayLaurent reopened this Nov 25, 2020
@GayLaurent
Copy link
Author

The main remarks you made are taken into account in the following PR.
Please validate this PR so that I can submit the rest to you.

@roosemberth
Copy link
Contributor

@GayLaurent Can you please address the discussions above and leave a comment with the hash of the commit addressing each of them?

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants