1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-11-22 04:54:18 +00:00

💥 Create test framework

This commit is contained in:
Stefano Pigozzi 2021-05-07 19:46:14 +02:00
parent d7e55e556d
commit 008efa1c24
Signed by untrusted user who does not match committer: steffo
GPG key ID: 6965406171929D01
37 changed files with 324 additions and 192 deletions

View file

@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Backend: Run tests" type="tests" factoryName="py.test">
<module name="backend" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
<env name="FLASK_CONFIG" value="../config.py" />
</envs>
<option name="SDK_HOME" value="$USER_HOME$/.cache/pypoetry/virtualenvs/nest-backend-3ypGnHKr-py3.9/bin/python" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/backend" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="_new_keywords" value="&quot;&quot;" />
<option name="_new_parameters" value="&quot;&quot;" />
<option name="_new_additionalArguments" value="&quot;&quot;" />
<option name="_new_target" value="&quot;nest_backend&quot;" />
<option name="_new_targetType" value="&quot;PYTHON&quot;" />
<method v="2" />
</configuration>
</component>

View file

@ -1,22 +1,23 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Backend: [Make your own run config!]" type="PythonConfigurationType" factoryName="Python" editBeforeRun="true"> <configuration default="false" name="Backend: Start server" type="PythonConfigurationType" factoryName="Python">
<module name="frontend" /> <module name="backend" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" /> <option name="PARENT_ENVS" value="true" />
<envs> <envs>
<env name="PYTHONUNBUFFERED" value="1" /> <env name="PYTHONUNBUFFERED" value="1" />
<env name="FLASK_CONFIG" value="../config.py" />
</envs> </envs>
<option name="SDK_HOME" value="C:\Users\giova\AppData\Local\pypoetry\Cache\virtualenvs\nest-backend-gZK59SuL-py3.9\Scripts\python.exe" /> <option name="SDK_HOME" value="$USER_HOME$/.cache/pypoetry/virtualenvs/nest-backend-3ypGnHKr-py3.9/bin/python" />
<option name="WORKING_DIRECTORY" value="" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/backend" />
<option name="IS_MODULE_SDK" value="false" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="" /> <option name="SCRIPT_NAME" value="nest_backend" />
<option name="PARAMETERS" value="" /> <option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" /> <option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" /> <option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" /> <option name="MODULE_MODE" value="true" />
<option name="REDIRECT_INPUT" value="false" /> <option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" /> <option name="INPUT_FILE" value="" />
<method v="2" /> <method v="2" />

View file

@ -1,3 +1,8 @@
# Flask config
config.py
configtest.py
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View file

@ -5,6 +5,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/nest_backend" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/nest_backend" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/nest_backend/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/nest_backend/test" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/old_unittest_tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/docs/_build" /> <excludeFolder url="file://$MODULE_DIR$/docs/_build" />
</content> </content>
<orderEntry type="jdk" jdkName="Poetry (backend)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Poetry (backend)" jdkType="Python SDK" />

View file

