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
27 changes: 27 additions & 0 deletions hera/tests/test_datalayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def clean_proj():
# 1. Project CRUD — all three collections
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestMeasurementsCRUD:
"""Add, get, get-as-dict, delete for Measurements collection."""
Expand Down Expand Up @@ -154,6 +155,7 @@ def test_delete(self, proj):
assert len(remaining) == 0


@pytest.mark.integration
@requires_mongo
class TestSimulationsCRUD:
"""Add, get, get-as-dict, delete for Simulations collection."""
Expand Down Expand Up @@ -193,6 +195,7 @@ def test_delete(self, proj):
assert len(remaining) == 0


@pytest.mark.integration
@requires_mongo
class TestCacheCRUD:
"""Add, get, get-as-dict, delete for Cache collection."""
Expand Down Expand Up @@ -236,6 +239,7 @@ def test_delete(self, proj):
# 2. Nested queries (dictToMongoQuery)
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestNestedQueries:
"""Test querying with nested desc fields."""
Expand Down Expand Up @@ -273,6 +277,7 @@ def test_multi_level_nested_query(self, clean_proj):
# 3. getAllDocuments — cross-collection query
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestGetAllDocuments:
"""Test getAllDocuments aggregates across collections."""
Expand All @@ -295,6 +300,7 @@ def test_returns_from_all_collections(self, clean_proj):
# 4. getDocumentByID
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestGetDocumentByID:
"""Test retrieving a document by its MongoDB ID."""
Expand All @@ -313,6 +319,7 @@ def test_round_trip(self, clean_proj):
# 5. getMetadata
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestGetMetadata:
"""Test getMetadata returns DataFrame of document descriptions."""
Expand All @@ -335,6 +342,7 @@ def test_returns_dataframe(self, clean_proj):
# 6. addDocumentFromDict
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestAddDocumentFromDict:
"""Test adding documents from a dictionary representation."""
Expand All @@ -358,6 +366,7 @@ def test_add_measurements_from_dict(self, clean_proj):
# 7. Counters
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestCounters:
"""Test counter operations: define, set, get, getAndAdd."""
Expand Down Expand Up @@ -401,6 +410,7 @@ def test_set_counter_overwrites(self, clean_proj):
# 8. Configuration
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestConfiguration:
"""Test initConfig, setConfig, getConfig."""
Expand Down Expand Up @@ -439,6 +449,7 @@ def test_init_config_does_not_overwrite(self, clean_proj):
# 9. Project properties
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestProjectProperties:
"""Test Project properties."""
Expand Down Expand Up @@ -468,6 +479,7 @@ def test_files_directory(self, proj):
# 10. saveData family — round-trip with real files
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestSaveData:
"""Test saveMeasurementData, saveCacheData, saveSimulationData with real data."""
Expand Down Expand Up @@ -575,6 +587,7 @@ def test_save_pickle(self, clean_proj, tmp_dir):
# 11. DataHandler round-trips (unit tests, no DB needed)
# ===========================================================================

@pytest.mark.unit
class TestDataHandlers:
"""Test DataHandler saveData/getData round-trips for key formats."""

Expand Down Expand Up @@ -639,6 +652,7 @@ def test_handler_pickle(self, tmp_dir):
# 12. datatypes class methods
# ===========================================================================

@pytest.mark.unit
class TestDatatypes:
"""Test datatypes introspection methods."""

Expand Down Expand Up @@ -684,6 +698,7 @@ def test_get_data_format_extension(self):
# 13. Collection direct access
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestCollectionDirect:
"""Test AbstractCollection and subclasses directly."""
Expand Down Expand Up @@ -809,6 +824,7 @@ def test_get_project_list(self):
# 14. MetadataFrame
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestMetadataFrame:
"""Test MetadataFrame.asDict and MetadataFrame.getData."""
Expand Down Expand Up @@ -851,6 +867,7 @@ def test_get_data_string(self, clean_proj):
# 15. nonDBMetadataFrame
# ===========================================================================

@pytest.mark.unit
class TestNonDBMetadataFrame:
"""Test nonDBMetadataFrame wrapping."""

Expand Down Expand Up @@ -889,6 +906,7 @@ def test_get_data_with_numpy(self):
# 16. autocache — @cacheFunction decorator
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestAutoCache:
"""Test cacheFunction decorator with real MongoDB caching."""
Expand Down Expand Up @@ -952,6 +970,7 @@ def func_b():
# 17. Export / Import round-trip
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestExportImport:
"""Test Project export and load round-trip."""
Expand Down Expand Up @@ -1004,6 +1023,7 @@ def test_export_and_load(self, tmp_dir):
# 18. Module-level functions
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestModuleFunctionsDB:
"""Test module-level functions that require MongoDB."""
Expand All @@ -1018,6 +1038,7 @@ def test_get_project_list(self, proj):
assert PROJECT_NAME in projects


