reorganized unit tests and fixed bug in cache handling

This commit is contained in:
Emmanuel Viennet 2021-07-20 18:32:04 +03:00
parent 3876b02626
commit d30c071c5d
10 changed files with 63 additions and 66 deletions

View File

@ -117,7 +117,7 @@ Pour créer un utilisateur "super admin", c'est à dire admin dans tous les dép
## Tests ## Tests
python -m unittest tests.test_users pytest tests/unit/test_users.py
# TODO # TODO

View File

@ -24,36 +24,20 @@ from config import Config
from app.scodoc import notesdb as ndb from app.scodoc import notesdb as ndb
from app.scodoc import sco_cache from app.scodoc import sco_cache
app = Flask(__name__) db = SQLAlchemy()
app.config.from_object(Config) migrate = Migrate()
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login = LoginManager() login = LoginManager()
login.login_view = "auth.login" login.login_view = "auth.login"
login.login_message = "Please log in to access this page." login.login_message = "Please log in to access this page."
mail = Mail() mail = Mail()
bootstrap = Bootstrap(app) bootstrap = Bootstrap()
moment = Moment() moment = Moment()
cache = Cache(config={"CACHE_TYPE": "MemcachedCache"}) # XXX TODO: configuration file cache = Cache(config={"CACHE_TYPE": "MemcachedCache"}) # XXX TODO: configuration file
@app.before_request
def open_dept_db_connection():
# current_app.logger.info("open_dept_db_connection")
if hasattr(g, "scodoc_dept") and not hasattr(g, "db_conn") and g.scodoc_dept:
g.db_conn = ndb.open_dept_connection()
@app.teardown_request
def close_dept_db_connection():
# current_app.logger.info("close_dept_db_connection")
if hasattr(g, "db_conn"):
ndb.close_dept_connection()
def create_app(config_class=Config): def create_app(config_class=Config):
print("create_app")
app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static") app = Flask(__name__, static_url_path="/ScoDoc/static", static_folder="static")
app.logger.setLevel(logging.DEBUG) app.logger.setLevel(logging.DEBUG)
app.config.from_object(config_class) app.config.from_object(config_class)

View File