@ -1,57 +1,23 @@
""" """
This is the runner for the server. This is the runner for the server.
""" """
import werkzeug.middleware.proxy_fix import os
from .routes import * import sys
from .gestione import * from .gestione import *
from flask_cors import CORS from .app import app, extension_sqlalchemy
from flask_jwt_extended import *
from .app import app
from .api_spec import spec
from .swagger import swagger_ui_blueprint, SWAGGER_URL
Base.init_app(app=app)
jwt = JWTManager(app)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
reverse_proxy_app = werkzeug.middleware.proxy_fix.ProxyFix(app=app, x_for=1, x_proto=0, x_host=1, x_port=0, x_prefix=0)
# Routes setup
app.add_url_rule("/doa", view_func=page_doa, methods=["GET", "POST"])
app.add_url_rule("/api/v1/login", view_func=page_login, methods=["POST"])
app.add_url_rule("/api/v1/users/", view_func=page_users, methods=["GET", "POST"])
app.add_url_rule("/api/v1/users/<string:email>", view_func=page_user, methods=["GET", "PATCH", "DELETE"])
app.add_url_rule("/api/v1/repositories/", view_func=page_repositories, methods=["GET", "POST"])
app.add_url_rule("/api/v1/repositories/<int:rid>", view_func=page_repository, methods=["GET", "PATCH", "DELETE", "PUT"])
app.add_url_rule("/api/v1/repositories/<int:rid>/conditions", view_func=page_repository_conditions,
methods=["GET", "POST"])
app.add_url_rule("/api/v1/conditions/<int:cid>", view_func=page_condition, methods=["GET", "PATCH", "DELETE"])
app.register_error_handler(Exception, error_handler)
app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL)
with app.test_request_context(): print(" * Swagger docs will be available at http://127.0.0.1:5000/docs")
print(" * Getting docs ready...")
for fn_name in app.view_functions:
if fn_name == 'static':
continue
view_fn = app.view_functions[fn_name]
spec.path(view=view_fn)
print(" * Docs have been compiled on http://127.0.0.1:5000/docs")
with app.app_context():
@app.route("/docs/swagger.json") print(" * Creating database tables...")
def create_swagger_doc(): extension_sqlalchemy.create_all(app=app)
return jsonify(spec.to_dict())
if __name__ == "__main__":
with app.app_context():
Base.create_all(app=app)
if not User.query.filter_by(isAdmin=True).all(): if not User.query.filter_by(isAdmin=True).all():
Base.session.add( print(" * Creating default admin account...")
extension_sqlalchemy.session.add(
User(email="admin@admin.com", password=gen_password("password"), username="admin", isAdmin=True)) User(email="admin@admin.com", password=gen_password("password"), username="admin", isAdmin=True))
Base.session.commit() extension_sqlalchemy.session.commit()
app.run(debug=__debug__) print(" * Created! Username: admin | Password: password")
app.run(debug=__debug__)

View file

