mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
* feat(java): inventory Spring bean candidates * fix(java): fail closed on Spring annotation shadowing * fix(java): resolve Spring beans after imports * fix(java): remove stale bean extraction path * style: satisfy locked Prettier version * fix(spring): address PR review findings * feat(spring): share bean inventory across Java and Kotlin * fix(spring): gate bean inventory analysis completeness * fix(kotlin): avoid reloading cached scope source * chore(autofix): apply prettier + eslint fixes via /autofix command --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
123 lines
4.6 KiB
TypeScript
123 lines
4.6 KiB
TypeScript
import { beforeAll, describe, expect, it } from 'vitest';
|
|
import path from 'node:path';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
import type { PipelineResult } from '../../types/pipeline.js';
|
|
|
|
const FIXTURE = path.resolve(__dirname, '..', 'fixtures', 'spring-bean-app');
|
|
|
|
describe('Spring Bean candidate inventory pipeline', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(FIXTURE, () => {}, {});
|
|
}, 60_000);
|
|
|
|
it('attaches canonical framework annotations after Java import resolution', () => {
|
|
const classes = new Map<string, Record<string, unknown>>();
|
|
result.graph.forEachNode((node) => {
|
|
if (node.label === 'Class') classes.set(String(node.properties.name), node.properties);
|
|
});
|
|
|
|
expect(classes.get('WidgetComponent')).toMatchObject({
|
|
frameworkAnnotations: ['org.springframework.stereotype.Component'],
|
|
});
|
|
expect(classes.get('BillingService')).toMatchObject({
|
|
frameworkAnnotations: ['org.springframework.stereotype.Service'],
|
|
});
|
|
expect(classes.get('WidgetRepository')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Repository',
|
|
]);
|
|
expect(classes.get('PageController')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Controller',
|
|
]);
|
|
expect(classes.get('ApiController')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.web.bind.annotation.RestController',
|
|
]);
|
|
expect(classes.get('AppConfiguration')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.context.annotation.Configuration',
|
|
]);
|
|
expect(classes.get('NestedService')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(classes.get('ExplicitAlongsideWildcard')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(classes.get('WildcardService')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
|
|
for (const name of [
|
|
'PlainUtility',
|
|
'WildcardCandidate',
|
|
'MultipleWildcardService',
|
|
'MemberShadowedService',
|
|
'ConflictingBean',
|
|
'ComposedService',
|
|
'ExplicitCustomService',
|
|
'TopLevelShadowedComponent',
|
|
'InheritedMemberShadowedService',
|
|
'StaticImportShadowedService',
|
|
]) {
|
|
expect(classes.get(name)).not.toHaveProperty('frameworkAnnotations');
|
|
}
|
|
});
|
|
|
|
it('uses the same candidate rules for Kotlin classes', () => {
|
|
const symbols = new Map<string, Record<string, unknown>>();
|
|
result.graph.forEachNode((node) => {
|
|
symbols.set(String(node.properties.name), node.properties);
|
|
});
|
|
|
|
expect(symbols.get('KotlinWidgetComponent')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Component',
|
|
]);
|
|
expect(symbols.get('KotlinBillingService')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(symbols.get('KotlinConfiguration')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.context.annotation.Configuration',
|
|
]);
|
|
expect(symbols.get('KotlinAbstractService')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(symbols.get('KotlinApiController')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.web.bind.annotation.RestController',
|
|
]);
|
|
expect(symbols.get('KotlinServiceId')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(symbols.get('KotlinNestedService')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Service',
|
|
]);
|
|
expect(symbols.get('KotlinWildcardRepository')?.frameworkAnnotations).toEqual([
|
|
'org.springframework.stereotype.Repository',
|
|
]);
|
|
|
|
for (const name of [
|
|
'KotlinServiceContract',
|
|
'KotlinServiceObject',
|
|
'KotlinServiceState',
|
|
'KotlinServiceMarker',
|
|
'KotlinComposedCandidate',
|
|
'KotlinMemberShadowedService',
|
|
'KotlinShadowedService',
|
|
'KotlinMultipleWildcardService',
|
|
]) {
|
|
expect(symbols.get(name)).not.toHaveProperty('frameworkAnnotations');
|
|
}
|
|
});
|
|
|
|
it('keeps RestController route discovery and HANDLES_ROUTE emission intact', () => {
|
|
let pingRouteId: string | undefined;
|
|
result.graph.forEachNode((node) => {
|
|
if (node.label === 'Route' && node.properties.name === '/ping') pingRouteId = node.id;
|
|
});
|
|
|
|
expect(pingRouteId).toBeDefined();
|
|
let handlesRoute = false;
|
|
result.graph.forEachRelationship((rel) => {
|
|
if (rel.type === 'HANDLES_ROUTE' && rel.targetId === pingRouteId) handlesRoute = true;
|
|
});
|
|
expect(handlesRoute).toBe(true);
|
|
});
|
|
});
|