@ -156,7 +156,7 @@ def scodoc7func(context):
elif not hasattr(g, "scodoc_dept"): elif not hasattr(g, "scodoc_dept"):
g.scodoc_dept = None g.scodoc_dept = None
# --- Open DB connection # --- Open DB connection
app.open_dept_db_connection() app.views.open_dept_db_connection()
# --- Emulate Zope's REQUEST # --- Emulate Zope's REQUEST
REQUEST = ZRequest() REQUEST = ZRequest()
g.zrequest = REQUEST g.zrequest = REQUEST
@ -227,10 +227,10 @@ class ScoDoc7Context(object):
def __init__(self, name=""): def __init__(self, name=""):
self.name = name self.name = name
logging.getLogger(__name__).info("created %s" % self) # logging.getLogger(__name__).info("created %s" % self)
def populate(self, globals_dict): def populate(self, globals_dict):
logging.getLogger(__name__).info("populating context %s" % self) # logging.getLogger(__name__).info("populating context %s" % self)
for k in globals_dict: for k in globals_dict:
if (not k.startswith("_")) and ( if (not k.startswith("_")) and (
isinstance(globals_dict[k], types.FunctionType) isinstance(globals_dict[k], types.FunctionType)

View File

@ -75,7 +75,7 @@ class ScoDocCache:
@classmethod @classmethod
def _get_key(cls, oid): def _get_key(cls, oid):
return g.scodoc_dept + "_" + cls.prefix + oid return g.scodoc_dept + "_" + cls.prefix + "_" + oid
@classmethod @classmethod
def get(cls, oid): def get(cls, oid):
@ -84,16 +84,10 @@ class ScoDocCache:
@classmethod @classmethod
def set(cls, oid, value): def set(cls, oid, value):
"""Store evaluation""" """Store value"""
return CACHE.set(cls._get_key(oid), value, timeout=cls.timeout) key = cls._get_key(oid)
log(f"CACHE key={key}, timeout={cls.timeout}")
@classmethod return CACHE.set(key, value, timeout=cls.timeout)
def get_cached(cls, oid):
"""get cached object. Clients should use .get()
This function is usefull for tests, because get() may be
overloaded by subclasses.
"""
return ScoDocCache.get(oid)
@classmethod @classmethod
def delete(cls, oid): def delete(cls, oid):
@ -103,7 +97,10 @@ class ScoDocCache:
@classmethod @classmethod
def delete_many(cls, oids): def delete_many(cls, oids):
"""Remove multiple keys at once""" """Remove multiple keys at once"""
CACHE.delete_many([cls._get_key(oid) for oid in oids]) # delete_many seems bugged:
# CACHE.delete_many([cls._get_key(oid) for oid in oids])
for oid in oids:
cls.delete(oid)
class EvaluationCache(ScoDocCache): class EvaluationCache(ScoDocCache):
@ -190,13 +187,13 @@ class NotesTableCache(ScoDocCache):
prefix = "NT" prefix = "NT"
@classmethod @classmethod
def get(cls, formsemestre_id): def get(cls, formsemestre_id, compute=True):
"""Returns NotesTable for this formsemestre """Returns NotesTable for this formsemestre
If not in cache, build it and cache it. If not in cache and compute is True, build it and cache it.
""" """
key = cls._get_key(formsemestre_id) key = cls._get_key(formsemestre_id)
nt = CACHE.get(key) nt = CACHE.get(key)
if nt: if nt or not compute:
return nt return nt
from app.scodoc import notes_table from app.scodoc import notes_table

View File

@ -8,7 +8,7 @@ import psycopg2
import psycopg2.extras import psycopg2.extras
from flask import current_app from flask import current_app
from app import app, db from app import db
from app.auth.models import User, Role from app.auth.models import User, Role

View File

@ -2,6 +2,8 @@
"""ScoDoc Flask views """ScoDoc Flask views
""" """
from flask import Blueprint from flask import Blueprint
from flask import g
from app.scodoc import notesdb as ndb
scodoc_bp = Blueprint("scodoc", __name__) scodoc_bp = Blueprint("scodoc", __name__)
scolar_bp = Blueprint("scolar", __name__) scolar_bp = Blueprint("scolar", __name__)
@ -10,6 +12,19 @@ users_bp = Blueprint("users", __name__)
absences_bp = Blueprint("absences", __name__) absences_bp = Blueprint("absences", __name__)
essais_bp = Blueprint("essais", __name__) essais_bp = Blueprint("essais", __name__)
from app.views import scodoc, notes, scolar, absences, users, essais
scolar.context.Notes = notes.context # XXX transitoire #sco8 @scodoc_bp.before_app_request
def open_dept_db_connection():
# current_app.logger.info("open_dept_db_connection")
if hasattr(g, "scodoc_dept") and not hasattr(g, "db_conn") and g.scodoc_dept:
g.db_conn = ndb.open_dept_connection()
@scodoc_bp.teardown_app_request
def close_dept_db_connection(arg):
# current_app.logger.info("close_dept_db_connection")
if hasattr(g, "db_conn"):
ndb.close_dept_connection()
from app.views import scodoc, notes, scolar, absences, users, essais

View File

@ -7,7 +7,7 @@ BASEDIR = os.path.abspath(os.path.dirname(__file__))
load_dotenv(os.path.join(BASEDIR, ".env")) load_dotenv(os.path.join(BASEDIR, ".env"))
class ConfigClass(object): class Config:
"""General configuration. Mostly loaded from environment via .env""" """General configuration. Mostly loaded from environment via .env"""
SECRET_KEY = os.environ.get("SECRET_KEY") or "un-grand-secret-introuvable" SECRET_KEY = os.environ.get("SECRET_KEY") or "un-grand-secret-introuvable"
@ -30,6 +30,9 @@ class ConfigClass(object):
BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL") BOOTSTRAP_SERVE_LOCAL = os.environ.get("BOOTSTRAP_SERVE_LOCAL")
# for ScoDoc 7 compat (à changer) # for ScoDoc 7 compat (à changer)
INSTANCE_HOME = os.environ.get("INSTANCE_HOME", "/opt/scodoc") INSTANCE_HOME = os.environ.get("INSTANCE_HOME", "/opt/scodoc")
SCODOC_VAR_DIR = os.path.join(
os.environ.get("INSTANCE_HOME", "/opt/scodoc"), "var", "scodoc"
)
# For legacy ScoDoc7 installs: postgresql user # For legacy ScoDoc7 installs: postgresql user
SCODOC7_SQL_USER = os.environ.get("SCODOC7_SQL_USER", "www-data") SCODOC7_SQL_USER = os.environ.get("SCODOC7_SQL_USER", "www-data")
@ -37,10 +40,3 @@ class ConfigClass(object):
# STATIC_URL_PATH = "/ScoDoc/static" # STATIC_URL_PATH = "/ScoDoc/static"
# static_folder = "stat" # static_folder = "stat"
# SERVER_NAME = os.environ.get("SERVER_NAME") # SERVER_NAME = os.environ.get("SERVER_NAME")
def __init__(self):
"""Used to build some config variable at startup time"""
self.SCODOC_VAR_DIR = os.path.join(self.INSTANCE_HOME, "var", "scodoc")
Config = ConfigClass()

View File

@ -1,2 +1,2 @@
# #
import tests.test_users import tests.unit.test_users

View File

@ -1,20 +1,29 @@
import pytest import pytest
from app import app, db from flask import g
import app as myapp
from app import db, create_app
from app.auth.models import User, Role, Permission from app.auth.models import User, Role, Permission
from app.scodoc import sco_bulletins_standard
from app.scodoc import notesdb as ndb
@pytest.fixture @pytest.fixture()
def test_client(): def test_client():
# Setup # Setup
app.config["TESTING"] = True myapp.Config.TESTING = True
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite://" myapp.Config.SQLALCHEMY_DATABASE_URI = "sqlite://"
apptest = create_app()
# Run tests: # Run tests:
with app.test_client() as client: with apptest.test_client() as client:
with app.app_context(): with apptest.app_context():
db.create_all() db.create_all()
Role.insert_roles() Role.insert_roles()
g.scodoc_dept = "RT"
g.db_conn = ndb.open_dept_connection()
yield client yield client
# Teardown: ndb.close_dept_connection()
db.session.remove() # Teardown:
db.drop_all() db.session.remove()
db.drop_all()

View File

@ -4,14 +4,14 @@
-écriture de test_users avec pytest. -écriture de test_users avec pytest.
Usage: Usage: pytest tests/unit/test_users.py
""" """
import pytest import pytest
from tests.conftest import test_client from tests.conftest import test_client
from flask import current_app from flask import current_app
from app import app, db from app import db
from app.auth.models import User, Role, Permission from app.auth.models import User, Role, Permission
from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
@ -19,10 +19,6 @@ from app.scodoc.sco_roles_default import SCO_ROLES_DEFAULTS
DEPT = "XX" DEPT = "XX"
def test_test(test_client):
assert 1 == 1
def test_password_hashing(test_client): def test_password_hashing(test_client):
u = User(user_name="susan") u = User(user_name="susan")
db.session.add(u) db.session.add(u)