@ -1,16 +1,101 @@
from flask import Flask from flask import Flask
import os from flask_cors import CORS as FlaskCORS
from flask_jwt_extended import JWTManager as FlaskJWTManager
from flask_sqlalchemy import SQLAlchemy as FlaskSQLAlchemy
from werkzeug.middleware.proxy_fix import ProxyFix as MiddlewareProxyFix
from . import database, routes, gestione, swagger
from .api_spec import spec
# --- MAIN APP ---
app = Flask(__name__) app = Flask(__name__)
if os.getenv('COOKIE_SECRET'):
app.secret_key = os.getenv('COOKIE_SECRET') # --- APP CONFIG
else:
app.secret_key = "testing" app.config.from_envvar("FLASK_CONFIG")
if os.getenv("JWT_SECRET_KEY"):
app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY") # --- EXTENSIONS ---
else:
app.config["JWT_SECRET_KEY"] = "testing" app.config['CORS_HEADERS'] = 'Content-Type'
if os.getenv("DATABASE_URI"): extension_cors = FlaskCORS(app=app)
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URI')
else: extension_jwt = FlaskJWTManager(app=app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:password@localhost:5432/PdSDev'
extension_sqlalchemy = database.ext
extension_sqlalchemy.init_app(app=app)
# --- API ROUTES ---
app.add_url_rule(
"/doa",
view_func=routes.page_doa,
methods=["GET", "POST"],
)
app.add_url_rule(
"/api/v1/login",
view_func=routes.page_login,
methods=["POST"],
)
app.add_url_rule(
"/api/v1/users/",
view_func=routes.page_users,
methods=["GET", "POST"],
)
app.add_url_rule(
"/api/v1/users/<string:email>",
view_func=routes.page_user,
methods=["GET", "PATCH", "DELETE"],
)
app.add_url_rule(
"/api/v1/repositories/",
view_func=routes.page_repositories,
methods=["GET", "POST"],
)
app.add_url_rule(
"/api/v1/repositories/<int:rid>",
view_func=routes.page_repository,
methods=["GET", "PATCH", "DELETE", "PUT"],
)
app.add_url_rule(
"/api/v1/repositories/<int:rid>/conditions",
view_func=routes.page_repository_conditions,
methods=["GET", "POST"],
)
app.add_url_rule(
"/api/v1/conditions/<int:cid>",
view_func=routes.page_condition,
methods=["GET", "PATCH", "DELETE"],
)
# --- SWAGGER DOCS ---
app.register_blueprint(
swagger.swagger_ui_blueprint,
url_prefix=swagger.SWAGGER_URL
)
with app.test_request_context():
for fn_name in app.view_functions:
if fn_name == 'static':
continue
view_fn = app.view_functions[fn_name]
spec.path(view=view_fn)
app.add_url_rule(
"/docs/swagger.json",
view_func=lambda: gestione.jsonify(spec.to_dict()),
methods=["GET"],
)
# --- ERROR HANDLER ---
app.register_error_handler(
Exception,
gestione.error_handler
)
# --- REVERSE PROXY ---
if not __debug__:
app = MiddlewareProxyFix(app=app, x_for=1, x_proto=0, x_host=1, x_port=0, x_prefix=0)

View file

@ -3,4 +3,4 @@ This module imports all the tables and the declarative base
""" """
from .tables import * from .tables import *
from .base import Base from .base import ext

View file

@ -1,7 +1,3 @@
"""
This module creates the declarative base
"""
import flask_sqlalchemy import flask_sqlalchemy
from sqlalchemy.orm import backref
Base = flask_sqlalchemy.SQLAlchemy() ext = flask_sqlalchemy.SQLAlchemy()

View file

@ -2,18 +2,18 @@
This module defines the Alert database class. This module defines the Alert database class.
""" """
from ..base import Base from ..base import ext
class Alert(Base.Model): class Alert(ext.Model):
__tablename__ = "alert" __tablename__ = "alert"
id = Base.Column(Base.Integer, primary_key=True) id = ext.Column(ext.Integer, primary_key=True)
name = Base.Column(Base.String, nullable=False) name = ext.Column(ext.String, nullable=False)
limit = Base.Column(Base.Integer, nullable=False) limit = ext.Column(ext.Integer, nullable=False)
window_size = Base.Column(Base.Integer, nullable=False) window_size = ext.Column(ext.Integer, nullable=False)
# Foreign Keys # Foreign Keys
repository_id = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False) repository_id = ext.Column(ext.Integer, ext.ForeignKey("repository.id", ondelete="CASCADE"), nullable=False)
# Relationships # Relationships
repository = Base.relationship("Repository", back_populates="alerts") repository = ext.relationship("Repository", back_populates="alerts")
notifications = Base.relationship("Notification", back_populates="alert", cascade="all, delete") notifications = ext.relationship("Notification", back_populates="alert", cascade="all, delete")
operations = Base.relationship("BoolOperation", back_populates="alert", cascade="all, delete") operations = ext.relationship("BoolOperation", back_populates="alert", cascade="all, delete")

View file

@ -2,12 +2,12 @@
This module defines the Authorization database class. This module defines the Authorization database class.
""" """
from ..base import Base from ..base import ext
class Authorization(Base.Model): class Authorization(ext.Model):
rid = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True) rid = ext.Column(ext.Integer, ext.ForeignKey("repository.id", ondelete="CASCADE"), primary_key=True)
email = Base.Column(Base.String, Base.ForeignKey("user.email", ondelete="CASCADE"), primary_key=True) email = ext.Column(ext.String, ext.ForeignKey("user.email", ondelete="CASCADE"), primary_key=True)
# Relationships # Relationships
repository = Base.relationship("Repository", back_populates="authorizations") repository = ext.relationship("Repository", back_populates="authorizations")
user = Base.relationship("User", back_populates="authorizations") user = ext.relationship("User", back_populates="authorizations")

View file

