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:
parent
d7e55e556d
commit
008efa1c24
37 changed files with 324 additions and 192 deletions
23
.idea/runConfigurations/Backend__Run_tests.xml
Normal file
23
.idea/runConfigurations/Backend__Run_tests.xml
Normal 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="""" />
|
||||||
|
<option name="_new_parameters" value="""" />
|
||||||
|
<option name="_new_additionalArguments" value="""" />
|
||||||
|
<option name="_new_target" value=""nest_backend"" />
|
||||||
|
<option name="_new_targetType" value=""PYTHON"" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -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" />
|
5
code/backend/.gitignore
vendored
5
code/backend/.gitignore
vendored
|
@ -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]
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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")
|
|
@ -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")
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
7
code/backend/nest_backend/test/conftest.py
Normal file
7
code/backend/nest_backend/test/conftest.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
"""
|
||||||
|
Pytest configuration file.
|
||||||
|
|
||||||
|
Import global fixtures here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from fixtures.flask_client import flask_client
|
37
code/backend/nest_backend/test/fixtures/flask_client.py
vendored
Normal file
37
code/backend/nest_backend/test/fixtures/flask_client.py
vendored
Normal 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;""")
|
9
code/backend/nest_backend/test/test_doa.py
Normal file
9
code/backend/nest_backend/test/test_doa.py
Normal 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
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue