fix(test): stabilize local Windows gate baselines (#2314)

This commit is contained in:
azizur100389 2026-06-29 22:27:50 +01:00 committed by GitHub
parent a7df8f861a
commit 8ad4469e96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 71 additions and 14 deletions

View file

@ -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',

View file

@ -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: {

View file

@ -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', {

View file

@ -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 () => {

View file

@ -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

View file

@ -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 });
}
});

View file

@ -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 });

View file

@ -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');