mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
fix(test): stabilize local Windows gate baselines (#2314)
This commit is contained in:
parent
a7df8f861a
commit
8ad4469e96
8 changed files with 71 additions and 14 deletions
|
|
@ -36,6 +36,7 @@ const PLATFORM_LOGIC = [
|
|||
'test/unit/lbug-pool-fts-load.test.ts',
|
||||
'test/unit/repo-manager.test.ts',
|
||||
'test/unit/repo-manager-finalize-invariant.test.ts',
|
||||
'test/unit/git-utils.test.ts',
|
||||
'test/unit/hooks.test.ts',
|
||||
'test/unit/hook-db-lock-probe.test.ts',
|
||||
'test/unit/cursor-hook.test.ts',
|
||||
|
|
|
|||
|
|
@ -253,6 +253,10 @@ function contractIdFor(method: string, pathNorm: string): string {
|
|||
return `http::${method.toUpperCase()}::${pathNorm}`;
|
||||
}
|
||||
|
||||
export function normalizeRepoRelPath(filePath: string): string {
|
||||
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
||||
}
|
||||
|
||||
// ─── Graph row helpers ───────────────────────────────────────────────
|
||||
|
||||
function methodFromRouteReason(reason: string): string | null {
|
||||
|
|
@ -764,20 +768,21 @@ export class HttpRouteExtractor implements ContractExtractor {
|
|||
const out: ExtractedContract[] = [];
|
||||
for (const rel of files) {
|
||||
const detections = await getDetections(rel);
|
||||
const filePath = normalizeRepoRelPath(rel);
|
||||
for (const d of detections) {
|
||||
if (d.role !== 'provider') continue;
|
||||
const pathNorm = normalizeHttpPath(d.path);
|
||||
// Resolve the handler to a real symbol (named handler, or the inline
|
||||
// arrow that encloses the registration line) so the contract carries a
|
||||
// real symbolUid; fall back to the file + detection name otherwise.
|
||||
const resolved = await resolveSymbol(rel, d);
|
||||
const resolved = await resolveSymbol(filePath, d);
|
||||
out.push({
|
||||
contractId: contractIdFor(d.method, pathNorm),
|
||||
type: 'http',
|
||||
role: 'provider',
|
||||
symbolUid: resolved?.uid ?? '',
|
||||
symbolRef: {
|
||||
filePath: resolved?.filePath || rel,
|
||||
filePath: resolved?.filePath || filePath,
|
||||
name: resolved?.name ?? d.name ?? 'handler',
|
||||
},
|
||||
symbolName: resolved?.name ?? d.name ?? 'handler',
|
||||
|
|
@ -883,19 +888,20 @@ export class HttpRouteExtractor implements ContractExtractor {
|
|||
const out: ExtractedContract[] = [];
|
||||
for (const rel of files) {
|
||||
const detections = await getDetections(rel);
|
||||
const filePath = normalizeRepoRelPath(rel);
|
||||
for (const d of detections) {
|
||||
if (d.role !== 'consumer') continue;
|
||||
const pathNorm = normalizeConsumerPath(d.path);
|
||||
// Resolve the function CONTAINING the fetch/axios call so the consumer
|
||||
// contract carries a real symbolUid (was always '' — the gap that left
|
||||
// cross-repo trace/impact unable to traverse HTTP links).
|
||||
const resolved = await resolveSymbol(rel, d);
|
||||
const resolved = await resolveSymbol(filePath, d);
|
||||
out.push({
|
||||
contractId: contractIdFor(d.method, pathNorm),
|
||||
type: 'http',
|
||||
role: 'consumer',
|
||||
symbolUid: resolved?.uid ?? '',
|
||||
symbolRef: { filePath: resolved?.filePath || rel, name: resolved?.name ?? 'fetch' },
|
||||
symbolRef: { filePath: resolved?.filePath || filePath, name: resolved?.name ?? 'fetch' },
|
||||
symbolName: resolved?.name ?? 'fetch',
|
||||
confidence: d.confidence,
|
||||
meta: {
|
||||
|
|
|
|||
|
|
@ -103,6 +103,12 @@ export const getRemoteUrl = (repoPath: string): string | undefined => {
|
|||
* Find the git repository root from any path inside the repo
|
||||
*/
|
||||
export const getGitRoot = (fromPath: string): string | null => {
|
||||
const resolved = path.resolve(fromPath);
|
||||
// Avoid git rev-parse --show-toplevel trimming trailing spaces from the
|
||||
// repository root on Windows; callers that need identity keys canonicalize
|
||||
// this value with realpath before comparing it.
|
||||
if (hasGitDir(resolved)) return resolved;
|
||||
|
||||
try {
|
||||
const raw = chompGitOutput(
|
||||
execSync('git rev-parse --show-toplevel', {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func main() {
|
|||
const result = await runPipelineFromRepo(repoDir, () => {}, {});
|
||||
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
|
||||
await adapter.initLbug(dbPath);
|
||||
await adapter.loadGraphToLbug(result.graph, tmpBase, storagePath);
|
||||
await adapter.loadGraphToLbug(result.graph, repoDir, storagePath);
|
||||
}, 120_000);
|
||||
|
||||
afterAll(async () => {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ describe('literal-collectors', () => {
|
|||
(f) =>
|
||||
f.field === 'pattern' &&
|
||||
f.receiverNodeType === 'is_pattern_expression' &&
|
||||
f.file.endsWith('type-extractors/csharp.ts'),
|
||||
f.file.replace(/\\/g, '/').endsWith('type-extractors/csharp.ts'),
|
||||
);
|
||||
expect(scoped).toBeDefined();
|
||||
// a childForFieldName NOT inside a single positive type-guard stays unscoped
|
||||
|
|
|
|||
|
|
@ -1317,6 +1317,13 @@ describe('LocalBackend.callTool', () => {
|
|||
},
|
||||
])
|
||||
.mockResolvedValue([]);
|
||||
const repoDir = mkdtempSync(path.join(os.tmpdir(), 'gnx-rename-'));
|
||||
(listRegisteredRepos as any).mockResolvedValue([
|
||||
{ ...MOCK_REPO_ENTRY, path: repoDir, storagePath: path.join(repoDir, '.gitnexus') },
|
||||
]);
|
||||
backend = new LocalBackend();
|
||||
await backend.init();
|
||||
|
||||
const readSpy = vi
|
||||
.spyOn(fsPromises, 'readFile')
|
||||
.mockResolvedValue('function oldName() {}\n' as unknown as Buffer);
|
||||
|
|
@ -1337,6 +1344,7 @@ describe('LocalBackend.callTool', () => {
|
|||
} finally {
|
||||
readSpy.mockRestore();
|
||||
writeSpy.mockRestore();
|
||||
rmSync(repoDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,30 @@ import { describe, it, expect, vi } from 'vitest';
|
|||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import { execFileSync, execSync } from 'child_process';
|
||||
|
||||
const gitExecutable = (() => {
|
||||
if (process.platform !== 'win32') return 'git';
|
||||
try {
|
||||
return (
|
||||
execFileSync('where.exe', ['git'], { encoding: 'utf8' }).split(/\r?\n/).find(Boolean) ?? 'git'
|
||||
);
|
||||
} catch {
|
||||
return 'git';
|
||||
}
|
||||
})();
|
||||
|
||||
const isolatedTmpRoot = (() => {
|
||||
const root =
|
||||
process.platform === 'win32'
|
||||
? path.join(path.parse(os.tmpdir()).root, 'gitnexus-outside-git')
|
||||
: path.join(os.tmpdir(), 'gitnexus-outside-git');
|
||||
fs.mkdirSync(root, { recursive: true });
|
||||
return root;
|
||||
})();
|
||||
|
||||
const makeIsolatedTempDir = (prefix = 'gitnexus-test-'): string =>
|
||||
fs.mkdtempSync(path.join(isolatedTmpRoot, prefix));
|
||||
|
||||
// ─── hasGitDir ────────────────────────────────────────────────────────────
|
||||
//
|
||||
|
|
@ -71,7 +94,7 @@ describe('hasGitDir', () => {
|
|||
describe('isGitRepo', () => {
|
||||
it('returns false for a plain (non-git) directory', async () => {
|
||||
const { isGitRepo } = await import('../../src/storage/git.js');
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-test-'));
|
||||
const tmpDir = makeIsolatedTempDir();
|
||||
try {
|
||||
expect(isGitRepo(tmpDir)).toBe(false);
|
||||
} finally {
|
||||
|
|
@ -124,7 +147,7 @@ describe('getCurrentCommit', () => {
|
|||
describe('getGitRoot', () => {
|
||||
it('returns null for a plain temp directory', async () => {
|
||||
const { getGitRoot } = await import('../../src/storage/git.js');
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-test-'));
|
||||
const tmpDir = makeIsolatedTempDir();
|
||||
try {
|
||||
expect(getGitRoot(tmpDir)).toBeNull();
|
||||
} finally {
|
||||
|
|
@ -150,10 +173,12 @@ describe('getGitRoot', () => {
|
|||
it('preserves a trailing-space repository directory name (#2190)', async () => {
|
||||
const { getGitRoot } = await import('../../src/storage/git.js');
|
||||
const parentDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-space-root-'));
|
||||
const initDir = path.join(parentDir, 'repo-init');
|
||||
const repoDir = path.join(parentDir, 'repo ');
|
||||
try {
|
||||
fs.mkdirSync(repoDir);
|
||||
execSync('git init -q', { cwd: repoDir });
|
||||
fs.mkdirSync(initDir);
|
||||
execFileSync(gitExecutable, ['init', '-q'], { cwd: initDir, stdio: 'ignore' });
|
||||
fs.renameSync(initDir, repoDir);
|
||||
|
||||
expect(getGitRoot(repoDir)).toBe(path.resolve(repoDir));
|
||||
} finally {
|
||||
|
|
@ -241,7 +266,7 @@ describe('getRemoteUrl', () => {
|
|||
describe('getCanonicalRepoRoot', () => {
|
||||
it('returns null for a plain temp directory (not a git repo)', async () => {
|
||||
const { getCanonicalRepoRoot } = await import('../../src/storage/git.js');
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-canonical-'));
|
||||
const tmpDir = makeIsolatedTempDir('gitnexus-canonical-');
|
||||
try {
|
||||
expect(getCanonicalRepoRoot(tmpDir)).toBeNull();
|
||||
} finally {
|
||||
|
|
@ -276,7 +301,7 @@ describe('getCanonicalRepoRoot', () => {
|
|||
const { getCanonicalRepoRoot, getGitRoot } = await import('../../src/storage/git.js');
|
||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitnexus-canonical-wt-'));
|
||||
try {
|
||||
execSync('git init -q', { cwd: repoDir });
|
||||
execFileSync(gitExecutable, ['init', '-q'], { cwd: repoDir, stdio: 'ignore' });
|
||||
// `git worktree add` requires at least one commit on a real branch.
|
||||
execSync('git config user.email "test@example.com"', { cwd: repoDir });
|
||||
execSync('git config user.name "Test"', { cwd: repoDir });
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ vi.mock('../../../src/core/tree-sitter/safe-parse.js', async () => {
|
|||
return buildSafeParseMock(parseSourceSafeSpy);
|
||||
});
|
||||
|
||||
import { HttpRouteExtractor } from '../../../src/core/group/extractors/http-route-extractor.js';
|
||||
import {
|
||||
HttpRouteExtractor,
|
||||
normalizeRepoRelPath,
|
||||
} from '../../../src/core/group/extractors/http-route-extractor.js';
|
||||
import { getPluginForFile } from '../../../src/core/group/extractors/http-patterns/index.js';
|
||||
import type { RepoHandle } from '../../../src/core/group/types.js';
|
||||
|
||||
|
|
@ -43,6 +46,14 @@ describe('HttpRouteExtractor', () => {
|
|||
|
||||
const toPosixPath = (filePath: string): string => filePath.replace(/\\/g, '/');
|
||||
|
||||
describe('repo-relative path normalization', () => {
|
||||
it('normalizes Windows source-scan paths before symbol lookup', () => {
|
||||
expect(normalizeRepoRelPath('src\\api\\users.ts')).toBe('src/api/users.ts');
|
||||
expect(normalizeRepoRelPath('.\\src\\api\\users.ts')).toBe('src/api/users.ts');
|
||||
expect(normalizeRepoRelPath('./src/api/users.ts')).toBe('src/api/users.ts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('symbolUid resolution via containment', () => {
|
||||
it('resolves a source-scan consumer to the function CONTAINING the fetch', async () => {
|
||||
const dir = path.join(tmpDir, 'consumer-containment');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue