@@ -396,6 +396,210 @@ describe('api-resource generator', () => {
396396 } ) ;
397397 } ) ;
398398
399+ describe ( 'deprecated operations' , ( ) => {
400+ it ( 'emits /** @deprecated */ JSDoc on a deprecated token' , async ( ) => {
401+ vi . mocked ( SwaggerParser . dereference ) . mockResolvedValue ( {
402+ paths : {
403+ '/old-resource' : {
404+ get : {
405+ operationId : 'legacyGet' ,
406+ tags : [ 'legacy' ] ,
407+ deprecated : true ,
408+ responses : { '200' : { content : { 'application/json' : { schema : { } } } } } ,
409+ } ,
410+ } ,
411+ } ,
412+ } as never ) ;
413+
414+ await apiResourceGenerator ( tree , {
415+ specPath : 'specs/petstore.yaml' ,
416+ outputDir : 'libs/deprecated/src' ,
417+ } ) ;
418+ const content = tree . read ( 'libs/deprecated/src/legacy/legacy-get.token.ts' , 'utf-8' ) ! ;
419+ expect ( content ) . toContain ( '/** @deprecated */' ) ;
420+ } ) ;
421+
422+ it ( 'does NOT emit @deprecated for a non-deprecated operation' , async ( ) => {
423+ await apiResourceGenerator ( tree , {
424+ specPath : 'specs/petstore.yaml' ,
425+ outputDir : 'libs/not-deprecated/src' ,
426+ } ) ;
427+ const content = tree . read ( 'libs/not-deprecated/src/pets/list-pets.token.ts' , 'utf-8' ) ! ;
428+ expect ( content ) . not . toContain ( '@deprecated' ) ;
429+ } ) ;
430+ } ) ;
431+
432+ describe ( 'response type unions' , ( ) => {
433+ it ( 'emits a union type when an endpoint returns 200 and 201 JSON responses' , async ( ) => {
434+ vi . mocked ( SwaggerParser . dereference ) . mockResolvedValue ( {
435+ paths : {
436+ '/resources' : {
437+ put : {
438+ operationId : 'upsertResource' ,
439+ tags : [ 'resources' ] ,
440+ requestBody : { content : { 'application/json' : { schema : { } } } } ,
441+ responses : {
442+ '200' : { content : { 'application/json' : { schema : { } } } } ,
443+ '201' : { content : { 'application/json' : { schema : { } } } } ,
444+ } ,
445+ } ,
446+ } ,
447+ } ,
448+ } as never ) ;
449+
450+ await apiResourceGenerator ( tree , {
451+ specPath : 'specs/petstore.yaml' ,
452+ outputDir : 'libs/union/src' ,
453+ } ) ;
454+ const content = tree . read ( 'libs/union/src/resources/upsert-resource.token.ts' , 'utf-8' ) ! ;
455+ expect ( content ) . toContain ( "['responses']['200']['content']['application/json']" ) ;
456+ expect ( content ) . toContain ( "['responses']['201']['content']['application/json']" ) ;
457+ // The union pipe character should appear in the type definition
458+ expect ( content ) . toMatch ( / \| \s * p a t h s \[ / ) ;
459+ } ) ;
460+
461+ it ( 'emits a single type when only one 2xx JSON response exists' , async ( ) => {
462+ await apiResourceGenerator ( tree , {
463+ specPath : 'specs/petstore.yaml' ,
464+ outputDir : 'libs/single-resp/src' ,
465+ } ) ;
466+ const content = tree . read ( 'libs/single-resp/src/pets/list-pets.token.ts' , 'utf-8' ) ! ;
467+ // Single status — no leading pipe in the type alias line
468+ expect ( content ) . toContain ( "export type ListPetsResponse =" ) ;
469+ expect ( content ) . toContain ( "['responses']['200']" ) ;
470+ expect ( content ) . not . toMatch ( / e x p o r t t y p e L i s t P e t s R e s p o n s e = \s * \| / ) ;
471+ } ) ;
472+ } ) ;
473+
474+ describe ( 'binary body' , ( ) => {
475+ it ( 'emits Blob | ArrayBuffer for octet-stream request body' , async ( ) => {
476+ vi . mocked ( SwaggerParser . dereference ) . mockResolvedValue ( {
477+ paths : {
478+ '/upload' : {
479+ post : {
480+ operationId : 'uploadBinary' ,
481+ tags : [ 'upload' ] ,
482+ requestBody : {
483+ content : { 'application/octet-stream' : { schema : { type : 'string' , format : 'binary' } } } ,
484+ } ,
485+ responses : { '200' : { content : { 'application/json' : { schema : { } } } } } ,
486+ } ,
487+ } ,
488+ } ,
489+ } as never ) ;
490+
491+ await apiResourceGenerator ( tree , {
492+ specPath : 'specs/petstore.yaml' ,
493+ outputDir : 'libs/binary/src' ,
494+ } ) ;
495+ const content = tree . read ( 'libs/binary/src/upload/upload-binary.token.ts' , 'utf-8' ) ! ;
496+ expect ( content ) . toContain ( 'Blob | ArrayBuffer' ) ;
497+ // Must NOT reference the paths type for the body (would be wrong for binary)
498+ expect ( content ) . not . toContain ( "['requestBody']['content']['application/octet-stream']" ) ;
499+ } ) ;
500+
501+ it ( 'does not emit binary body type for standard json body' , async ( ) => {
502+ await apiResourceGenerator ( tree , {
503+ specPath : 'specs/petstore.yaml' ,
504+ outputDir : 'libs/json-body/src' ,
505+ } ) ;
506+ const content = tree . read ( 'libs/json-body/src/pets/create-pet.token.ts' , 'utf-8' ) ! ;
507+ expect ( content ) . not . toContain ( 'Blob | ArrayBuffer' ) ;
508+ // Prettier may split the long path across lines, so check the key parts separately
509+ expect ( content ) . toContain ( "requestBody']" ) ;
510+ expect ( content ) . toContain ( "['content']['application/json']" ) ;
511+ } ) ;
512+ } ) ;
513+
514+ describe ( 'cookie parameters' , ( ) => {
515+ it ( 'adds required cookie param as a required function arg and Cookie header' , async ( ) => {
516+ vi . mocked ( SwaggerParser . dereference ) . mockResolvedValue ( {
517+ paths : {
518+ '/me' : {
519+ get : {
520+ operationId : 'getCurrentUser' ,
521+ tags : [ 'user' ] ,
522+ parameters : [
523+ { in : 'cookie' , name : 'session' , required : true , schema : { type : 'string' } } ,
524+ ] ,
525+ responses : { '200' : { content : { 'application/json' : { schema : { } } } } } ,
526+ } ,
527+ } ,
528+ } ,
529+ } as never ) ;
530+
531+ await apiResourceGenerator ( tree , {
532+ specPath : 'specs/petstore.yaml' ,
533+ outputDir : 'libs/cookies/src' ,
534+ } ) ;
535+ const content = tree . read ( 'libs/cookies/src/user/get-current-user.token.ts' , 'utf-8' ) ! ;
536+ expect ( content ) . toContain ( 'session: string' ) ;
537+ // Prettier strips quotes from valid identifier keys: 'Cookie' → Cookie
538+ expect ( content ) . toContain ( 'Cookie:' ) ;
539+ expect ( content ) . toContain ( 'session=' ) ;
540+ } ) ;
541+
542+ it ( 'adds optional cookie param with conditional spread in Cookie header' , async ( ) => {
543+ vi . mocked ( SwaggerParser . dereference ) . mockResolvedValue ( {
544+ paths : {
545+ '/prefs' : {
546+ get : {
547+ operationId : 'getPreferences' ,
548+ tags : [ 'prefs' ] ,
549+ parameters : [
550+ { in : 'cookie' , name : 'theme' , required : false , schema : { type : 'string' } } ,
551+ ] ,
552+ responses : { '200' : { content : { 'application/json' : { schema : { } } } } } ,
553+ } ,
554+ } ,
555+ } ,
556+ } as never ) ;
557+
558+ await apiResourceGenerator ( tree , {
559+ specPath : 'specs/petstore.yaml' ,
560+ outputDir : 'libs/cookies-opt/src' ,
561+ } ) ;
562+ const content = tree . read ( 'libs/cookies-opt/src/prefs/get-preferences.token.ts' , 'utf-8' ) ! ;
563+ expect ( content ) . toContain ( 'theme?: string' ) ;
564+ // Prettier strips quotes from valid identifier keys: 'Cookie' → Cookie
565+ expect ( content ) . toContain ( 'Cookie:' ) ;
566+ // Optional cookie uses the conditional spread pattern
567+ expect ( content ) . toContain ( 'theme != null' ) ;
568+ } ) ;
569+ } ) ;
570+
571+ describe ( 'verbose output' , ( ) => {
572+ it ( 'prints a file summary when verbose is true' , async ( ) => {
573+ const consoleSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => undefined ) ;
574+ try {
575+ await apiResourceGenerator ( tree , {
576+ specPath : 'specs/petstore.yaml' ,
577+ outputDir : 'libs/verbose/src' ,
578+ verbose : true ,
579+ } ) ;
580+ expect ( consoleSpy ) . toHaveBeenCalled ( ) ;
581+ const output = consoleSpy . mock . calls . flat ( ) . join ( '\n' ) ;
582+ expect ( output ) . toContain ( '[openapi-resource-gen]' ) ;
583+ expect ( output ) . toContain ( '+' ) ;
584+ } finally {
585+ consoleSpy . mockRestore ( ) ;
586+ }
587+ } ) ;
588+
589+ it ( 'does not print anything when verbose is false' , async ( ) => {
590+ const consoleSpy = vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => undefined ) ;
591+ try {
592+ await apiResourceGenerator ( tree , {
593+ specPath : 'specs/petstore.yaml' ,
594+ outputDir : 'libs/quiet/src' ,
595+ } ) ;
596+ expect ( consoleSpy ) . not . toHaveBeenCalled ( ) ;
597+ } finally {
598+ consoleSpy . mockRestore ( ) ;
599+ }
600+ } ) ;
601+ } ) ;
602+
399603 describe ( 'descriptive errors' , ( ) => {
400604 it ( 'error message for missing openapi field includes version guidance' , ( ) => {
401605 // Verify the error text is descriptive before we even hit SwaggerParser.
0 commit comments