# Epic 3 Technical Spec: NetSuite Mock Server

## Objective

Build a standalone **NestJS** application that accurately simulates NetSuite's RESTlet ingestion, cursor-based search pagination, and aggressive API rate limiting. This allows SuiteX to develop and load-test its polling and writing workers without exhausting live NetSuite governance limits.

## Architecture & Tech Stack

NestJS is the designated framework because its modular architecture natively supports the interception and validation patterns required to simulate NetSuite's quirks.

| Area | Choice |
|------|--------|
| **Framework** | NestJS (TypeScript). |
| **Validation** | class-validator combined with Epic 1's Canonical JSON Schemas (via custom validation pipes or Ajv integration). |
| **Hosting** | Google Cloud Run. Deployed as a stateless Docker container. Connects to the database via the Cloud SQL Auth Proxy/VPC connector. |
| **Database** | Cloud SQL (MySQL 8.*) via TypeORM or Prisma. MySQL 8.* is required because Cloud Run containers are ephemeral; local SQLite would be destroyed when the container scales to zero, breaking the SuiteX polling simulation. |
| **Rate Limiting** | NestJS ThrottlerModule and custom Interceptors for Chaos Engineering. |

## Database Schema (State Persistence)

To support the SuiteX Poller, the mock server must persist state. Rather than building complex relational tables for every future record type, the database should use a flexible **Document-Relational** pattern.

### Entity Model (MockRecord)

```typescript
@Entity()
export class MockRecord {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  recordType: string; // e.g., 'project', 'project_task'

  @Column()
  internalId: string; // Simulated NetSuite ID (e.g., 'NS-9932')

  @Column({ type: 'datetime' })
  lastModifiedDate: Date; // Required for cursor polling

  @Column({ type: 'json' })
  payload: any; // The validated Canonical JSON data

  @Column()
  writeId: string; // The idempotency tracking UUID from SuiteX
}
```

**Behavioral requirement:** Upon saving a new record, the Mock Server must auto-generate an `internalId` and set `lastModifiedDate` to the current UTC time, mimicking NetSuite's internal database atomic commits.

## The RESTlet Endpoints (Ingress & Egress)

The server will expose two primary endpoint archetypes to simulate NetSuite's API.

### A. The Write Endpoint (Simulating the Inbound RESTlet)

- **Route:** `POST /mock-netsuite/restlet/:recordType`
- **Validation layer:** A NestJS ValidationPipe must intercept the payload. It must cross-reference the `:recordType` param against the Epic 1 JSON Schemas. If the payload fails (e.g., missing `writeId` or invalid date format), the endpoint must return a **400 Bad Request**.
- **Idempotency check:** The endpoint must query the Cloud SQL database for the incoming `writeId`. If it exists, return a **200 OK** with the existing `internalId` (simulating NetSuite RESTlet idempotency patterns).
- **Success response:** Returns the newly minted `internalId`.

### B. The Polling Discovery Endpoint (Simulating NetSuite Search)

- **Route:** `POST /mock-netsuite/search/:recordType`
- **Payload:** Expects a cursor: `{ "lastModifiedDate": "ISO8601", "lastInternalId": "string" }`.
- **Logic:** Queries the Cloud SQL database for records matching the cursor.
- **Constraint simulation:** The endpoint **MUST** sort results by `lastModifiedDate` ASC, `internalId` ASC and strictly limit the response to a maximum of **1,000 results per page**, mimicking NetSuite's `Search.runPaged()` governance limits.

## Chaos Engineering Middleware (Governance Simulation)

This is the most critical feature of the mock server. NetSuite heavily throttles high-concurrency connections. SuiteX's adaptive throttling cannot be tested without the mock server fighting back.

### The Governance Interceptor (ChaosInterceptor)

Implement a global NestJS Interceptor with configurable environment variables to simulate network instability and governance exhaustion.

- **Artificial latency:** Inject a random delay between **200ms** and **2500ms** on every request to simulate NetSuite's sluggish RESTlet execution times.
- **Concurrency throttling (429 generator):**
  - Track active, in-flight requests in memory.
  - If `ACTIVE_REQUESTS > SIMULATED_TIER_LIMIT` (e.g., limit set to 5 to simulate a Premium tier), immediately short-circuit the request and return **HTTP 429 Too Many Requests**.
- **Transient failure simulation (optional/configurable):** Have a 5% chance to drop the connection or return a **503 Service Unavailable** to ensure SuiteX's exponential backoff and retry queues function correctly.

## Acceptance Criteria

| ID | Criterion |
|----|-----------|
| **AC1** (Ingestion & Normalization) | Given SuiteX sends a valid Canonical Project payload, the NestJS endpoint validates it, saves it to the Cloud SQL database, and returns a simulated NetSuite `internalId`. |
| **AC2** (Governance Exhaustion) | Given SuiteX attempts to execute 15 concurrent POST requests, the Mock Server accepts the defined limit (e.g., 5) and explicitly rejects the remaining 10 with a **429 Too Many Requests** status, validating the Chaos Interceptor. |
| **AC3** (Cursor Pagination) | Given SuiteX queries the search endpoint with a `lastModifiedDate` of yesterday, the Mock Server returns up to 1,000 mocked Project records sorted chronologically, allowing SuiteX to test its detail-fetch pipeline. |
