# 機能設計書 120-Test Mode

## 概要

本ドキュメントは、Next.jsの実験的テストモード（Experimental Test Mode）の設計を記載する。Playwright等のE2Eテストフレームワークと統合し、サーバーサイドのfetchリクエストをテストプロキシ経由でインターセプト・モック可能にする仕組みを提供する。

### 本機能の処理概要

**業務上の目的・背景**：E2Eテストにおいて、Next.jsサーバーサイドのfetchリクエスト（Server Components、Server Actions、API Routes等）をモックすることは困難であった。本機能は、テスト実行時にサーバーサイドのfetchを透過的にプロキシサーバーに転送し、テストコードからレスポンスを制御可能にする。これにより、外部API依存のテストを安定化し、テストの決定性と速度を向上させる。

**機能の利用シーン**：E2Eテストで外部APIレスポンスをモックする場面、Server Componentsのデータフェッチをテスト制御する場面、MSW（Mock Service Worker）との統合テストを行う場面で利用される。

**主要な処理内容**：
1. テストリクエストの識別（`next-test-proxy-port`、`next-test-data`ヘッダー）
2. AsyncLocalStorageによるリクエストコンテキストの伝搬
3. グローバルfetchのインターセプト（テストプロキシへの転送）
4. Node.jsのhttp.getインターセプト
5. Playwrightとの統合（フィクスチャ、ページルーティング、レポート）
6. プロキシサーバーによるテストとNext.jsサーバー間の中継

**関連システム・外部連携**：Playwright、MSW（Mock Service Worker）、プロキシサーバーと連携する。

**権限による制御**：特に権限制御はない。テスト実行時にのみ有効化される。

## 関連画面

本機能はテスト基盤であり、直接関連する画面は存在しない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | E2Eテスト実行時のサーバーサイドfetchインターセプト |

## 機能種別

テスト基盤・リクエストインターセプト

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| next-test-proxy-port | string (header) | Yes | テストプロキシのポート番号 | 有効なポート番号 |
| next-test-data | string (header) | No | テスト識別データ（testId） | - |
| nextOptions.fetchLoopback | boolean | No | マッチしないfetchをループバックするか | - |

### 入力データソース

- HTTPリクエストヘッダー（テスト用メタデータ）
- Playwrightテストコード（フィクスチャ設定）
- プロキシサーバーからのレスポンス

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ProxyFetchResponse | object | プロキシからのモックレスポンス（status, headers, body） |
| ProxyContinueResponse | object | 元のfetchを続行する指示 |
| ProxyAbortResponse | object | fetchを中断する指示 |

### 出力先

- fetchの戻り値（Response object）
- Playwrightテストレポート

## 処理フロー

### 処理シーケンス

```
1. テストの開始
   └─ Playwrightがページリクエストにテスト用ヘッダーを注入
2. Next.jsサーバーがリクエストを受信
   └─ wrapRequestHandlerでリクエストをラップ
3. テストコンテキストの抽出
   └─ next-test-proxy-portとnext-test-dataヘッダーからTestReqInfoを抽出
4. AsyncLocalStorageへの保存
   └─ testStorage.run()でリクエストスコープのコンテキストを設定
5. サーバーサイドfetchの実行
   └─ インターセプトされたglobal.fetchが呼ばれる
6. プロキシへの転送
   └─ fetchリクエスト情報をJSON化してプロキシサーバーにPOST
7. プロキシの応答処理
   └─ continue: 元のfetchを実行 / fetch: モックレスポンスを返却 / abort: エラー
8. レスポンスの返却
   └─ テストで制御されたレスポンスがServer Component等に渡される
```

### フローチャート

