@@ -324,6 +324,58 @@ public function testResolveUrlPreservesEncodedIdAsSingleSegment(): void
324324 $ this ->assertSame ('/organizations/om_xyz/foo ' , $ rawSlashRequest ->getUri ()->getPath ());
325325 }
326326
327+ public function testEmptyBodySerializesAsJsonObject (): void
328+ {
329+ // Issue #400: an all-optional body with every field omitted reduces to
330+ // an empty PHP array. Guzzle's `json` option encodes that to the JSON
331+ // array `[]`, which JSON-object endpoints (e.g. challengeFactor on a
332+ // TOTP factor) reject with a 422. The body must serialize to `{}`.
333+ $ mock = new MockHandler ([
334+ new Response (200 , ['Content-Type ' => 'application/json ' ], '{} ' ),
335+ ]);
336+ $ history = [];
337+ $ handler = HandlerStack::create ($ mock );
338+ $ handler ->push (\GuzzleHttp \Middleware::history ($ history ));
339+
340+ $ client = new HttpClient (
341+ apiKey: 'test_key ' ,
342+ clientId: null ,
343+ baseUrl: 'https://api.workos.com ' ,
344+ timeout: 10 ,
345+ maxRetries: 0 ,
346+ handler: $ handler ,
347+ );
348+
349+ $ client ->request ('POST ' , 'auth/factors/auth_factor_123/challenge ' , body: []);
350+
351+ $ request = $ history [array_key_last ($ history )]['request ' ];
352+ $ this ->assertSame ('{} ' , (string ) $ request ->getBody ());
353+ }
354+
355+ public function testNonEmptyBodyStillSerializesAsJsonObject (): void
356+ {
357+ $ mock = new MockHandler ([
358+ new Response (200 , ['Content-Type ' => 'application/json ' ], '{} ' ),
359+ ]);
360+ $ history = [];
361+ $ handler = HandlerStack::create ($ mock );
362+ $ handler ->push (\GuzzleHttp \Middleware::history ($ history ));
363+
364+ $ client = new HttpClient (
365+ apiKey: 'test_key ' ,
366+ clientId: null ,
367+ baseUrl: 'https://api.workos.com ' ,
368+ timeout: 10 ,
369+ maxRetries: 0 ,
370+ handler: $ handler ,
371+ );
372+
373+ $ client ->request ('POST ' , 'auth/factors/auth_factor_123/challenge ' , body: ['sms_template ' => 'Your code is {{code}} ' ]);
374+
375+ $ request = $ history [array_key_last ($ history )]['request ' ];
376+ $ this ->assertSame ('{"sms_template":"Your code is {{code}}"} ' , (string ) $ request ->getBody ());
377+ }
378+
327379 public function testNonStringCodeFieldIsIgnored (): void
328380 {
329381 $ body = json_encode ([
0 commit comments