test(11-01): add failing tender-query.builder spec (RED)

Extends TenderQueryDto with validated filter/sort params (q, openOnly,
deadlineFrom/To, valueMin/Max, includeNullValue, sort) and adds the
RED-first spec for the not-yet-implemented tender-query.builder.ts:
NULL-graceful value filter (D-05), openOnly deadline default (D-04),
explicit deadline range, and sort whitelist (UI-01).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 15:47:40 +02:00
parent 34b8df28d5
commit 426a1ea3b8
2 changed files with 238 additions and 3 deletions
+83 -3
View File
@@ -1,5 +1,16 @@
import { Type } from 'class-transformer';
import { IsIn, IsInt, IsOptional, Max, Min } from 'class-validator';
import {
IsBoolean,
IsDate,
IsIn,
IsInt,
IsNumber,
IsOptional,
IsString,
Max,
MaxLength,
Min,
} from 'class-validator';
/**
* Query DTO for paginating + filtering the global tender catalog.
@@ -7,10 +18,14 @@ import { IsIn, IsInt, IsOptional, Max, Min } from 'class-validator';
* Security:
* - T-10-15: pagination bounds (limit @Max(100)) mitigate oversized
* result-set DoS — same convention as DkvHistoryQueryDto.
* - T-11-01: `sort` is validated against a fixed whitelist (@IsIn) — the
* actual Prisma orderBy key mapping happens in tender-query.builder.ts's
* SORT_MAP, never from a raw user-supplied field name.
*
* Used for: GET /modules/tender-radar?page=1&limit=20&status=active
* Used for: GET /modules/tender-radar?page=1&limit=20&status=active&q=...
*
* No region/CPV filters here — rich filtering is Phase 11 (FILTER-*).
* Region/PLZ/Bundesland/CPV/favOnly filters are added in later Phase-11
* plans (11-02/03/05) — deliberately NOT added here.
*/
export class TenderQueryDto {
/**
@@ -41,4 +56,69 @@ export class TenderQueryDto {
@IsOptional()
@IsIn(['active', 'expired'])
status?: string;
/**
* Freitext-Suche über title + buyerName (FILTER-01, D-01).
* MaxLength bounds an otherwise-unbounded `contains` scan input.
*/
@IsOptional()
@IsString()
@MaxLength(200)
q?: string;
/**
* "Nur noch offene" Default-Ansicht (FILTER-04, D-04). Default-Semantik
* (true when omitted) is applied in tender-query.builder.ts, not here —
* the DTO only validates the shape of an explicitly-supplied value.
*/
@IsOptional()
@Type(() => Boolean)
@IsBoolean()
openOnly?: boolean;
/**
* Abgabefrist-Datumsbereich (FILTER-04). Combinable with openOnly.
*/
@IsOptional()
@Type(() => Date)
@IsDate()
deadlineFrom?: Date;
@IsOptional()
@Type(() => Date)
@IsDate()
deadlineTo?: Date;
/**
* Geschätzter Auftragswert min/max (FILTER-05, D-05).
*/
@IsOptional()
@Type(() => Number)
@IsNumber()
valueMin?: number;
@IsOptional()
@Type(() => Number)
@IsNumber()
valueMax?: number;
/**
* Whether NULL-value rows stay visible while a value filter is active.
* Default-Semantik (true when omitted) applied in the builder — D-05
* critical requirement: NULL-Wert-Zeilen dürfen NIE stillschweigend
* verschwinden.
*/
@IsOptional()
@Type(() => Boolean)
@IsBoolean()
includeNullValue?: boolean;
/**
* Sort-Whitelist (UI-01, D-06, T-11-01): only these three keys are
* accepted; the DTO-level @IsIn rejects anything else before the
* request reaches the builder's SORT_MAP.
*/
@IsOptional()
@IsIn(['deadline', 'value', 'published'])
sort?: string;
}