@ -2,24 +2,25 @@
This module defines the BoolOperation database class. This module defines the BoolOperation database class.
""" """
from ..base import Base, backref from ..base import ext
from .Enums import OperationType from .Enums import OperationType
from sqlalchemy.orm import backref
class BoolOperation(Base.Model): class BoolOperation(ext.Model):
__tablename__ = "bool_operation" __tablename__ = "bool_operation"
id = Base.Column(Base.Integer, primary_key=True) id = ext.Column(ext.Integer, primary_key=True)
operation = Base.Column(Base.Enum(OperationType), nullable=False) operation = ext.Column(ext.Enum(OperationType), nullable=False)
isRoot = Base.Column(Base.Boolean, default=False, nullable=False) isRoot = ext.Column(ext.Boolean, default=False, nullable=False)
# Foreign Keys # Foreign Keys
condition_id = Base.Column(Base.Integer, Base.ForeignKey("condition.id")) condition_id = ext.Column(ext.Integer, ext.ForeignKey("condition.id"))
node_1_id = Base.Column(Base.Integer, Base.ForeignKey("bool_operation.id")) node_1_id = ext.Column(ext.Integer, ext.ForeignKey("bool_operation.id"))
node_2_id = Base.Column(Base.Integer, Base.ForeignKey("bool_operation.id")) node_2_id = ext.Column(ext.Integer, ext.ForeignKey("bool_operation.id"))
alert_id = Base.Column(Base.Integer, Base.ForeignKey("alert.id")) alert_id = ext.Column(ext.Integer, ext.ForeignKey("alert.id"))
# Relationships # Relationships
condition = Base.relationship("Condition", back_populates="operations") condition = ext.relationship("Condition", back_populates="operations")
node_1 = Base.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_1_id==bool_operation.c.id"), node_1 = ext.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_1_id==bool_operation.c.id"),
remote_side="BoolOperation.id", backref=backref("father_1", uselist=False)) remote_side="BoolOperation.id", backref=backref("father_1", uselist=False))
node_2 = Base.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_2_id==bool_operation.c.id"), node_2 = ext.relationship("BoolOperation", primaryjoin=("bool_operation.c.node_2_id==bool_operation.c.id"),
remote_side="BoolOperation.id", backref=backref("father_2", uselist=False)) remote_side="BoolOperation.id", backref=backref("father_2", uselist=False))
alert = Base.relationship("Alert", back_populates="operations") alert = ext.relationship("Alert", back_populates="operations")

View file

@ -2,13 +2,13 @@
This module defines the Composed database class. This module defines the Composed database class.
""" """
from ..base import Base from ..base import ext
class Composed(Base.Model): class Composed(ext.Model):
__tablename__ = "composed" __tablename__ = "composed"
rid = Base.Column(Base.Integer, Base.ForeignKey("repository.id"), primary_key=True) rid = ext.Column(ext.Integer, ext.ForeignKey("repository.id"), primary_key=True)
snowflake = Base.Column(Base.String, Base.ForeignKey("tweet.snowflake"), primary_key=True) snowflake = ext.Column(ext.String, ext.ForeignKey("tweet.snowflake"), primary_key=True)
# Relationships # Relationships
repository = Base.relationship("Repository", back_populates="tweets") repository = ext.relationship("Repository", back_populates="tweets")
tweet = Base.relationship("Tweet", back_populates="repositories") tweet = ext.relationship("Tweet", back_populates="repositories")

View file

