fix(google-sheets): upgrade to SDK 2.0.0, fix Lambda crash on error paths#289
Conversation
…aths, add unit tests
Converts all error paths from ActionResult(data={error, result: False}) to
ActionError(message=...) to fix output schema validation crashes in SDK 2.0.0.
Removes error/result fields from all output schemas. Adds 51 pytest unit tests.
🔍 Integration Validation ResultsCommit: Changed directories:
|
TheRealAgentK
left a comment
There was a problem hiding this comment.
LGTM and thanks for moving fast on this — the production Lambda crash fix is the priority here.
SDK 2.0.0 migration is clean — context.fetch().data, ActionError(message=...) everywhere, schema envelope fields removed, version bumped. 51 unit tests.
Local CI verified: validate_integration ✅, check_code ✅, ruff ✅, pytest ✅.
Follow-up tracked in #297 (high priority) — please add a test_google_sheets_integration.py in a follow-up PR. The class of bug this PR fixed (schema-validation failures from real API responses) is exactly what mocked unit tests cannot catch, so this gap is worth closing soon. Not blocking this merge given the live impact.
Process nit: branch doesn't follow <type>/<issue#>/<desc> per AGENTS.md. Please follow the convention next time.
…ate .env.example
- Fix build_credentials to use context.auth.get("credentials", {}).get("access_token", "")
- Add test_google_sheets_integration.py with GOOGLE_SHEETS_ACCESS_TOKEN skip guard
- Add GOOGLE_SHEETS_ACCESS_TOKEN and GOOGLE_SHEETS_TEST_SPREADSHEET_ID to .env.example
- Apply ruff format to all google-sheets files
|
@TheRealAgentK unit and live integration tests are all done. Here is a summary: Unit tests: 51/51 passed at 99% coverage across all actions. Live integration tests: 13/14 passed against the real Google Sheets and Drive API, covering all 10 actions:
Also fixed the optional param access issue flagged by the config-code sync check (pageSize and pageToken now use inputs.get()) and ruff formatting is clean at line-length 120. |
TheRealAgentK
left a comment
There was a problem hiding this comment.
1. Dead, broken helper — tests/test_google_sheets_integration.py:57-69
def get_first_spreadsheet_id(live_context):
import asyncio
async def _get(): ...
return asyncio.get_event_loop().run_until_complete(_get())Never called — every test inlines the sheets_list_spreadsheets call. asyncio.get_event_loop() is also deprecated and would conflict with pytest-asyncio's loop if invoked. Either delete it or use it (replace the inline list-call boilerplate at the top of each chained test).
2. Weak assertions in destructive tests — lines 258, 281, 307
assert result.result is not Noneresult.result is ActionResultData on success or ActionErrorData on failure — always truthy. The test passes even when the action returned an ActionError. Use:
from autohive_integrations_sdk.integration import ResultType
assert result.type == ResultType.ACTION3. Stale tests/context.py — delete
Pre-SDK-2.0 testbed shim. Unused once conftest is fixed.
4. tests/conftest.py is a no-op
sys.path.insert(0, os.path.dirname(__file__)) puts tests/ on path, not the integration root. The real wiring is the importlib block duplicated across all 4 test files. Replacement:
import os, sys
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))Test files then drop the importlib block and use from google_sheets import google_sheets.
5. inputs.get("pageSize") truthy check — google_sheets.py:57, 59
pageSize=0 is silently dropped. Use is not None. Minor.
#3 and #4 are the same issues raised again in #284 / #285 / #306.
…heck A pageSize of 0 (or pageToken of empty string) was previously dropped by the truthy check. Use 'is not None' so explicit falsy values still flow through to the Drive API request.
- conftest.py now adds the integration root to sys.path (was a no-op inserting tests/ which exposed nothing useful). - Each unit test file replaces the spec_from_file_location dance with a plain 'from google_sheets import google_sheets'. - @patch targets switch from per-file unique mod names back to 'google_sheets.build'. - Delete stale tests/context.py (pre-SDK-2.0 testbed shim, no longer imported anywhere).
…on tests 'result.result is not None' is always true (it's ActionResultData on success or ActionErrorData on failure), so the destructive tests for sheets_format_range, sheets_freeze, and sheets_batch_update would pass even when the action returned an ActionError. Assert on result.type instead so the tests fail when the action errors.
The helper was never called — every test inlined the sheets_list_spreadsheets call directly. It also used the deprecated asyncio.get_event_loop().run_until_complete() pattern, which would clash with pytest-asyncio's loop if it ever were invoked.
…tests Mirrors the unit test refactor in 3a33344 — replace the spec_from_file_location dance with 'from google_sheets import google_sheets' and remove the now-redundant inline ResultType import in TestDuplicateSpreadsheet.
The 'owner' input is interpolated unescaped into the Drive q-string, which would break (and could in principle inject into) the query for emails containing apostrophes. Mirror the existing escape applied to 'name_contains'.
…pdate The handler's 'requests must be a list of dicts' check is unreachable: the SDK validates the input schema before the handler runs and returns a VALIDATION_ERROR for non-list / non-dict-item inputs. The two existing unit tests confirm this — both still pass and assert ResultType.VALIDATION_ERROR coming from the SDK layer.
…h format style - TestListSpreadsheets: assert pageSize and pageToken reach drive.files().list(**params) when supplied. Closes the 1% coverage gap and locks in the 'is not None' fix from 5e9f80a. - TestBuildCredentials: direct tests for the empty-string fallback paths added to build_credentials so a regression to bracket indexing on missing 'credentials' or 'access_token' is caught. - TestFormatRange: richer style payload (background + textFormat + alignment) flows through to userEnteredFormat unchanged, and the request range merges sheetId with the gridRange.
TheRealAgentK
left a comment
There was a problem hiding this comment.
Approving now - @Shubhank-Jonnada please do another review on your end before merging.
The integration root is already on sys.path via the workspace-level conftest.py (which patches Integration.load to find config.json). The redundant importlib.util.spec_from_file_location dance and the tests/conftest.py sys.path tweak are no longer needed. Matches the simpler bootstrap pattern used by google-sheets in PR #289.
The previous conftest.py inserted the tests directory itself onto sys.path, which is a no-op for the intended purpose of making the integration's modules importable from test files. Insert the parent (integration root) instead, matching the pattern from PR #280 (supadata) and PR #289 (google-sheets).
With tests/conftest.py now putting the integration root on sys.path and the workspace conftest.py patching Integration.load to find config.json from the caller's directory, tests can import the integration with a plain 'from humanitix import humanitix'. Removes: - The importlib.util.spec_from_file_location dance in both test files. - The process-global os.chdir(_parent) calls (the integration test file did not restore cwd, leaving it inside humanitix/ for any test file collected after it). - The legacy tests/context.py shim from the pre-SDK-2.0 testbed. Matches the simpler pattern used in PR #280 (supadata), PR #289 (google-sheets), and PR #307 (coda).
Errors now flow through ActionError, so the boolean 'result' flag and the 'statusCode'/'error'/'message' error fields are dead weight in output schemas. Removes them from config.json and from the data dictionaries built by fetch_single_resource and build_paginated_result in helpers.py. Drops the corresponding 'assert data["result"] is True' lines from the unit tests. Brings humanitix in line with PR #280 (supadata), PR #289 (google-sheets), and PR #307 (coda).
With tests/conftest.py putting the integration root on sys.path and the workspace conftest.py patching Integration.load to find config.json, test files can now import the integration with a plain 'from app_business_reviews import app_business_reviews'. Removes the importlib.util.spec_from_file_location dance from both test files and the now-unused dependencies/ sys.path entry. Also deletes the legacy tests/context.py shim from the pre-SDK-2.0 testbed. Matches the simpler pattern used in PRs #280, #289, #306, #307.
…ts (#285) * feat(app-business-reviews): upgrade SDK to 2.0.0, add pytest unit tests - Pin autohive-integrations-sdk~=2.0.0 in requirements.txt - Add .data to all context.fetch() return accesses - Convert raise ValueError to return ActionError(message=...) - Return ActionResult(data=..., cost_usd=0.0) from all action handlers - Bump config.json version to 2.0.0 - Add tests/conftest.py and 40-test unit suite covering all 6 actions * fix(app-business-reviews): fix auth, optional params, add proper unit and integration tests - Fix api_key default from {} to "" in all action handlers - Delete old manual asyncio test file - Add test_app_business_reviews_integration.py with pytest.mark.integration, APP_BUSINESS_REVIEWS_API_KEY env var - Add APP_BUSINESS_REVIEWS_API_KEY to .env.example * fix(app-business-reviews): fix integration tests per skill guidelines * fix(app-business-reviews): fix ruff formatting issues * fix(app-business-reviews): fix ruff formatting * fix(app-business-reviews): reformat with line-length 120 to match tooling ruff config * fix(app-business-reviews): correct mock_context.auth shape in unit tests The source code reads context.auth.get("credentials", {}).get("api_key", ""), but the test fixture set ctx.auth = {"api_key": "..."}. As a result every action received api_key="" in unit tests. The mismatch was silent because no test asserted on params["api_key"]. Adds a regression assertion in TestSearchAppsIOS::test_request_params_include_term that verifies api_key is forwarded from the credentials block, so any future auth-shape drift will fail the suite immediately. * fix(app-business-reviews): point tests/conftest.py at integration root The previous conftest inserted the tests directory itself onto sys.path, which is a no-op for the intended purpose of making the integration's modules importable from test files. Insert the parent (integration root) instead, matching the pattern from PRs #280, #289, #306, and #307. * refactor(app-business-reviews): drop importlib boilerplate from tests With tests/conftest.py putting the integration root on sys.path and the workspace conftest.py patching Integration.load to find config.json, test files can now import the integration with a plain 'from app_business_reviews import app_business_reviews'. Removes the importlib.util.spec_from_file_location dance from both test files and the now-unused dependencies/ sys.path entry. Also deletes the legacy tests/context.py shim from the pre-SDK-2.0 testbed. Matches the simpler pattern used in PRs #280, #289, #306, #307. * refactor(app-business-reviews): remove response = None / 'is not None' guards Both get_reviews_google_play and get_reviews_google_maps tracked the last response object across the pagination loop just so the post-loop code could call response.data.get(...) for product/place info, with a defensive 'if response is not None' guard for the max_pages=0 case. Capture app_info / place_info inside the loop instead, initialized to an empty dict before the loop. Same behavior, no defensive guard, no loop-leaked variable. * refactor(app-business-reviews): restore post-loop app/place_info extraction Reverts the in-loop assignment introduced in c1455c7. The original code extracted product_info / place_info from the response variable once after the pagination loop, which is the more idiomatic shape (one read, one write). Keeps the sentinel-and-guard pattern that c1455c7 was meant to clean up, but adds a comment explaining why the guard is there (max_pages=0 edge case where the loop never executes). --------- Co-authored-by: Shubhank <72601061+Sagsgit@users.noreply.github.com> Co-authored-by: Kai Koenig <kai@ventego-creative.co.nz>
* fix(coda): upgrade to SDK 2.0.0, add unit and integration tests, fix validation
- Upgrade autohive-integrations-sdk from 1.0.2 to 2.0.0
- Update all action handlers to return ActionResult/ActionError instead of raw dicts
- Fix auth header extraction (api_token directly from context.auth, not nested under credentials)
- Fix response data access via response.data.get() for FetchResponse objects
- Fix config.json display_name capitalisation ("Coda")
- Delete old manual test script (tests/test_coda.py, tests/context.py)
- Add tests/conftest.py for pytest path resolution
- Add tests/test_coda_unit.py with 34 mocked unit tests covering all 16 actions
- Add tests/test_coda_integration.py with live API tests (skipped without CODA_API_KEY)
* chore(coda): bump version to 1.1.0
* fix(coda): replace bracket access with inputs.get() for optional params to fix linter warnings
* fix(coda): use bracket access for required params, inputs.get() only for optional params
* fix(coda): bump version to 2.0.0, remove error fields from output schemas, document CODA_API_KEY in .env.example
* fix(coda): read api_token from context.auth["credentials"] wrapper, not directly from auth root
* test(coda): add unit tests for update_doc, delete_doc, create_page, update_page, delete_page, update_row
* fix(coda): remove elevenlabs entry from .env.example
* fix(coda): remove result field from ActionResult returns and output schemas
* fix(coda): remove stale result assertions from unit tests
* fix(coda): remove stale result key assertion from integration test
* test(coda): expand integration tests to cover all 20 actions
* fix(coda): fix ruff line length violations in integration tests
* fix(coda): preserve explicit false for disable_parsing
Use 'is not None' check so callers can pass disable_parsing=false
explicitly. The previous truthiness check silently dropped false,
making the parameter unsettable from a non-default value.
* refactor(coda): drop importlib boilerplate from test bootstrap
The integration root is already on sys.path via the workspace-level
conftest.py (which patches Integration.load to find config.json).
The redundant importlib.util.spec_from_file_location dance and the
tests/conftest.py sys.path tweak are no longer needed.
Matches the simpler bootstrap pattern used by google-sheets in PR #289.
* test(coda): cover optional params, pagination tokens, and disable_parsing=false
Adds TestOptionalParams class exercising every previously untested
optional input branch across list_docs, list_pages, update_page,
list_tables, list_columns, list_rows, get_row, upsert_rows, and
update_row, plus the next_page_token surfacing for the list endpoints.
Includes explicit regression coverage for upsert_rows and update_row
with disable_parsing=false (locks in the fix from 0213f43 — the
previous truthiness check silently dropped the explicit false value).
Coverage: 92% -> 100%.
* test(coda): restore tests/conftest.py for tooling validator
The workspace-level conftest.py already handles sys.path so the file
is functionally unnecessary, but validate_integration.py requires
tests/conftest.py (or tests/context.py) to be present in every
integration.
* test(coda): add full live integration test suite for all 20 actions
Covers all actions exercised against the real Coda API:
- Docs: list, get, update, create, delete
- Pages: list, get, update, create, delete
- Tables/Columns/Rows: skipped gracefully when no pre-existing table doc exists
(Coda API does not support creating tables programmatically; set CODA_TABLE_DOC_ID
env var or create a doc with a table manually to enable those tests)
| Category | Tests | Status |
|--------------|-------|---------|
| Docs | 9 | passing |
| Pages | 7 | passing |
| Tables | 12 | skipped (no table doc in account) |
Session fixtures create/teardown a fresh doc + page so tests are idempotent.
* style(coda): fix E501 line-too-long and ruff formatting in integration tests
---------
Co-authored-by: Shubhank <72601061+Sagsgit@users.noreply.github.com>
Co-authored-by: Kai Koenig <kai@ventego-creative.co.nz>
…#304) (#306) * fix(humanitix): add proper unit and integration tests, fix Integration.load() - Fix humanitix.py to use Integration.load("config.json") so the config is resolved relative to cwd (required for multi-file integrations with an actions/ sub-package, per SDK documentation) - Delete the old manual test script (tests/test_humanitix.py) - Add tests/conftest.py for pytest path resolution - Add tests/test_humanitix_unit.py: 33 unit tests covering all 6 actions (get_events, get_orders, get_tickets, get_tags, check_in, check_out) using AsyncMock for context.fetch - Add tests/test_humanitix_integration.py: live integration tests for read-only actions (get_events, get_tags) that skip if HUMANITIX_API_KEY is not set * fix(humanitix): migrate to SDK 2.0.0, fix FetchResponse handling, use ActionError for errors * fix(humanitix): bump version to 2.0.0, fix checkin SDK 2.0 migration, use Integration.load() * fix(humanitix): guard non-dict error bodies in fetch_single_resource to avoid AttributeError * fix(humanitix): use curl_cffi to bypass Cloudflare in integration tests * test(humanitix): add integration tests for get_orders, get_tickets, check_in, check_out * fix(humanitix): point tests/conftest.py at integration root The previous conftest.py inserted the tests directory itself onto sys.path, which is a no-op for the intended purpose of making the integration's modules importable from test files. Insert the parent (integration root) instead, matching the pattern from PR #280 (supadata) and PR #289 (google-sheets). * refactor(humanitix): drop importlib boilerplate and os.chdir from tests With tests/conftest.py now putting the integration root on sys.path and the workspace conftest.py patching Integration.load to find config.json from the caller's directory, tests can import the integration with a plain 'from humanitix import humanitix'. Removes: - The importlib.util.spec_from_file_location dance in both test files. - The process-global os.chdir(_parent) calls (the integration test file did not restore cwd, leaving it inside humanitix/ for any test file collected after it). - The legacy tests/context.py shim from the pre-SDK-2.0 testbed. Matches the simpler pattern used in PR #280 (supadata), PR #289 (google-sheets), and PR #307 (coda). * fix(humanitix): drop result/statusCode/error/message from output schemas Errors now flow through ActionError, so the boolean 'result' flag and the 'statusCode'/'error'/'message' error fields are dead weight in output schemas. Removes them from config.json and from the data dictionaries built by fetch_single_resource and build_paginated_result in helpers.py. Drops the corresponding 'assert data["result"] is True' lines from the unit tests. Brings humanitix in line with PR #280 (supadata), PR #289 (google-sheets), and PR #307 (coda). * refactor(humanitix): rename build_error_result to build_action_error The function returns an ActionError, not an ActionResult, so the '_result' suffix is misleading. Renames the helper and all four call sites in actions/. * test(humanitix): cover optional params on get_orders/get_tickets and list error path Adds tests for previously untested optional params: - get_orders: override_location, page_size, since - get_tickets: override_location, event_date_id, page_size, since - get_tickets: api_error path on list endpoint (not just single) Also drops the dead 'if not hasattr(response, "status")' defensive branch in build_action_error — every caller passes a FetchResponse, so the branch is unreachable. Coverage: 95% -> 100% (32 -> 40 tests). * fix(humanitix): correct get_events integration test assertions The Humanitix API returns the event list shape directly (events, total, page, pageSize) without a wrapping 'result' key. Drop the bogus 'result' assertion in test_returns_events_list and remove the unreachable 'if data.get("result") is True:' guard in test_has_pagination_fields so the pagination fields are actually verified. * fix(humanitix): correct get_tags integration test assertions The Humanitix API returns the tag list shape directly (tags, total, page, pageSize) without a wrapping 'result' key. Drop the bogus 'result' assertion in test_returns_tags_list and remove the unreachable 'if data.get("result") is True:' guards in test_tags_structure and test_pagination_params_respected so the structural and pagination checks are actually executed. * fix(humanitix): add comment explaining curl_cffi Cloudflare requirement in integration tests --------- Co-authored-by: Shubhank <72601061+Sagsgit@users.noreply.github.com> Co-authored-by: Kai Koenig <kai@ventego-creative.co.nz>
ba95c8d
Summary
Fixes production Lambda crash: `integration-google-sheets` returning "Unhandled" error.
Root cause: Error paths returning `ActionResult(data={"error": ..., "result": False})` fail output schema validation in SDK 2.0.0, causing unhandled exceptions and Lambda crashes.
Fix:
Test Results
Unit Tests (mocked, CI-safe)
Integration Tests (live Google Sheets/Drive API)
Validation
How to run
```bash
Unit tests
python -m pytest google-sheets/tests/ -m unit -v
Integration tests - read only
GOOGLE_SHEETS_ACCESS_TOKEN= python -m pytest google-sheets/tests/test_google_sheets_integration.py -m "integration and not destructive" -v
Integration tests - destructive
GOOGLE_SHEETS_ACCESS_TOKEN= python -m pytest google-sheets/tests/test_google_sheets_integration.py -m "integration and destructive" -v
```