fork of hey-api/openapi-ts because I need some additional things
at feat/skip-token 654 lines 19 kB view raw
1import fs from 'node:fs'; 2import path from 'node:path'; 3import { fileURLToPath } from 'node:url'; 4 5import { createClient, type DefinePlugin, type UserConfig } from '@hey-api/openapi-ts'; 6 7import { getFilePaths, getSpecsPath } from '../../utils'; 8 9const __filename = fileURLToPath(import.meta.url); 10const __dirname = path.dirname(__filename); 11 12const versions = ['2.0.x', '3.0.x', '3.1.x']; 13 14for (const version of versions) { 15 const namespace = 'plugins'; 16 17 const outputDir = path.join(__dirname, 'generated', version, namespace); 18 19 describe(`OpenAPI ${version} ${namespace}`, () => { 20 const createConfig = ( 21 userConfig: Omit<UserConfig, 'input'> & 22 Pick<Required<UserConfig>, 'plugins'> & 23 Pick<Partial<UserConfig>, 'input'>, 24 ) => 25 ({ 26 ...userConfig, 27 input: path.join( 28 getSpecsPath(), 29 version, 30 typeof userConfig.input === 'string' ? userConfig.input : 'full.yaml', 31 ), 32 logs: { 33 level: 'silent', 34 }, 35 output: path.join( 36 outputDir, 37 typeof userConfig.plugins[0] === 'string' 38 ? userConfig.plugins[0] 39 : userConfig.plugins[0]!.name, 40 typeof userConfig.output === 'string' ? userConfig.output : '', 41 ), 42 plugins: userConfig.plugins ?? ['@hey-api/client-fetch'], 43 }) as const satisfies UserConfig; 44 45 const scenarios = [ 46 { 47 config: createConfig({ 48 output: 'fetch', 49 plugins: ['@tanstack/angular-query-experimental', '@hey-api/client-fetch'], 50 }), 51 description: 'generate Fetch API client with TanStack Angular Query Experimental plugin', 52 }, 53 { 54 config: createConfig({ 55 output: 'fetch', 56 plugins: ['@tanstack/react-query', '@hey-api/client-fetch'], 57 }), 58 description: 'generate Fetch API client with TanStack React Query plugin', 59 }, 60 { 61 config: createConfig({ 62 output: 'useQuery', 63 plugins: [ 64 { 65 name: '@tanstack/react-query', 66 useQuery: true, 67 }, 68 '@hey-api/client-fetch', 69 ], 70 }), 71 description: 72 'generate Fetch API client with TanStack React Query plugin with useQuery hooks', 73 }, 74 { 75 config: createConfig({ 76 output: 'skipToken', 77 plugins: [ 78 { 79 name: '@tanstack/react-query', 80 skipToken: true, 81 useQuery: true, 82 }, 83 '@hey-api/client-fetch', 84 ], 85 }), 86 description: 'generate Fetch API client with TanStack React Query plugin with skipToken', 87 }, 88 { 89 config: createConfig({ 90 output: 'fetch', 91 plugins: ['@tanstack/solid-query', '@hey-api/client-fetch'], 92 }), 93 description: 'generate Fetch API client with TanStack Solid Query plugin', 94 }, 95 { 96 config: createConfig({ 97 output: 'fetch', 98 plugins: ['@tanstack/svelte-query', '@hey-api/client-fetch'], 99 }), 100 description: 'generate Fetch API client with TanStack Svelte Query plugin', 101 }, 102 { 103 config: createConfig({ 104 output: 'fetch', 105 plugins: ['@tanstack/vue-query', '@hey-api/client-fetch'], 106 }), 107 description: 'generate Fetch API client with TanStack Vue Query plugin', 108 }, 109 { 110 config: createConfig({ 111 output: 'axios', 112 plugins: ['@tanstack/angular-query-experimental', '@hey-api/client-axios'], 113 }), 114 description: 'generate Axios client with TanStack Angular Query Experimental plugin', 115 }, 116 { 117 config: createConfig({ 118 output: 'axios', 119 plugins: ['@tanstack/react-query', '@hey-api/client-axios'], 120 }), 121 description: 'generate Axios client with TanStack React Query plugin', 122 }, 123 { 124 config: createConfig({ 125 output: 'axios', 126 plugins: ['@tanstack/solid-query', '@hey-api/client-axios'], 127 }), 128 description: 'generate Axios client with TanStack Solid Query plugin', 129 }, 130 { 131 config: createConfig({ 132 output: 'axios', 133 plugins: ['@tanstack/svelte-query', '@hey-api/client-axios'], 134 }), 135 description: 'generate Axios client with TanStack Svelte Query plugin', 136 }, 137 { 138 config: createConfig({ 139 output: 'axios', 140 plugins: ['@tanstack/vue-query', '@hey-api/client-axios'], 141 }), 142 description: 'generate Axios client with TanStack Vue Query plugin', 143 }, 144 { 145 config: createConfig({ 146 input: 'sdk-instance.yaml', 147 output: 'asClass', 148 plugins: [ 149 '@tanstack/angular-query-experimental', 150 '@hey-api/client-fetch', 151 { 152 asClass: true, 153 classNameBuilder: '{{name}}Service', 154 name: '@hey-api/sdk', 155 }, 156 ], 157 }), 158 description: 159 'generate Fetch API client with TanStack Angular Query Experimental plugin using class-based SDKs', 160 }, 161 { 162 config: createConfig({ 163 input: 'sdk-instance.yaml', 164 output: 'asClass', 165 plugins: [ 166 '@tanstack/react-query', 167 '@hey-api/client-fetch', 168 { 169 asClass: true, 170 classNameBuilder: '{{name}}Service', 171 name: '@hey-api/sdk', 172 }, 173 ], 174 }), 175 description: 176 'generate Fetch API client with TanStack React Query plugin using class-based SDKs', 177 }, 178 { 179 config: createConfig({ 180 input: 'sdk-instance.yaml', 181 output: 'asClass', 182 plugins: [ 183 '@tanstack/solid-query', 184 '@hey-api/client-fetch', 185 { 186 asClass: true, 187 classNameBuilder: '{{name}}Service', 188 name: '@hey-api/sdk', 189 }, 190 ], 191 }), 192 description: 193 'generate Fetch API client with TanStack Solid Query plugin using class-based SDKs', 194 }, 195 { 196 config: createConfig({ 197 input: 'sdk-instance.yaml', 198 output: 'asClass', 199 plugins: [ 200 '@tanstack/svelte-query', 201 '@hey-api/client-fetch', 202 { 203 asClass: true, 204 classNameBuilder: '{{name}}Service', 205 name: '@hey-api/sdk', 206 }, 207 ], 208 }), 209 description: 210 'generate Fetch API client with TanStack Svelte Query plugin using class-based SDKs', 211 }, 212 { 213 config: createConfig({ 214 input: 'sdk-instance.yaml', 215 output: 'asClass', 216 plugins: [ 217 '@tanstack/vue-query', 218 '@hey-api/client-fetch', 219 { 220 asClass: true, 221 classNameBuilder: '{{name}}Service', 222 name: '@hey-api/sdk', 223 }, 224 ], 225 }), 226 description: 227 'generate Fetch API client with TanStack Vue Query plugin using class-based SDKs', 228 }, 229 { 230 config: createConfig({ 231 input: 'sdk-instance.yaml', 232 output: 'name-builder', 233 plugins: [ 234 { 235 infiniteQueryKeys: { 236 name: '{{name}}A', 237 }, 238 infiniteQueryOptions: { 239 name: '{{name}}B', 240 }, 241 mutationOptions: { 242 name: '{{name}}C', 243 }, 244 name: '@tanstack/angular-query-experimental', 245 queryKeys: { 246 name: '{{name}}D', 247 }, 248 queryOptions: { 249 name: '{{name}}E', 250 }, 251 }, 252 '@hey-api/client-fetch', 253 '@hey-api/sdk', 254 ], 255 }), 256 description: 257 'generate Fetch API client with TanStack Angular Query Experimental plugin with custom names', 258 }, 259 { 260 config: createConfig({ 261 input: 'sdk-instance.yaml', 262 output: 'name-builder', 263 plugins: [ 264 { 265 infiniteQueryKeys: { 266 name: '{{name}}A', 267 }, 268 infiniteQueryOptions: { 269 name: '{{name}}B', 270 }, 271 mutationOptions: { 272 name: '{{name}}C', 273 }, 274 name: '@tanstack/react-query', 275 queryKeys: { 276 name: '{{name}}D', 277 }, 278 queryOptions: { 279 name: '{{name}}E', 280 }, 281 }, 282 '@hey-api/client-fetch', 283 '@hey-api/sdk', 284 ], 285 }), 286 description: 'generate Fetch API client with TanStack React Query plugin with custom names', 287 }, 288 { 289 config: createConfig({ 290 input: 'sdk-instance.yaml', 291 output: 'name-builder', 292 plugins: [ 293 { 294 infiniteQueryKeys: { 295 name: '{{name}}A', 296 }, 297 infiniteQueryOptions: { 298 name: '{{name}}B', 299 }, 300 mutationOptions: { 301 name: '{{name}}C', 302 }, 303 name: '@tanstack/solid-query', 304 queryKeys: { 305 name: '{{name}}D', 306 }, 307 queryOptions: { 308 name: '{{name}}E', 309 }, 310 }, 311 '@hey-api/client-fetch', 312 '@hey-api/sdk', 313 ], 314 }), 315 description: 'generate Fetch API client with TanStack Solid Query plugin with custom names', 316 }, 317 { 318 config: createConfig({ 319 input: 'sdk-instance.yaml', 320 output: 'name-builder', 321 plugins: [ 322 { 323 infiniteQueryKeys: { 324 name: '{{name}}A', 325 }, 326 infiniteQueryOptions: { 327 name: '{{name}}B', 328 }, 329 mutationOptions: { 330 name: '{{name}}C', 331 }, 332 name: '@tanstack/svelte-query', 333 queryKeys: { 334 name: '{{name}}D', 335 }, 336 queryOptions: { 337 name: '{{name}}E', 338 }, 339 }, 340 '@hey-api/client-fetch', 341 '@hey-api/sdk', 342 ], 343 }), 344 description: 345 'generate Fetch API client with TanStack Svelte Query plugin with custom names', 346 }, 347 { 348 config: createConfig({ 349 input: 'sdk-instance.yaml', 350 output: 'name-builder', 351 plugins: [ 352 { 353 infiniteQueryKeys: { 354 name: '{{name}}A', 355 }, 356 infiniteQueryOptions: { 357 name: '{{name}}B', 358 }, 359 mutationOptions: { 360 name: '{{name}}C', 361 }, 362 name: '@tanstack/vue-query', 363 queryKeys: { 364 name: '{{name}}D', 365 }, 366 queryOptions: { 367 name: '{{name}}E', 368 }, 369 }, 370 '@hey-api/client-fetch', 371 '@hey-api/sdk', 372 ], 373 }), 374 description: 'generate Fetch API client with TanStack Vue Query plugin with custom names', 375 }, 376 { 377 config: createConfig({ 378 output: 'default', 379 plugins: ['@hey-api/schemas'], 380 }), 381 description: 'generate schemas', 382 }, 383 { 384 config: createConfig({ 385 output: 'default', 386 plugins: ['@hey-api/sdk', '@hey-api/client-fetch'], 387 }), 388 description: 'generate SDK', 389 }, 390 { 391 config: createConfig({ 392 output: 'throwOnError', 393 plugins: [ 394 '@hey-api/sdk', 395 { 396 name: '@hey-api/client-fetch', 397 throwOnError: true, 398 }, 399 ], 400 }), 401 description: 'generate SDK that throws on error', 402 }, 403 { 404 config: createConfig({ 405 input: 'sdk-instance.yaml', 406 output: 'instance', 407 plugins: [ 408 { 409 instance: true, 410 name: '@hey-api/sdk', 411 }, 412 '@hey-api/client-fetch', 413 ], 414 }), 415 description: 'generate SDK instance', 416 }, 417 { 418 config: createConfig({ 419 output: 'default', 420 plugins: ['fastify'], 421 }), 422 description: 'generate Fastify types with Fastify plugin', 423 }, 424 { 425 config: createConfig({ 426 output: 'default', 427 plugins: ['valibot'], 428 }), 429 description: 'generate Valibot schemas with Valibot plugin', 430 }, 431 { 432 config: createConfig({ 433 input: 'type-format.yaml', 434 output: 'type-format-valibot', 435 plugins: [ 436 '@hey-api/transformers', 437 '@hey-api/client-fetch', 438 'valibot', 439 { 440 name: '@hey-api/sdk', 441 transformer: true, 442 validator: true, 443 }, 444 ], 445 }), 446 description: 'handles various schema types and formats', 447 }, 448 { 449 config: createConfig({ 450 input: 'type-format.yaml', 451 output: 'type-format-zod', 452 plugins: [ 453 '@hey-api/transformers', 454 '@hey-api/client-fetch', 455 'zod', 456 { 457 name: '@hey-api/sdk', 458 transformer: true, 459 validator: true, 460 }, 461 ], 462 }), 463 description: 'handles various schema types and formats', 464 }, 465 { 466 config: createConfig({ 467 input: 'transforms-read-write.yaml', 468 output: 'transforms-read-write-ignore', 469 parser: { 470 transforms: { 471 readWrite: false, 472 }, 473 }, 474 plugins: ['@hey-api/typescript', '@hey-api/client-fetch'], 475 }), 476 description: 'ignores read-only and write-only handling', 477 }, 478 { 479 config: createConfig({ 480 input: 'transforms-read-write.yaml', 481 output: 'transforms-read-write-custom-name', 482 parser: { 483 transforms: { 484 readWrite: { 485 requests: 'Writable{{name}}', 486 responses: 'Readable{{name}}', 487 }, 488 }, 489 }, 490 plugins: ['@hey-api/typescript', '@hey-api/client-fetch'], 491 }), 492 description: 'custom read-only and write-only naming', 493 }, 494 { 495 config: createConfig({ 496 input: 'sdk-nested-classes.yaml', 497 output: 'sdk-nested-classes', 498 plugins: [ 499 '@hey-api/client-fetch', 500 { 501 asClass: true, 502 classStructure: 'auto', 503 name: '@hey-api/sdk', 504 }, 505 ], 506 }), 507 description: 'generate nested classes with auto class structure', 508 }, 509 { 510 config: createConfig({ 511 input: 'sdk-nested-classes.yaml', 512 output: 'sdk-nested-classes-instance', 513 plugins: [ 514 '@hey-api/client-fetch', 515 { 516 asClass: true, 517 classStructure: 'auto', 518 instance: 'NestedSdkWithInstance', 519 name: '@hey-api/sdk', 520 }, 521 ], 522 }), 523 description: 'generate nested classes with auto class structure', 524 }, 525 { 526 config: createConfig({ 527 output: 'fetch', 528 plugins: ['@pinia/colada', '@hey-api/client-fetch'], 529 }), 530 description: 'generate Fetch API client with Pinia Colada plugin', 531 }, 532 { 533 config: createConfig({ 534 input: 'sdk-instance.yaml', 535 output: 'asClass', 536 plugins: [ 537 '@pinia/colada', 538 '@hey-api/client-fetch', 539 { 540 asClass: true, 541 classNameBuilder: '{{name}}Service', 542 name: '@hey-api/sdk', 543 }, 544 ], 545 }), 546 description: 'generate Fetch API client with Pinia Colada plugin using class-based SDKs', 547 }, 548 { 549 config: createConfig({ 550 output: 'default', 551 plugins: ['@angular/common', '@hey-api/client-angular'], 552 }), 553 description: 'generate Angular requests and resources', 554 }, 555 { 556 config: createConfig({ 557 output: 'default-class', 558 plugins: [ 559 { 560 httpRequests: { 561 containerName: '{{name}}ServiceRequests', 562 segmentName: '{{name}}Service', 563 strategy: 'byTags', 564 }, 565 httpResources: { 566 containerName: '{{name}}ServiceResources', 567 segmentName: '{{name}}Service', 568 strategy: 'byTags', 569 }, 570 name: '@angular/common', 571 }, 572 '@hey-api/client-angular', 573 ], 574 }), 575 description: 'generate Angular requests and resources (class)', 576 }, 577 ]; 578 579 it.each(scenarios)('$description', async ({ config }) => { 580 await createClient(config); 581 582 const filePaths = getFilePaths(config.output); 583 584 await Promise.all( 585 filePaths.map(async (filePath) => { 586 const fileContent = fs.readFileSync(filePath, 'utf-8'); 587 await expect(fileContent).toMatchFileSnapshot( 588 path.join( 589 __dirname, 590 '__snapshots__', 591 version, 592 namespace, 593 filePath.slice(outputDir.length + 1), 594 ), 595 ); 596 }), 597 ); 598 }); 599 }); 600 601 describe('custom plugin', () => { 602 it('handles a custom plugin', async () => { 603 const myPlugin: DefinePlugin<{ 604 customOption: boolean; 605 name: any; 606 }>['Config'] = { 607 api: undefined, 608 config: { 609 customOption: true, 610 }, 611 dependencies: ['@hey-api/typescript'], 612 handler: vi.fn(), 613 name: 'my-plugin', 614 }; 615 616 await createClient({ 617 input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 618 logs: { 619 level: 'silent', 620 }, 621 output: path.join(outputDir, myPlugin.name, 'default'), 622 plugins: [myPlugin, '@hey-api/client-fetch'], 623 }); 624 625 expect(myPlugin.handler).toHaveBeenCalled(); 626 }); 627 628 // TODO: fix test 629 it.skip('throws on invalid dependency', async () => { 630 const myPlugin: DefinePlugin<{ 631 name: any; 632 }>['Config'] = { 633 api: undefined, 634 config: {}, 635 dependencies: ['@hey-api/oops'], 636 handler: vi.fn(), 637 name: 'my-plugin', 638 }; 639 640 await expect(() => 641 createClient({ 642 input: path.join(getSpecsPath(), '3.1.x', 'full.yaml'), 643 logs: { 644 level: 'silent', 645 }, 646 output: path.join(outputDir, myPlugin.name, 'default'), 647 plugins: [myPlugin, '@hey-api/client-fetch'], 648 }), 649 ).rejects.toThrowError(/Found 1 configuration error./g); 650 651 expect(myPlugin.handler).not.toHaveBeenCalled(); 652 }); 653 }); 654}