Skip to content

feat(substack): upgrade to SDK 2.0.0 with unit + integration tests#384

Merged
TheRealAgentK merged 3 commits into
masterfrom
feat/substack-sdk-v2-migration
Jun 23, 2026
Merged

feat(substack): upgrade to SDK 2.0.0 with unit + integration tests#384
TheRealAgentK merged 3 commits into
masterfrom
feat/substack-sdk-v2-migration

Conversation

@Tram-Nguyen87

@Tram-Nguyen87 Tram-Nguyen87 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Migrates the Substack integration to autohive-integrations-sdk~=2.0.0.

Substack's public API requires no authentication, so this integration has no credentials, no auth config, and no error-signalling action returns — it relies on context.fetch raising HTTPError/RateLimitError on non-2xx (the SDK v2 behaviour).

Changes

Source (substack.py)

  • FetchResponse unwrap — access the body via .data at all 5 context.fetch call sites: get_publication_posts, get_post, search_publications, search_posts, get_post_comments. The two dict-guarded sites assign body = response.data first so isinstance(...) checks the body, not the FetchResponse.
  • No ActionError conversion — the integration has no ActionResult(data={"error": ...}) patterns; the 1.x source had no try/except either, so the no-error-handling stance is unchanged and consistent. Hardening is out of pure-migration scope.

config.json

  • Version 1.0.02.0.0.
  • Comment item fields made nullable (body, date, author_name, author_id, like_count, id). Comments are returned as raw API passthrough (unlike posts/publications, which _drop_none cleans). The live API returns body: null on deleted comments — without nullable types, SDK v2 output validation recurses into the array items and raises VALIDATION_ERROR on a successful 200. Confirmed against the live API, not speculative.

requirements.txt

  • Pin autohive-integrations-sdk~=2.0.0.

