Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions python/nwsc_proxy/src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,54 @@ def to_iso(dt: datetime) -> str:
if dt.tzname() in [None, str(UTC)]
else dt.strftime("%Z")[3:]
)


# a Vulnerability from real NWSC Vulnerabilities API; dynamic objects should extend this format
VULNERABILITY_TEMPLATE = {
"vulnerabilityType": "EVENT",
"name": None,
"description": None,
"primaryOfficeId": "GSL",
"geometry": "",
"activeTime": {
"startTime": None,
"endTime": None,
"recurrenceRule": None,
},
"support": {
"summary": None,
"notes": "",
"briefings": [
{
"type": "email_briefing",
"schedule": {"startTime": None, "endTime": None, "recurrenceRule": None},
}
],
"recipients": {"partners": [], "externals": []},
},
"notes": "",
"hazards": [],
"url": "",
"dailyAttendance": None,
"venueType": "OUTDOOR",
"venueAddress": {
"vulnerabilityId": None,
"streetLine1": None,
"streetLine2": None,
"city": "",
"state": "",
"postalCode": None,
"countryCode": None,
},
"isNsseEvent": False,
"isSearEvent": False,
"searEventLevel": None,
"evacuationTimeMinutes": 0,
"timezone": "UTC",
"originalRequestId": None,
"incidentName": None,
"scheduledEventData": None,
"hazmatResponseData": None,
"searchRescueData": None,
"wildfireData": None,
}
21 changes: 15 additions & 6 deletions python/nwsc_proxy/src/vulnerability_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from dateutil.parser import parse as dt_parse

from src.utils import deep_update
from src.utils import deep_update, VULNERABILITY_TEMPLATE

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -110,6 +110,13 @@ def data_sources(self) -> set[str]:
except KeyError:
return set([self.DEFAULT_DATA_SOURCE]) # couldn't lookup dataSources; default to NBM

def as_vulnerability(self) -> dict:
"""CachedProfile data formatted as real Vulnerability; any real data stored in the Profile
is augmented by nulls/zeros for any properties that would exist in a Vulnerbility returned
by the real NWS Connect API's database.
"""
return {**VULNERABILITY_TEMPLATE, **self.data}

def __str__(self):
return (
f"{self.__class__.__name__}(id='{self.id}', name='{self.name}', "
Expand Down Expand Up @@ -203,10 +210,12 @@ def get_all(

if data_source == "ANY":
# all data sources requested, so do not filter by products used
return [profile.data for profile in profiles_by_status]
return [profile.as_vulnerability() for profile in profiles_by_status]

return [
profile.data for profile in profiles_by_status if data_source in profile.data_sources
profile.as_vulnerability()
for profile in profiles_by_status
if data_source in profile.data_sources
]

def get(self, profile_id: str) -> dict | None:
Expand All @@ -219,7 +228,7 @@ def get(self, profile_id: str) -> dict | None:
dict | None: The Profile JSON data, or None if `profile_id` does not exist.
"""
cached_profile = self._cache.get(profile_id, None)
return cached_profile.data if cached_profile else None
return cached_profile.as_vulnerability() if cached_profile else None

def save(self, profile_data: dict) -> dict | None:
"""Persist a new Profile to this API
Expand Down Expand Up @@ -247,7 +256,7 @@ def save(self, profile_data: dict) -> dict | None:
# add profile to in-memory cache
self._cache[cached_profile.id] = cached_profile
logger.info("Saved profile to cache, file location: %s", filepath)
return cached_profile.data
return cached_profile.as_vulnerability()

def update(self, profile_id: str, data: dict) -> dict:
"""Update a Profile in storage based on its ID.
Expand Down Expand Up @@ -280,7 +289,7 @@ def update(self, profile_id: str, data: dict) -> dict:

# update in-memory cache to overwrite previous profile by ID
self._cache[profile_id] = updated_profile
return updated_profile.data
return updated_profile.as_vulnerability()

def delete(self, profile_id: str) -> bool:
"""Delete a Profile from storage, based on its UUID.
Expand Down
19 changes: 19 additions & 0 deletions python/nwsc_proxy/test/test_vulnerability_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ def test_save_ignores_profile_id(store: VulnerabilityStore, mock_uuid: Mock):
assert new_profile["id"] not in updated_profile_list


def test_save_appends_missing_props(store: VulnerabilityStore, mock_uuid: Mock):
expected_id = str(uuid4())
mock_uuid.return_value = expected_id
# attempt to create Vulnerability without all the boilerplate Vulnerability properties
expected_profile = deepcopy(EXAMPLE_PROFILE)
expected_profile["name"] = "Partial profile"
missing_prop = "vulnerabilityType"
if missing_prop in expected_profile:
del expected_profile[missing_prop]

actual_profile = store.save(expected_profile)
updated_profile_list = store.get_all()

# backend appended vulnerabilityType to response even though saved Profile doesn't have it
assert actual_profile[missing_prop] == "EVENT"
updated_profile = [p for p in updated_profile_list if p["name"] == expected_profile["name"]][0]
assert updated_profile[missing_prop] == "EVENT"


def test_delete_profile(store: VulnerabilityStore, base_dir: str):
existing_profile_list = store.get_all()
profile_id = existing_profile_list[0]["id"]
Expand Down
Loading