diff --git a/ordr3/models.py b/ordr3/models.py index cfad789..e57d036 100644 --- a/ordr3/models.py +++ b/ordr3/models.py @@ -4,6 +4,7 @@ from collections import namedtuple VendorAggregate = namedtuple("VendorAggregate", ["name", "terms"]) + @enum.unique class OrderStatus(enum.Enum): OPEN = 1 @@ -160,6 +161,7 @@ class Vendor(Model): self.term = term self.name = name + class User(Model): id = None diff --git a/ordr3/repo.py b/ordr3/repo.py index acf8207..5257b7c 100644 --- a/ordr3/repo.py +++ b/ordr3/repo.py @@ -21,6 +21,7 @@ class AbstractOrderRepository(abc.ABC): def __init__(self, session): self.session = session + @abc.abstractmethod def add_order(self, order): """ add an order to the datastore """ @@ -33,8 +34,8 @@ class AbstractOrderRepository(abc.ABC): """ remove an order from the datastore """ @abc.abstractmethod - def list_orders(self): - """ list orders orderd by date, descending """ + def list_consumable_candidates(self, limit_date, statuses): + """ list orders that might be consumables """ @abc.abstractmethod def add_user(self, user): @@ -56,10 +57,6 @@ class AbstractOrderRepository(abc.ABC): def get_user_by_email(self, reference): """ get a user from the datastore by email """ - @abc.abstractmethod - def list_users(self): - """ list users orderd by username """ - @abc.abstractmethod def search_vendor(self, reference): """ search for a vendor by its canonical name """ @@ -119,14 +116,6 @@ class SqlAlchemyRepository(AbstractOrderRepository): except NoResultFound as exc: raise RepoItemNotFound from exc - def list_orders(self): - """ list orders orderd by date, descending """ - return ( - self.session.query(models.OrderItem) - .order_by(models.OrderItem.created_on.desc()) - .all() - ) - def list_consumable_candidates(self, limit_date, statuses): """ list orders that might be consumables """ return ( @@ -176,14 +165,6 @@ class SqlAlchemyRepository(AbstractOrderRepository): except NoResultFound as exc: raise RepoItemNotFound from exc - def list_users(self): - """ list users orderd by username """ - return ( - self.session.query(models.User) - .order_by(models.User.username) - .all() - ) - def count_new_users(self): """ count the number of new users that need approval """ return ( @@ -202,10 +183,12 @@ class SqlAlchemyRepository(AbstractOrderRepository): def get_vendor_aggregates(self, reference): """ list a all canonical names of vendors """ - vendors = self.session.query(models.Vendor).filter_by(name=reference).all() + vendors = ( + self.session.query(models.Vendor).filter_by(name=reference).all() + ) if not vendors: raise RepoItemNotFound - terms = sorted((v.term for v in vendors), key=lambda x:x.lower()) + terms = sorted((v.term for v in vendors), key=lambda x: x.lower()) return models.VendorAggregate(vendors[0].name, terms) def add_reset_token(self, token): diff --git a/ordr3/resources.py b/ordr3/resources.py index 50519cc..5f422ee 100644 --- a/ordr3/resources.py +++ b/ordr3/resources.py @@ -162,7 +162,6 @@ class Root(BaseResource): ] - def includeme(config): """ Initialize the resources for traversal in a Pyramid app. diff --git a/ordr3/views/root.py b/ordr3/views/root.py index 2ed218e..9cc1d76 100644 --- a/ordr3/views/root.py +++ b/ordr3/views/root.py @@ -9,8 +9,8 @@ from pyramid.view import ( from pyramid.httpexceptions import HTTPFound -#@forbidden_view_config() -#@notfound_view_config() +@forbidden_view_config() +@notfound_view_config() @view_config( context="ordr3:resources.Root", permission="view", ) diff --git a/ordr3/views/vendors.py b/ordr3/views/vendors.py index b94a0c0..2a044b0 100644 --- a/ordr3/views/vendors.py +++ b/ordr3/views/vendors.py @@ -1,10 +1,13 @@ -import deform +# import deform from sqlalchemy import func -from pyramid.csrf import get_csrf_token + +# from pyramid.csrf import get_csrf_token from pyramid.view import view_config -from pyramid.httpexceptions import HTTPFound -from .. import events, models, services, resources +# from .. import events, models, services, resources +from .. import models + +# from pyramid.httpexceptions import HTTPFound @view_config( @@ -15,6 +18,11 @@ from .. import events, models, services, resources ) def vendor_list(context, request): - vendors = request.repo.session.query(models.Vendor.name).distinct(models.Vendor.name).order_by(func.lower(models.Vendor.name)).all() + vendors = ( + request.repo.session.query(models.Vendor.name) + .distinct(models.Vendor.name) + .order_by(func.lower(models.Vendor.name)) + .all() + ) - return {"vendors":vendors} + return {"vendors": vendors} diff --git a/tests/test_repo.py b/tests/test_repo.py index 64bb1d6..984e218 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -2,12 +2,12 @@ import pytest @pytest.fixture() -def example_orders(): +def example_order_data(): from datetime import datetime - from ordr3.models import OrderItem, OrderCategory, OrderStatus + from ordr3.models import OrderCategory, OrderStatus return [ - OrderItem( + ( 1, "Ethanol", "1-23", @@ -23,7 +23,7 @@ def example_orders(): "me", OrderStatus.OPEN, ), - OrderItem( + ( 2, "Gloves", "12-3", @@ -37,11 +37,34 @@ def example_orders(): "no comment", datetime(2020, 2, 4, 15, 14, 13), "you", - OrderStatus.APPROVAL, + OrderStatus.COMPLETED, ), ] +@pytest.fixture() +def example_orders(example_order_data): + from ordr3.models import OrderItem + + return [OrderItem(*data) for data in example_order_data] + + +@pytest.fixture() +def example_consumables(example_order_data): + from datetime import datetime, timedelta + from ordr3.models import OrderItem + + consumables = [] + order_data = example_order_data * 3 + for i, data in enumerate(order_data, start=1): + order = OrderItem(*data) + order.id = i + order.created_on = datetime.utcnow() - timedelta(days=i * 150) + consumables.append(order) + + return consumables + + @pytest.fixture() def example_users(): from ordr3.models import User, UserRole @@ -82,7 +105,7 @@ def test_sql_repo_add_order(session, example_orders): def test_sql_repo_delete_order(session, example_orders): from ordr3.repo import SqlAlchemyRepository from ordr3.services import create_log_entry - from ordr3.models import LogEntry, OrderStatus, User + from ordr3.models import LogEntry, OrderStatus, User, OrderItem repo = SqlAlchemyRepository(session) repo.add_order(example_orders[0]) @@ -95,7 +118,7 @@ def test_sql_repo_delete_order(session, example_orders): repo.delete_order(example_orders[0]) session.flush() - assert repo.list_orders() == [example_orders[1]] + assert session.query(OrderItem).all() == [example_orders[1]] assert session.query(LogEntry).all() == example_orders[1].log @@ -121,16 +144,24 @@ def test_sql_repo_get_order_raises_exception(session, example_orders): repo.get_order(2) -def test_sql_repo_list_orders(session, example_orders): +def test_sql_list_consumable_candidates(session, example_consumables): from ordr3.repo import SqlAlchemyRepository + from ordr3.models import OrderStatus + from datetime import datetime, timedelta - earlier, later = example_orders repo = SqlAlchemyRepository(session) - repo.add_order(earlier) - repo.add_order(later) + for example in example_consumables: + repo.add_order(example) + print(example.id, example.created_on) session.flush() - assert repo.list_orders() == [later, earlier] + limit_date = datetime.utcnow() - timedelta(days=2 * 365) + states = {OrderStatus.COMPLETED} + + result = repo.list_consumable_candidates(limit_date, states) + + assert len(result) == 2 + assert [o.id for o in result] == [2, 4] def test_sql_repo_add_user(session, example_users): @@ -148,6 +179,7 @@ def test_sql_repo_add_user(session, example_users): def test_sql_repo_delte_user(session, example_users): from ordr3.repo import SqlAlchemyRepository + from ordr3.models import User repo = SqlAlchemyRepository(session) repo.add_user(example_users[0]) @@ -156,7 +188,7 @@ def test_sql_repo_delte_user(session, example_users): repo.delete_user(example_users[0]) - assert repo.list_users() == [example_users[1]] + assert session.query(User).all() == [example_users[1]] def test_sql_repo_get_user(session, example_users): @@ -225,18 +257,6 @@ def test_sql_repo_get_user_by_email_exception(session, example_users): repo.get_user_by_email("unknown email") -def test_sql_repo_list_users(session, example_users): - from ordr3.repo import SqlAlchemyRepository - - later, earlier = example_users - repo = SqlAlchemyRepository(session) - repo.add_user(later) - repo.add_user(earlier) - session.flush() - - assert repo.list_users() == [earlier, later] - - def test_sql_repo_count_new_users(session, example_users): from ordr3.repo import SqlAlchemyRepository from ordr3.models import UserRole @@ -255,14 +275,43 @@ def test_sql_search_vendor(session, example_users): from ordr3.models import Vendor repo = SqlAlchemyRepository(session) - rep = Vendor("sa", "Sigma Aldrich") - session.add(rep) + entry = Vendor("sa", "Sigma Aldrich") + session.add(entry) session.flush() - assert repo.search_vendor("sa") == "Sigma Aldrich" + assert repo.search_vendor("sa") == entry assert repo.search_vendor("unknown") is None +def test_sql_get_vendor_aggregates(session): + from ordr3.repo import SqlAlchemyRepository + from ordr3.models import Vendor, VendorAggregate + + repo = SqlAlchemyRepository(session) + entry_1 = Vendor("sb", "Sigma Aldrich") + entry_2 = Vendor("sa", "Sigma Aldrich") + entry_3 = Vendor("vwr", "VWR") + session.add(entry_1) + session.add(entry_2) + session.add(entry_3) + session.flush() + + result = repo.get_vendor_aggregates("Sigma Aldrich") + + assert isinstance(result, VendorAggregate) + assert result.name == "Sigma Aldrich" + assert result.terms == ["sa", "sb"] + + +def test_sql_get_vendor_aggregates_raises_error(session): + from ordr3.repo import SqlAlchemyRepository, RepoItemNotFound + + repo = SqlAlchemyRepository(session) + + with pytest.raises(RepoItemNotFound): + repo.get_vendor_aggregates("Sigma Aldrich") + + def test_sql_repo_add_reset_token(session, example_tokens): from ordr3.repo import SqlAlchemyRepository from ordr3.models import PasswordResetToken @@ -315,7 +364,7 @@ def test_sql_repo_get_reset_token_raises_exception(session, example_tokens): repo.get_reset_token("unknown token") -def test_clear_stale_reset_tokens(session, example_tokens): +def test_sql_clear_stale_reset_tokens(session, example_tokens): from ordr3.repo import SqlAlchemyRepository from ordr3.models import PasswordResetToken diff --git a/tests/test_services.py b/tests/test_services.py index 46e4e25..8c46bff 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta import pytest from ordr3.repo import AbstractOrderRepository +from ordr3.models import VendorAggregate class FakeOrderRepository(AbstractOrderRepository): @@ -11,7 +12,7 @@ class FakeOrderRepository(AbstractOrderRepository): def __init__(self, session): self._orders = set() self._users = set() - self._vendors = {"sa": "Sigma Aldrich"} + self._vendors = set() self._tokens = set() def add_order(self, order): @@ -26,10 +27,6 @@ class FakeOrderRepository(AbstractOrderRepository): """ removes a user from the datastore """ self._orders.remove(order) - def list_orders(self): - """ list orders, sorted by descending creation date """ - return sorted(self._orders, reverse=True, key=lambda x: x.created_on) - def list_consumable_candidates(self, limit_date, statuses): """ list orders, sorted by descending creation date """ newer_orders = (o for o in self._orders if o.created_on > limit_date) @@ -56,13 +53,19 @@ class FakeOrderRepository(AbstractOrderRepository): """ retrieve a user from the datastore by email """ return next(o for o in self._users if o.email == reference) - def list_users(self): - """ list users, sorted by username """ - return sorted(self._users, key=lambda x: x.username) - def search_vendor(self, reference): """ search for a vendor by a canonical search term """ - return self._vendors.get(reference, None) + try: + return next(v for v in self._vendors if v.term == reference) + except StopIteration: + return None + + def get_vendor_aggregates(self, reference): + """ list a all canonical names of vendors """ + vendors = [v for v in self._vendors if v.name == reference] + terms = sorted((v.term for v in vendors), key=lambda x: x.lower()) + first_vendor = next(iter(vendors)) + return VendorAggregate(first_vendor.name, terms) def add_reset_token(self, token): """ add an password reset token """ @@ -103,7 +106,7 @@ class FakeEventQueue(list): @pytest.fixture def prefilled_repo(): from itertools import count - from ordr3.models import OrderItem, OrderStatus + from ordr3.models import OrderItem, OrderStatus, Vendor i = count() catalog = { @@ -143,6 +146,8 @@ def prefilled_repo(): repo.add_order(_create_order(5, today - month * 2, OrderStatus.ORDERED)) repo.add_order(_create_order(5, today - month * 3, OrderStatus.HOLD)) + repo._vendors.add(Vendor("sa", "Sigma Aldrich")) + return repo @@ -275,11 +280,11 @@ def test_vendor_with_common_domain(input, expected): ("http://SA.com/some/path", "Sigma Aldrich", True), ], ) -def test_check_vendor_name(input, name, found): +def test_check_vendor_name(prefilled_repo, input, name, found): from ordr3.services import check_vendor_name - repo = FakeOrderRepository(None) - result = check_vendor_name(repo, input) + result = check_vendor_name(prefilled_repo, input) + print(prefilled_repo._vendors) assert result.name == name assert result.found == found