@@ -125,8 +125,11 @@ test('placeholders a missing required field, then satisfies the type', async ()
125125 assert . deepEqual ( calls [ calls . length - 1 ] [ 0 ] , { age : 30 , name : '' } ) ;
126126} ) ;
127127
128- test ( 'chases required -> type -> minLength on the same placeholder field ' , async ( ) => {
128+ test ( 'drops item when a placeholder field carries a minLength it cannot satisfy ' , async ( ) => {
129129 // Schema: { required: ['name'], properties: { name: { type: 'string', minLength: 3 } } }
130+ // We placeholder name to '' (type: string), but we no longer fabricate a
131+ // `'_'.repeat(N)` string for minLength — a made-up string is customer-data
132+ // poison — so the item is dropped instead.
130133 const validate = ( item : Item ) : ValidationError [ ] | null => {
131134 const errors : ValidationError [ ] = [ ] ;
132135 if ( item ?. name === undefined ) {
@@ -158,14 +161,17 @@ test('chases required -> type -> minLength on the same placeholder field', async
158161 }
159162 return null ;
160163 } ;
161- const { pushFn, calls } = makeMockPush ( validate ) ;
164+ const { pushFn } = makeMockPush ( validate ) ;
162165 const res = await safePushData ( pushFn , { age : 30 } , { maxAttempts : 10 } ) ;
163- assert . equal ( res . pushed , 1 ) ;
164- assert . equal ( res . attempts , 4 ) ; // required, type, minLength, success
165- assert . equal ( ( calls [ calls . length - 1 ] [ 0 ] . name as string ) . length , 3 ) ;
166+ assert . equal ( res . pushed , 0 ) ;
167+ assert . equal ( res . dropped . length , 1 ) ;
168+ assert . deepEqual ( res . dropped [ 0 ] . item , { age : 30 } ) ;
166169} ) ;
167170
168- test ( 'placeholder for enum picks the first allowed value' , async ( ) => {
171+ test ( 'drops item on enum constraint instead of fabricating the first allowed value' , async ( ) => {
172+ // We used to placeholder an enum field with its first allowed value; that
173+ // silently injects a plausible-but-wrong value into the dataset, so we now
174+ // drop the item instead.
169175 const validate = ( item : Item ) : ValidationError [ ] | null => {
170176 if ( item ?. role === undefined ) {
171177 return [
@@ -200,13 +206,15 @@ test('placeholder for enum picks the first allowed value', async () => {
200206 }
201207 return null ;
202208 } ;
203- const { pushFn, calls } = makeMockPush ( validate ) ;
204- const res = await safePushData ( pushFn , { name : 'x' } ) ;
205- assert . equal ( res . pushed , 1 ) ;
206- assert . equal ( calls [ calls . length - 1 ] [ 0 ] . role , 'admin' ) ;
209+ const { pushFn } = makeMockPush ( validate ) ;
210+ const res = await safePushData ( pushFn , { name : 'x' } , { maxAttempts : 10 } ) ;
211+ assert . equal ( res . pushed , 0 ) ;
212+ assert . equal ( res . dropped . length , 1 ) ;
207213} ) ;
208214
209- test ( 'placeholder for format=email' , async ( ) => {
215+ test ( 'drops item on format=email instead of fabricating a fake address' , async ( ) => {
216+ // A made-up `placeholder@example.com` is exactly the kind of junk we no
217+ // longer inject; the item is dropped instead.
210218 const validate = ( item : Item ) : ValidationError [ ] | null => {
211219 if ( item ?. email === undefined ) {
212220 return [
@@ -240,10 +248,45 @@ test('placeholder for format=email', async () => {
240248 }
241249 return null ;
242250 } ;
251+ const { pushFn } = makeMockPush ( validate ) ;
252+ const res = await safePushData ( pushFn , { name : 'x' } , { maxAttempts : 10 } ) ;
253+ assert . equal ( res . pushed , 0 ) ;
254+ assert . equal ( res . dropped . length , 1 ) ;
255+ } ) ;
256+
257+ test ( 'required field with a union type that allows null is placeholder-filled with null' , async ( ) => {
258+ // Schema: { required: ['note'], properties: { note: { type: ['string', 'null'] } } }
259+ // The initial `required` placeholder sets note = null; because the field
260+ // allows null, the follow-up type error reports both allowed types and we
261+ // keep null (the cleanest placeholder) rather than coercing to ''.
262+ const validate = ( item : Item ) : ValidationError [ ] | null => {
263+ if ( ! ( 'note' in ( item ?? { } ) ) ) {
264+ return [
265+ {
266+ instancePath : '' ,
267+ keyword : 'required' ,
268+ params : { missingProperty : 'note' } ,
269+ message : "must have required property 'note'" ,
270+ } ,
271+ ] ;
272+ }
273+ if ( item . note !== null && typeof item . note !== 'string' ) {
274+ return [
275+ {
276+ instancePath : '/note' ,
277+ keyword : 'type' ,
278+ params : { type : [ 'string' , 'null' ] } ,
279+ message : 'must be string,null' ,
280+ } ,
281+ ] ;
282+ }
283+ return null ;
284+ } ;
243285 const { pushFn, calls } = makeMockPush ( validate ) ;
244286 const res = await safePushData ( pushFn , { name : 'x' } ) ;
245287 assert . equal ( res . pushed , 1 ) ;
246- assert . equal ( calls [ calls . length - 1 ] [ 0 ] . email , 'placeholder@example.com' ) ;
288+ assert . equal ( res . dropped . length , 0 ) ;
289+ assert . deepEqual ( calls [ calls . length - 1 ] [ 0 ] , { name : 'x' , note : null } ) ;
247290} ) ;
248291
249292test ( 'drops item when a placeholder constraint has no known fix (pattern)' , async ( ) => {
0 commit comments