Skip to content

Phase 2 — Server-side data mocking for SvelteKit / Astro / Remix #47

Description

@pelazas

Phase 2 — Server-side data mocking for SvelteKit / Astro / Remix

Part of the multi-framework effort (PR #45, single PR, gated per phase). Builds on Phase 0's mock-writer registry (#42) and Phase 1's allowlist writers (#46). This is the big one: make SvelteKit/Astro/Remix SSR routes render with data instead of crashing on a missing backend — replicating, per framework, what NextMockWriter already does.

No main.py change needed — it already dispatches write_server_mocks/write_unmocked_api_fallbacks/write_unmocked_banner + changed_api_paths through the registry by framework name. Implementing the three writers wires Phase 2 automatically.

Writer contract (each framework implements all four, mirroring NextMockWriter)

  1. write_server_mocks(repo_dir, mocks, diff) — for each mocked /api/... not in changed_api_paths(diff), write an on-disk endpoint returning the mock body+status.
  2. write_unmocked_fallbacks(repo_dir, mocks, diff) — discover existing server API endpoints; blind-stub unmocked/non-auth ones to []+x-renderpr-unmocked header; wrap PR-changed ones so the real handler runs with an error guard (5xx/throw → fallback + x-renderpr-route-error). No detectable handler → blind-stub.
  3. write_banner(repo_dir) — write _BANNER_JS to the framework's static dir + inject <script src="/__renderpr-unmocked.js"> into its served HTML host.
  4. changed_api_paths(diff) — diff → that framework's server API paths.

All return list[str] of generated relative paths (incl. .renderpr.bak) so restore_runtime_files reverts them. Reuse _backup_file, _explicit_mock_paths, _AUTH_API_PREFIX, API_FALLBACK_HEADER, API_ERROR_HEADER, _BANNER_JS from mock_server.py.


Structure: prep-then-parallel (file-disjoint lanes)

New package src/agent/mock_writers/ with __init__.py (imports the 3 submodules so they self-register) + sveltekit.py / astro.py / remix.py.

Lane Owns
0 (prep, first) mock_server.py, test_mock_server.py, mock_writers/__init__.py, the 3 stub modules
1 (parallel) mock_writers/sveltekit.py + test_mock_writers_sveltekit.py
2 (parallel) mock_writers/astro.py + test_mock_writers_astro.py
3 (parallel) mock_writers/remix.py + test_mock_writers_remix.py

Lane 0: create the package with the 3 writer classes as behavior-preserving stubs (allowlist method delegates to write_vite_allowed_hosts/write_astro_allowed_hosts; other methods no-op). In mock_server.py: delete the 3 thin Phase-1 writer classes + their registrations, add from src.agent import mock_writers # noqa at the very bottom (registers them; safe "register at the bottom" pattern — no circular import, src/agent/__init__.py is empty). In test_mock_server.py: change _STUB_FRAMEWORKS to ("cra","spa","vite") (the three frameworks now have full writers, tested in their own files). Suite stays green; behavior identical. Smoke-check: get_mock_writer('sveltekit').framework=='sveltekit'.


Per-framework mechanisms

SvelteKit — endpoint src/routes/api/<path>/+server.ts exporting GETjson(body,{status}) (from @sveltejs/kit); internal event.fetch resolves same-origin to these. Discover existing src/routes/api/**/+server.{ts,js}. Banner host src/app.html (before </body>); static static/__renderpr-unmocked.js. changed = src/routes/api/**/+server.*/api/.... Wrap uses GET/POST/... method set + sibling _renderpr_orig_server.

Astro — endpoint src/pages/api/<path>.ts exporting GETnew Response(JSON.stringify(body),{status,headers}). Discover existing src/pages/api/**/*.{ts,js}. Banner via src/middleware.ts (onRequest appends the <script> to HTML responses) — chosen over layout-patching because Astro has no guaranteed root layout; create-when-absent, skip-if-present (never merge an existing middleware → no corruption); static public/__renderpr-unmocked.js. changed = src/pages/api/**/api/.... Wrap uses method set + sibling <name>._renderpr_orig.

Remix — resource route app/routes/api.<seg>.<seg>.ts (dots=slashes; [id]/:id$id) exporting loaderjson(body,{status}) (from @remix-run/node). Discover existing app/routes/api.*.{ts,js}. Banner host app/root.tsx (before <Scripts/</body>); static public/__renderpr-unmocked.js. changed = app/routes/api.*/api/... (exclude api.auth.*). Wrap uses loader/action exports + sibling api.<...>._renderpr_orig (leading-_ segment keeps it non-routable).


Test plan (per framework lane, own test file)

Assert: server-mock file path+content; skipped-for-changed; backup of existing; blind-stub content+header for unmocked; auth/explicit skipped; changed→wrapped (sibling orig, __renderprWrap, error header, >=500 + ASCII-strip guards); no-handler→blind-stub; banner injection + static file; changed_api_paths mapping incl. auth exclusion; restore_runtime_files round-trip; idempotency (second run stable, banner returns []). failing-test-first; full suite green after every commit.

Non-goals / risks

  • Direct data access not via /api/ (SvelteKit load/+page.server.ts, Astro frontmatter await db(), Remix non-api.* loaders, Next RSC/direct-DB) is not mockable on diskinfer_routes only emits mocks for literal /api/ fetch/axios calls. Same limitation Next has.
  • Runtime correctness is unverified here — unit tests assert generated artifacts (paths/content/restore/idempotency), not that a booted dev server renders with the mock. That's Phase 3 against real framework repos (esp. SvelteKit event.fetch, Astro middleware HTML rewrite, Remix ._renderpr_orig non-routability).
  • Wrap-changed-handler is best-effort, framework-specific export shapes; falls back to blind-stub when undetected (parity with Next).

Commit breakdown (per lane, small commits)

Lane 0: (1) package + stubs, (2) move writers out of mock_server + bottom import, (3) _STUB_FRAMEWORKS edit. Lanes 1-3: (1) path mapping + changed_api_paths, (2) server_mocks, (3) unmocked_fallbacks, (4) banner, (5) test suite.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions