Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .cursor/plans/sdk_test_folder_reorg_d8edc8be.plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: SDK test folder reorg
overview: Consolidate every SDK package's test files into a top-level __tests__/ folder that mirrors the src/ subfolder structure, matching the established pattern in server/mcp/mcp-core. A one-off codemod performs the git mv plus relative-import rewrites; vitest configs and server's explicit test-file lists are updated to match.
todos:
- id: codemod
content: "Write scripts/reorg-tests.mjs: enumerate src test files per package, compute mirrored __tests__ destinations (strip __tests__ segments), collision-check, rewrite relative import/mock specifiers via path.relative, git mv files"
status: pending
- id: fixtures
content: Move create-solvapay src/types/mcp/__fixtures__ to __tests__/types/mcp/__fixtures__ (git mv) so import.meta.url resolution keeps working
status: pending
- id: run-codemod
content: Run node scripts/reorg-tests.mjs and review git status for unexpected changes
status: pending
- id: vitest-configs
content: Update vitest.config.ts include globs for cli, create-solvapay, init, next, mcp-core; trim dead src globs in core, server, mcp, react
status: pending
- id: server-scripts
content: Repoint moved test paths in server/package.json test, test:unit, test:integration scripts
status: pending
- id: verify
content: Run per-package tests, full pnpm test, pnpm build:packages, pnpm format:check; then delete the throwaway codemod script
status: pending
isProject: false
---

# SDK test folder reorganization

## Target convention (confirmed)

- Every package keeps tests in a single top-level `__tests__/` folder.
- Inside `__tests__/`, files mirror the `src/` subfolder path, with any existing `__tests__` segment stripped. Examples:
- `src/utils/format.test.ts` -> `__tests__/utils/format.test.ts`
- `src/utils/__tests__/headers.test.ts` -> `__tests__/utils/headers.test.ts`
- `src/mcp/views/__tests__/AppHeader.test.tsx` -> `__tests__/mcp/views/AppHeader.test.tsx`
- `src/__tests__/useTopup.test.ts` -> `__tests__/useTopup.test.ts`
- `.spec.ts` naming is preserved (files relocate, no rename).
- Source imports use the established style: `../src/...` (root) / `../../src/...` (one level deep), etc.

## What already conforms (no file moves)

- `packages/mcp` — all 8 tests already in `__tests__/` (incl. mirrored `fetch/`, `express/`).
- `packages/server`, `packages/core`, `packages/mcp-core` — already have top-level `__tests__/`; only the stragglers below move in.

## Files to move (by package)

- `auth`: `src/adapter.contract.test.ts`, `src/auth0.test.ts`, `src/constants.test.ts` -> `__tests__/`.
- `cli`: `src/cli.test.ts` -> `__tests__/cli.test.ts`; `src/commands/init.test.ts` -> `__tests__/commands/init.test.ts`.
- `core`: `src/business-details.test.ts`, `src/index.test.ts` -> `__tests__/`.
- `create-solvapay`: `src/args.test.ts`, `src/cli.test.ts` -> `__tests__/`; `src/types/mcp/*.test.ts` (6) -> `__tests__/types/mcp/`; **also move** `src/types/mcp/__fixtures__/` -> `__tests__/types/mcp/__fixtures__/` (resolved at runtime via `import.meta.url`; mirroring keeps `../../../scripts/mcp` valid).
- `init`: all 8 `src/*.test.ts` -> `__tests__/` (flat, mirrors src root).
- `mcp-core`: `src/narrate.spec.ts` -> `__tests__/narrate.spec.ts`.
- `next`: `src/helpers/__tests__/{middleware,response-shape,usage}.test.ts` -> `__tests__/helpers/`.
- `react`: all ~100 tests under `src/**` (colocated `*.test.tsx?` + nested `src/**/__tests__/**` + `src/__tests__/**`) -> `__tests__/<mirrored path>`. The existing `__tests__/types-surface.test-d.ts` and `__tests__/tsconfig.types.json` stay.
- `react-supabase`: `src/supabase-adapter.test.ts` -> `__tests__/supabase-adapter.test.ts`.
- `server`: `src/__tests__/edge-exports.test.ts` -> `__tests__/edge-exports.test.ts`; `src/helpers/{auto-recharge,balance-poll,error,payment,purchase,usage}.test.ts` -> `__tests__/helpers/`.

## Codemod (one-off, throwaway `scripts/reorg-tests.mjs`)

For each package, for each test file under `src/` (extensions `.test.ts`, `.test.tsx`, `.spec.ts`, `.spec.tsx`):

1. Compute destination: path relative to `src/`, strip any `__tests__/` segment, prefix `__tests__/`.
2. Abort if two sources collide on one destination (safety check).
3. Rewrite every **relative** specifier in `import ... from`, `export ... from`, `import(...)`, `require(...)`, `vi.mock(...)`, `vi.doMock(...)`, `vi.importActual/importMock(...)`:
- `newSpec = relative(newDir, resolve(oldDir, spec))`, normalized to POSIX with a leading `./`.
- Targets that are themselves moved test files keep their relative path unchanged (both shift identically); targets in `src` re-base to `../src/...`. Bare/`@solvapay/*` specifiers are untouched.
- Preserve original quote style (single quotes) and omit file extensions as in the originals.
4. `git mv` the file (preserve history); `git mv` the `create-solvapay/.../__fixtures__` dir.
Run with `node scripts/reorg-tests.mjs`, verify `git status`, then delete the script (not committed).

## Config + script updates

- `cli/vitest.config.ts`: include `['__tests__/**/*.test.{ts,tsx}']`.
- `create-solvapay/vitest.config.ts`: include `['__tests__/**/*.test.ts', '__tests__/**/*.spec.ts']`.
- `init/vitest.config.ts`: include `['__tests__/**/*.test.ts', '__tests__/**/*.spec.ts']`.
- `next/vitest.config.ts`: include `['__tests__/**/*.test.{ts,tsx}']`.
- `mcp-core/vitest.config.ts`: add `'__tests__/**/*.spec.ts'` (needed for moved `narrate.spec.ts`).
- `core`, `server`, `mcp`, `react`: trim now-dead `src/**` test globs so include only targets `__tests__/**` (react keeps its `coverage` block).
- `auth`, `react-supabase`: no config today; vitest default discovery still finds `__tests__/**`, so leave as-is.
- `server/package.json`: repoint the explicit paths in `test`, `test:unit`, `test:integration` — `src/__tests__/edge-exports.test.ts` -> `__tests__/edge-exports.test.ts`, `src/helpers/payment.test.ts` -> `__tests__/helpers/payment.test.ts`, `src/helpers/usage.test.ts` -> `__tests__/helpers/usage.test.ts` (preserve the curated set; do not add/remove other files).

## tsconfig / lint (verify, expected no change)

- Most package tsconfigs use `include: ["src"]`, so moved tests naturally leave the tsc/build scope; `server` already excludes `__tests__`. Build is via tsup entrypoints, so dist is unaffected.
- `react` `test:types` uses `__tests__/tsconfig.types.json`, which includes only `types-surface.test-d.ts` + `../src` — unaffected by moved runtime tests.
- `eslint src` scripts stop linting tests (they no longer sit in `src`); matches the current `server` behavior — acceptable, no new lint breakage.

## Verification

1. `git status` shows only expected renames + the config/script edits.
2. `pnpm -F @solvapay/<pkg> test` for each touched package (react, server, next, init, cli, create-solvapay, mcp-core, core, auth, react-supabase).
3. `pnpm test` (full monorepo) and `pnpm build:packages` green.
4. `pnpm format:check` clean on rewritten files.
5. Delete `scripts/reorg-tests.mjs`.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dist
.env.*
!.env.example
!.env.example.*
!.env.platform-local.example
.npmrc
.pnpm-store
.turbo
Expand Down
69 changes: 69 additions & 0 deletions examples/.env.platform-local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# =============================================================================
# Platform-local env template (shared across SDK examples)
# =============================================================================
#
# Use this when pointing any example at a local SolvaPay platform stack
# (sibling repo: ../platform). Copy the vars you need into the target
# example's own `.env` (or wrangler / Supabase secret store for edge
# examples) — each package loads dotenv from its own directory; nothing
# auto-reads this file.
#
# Critical: SDK routes (`/v1/sdk/*`) are split across provider / payment /
# billing / commerce services. The only local process that fans `/v1/*`
# out correctly is the provider-app Next proxy on :3010. Point every
# real-backend example at:
#
# SOLVAPAY_API_BASE_URL=http://localhost:3010
#
# Do NOT use :3001 (identity-service only), a production URL, or a deployed
# `api-dev` host unless that is intentionally what you want.
#
# Port remap: the platform occupies 3001–3012, 3020/3021, plus infra.
# Example defaults that collide (mcp-oauth-bridge 3004, mcp-time-app 3005,
# mcp-checkout-app 3006, express-provider-linkage 3002, checkout-demo
# tunnel 3010) should be remapped into a free band — proposed: 3030+.
#
# Merchant-logo assets are served under the same API origin, which the SDK
# auto-adds to the CSP `resource_domains`, so with base URL :3010 no extra
# MCP_ASSET_ORIGINS is needed for the happy path.
#
# Get SOLVAPAY_SECRET_KEY + SOLVAPAY_PRODUCT_REF from the provider console
# at http://localhost:3010 (create a product / secret key there).
#
# Platform start (from ../platform):
# npm run dev # stack + ngrok when ngrok.yml is configured
# npm run local # same + Stripe webhook forwarder
# ./scripts/dev.sh start --no-ngrok # stack without tunnels
#
# =============================================================================

# --- Shared (all real-backend examples) ----------------------------------------
SOLVAPAY_API_BASE_URL=http://localhost:3010
SOLVAPAY_SECRET_KEY=
SOLVAPAY_PRODUCT_REF=

# --- MCP server examples -------------------------------------------------------
# Used by: mcp-checkout-app, mcp-time-app, mcp-oauth-bridge
# Override each example's colliding default port (3004/3005/3006 → 3030+).
# Run one MCP example at a time on 3030, or stagger (3030, 3031, …).
MCP_PORT=3030
MCP_HOST=localhost
# Set by cloudflared (`pnpm tunnel` in the example) or platform ngrok `mcpapp`
# tunnel. Leave unset for same-machine MCP Inspector / basic-host.
# MCP_PUBLIC_BASE_URL=https://mcpapp.your-subdomain.ngrok.app
DEMO_TOOLS=true
# mcp-oauth-bridge only — webhook signing secret from the provider console
# SOLVAPAY_WEBHOOK_SECRET=

# --- Next.js checkout demos ----------------------------------------------------
# Used by: nextjs-auth0, tailwind-checkout, shadcn-checkout (and similar).
# Same value as SOLVAPAY_PRODUCT_REF; name differs per example.
# NEXT_PUBLIC_PRODUCT_REF=
# NEXT_PUBLIC_SOLVAPAY_PRODUCT_REF=

# --- Edge examples -------------------------------------------------------------
# cloudflare-workers-mcp: map the Shared vars into wrangler `.env` / `--var`
# supabase-edge-mcp: map the Shared vars into `supabase secrets set …`
# Stub-backed demos (express-basic, tailwind-checkout, shadcn-checkout without
# a secret key) run without any backend by default — skip this file until you
# want a real SolvaPay stack.
14 changes: 12 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

This directory contains example applications demonstrating how to use the SolvaPay SDK in different environments.

## Local platform stack

When pointing an example at a sibling [`platform`](../../platform) monorepo stack,
start from [`.env.platform-local.example`](./.env.platform-local.example). Copy
the vars you need into the target example's own `.env` — each package loads
dotenv from its directory. Use `SOLVAPAY_API_BASE_URL=http://localhost:3010`
(provider-app proxy) and remap MCP/example ports to `3030+` so they don't
collide with platform services. Convenience scripts from the repo root:
`pnpm mcp:checkout` / `pnpm mcp:checkout:tunnel`.

## Shared Utilities

The `shared/` folder contains reusable utilities used across multiple examples:
Expand Down Expand Up @@ -131,7 +141,7 @@ A non-hosted MCP server example demonstrating:
```bash
cd examples/mcp-oauth-bridge
pnpm install
cp .env.example .env
cp ../.env.platform-local.example .env # or cp .env.example .env
pnpm dev
```

Expand All @@ -149,7 +159,7 @@ A Model Context Protocol (MCP) app example demonstrating:
```bash
cd examples/mcp-time-app
pnpm install
cp .env.example .env
cp ../.env.platform-local.example .env # or cp .env.example .env
pnpm dev
```

