🚨 ⚠️ 💀 [NO TICKET]: Well inventory csv Feature Tests#247
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # core/initializers.py
…ling for unsupported file types
Codecov Report❌ Patch coverage is
|
… error handling, and SRID support
jacob-a-brown
left a comment
There was a problem hiding this comment.
Because a number of updates/changed from BDMS-221 and BDMS-227 will influence the import of these data should we wait for those two to get merged before this PR? So that necessary updates/refactors can occur
chasetmartin
left a comment
There was a problem hiding this comment.
This is looking great. The updates to the feature file based on AMP feedback should be relatively straightforward: 2 possible contacts and a few additional notes fields.
…ved location handling
…in well inventory processing
…in well inventory processing
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # =============================================================================== | ||
| from transfers.util import get_transferable_wells, read_csv |
There was a problem hiding this comment.
This appears to be an ad-hoc analysis script committed under the production transfers/ package. If it’s intended for one-off/interactive use, consider moving it to a scripts/ (or similar) directory to avoid it being treated as library code, and to reduce long-term maintenance surface in the transfers module.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| assert "datalogger_installation_status" in context.water_well_data | ||
| assert "open_status" in context.water_well_data | ||
| assert ( | ||
| context.water_well_data["is_suitable_for_datalogger"] | ||
| == context.objects["wells"][0].is_suitable_for_datalogger | ||
| context.water_well_data["datalogger_installation_status"] | ||
| == context.objects["wells"][0].datalogger_installation_status |
There was a problem hiding this comment.
This step asserts datalogger_installation_status, but the rest of the PR (e.g. Thing.datalogger_suitability_status property and OGC feature mapping) uses datalogger_suitability_status. Unless the API/ORM actually exposes datalogger_installation_status, this will fail. Align the test to the implemented field/property name.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| "well_construction_method_source", | ||
| ] | ||
| ) | ||
| well_data = data.model_dump(exclude=EXCLUDED_FIELDS) |
There was a problem hiding this comment.
pydantic.BaseModel.model_dump(exclude=...) expects a set/dict include-exclude structure (Pydantic v2 IncEx), not a list. Passing a list can raise a type error depending on the runtime path. Consider defining EXCLUDED_FIELDS as a set[str] (or using exclude=set(EXCLUDED_FIELDS)) to ensure compatibility.
| well_data = data.model_dump(exclude=EXCLUDED_FIELDS) | |
| well_data = data.model_dump(exclude=set(EXCLUDED_FIELDS)) |
| # router = APIRouter(prefix="/well-inventory-csv") | ||
|
|
||
|
|
||
| # @router.post("") |
There was a problem hiding this comment.
The entire API implementation is commented out, so /well-inventory-csv will not be registered and the Behave tests that POST to that endpoint will fail. Implement and export the FastAPI router (or delete the feature/steps that assume it exists) so the endpoint is actually available.
| class Group(Base, AutoBaseMixin, ReleaseMixin): | ||
| # --- Column Definitions --- | ||
| name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) | ||
| name: Mapped[str] = mapped_column(String(100), nullable=False) |
There was a problem hiding this comment.
This changes the DB uniqueness rules for group (from unique name to composite unique (name, group_type)), but this PR doesn’t include an Alembic migration to drop the old constraint and add the new one. Without a migration the runtime schema will drift and deployments/tests that rely on migrations will fail. Add an Alembic migration that (1) drops the previous unique constraint on name (if present) and (2) creates uq_group_name_type, accounting for existing data.
…_id in well_inventory_csv.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| # router = APIRouter(prefix="/well-inventory-csv") | ||
|
|
||
|
|
||
| # @router.post("") | ||
| # async def well_inventory_csv( |
There was a problem hiding this comment.
This module is entirely commented out, so the /well-inventory-csv endpoint won’t exist at runtime. The Behave steps added in this PR post to /well-inventory-csv, so those feature tests will fail unless the router and handler are restored (and the router is included in the main FastAPI app).
| elevation_ft = float(model.elevation_ft) | ||
| elevation_m = convert_ft_to_m(elevation_ft) | ||
|
|
||
| loc = Location( |
There was a problem hiding this comment.
The CSV row contains elevation_method, and downstream tests in this PR assert location.elevation_method equals the CSV value. _make_location currently sets only point and elevation. If Location has an elevation_method column (as the tests imply), it needs to be persisted on the Location record (and/or the tests should be updated to validate DataProvenance instead).
| loc = Location( | |
| elevation=elevation_m, | |
| elevation_method=model.elevation_method, |
| @@ -221,7 +221,7 @@ | |||
| "description": null | |||
| }, | |||
| { | |||
There was a problem hiding this comment.
Renaming the lexicon category from origin_source to origin_type is a breaking change for any existing DB-seeded lexicon data and any code expecting the old category name. This needs an accompanying migration/seed-update path (or backward-compatible aliasing) to ensure environments with an existing lexicon table don’t fail when building enums or validating lexicon terms.
| { | |
| { | |
| "name": "origin_source", | |
| "description": null | |
| }, | |
| { |
| # router = APIRouter(prefix="/well-inventory-csv") | ||
|
|
||
|
|
||
| # @router.post("") | ||
| # async def well_inventory_csv( | ||
| # user: amp_editor_dependency, | ||
| # session: session_dependency, | ||
| # file: UploadFile = File(...), | ||
| # ): |
There was a problem hiding this comment.
The entire /well-inventory-csv API router and handler are commented out, but the Behave steps post to /well-inventory-csv. As-is, this will return 404 and break feature tests; please implement the actual APIRouter route (or remove the feature steps) and ensure the router is included in the app.
| validate_email, | ||
| AfterValidator, | ||
| field_validator, | ||
| ) |
There was a problem hiding this comment.
Importing validate_email directly from pydantic is not reliable (and commonly fails in Pydantic v2 where it’s not exported from the top-level). Use EmailStr for the field type or import the validator from the supported module (e.g., pydantic.networks) to avoid runtime ImportError.
| validate_email, | |
| AfterValidator, | |
| field_validator, | |
| ) | |
| AfterValidator, | |
| field_validator, | |
| ) | |
| from pydantic.networks import validate_email |
|
|
||
| loc = Location( | ||
| point=transformed_point.wkt, | ||
| elevation=elevation_m, |
There was a problem hiding this comment.
The import creates Location records without setting elevation_method, but tests/test_well_inventory.py asserts location.elevation_method == file_content['elevation_method']. Either populate Location.elevation_method here (if it’s a column), or adjust the test to validate the intended provenance field instead.
| elevation=elevation_m, | |
| elevation=elevation_m, | |
| elevation_method=model.elevation_method, |
| assert "datalogger_installation_status" in context.water_well_data | ||
| assert "open_status" in context.water_well_data | ||
| assert ( | ||
| context.water_well_data["is_suitable_for_datalogger"] | ||
| == context.objects["wells"][0].is_suitable_for_datalogger | ||
| context.water_well_data["datalogger_installation_status"] | ||
| == context.objects["wells"][0].datalogger_installation_status | ||
| ) |
There was a problem hiding this comment.
The codebase surfaces the computed field as datalogger_suitability_status (see db/thing.py and schemas/thing.py), but this step asserts datalogger_installation_status and accesses Thing.datalogger_installation_status (which doesn’t appear in the model changes). Align the step to the actual API/property name (datalogger_suitability_status) to prevent failing feature tests.
| EXCLUDED_FIELDS = [ | ||
| "location_id", | ||
| "group_id", | ||
| "well_purposes", | ||
| "well_casing_materials", | ||
| "measuring_point_height", | ||
| "measuring_point_description", | ||
| "well_completion_date_source", | ||
| "well_construction_method_source", | ||
| "well_depth_source", | ||
| "alternate_ids", | ||
| "monitoring_frequencies", | ||
| "notes", | ||
| "is_suitable_for_datalogger", | ||
| "is_open", | ||
| "well_status", | ||
| ] |
There was a problem hiding this comment.
Pydantic v2 model_dump(exclude=...) expects a set/dict-style include/exclude structure; passing a plain list can raise a type error depending on the exact Pydantic version. To make this robust, define EXCLUDED_FIELDS as a set[str] (or a dict) before passing it to model_dump.
| rebuild = False | ||
| # rebuild = True | ||
| erase_data = True | ||
| erase_data = False | ||
| if rebuild: | ||
| erase_and_rebuild_db() | ||
| _drop_and_rebuild_db() |
There was a problem hiding this comment.
Hard-coding rebuild = True makes every Behave run drop and rebuild the database, which is slow and potentially destructive for local workflows. Since .env.example introduces REBUILD_DB, this should be controlled via environment variables (e.g., default to no rebuild, enable in CI when needed).
| def test_upload_headers_only(self): | ||
| """Upload fails when CSV has headers but no data rows.""" | ||
| file_path = Path("tests/features/data/well-inventory-no-data-headers.csv") | ||
| if file_path.exists(): | ||
| result = well_inventory_csv(file_path) | ||
| assert result.exit_code == 1 | ||
| assert "No data rows found" in result.stderr |
There was a problem hiding this comment.
Several tests are effectively skipped when fixture files are missing (if file_path.exists(): ...). This can silently reduce coverage and hide regressions. Prefer asserting the fixture exists (fail fast) or generate the CSV content in the test using tmp_path so the test always executes.
| permission = _make_well_permission( | ||
| well=well, | ||
| contact=contact_for_permissions, | ||
| permission_type=permission_type, | ||
| permission_allowed=permission_allowed, | ||
| start_date=model.date_time.date(), | ||
| ) |
There was a problem hiding this comment.
_make_well_permission raises PydanticStyleException when permissions are provided but no contact exists. _import_well_inventory_csv only catches ValueError and DatabaseError, so this exception would bubble out and abort the whole import unexpectedly. Handle PydanticStyleException (or convert this case into a row-level validation error) so the import returns consistent validation_errors output.
| permission = _make_well_permission( | |
| well=well, | |
| contact=contact_for_permissions, | |
| permission_type=permission_type, | |
| permission_allowed=permission_allowed, | |
| start_date=model.date_time.date(), | |
| ) | |
| try: | |
| permission = _make_well_permission( | |
| well=well, | |
| contact=contact_for_permissions, | |
| permission_type=permission_type, | |
| permission_allowed=permission_allowed, | |
| start_date=model.date_time.date(), | |
| ) | |
| except PydanticStyleException as exc: | |
| # Convert to a ValueError so the import logic can treat this as | |
| # a row-level validation error instead of aborting the import. | |
| raise ValueError(str(exc)) from exc |
🚨⚠️ 💀 DO NOT MERGE UNTIL VERIFYING DB SCHEMA IS COMPATIBLE WITH THIS PR
Why
This PR addresses the following problem / context:
How
Implementation summary - the following was changed / added / removed:
Notes
Any special considerations, workarounds, or follow-up work to note?