Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/PI-320] Device created on and deleted on date #195

Merged
merged 15 commits into from
May 23, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The first time it will also set up your pre-commit hooks.

We use [Proxygen](https://github.com/NHSDigital/proxygen-cli) to deploy our proxies and specs to APIM which we have wrapped up into a make command:

- `make apigee-deploy`
- `make apigee--deploy`

This when run locally will need you to have done a local `terraform--plan` and `terraform--apply` (as it will read some details from the output files)

Expand Down
7 changes: 7 additions & 0 deletions src/etl/sds/worker/load/tests/test_load_worker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from collections import deque
from datetime import datetime
from itertools import chain, permutations
from typing import Callable, Generator
from unittest import mock
Expand Down Expand Up @@ -33,6 +34,9 @@
"product_team_id": str(UUID(int=1)),
"ods_code": "ABC",
"status": "active",
"created_on": datetime.utcnow(),
"updated_on": None,
"deleted_on": None,
}
}
GOOD_CPM_EVENT_2 = {
Expand All @@ -43,6 +47,9 @@
"product_team_id": str(UUID(int=2)),
"ods_code": "ABC",
"status": "active",
"created_on": datetime.utcnow(),
"updated_on": None,
"deleted_on": None,
}
}

Expand Down
20 changes: 19 additions & 1 deletion src/layers/domain/core/device.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from collections import defaultdict
from datetime import datetime
from enum import StrEnum, auto
from itertools import chain
from typing import Optional
from uuid import UUID, uuid4

from attr import dataclass, field
Expand Down Expand Up @@ -40,6 +42,9 @@ class DeviceCreatedEvent(Event):
product_team_id: UUID
ods_code: str
status: "DeviceStatus"
created_on: str
updated_on: Optional[str] = None
deleted_on: Optional[str] = None
_trust: bool = field(alias="_trust", default=False)


Expand All @@ -51,6 +56,9 @@ class DeviceUpdatedEvent(Event):
product_team_id: UUID
ods_code: str
status: "DeviceStatus"
created_on: str
updated_on: str
deleted_on: Optional[str] = None


@dataclass(kw_only=True, slots=True)
Expand Down Expand Up @@ -149,18 +157,28 @@ class Device(AggregateRoot):
status: DeviceStatus = Field(default=DeviceStatus.ACTIVE)
product_team_id: UUID
ods_code: str
created_on: datetime = Field(default_factory=datetime.utcnow, immutable=True)
updated_on: Optional[datetime] = Field(default=None)
deleted_on: Optional[datetime] = Field(default=None)
keys: dict[str, DeviceKey] = Field(default_factory=dict, exclude=True)
questionnaire_responses: dict[str, list[QuestionnaireResponse]] = Field(
default_factory=lambda: defaultdict(list), exclude=True
)

def update(self, **kwargs) -> DeviceUpdatedEvent:
if "updated_on" not in kwargs:
kwargs["updated_on"] = datetime.utcnow()
device_data = self._update(data=kwargs)
event = DeviceUpdatedEvent(**device_data)
return self.add_event(event)

def delete(self) -> DeviceUpdatedEvent:
return self.update(status=DeviceStatus.INACTIVE)
deletion_datetime = datetime.utcnow()
return self.update(
status=DeviceStatus.INACTIVE,
updated_on=deletion_datetime,
deleted_on=deletion_datetime,
)

def add_key(self, type: str, key: str, _trust=False) -> DeviceKeyAddedEvent:
if key in self.keys:
Expand Down
18 changes: 18 additions & 0 deletions src/layers/domain/core/tests/test_device.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from itertools import chain

import pytest
Expand Down Expand Up @@ -58,15 +59,32 @@ def another_questionnaire_response() -> QuestionnaireResponse:
return questionnaire.respond(responses=[{"question1": ["bye"]}])


def test_device_created_with_datetime(device: Device):
assert isinstance(device.created_on, datetime)
assert device.updated_on == None
assert device.deleted_on == None


def test_device_update(device: Device):
device_created_on = device.created_on
device_updated_on = device.updated_on
event = device.update(name="bar")
assert device.name == "bar"
assert device.deleted_on == None
assert isinstance(device.updated_on, datetime)
assert device.updated_on != device_updated_on
assert device.created_on == device_created_on
assert isinstance(event, DeviceUpdatedEvent)


def test_device_delete(device: Device):
device_created_on = device.created_on
assert device.deleted_on == None
event = device.delete()
assert device.status == DeviceStatus.INACTIVE
assert device.created_on == device_created_on
assert isinstance(device.deleted_on, datetime)
assert device.updated_on == device.deleted_on
assert isinstance(event, DeviceUpdatedEvent)


Expand Down
4 changes: 2 additions & 2 deletions src/layers/domain/repository/marshall.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .errors import UnableToUnmarshall

MARSHALL_FUNCTION_BY_TYPE = {
type(None): (lambda _: {"Null": True}),
type(None): (lambda _: {"NULL": True}),
bool: (lambda x: {"BOOL": x}),
int: (lambda x: {"N": str(x)}),
float: (lambda x: {"N": str(x)}),
Expand All @@ -28,7 +28,7 @@ def _unmarshall_mapping(mapping: dict[str, dict[str, Any]]) -> dict[str, Any]:
def unmarshall_value(record: dict[str, str | dict | list]):
((_type_name, value),) = record.items()
match _type_name:
case "Null":
case "NULL":
return None
case "S":
return str(value)
Expand Down
10 changes: 5 additions & 5 deletions src/layers/domain/repository/tests/test_marshall.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, depth=0):
@pytest.mark.parametrize(
"value,expected",
[
[None, {"Null": True}],
[None, {"NULL": True}],
["foo", {"S": "foo"}],
[123, {"N": "123"}],
[True, {"BOOL": True}],
Expand All @@ -29,7 +29,7 @@ def __init__(self, depth=0):
],
{
"L": [
{"Null": True},
{"NULL": True},
{"N": "1"},
{"N": "2.0"},
{"S": "3"},
Expand All @@ -51,7 +51,7 @@ def __init__(self, depth=0):
},
{
"M": {
"none": {"Null": True},
"none": {"NULL": True},
"bool": {"BOOL": False},
"int": {"N": "1"},
"float": {"N": "2"},
Expand All @@ -71,15 +71,15 @@ def test_marshall_value(value, expected):
@pytest.mark.parametrize(
"value,expected",
[
[{"Null": True}, None],
[{"NULL": True}, None],
[{"BOOL": False}, False],
[{"BOOL": True}, True],
[{"N": "0"}, 0.0],
[{"N": "1"}, 1.0],
[{"N": "1.2"}, 1.2],
[{"S": "x"}, "x"],
[{"L": []}, []],
[{"L": [{"Null": True}]}, [None]],
[{"L": [{"NULL": True}]}, [None]],
[{"M": {}}, {}],
[{"M": {"foo": {"BOOL": False}, "bar": {"N": "1"}}}, {"foo": False, "bar": 1}],
],
Expand Down
3 changes: 3 additions & 0 deletions src/layers/etl_utils/io/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import pickle
from collections import deque
from datetime import datetime
from io import BytesIO
from typing import IO
from uuid import UUID
Expand All @@ -18,6 +19,8 @@ def default(self, obj):
return list(obj)
if isinstance(obj, UUID):
return str(obj)
if isinstance(obj, datetime):
return str(obj)
return json.JSONEncoder.default(self, obj)


Expand Down
14 changes: 14 additions & 0 deletions src/layers/sds/cpm_translation/tests/test_cpm_translation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from itertools import chain
from string import ascii_letters, digits
from typing import Generator
Expand Down Expand Up @@ -212,15 +213,28 @@ def test_delete_devices(repository: DeviceRepository):
product_team_id=_device_1.product_team_id,
ods_code=_device_1.ods_code,
status=DeviceStatus.INACTIVE,
created_on=_device_1.created_on,
updated_on=event_1.updated_on,
deleted_on=event_1.deleted_on,
)
assert isinstance(event_1.updated_on, datetime)
assert isinstance(event_1.deleted_on, datetime)
assert event_1.deleted_on == event_1.updated_on

assert event_2 == DeviceUpdatedEvent(
id=_device_2.id,
name=_device_2.name,
type=_device_2.type,
product_team_id=_device_2.product_team_id,
ods_code=_device_2.ods_code,
status=DeviceStatus.INACTIVE,
created_on=_device_2.created_on,
updated_on=event_2.updated_on,
deleted_on=event_2.deleted_on,
)
assert isinstance(event_2.updated_on, datetime)
assert isinstance(event_2.deleted_on, datetime)
assert event_2.deleted_on == event_2.updated_on


@pytest.mark.integration
Expand Down
Loading