Expand Down
3 changes: 2 additions & 1 deletion examples/express-provider-linkage/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PORT=3002
# 3031 avoids the local platform band (3001–3012) and the MCP examples on 3030.
PORT=3031
# Demo mode uses the shared stub client (no real API key required).
# For production, set SOLVAPAY_SECRET_KEY and SOLVAPAY_API_BASE_URL instead.
SOLVAPAY_SECRET_KEY=
Expand Down
2 changes: 1 addition & 1 deletion examples/express-provider-linkage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pnpm dev

```bash
# Provider user already authenticated (e.g. after `your-cli login`)
curl -X POST http://localhost:3002/tasks \
curl -X POST http://localhost:3031/tasks \
-H "Content-Type: application/json" \
-H "x-provider-user-id: auth0|demo-user" \
-H "x-provider-user-email: demo@example.com" \
Expand Down
4 changes: 2 additions & 2 deletions examples/express-provider-linkage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createStubClient } from '../../shared/stub-api-client'
import { createTask, getTask, listTasks, deleteTask, getTaskCount } from '@solvapay/demo-services'

const app: Express = express()
const port = parseInt(process.env.PORT || '3002', 10)
const port = parseInt(process.env.PORT || '3031', 10)

const apiClient = createStubClient({
freeTierLimit: 5,
Expand Down Expand Up @@ -64,7 +64,7 @@ app.get('/', (_req, res) => {
optionalProfile: ['x-provider-user-email', 'x-provider-user-name'],
},
example: [
'curl -H "x-provider-user-id: auth0|demo-user" http://localhost:3002/tasks',
`curl -H "x-provider-user-id: auth0|demo-user" http://localhost:${port}/tasks`,
],
})
})
Expand Down
21 changes: 13 additions & 8 deletions examples/mcp-checkout-app/.env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
MCP_PORT=3006
# Prefer the shared platform-local template for a local platform stack:
# ../.env.platform-local.example
# Copy the vars you need into this file. Against the platform monorepo,
# SOLVAPAY_API_BASE_URL must be http://localhost:3010 (provider-app proxy)
# and MCP_PORT should be remapped out of the platform band (e.g. 3030).

MCP_PORT=3030
MCP_HOST=localhost
# Public URL this MCP server is reachable at. Defaults to http://localhost:$MCP_PORT,
# which works for MCP Inspector running on the same machine. Remote MCP clients
# (Claude Desktop, ChatGPT, MCPJam) need a publicly reachable URL — run
# `pnpm tunnel` to get one automatically via cloudflared, or set this manually.
# `pnpm tunnel` to get one automatically via cloudflared, or set this manually
# (or use the platform ngrok `mcpapp` tunnel on :3030).
# MCP_PUBLIC_BASE_URL=https://xxxx.trycloudflare.com
SOLVAPAY_API_BASE_URL=http://localhost:3001
SOLVAPAY_API_BASE_URL=http://localhost:3010
SOLVAPAY_PRODUCT_REF=
SOLVAPAY_SECRET_KEY=

Expand All @@ -20,10 +27,8 @@ DEMO_TOOLS=true
# from, merged into the CSP's `resource_domains`. Comma-separated list of bare
# origins (scheme + host + port — no paths).
#
# In local dev the backend admin serves provider-uploaded merchant logos from
# `http://localhost:<port>/ui/files/download/...`, so you'd add those origins
# here to see the logo in the iframe. The API origin (SOLVAPAY_API_BASE_URL)
# is always included, no need to repeat it.
# The API origin (SOLVAPAY_API_BASE_URL) is always included — with
# http://localhost:3010, merchant logos need no extra MCP_ASSET_ORIGINS.
#
# Leave unset in production — keep the CSP tight.
# MCP_ASSET_ORIGINS=http://localhost:6274,http://localhost:3001
# MCP_ASSET_ORIGINS=http://localhost:6274
26 changes: 17 additions & 9 deletions examples/mcp-checkout-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,25 @@ runtime probe keeps us safe on hosts that don't.

## Prerequisites

1. SolvaPay backend running (defaults to `http://localhost:3001`; port 3000 is the Next.js frontend)
2. A product with at least one active plan
1. SolvaPay platform stack running locally (`npm run dev` / `npm run local` in
the sibling `platform` repo). SDK calls go through the provider-app proxy at
`http://localhost:3010` — see [`../.env.platform-local.example`](../.env.platform-local.example).
2. A product with at least one active plan (create in the console at `:3010`)
3. `SOLVAPAY_SECRET_KEY` scoped to that product
4. An MCP host such as [`basic-host`](https://github.com/modelcontextprotocol/basic-host)
running at `http://localhost:8080`

## Configure

```bash
cp .env.example .env
# Fill in SOLVAPAY_SECRET_KEY and SOLVAPAY_PRODUCT_REF. The Stripe
# publishable key used for embedded Elements is fetched from the
# SolvaPay backend at boot (GET /sdk/platform-config) — no local
# config needed.
# Against a local platform stack, start from the shared template:
cp ../.env.platform-local.example .env
# Or: cp .env.example .env
# Fill in SOLVAPAY_SECRET_KEY and SOLVAPAY_PRODUCT_REF. Keep
# SOLVAPAY_API_BASE_URL=http://localhost:3010 and MCP_PORT=3030 (platform
# owns 3001–3012). The Stripe publishable key used for embedded Elements is
# fetched from the SolvaPay backend at boot (GET /sdk/platform-config) — no
# local config needed.
```

## Run
Expand All @@ -71,9 +76,10 @@ Watch mode (rebuilds the UI bundle and restarts the server on changes):

```bash
pnpm --filter @example/mcp-checkout-app dev
# or from the SDK repo root: pnpm mcp:checkout
```

Point `basic-host` at `http://localhost:3006/mcp` and open the app from
Point `basic-host` at `http://localhost:3030/mcp` and open the app from
its tool list. On `basic-host` and ChatGPT the iframe renders inline
Stripe Elements; enter the test card `4242 4242 4242 4242` and pay
without leaving the host. On Claude the probe detects that
Expand All @@ -82,6 +88,8 @@ button that opens hosted checkout in a new tab — returning to the host
fires `refreshBootstrap()` (which calls `manage_account` under the
hood) and flips the card to **Manage purchase**.

For a public URL, run `pnpm tunnel` / `pnpm mcp:checkout:tunnel` (cloudflared)
or enable the platform `mcpapp` ngrok tunnel on `:3030`.
## Flow

```mermaid
Expand Down Expand Up @@ -332,7 +340,7 @@ widget to mount on a data-tool call.
balance-zeroing; the two paid plans exercise the brief's PAYG and
Recurring activation branches.
2. Start the example (`pnpm --filter @example/mcp-checkout-app dev`) and
point `basic-host` at `http://localhost:3006/mcp`.
point `basic-host` at `http://localhost:3030/mcp`.
3. Customer is on Free by default. Call `/search_knowledge query: "hi"`
N times; each call drains the free quota.
4. When the Free quota exhausts, the next call returns a **paywall
Expand Down
2 changes: 1 addition & 1 deletion examples/mcp-checkout-app/SMOKE_TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ out of the plan-selection surface — it shows paid options only.

## Prerequisites

- Backend running at `http://localhost:3001` with your product and
- Platform stack running with API fan-out at `http://localhost:3010` with your product and
plans created.
- `.env` set: `SOLVAPAY_SECRET_KEY`, `SOLVAPAY_PRODUCT_REF`,
`DEMO_TOOLS=true`.
Expand Down
12 changes: 9 additions & 3 deletions examples/mcp-oauth-bridge/.env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
MCP_PORT=3004
# Prefer the shared platform-local template for a local platform stack:
# ../.env.platform-local.example
# Against the platform monorepo, SOLVAPAY_API_BASE_URL must be
# http://localhost:3010 and MCP_PORT should avoid the platform band (e.g. 3030).

MCP_PORT=3030
MCP_HOST=0.0.0.0

# Public URL shown to clients in OAuth metadata. Defaults to http://localhost:$MCP_PORT.
# Run `pnpm tunnel` to expose the server publicly and set this automatically.
# MCP_PUBLIC_BASE_URL=https://xxxx.trycloudflare.com

# SolvaPay backend API base URL (used for OAuth endpoints and SDK API calls)
SOLVAPAY_API_BASE_URL=http://localhost:3000
# SolvaPay backend API base URL (used for OAuth endpoints and SDK API calls).
# Local platform stack: provider-app Next proxy on :3010 fans /v1/sdk/* out.
SOLVAPAY_API_BASE_URL=http://localhost:3010

# Product reference used by payable.mcp and OAuth DCR endpoint construction
SOLVAPAY_PRODUCT_REF=basic-crud
Expand Down
Loading
Loading