generate-operation-ids.spec.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import fs from 'node:fs/promises';
  2. import { tmpdir } from 'node:os';
  3. import path from 'node:path';
  4. import type { OpenAPI3 } from 'openapi-typescript';
  5. import { describe, expect, it } from 'vitest';
  6. import { generateOperationIds } from './generate-operation-ids';
  7. async function createTempOpenAPIFile(spec: OpenAPI3): Promise<string> {
  8. const tempDir = await fs.mkdtemp(path.join(tmpdir(), 'openapi-test-'));
  9. const filePath = path.join(tempDir, 'openapi.json');
  10. await fs.writeFile(filePath, JSON.stringify(spec));
  11. return filePath;
  12. }
  13. async function cleanup(filePath: string): Promise<void> {
  14. try {
  15. await fs.unlink(filePath);
  16. await fs.rmdir(path.dirname(filePath));
  17. } catch (err) {
  18. // biome-ignore lint/suspicious/noConsole: This is a test file
  19. console.error('Cleanup failed:', err);
  20. }
  21. }
  22. describe('generateOperationIds', () => {
  23. it('should generate correct operationId for simple paths', async () => {
  24. const spec: OpenAPI3 = {
  25. openapi: '3.0.0',
  26. info: { title: 'Test API', version: '1.0.0' },
  27. paths: {
  28. '/foo': {
  29. get: {},
  30. post: {},
  31. },
  32. },
  33. };
  34. const filePath = await createTempOpenAPIFile(spec);
  35. try {
  36. const result = await generateOperationIds(filePath);
  37. const parsed = JSON.parse(result);
  38. expect(parsed.paths['/foo'].get.operationId).toBe('getFoo');
  39. expect(parsed.paths['/foo'].post.operationId).toBe('postFoo');
  40. } finally {
  41. await cleanup(filePath);
  42. }
  43. });
  44. it('should generate correct operationId for paths with parameters', async () => {
  45. const spec: OpenAPI3 = {
  46. openapi: '3.0.0',
  47. info: { title: 'Test API', version: '1.0.0' },
  48. paths: {
  49. '/foo/{id}': {
  50. get: {},
  51. },
  52. '/foo/{id}/bar/{page}': {
  53. get: {},
  54. },
  55. },
  56. };
  57. const filePath = await createTempOpenAPIFile(spec);
  58. try {
  59. const result = await generateOperationIds(filePath);
  60. const parsed = JSON.parse(result);
  61. expect(parsed.paths['/foo/{id}'].get.operationId).toBe('getFooById');
  62. expect(parsed.paths['/foo/{id}/bar/{page}'].get.operationId).toBe(
  63. 'getBarByPageByIdForFoo',
  64. );
  65. } finally {
  66. await cleanup(filePath);
  67. }
  68. });
  69. it('should generate correct operationId for nested resources', async () => {
  70. const spec: OpenAPI3 = {
  71. openapi: '3.0.0',
  72. info: { title: 'Test API', version: '1.0.0' },
  73. paths: {
  74. '/foo/bar': {
  75. get: {},
  76. },
  77. },
  78. };
  79. const filePath = await createTempOpenAPIFile(spec);
  80. try {
  81. const result = await generateOperationIds(filePath);
  82. const parsed = JSON.parse(result);
  83. expect(parsed.paths['/foo/bar'].get.operationId).toBe('getBarForFoo');
  84. } finally {
  85. await cleanup(filePath);
  86. }
  87. });
  88. it('should preserve existing operationId when overwriteExisting is false', async () => {
  89. const existingOperationId = 'existingOperation';
  90. const spec: OpenAPI3 = {
  91. openapi: '3.0.0',
  92. info: { title: 'Test API', version: '1.0.0' },
  93. paths: {
  94. '/foo': {
  95. get: {
  96. operationId: existingOperationId,
  97. },
  98. },
  99. },
  100. };
  101. const filePath = await createTempOpenAPIFile(spec);
  102. try {
  103. const result = await generateOperationIds(filePath, {
  104. overwriteExisting: false,
  105. });
  106. const parsed = JSON.parse(result);
  107. expect(parsed.paths['/foo'].get.operationId).toBe(existingOperationId);
  108. } finally {
  109. await cleanup(filePath);
  110. }
  111. });
  112. it('should overwrite existing operationId when overwriteExisting is true', async () => {
  113. const spec: OpenAPI3 = {
  114. openapi: '3.0.0',
  115. info: { title: 'Test API', version: '1.0.0' },
  116. paths: {
  117. '/foo': {
  118. get: {
  119. operationId: 'existingOperation',
  120. },
  121. },
  122. },
  123. };
  124. const filePath = await createTempOpenAPIFile(spec);
  125. try {
  126. const result = await generateOperationIds(filePath, {
  127. overwriteExisting: true,
  128. });
  129. const parsed = JSON.parse(result);
  130. expect(parsed.paths['/foo'].get.operationId).toBe('getFoo');
  131. } finally {
  132. await cleanup(filePath);
  133. }
  134. });
  135. it('should generate correct operationId for root path', async () => {
  136. const spec: OpenAPI3 = {
  137. openapi: '3.0.0',
  138. info: { title: 'Test API', version: '1.0.0' },
  139. paths: {
  140. '/': {
  141. get: {},
  142. },
  143. },
  144. };
  145. const filePath = await createTempOpenAPIFile(spec);
  146. try {
  147. const result = await generateOperationIds(filePath);
  148. const parsed = JSON.parse(result);
  149. expect(parsed.paths['/'].get.operationId).toBe('getRoot');
  150. } finally {
  151. await cleanup(filePath);
  152. }
  153. });
  154. it('should generate operationId for all HTTP methods', async () => {
  155. const spec: OpenAPI3 = {
  156. openapi: '3.0.0',
  157. info: { title: 'Test API', version: '1.0.0' },
  158. paths: {
  159. '/foo': {
  160. get: {},
  161. post: {},
  162. put: {},
  163. delete: {},
  164. patch: {},
  165. options: {},
  166. head: {},
  167. trace: {},
  168. },
  169. },
  170. };
  171. const filePath = await createTempOpenAPIFile(spec);
  172. try {
  173. const result = await generateOperationIds(filePath);
  174. const parsed = JSON.parse(result);
  175. expect(parsed.paths['/foo'].get.operationId).toBe('getFoo');
  176. expect(parsed.paths['/foo'].post.operationId).toBe('postFoo');
  177. expect(parsed.paths['/foo'].put.operationId).toBe('putFoo');
  178. expect(parsed.paths['/foo'].delete.operationId).toBe('deleteFoo');
  179. expect(parsed.paths['/foo'].patch.operationId).toBe('patchFoo');
  180. expect(parsed.paths['/foo'].options.operationId).toBe('optionsFoo');
  181. expect(parsed.paths['/foo'].head.operationId).toBe('headFoo');
  182. expect(parsed.paths['/foo'].trace.operationId).toBe('traceFoo');
  183. } finally {
  184. await cleanup(filePath);
  185. }
  186. });
  187. it('should throw error for non-existent file', async () => {
  188. await expect(
  189. generateOperationIds('non-existent-file.json'),
  190. ).rejects.toThrow();
  191. });
  192. });