From 9c38f3a09d78b31e59a0f8b0b0fad074dcdba2b9 Mon Sep 17 00:00:00 2001
From: Alexandra Hobbs <ahobbs@contractor.usgs.gov>
Date: Mon, 2 Dec 2024 09:43:46 -0700
Subject: [PATCH] merging in master

---
 geomagio/metadata/Metadata.py           |   9 +-
 test/db/MetadataDatabaseFactory_test.py | 143 ++++++++++++++++++++----
 2 files changed, 131 insertions(+), 21 deletions(-)

diff --git a/geomagio/metadata/Metadata.py b/geomagio/metadata/Metadata.py
index e1a7bd18..bfc6a35f 100644
--- a/geomagio/metadata/Metadata.py
+++ b/geomagio/metadata/Metadata.py
@@ -2,7 +2,7 @@ from datetime import timezone
 from typing import Dict, Optional
 
 from obspy import UTCDateTime
-from pydantic import field_serializer, BaseModel, Field
+from pydantic import field_validator, field_serializer, BaseModel, Field
 
 from .MetadataCategory import MetadataCategory
 from ..pydantic_utcdatetime import CustomUTCDateTimeType
@@ -52,7 +52,7 @@ class Metadata(BaseModel):
     metadata_id: Optional[int] = None
     # author
     created_by: Optional[str] = None
-    created_time: CustomUTCDateTimeType = Field(default_factory=lambda: UTCDateTime())
+    created_time: Optional[CustomUTCDateTimeType] = None
     # editor
     updated_by: Optional[str] = None
     updated_time: Optional[CustomUTCDateTimeType] = None
@@ -104,3 +104,8 @@ class Metadata(BaseModel):
         if endtime is not None:
             endtime = endtime.datetime.replace(tzinfo=timezone.utc)
         return endtime
+
+    @field_validator("created_time")
+    @classmethod
+    def set_default_created_time(cls, created_time: UTCDateTime = None) -> UTCDateTime:
+        return created_time or UTCDateTime()
diff --git a/test/db/MetadataDatabaseFactory_test.py b/test/db/MetadataDatabaseFactory_test.py
index 045d297e..61e60299 100644
--- a/test/db/MetadataDatabaseFactory_test.py
+++ b/test/db/MetadataDatabaseFactory_test.py
@@ -7,16 +7,14 @@ from databases import Database
 from obspy import UTCDateTime
 
 from geomagio.api.db import MetadataDatabaseFactory
-from geomagio.metadata import Metadata, MetadataCategory
+from geomagio.metadata import Metadata, MetadataCategory, MetadataQuery
 
 
 class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
 
     @patch("databases.Database.execute", new_callable=AsyncMock)
     async def test_create_metadata_defaults(self, mock_execute):
