From 2e72c9f8185210c70174c8013b07c2477646d3d1 Mon Sep 17 00:00:00 2001 From: Scot Hacker Date: Mon, 18 Mar 2019 23:56:55 -0700 Subject: [PATCH] General test suite for CSV importer --- test_settings.py | 2 +- todo/migrations/0009_priority_optional.py | 22 +++++++ todo/models.py | 4 +- todo/operations/csv_importer.py | 23 +++---- todo/tests/data/csv_import_data.csv | 4 ++ todo/tests/test_import.py | 76 +++++++++++++++++++++++ 6 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 todo/migrations/0009_priority_optional.py create mode 100644 todo/tests/data/csv_import_data.csv create mode 100644 todo/tests/test_import.py diff --git a/test_settings.py b/test_settings.py index 14068d3..81007c8 100644 --- a/test_settings.py +++ b/test_settings.py @@ -80,7 +80,7 @@ LOGGING = { }, 'django': { 'handlers': ['console'], - 'level': 'DEBUG', + 'level': 'WARNING', 'propagate': True, }, 'django.request': { diff --git a/todo/migrations/0009_priority_optional.py b/todo/migrations/0009_priority_optional.py new file mode 100644 index 0000000..108ec30 --- /dev/null +++ b/todo/migrations/0009_priority_optional.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.7 on 2019-03-18 23:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo', '0008_mail_tracker'), + ] + + operations = [ + migrations.AlterModelOptions( + name='task', + options={'ordering': ['priority', 'created_date']}, + ), + migrations.AlterField( + model_name='task', + name='priority', + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/todo/models.py b/todo/models.py index b378b02..80eb3d1 100644 --- a/todo/models.py +++ b/todo/models.py @@ -82,7 +82,7 @@ class Task(models.Model): on_delete=models.CASCADE, ) note = models.TextField(blank=True, null=True) - priority = models.PositiveIntegerField() + priority = models.PositiveIntegerField(blank=True, null=True) # Has due date for an instance of this object passed? def overdue_status(self): @@ -115,7 +115,7 @@ class Task(models.Model): self.delete() class Meta: - ordering = ["priority"] + ordering = ["priority", "created_date"] class Comment(models.Model): diff --git a/todo/operations/csv_importer.py b/todo/operations/csv_importer.py index 9da4c84..9fb3b18 100644 --- a/todo/operations/csv_importer.py +++ b/todo/operations/csv_importer.py @@ -66,21 +66,22 @@ class CSVImporter: if newrow: # newrow at this point is fully validated, and all FK relations exist, # e.g. `newrow.get("Assigned To")`, is a Django User instance. + assignee = newrow.get("Assigned To") if newrow.get("Assigned To") else None + created_date = newrow.get("Created Date") if newrow.get("Created Date") else datetime.datetime.today() + due_date = newrow.get("Due Date") if newrow.get("Due Date") else None + priority = newrow.get("Priority") if newrow.get("Priority") else None + obj, created = Task.objects.update_or_create( created_by=newrow.get("Created By"), task_list=newrow.get("Task List"), title=newrow.get("Title"), defaults={ - "assigned_to": newrow.get("Assigned To") - if newrow.get("Assigned To") - else None, + "assigned_to": assignee, "completed": newrow.get("Completed"), - "created_date": newrow.get("Created Date") - if newrow.get("Created Date") - else None, - "due_date": newrow.get("Due Date") if newrow.get("Due Date") else None, + "created_date": created_date, + "due_date": due_date, "note": newrow.get("Note"), - "priority": newrow.get("Priority"), + "priority": priority, }, ) self.upsert_count += 1 @@ -90,7 +91,7 @@ class CSVImporter: ) self.upserts.append(msg) - self.summaries.append(f"\nProcessed {self.line_count} CSV rows") + self.summaries.append(f"Processed {self.line_count} CSV rows") self.summaries.append(f"Upserted {self.upsert_count} rows") self.summaries.append(f"Skipped {self.line_count - self.upsert_count} rows") @@ -117,6 +118,7 @@ class CSVImporter: # ####################### # If specified, Assignee must exist + assignee = None # Perfectly valid if row.get("Assigned To"): assigned = get_user_model().objects.filter(username=row.get("Assigned To")) if assigned.exists(): @@ -124,8 +126,6 @@ class CSVImporter: else: msg = f"Missing or invalid task assignee {row.get('Assigned To')}" row_errors.append(msg) - else: - assignee = None # Perfectly valid # ####################### # Group must exist @@ -134,6 +134,7 @@ class CSVImporter: except Group.DoesNotExist: msg = f"Could not find group {row.get('Group')}." row_errors.append(msg) + target_group = None # ####################### # Task creator must be in the target group diff --git a/todo/tests/data/csv_import_data.csv b/todo/tests/data/csv_import_data.csv new file mode 100644 index 0000000..22c0520 --- /dev/null +++ b/todo/tests/data/csv_import_data.csv @@ -0,0 +1,4 @@ +Title,Group,Task List,Created By,Created Date,Due Date,Completed,Assigned To,Note,Priority +Make dinner,Workgroup One,Zip,u1,,2019-06-14,No,u1,This is note one,3 +Bake bread,Workgroup One,Zip,u1,2012-03-14,,Yes,,, +Bring dessert,Workgroup Two,Zap,u2,2015-06-248,,,,This is note two,77 \ No newline at end of file diff --git a/todo/tests/test_import.py b/todo/tests/test_import.py new file mode 100644 index 0000000..0ab612f --- /dev/null +++ b/todo/tests/test_import.py @@ -0,0 +1,76 @@ +import datetime +from pathlib import Path + +import pytest +from django.contrib.auth import get_user_model + +from todo.models import Task, TaskList +from todo.operations.csv_importer import CSVImporter + + +""" +Exercise the "Import CSV" feature, which shares a functional module that serves +both the `import_csv` management command and the "Import CSV" web interface. +""" + + +@pytest.mark.django_db +@pytest.fixture +def import_setup(todo_setup): + app_path = Path(__file__).resolve().parent.parent + filepath = Path(app_path, "tests/data/csv_import_data.csv") + with filepath.open(mode="r", encoding="utf-8-sig") as fileobj: + importer = CSVImporter() + results = importer.upsert(fileobj, as_string_obj=True) + assert results + return {"results": results} + + +@pytest.mark.django_db +def test_setup(todo_setup): + """Confirm what we should have from conftest, prior to importing CSV.""" + assert TaskList.objects.all().count() == 2 + assert Task.objects.all().count() == 6 + + +@pytest.mark.django_db +def test_import(import_setup): + """Confirm that importing the CSV gave us two more rows (one should have been skipped)""" + assert Task.objects.all().count() == 8 # 2 out of 3 rows should have imported; one was an error + + +@pytest.mark.django_db +def test_report(import_setup): + """Confirm that importing the CSV returned expected report messaging.""" + + results = import_setup["results"] + + assert "Processed 3 CSV rows" in results["summaries"] + assert "Upserted 2 rows" in results["summaries"] + assert "Skipped 1 rows" in results["summaries"] + + assert isinstance(results["errors"], list) + assert len(results["errors"]) == 1 + assert ( + results["errors"][0].get(3)[0] + == "Could not convert Created Date 2015-06-248 to valid date instance" + ) + + assert ( + 'Upserted task 7: "Make dinner" in list "Zip" (group "Workgroup One")' in results["upserts"] + ) + assert ( + 'Upserted task 8: "Bake bread" in list "Zip" (group "Workgroup One")' in results["upserts"] + ) + + +@pytest.mark.django_db +def test_inserted_row(import_setup): + """Confirm that one inserted row is exactly right.""" + task = Task.objects.get(title="Make dinner", task_list__name="Zip") + assert task.created_by == get_user_model().objects.get(username="u1") + assert task.assigned_to == get_user_model().objects.get(username="u1") + assert not task.completed + assert task.note == "This is note one" + assert task.priority == 3 + assert task.created_date == datetime.datetime.today().date()