Skip to content

Commit c8a6413

Browse files
authored
Merge pull request #19 from abraham/copilot/fix-18
[WIP] Generate OpenAPI schema file
2 parents 0bdf148 + 60d191f commit c8a6413

File tree

5 files changed

+585
-44
lines changed

5 files changed

+585
-44
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { OpenAPIGenerator } from '../../generators/OpenAPIGenerator';
2+
import { EntityClass } from '../../interfaces/EntityClass';
3+
import { ApiMethodsFile } from '../../interfaces/ApiMethodsFile';
4+
5+
describe('OpenAPIGenerator', () => {
6+
let generator: OpenAPIGenerator;
7+
8+
beforeEach(() => {
9+
generator = new OpenAPIGenerator();
10+
});
11+
12+
describe('generateSchema', () => {
13+
it('should generate a valid OpenAPI spec with entities and methods', () => {
14+
const entities: EntityClass[] = [
15+
{
16+
name: 'TestEntity',
17+
description: 'A test entity',
18+
attributes: [
19+
{
20+
name: 'id',
21+
type: 'Integer',
22+
description: 'The ID of the entity'
23+
},
24+
{
25+
name: 'name',
26+
type: 'String',
27+
description: 'The name of the entity',
28+
optional: true
29+
},
30+
{
31+
name: 'active',
32+
type: 'Boolean',
33+
description: 'Whether the entity is active',
34+
deprecated: true
35+
}
36+
]
37+
}
38+
];
39+
40+
const methodFiles: ApiMethodsFile[] = [
41+
{
42+
name: 'test API methods',
43+
description: 'Test API methods',
44+
methods: [
45+
{
46+
name: 'Get test entity',
47+
httpMethod: 'GET',
48+
endpoint: '/api/v1/test/:id',
49+
description: 'Retrieve a test entity',
50+
returns: 'TestEntity',
51+
oauth: 'User token + read'
52+
},
53+
{
54+
name: 'Create test entity',
55+
httpMethod: 'POST',
56+
endpoint: '/api/v1/test',
57+
description: 'Create a new test entity',
58+
parameters: [
59+
{
60+
name: 'name',
61+
description: 'Name of the entity',
62+
required: true
63+
},
64+
{
65+
name: 'active',
66+
description: 'Whether entity is active'
67+
}
68+
],
69+
oauth: 'User token + write'
70+
}
71+
]
72+
}
73+
];
74+
75+
const spec = generator.generateSchema(entities, methodFiles);
76+
77+
// Check basic structure
78+
expect(spec.openapi).toBe('3.0.3');
79+
expect(spec.info.title).toBe('Mastodon API');
80+
expect(spec.paths).toBeDefined();
81+
expect(spec.components?.schemas).toBeDefined();
82+
83+
// Check entity conversion
84+
expect(spec.components?.schemas?.['TestEntity']).toBeDefined();
85+
const entitySchema = spec.components!.schemas!['TestEntity'];
86+
expect(entitySchema.type).toBe('object');
87+
expect(entitySchema.description).toBe('A test entity');
88+
expect(entitySchema.properties?.['id']).toBeDefined();
89+
expect(entitySchema.properties?.['id'].type).toBe('integer');
90+
expect(entitySchema.properties?.['name']).toBeDefined();
91+
expect(entitySchema.properties?.['name'].type).toBe('string');
92+
expect(entitySchema.properties?.['active']).toBeDefined();
93+
expect(entitySchema.properties?.['active'].deprecated).toBe(true);
94+
95+
// Check required fields (should include non-optional fields)
96+
expect(entitySchema.required).toContain('id');
97+
expect(entitySchema.required).toContain('active');
98+
expect(entitySchema.required).not.toContain('name'); // optional
99+
100+
// Check path conversion
101+
expect(spec.paths['/api/v1/test/{id}']).toBeDefined();
102+
expect(spec.paths['/api/v1/test/{id}'].get).toBeDefined();
103+
expect(spec.paths['/api/v1/test']).toBeDefined();
104+
expect(spec.paths['/api/v1/test'].post).toBeDefined();
105+
106+
// Check GET operation
107+
const getOp = spec.paths['/api/v1/test/{id}'].get!;
108+
expect(getOp.summary).toBe('Get test entity');
109+
expect(getOp.description).toBe('Retrieve a test entity');
110+
expect(getOp.tags).toEqual(['test API methods']);
111+
expect(getOp.security).toEqual([{ OAuth2: [] }]);
112+
expect(getOp.parameters).toBeDefined();
113+
expect(getOp.parameters![0].name).toBe('id');
114+
expect(getOp.parameters![0].in).toBe('path');
115+
expect(getOp.parameters![0].required).toBe(true);
116+
117+
// Check POST operation
118+
const postOp = spec.paths['/api/v1/test'].post!;
119+
expect(postOp.summary).toBe('Create test entity');
120+
expect(postOp.requestBody).toBeDefined();
121+
expect(postOp.requestBody?.content['application/x-www-form-urlencoded']).toBeDefined();
122+
});
123+
124+
it('should handle empty inputs', () => {
125+
const spec = generator.generateSchema([], []);
126+
127+
expect(spec.openapi).toBe('3.0.3');
128+
expect(spec.paths).toEqual({});
129+
expect(spec.components?.schemas).toEqual({});
130+
});
131+
});
132+
133+
describe('toJSON', () => {
134+
it('should return valid JSON string', () => {
135+
const entities: EntityClass[] = [];
136+
const methodFiles: ApiMethodsFile[] = [];
137+
138+
generator.generateSchema(entities, methodFiles);
139+
const json = generator.toJSON();
140+
141+
expect(() => JSON.parse(json)).not.toThrow();
142+
const parsed = JSON.parse(json);
143+
expect(parsed.openapi).toBe('3.0.3');
144+
});
145+
});
146+
});

src/generate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ export { EntityClass } from './interfaces/EntityClass';
44
export { ApiParameter } from './interfaces/ApiParameter';
55
export { ApiMethod } from './interfaces/ApiMethod';
66
export { ApiMethodsFile } from './interfaces/ApiMethodsFile';
7+
export { OpenAPISpec } from './interfaces/OpenAPISchema';
78
export { EntityParser } from './parsers/EntityParser';
89
export { MethodParser } from './parsers/MethodParser';
10+
export { OpenAPIGenerator } from './generators/OpenAPIGenerator';
911
export { main } from './main';
1012

1113
// If this module is run directly, call main

0 commit comments

Comments
 (0)