fosmvvm-serverrequest-test-generator

TotalClaw 作者 totalclaw

使用 VaporTesting 生成 ServerRequest 测试。涵盖显示、创建、更新和删除操作的键入请求/响应验证。

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install totalclaw:totalclaw~foscomputerservices-fosmvvm-serverrequest-test-generator
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~foscomputerservices-fosmvvm-serverrequest-test-generator/file -o foscomputerservices-fosmvvm-serverrequest-test-generator.md
## 概述(中文)

使用 VaporTesting 生成 ServerRequest 测试。涵盖显示、创建、更新和删除操作的键入请求/响应验证。

## 原文

# FOSMVVM ServerRequest Test Generator

Generate test files for ServerRequest types using VaporTesting infrastructure.

## Conceptual Foundation

> For full architecture context, see [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) | [OpenClaw reference]({baseDir}/references/FOSMVVMArchitecture.md)

ServerRequest testing uses **VaporTesting** infrastructure to send typed requests through the full server stack:

```
┌─────────────────────────────────────────────────────────────────────┐
│                    ServerRequest Test Flow                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Test Code:                                                          │
│    let request = MyRequest(query: .init(...))                        │
│    app.testing().test(request, locale: en) { response in }           │
│                                                                      │
│  Infrastructure handles:                                             │
│    • Path derivation from type name (MyRequest → /my)                │
│    • HTTP method from action (ShowRequest → GET)                     │
│    • Query/body encoding                                             │
│    • Header injection (locale, version)                              │
│    • Response decoding to ResponseBody type                          │
│                                                                      │
│  You verify:                                                         │
│    • response.status (HTTPStatus)                                    │
│    • response.body (R.ResponseBody? - typed!)                        │
│    • response.error (R.ResponseError? - typed!)                      │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

---

## STOP AND READ THIS

**Testing ServerRequests uses VaporTesting infrastructure. No manual URL construction. Ever.**

```
┌──────────────────────────────────────────────────────────────────────┐
│          SERVERREQUEST TESTING USES TestingApplicationTester          │
├──────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  1. Configure Vapor Application with routes                           │
│  2. Use app.testing().test(request, locale:) { response in }          │
│  3. Verify response.status, response.body, response.error             │
│                                                                       │
│  TestingServerRequestResponse<R> provides TYPED access to:            │
│    • status: HTTPStatus                                               │
│    • headers: HTTPHeaders                                             │
│    • body: R.ResponseBody?     ← Auto-decoded!                        │
│    • error: R.ResponseError?   ← Auto-decoded!                        │
│                                                                       │
└──────────────────────────────────────────────────────────────────────┘
```

### What You Must NEVER Do

```swift
// ❌ WRONG - manual URL construction
let url = URL(string: "http://localhost:8080/my_request?query=value")!
let response = try await URLSession.shared.data(from: url)

// ❌ WRONG - string path with method
try await app.test(.GET, "/my_request") { response in }

// ❌ WRONG - manual JSON encoding/decoding
let json = try JSONEncoder().encode(requestBody)
let decoded = try JSONDecoder().decode(ResponseBody.self, from: data)

// ❌ WRONG - constructing TestingHTTPRequest manually
let httpRequest = TestingHTTPRequest(method: .GET, url: "/path", headers: headers)
try await app.testing().performTest(request: httpRequest)
```

### What You Must ALWAYS Do

```swift
// ✅ RIGHT - Use TestingApplicationTester.test() with ServerRequest
let request = MyShowRequest(query: .init(userId: userId))
try await app.testing().test(request, locale: en) { response in
    #expect(response.status == .ok)
    #expect(response.body?.viewModel.name == "Expected Name")
}

// ✅ RIGHT - Test multiple locales
for locale in [en, es] {
    try await app.testing().test(request, locale: locale) { response in
        #expect(response.status == .ok)
        // Localized values are automatically handled
    }
}

// ✅ RIGHT - Test error responses
let badRequest = MyShowRequest(query: .init(userId: invalidId))
try await app.testing().test(badRequest, locale: en) { response in
    #expect(response.status == .notFound)
    #expect(response.error != nil)
}
```

**The path is derived from the ServerRequest type. HTTP method comes from the action. Headers are automatic. You NEVER write URL strings or decode JSON manually.**

---

## When to Use This Skill

- Testing any ServerRequest implementation
- Verifying server responses for CRUD operations
- Testing error handling and edge cases
- Multi-locale response verification
- Integration testing between client request types and server controllers

**If you're about to write `URLSession`, `app.test(.GET, "/path")`, or manual JSON decoding, STOP and use this skill instead.**

## What This Skill Generates

| File | Location | Purpose |
|------|----------|---------|
| `{Feature}RequestTests.swift` | `Tests/{Target}Tests/Requests/` | Test suite for ServerRequest |
| Test YAML (if needed) | `Tests/{Target}Tests/TestYAML/` | Localization for test ViewModels |

## Project Structure Configuration

| Placeholder | Description | Example |
|-------------|-------------|---------|
| `{Feature}` | Feature or entity name (PascalCase) | `Idea`, `User`, `Dashboard` |
| `{Target}` | Server test target | `WebServerTests`, `AppTests` |
| `{ViewModelsTarget}` | Shared ViewModels SPM target | `ViewModels` |
| `{WebServerTarget}` | Server-side target | `WebServer`, `AppServer` |
| `{ResourceDir}` | YAML resource directory | `TestYAML`, `Resources` |

---

## Key Types

### TestingServerRequestResponse<R>

Wraps HTTP response with typed access:

| Property | Type | Description |
|----------|------|-------------|
| `status` | `HTTPStatus` | HTTP status code (.ok, .notFound, etc.) |
| `headers` | `HTTPHeaders` | Response headers |
| `body` | `R.ResponseBody?` | **Typed** response body (auto-decoded) |
| `error` | `R.ResponseError?` | **Typed** error (auto-decoded) |

### TestingApplicationTester Extension

```swift
func test<R: ServerRequest>(
    _ request: R,
    locale: Locale = en,
    headers: HTTPHeaders = [:],
    afterResponse: (TestingServerRequestResponse<R>) async throws -> Void
) async throws -> any TestingApplicationTester
```

### Convenience Locales

Available on `TestingApplicationTester`:
- `en` - English
- `enUS` - English (US)
- `enGB` - English (UK)
- `es` - Spanish

---

## Test Structure

### Basic Test Suite

```swift
import FOSFoundation
@testable import FOSMVVM
import FOSTesting
import FOSTestingVapor
import Foundation
import Testing
import Vapor
import VaporTesting

@Suite("MyFeature Request Tests")
struct MyFeatureRequestTests {
    @Test func showRequest_success() async throws {
        try await withTestApp { app in
            let request = MyShowRequest(query: .init(id: validId))

            try await app.testing().test(request, locale: en) { response in
                #expect(response.status == .ok)
                #expect(response.body?.viewModel != nil)
            }
        }
    }

    @Test func showRequest_notFound() async throws {
        try await withTestApp { app in
            let request = MyShowRequest(query: .init(id: invalidId))

            try await app.testing().test(request, locale: en) { response in
                #expect(response.status == .notFound)
            }
        }
    }
}

private func withTestApp(_ test: (Application) async throws -> Void) async throw