From 469c1baad46453ff8b47a5c435deeffd72d1453e Mon Sep 17 00:00:00 2001 From: Philomena Kelly Date: Fri, 13 May 2022 12:36:58 -0400 Subject: [PATCH] Added everything --- Procfile | 1 + ada-project-docs/wave_01.md | 2 +- app/__init__.py | 7 + app/models/goal.py | 30 ++++ app/models/task.py | 41 +++++ app/routes.py | 256 ++++++++++++++++++++++++++- migrations/README | 1 + migrations/alembic.ini | 45 +++++ migrations/env.py | 96 ++++++++++ migrations/script.py.mako | 24 +++ migrations/versions/876ff04f43fb_.py | 30 ++++ migrations/versions/9cfe63e2bc07_.py | 39 ++++ migrations/versions/e0f1eda3cf1b_.py | 28 +++ tests/test_wave_01.py | 32 ++-- tests/test_wave_02.py | 4 +- tests/test_wave_03.py | 22 +-- tests/test_wave_05.py | 92 +++++----- tests/test_wave_06.py | 15 +- 18 files changed, 684 insertions(+), 81 deletions(-) create mode 100644 Procfile create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/876ff04f43fb_.py create mode 100644 migrations/versions/9cfe63e2bc07_.py create mode 100644 migrations/versions/e0f1eda3cf1b_.py diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..62e430aca --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn 'app:create_app()' \ No newline at end of file diff --git a/ada-project-docs/wave_01.md b/ada-project-docs/wave_01.md index b23485a67..ff7786104 100644 --- a/ada-project-docs/wave_01.md +++ b/ada-project-docs/wave_01.md @@ -8,7 +8,7 @@ Tasks are entities that describe a task a user wants to complete. They contain a - title to name the task - description to hold details about the task -- an optional datetime that the task is completed on +- an optional datetime that the task is completed on Our goal for this wave is to be able to create, read, update, and delete different tasks. We will create RESTful routes for this different operations. diff --git a/app/__init__.py b/app/__init__.py index 2764c4cc8..c2e39232c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -31,4 +31,11 @@ def create_app(test_config=None): # Register Blueprints here + from .routes import tasks_bp + app.register_blueprint(tasks_bp) + + from .routes import goals_bp + app.register_blueprint(goals_bp) + + return app diff --git a/app/models/goal.py b/app/models/goal.py index b0ed11dd8..655bb99e0 100644 --- a/app/models/goal.py +++ b/app/models/goal.py @@ -1,5 +1,35 @@ from app import db +from datetime import datetime class Goal(db.Model): goal_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + tasks = db.relationship("Task", back_populates="goal") + + + def to_json(self, category=None): + + goal_dict = { + "id": self.goal_id, + "title": self.title, + } + + if category == "tasks": + goal_dict["tasks"] = [ + task.to_json() for task in self.tasks + ] + + return goal_dict + + + def update(self,req_body): + self.title = req_body["title"] + + + @classmethod + def create(cls,req_body): + new_goal = cls( + title=req_body['title'], + ) + return new_goal \ No newline at end of file diff --git a/app/models/task.py b/app/models/task.py index c91ab281f..c0ebd4e01 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -1,5 +1,46 @@ from app import db +from datetime import datetime class Task(db.Model): task_id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String) + description = db.Column(db.String) + completed_at = db.Column(db.DateTime, nullable=True) + is_complete = False + goal_id = db.Column(db.Integer, db.ForeignKey('goal.goal_id')) + goal = db.relationship("Goal", back_populates="tasks") + + + def to_json(self): + task_dict = { + "id": self.task_id, + "title": self.title, + "description": self.description, + "is_complete" : self.is_complete + } + + if self.goal_id: + task_dict["goal_id"] = self.goal_id + + return task_dict + + def written_out(cls): + return "task" + + def update(self,req_body): + self.title = req_body["title"] + self.description = req_body["description"] + + if req_body.get("completed_at"): + self.is_complete = True + self.completed_at = datetime.now() + + + @classmethod + def create(cls,req_body): + new_task = cls( + title=req_body['title'], + description=req_body['description'], + ) + return new_task \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index 3aae38d49..517e9560c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1 +1,255 @@ -from flask import Blueprint \ No newline at end of file +from os import abort +from app import db +from app.models.task import Task +from flask import Blueprint, jsonify, abort, make_response, request +from sqlalchemy.sql import text +from datetime import datetime +import requests +import os +from dotenv import load_dotenv +from app.models.goal import Goal + +tasks_bp = Blueprint("tasks_bp", __name__, url_prefix="/tasks") +goals_bp = Blueprint("goals_bp", __name__, url_prefix="/goals") + + +@goals_bp.route("//tasks", methods=["POST"]) +def create_task(goal_id): + + goal = validate_object("goal", goal_id) + + request_body = request.get_json() + + for task in request_body["task_ids"]: + task = validate_object("task", task) + task.goal_id = goal_id + + db.session.commit() + + tasks = [] + + for task in goal.tasks: + tasks.append(task.task_id) + + response = { + "id" : goal.goal_id, + "task_ids" : tasks + } + + return make_response(jsonify(response), 200) + + + +@goals_bp.route("//tasks", methods=["GET"]) +def get_tasks_by_goals(goal_id): + + goal = validate_object("goal", goal_id) + + response = goal.to_json(category="tasks") + + return make_response(jsonify(response), 200) + + + + + +@goals_bp.route("", methods=["POST"]) +def create_one_goal(): + + request_body = request.get_json() + + try: + new_goal = Goal.create(request_body) + except KeyError: + return make_response(jsonify({"details" : "Invalid data"}), 400) + + db.session.add(new_goal) + db.session.commit() + + response = { "goal": new_goal.to_json()} + + return make_response(response, 201) + + + +@goals_bp.route("", methods=["GET"]) +def read_all_goals(): + title_query = request.args.get("sort") + + if title_query: + goals = Goal.query.order_by(text(f'title {title_query}')) + else: + goals = Goal.query.all() + + goals_response = [] + for goal in goals: + goals_response.append( + goal.to_json() + ) + return make_response(jsonify(goals_response), 200) + +@goals_bp.route("/", methods=["GET"]) +def get_one_goal(goal_id): + goal = validate_object("goal", goal_id) + + response = { "goal": goal.to_json()} + + return make_response(response, 200) + + +@goals_bp.route("/", methods=["PUT"]) +def update_one_goal(goal_id): + + goal = validate_object("goal", goal_id) + + request_body = request.get_json() + + goal.update(request_body) + + db.session.commit() + + response = { "goal": goal.to_json()} + return make_response(response, 200) + + +@goals_bp.route("/", methods=["DELETE"]) +def delete_one_goal(goal_id): + + goal = validate_object("goal", goal_id) + + db.session.delete(goal) + db.session.commit() + + return make_response(jsonify({"details" : f"Goal {goal.goal_id} \"{goal.title}\" successfully deleted"})) + + +@tasks_bp.route("", methods=["POST"]) +def create_task(): + + request_body = request.get_json() + try: + new_task = Task.create(request_body) + except KeyError: + return make_response(jsonify({"details" : "Invalid data"}), 400) + + if request_body.get("completed_at"): + new_task.is_complete = True + new_task.completed_at = datetime.now() + + db.session.add(new_task) + db.session.commit() + + response = { "task": new_task.to_json()} + + return make_response(response, 201) + + + +@tasks_bp.route("", methods=["GET"]) +def read_all_tasks(): + title_query = request.args.get("sort") + + if title_query: + tasks = Task.query.order_by(text(f'title {title_query}')) + else: + tasks = Task.query.all() + + tasks_response = [] + for task in tasks: + tasks_response.append( + task.to_json() + ) + return make_response(jsonify(tasks_response), 200) + + +@tasks_bp.route("/", methods=["GET"]) +def get_one_task(task_id): + task = validate_object("task", task_id) + + response = { "task": task.to_json()} + + return make_response(response, 200) + + +@tasks_bp.route("/", methods=["PUT"]) +def update_one_task(task_id): + + task = validate_object("task", task_id) + + request_body = request.get_json() + + task.update(request_body) + + db.session.commit() + + response = { "task": task.to_json()} + return make_response(response, 200) + + + +@tasks_bp.route("/", methods=["DELETE"]) +def delete_one_task(task_id): + + task = validate_object("task", task_id) + + db.session.delete(task) + db.session.commit() + + return make_response(jsonify({"details" : f"Task {task.task_id} \"{task.title}\" successfully deleted"})) + + +@tasks_bp.route("/mark_complete", methods=["PATCH"]) +def mark_task_completed(task_id): + task = validate_object("task", task_id) + + task.is_complete = True + task.completed_at = datetime.now() + + db.session.commit() + + URL = "https://slack.com/api/chat.postMessage" + PARAMS = {'text' : f"Someone just completed the task {task.title}", + 'channel' : 'task-notifications'} + HEADERS = {'Authorization' : f'Bearer {os.environ.get("TASKLIST_BOT_KEY")}'} + r = requests.get(url = URL, params = PARAMS, headers = HEADERS) + + response = { "task": task.to_json()} + + return response, 200 + + +@tasks_bp.route("/mark_incomplete", methods=["PATCH"]) +def mark_task_incompleted(task_id): + task = validate_object("task", task_id) + + task.is_complete = False + task.completed_at = None + + db.session.commit() + + response = { "task": task.to_json()} + + return response, 200 + + + + + +def validate_object(object_type, object_id): + + try: + object_id = int(object_id) + except: + abort(make_response({"message":f"{object_type} {object_id} invalid"}, 400)) + + if object_type == "task": + item = Task.query.get(object_id) + elif object_type == "goal": + item = Goal.query.get(object_id) + + if not item: + abort(make_response({"message":f"{object_type} {object_id} not found"}, 404)) + + return item + + diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 000000000..f8ed4801f --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..8b3fb3353 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,96 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/876ff04f43fb_.py b/migrations/versions/876ff04f43fb_.py new file mode 100644 index 000000000..0c364fc13 --- /dev/null +++ b/migrations/versions/876ff04f43fb_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 876ff04f43fb +Revises: e0f1eda3cf1b +Create Date: 2022-05-13 10:33:52.399987 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '876ff04f43fb' +down_revision = 'e0f1eda3cf1b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('task', sa.Column('goal_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'task', 'goal', ['goal_id'], ['goal_id']) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'task', type_='foreignkey') + op.drop_column('task', 'goal_id') + # ### end Alembic commands ### diff --git a/migrations/versions/9cfe63e2bc07_.py b/migrations/versions/9cfe63e2bc07_.py new file mode 100644 index 000000000..14209dce7 --- /dev/null +++ b/migrations/versions/9cfe63e2bc07_.py @@ -0,0 +1,39 @@ +"""empty message + +Revision ID: 9cfe63e2bc07 +Revises: +Create Date: 2022-05-06 14:22:22.711965 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9cfe63e2bc07' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('goal', + sa.Column('goal_id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('goal_id') + ) + op.create_table('task', + sa.Column('task_id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('completed_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('task_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task') + op.drop_table('goal') + # ### end Alembic commands ### diff --git a/migrations/versions/e0f1eda3cf1b_.py b/migrations/versions/e0f1eda3cf1b_.py new file mode 100644 index 000000000..bdbd50f0c --- /dev/null +++ b/migrations/versions/e0f1eda3cf1b_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: e0f1eda3cf1b +Revises: 9cfe63e2bc07 +Create Date: 2022-05-12 14:26:20.170931 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e0f1eda3cf1b' +down_revision = '9cfe63e2bc07' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('goal', sa.Column('title', sa.String(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('goal', 'title') + # ### end Alembic commands ### diff --git a/tests/test_wave_01.py b/tests/test_wave_01.py index dca626d78..5cb4c2e69 100644 --- a/tests/test_wave_01.py +++ b/tests/test_wave_01.py @@ -2,7 +2,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_no_saved_tasks(client): # Act response = client.get("/tasks") @@ -13,7 +13,7 @@ def test_get_tasks_no_saved_tasks(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_one_saved_tasks(client, one_task): # Act response = client.get("/tasks") @@ -32,7 +32,7 @@ def test_get_tasks_one_saved_tasks(client, one_task): ] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_task(client, one_task): # Act response = client.get("/tasks/1") @@ -51,7 +51,7 @@ def test_get_task(client, one_task): } -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_not_found(client): # Act response = client.get("/tasks/1") @@ -59,14 +59,13 @@ def test_get_task_not_found(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") + assert response_body == {"message":"task 1 not found"} # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task(client): # Act response = client.post("/tasks", json={ @@ -93,7 +92,7 @@ def test_create_task(client): assert new_task.completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_update_task(client, one_task): # Act response = client.put("/tasks/1", json={ @@ -119,7 +118,7 @@ def test_update_task(client, one_task): assert task.completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_update_task_not_found(client): # Act response = client.put("/tasks/1", json={ @@ -130,14 +129,13 @@ def test_update_task_not_found(client): # Assert assert response.status_code == 404 - - raise Exception("Complete test with assertion about response body") - # ***************************************************************** + assert response_body == {"message":"task 1 not found"} + # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task(client, one_task): # Act response = client.delete("/tasks/1") @@ -152,7 +150,7 @@ def test_delete_task(client, one_task): assert Task.query.get(1) == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_task_not_found(client): # Act response = client.delete("/tasks/1") @@ -161,7 +159,7 @@ def test_delete_task_not_found(client): # Assert assert response.status_code == 404 - raise Exception("Complete test with assertion about response body") + assert response_body == {"message":"task 1 not found"} # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** @@ -169,7 +167,7 @@ def test_delete_task_not_found(client): assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_title(client): # Act response = client.post("/tasks", json={ @@ -186,7 +184,7 @@ def test_create_task_must_contain_title(client): assert Task.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_must_contain_description(client): # Act response = client.post("/tasks", json={ diff --git a/tests/test_wave_02.py b/tests/test_wave_02.py index a087e0909..651e3aebd 100644 --- a/tests/test_wave_02.py +++ b/tests/test_wave_02.py @@ -1,7 +1,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_asc(client, three_tasks): # Act response = client.get("/tasks?sort=asc") @@ -29,7 +29,7 @@ def test_get_tasks_sorted_asc(client, three_tasks): ] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_sorted_desc(client, three_tasks): # Act response = client.get("/tasks?sort=desc") diff --git a/tests/test_wave_03.py b/tests/test_wave_03.py index 959176ceb..e95ae3e0b 100644 --- a/tests/test_wave_03.py +++ b/tests/test_wave_03.py @@ -5,7 +5,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_incomplete_task(client, one_task): # Arrange """ @@ -42,7 +42,7 @@ def test_mark_complete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_complete_task(client, completed_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -62,7 +62,7 @@ def test_mark_incomplete_on_complete_task(client, completed_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_on_completed_task(client, completed_task): # Arrange """ @@ -99,7 +99,7 @@ def test_mark_complete_on_completed_task(client, completed_task): assert Task.query.get(1).completed_at -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_on_incomplete_task(client, one_task): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -119,22 +119,24 @@ def test_mark_incomplete_on_incomplete_task(client, one_task): assert Task.query.get(1).completed_at == None -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_complete_missing_task(client): # Act response = client.patch("/tasks/1/mark_complete") response_body = response.get_json() # Assert + assert response.status_code == 404 + assert response_body == {"message":"task 1 not found"} + - raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_mark_incomplete_missing_task(client): # Act response = client.patch("/tasks/1/mark_incomplete") @@ -142,8 +144,8 @@ def test_mark_incomplete_missing_task(client): # Assert assert response.status_code == 404 + assert response_body == {"message":"task 1 not found"} - raise Exception("Complete test with assertion about response body") # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** @@ -151,7 +153,7 @@ def test_mark_incomplete_missing_task(client): # Let's add this test for creating tasks, now that # the completion functionality has been implemented -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_task_with_valid_completed_at(client): # Act response = client.post("/tasks", json={ @@ -181,7 +183,7 @@ def test_create_task_with_valid_completed_at(client): # Let's add this test for updating tasks, now that # the completion functionality has been implemented -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_update_task_with_completed_at_date(client, completed_task): # Act response = client.put("/tasks/1", json={ diff --git a/tests/test_wave_05.py b/tests/test_wave_05.py index aee7c52a1..40487c000 100644 --- a/tests/test_wave_05.py +++ b/tests/test_wave_05.py @@ -1,7 +1,7 @@ import pytest +from app.models.goal import Goal - -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_no_saved_goals(client): # Act response = client.get("/goals") @@ -12,7 +12,7 @@ def test_get_goals_no_saved_goals(client): assert response_body == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goals_one_saved_goal(client, one_goal): # Act response = client.get("/goals") @@ -29,7 +29,7 @@ def test_get_goals_one_saved_goal(client, one_goal): ] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_goal(client, one_goal): # Act response = client.get("/goals/1") @@ -46,22 +46,19 @@ def test_get_goal(client, one_goal): } -@pytest.mark.skip(reason="test to be completed by student") +# @pytest.mark.skip(reason="test to be completed by student") def test_get_goal_not_found(client): pass # Act response = client.get("/goals/1") response_body = response.get_json() - raise Exception("Complete test") - # Assert - # ---- Complete Test ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Test ---- + assert response.status_code == 404 + assert response_body == {"message":"goal 1 not found"} + -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal(client): # Act response = client.post("/goals", json={ @@ -80,34 +77,47 @@ def test_create_goal(client): } -@pytest.mark.skip(reason="test to be completed by student") +# @pytest.mark.skip(reason="test to be completed by student") def test_update_goal(client, one_goal): - raise Exception("Complete test") - # Act + # Act # ---- Complete Act Here ---- - + response = client.put("/goals/1", json={ + "title": "Updated Goal Title", + }) + response_body = response.get_json() # Assert # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # assertion 3 goes here + assert response.status_code == 200 + assert "goal" in response_body + assert response_body == { + "goal" : { + "id" : 1, + "title" : "Updated Goal Title" + } + } # ---- Complete Assertions Here ---- + goal = Goal.query.get(1) + assert goal.title == "Updated Goal Title" - -@pytest.mark.skip(reason="test to be completed by student") +# @pytest.mark.skip(reason="test to be completed by student") def test_update_goal_not_found(client): - raise Exception("Complete test") + # raise Exception("Complete test") # Act # ---- Complete Act Here ---- - + response = client.put("/goals/1", json={ + "title": "Updated Goal Title", + }) + response_body = response.get_json() + # Assert # Assert # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here + assert response.status_code == 404 + assert response_body == {"message":"goal 1 not found"} + # ---- Complete Assertions Here ---- -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_delete_goal(client, one_goal): # Act response = client.delete("/goals/1") @@ -121,30 +131,26 @@ def test_delete_goal(client, one_goal): } # Check that the goal was deleted - response = client.get("/goals/1") - assert response.status_code == 404 + assert Goal.query.get(1) == None + - raise Exception("Complete test with assertion about response body") - # ***************************************************************** - # **Complete test with assertion about response body*************** - # ***************************************************************** - - -@pytest.mark.skip(reason="test to be completed by student") +# @pytest.mark.skip(reason="test to be completed by student") def test_delete_goal_not_found(client): - raise Exception("Complete test") - # Act - # ---- Complete Act Here ---- + response = client.delete("/goals/1") + response_body = response.get_json() # Assert - # ---- Complete Assertions Here ---- - # assertion 1 goes here - # assertion 2 goes here - # ---- Complete Assertions Here ---- + assert response.status_code == 404 + + assert response_body == {"message":"goal 1 not found"} + # ***************************************************************** + # **Complete test with assertion about response body*************** + # ***************************************************************** + assert Goal.query.all() == [] -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_create_goal_missing_title(client): # Act response = client.post("/goals", json={}) diff --git a/tests/test_wave_06.py b/tests/test_wave_06.py index 8afa4325e..518464bc3 100644 --- a/tests/test_wave_06.py +++ b/tests/test_wave_06.py @@ -2,7 +2,7 @@ import pytest -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal(client, one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -23,7 +23,7 @@ def test_post_task_ids_to_goal(client, one_goal, three_tasks): assert len(Goal.query.get(1).tasks) == 3 -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_one_goal, three_tasks): # Act response = client.post("/goals/1/tasks", json={ @@ -42,7 +42,7 @@ def test_post_task_ids_to_goal_already_with_goals(client, one_task_belongs_to_on assert len(Goal.query.get(1).tasks) == 2 -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_goal(client): # Act response = client.get("/goals/1/tasks") @@ -51,13 +51,14 @@ def test_get_tasks_for_specific_goal_no_goal(client): # Assert assert response.status_code == 404 - raise Exception("Complete test with assertion about response body") + assert response_body == {"message":"goal 1 not found"} + # ***************************************************************** # **Complete test with assertion about response body*************** # ***************************************************************** -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): # Act response = client.get("/goals/1/tasks") @@ -74,7 +75,7 @@ def test_get_tasks_for_specific_goal_no_tasks(client, one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): # Act response = client.get("/goals/1/tasks") @@ -99,7 +100,7 @@ def test_get_tasks_for_specific_goal(client, one_task_belongs_to_one_goal): } -@pytest.mark.skip(reason="No way to test this feature yet") +# @pytest.mark.skip(reason="No way to test this feature yet") def test_get_task_includes_goal_id(client, one_task_belongs_to_one_goal): response = client.get("/tasks/1") response_body = response.get_json()