@ -2,21 +2,21 @@
This module defines the Condition database class. This module defines the Condition database class.
""" """
from ..base import Base from ..base import ext
from .Enums import ConditionType from .Enums import ConditionType
class Condition(Base.Model): class Condition(ext.Model):
__tablename__ = "condition" __tablename__ = "condition"
id = Base.Column(Base.Integer, primary_key=True) id = ext.Column(ext.Integer, primary_key=True)
type = Base.Column(Base.Enum(ConditionType), nullable=False) type = ext.Column(ext.Enum(ConditionType), nullable=False)
content = Base.Column(Base.String, nullable=False) content = ext.Column(ext.String, nullable=False)
# FK # FK
repository_id = Base.Column(Base.Integer, Base.ForeignKey("repository.id", ondelete="CASCADE")) repository_id = ext.Column(ext.Integer, ext.ForeignKey("repository.id", ondelete="CASCADE"))
# Relationships # Relationships
repository = Base.relationship("Repository", back_populates="conditions", cascade="all, delete") repository = ext.relationship("Repository", back_populates="conditions", cascade="all, delete")
tweets = Base.relationship("Contains", back_populates="condition") tweets = ext.relationship("Contains", back_populates="condition")
operations = Base.relationship("BoolOperation", back_populates="condition") operations = ext.relationship("BoolOperation", back_populates="condition")
def to_json(self): def to_json(self):
return { return {

View file

@ -2,13 +2,13 @@
This module defines the Contains database class. This module defines the Contains database class.
""" """
from ..base import Base from ..base import ext
class Contains(Base.Model): class Contains(ext.Model):
__tablename__ = "contains" __tablename__ = "contains"
cid = Base.Column(Base.Integer, Base.ForeignKey("condition.id"), primary_key=True) cid = ext.Column(ext.Integer, ext.ForeignKey("condition.id"), primary_key=True)
snowflake = Base.Column(Base.String, Base.ForeignKey("tweet.snowflake"), primary_key=True) snowflake = ext.Column(ext.String, ext.ForeignKey("tweet.snowflake"), primary_key=True)
# Relationships # Relationships
condition = Base.relationship("Condition", back_populates="tweets") condition = ext.relationship("Condition", back_populates="tweets")
tweet = Base.relationship("Tweet", back_populates="conditions") tweet = ext.relationship("Tweet", back_populates="conditions")

View file

@ -2,13 +2,14 @@
This module defines the Notification database class. This module defines the Notification database class.
""" """
from ..base import Base from ..base import ext
class Notification(Base.Model):
class Notification(ext.Model):
__tablename__ = "notification" __tablename__ = "notification"
id = Base.Column(Base.Integer, primary_key=True) id = ext.Column(ext.Integer, primary_key=True)
ora = Base.Column(Base.DateTime, nullable=False) ora = ext.Column(ext.DateTime, nullable=False)
# Foreign Key # Foreign Key
alert_id = Base.Column(Base.Integer, Base.ForeignKey("alert.id"), nullable=False) alert_id = ext.Column(ext.Integer, ext.ForeignKey("alert.id"), nullable=False)
# Relationships # Relationships
alert = Base.relationship("Alert", back_populates="notifications") alert = ext.relationship("Alert", back_populates="notifications")

View file

@ -2,30 +2,30 @@
This module defines the :class:`.Repository` database class. This module defines the :class:`.Repository` database class.
""" """
from ..base import Base from ..base import ext
from .Enums import ConditionMode from .Enums import ConditionMode
class Repository(Base.Model): class Repository(ext.Model):
__tablename__ = "repository" __tablename__ = "repository"
# Columns # Columns
id = Base.Column(Base.Integer, primary_key=True) id = ext.Column(ext.Integer, primary_key=True)
name = Base.Column(Base.String, nullable=False) name = ext.Column(ext.String, nullable=False)
start = Base.Column(Base.DateTime, nullable=True) start = ext.Column(ext.DateTime, nullable=True)
end = Base.Column(Base.DateTime, nullable=True) end = ext.Column(ext.DateTime, nullable=True)
is_active = Base.Column(Base.Boolean, nullable=False, default=False) is_active = ext.Column(ext.Boolean, nullable=False, default=False)
evaluation_mode = Base.Column(Base.Enum(ConditionMode), default=ConditionMode.all_or.value) evaluation_mode = ext.Column(ext.Enum(ConditionMode), default=ConditionMode.all_or.value)
# Foreign Keys # Foreign Keys
owner_id = Base.Column(Base.String, Base.ForeignKey("user.email", ondelete="CASCADE"), nullable=False) owner_id = ext.Column(ext.String, ext.ForeignKey("user.email", ondelete="CASCADE"), nullable=False)
# Relationships # Relationships
owner = Base.relationship("User", back_populates="owner_of") owner = ext.relationship("User", back_populates="owner_of")
authorizations = Base.relationship("Authorization", back_populates="repository", cascade="all, delete") authorizations = ext.relationship("Authorization", back_populates="repository", cascade="all, delete")
tweets = Base.relationship("Composed", back_populates="repository", cascade="all, delete") tweets = ext.relationship("Composed", back_populates="repository", cascade="all, delete")
alerts = Base.relationship("Alert", back_populates="repository", cascade="all, delete") alerts = ext.relationship("Alert", back_populates="repository", cascade="all, delete")
conditions = Base.relationship("Condition", back_populates="repository") conditions = ext.relationship("Condition", back_populates="repository")
def to_json(self): def to_json(self):
return { return {

View file

@ -2,15 +2,15 @@
This module defines the Tweet database class. This module defines the Tweet database class.
""" """
from ..base import Base from ..base import ext
class Tweet(Base.Model): class Tweet(ext.Model):
__tablename__ = "tweet" __tablename__ = "tweet"
snowflake = Base.Column(Base.String, primary_key=True) snowflake = ext.Column(ext.String, primary_key=True)
content = Base.Column(Base.String) content = ext.Column(ext.String)
location = Base.Column(Base.String) # Todo: see if a dedicated class for locations is needed. This is likely. location = ext.Column(ext.String) # Todo: see if a dedicated class for locations is needed. This is likely.
poster = Base.Column(Base.String) # Todo: see if a dedicated class for posters is needed. poster = ext.Column(ext.String) # Todo: see if a dedicated class for posters is needed.
# Relationships # Relationships
repositories = Base.relationship("Composed", back_populates="tweet", cascade="all, delete") repositories = ext.relationship("Composed", back_populates="tweet", cascade="all, delete")
conditions = Base.relationship("Contains", back_populates="tweet", cascade="all, delete") conditions = ext.relationship("Contains", back_populates="tweet", cascade="all, delete")

View file

@ -2,18 +2,18 @@
This module defines the User database class. This module defines the User database class.
""" """
from ..base import Base from ..base import ext
class User(Base.Model): class User(ext.Model):
__tablename__ = "user" __tablename__ = "user"
email = Base.Column(Base.String, primary_key=True) email = ext.Column(ext.String, primary_key=True)
username = Base.Column(Base.String, nullable=False) username = ext.Column(ext.String, nullable=False)
password = Base.Column(Base.LargeBinary, nullable=False) password = ext.Column(ext.LargeBinary, nullable=False)
isAdmin = Base.Column(Base.Boolean, default=False) isAdmin = ext.Column(ext.Boolean, default=False)
# Relationships # Relationships
owner_of = Base.relationship("Repository", back_populates="owner", cascade="all, delete") owner_of = ext.relationship("Repository", back_populates="owner", cascade="all, delete")
authorizations = Base.relationship("Authorization", back_populates="user", cascade="all, delete") authorizations = ext.relationship("Authorization", back_populates="user", cascade="all, delete")
def to_json(self): def to_json(self):
return {'email': self.email, 'username': self.username, 'isAdmin': self.isAdmin} return {'email': self.email, 'username': self.username, 'isAdmin': self.isAdmin}

View file

@ -9,7 +9,7 @@ from .database import *
import bcrypt import bcrypt
import functools import functools
from flask_jwt_extended import get_jwt_identity from flask_jwt_extended import get_jwt_identity
from flask import request, jsonify from flask import jsonify
def authenticate(username, password): def authenticate(username, password):

View file

@ -123,9 +123,9 @@ def page_condition(cid):
if content := request.json.get("content"): if content := request.json.get("content"):
condition.content = content condition.content = content
Base.session.commit() ext.session.commit()
return json_success(condition.to_json()), 200 return json_success(condition.to_json()), 200
if request.method == "DELETE": if request.method == "DELETE":
Base.session.delete(condition) ext.session.delete(condition)
Base.session.commit() ext.session.commit()
return json_success("Deleted."), 200 return json_success("Deleted."), 200

View file

@ -1,7 +1,8 @@
from flask import render_template, abort, jsonify, request from flask import request
from nest_backend.database import *
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from nest_backend.gestione import * from nest_backend.gestione import repository_auth, json_error, json_success, ConditionType, Condition, Repository, \
find_user, get_jwt_identity
from nest_backend.database import ext as extension_sqlalchemy
from flask_cors import cross_origin from flask_cors import cross_origin
@ -91,7 +92,7 @@ def page_repository_conditions(rid):
return json_error("Missing `content` parameter."), 400 return json_error("Missing `content` parameter."), 400
condition = Condition(content=content, type=type_, repository_id=rid) condition = Condition(content=content, type=type_, repository_id=rid)
Base.session.add(condition) extension_sqlalchemy.session.add(condition)
Base.session.commit() extension_sqlalchemy.session.commit()
return json_success(condition.to_json()), 200 return json_success(condition.to_json()), 200

View file

@ -77,6 +77,6 @@ def page_repositories():
if not name: if not name:
return json_error("Missing one or more parameters"), 400 return json_error("Missing one or more parameters"), 400
repository = Repository(name=name, owner_id=user.email) repository = Repository(name=name, owner_id=user.email)
Base.session.add(repository) ext.session.add(repository)
Base.session.commit() ext.session.commit()
return json_success(repository.to_json()), 200 return json_success(repository.to_json()), 200

View file

@ -174,16 +174,16 @@ def page_repository(rid):
except KeyError: except KeyError:
return json_error("Unknown `type` specified."), 400 return json_error("Unknown `type` specified."), 400
repository.evaluation_mode = evaluation_mode repository.evaluation_mode = evaluation_mode
Base.session.commit() ext.session.commit()
return json_success(repository.to_json()), 200 return json_success(repository.to_json()), 200
elif request.method == "DELETE": elif request.method == "DELETE":
if repository.owner_id != user.email and not user.isAdmin: if repository.owner_id != user.email and not user.isAdmin:
return json_error("You are not the owner of this repository."), 403 return json_error("You are not the owner of this repository."), 403
try: try:
Base.session.delete(repository) ext.session.delete(repository)
Base.session.commit() ext.session.commit()
except Exception as e: except Exception as e:
Base.session.rollback() ext.session.rollback()
return json_error("Cant delete repository because of dependencies."), 500 return json_error("Cant delete repository because of dependencies."), 500
return json_success("Success"), 200 return json_success("Success"), 200
elif request.method == "PUT": elif request.method == "PUT":
@ -200,8 +200,8 @@ def page_repository(rid):
# Delete no longer needed conditions. # Delete no longer needed conditions.
for c in repository.conditions: for c in repository.conditions:
if c.id not in ids: if c.id not in ids:
Base.session.delete(c) ext.session.delete(c)
Base.session.commit() ext.session.commit()
# Create brand new conditions # Create brand new conditions
for c in request.json['conditions']: for c in request.json['conditions']:
if not c['id']: if not c['id']:
@ -210,6 +210,6 @@ def page_repository(rid):
type_ = ConditionType(type_) type_ = ConditionType(type_)
except KeyError: except KeyError:
return json_error("Unknown `type` specified."), 400 return json_error("Unknown `type` specified."), 400
Base.session.add(Condition(type=type_, content=c['content'], repository_id=rid)) ext.session.add(Condition(type=type_, content=c['content'], repository_id=rid))
Base.session.commit() ext.session.commit()
return json_success(repository.to_json()), 200 return json_success(repository.to_json()), 200

View file

@ -127,11 +127,11 @@ def page_user(email):
return json_error("User is not admin."), 403 return json_error("User is not admin."), 403
if user == target: if user == target:
return json_error("The user cant delete himself. Its a sin."), 406 return json_error("The user cant delete himself. Its a sin."), 406
Base.session.delete(target) ext.session.delete(target)
try: try:
Base.session.commit() ext.session.commit()
except Exception: except Exception:
Base.session.rollback() ext.session.rollback()
return json_error("Could not delete the user."), 500 return json_error("Could not delete the user."), 500
return json_success("The user has been deleted.") return json_success("The user has been deleted.")
elif request.method == "PATCH": elif request.method == "PATCH":
@ -142,5 +142,5 @@ def page_user(email):
target.username = request.json.get("username") target.username = request.json.get("username")
if request.json.get("password"): if request.json.get("password"):
target.password = gen_password(request.json.get("password")) target.password = gen_password(request.json.get("password"))
Base.session.commit() ext.session.commit()
return json_success(target.to_json()) return json_success(target.to_json())

View file

@ -68,6 +68,6 @@ def page_users():
return json_error("User is not admin. Thou art not authorized."), 403 return json_error("User is not admin. Thou art not authorized."), 403
new_user = User(email=request.json.get("email"), password=gen_password(request.json.get("password")), new_user = User(email=request.json.get("email"), password=gen_password(request.json.get("password")),
username=request.json.get("username")) username=request.json.get("username"))
Base.session.add(new_user) ext.session.add(new_user)
Base.session.commit() ext.session.commit()
return json_success(new_user.to_json()) return json_success(new_user.to_json())

View file

@ -2,8 +2,7 @@
from flask_swagger_ui import get_swaggerui_blueprint from flask_swagger_ui import get_swaggerui_blueprint
SWAGGER_URL = '/docs/' SWAGGER_URL = '/docs'
# FIXME: this will break if the website root isn't /, use url_for() instead!
API_URL = "/docs/swagger.json" API_URL = "/docs/swagger.json"
# Call factory function to create our blueprint # Call factory function to create our blueprint

View file

@ -0,0 +1,7 @@
"""
Pytest configuration file.
Import global fixtures here.
"""
from fixtures.flask_client import flask_client

View file

@ -0,0 +1,37 @@
import pytest
import uuid
from nest_backend.app import app
from nest_backend.gestione import gen_password
from nest_backend.database import ext
from nest_backend.database.tables import User
@pytest.fixture(scope="package", autouse=True)
def flask_client():
# Enter testing mode
app.config["TESTING"] = True
# Get environment variables
app.config.from_envvar("FLASK_CONFIG")
# Initialize database
with app.app_context():
# Use unique db schemas for each test session
uniq_schema = f"test_{uuid.uuid4()}"
ext.engine.execute(f"""CREATE SCHEMA "{uniq_schema}";""")
for table in ext.Model.metadata.tables.values():
table.schema = uniq_schema
ext.create_all(app=app)
if not User.query.filter_by(isAdmin=True).all():
ext.session.add(
User(email="admin@admin.com", password=gen_password("password"), username="admin", isAdmin=True))
ext.session.commit()
# Prepare test client
with app.test_client(use_cookies=False) as client:
yield client
# Teardown schema
with app.app_context():
ext.engine.execute(f"""DROP SCHEMA "{uniq_schema}" CASCADE;""")

View file

@ -0,0 +1,9 @@
import pytest
from flask.testing import Client
from nest_backend.test.fixtures import flask_client
# noinspection PyShadowingNames
def test_doa(flask_client: Client):
response = flask_client.get("/doa")
assert b"If you see this, the server is fine." in response.data

View file

@ -7,7 +7,7 @@ from nltk.corpus import stopwords
Base.init_app(app=app) ext.init_app(app=app)
def authenticate(): def authenticate():
c_k = "GEhtSyP9e98mzFeiOCSW0lvQX" c_k = "GEhtSyP9e98mzFeiOCSW0lvQX"
@ -81,5 +81,5 @@ if __name__ == "__main__":
search_repo_conditions() search_repo_conditions()
#print(stopwords.words('italian')) #print(stopwords.words('italian'))
with app.app_context(): with app.app_context():
Base.create_all(app=app) ext.create_all(app=app)
#start_exploring() #start_exploring()