Tests

  • Removed the legacy unittest suite (tests/context.py, tests/test_substack.py).
  • Unit (test_substack_unit.py) — 38 tests: every action, FetchResponse mocks, _drop_none null-field handling, validation errors, and a regression test (test_null_comment_fields_pass_output_validation) reproducing the null-body case the schema fix addresses.
  • Integration (test_substack_integration.py) — 14 tests, live, no auth: every action against a real public publication (default https://www.astralcodexten.com, overridable via SUBSTACK_TEST_PUBLICATION_URL), plus a bad-slug test asserting HTTPError propagates so the real_fetch raise path is actually exercised. real_fetch returns FetchResponse and raises HTTPError/RateLimitError on non-ok per repo convention.

Verification (run on the final commit, efbbf02)

Check Result
validate_integration.py ✅ 0 errors, 0 warnings
check_code.py (lint, format, bandit, pip-audit, config-sync, fetch patterns) ✅ CODE CHECK PASSED
Unit tests ✅ 38 passed
Integration tests (live) ✅ 14 passed, 1 skipped

Self-review

Reviewed against the upgrading-sdk-v2, writing-unit-tests, and writing-integration-tests skills, plus an independent fresh-context review of the branch diff. The comment-nullability bug and the missing error-path test coverage were both found and fixed pre-emptively during that review rather than left for a reviewer to flag. The 1 skip is expected (get_post_comments skips when a post has no numeric id).

Migrate the Substack integration to autohive-integrations-sdk 2.0.0.

Source:
- Unwrap FetchResponse: access response body via .data at all 5 fetch
  call sites (get_publication_posts, get_post, search_publications,
  search_posts, get_post_comments).
- No ActionError conversion needed — the integration has no
  error-signalling returns and relies on context.fetch raising on
  non-2xx (consistent with the 1.x source).

config.json:
- Bump version 1.0.0 -> 2.0.0.
- Make raw-passthrough comment item fields nullable (body, date,
  author_name, author_id, like_count, id). Comments are returned
  unmodified (not via _drop_none like posts), and the live API returns
  null body on deleted comments — without this, SDK v2 output validation
  recurses into array items and raises VALIDATION_ERROR on a 200.

requirements.txt:
- Pin autohive-integrations-sdk~=2.0.0.

Tests:
- Replace the old unittest suite with pytest unit + integration suites.
- Unit (38): all actions, FetchResponse mocks, null-field handling,
  validation errors; includes a regression test for null comment fields.
- Integration (14, live, no auth): all actions against a real public
  publication, plus a bad-slug test asserting HTTPError propagates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🔍 Integration Validation Results

Commit: f89226f5e16b693b3b1db206c599f527edca5811 · docs(substack): add test env vars to .env.example
Updated: 2026-06-22T21:37:34Z

Changed directories: substack

Check Result
Structure ✅ Passed
Code ✅ Passed
Tests ✅ Passed
README ✅ Passed
Version ✅ Passed
✅ Structure Check output
Validating 1 integration(s)...

============================================================
Integration: substack
============================================================
✅ All checks passed!

============================================================
SUMMARY
============================================================
Integrations validated: 1
Total errors: 0
Total warnings: 0

✅ All validations passed!
✅ Code Check output
----------------------------------------
Checking: substack
----------------------------------------

📦 Installing dependencies...

🐍 Checking Python syntax...
   ✅ Syntax OK

📥 Checking imports...
   ✅ Imports OK

📄 Checking JSON files...
   ✅ JSON files OK

🔍 Linting with ruff...
   ✅ Lint OK

🎨 Checking formatting with ruff...
   ✅ Formatting OK

🔒 Scanning for security issues with bandit...
   ✅ Security OK

🛡️ Checking dependencies for vulnerabilities with pip-audit...
   ✅ Dependencies OK

🔗 Checking config-code sync...
   ✅ Config-code sync OK

🔄 Checking fetch patterns...
   ✅ Fetch patterns OK

========================================
✅ CODE CHECK PASSED
========================================
✅ Tests Check output

Integration   Tests  Coverage        Status
-------------------------------------------
substack     38/38      100%      ✅ Passed
-------------------------------------------
Total        38/38            ✅ All passed

✅ Tests passed: substack
✅ README Check output
========================================
✅ README CHECK PASSED
========================================
✅ Version Check output
✅ substack: 1.0.0 → 2.0.0 (major bump)

========================================
✅ VERSION CHECK PASSED
========================================

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efbbf029bf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread substack/tests/test_substack_unit.py
tests/conftest.py prepended the substack/ directory to sys.path, which
made substack.py importable as a top-level module and shadowed the
substack package. Under that path ordering, the package-style imports in
the test modules (from substack import substack / from substack.substack
import _normalise_url) fail at collection with "substack is not a
package" — flagged in review and reproducible when the repo root is not
resolved first.

Drop the path manipulation and rely on repo-root package resolution (the
root conftest.py puts the repo root on sys.path, and import-mode=importlib
does not re-insert test dirs). This matches the circle integration, which
uses the same __init__ re-export and package imports with no path hack.
The conftest is kept (validate_integration.py requires tests/conftest.py)
but now only documents the import strategy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Tram-Nguyen87

Tram-Nguyen87 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

confirmed and fixed in 4c8360f.

tests/conftest.py was prepending the substack/ directory to sys.path, which makes substack.py importable as a top-level module and shadows the substack package. Reproduced in a clean interpreter: with substack/ on sys.path[0], import substack resolves to substack/substack.py (a module, not a package), so the package-style imports (from substack import substack, from substack.substack import _normalise_url) fail at collection.

Fix: removed the sys.path manipulation and rely on repo-root package resolution (the root conftest.py puts the repo root on sys.path, and --import-mode=importlib does not re-insert test dirs). This matches the circle integration, which uses the same __init__ re-export + package imports with no path hack. The conftest is kept (the validator requires tests/conftest.py) but now only documents the import strategy.

Verified after the fix: unit 38 passed, integration 14 passed / 1 skipped (live), validate_integration.py 0 errors, check_code.py passed.

@Tram-Nguyen87 Tram-Nguyen87 requested review from TheRealAgentK and removed request for TheRealAgentK June 22, 2026 02:14

@TheRealAgentK TheRealAgentK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting one small should-fix before merge:

substack/tests/test_substack_integration.py reads SUBSTACK_TEST_PUBLICATION_URL and SUBSTACK_TEST_POST_SLUG, but this PR does not add blank template entries for them in the root .env.example. Even though both are optional, the integration-test checklist asks that every env var read by integration tests is documented there.

Suggested addition:

# -- Substack --
# Optional: publication URL used by Substack live integration tests.
# SUBSTACK_TEST_PUBLICATION_URL=
# Optional: post slug used by Substack get_post live integration tests.
# SUBSTACK_TEST_POST_SLUG=

Everything else in the SDK v2 migration, tests, and validation looked good.

Documents SUBSTACK_TEST_PUBLICATION_URL and SUBSTACK_TEST_POST_SLUG
in the root .env.example as requested in PR review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@TheRealAgentK TheRealAgentK left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now

@TheRealAgentK TheRealAgentK merged commit 16beea6 into master Jun 23, 2026
3 checks passed
@TheRealAgentK TheRealAgentK deleted the feat/substack-sdk-v2-migration branch June 23, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants