Conversation
Updating test for endpoints
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
bf2caf2 to
b3ebb01
Compare
- Use conditional logger.info calls instead of mixing f-strings with loguru placeholders in log_start/log_retry - Add comment explaining _saas_version is snapshotted at construction time - Fix missing blank line between test functions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix unsorted imports (ruff I001) in base.py, seed.py, and connector_template_endpoints.py - Fix mypy return type error in list_connector_template_versions — convert ORM rows to SaaSConfigVersionResponse explicitly - Annotate saas_config_version and connection_config_saas_history tables in db_dataset.yml to fix fides_db_scan - Add changelog entry for PR #7688 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review Summary
Good feature overall — the two-table design (template-level SaaSConfigVersion vs. connection-scoped ConnectionConfigSaaSHistory) is well-motivated and the append-only history approach is sound. Test coverage for the connection-history endpoints is thorough. A few things need attention before merge:
Critical (Must Fix)
.mcp.jsonshould not be committed — contains hardcoded local machine paths (/Users/bruno/Ethyca/...). Add to.gitignoreinstead.- Unused imports —
load_config_from_stringandload_dataset_from_stringare imported inconnector_template_endpoints.pybut never used. - Missing tests — the three new connector-template version endpoints (
/versions,/versions/{version}/config,/versions/{version}/dataset) have no test coverage.
Suggestions
- Model method doing cross-model work —
update_saas_confignow importsDatasetConfigandConnectionConfigSaaSHistory, queries related rows, and creates a snapshot. This is heavy for a model method and makes it hard to test in isolation. Consider moving the orchestration to a service layer. - Business logic in the route handler —
SaaSConfigVersion.upsertis called directly insidepatch_saas_config. This belongs in a service or co-located withupdate_saas_config. - Double
ConnectorRegistry.get_connector_templatecall — inpatch_saas_configthe template can be fetched twice. Fetching once before theif not existing_saas_configblock and reusing the result is cleaner. - Missing index on
connection_key— the column is denormalized specifically so history survives connection deletion, but no index backs it up. - DB queries in the endpoint file —
_get_version_rowand the inline queries inlist_connector_template_versionsshould live in a service or repository, not directly in the endpoint module. - Startup queries one row per connector per boot —
sync_oob_saas_config_versionscan be made more efficient by fetching all existing(connector_type, version)pairs in one query and only inserting the missing ones.
Nice to Have
- Migration filename prefix — both new migration files use
xx_rather than the hash-based prefix convention used elsewhere in this repo. - Bare
dictinSaaSConfigVersionDetailResponse— useDict[str, Any]to match the sibling schema's style. - Double
None-coalescing —datasets if datasets else Noneat the call site combined withdatasets or Noneinsidecreate_snapshotis redundant; handle it in one place.
.mcp.json
Outdated
There was a problem hiding this comment.
Critical: Personal developer config should not be committed.
This file contains hardcoded paths to the author's local machine (/Users/bruno/Ethyca/...). It's a developer-specific MCP server configuration and should be added to .gitignore rather than committed to the repository. Anyone cloning the repo will have a broken/irrelevant .mcp.json pointing to non-existent paths on their machine.
| from fides.api.service.connectors.saas.connector_registry_service import ( | ||
| ConnectorRegistry, | ||
| CustomConnectorTemplateLoader, | ||
| ) |
There was a problem hiding this comment.
Unused imports.
load_config_from_string and load_dataset_from_string are imported here but never called in any of the new endpoint handlers in this file. They should be removed.
| connection_key=self.key, | ||
| version=saas_config.version, | ||
| config=self.saas_config, | ||
| datasets=datasets if datasets else None, |
There was a problem hiding this comment.
Double redundancy: datasets if datasets else None → datasets or None → still None when list is empty.
The call site passes datasets if datasets else None and the create_snapshot body stores datasets or None. Both expressions reduce to the same thing — the double guard is confusing and implies the distinction matters somewhere in between. Pick one location for the None-coalescing and remove the other.
More importantly, an empty [] (no associated datasets found) and None (datasets intentionally not provided) have different semantics. If the intent is always to store None when there are no datasets, that's fine, but it should be explicit rather than implicit via two chained falsy checks.
| self.secrets = updated_secrets | ||
| self.saas_config = saas_config.model_dump(mode="json") | ||
|
|
||
| datasets = [ |
There was a problem hiding this comment.
Model method doing cross-model querying and side-effect writes — consider moving to a service.
update_saas_config now imports DatasetConfig and ConnectionConfigSaaSHistory, queries related rows, and creates a history snapshot, all inside a model method. This makes the model aware of other models' internals and adds a hidden DB query on every call.
Ideally this orchestration belongs in a service layer that coordinates the update and the snapshot creation explicitly. At minimum, accepting a pre-fetched datasets list as an optional parameter to update_saas_config would keep the model's surface area narrow and let callers decide whether to include dataset context.
| version=saas_config.version, | ||
| config=saas_config.model_dump(mode="json"), | ||
| dataset=None, # PATCH only updates the config; dataset is managed separately | ||
| is_custom=template.custom if template else False, |
There was a problem hiding this comment.
Business logic directly in route handler.
Calling SaaSConfigVersion.upsert(...) here mixes persistence logic into the route layer. This belongs in a service (or at minimum, inside connection_config.update_saas_config()), so routes stay thin. The existing update_saas_config call just above already does the config update — the version upsert could be co-located there or delegated to a dedicated service method.
| ["connection_config_id"], | ||
| ["connectionconfig.id"], | ||
| ondelete="SET NULL", | ||
| ), |
There was a problem hiding this comment.
Missing index on connection_key.
connection_key is explicitly denormalized so history remains queryable after the parent ConnectionConfig is deleted (when connection_config_id becomes NULL). But without an index on connection_key, those orphaned rows can only be found via a full table scan.
If the intent is purely for audit/ad-hoc use this may be acceptable, but since the docstring calls out post-deletion queryability as a design goal, an index would make that goal actionable:
op.create_index(
op.f("ix_connection_config_saas_history_connection_key"),
"connection_config_saas_history",
["connection_key"],
unique=False,
)| class SaaSConfigVersionDetailResponse(SaaSConfigVersionResponse): | ||
| """Full detail for a single version, including config and dataset as raw dicts.""" | ||
|
|
||
| config: dict |
There was a problem hiding this comment.
Use Dict[str, Any] instead of bare dict for consistency.
ConnectionConfigSaaSHistoryDetailResponse (the sibling schema) uses Dict[str, Any] for its config field. Using the bare dict annotation here is less explicit and inconsistent. Prefer Dict[str, Any] (already imported in the project's typing conventions) for both fields.
|
|
||
| @router.get( | ||
| CONNECTOR_TEMPLATES_VERSIONS, | ||
| dependencies=[Security(verify_oauth_client, scopes=[CONNECTOR_TEMPLATE_READ])], |
There was a problem hiding this comment.
No tests for the three new connector template version endpoints.
list_connector_template_versions, get_connector_template_version_config, and get_connector_template_version_dataset have no test coverage. The TestListSaaSConfigHistory / TestGetSaaSConfigHistoryByVersion classes cover the connection-scoped endpoints well — the same level of coverage (unauthenticated, wrong scope, not found, happy path) should exist for the template-version endpoints too.
| ) -> Optional[SaaSConfigVersion]: | ||
| """ | ||
| Returns the most recent stored row for (connector_type, version). | ||
|
|
There was a problem hiding this comment.
DB query helper living in the endpoint file.
_get_version_row performs a direct db.query(SaaSConfigVersion) inside connector_template_endpoints.py. Endpoint files in this codebase are generally thin — data access logic belongs in a service or repository layer. This helper (and the inline queries in list_connector_template_versions) should live in a service method.
| SaaSConfig, # pylint: disable=import-outside-toplevel | ||
| ) | ||
| from fides.api.service.connectors.saas.connector_registry_service import ( # pylint: disable=import-outside-toplevel | ||
| FileConnectorTemplateLoader, |
There was a problem hiding this comment.
Startup queries one row per OOB connector on every boot.
sync_oob_saas_config_versions iterates over every bundled connector template and issues a SELECT per connector to check whether the row already exists. For OOB connectors (immutable once written) this is a no-op after first deploy, but the queries still fire on every startup.
Consider a set-difference approach: fetch all (connector_type, version) pairs already in the table in a single query and only parse + insert the ones that are missing. This keeps startup overhead bounded regardless of how many OOB connectors exist.
Ticket []
Description Of Changes
Setting up the backend for Saas Config historic information
This PR sets up the backend for the historical record keeping of saas integrations, creating two new models,
SaaSConfigVersionfor storing version changes of out of the box integrations, andConnectionConfigSaaSHistory, to store the history of a specific instance of a saas connection. It also creates the endpoints that would be used to request data from these history versionsCode Changes
Steps to Confirm
/connector-templates/{connector_template_type}/versions/connection/{connection_key}/saas-historyand check that the saas history is recordedPre-Merge Checklist
CHANGELOG.mdupdatedmaindowngrade()migration is correct and works