-        now = UTCDateTime()
         test_data = Metadata(
-            created_time=now,
             category=MetadataCategory.INSTRUMENT,
             created_by="test_metadata.py",
             network="NT",
@@ -50,16 +48,6 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
 
         # assert data_valid, priority, and status are set to the correct defaults
         expected_values = {
-            "created_time": datetime.datetime(
-                year=now.year,
-                month=now.month,
-                day=now.day,
-                hour=now.hour,
-                minute=now.minute,
-                second=now.second,
-                microsecond=now.microsecond,
-                tzinfo=tz.tzutc(),
-            ),
             "category": "instrument",
             "created_by": "test_metadata.py",
             "network": "NT",
@@ -96,12 +84,10 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
         assert called_params == expected_values
 
     @patch("databases.Database.execute", new_callable=AsyncMock)
-    async def test_create_metadata_with_ids(self, mock_execute):
+    async def test_create_metadata_created_time(self, mock_execute):
         now = UTCDateTime()
         test_data = Metadata(
-            id=1234,
             created_time=now,
-            metadata_id=5678,
             category=MetadataCategory.INSTRUMENT,
             created_by="test_metadata.py",
             network="NT",
@@ -133,7 +119,7 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
 
         await MetadataDatabaseFactory(database=db).create_metadata(test_data)
 
-        # assert id and metadata_id are removed
+        # assert data_valid, priority, and status are set to the correct defaults
         expected_values = {
             "created_time": datetime.datetime(
                 year=now.year,
@@ -181,8 +167,9 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
         assert called_params == expected_values
 
     @patch("databases.Database.execute", new_callable=AsyncMock)
-    async def test_create_metadata_without_created_time(self, mock_execute):
+    async def test_create_metadata_with_ids(self, mock_execute):
         test_data = Metadata(
+            id=1234,
             metadata_id=5678,
             category=MetadataCategory.INSTRUMENT,
             created_by="test_metadata.py",
@@ -215,10 +202,42 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
 
         await MetadataDatabaseFactory(database=db).create_metadata(test_data)
 
+        # assert id and metadata_id are removed
+        expected_values = {
+            "category": "instrument",
+            "created_by": "test_metadata.py",
+            "network": "NT",
+            "station": "BDT",
+            "metadata": {
+                "type": "FGE",
+                "channels": {
+                    "U": [{"channel": "U_Volt", "offset": 0, "scale": 313.2}],
+                    "V": [{"channel": "V_Volt", "offset": 0, "scale": 312.3}],
+                    "W": [{"channel": "W_Volt", "offset": 0, "scale": 312.0}],
+                },
+                "electronics": {
+                    "serial": "E0542",
+                    "x-scale": 313.2,
+                    "y-scale": 312.3,
+                    "z-scale": 312.0,
+                    "temperature-scale": 0.01,
+                },
+                "sensor": {
+                    "serial": "S0419",
+                    "x-constant": 36958,
+                    "y-constant": 36849,
+                    "z-constant": 36811,
+                },
+            },
+            "data_valid": True,
+            "priority": 1,
+            "status": "new",
+        }
+
         mock_execute.assert_called_once()
         called_params = mock_execute.call_args.args[0].compile().params
 
-        assert called_params["created_time"] is not None
+        assert called_params == expected_values
 
     @patch("databases.Database.execute", new_callable=AsyncMock)
     async def test_create_metadata_with_starttime_and_endtime(self, mock_execute):
@@ -336,3 +355,89 @@ class TestMetadataDatabaseFactory(unittest.IsolatedAsyncioTestCase):
             microsecond=e.microsecond,
             tzinfo=tz.tzutc(),
         )
+
+    @patch("databases.Database.execute", new_callable=AsyncMock)
+    @patch("databases.Database.fetch_all", new_callable=AsyncMock)
+    async def test_update_metadata_defaults(self, mock_fetch_all, mock_execute):
+        test_data = Metadata(
+            category=MetadataCategory.INSTRUMENT,
+            network="NT",
+            station="BDT",
+            metadata={
+                "type": "FGE",
+                "channels": {
+                    "U": [{"channel": "U_Volt", "offset": 0, "scale": 313.2}],
+                    "V": [{"channel": "V_Volt", "offset": 0, "scale": 312.3}],
+                    "W": [{"channel": "W_Volt", "offset": 0, "scale": 312.0}],
+                },
+                "electronics": {
+                    "serial": "E0542",
+                    "x-scale": 313.2,
+                    "y-scale": 312.3,
+                    "z-scale": 312.0,
+                    "temperature-scale": 0.01,
+                },
+                "sensor": {
+                    "serial": "S0419",
+                    "x-constant": 36958,
+                    "y-constant": 36849,
+                    "z-constant": 36811,
+                },
+            },
+        )
+
+        db = Database("sqlite:///:memory:")
+        yesterday = datetime.datetime(2024, 11, 1, 8, 15, tzinfo=tz.tzutc())
+
+        mock_fetch_all.return_value = (
+            {
+                "id": 1234,
+                "created_time": yesterday,
+                "category": "instrument",
+                "network": "NT",
+                "station": "BDT",
+                "metadata": {
+                    "foo": "bar",
+                },
+            },
+        )
+
+        await MetadataDatabaseFactory(database=db).update_metadata(
+            meta=test_data, updated_by="test_user"
+        )
+
+        assert mock_fetch_all.call_count == 2
+        assert mock_execute.call_count == 2
+
+        first_called_params = mock_execute.call_args_list[0].args[0].compile().params
+        second_called_params = mock_execute.call_args_list[1].args[0].compile().params
+
+        assert first_called_params["metadata_id"] == 1234
+        assert first_called_params["created_time"] == yesterday
+        assert first_called_params["metadata"] == {"foo": "bar"}
+
+        assert second_called_params["updated_by"] == "test_user"
+        assert second_called_params["updated_time"] is not None
+        assert second_called_params["metadata"] == test_data.metadata
+
+    @patch("databases.Database.fetch_all", new_callable=AsyncMock)
+    async def test_get_metadata(self, mock_fetch_all):
+        test_query = MetadataQuery(
+            category=MetadataCategory.INSTRUMENT,
+            station="BSL",
+            starttime=UTCDateTime(2020, 1, 20),
+        )
+
+        db = Database("sqlite:///:memory:")
+
+        await MetadataDatabaseFactory(database=db).get_metadata(params=test_query)
+
+        mock_fetch_all.assert_called_once()
+
+        called_params = mock_fetch_all.call_args.args[0].compile().params
+
+        assert called_params["category_1"] == "instrument"
+        assert called_params["station_1"] == "BSL"
+        assert called_params["endtime_1"] == datetime.datetime(
+            2020, 1, 20, tzinfo=tz.tzutc()
+        )
-- 
GitLab