Skip to content
Open
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
52 changes: 52 additions & 0 deletions PR_DESCRIPTION_64.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# PR Description: Fix Pagination NaN Bug in IntentsController.list()

## Issue Reference

- Closes #64

## PR Links

- PR #158: https://github.com/stellar-vortex-protocol/vortex-backend/pull/158
- Branch: `fix/pagination-nan-validation`

## Summary

`IntentsController.list()` was using `parseInt()` on raw query parameters (`limitRaw`, `offsetRaw`) without any validation. When a non-numeric value was provided (e.g., `?limit=abc`), `parseInt()` returned `NaN`, which silently corrupted the pagination slice (`intents.slice(offset, offset + limit)`) instead of returning a clear 400 error.

## Changes

### New File: `src/intents/dto/list-intents.dto.ts`

Introduced a typed DTO for list query parameters using `class-validator` decorators consistent with the rest of the codebase:

- `limit`: `@IsInt()`, `@Min(1)`, `@Max(100)` — ensures the value is a valid integer between 1 and 100
- `offset`: `@IsInt()`, `@Min(0)` — ensures the value is a valid non-negative integer
- `state`, `user`, `chain`: `@IsOptional()`, `@IsString()` — optional string filters

### Modified File: `src/intents/intents.controller.ts`

- Changed `list()` method signature from individual `@Query()` parameters to a single `@Query() dto: ListIntentsDto`
- Replaced `Math.min(parseInt(limitRaw, 10), 100)` and `parseInt(offsetRaw, 10)` with `dto.limit` and `dto.offset`
- The global `ValidationPipe` (configured in `main.ts` with `whitelist: true, transform: true`) automatically validates the DTO and returns a 400 response with detailed error information when validation fails

### Modified File: `test/intents.e2e-spec.ts`

Added two new e2e tests:

1. **`GET /api/v1/intents with non-numeric limit returns 400`** — Asserts that `?limit=abc` returns HTTP 400 with `error: "Validation failed"` and an array of `details`
2. **`GET /api/v1/intents with non-numeric offset returns 400`** — Asserts that `?offset=xyz` returns HTTP 400 with `error: "Validation failed"` and an array of `details`

## Verification

- `npm run lint` passes
- `npm run typecheck` passes
- `npm test` passes
- `npm run test:e2e` passes (new e2e tests for non-numeric limit/offset)

## Acceptance Criteria

- [x] Limit/offset moved into a proper DTO with `@IsInt()`/`@Min()`/`@Max()` validators
- [x] Non-numeric `limit` query param returns 400, not a silently broken page
- [x] Non-numeric `offset` query param returns 400, not a silently broken page
- [x] E2E tests added and passing
- [x] PR description includes "Closes #64"
46 changes: 46 additions & 0 deletions PR_DESCRIPTION_65.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# PR Description: Guard BigInt(dto.fillAmount) Against Malformed Input

## Issue Reference

- Closes #65

## PR Links

- PR #159: https://github.com/stellar-vortex-protocol/vortex-backend/pull/159
- Branch: `fix/fill-amount-validation`

## Summary

`IntentsController.fill()` calls `BigInt(dto.fillAmount)` directly. While `FillIntentDto` already has `@Matches(/^\d+$/)` on `fillAmount` (matching the pattern used on `CreateIntentDto.srcAmount`), there was no e2e test asserting that a non-numeric `fillAmount` returns a clean 400 validation error instead of an ungraceful 500 `SyntaxError` from `BigInt()`.

## Changes

### Modified File: `test/intents.e2e-spec.ts`

Added a new e2e test:

**`POST /api/v1/intents/:id/fill with non-numeric fillAmount returns 400`**

- Creates an intent and accepts it with a valid solver
- Submits a fill request with `fillAmount: "abc"` (non-numeric)
- Asserts HTTP 400 response with `error: "Validation failed"` and an array of `details`
- Confirms the response is a clean validation error, not an ungraceful 500 from `BigInt("abc")` throwing a `SyntaxError`

## Background

The `FillIntentDto` already includes the `@Matches(/^\d+$/)` decorator on `fillAmount`, which ensures only digit-only strings reach the `BigInt()` constructor. The `ValidationPipe` (configured globally in `main.ts` with `whitelist: true, transform: true`) intercepts invalid input before it reaches the controller method and returns a structured 400 response.

This test closes the gap by verifying the end-to-end behavior: a malformed `fillAmount` query body is rejected at the validation layer with a clear 400 status.

## Verification

- `npm run lint` passes
- `npm run typecheck` passes
- `npm test` passes
- `npm run test:e2e` passes (new e2e test for non-numeric fillAmount)

## Acceptance Criteria

- [x] `FillIntentDto.fillAmount` has `@Matches(/^\d+$/)` pattern matching `CreateIntentDto.srcAmount`
- [x] E2E test added asserting non-numeric `fillAmount` returns 400 with validation error, not 500
- [x] PR description includes "Closes #65"
15 changes: 15 additions & 0 deletions test/intents.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ describe("IntentsController (e2e)", () => {
});
});

it("POST /api/v1/intents/:id/fill with non-numeric fillAmount returns 400", async () => {
const created = await createIntent({ user: "GFILLAMOUNT123456" });
await request(app.getHttpServer())
.post(`/api/v1/intents/${created.intentId}/accept`)
.send({ solver: "SOLVER_ALPHA" })
.expect(201);

const res = await request(app.getHttpServer())
.post(`/api/v1/intents/${created.intentId}/fill`)
.send({ solver: "SOLVER_ALPHA", fillAmount: "abc", txHash: "e2e-hash" })
.expect(400);
expect(res.body.error).toBe("Validation failed");
expect(Array.isArray(res.body.details)).toBe(true);
});

it("accept with an unknown/inactive solver is forbidden", async () => {
const created = await createIntent({ user: "GUNKNOWNSOLVER12345" });
await request(app.getHttpServer())
Expand Down