Create eTags, lots, and events in dummy
This commit is contained in:
parent
32837f5f59
commit
f56ca473e8
|
@ -1,4 +1,7 @@
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import click_spinner
|
import click_spinner
|
||||||
|
@ -7,22 +10,26 @@ import yaml
|
||||||
from ereuse_devicehub.client import UserClient
|
from ereuse_devicehub.client import UserClient
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.agent.models import Person
|
from ereuse_devicehub.resources.agent.models import Person
|
||||||
from ereuse_devicehub.resources.event.models import Snapshot
|
from ereuse_devicehub.resources.event import models as m
|
||||||
from ereuse_devicehub.resources.inventory import Inventory
|
from ereuse_devicehub.resources.inventory import Inventory
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
from ereuse_devicehub.resources.user import User
|
from ereuse_devicehub.resources.user import User
|
||||||
|
|
||||||
|
|
||||||
class Dummy:
|
class Dummy:
|
||||||
SNAPSHOTS = (
|
|
||||||
'workbench-server-1',
|
|
||||||
'computer-monitor'
|
|
||||||
)
|
|
||||||
TAGS = (
|
TAGS = (
|
||||||
'tag1',
|
'tag1',
|
||||||
'tag2',
|
'tag2',
|
||||||
'tag3'
|
'tag3'
|
||||||
)
|
)
|
||||||
|
"""Tags to create."""
|
||||||
|
ET = (
|
||||||
|
('A0000000000001', 'DT-AAAAA'),
|
||||||
|
('A0000000000002', 'DT-BBBBB'),
|
||||||
|
)
|
||||||
|
"""eTags to create."""
|
||||||
|
ORG = 'eReuse.org CAT', 'G-60437761', 'ES'
|
||||||
|
"""An organization to create."""
|
||||||
|
|
||||||
def __init__(self, app) -> None:
|
def __init__(self, app) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -34,20 +41,60 @@ class Dummy:
|
||||||
'Do you want to continue?')
|
'Do you want to continue?')
|
||||||
def run(self):
|
def run(self):
|
||||||
print('Preparing the database...'.ljust(30), end='')
|
print('Preparing the database...'.ljust(30), end='')
|
||||||
|
runner = self.app.test_cli_runner()
|
||||||
with click_spinner.spinner():
|
with click_spinner.spinner():
|
||||||
self.app.init_db(erase=True)
|
self.app.init_db(erase=True)
|
||||||
|
out = runner.invoke(args=['create-org', *self.ORG], catch_exceptions=False).output
|
||||||
|
org_id = json.loads(out)['id']
|
||||||
user = self.user_client('user@dhub.com', '1234')
|
user = self.user_client('user@dhub.com', '1234')
|
||||||
|
# todo put user's agent into Org
|
||||||
for id in self.TAGS:
|
for id in self.TAGS:
|
||||||
user.post({'id': id}, res=Tag)
|
user.post({'id': id}, res=Tag)
|
||||||
|
for id, sec in self.ET:
|
||||||
|
runner.invoke(args=[
|
||||||
|
'create-tag', id,
|
||||||
|
'-p', 'https://t.devicetag.io',
|
||||||
|
'-s', sec,
|
||||||
|
'-o', org_id
|
||||||
|
],
|
||||||
|
catch_exceptions=False)
|
||||||
files = tuple(Path(__file__).parent.joinpath('files').iterdir())
|
files = tuple(Path(__file__).parent.joinpath('files').iterdir())
|
||||||
print('done.')
|
print('done.')
|
||||||
|
pcs = set() # type: Set[int]
|
||||||
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
|
with click.progressbar(files, label='Creating devices...'.ljust(28)) as bar:
|
||||||
for path in bar:
|
for path in bar:
|
||||||
with path.open() as f:
|
with path.open() as f:
|
||||||
snapshot = yaml.load(f)
|
snapshot = yaml.load(f)
|
||||||
user.post(res=Snapshot, data=snapshot)
|
s, _ = user.post(res=m.Snapshot, data=snapshot)
|
||||||
|
pcs.add(s['device']['id'])
|
||||||
inventory, _ = user.get(res=Inventory)
|
inventory, _ = user.get(res=Inventory)
|
||||||
assert len(inventory['devices'])
|
assert len(inventory['devices'])
|
||||||
|
|
||||||
|
# Link tags and eTags
|
||||||
|
for tag, pc in zip((self.TAGS[1], self.TAGS[2], self.ET[0][0], self.ET[1][1]), pcs):
|
||||||
|
user.put({}, res=Tag, item='{}/device/{}'.format(tag, pc), status=204)
|
||||||
|
|
||||||
|
# Perform generic events
|
||||||
|
for pc, model in zip(pcs,
|
||||||
|
{m.ToRepair, m.Repair, m.ToPrepare, m.ReadyToUse, m.ToPrepare,
|
||||||
|
m.Prepare}):
|
||||||
|
user.post({'type': model.t, 'devices': [pc]}, res=m.Event)
|
||||||
|
|
||||||
|
# Perform a Sell to several devices
|
||||||
|
user.post(
|
||||||
|
{
|
||||||
|
'type': m.Sell.t,
|
||||||
|
'to': user.user['individuals'][0]['id'],
|
||||||
|
'devices': list(itertools.islice(pcs, len(pcs) // 2))
|
||||||
|
},
|
||||||
|
res=m.Event)
|
||||||
|
|
||||||
|
from tests.test_lot import test_post_add_children_view, test_post_add_device_view
|
||||||
|
test_post_add_children_view(user)
|
||||||
|
|
||||||
|
# todo this does not add devices to lots
|
||||||
|
test_post_add_device_view(user)
|
||||||
|
|
||||||
print('⭐ Done.')
|
print('⭐ Done.')
|
||||||
|
|
||||||
def user_client(self, email: str, password: str):
|
def user_client(self, email: str, password: str):
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import json
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
from teal.db import SQLAlchemy
|
from teal.db import SQLAlchemy
|
||||||
|
@ -39,7 +41,9 @@ class OrganizationDef(AgentDef):
|
||||||
))
|
))
|
||||||
db.session.add(org)
|
db.session.add(org)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return self.schema.dump(org)
|
o = self.schema.dump(org)
|
||||||
|
print(json.dumps(o, indent=2))
|
||||||
|
return o
|
||||||
|
|
||||||
def init_db(self, db: SQLAlchemy):
|
def init_db(self, db: SQLAlchemy):
|
||||||
"""Creates the default organization."""
|
"""Creates the default organization."""
|
||||||
|
|
|
@ -43,11 +43,6 @@ class Agent(Thing):
|
||||||
telephone = Column(PhoneNumberType())
|
telephone = Column(PhoneNumberType())
|
||||||
email = Column(EmailType, unique=True)
|
email = Column(EmailType, unique=True)
|
||||||
|
|
||||||
user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
|
|
||||||
user = relationship(User,
|
|
||||||
backref=backref('individuals', lazy=True, collection_class=set),
|
|
||||||
primaryjoin=user_id == User.id)
|
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint(tax_id, country, name='Registration Number per country.'),
|
UniqueConstraint(tax_id, country, name='Registration Number per country.'),
|
||||||
)
|
)
|
||||||
|
@ -100,6 +95,11 @@ class Individual(JoinedTableMixin, Agent):
|
||||||
active_org_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id))
|
active_org_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id))
|
||||||
active_org = relationship(Organization, primaryjoin=active_org_id == Organization.id)
|
active_org = relationship(Organization, primaryjoin=active_org_id == Organization.id)
|
||||||
|
|
||||||
|
user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
|
||||||
|
user = relationship(User,
|
||||||
|
backref=backref('individuals', lazy=True, collection_class=set),
|
||||||
|
primaryjoin=user_id == User.id)
|
||||||
|
|
||||||
|
|
||||||
class Membership(Thing):
|
class Membership(Thing):
|
||||||
"""Organizations that are related to the Individual.
|
"""Organizations that are related to the Individual.
|
||||||
|
|
|
@ -727,10 +727,7 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
If no price is set it is supposed that the trade was
|
If no price is set it is supposed that the trade was
|
||||||
not payed, usual in donations.
|
not payed, usual in donations.
|
||||||
"""
|
"""
|
||||||
to_id = Column(UUID(as_uuid=True),
|
to_id = Column(UUID(as_uuid=True), ForeignKey(Agent.id), nullable=False)
|
||||||
ForeignKey(Agent.id),
|
|
||||||
nullable=False,
|
|
||||||
default=lambda: g.user.id)
|
|
||||||
# todo compute the org
|
# todo compute the org
|
||||||
to = relationship(Agent,
|
to = relationship(Agent,
|
||||||
backref=backref('events_to',
|
backref=backref('events_to',
|
||||||
|
@ -738,6 +735,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
|
||||||
collection_class=OrderedSet,
|
collection_class=OrderedSet,
|
||||||
order_by=lambda: Event.created),
|
order_by=lambda: Event.created),
|
||||||
primaryjoin=to_id == Agent.id)
|
primaryjoin=to_id == Agent.id)
|
||||||
|
to_comment = """
|
||||||
|
The agent that gets the device due this deal.
|
||||||
|
"""
|
||||||
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
|
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
|
||||||
confirms = relationship(Organize,
|
confirms = relationship(Organize,
|
||||||
backref=backref('confirmation', lazy=True, uselist=False),
|
backref=backref('confirmation', lazy=True, uselist=False),
|
||||||
|
|
|
@ -352,7 +352,7 @@ class Trade(EventWithMultipleDevices):
|
||||||
shipping_date = DateTime(data_key='shippingDate')
|
shipping_date = DateTime(data_key='shippingDate')
|
||||||
invoice_number = String(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
|
invoice_number = String(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
|
||||||
price = NestedOn(Price)
|
price = NestedOn(Price)
|
||||||
to = NestedOn(Agent, only_query='id')
|
to = NestedOn(Agent, only_query='id', required=True, comment=m.Trade.to_comment)
|
||||||
confirms = NestedOn(Organize)
|
confirms = NestedOn(Organize)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@ class Tag(Thing):
|
||||||
primaryjoin=Organization.id == org_id,
|
primaryjoin=Organization.id == org_id,
|
||||||
collection_class=set)
|
collection_class=set)
|
||||||
"""The organization that issued the tag."""
|
"""The organization that issued the tag."""
|
||||||
provider = Column(URL(),
|
provider = Column(URL())
|
||||||
comment='The tag provider URL. If None, the provider is this Devicehub.')
|
provider.comment = """
|
||||||
provider.comment = """The provider URL."""
|
The tag provider URL. If None, the provider is this Devicehub.
|
||||||
|
"""
|
||||||
device_id = Column(BigInteger,
|
device_id = Column(BigInteger,
|
||||||
# We don't want to delete the tag on device deletion, only set to null
|
# We don't want to delete the tag on device deletion, only set to null
|
||||||
ForeignKey(Device.id, ondelete=DB_CASCADE_SET_NULL))
|
ForeignKey(Device.id, ondelete=DB_CASCADE_SET_NULL))
|
||||||
|
@ -68,6 +69,13 @@ class Tag(Thing):
|
||||||
raise ValidationError('Tags cannot contain slashes (/).')
|
raise ValidationError('Tags cannot contain slashes (/).')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@validates('provider')
|
||||||
|
def use_only_domain(self, _, url: URL):
|
||||||
|
if url.path:
|
||||||
|
raise ValidationError('Provider can only contain scheme and host',
|
||||||
|
field_names=['provider'])
|
||||||
|
return url
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint(device_id, org_id, name='one_tag_per_org'),
|
UniqueConstraint(device_id, org_id, name='one_tag_per_org'),
|
||||||
UniqueConstraint(secondary, org_id, name='one_secondary_per_org')
|
UniqueConstraint(secondary, org_id, name='one_secondary_per_org')
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from marshmallow.fields import String
|
from marshmallow.fields import String
|
||||||
|
from sqlalchemy.util import OrderedSet
|
||||||
from teal.marshmallow import URL
|
from teal.marshmallow import URL
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
|
from ereuse_devicehub.resources.agent.schemas import Organization
|
||||||
from ereuse_devicehub.resources.device.schemas import Device
|
from ereuse_devicehub.resources.device.schemas import Device
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
from ereuse_devicehub.resources.tag import model as m
|
from ereuse_devicehub.resources.tag import model as m
|
||||||
|
@ -19,5 +21,5 @@ class Tag(Thing):
|
||||||
provider = URL(description=m.Tag.provider.comment,
|
provider = URL(description=m.Tag.provider.comment,
|
||||||
validator=without_slash)
|
validator=without_slash)
|
||||||
device = NestedOn(Device, dump_only=True)
|
device = NestedOn(Device, dump_only=True)
|
||||||
org = String()
|
org = NestedOn(Organization, collection_class=OrderedSet, only_query='id')
|
||||||
secondary = String(description=m.Tag.secondary.comment)
|
secondary = String(description=m.Tag.secondary.comment)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from click import argument, option
|
from click import argument, option
|
||||||
|
from flask import current_app
|
||||||
from teal.resource import Converters, Resource
|
from teal.resource import Converters, Resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
|
@ -22,14 +23,26 @@ class UserDef(Resource):
|
||||||
self.add_url_rule('/login', view_func=login, methods={'POST'})
|
self.add_url_rule('/login', view_func=login, methods={'POST'})
|
||||||
|
|
||||||
@argument('email')
|
@argument('email')
|
||||||
@option('--password', prompt=True, hide_input=True, confirmation_prompt=True)
|
@option('-a', '--agent', help='The name of an agent to create with the user.')
|
||||||
def create_user(self, email: str, password: str) -> dict:
|
@option('-c', '--country', help='The country of the agent (if --agent is set).')
|
||||||
"""
|
@option('-t', '--telephone', help='The telephone of the agent (if --agent is set).')
|
||||||
Creates an user.
|
@option('-t', '--tax-id', help='The tax id of the agent (if --agent is set).')
|
||||||
|
@option('-p', '--password', prompt=True, hide_input=True, confirmation_prompt=True)
|
||||||
|
def create_user(self, email: str, password: str, agent: str = None, country: str = None,
|
||||||
|
telephone: str = None, tax_id: str = None) -> dict:
|
||||||
|
"""Creates an user.
|
||||||
|
|
||||||
|
If ``--agent`` is passed, it creates an ``Individual`` agent
|
||||||
|
that represents the user.
|
||||||
"""
|
"""
|
||||||
|
from ereuse_devicehub.resources.agent.models import Individual
|
||||||
u = self.SCHEMA(only={'email', 'password'}, exclude=('token',)) \
|
u = self.SCHEMA(only={'email', 'password'}, exclude=('token',)) \
|
||||||
.load({'email': email, 'password': password})
|
.load({'email': email, 'password': password})
|
||||||
user = User(**u)
|
user = User(**u)
|
||||||
|
agent = Individual(**current_app.resources[Individual.t].schema.load(
|
||||||
|
dict(name=agent, email=email, country=country, telephone=telephone, taxId=tax_id)
|
||||||
|
))
|
||||||
|
user.individuals.add(agent)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return self.schema.dump(user)
|
return self.schema.dump(user)
|
||||||
|
|
|
@ -3,6 +3,8 @@ from uuid import UUID
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy_utils import Password
|
from sqlalchemy_utils import Password
|
||||||
|
from teal.enums import Country
|
||||||
|
from teal.marshmallow import ValidationError
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from ereuse_devicehub.client import Client
|
from ereuse_devicehub.client import Client
|
||||||
|
@ -11,23 +13,33 @@ from ereuse_devicehub.devicehub import Devicehub
|
||||||
from ereuse_devicehub.resources.user import UserDef
|
from ereuse_devicehub.resources.user import UserDef
|
||||||
from ereuse_devicehub.resources.user.exceptions import WrongCredentials
|
from ereuse_devicehub.resources.user.exceptions import WrongCredentials
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from teal.marshmallow import ValidationError
|
|
||||||
from tests.conftest import app_context, create_user
|
from tests.conftest import app_context, create_user
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(app_context.__name__)
|
@pytest.mark.usefixtures(app_context.__name__)
|
||||||
def test_create_user_method(app: Devicehub):
|
def test_create_user_method_with_agent(app: Devicehub):
|
||||||
"""
|
"""
|
||||||
Tests creating an user through the main method.
|
Tests creating an user through the main method.
|
||||||
|
|
||||||
This method checks that the token is correct, too.
|
This method checks that the token is correct, too.
|
||||||
"""
|
"""
|
||||||
user_def = app.resources['User'] # type: UserDef
|
user_def = app.resources['User'] # type: UserDef
|
||||||
u = user_def.create_user(email='foo@foo.com', password='foo')
|
u = user_def.create_user(email='foo@foo.com',
|
||||||
|
password='foo',
|
||||||
|
agent='Nice Person',
|
||||||
|
country=Country.ES.name,
|
||||||
|
telephone='+34 666 66 66 66',
|
||||||
|
tax_id='1234')
|
||||||
user = User.query.filter_by(id=u['id']).one() # type: User
|
user = User.query.filter_by(id=u['id']).one() # type: User
|
||||||
assert user.email == 'foo@foo.com'
|
assert user.email == 'foo@foo.com'
|
||||||
assert isinstance(user.token, UUID)
|
assert isinstance(user.token, UUID)
|
||||||
assert User.query.filter_by(email='foo@foo.com').one() == user
|
assert User.query.filter_by(email='foo@foo.com').one() == user
|
||||||
|
individual = next(iter(user.individuals))
|
||||||
|
assert individual.name == 'Nice Person'
|
||||||
|
assert individual.tax_id == '1234'
|
||||||
|
assert individual.telephone.e164 == '+34666666666'
|
||||||
|
assert individual.country == Country.ES
|
||||||
|
assert individual.email == user.email
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(app_context.__name__)
|
@pytest.mark.usefixtures(app_context.__name__)
|
||||||
|
|
Reference in New Issue