```mermaid
flowchart TD
    A[Playwrightテスト開始] --> B[ページリクエスト + テストヘッダー]
    B --> C[Next.jsサーバー受信]
    C --> D[wrapRequestHandler]
    D --> E[TestReqInfo抽出]
    E --> F[AsyncLocalStorage.run]
    F --> G[Server Component処理]
    G --> H[global.fetch呼び出し]
    H --> I{テストコンテキストあり?}
    I -->|No| J[通常のfetch実行]
    I -->|Yes| K[プロキシにPOST]
    K --> L{プロキシ応答}
    L -->|continue| J
    L -->|fetch| M[モックResponse構築]
    L -->|abort| N[Error throw]
    J --> O[Response返却]
    M --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ヘッダーベース識別 | next-test-proxy-portヘッダーが存在する場合のみテストモードが有効化される | リクエスト受信時 |
| BR-02 | 内部リクエスト除外 | init.next.internalフラグが設定されたfetchはインターセプトを透過する | プロキシ通信自体のfetch |
| BR-03 | スタックトレース送信 | fetchリクエスト時にスタックトレース（最大5行）をnext-test-stackヘッダーで送信 | テストfetch時 |
| BR-04 | フレームワーク行除外 | スタックトレースから`/next/dist/`を含む行は除外される | スタック取得時 |
| BR-05 | プロキシ応答種別 | continue（続行）、fetch（モック）、abort（中断）、unhandled（未処理）の4種類 | プロキシ応答受信時 |
| BR-06 | fetchLoopback | fetchLoopbackオプションがtrueの場合、ハンドラーが一致しないfetchは通常実行される | Playwrightフィクスチャ |

### 計算ロジック

特になし。

## データベース操作仕様

本機能はデータベースを使用しない。

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | プロキシリクエスト失敗 | プロキシサーバーへのPOSTが非200レスポンス | `Proxy request failed: {status}`エラー |
| - | リクエスト中断 | プロキシがabortまたはunhandledを返す | `Proxy request aborted [{method} {url}]`エラー |

### リトライ仕様

リトライ機能は実装されていない。テスト失敗として報告される。

## トランザクション仕様

トランザクション管理は不要。

## パフォーマンス要件

- AsyncLocalStorageによる軽量なコンテキスト伝搬
- プロキシ通信はlocalhost上で行われるため低レイテンシ
- スタックトレースは最大5行に制限

## セキュリティ考慮事項

- テストモードはnext-test-proxy-portヘッダーの存在により暗黙的に有効化されるため、プロダクション環境ではこのヘッダーを受け付けないよう注意が必要
- プロキシサーバーはlocalhostでのみ動作する設計

## 備考

- Playwrightの`test`フィクスチャを拡張した`next`フィクスチャを提供
- `defineConfig`関数でPlaywrightの設定にデフォルト値を適用
- MSW（Mock Service Worker）との統合もサポートされている
- `next/experimental/testmode/playwright`からインポートして使用する
- Edge Runtimeのサーバーにも対応（server-edge.ts）

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | context.ts | `packages/next/src/experimental/testmode/context.ts` | TestReqInfo型、TestRequestReaderインターフェース、AsyncLocalStorage |
| 1-2 | proxy/types.ts | `packages/next/src/experimental/testmode/proxy/types.ts` | ProxyRequest、ProxyResponse型の定義 |

**読解のコツ**: TestReqInfoは`url`, `proxyPort`, `testData`の3フィールドのみ。AsyncLocalStorageでリクエストスコープのコンテキスト管理を行う。

#### Step 2: サーバーサイドのインターセプトを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | server.ts | `packages/next/src/experimental/testmode/server.ts` | interceptTestApis、wrapRequestHandlerWorker、wrapRequestHandlerNode |

**主要処理フロー**:
- **8-22行目**: IncomingMessage用のTestRequestReader実装
- **24-34行目**: interceptTestApis関数（fetchとhttp.getのインターセプト設定・復元）
- **36-40行目**: wrapRequestHandlerWorker（WorkerRequestHandlerのラッパー）
- **42-47行目**: wrapRequestHandlerNode（NodeRequestHandlerのラッパー）

#### Step 3: fetchインターセプトを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | fetch.ts | `packages/next/src/experimental/testmode/fetch.ts` | handleFetch、interceptFetch、buildProxyRequest、buildResponse |

**主要処理フロー**:
- **12-19行目**: Request用のTestRequestReader
- **21-37行目**: getTestStack関数（スタックトレース取得、フレームワーク行除外、最大5行）
- **39-75行目**: buildProxyRequest（fetch情報のJSON化）
- **77-83行目**: buildResponse（プロキシレスポンスからResponseを構築）
- **85-125行目**: handleFetch（メイン処理: テスト判定、プロキシ転送、応答分岐）
- **127-142行目**: interceptFetch（global.fetchの置換と復元）

#### Step 4: Playwright統合を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | playwright/index.ts | `packages/next/src/experimental/testmode/playwright/index.ts` | test拡張、defineConfig、エクスポート |
| 4-2 | playwright/next-fixture.ts | `packages/next/src/experimental/testmode/playwright/next-fixture.ts` | NextFixtureImpl（onFetch、setup、teardown） |

**主要処理フロー**:
- **playwright/index.ts 14-31行目**: defineConfig関数（webServer設定のマージ処理）
- **playwright/index.ts 36-58行目**: test拡張（nextフィクスチャ、nextOptionsフィクスチャ）
- **playwright/next-fixture.ts 12-61行目**: NextFixtureImpl（テストごとのfetchハンドラー管理）
- **playwright/next-fixture.ts 26-37行目**: setup（ページコンテキストのルーティングにテストヘッダーを注入）

#### Step 5: プロキシサーバーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | proxy/index.ts | `packages/next/src/experimental/testmode/proxy/index.ts` | createProxyServerのエクスポート |
| 5-2 | proxy/server.ts | `packages/next/src/experimental/testmode/proxy/server.ts` | プロキシサーバーの実装 |

### プログラム呼び出し階層図

```
[Playwright Test]
    |
    +-- test.extend({ next: NextFixture })
    |       +-- NextFixtureImpl.setup()
    |       |       +-- page.context().route('**', handleRoute)
    |       |               +-- テストヘッダー注入 (Next-Test-Proxy-Port, Next-Test-Data)
    |       |
    |       +-- NextFixtureImpl.onFetch(handler)
    |               +-- fetchHandlers.push(handler)
    |
    +-- [ブラウザ] -> [Next.js サーバー]
            |
            +-- wrapRequestHandler
            |       +-- withRequest(req, reader, fn)
            |               +-- extractTestInfoFromRequest
            |               +-- testStorage.run(testReqInfo, fn)
            |
            +-- Server Component / Server Action
                    |
                    +-- global.fetch (intercepted)
                            |
                            +-- handleFetch
                                    +-- getTestReqInfo
                                    +-- buildProxyRequest
                                    +-- fetch(`http://localhost:${proxyPort}`)
                                    +-- [Proxy Server]
                                    |       +-- NextFixtureImpl.handleFetch
                                    |               +-- fetchHandlers (逆順評価)
                                    |
                                    +-- buildResponse / originalFetch / throw Error
```

### データフロー図

```
[入力]                              [処理]                         [出力]

Playwright Test -----> page.route('**')
                       テストヘッダー注入
                              |
                       Next.js Server
                              |
                       wrapRequestHandler
                       AsyncLocalStorage.run
                              |
Server Component ----> fetch() [intercepted]
                              |
                       buildProxyRequest
                              |
                       POST localhost:{proxyPort}
                              |
                       [Proxy Server]
                              |
                       NextFixture.handleFetch
                       fetchHandlers 評価
                              |
                       ProxyResponse -----> Response構築 -----> Server Component
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| context.ts | `packages/next/src/experimental/testmode/context.ts` | ソース | テストコンテキスト管理（AsyncLocalStorage） |
| server.ts | `packages/next/src/experimental/testmode/server.ts` | ソース | サーバーサイドインターセプト設定 |
| server-edge.ts | `packages/next/src/experimental/testmode/server-edge.ts` | ソース | Edge Runtimeサーバーサイドインターセプト |
| fetch.ts | `packages/next/src/experimental/testmode/fetch.ts` | ソース | fetchインターセプト・プロキシ転送 |
| httpget.ts | `packages/next/src/experimental/testmode/httpget.ts` | ソース | http.getインターセプト |
| proxy/index.ts | `packages/next/src/experimental/testmode/proxy/index.ts` | ソース | プロキシエクスポート |
| proxy/server.ts | `packages/next/src/experimental/testmode/proxy/server.ts` | ソース | プロキシサーバー実装 |
| proxy/fetch-api.ts | `packages/next/src/experimental/testmode/proxy/fetch-api.ts` | ソース | プロキシfetch API型定義 |
| proxy/types.ts | `packages/next/src/experimental/testmode/proxy/types.ts` | ソース | プロキシ型定義 |
| playwright/index.ts | `packages/next/src/experimental/testmode/playwright/index.ts` | ソース | Playwright統合エントリーポイント |
| playwright/next-fixture.ts | `packages/next/src/experimental/testmode/playwright/next-fixture.ts` | ソース | NextFixture実装 |
| playwright/next-worker-fixture.ts | `packages/next/src/experimental/testmode/playwright/next-worker-fixture.ts` | ソース | ワーカーフィクスチャ |
| playwright/next-options.ts | `packages/next/src/experimental/testmode/playwright/next-options.ts` | ソース | フィクスチャオプション型定義 |
| playwright/page-route.ts | `packages/next/src/experimental/testmode/playwright/page-route.ts` | ソース | ページルーティング処理 |
| playwright/report.ts | `packages/next/src/experimental/testmode/playwright/report.ts` | ソース | テストレポート |
| playwright/step.ts | `packages/next/src/experimental/testmode/playwright/step.ts` | ソース | テストステップ処理 |
| playwright/msw.ts | `packages/next/src/experimental/testmode/playwright/msw.ts` | ソース | MSW統合 |
| playwright/default-config.ts | `packages/next/src/experimental/testmode/playwright/default-config.ts` | ソース | デフォルトPlaywright設定 |
