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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user