@pytest.mark.unit
class TestModuleFunctionsLocal:
"""Test module-level functions that do NOT require MongoDB."""

Expand All @@ -1037,6 +1058,7 @@ def test_create_project_directory(self, tmp_dir):
# 19. parseConnectionString (no DB, no config file)
# ===========================================================================

@pytest.mark.unit
class TestParseConnectionString:
"""Test connection string parsing — pure function, no side effects."""

Expand Down Expand Up @@ -1068,6 +1090,7 @@ def test_special_chars_in_password_raises(self):
# 20. Config file management (mocked — no real config file touched)
# ===========================================================================

@pytest.mark.unit
class TestConfigFileManagement:
"""Test getMongoJSON, addOrUpdateDatabase, removeConnection, getDBNamesFromJSON.

Expand Down Expand Up @@ -1190,6 +1213,7 @@ def test_addOrUpdateDatabase_no_config_raises(self, tmp_dir, monkeypatch):
# 21. saveData with auto-format detection (requires MongoDB)
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestSaveDataAutoDetect:
"""Test that saveData auto-detects the format when dataFormat is not specified."""
Expand Down Expand Up @@ -1239,6 +1263,7 @@ def test_auto_detect_numpy(self, clean_proj, tmp_dir):
# 22. Simulations/Cache getDocumentsAsDict (requires MongoDB)
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestSimulationsCacheAsDict:
"""Test getDocumentsAsDict for Simulations and Cache (Measurements already tested)."""
Expand Down Expand Up @@ -1266,6 +1291,7 @@ def test_cache_as_dict(self, clean_proj):
# 23. Empty project edge cases (requires MongoDB)
# ===========================================================================

@pytest.mark.integration
@requires_mongo
class TestEmptyProjectEdgeCases:
"""Test queries against a project with no documents."""
Expand Down Expand Up @@ -1296,6 +1322,7 @@ def test_get_metadata_empty(self, clean_proj):
# 24. guessHandler (no DB)
# ===========================================================================

@pytest.mark.unit
class TestGuessHandler:
"""Test guessHandler with various Python objects."""

Expand Down
11 changes: 11 additions & 0 deletions hera/tests/test_demography.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def population_gdf(demo_toolkit):
# 1. Toolkit initialization and properties
# ===========================================================================

@pytest.mark.integration
class TestDemographyToolkitInit:
"""Test toolkit construction and properties (requires DB)."""

Expand All @@ -155,6 +156,7 @@ def test_population_types(self, demo_toolkit):
# 2. setDefaultDirectory
# ===========================================================================

@pytest.mark.integration
class TestSetDefaultDirectory:
def test_creates_and_sets_path(self, demo_toolkit):
with tempfile.TemporaryDirectory() as tmpdir:
Expand All @@ -172,6 +174,7 @@ def test_raises_if_not_found_and_create_false(self, demo_toolkit):
# 3. analysis.calculatePopulationInPolygon
# ===========================================================================

@pytest.mark.integration
class TestCalculatePopulationInPolygon:
def test_basic(self, demo_toolkit, population_gdf):
"""Polygon enclosing a census area returns non-empty result."""
Expand Down Expand Up @@ -294,6 +297,7 @@ def test_full_coverage_sums_to_total(self, demo_toolkit, synthetic_gdf, large_po
# 4. analysis.createNewArea
# ===========================================================================

@pytest.mark.integration
class TestCreateNewArea:
def test_simple(self, demo_toolkit, population_gdf):
"""Create area covering all population data."""
Expand Down Expand Up @@ -369,6 +373,7 @@ def test_with_specific_population_types(self, demo_toolkit, synthetic_gdf, large
# 5. presentation.plotPopulationDensity
# ===========================================================================

@pytest.mark.integration
class TestPlotPopulationDensity:
"""Test plotPopulationDensity with synthetic data (no DB needed)."""

Expand Down Expand Up @@ -459,6 +464,7 @@ def test_wgs84_input_with_reprojection(self, demo_toolkit, synthetic_gdf_wgs84):
# 6. presentation.plotPopulation
# ===========================================================================

@pytest.mark.integration
class TestPlotPopulation:
"""Test plotPopulation (absolute counts)."""

Expand Down Expand Up @@ -503,6 +509,7 @@ def test_with_existing_axes(self, demo_toolkit, synthetic_gdf):
# 7. presentation.plotPopulationByType
# ===========================================================================

@pytest.mark.integration
class TestPlotPopulationByType:
"""Test plotPopulationByType (subplot grid)."""

Expand Down Expand Up @@ -545,6 +552,7 @@ def test_custom_figsize_and_cmap(self, demo_toolkit, synthetic_gdf):
# 8. presentation.plotPopulationInPolygon
# ===========================================================================

@pytest.mark.integration
class TestPlotPopulationInPolygon:
"""Test plotPopulationInPolygon (intersection visualization)."""

Expand Down Expand Up @@ -593,6 +601,7 @@ def test_with_all_options(self, demo_toolkit, intersection_result,
# 9. presentation.plotArea
# ===========================================================================

@pytest.mark.integration
class TestPlotArea:
"""Test plotArea (custom area with population annotation)."""

Expand Down Expand Up @@ -651,6 +660,7 @@ def test_custom_styling(self, demo_toolkit, area_result, synthetic_gdf):
# 10. presentation.plotPopulationOnMap (mocked TilesToolkit)
# ===========================================================================

@pytest.mark.integration
class TestPlotPopulationOnMap:
"""Test plotPopulationOnMap with a mocked TilesToolkit."""

Expand Down Expand Up @@ -716,6 +726,7 @@ def test_custom_zoom_and_styling(self, demo_toolkit, synthetic_gdf):
# 11. Edge cases
# ===========================================================================

@pytest.mark.integration
class TestEdgeCases:
"""Edge cases and error handling."""

Expand Down
12 changes: 12 additions & 0 deletions hera/tests/test_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ def experiment(exp_home):
# 1. experimentHome
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestExperimentHome:
Expand Down Expand Up @@ -393,6 +394,7 @@ def test_nonexistent_experiment(self, exp_home):
# 2. experimentSetupWithData — properties
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestExperimentSetup:
Expand Down Expand Up @@ -426,6 +428,7 @@ def test_get_experiment_data(self, experiment):
# 3. TrialSetWithData and TrialWithdata
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestTrialAccess:
Expand Down Expand Up @@ -469,6 +472,7 @@ def test_trial_get_data_trh(self, experiment):
# 4. EntityTypeWithData
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestEntityType:
Expand Down Expand Up @@ -501,6 +505,7 @@ def test_get_data_trial(self, experiment):
# 5. EntityWithData
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestEntity:
Expand All @@ -522,6 +527,7 @@ def test_entity_get_data(self, experiment):
# 6. getDataFromDateRange
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestGetDataFromDateRange:
Expand All @@ -542,6 +548,7 @@ def test_basic(self, experiment):
# 7. dataEngineFactory
# ===========================================================================

@pytest.mark.unit
@pytest.mark.skipif(not _ENGINE_AVAILABLE, reason="dataEngine not importable")
class TestDataEngineFactory:
"""Test engine type constants (no DB needed)."""
Expand All @@ -561,6 +568,7 @@ def test_constants_are_strings(self):
# 8. experimentAnalysis — synthetic (no DB)
# ===========================================================================

@pytest.mark.unit
@pytest.mark.skipif(not _ANALYSIS_AVAILABLE, reason="experimentAnalysis not importable")
class TestAnalysisSynthetic:
"""Test analysis methods with synthetic DataFrames."""
Expand Down Expand Up @@ -608,6 +616,7 @@ def __init__(self):
# 9. experimentPresentation — smoke test (no DB)
# ===========================================================================

@pytest.mark.unit
@pytest.mark.skipif(not _PRESENTATION_AVAILABLE, reason="experimentPresentation not importable")
class TestPresentationInit:
"""Test presentation can be instantiated."""
Expand All @@ -632,6 +641,7 @@ class MockAnalysis:
# 10. Parsers — synthetic (no DB)
# ===========================================================================

@pytest.mark.unit
class TestParsers:
"""Test parsers with synthetic data."""

Expand All @@ -648,6 +658,7 @@ def test_campbell_binary_parser_import(self):
# 11. Argos zip parsing (no DB)
# ===========================================================================

@pytest.mark.integration
@requires_experiment
class TestArgosZipParsing:
"""Test that Argos zip files are parsed correctly."""
Expand Down Expand Up @@ -717,6 +728,7 @@ def test_trial_entities_table(self, experiment_dir):
# 12. Full integration: load → navigate → get data
# ===========================================================================

@pytest.mark.integration
@requires_mongo
@requires_experiment
class TestFullIntegration:
Expand Down
Loading
Loading