Skip to content

Commit 741067a

Browse files
authored
Merge pull request #386 from AllenNeuralDynamics/385-input-metadata-location-and-output
Integration testing and allowing different input/output metadata paths
2 parents 45ce06a + f3fa3d8 commit 741067a

10 files changed

Lines changed: 3418 additions & 287 deletions

File tree

scripts/vr-foraging/acquisition.json

Lines changed: 1143 additions & 0 deletions
Large diffs are not rendered by default.

scripts/vr-foraging/instrument.json

Lines changed: 1622 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""Integration test for VR Foraging metadata collection."""
2+
3+
from datetime import datetime
4+
import json
5+
import logging
6+
import tempfile
7+
from pathlib import Path
8+
from unittest.mock import patch, Mock
9+
10+
from aind_data_schema_models.modalities import Modality
11+
12+
from aind_metadata_mapper.gather_metadata import GatherMetadataJob
13+
from aind_metadata_mapper.models import JobSettings
14+
15+
logging.basicConfig(level=logging.INFO)
16+
17+
# Set to False to use mock responses from tests/resources/ instead of the metadata service
18+
USE_METADATA_SERVICE = False
19+
20+
source_metadata_path = Path(__file__).parent
21+
tests_resources_path = Path(__file__).parent.parent.parent / "tests" / "resources"
22+
23+
output_subfolder = Path(tempfile.mkdtemp(prefix="vr_foraging_test_"))
24+
25+
26+
def load_mock_response(filename: str):
27+
"""Load a mock response from tests/resources/metadata_service/"""
28+
filepath = tests_resources_path / "metadata_service" / filename
29+
with open(filepath, "r") as f:
30+
return json.load(f)
31+
32+
33+
def mock_requests_get(url):
34+
"""Mock requests.get to return responses from local files"""
35+
mock_response = Mock()
36+
37+
if "/subject/" in url:
38+
mock_response.status_code = 200
39+
mock_response.json.return_value = load_mock_response("subject_response.json")
40+
elif "/procedures/" in url:
41+
mock_response.status_code = 200
42+
mock_response.json.return_value = load_mock_response("procedures_response.json")
43+
elif "/funding/" in url:
44+
mock_response.status_code = 200
45+
mock_response.json.return_value = load_mock_response("funding_response.json")
46+
else:
47+
mock_response.status_code = 404
48+
mock_response.json.return_value = {}
49+
50+
return mock_response
51+
52+
53+
def run_test():
54+
"""Run the actual test logic"""
55+
print("\n" + "=" * 80)
56+
print("INTEGRATION TEST: VR Foraging Metadata")
57+
print("=" * 80)
58+
print(f"Source metadata: {source_metadata_path}")
59+
print(f"Output directory: {output_subfolder}")
60+
print(f"Using metadata service: {USE_METADATA_SERVICE}")
61+
print()
62+
63+
settings = JobSettings(
64+
metadata_dir=str(source_metadata_path),
65+
output_dir=str(output_subfolder),
66+
subject_id="828422",
67+
project_name="Cognitive flexibility in patch foraging",
68+
modalities=[Modality.BEHAVIOR, Modality.BEHAVIOR_VIDEOS],
69+
acquisition_start_time=datetime.fromisoformat("2025-11-13T17:38:37.079861+00:00"),
70+
)
71+
72+
job = GatherMetadataJob(settings=settings)
73+
74+
print("=" * 80)
75+
print("STEP 1: Testing individual metadata retrieval")
76+
print("=" * 80)
77+
78+
print("\nLoading acquisition from local file...")
79+
acquisition = job.get_acquisition()
80+
if acquisition:
81+
print(f"✓ Acquisition loaded for subject: {acquisition.get('subject_id')}")
82+
print(f" Start time: {acquisition.get('acquisition_start_time')}")
83+
print(f" Instrument ID: {acquisition.get('instrument_id')}")
84+
print(f" Acquisition type: {acquisition.get('acquisition_type')}")
85+
else:
86+
print("✗ Failed to load acquisition")
87+
88+
print("\nLoading instrument from local file...")
89+
instrument = job.get_instrument()
90+
if instrument:
91+
print(f"✓ Instrument loaded: {instrument.get('instrument_id')}")
92+
print(f" Modification date: {instrument.get('modification_date')}")
93+
modalities = instrument.get("modalities", [])
94+
print(f" Modalities: {len(modalities)}")
95+
for mod in modalities:
96+
print(f" - {mod.get('name')}")
97+
else:
98+
print("✗ Failed to load instrument")
99+
100+
print("\n" + "=" * 80)
101+
print("STEP 2: Running full run_job() workflow")
102+
print("=" * 80)
103+
print("This will fetch all metadata, validate, and write JSON files...")
104+
print()
105+
106+
job.run_job()
107+
108+
print("\n" + "=" * 80)
109+
print("STEP 3: Checking generated files in output folder")
110+
print("=" * 80)
111+
112+
expected_files = [
113+
"data_description.json",
114+
"subject.json",
115+
"procedures.json",
116+
"acquisition.json",
117+
"instrument.json",
118+
]
119+
120+
generated_files = []
121+
for file_name in expected_files:
122+
file_path = output_subfolder / file_name
123+
if file_path.exists():
124+
size = file_path.stat().st_size
125+
print(f"✓ {file_name} ({size:,} bytes)")
126+
generated_files.append(file_name)
127+
else:
128+
print(f"✗ {file_name} (not found)")
129+
130+
print("\n" + "=" * 80)
131+
print("FINAL SUMMARY")
132+
print("=" * 80)
133+
print("Individual retrieval tests:")
134+
print(f" Acquisition: {'✓' if acquisition else '✗'}")
135+
print(f" Instrument: {'✓' if instrument else '✗'}")
136+
print()
137+
print("run_job() execution:")
138+
print(f" Files generated: {len(generated_files)}/{len(expected_files)}")
139+
print(f" Status: {'✓ SUCCESS' if len(generated_files) == len(expected_files) else '✗ INCOMPLETE'}")
140+
print("=" * 80 + "\n")
141+
142+
143+
if __name__ == "__main__":
144+
try:
145+
if USE_METADATA_SERVICE:
146+
# Run test with actual metadata service
147+
run_test()
148+
else:
149+
# Run test with mocked responses from tests/resources/
150+
print("Using mock responses from tests/resources/metadata_service/")
151+
with patch("aind_metadata_mapper.gather_metadata.requests.get", side_effect=mock_requests_get):
152+
run_test()
153+
finally:
154+
pass
155+
# print(f"Cleaning up output directory: {output_subfolder}")
156+
# shutil.rmtree(output_subfolder, ignore_errors=True)
157+
# print("✓ Cleanup complete\n")

src/aind_metadata_mapper/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Base classes for metadata mappers"""
2+
23
from pathlib import Path
34
from pydantic import BaseModel
45

0 commit comments

Comments
 (0)