Skip to content

Commit 3f09851

Browse files
committed
Increase test coverage
1 parent abb5e1a commit 3f09851

File tree

8 files changed

+124
-107
lines changed

8 files changed

+124
-107
lines changed

tests/00-core/02-utils-coverage.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,13 @@ describe('Core: utils.ts (Coverage)', () => {
5555
expect(utils.getTypeScriptType(schema, config, [])).toBe('any');
5656
});
5757

58-
it('should return "any" for default switch case', () => {
59-
const schema: SwaggerDefinition = { type: 'null' };
58+
it('should return "any" for default switch case with unknown type', () => {
59+
const schema: SwaggerDefinition = { type: 'unknown' as any };
60+
expect(utils.getTypeScriptType(schema, config, [])).toBe('any');
61+
});
62+
63+
it('should return "any" for default switch case with array type', () => {
64+
const schema: SwaggerDefinition = { type: ['string', 'null'] };
6065
expect(utils.getTypeScriptType(schema, config, [])).toBe('any');
6166
});
6267

tests/00-core/03-parser-coverage.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ describe('Core: SwaggerParser (Coverage)', () => {
2020

2121
it('getPolymorphicSchemaOptions should return empty array for non-polymorphic schema', () => {
2222
const parser = new SwaggerParser({} as any, { options: {} } as GeneratorConfig);
23-
// Case 1: No oneOf or discriminator
2423
expect(parser.getPolymorphicSchemaOptions({ type: 'object' })).toEqual([]);
25-
// Case 2: Has discriminator but no oneOf
2624
expect(parser.getPolymorphicSchemaOptions({ discriminator: { propertyName: 'type' } })).toEqual([]);
2725
});
2826

@@ -43,6 +41,7 @@ describe('Core: SwaggerParser (Coverage)', () => {
4341
schemas: {
4442
...parserCoverageSpec.components.schemas,
4543
BadMap: {
44+
oneOf: [],
4645
discriminator: {
4746
propertyName: 'type',
4847
mapping: { 'bad': '#/non/existent' }
@@ -59,12 +58,10 @@ describe('Core: SwaggerParser (Coverage)', () => {
5958

6059
it('should correctly infer discriminator mapping when it is not explicitly provided', () => {
6160
const parser = new SwaggerParser(parserCoverageSpec as any, { options: {} } as GeneratorConfig);
62-
// This schema has a discriminator but no `mapping` property.
6361
const schema = parser.getDefinition('PolyWithInline');
6462
const options = parser.getPolymorphicSchemaOptions(schema!);
65-
// This covers the `|| {}` branch.
6663
expect(options).toHaveLength(1);
67-
expect(options[0].name).toBe('sub3'); // Inferred from the sub-schema's enum
64+
expect(options[0].name).toBe('sub3');
6865
});
6966

7067
it('getPolymorphicSchemaOptions should handle oneOf items that are not refs', () => {
@@ -84,7 +81,6 @@ describe('Core: SwaggerParser (Coverage)', () => {
8481

8582
it('loadContent should handle non-Error exceptions from fetch', async () => {
8683
global.fetch = vi.fn().mockImplementation(() => {
87-
// eslint-disable-next-line @typescript-eslint/no-throw-literal
8884
throw 'Network failure';
8985
});
9086
await expect(SwaggerParser.create('http://bad.url', {} as GeneratorConfig)).rejects.toThrow(

tests/30-emit-service/00-service-generator.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Project, Scope } from 'ts-morph';
55
import { ServiceGenerator } from '@src/service/emit/service/service.generator.js';
66
import { SwaggerParser } from '@src/core/parser.js';
77
import { GeneratorConfig } from '@src/core/types.js';
8-
import { coverageSpec, fullCRUD_Users } from '../shared/specs.js';
8+
import { coverageSpec, fullCRUD_Users, branchCoverageSpec } from '../shared/specs.js';
99
import { groupPathsByController } from '@src/service/parse.js';
1010
import { TypeGenerator } from '@src/service/emit/type/type.generator.js';
1111
import { TokenGenerator } from '@src/service/emit/utility/token.generator.js';
@@ -58,14 +58,25 @@ describe('Emitter: ServiceGenerator', () => {
5858

5959
it('should use a custom method name when provided in config', () => {
6060
const project = createTestEnvironment(coverageSpec, {
61-
// FIX: The operationId contains dashes which need to be replaced.
6261
customizeMethodName: (opId) => `custom_${opId.replace(/-/g, '_')}`
6362
});
6463
const serviceFile = project.getSourceFileOrThrow('/out/services/customName.service.ts');
6564
const serviceClass = serviceFile.getClassOrThrow('CustomNameService');
6665
expect(serviceClass.getMethod('custom_get_custom_name')).toBeDefined();
6766
});
6867

68+
it('should fall back to path-based name if customizer exists but op has no ID', () => {
69+
const project = createTestEnvironment(branchCoverageSpec, {
70+
customizeMethodName: (opId) => `custom_${opId}`
71+
});
72+
// The tag was changed to 'NoOperationId' which creates 'noOperationId.service.ts'
73+
const serviceFile = project.getSourceFileOrThrow('/out/services/noOperationId.service.ts');
74+
// The generator should ignore the customizer and create a name from the path.
75+
// head /no-operation-id -> headNoOperationId
76+
const serviceClass = serviceFile.getClassOrThrow('NoOperationIdService');
77+
expect(serviceClass.getMethod('headNoOperationId')).toBeDefined();
78+
});
79+
6980
it('should de-duplicate method names from conflicting operationIds', () => {
7081
const project = createTestEnvironment(coverageSpec);
7182
const serviceFile = project.getSourceFileOrThrow('/out/services/duplicateName.service.ts');

tests/40-emit-utility/06-provider-generator.spec.ts

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,63 @@ import { DateTransformerGenerator } from '../../src/service/emit/utility/date-tr
1111
import { emptySpec, securitySpec } from '../shared/specs.js';
1212

1313
describe('Emitter: ProviderGenerator', () => {
14-
// This helper function is the key. It simulates the entire generation process
15-
// for the provider file and its dependencies.
16-
const runGenerator = (spec: object, configOverrides: Partial<GeneratorConfig['options']> = {}) => {
14+
const runGenerator = (spec: object, config: Partial<GeneratorConfig> = {}) => {
1715
const project = new Project({ useInMemoryFileSystem: true });
18-
const config: GeneratorConfig = {
16+
const fullConfig: GeneratorConfig = {
1917
input: '',
2018
output: '/out',
21-
clientName: 'Test',
22-
options: { generateServices: true, dateType: 'string', enumStyle: 'enum', ...configOverrides },
19+
// `clientName` is deliberately omitted from the base to test fallback logic
20+
options: { generateServices: true, dateType: 'string', enumStyle: 'enum' },
21+
...config
2322
};
24-
const parser = new SwaggerParser(spec as any, config);
2523

26-
// Generate dependencies that the ProviderGenerator needs to import from.
27-
new TokenGenerator(project, config.clientName).generate(config.output);
28-
new BaseInterceptorGenerator(project, config.clientName).generate(config.output);
24+
const parser = new SwaggerParser(spec as any, fullConfig);
2925

30-
if (config.options.dateType === 'Date') {
31-
new DateTransformerGenerator(project).generate(config.output);
26+
new TokenGenerator(project, fullConfig.clientName).generate(fullConfig.output);
27+
new BaseInterceptorGenerator(project, fullConfig.clientName).generate(fullConfig.output);
28+
29+
if (fullConfig.options.dateType === 'Date') {
30+
new DateTransformerGenerator(project).generate(fullConfig.output);
3231
}
3332

3433
let tokenNames: string[] = [];
35-
// This logic mimics the orchestrator to decide if auth files are needed
36-
// and to get the `tokenNames` which is crucial for the ProviderGenerator's constructor.
3734
if (Object.keys(parser.getSecuritySchemes()).length > 0) {
38-
new AuthTokensGenerator(project).generate(config.output);
39-
const authInterceptorResult = new AuthInterceptorGenerator(parser, project).generate(config.output);
35+
new AuthTokensGenerator(project).generate(fullConfig.output);
36+
const authInterceptorResult = new AuthInterceptorGenerator(parser, project).generate(fullConfig.output);
4037
tokenNames = authInterceptorResult?.tokenNames || [];
4138
}
4239

43-
// The actual generator under test.
44-
new ProviderGenerator(parser, project, tokenNames).generate(config.output);
40+
new ProviderGenerator(parser, project, tokenNames).generate(fullConfig.output);
4541
return project.getSourceFile('/out/providers.ts')?.getText();
4642
};
4743

44+
it('should use "Default" client name when none is provided in config', () => {
45+
// Run with an empty config, so clientName is undefined
46+
const fileContent = runGenerator(emptySpec, {});
47+
expect(fileContent).toContain('export function provideDefaultClient(config: DefaultConfig)');
48+
expect(fileContent).toContain('export interface DefaultConfig');
49+
});
50+
4851
it('should not generate if generateServices is false', () => {
49-
const fileContent = runGenerator(emptySpec, { generateServices: false });
52+
const fileContent = runGenerator(emptySpec, { options: { generateServices: false } as any });
5053
expect(fileContent).toBeUndefined();
5154
});
5255

5356
it('should generate a basic provider without security or date transform', () => {
54-
const fileContent = runGenerator(emptySpec);
57+
const fileContent = runGenerator(emptySpec, { clientName: 'Test' });
5558
expect(fileContent).toBeDefined();
5659
expect(fileContent).toContain('export function provideTestClient(config: TestConfig)');
5760
expect(fileContent).toContain('export interface TestConfig');
58-
// Check interface props
5961
expect(fileContent).toContain('basePath: string');
6062
expect(fileContent).toContain('enableDateTransform?: boolean');
6163
expect(fileContent).not.toContain('apiKey?: string');
6264
expect(fileContent).not.toContain('bearerToken?:');
63-
// Check provider logic
6465
expect(fileContent).not.toContain('AuthInterceptor');
6566
expect(fileContent).not.toContain('DateInterceptor');
6667
});
6768

6869
it('should add providers for both API key and Bearer token when spec contains both', () => {
69-
const fileContent = runGenerator(securitySpec);
70+
const fileContent = runGenerator(securitySpec, { clientName: 'Test' });
7071
expect(fileContent).toContain('apiKey?: string');
7172
expect(fileContent).toContain('bearerToken?: string | (() => string)');
7273
expect(fileContent).toContain('if (config.apiKey)');
@@ -78,60 +79,48 @@ describe('Emitter: ProviderGenerator', () => {
7879

7980
it('should add providers for ONLY API key when spec contains only that', () => {
8081
const apiKeySpec = { ...emptySpec, components: { securitySchemes: { ApiKeyAuth: {type: 'apiKey', in: 'header', name: 'X-API-KEY'} } } };
81-
const fileContent = runGenerator(apiKeySpec);
82-
// Check interface
82+
const fileContent = runGenerator(apiKeySpec, { clientName: 'Test' });
8383
expect(fileContent).toContain('apiKey?: string');
8484
expect(fileContent).not.toContain('bearerToken?:');
85-
// Check provider function
8685
expect(fileContent).toContain('if (config.apiKey)');
8786
expect(fileContent).not.toContain('if (config.bearerToken)');
88-
expect(fileContent).toContain('AuthInterceptor'); // Auth interceptor is still needed
87+
expect(fileContent).toContain('AuthInterceptor');
8988
});
9089

9190
it('should add providers for ONLY Bearer token when spec contains only that', () => {
9291
const bearerSpec = { ...emptySpec, components: { securitySchemes: { BearerAuth: {type: 'http', scheme: 'bearer'} } } };
93-
const fileContent = runGenerator(bearerSpec);
94-
// Check interface
92+
const fileContent = runGenerator(bearerSpec, { clientName: 'Test' });
9593
expect(fileContent).not.toContain('apiKey?: string');
9694
expect(fileContent).toContain('bearerToken?: string | (() => string)');
97-
// Check provider function
9895
expect(fileContent).not.toContain('if (config.apiKey)');
9996
expect(fileContent).toContain('if (config.bearerToken)');
10097
expect(fileContent).toContain('AuthInterceptor');
10198
});
10299

103100
it('should add DateInterceptor if dateType is "Date"', () => {
104-
const fileContent = runGenerator(emptySpec, { dateType: 'Date' });
101+
const fileContent = runGenerator(emptySpec, { clientName: 'Test', options: { dateType: 'Date' } as any });
105102
expect(fileContent).toContain('if (config.enableDateTransform !== false)');
106103
expect(fileContent).toContain('customInterceptors.unshift(new DateInterceptor());');
107104
});
108105

109106
it('should generate an empty custom interceptors array if none are provided', () => {
110-
const fileContent = runGenerator(emptySpec);
107+
const fileContent = runGenerator(emptySpec, { clientName: 'Test' });
111108
expect(fileContent).toContain(
112109
'const customInterceptors = config.interceptors?.map(InterceptorClass => new InterceptorClass()) || [];',
113110
);
114111
expect(fileContent).toContain(`provide: HTTP_INTERCEPTORS_TEST, useValue: customInterceptors`);
115112
});
116113

117-
it('should handle config.interceptors being an empty array', () => {
118-
// This covers the `?.map` chain where `interceptors` exists but is empty
119-
const fileContent = runGenerator(emptySpec); // The logic is the same as the above test
120-
expect(fileContent).toContain('const customInterceptors = config.interceptors?.map');
121-
});
122-
123114
it('should not include token providers for unsupported security schemes (e.g., cookie)', () => {
124115
const spec = {
125116
...emptySpec,
126117
components: { securitySchemes: { Cookie: { type: 'apiKey', in: 'cookie', name: 'sid' } } },
127118
};
128-
const fileContent = runGenerator(spec);
119+
const fileContent = runGenerator(spec, { clientName: 'Test' });
129120

130-
// AuthInterceptorGenerator returns undefined, so ProviderGenerator gets an empty tokenNames array
131121
expect(fileContent).not.toContain(
132122
'providers.push({ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true });',
133123
);
134124
expect(fileContent).not.toContain('API_KEY_TOKEN');
135-
expect(fileContent).not.toContain('BEARER_TOKEN_TOKEN');
136125
});
137126
});

tests/60-e2e/00-orchestrator.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ describe('E2E: Full Generation Orchestrator', () => {
2525
expect(filePaths).toContain('/generated/services/index.ts');
2626
});
2727

28+
it('should generate date transformer when dateType is Date', async () => {
29+
const project = await runGeneratorWithConfig(coverageSpec, { dateType: 'Date' });
30+
const filePaths = project.getSourceFiles().map(f => f.getFilePath());
31+
expect(filePaths).toContain('/generated/utils/date-transformer.ts');
32+
});
33+
2834
it('should generate auth-related files when security spec is provided', async () => {
2935
const project = createTestProject();
3036
const config: GeneratorConfig = { input: '', output: '/generated', options: { generateServices: true } as any };

0 commit comments

Comments
 (0)