mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
* feat(spring): index Bean factories and Resource injection * fix(spring): address Bean and Resource review findings * refactor(lbug): keep relation pair parsing in router * test(lbug): preserve schema exports in WAL mocks * test(cache): align schema bump pin --------- Co-authored-by: Shining <xuenning@qiyi.com> Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
209 lines
7.5 KiB
TypeScript
209 lines
7.5 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { expect, it } from 'vitest';
|
|
import { buildTestGraph } from '../helpers/test-graph.js';
|
|
import { withTestLbugDB } from '../helpers/test-indexed-db.js';
|
|
import { streamAllCSVsToDisk } from '../../src/core/lbug/csv-generator.js';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
|
|
const CLASS_ID = 'Class:src/BillingService.java:BillingService';
|
|
const FRAMEWORK_MARKER = 'com.acme.FrameworkMarker';
|
|
const itLbugReopen = process.platform === 'win32' ? it.skip : it;
|
|
|
|
withTestLbugDB('spring-bean-metadata-roundtrip', (handle) => {
|
|
it('preserves Class framework annotations through all write paths', async () => {
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
const graph = buildTestGraph([
|
|
{
|
|
id: CLASS_ID,
|
|
label: 'Class',
|
|
name: 'BillingService',
|
|
filePath: 'src/BillingService.java',
|
|
extra: {
|
|
frameworkAnnotations: ['org.springframework.stereotype.Service', FRAMEWORK_MARKER],
|
|
},
|
|
},
|
|
]);
|
|
|
|
const csvDir = path.join(handle.tmpHandle.dbPath, 'csv-spring-bean');
|
|
const repoDir = path.join(handle.tmpHandle.dbPath, 'repo-spring-bean');
|
|
await fs.mkdir(repoDir, { recursive: true });
|
|
await streamAllCSVsToDisk(graph, repoDir, csvDir);
|
|
|
|
const classCsvPath = path.join(csvDir, 'class.csv');
|
|
const classCsv = await fs.readFile(classCsvPath, 'utf8');
|
|
expect(classCsv.split('\n')[0]).toBe(
|
|
'id,name,filePath,startLine,endLine,isExported,content,description,frameworkAnnotations',
|
|
);
|
|
expect(classCsv).toContain('org.springframework.stereotype.Service');
|
|
expect(classCsv).toContain(FRAMEWORK_MARKER);
|
|
|
|
await adapter.executeQuery(adapter.getCopyQuery('Class', classCsvPath.replace(/\\/g, '/')));
|
|
expect(
|
|
await adapter.executeQuery(
|
|
`MATCH (c:Class {id: '${CLASS_ID}'}) RETURN c.frameworkAnnotations AS frameworkAnnotations`,
|
|
),
|
|
).toEqual([
|
|
{
|
|
frameworkAnnotations: ['org.springframework.stereotype.Service', FRAMEWORK_MARKER],
|
|
},
|
|
]);
|
|
|
|
expect(
|
|
await adapter.insertNodeToLbug('Class', {
|
|
id: 'Class:src/Widget.java:Widget',
|
|
name: 'Widget',
|
|
filePath: 'src/Widget.java',
|
|
frameworkAnnotations: ['org.springframework.stereotype.Component'],
|
|
}),
|
|
).toBe(true);
|
|
expect(
|
|
await adapter.executeQuery(
|
|
`MATCH (c:Class {id: 'Class:src/Widget.java:Widget'}) RETURN c.frameworkAnnotations AS frameworkAnnotations`,
|
|
),
|
|
).toEqual([
|
|
{
|
|
frameworkAnnotations: ['org.springframework.stereotype.Component'],
|
|
},
|
|
]);
|
|
});
|
|
|
|
itLbugReopen('preserves Class framework annotations through batch upserts', async () => {
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
// The batch helper owns its connection, so release the singleton lock for
|
|
// the call and restore it before the shared fixture tears down.
|
|
await adapter.closeLbug();
|
|
let upsertResult: { inserted: number; failed: number };
|
|
try {
|
|
upsertResult = await adapter.batchInsertNodesToLbug(
|
|
[
|
|
{
|
|
label: 'Class',
|
|
properties: {
|
|
id: CLASS_ID,
|
|
name: 'BillingService',
|
|
filePath: 'src/BillingService.java',
|
|
frameworkAnnotations: ['org.springframework.stereotype.Repository', FRAMEWORK_MARKER],
|
|
},
|
|
},
|
|
],
|
|
handle.dbPath,
|
|
);
|
|
} finally {
|
|
await adapter.initLbug(handle.dbPath);
|
|
}
|
|
|
|
expect(upsertResult).toEqual({ inserted: 1, failed: 0 });
|
|
expect(
|
|
await adapter.executeQuery(
|
|
`MATCH (c:Class {id: '${CLASS_ID}'}) RETURN c.frameworkAnnotations AS frameworkAnnotations`,
|
|
),
|
|
).toEqual([
|
|
{
|
|
frameworkAnnotations: ['org.springframework.stereotype.Repository', FRAMEWORK_MARKER],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('rejects framework annotation items that COPY cannot encode losslessly', async () => {
|
|
const graph = buildTestGraph([
|
|
{
|
|
id: 'Class:src/Unsafe.java:Unsafe',
|
|
label: 'Class',
|
|
name: 'Unsafe',
|
|
filePath: 'src/Unsafe.java',
|
|
extra: { frameworkAnnotations: ['com.acme.Has,Comma'] },
|
|
},
|
|
]);
|
|
const csvDir = path.join(handle.tmpHandle.dbPath, 'csv-unsafe-framework-annotation');
|
|
const repoDir = path.join(handle.tmpHandle.dbPath, 'repo-unsafe-framework-annotation');
|
|
await fs.mkdir(repoDir, { recursive: true });
|
|
|
|
await expect(streamAllCSVsToDisk(graph, repoDir, csvDir)).rejects.toThrow(
|
|
'Cannot safely encode CSV string-list item',
|
|
);
|
|
});
|
|
|
|
it('persists pipeline-produced Class and Method INJECTS edges to Bean declarations', async () => {
|
|
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
|
const root = path.join(handle.tmpHandle.dbPath, 'spring-bean-injects-roundtrip');
|
|
const repoDir = path.join(root, 'repo');
|
|
const storageDir = path.join(root, 'storage');
|
|
await fs.mkdir(repoDir, { recursive: true });
|
|
await fs.mkdir(storageDir, { recursive: true });
|
|
await Promise.all([
|
|
fs.writeFile(
|
|
path.join(repoDir, 'Gateway.java'),
|
|
`package com.persisted;
|
|
public interface Gateway {}
|
|
`,
|
|
),
|
|
fs.writeFile(
|
|
path.join(repoDir, 'DefaultGateway.java'),
|
|
`package com.persisted;
|
|
public class DefaultGateway implements Gateway {}
|
|
`,
|
|
),
|
|
fs.writeFile(
|
|
path.join(repoDir, 'PersistedConfig.java'),
|
|
`package com.persisted;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
@Configuration
|
|
public class PersistedConfig {
|
|
@Bean Gateway persistedGateway() { return new DefaultGateway(); }
|
|
@Bean Object persistedAggregate(Gateway gateway) { return new Object(); }
|
|
}
|
|
`,
|
|
),
|
|
fs.writeFile(
|
|
path.join(repoDir, 'PersistedConsumer.java'),
|
|
`package com.persisted;
|
|
import jakarta.annotation.Resource;
|
|
public class PersistedConsumer {
|
|
@Resource(name = "persistedGateway") Gateway selected;
|
|
}
|
|
`,
|
|
),
|
|
]);
|
|
|
|
const { graph } = await runPipelineFromRepo(repoDir, () => {}, {});
|
|
expect(
|
|
graph.relationships.some(
|
|
(relationship) =>
|
|
relationship.type === 'INJECTS' &&
|
|
graph.getNode(relationship.sourceId)?.properties.name === 'PersistedConsumer' &&
|
|
graph.getNode(relationship.targetId)?.properties.name === 'persistedGateway',
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
graph.relationships.some(
|
|
(relationship) =>
|
|
relationship.type === 'INJECTS' &&
|
|
graph.getNode(relationship.sourceId)?.properties.name === 'persistedAggregate' &&
|
|
graph.getNode(relationship.targetId)?.properties.name === 'persistedGateway',
|
|
),
|
|
).toBe(true);
|
|
|
|
await adapter.loadGraphToLbug(graph, repoDir, storageDir);
|
|
|
|
expect(
|
|
await adapter.executeQuery(
|
|
`MATCH (source:Class)-[r:CodeRelation]->(target:CodeElement)
|
|
WHERE r.type = 'INJECTS'
|
|
AND source.name = 'PersistedConsumer'
|
|
AND target.name = 'persistedGateway'
|
|
RETURN source.name AS source, target.name AS target`,
|
|
),
|
|
).toEqual([{ source: 'PersistedConsumer', target: 'persistedGateway' }]);
|
|
expect(
|
|
await adapter.executeQuery(
|
|
`MATCH (source:Method)-[r:CodeRelation]->(target:CodeElement)
|
|
WHERE r.type = 'INJECTS'
|
|
AND source.name = 'persistedAggregate'
|
|
AND target.name = 'persistedGateway'
|
|
RETURN source.name AS source, target.name AS target`,
|
|
),
|
|
).toEqual([{ source: 'persistedAggregate', target: 'persistedGateway' }]);
|
|
}, 90_000);
|
|
});
|