mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
Some checks are pending
CI / quality (push) Waiting to run
CI / tests (push) Waiting to run
CI / e2e (push) Waiting to run
CI / scope-parity (push) Waiting to run
CI / Save PR Metadata (push) Blocked by required conditions
CI / CI Gate (push) Blocked by required conditions
Release Candidate / Check if release candidate should run (push) Waiting to run
Release Candidate / ci (push) Blocked by required conditions
Release Candidate / Publish release candidate to npm (push) Blocked by required conditions
Release Candidate / Build & Push RC Docker images (push) Blocked by required conditions
* Initial plan * feat: detect sibling-clone graph drift via remote URL fingerprint Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e5decb67-7fec-40e7-b2a1-b5e94a0d393f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: address review feedback — fake commit, same-commit case, regex docs Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e5decb67-7fec-40e7-b2a1-b5e94a0d393f Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(mcp): address review feedback — CI green, perf, dead branch, one-shot test Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/cc2259f7-94e4-4243-aaa9-e03b7c632d32 * Merge branch 'main' into copilot/fix-single-path-indexing-issue Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/5840b3dd-e879-4854-a067-d1622bec2634 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * Merge branch 'main' into copilot/fix-single-path-indexing-issue Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/9025262f-4dd4-4774-8f32-e14434100004 Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * style: prettier format run-analyze.ts after merge with main Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/a7be18dd-102f-4a7b-ac56-53fbd414fe3b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * test: realpath both sides of cwdGitRoot assertion for Windows 8.3 short-name compat Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b2a1c6a3-e454-4b87-b0e4-69d7c0d9a51b Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> * fix(test): use path-agnostic assertion for cwdGitRoot on Windows (#1015) git rev-parse --show-toplevel returns long path names on Windows while os.tmpdir() returns 8.3 short names. fs.realpathSync does not expand short names, so exact path comparison always fails on Windows CI runners. Replace with behavioral assertions instead. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <copilot-swe-agent[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: evolution <wjc163@sina.cn>
206 lines
7.3 KiB
TypeScript
206 lines
7.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import path from 'node:path';
|
|
|
|
const mockAccess = vi.fn();
|
|
const mockGetStoragePaths = vi.fn();
|
|
const mockLoadMeta = vi.fn();
|
|
const mockRegisterRepo = vi.fn();
|
|
const mockAddToGitignore = vi.fn();
|
|
const mockGetGitRoot = vi.fn();
|
|
const mockIsGitRepo = vi.fn();
|
|
|
|
vi.mock('fs/promises', () => ({
|
|
default: {
|
|
access: mockAccess,
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../src/storage/repo-manager.js', () => ({
|
|
getStoragePaths: mockGetStoragePaths,
|
|
loadMeta: mockLoadMeta,
|
|
registerRepo: mockRegisterRepo,
|
|
addToGitignore: mockAddToGitignore,
|
|
}));
|
|
|
|
vi.mock('../../src/storage/git.js', () => ({
|
|
getGitRoot: mockGetGitRoot,
|
|
isGitRepo: mockIsGitRepo,
|
|
// `index-repo.ts` calls `getRemoteUrl` to backfill `remoteUrl` on
|
|
// older `.gitnexus/meta.json` files. The unit tests don't care
|
|
// about the remote URL, so a static `undefined` keeps behaviour
|
|
// identical to the pre-feature path.
|
|
getRemoteUrl: vi.fn().mockReturnValue(undefined),
|
|
}));
|
|
|
|
describe('indexCommand', () => {
|
|
const resolvedRepo = path.resolve('/repo');
|
|
const resolvedOutside = path.resolve('/outside/path');
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.restoreAllMocks();
|
|
process.exitCode = undefined;
|
|
|
|
mockGetStoragePaths.mockImplementation((repoPath: string) => ({
|
|
storagePath: `${repoPath}/.gitnexus`,
|
|
lbugPath: `${repoPath}/.gitnexus/lbug`,
|
|
metaPath: `${repoPath}/.gitnexus/meta.json`,
|
|
}));
|
|
mockLoadMeta.mockResolvedValue({
|
|
repoPath: resolvedRepo,
|
|
lastCommit: 'abc123',
|
|
indexedAt: '2026-03-20T00:00:00.000Z',
|
|
stats: { nodes: 10, edges: 20 },
|
|
});
|
|
mockAccess.mockResolvedValue(undefined);
|
|
mockAddToGitignore.mockResolvedValue(undefined);
|
|
mockGetGitRoot.mockReturnValue(resolvedRepo);
|
|
mockIsGitRepo.mockReturnValue(true);
|
|
});
|
|
|
|
it('fails when target path is not a git repository', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
mockIsGitRepo.mockReturnValue(false);
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/outside/path']);
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
expect(logSpy).toHaveBeenCalledWith(` Not a git repository: ${resolvedOutside}`);
|
|
});
|
|
|
|
it('fails when .gitnexus folder does not exist', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
mockAccess.mockRejectedValueOnce(new Error('missing .gitnexus'));
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo']);
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
expect(logSpy).toHaveBeenCalledWith(
|
|
` No .gitnexus/ folder found at: ${resolvedRepo}/.gitnexus`,
|
|
);
|
|
});
|
|
|
|
it('fails when lbug database does not exist', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
mockAccess.mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error('missing lbug'));
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo']);
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
expect(logSpy).toHaveBeenCalledWith(
|
|
' .gitnexus/ folder exists but contains no LadybugDB index.',
|
|
);
|
|
});
|
|
|
|
it('fails when meta.json is missing and --force is not set', async () => {
|
|
mockLoadMeta.mockResolvedValue(null);
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo']);
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
});
|
|
|
|
it('registers with minimal metadata when meta is missing and --force is set', async () => {
|
|
mockLoadMeta.mockResolvedValue(null);
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo'], { force: true });
|
|
|
|
expect(mockRegisterRepo).toHaveBeenCalledTimes(1);
|
|
expect(mockRegisterRepo).toHaveBeenCalledWith(
|
|
resolvedRepo,
|
|
expect.objectContaining({
|
|
repoPath: resolvedRepo,
|
|
lastCommit: '',
|
|
}),
|
|
);
|
|
expect(process.exitCode).toBeUndefined();
|
|
});
|
|
|
|
it('registers successfully with existing metadata', async () => {
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo']);
|
|
|
|
expect(mockRegisterRepo).toHaveBeenCalledTimes(1);
|
|
expect(mockRegisterRepo).toHaveBeenCalledWith(
|
|
resolvedRepo,
|
|
expect.objectContaining({ repoPath: resolvedRepo }),
|
|
);
|
|
expect(mockAddToGitignore).toHaveBeenCalledTimes(1);
|
|
expect(mockAddToGitignore).toHaveBeenCalledWith(resolvedRepo);
|
|
expect(process.exitCode).toBeUndefined();
|
|
});
|
|
|
|
it('registers non-git path when --allow-non-git is set', async () => {
|
|
mockIsGitRepo.mockReturnValue(false);
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/outside/path'], { allowNonGit: true });
|
|
|
|
expect(mockRegisterRepo).toHaveBeenCalledTimes(1);
|
|
expect(process.exitCode).toBeUndefined();
|
|
});
|
|
|
|
it('fails when called with no path and cwd is not inside a git repo', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
mockGetGitRoot.mockReturnValue(null);
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(); // no args
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
expect(logSpy).toHaveBeenCalledWith(' Not inside a git repository, try to run git init\n');
|
|
});
|
|
|
|
it('registers from cwd when no path is provided', async () => {
|
|
// getGitRoot already mocked to return resolvedRepo in beforeEach
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(); // no args
|
|
|
|
expect(mockRegisterRepo).toHaveBeenCalledWith(
|
|
resolvedRepo,
|
|
expect.objectContaining({ repoPath: resolvedRepo }),
|
|
);
|
|
expect(mockAddToGitignore).toHaveBeenCalledWith(resolvedRepo);
|
|
expect(process.exitCode).toBeUndefined();
|
|
});
|
|
|
|
it('fails when multiple path parts do not resolve to a single existing path', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
const ambiguousPath = path.resolve('/repo /other');
|
|
|
|
mockAccess.mockImplementation(async (targetPath: string) => {
|
|
if (targetPath === ambiguousPath) {
|
|
throw new Error('missing combined path');
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo', '/other']);
|
|
|
|
expect(mockRegisterRepo).not.toHaveBeenCalled();
|
|
expect(mockAddToGitignore).not.toHaveBeenCalled();
|
|
expect(process.exitCode).toBe(1);
|
|
expect(logSpy).toHaveBeenCalledWith(' The `index` command accepts a single path only.');
|
|
});
|
|
|
|
it('prints node and edge stats after registration', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
|
|
const { indexCommand } = await import('../../src/cli/index-repo.js');
|
|
await indexCommand(['/repo']);
|
|
|
|
expect(logSpy).toHaveBeenCalledWith(' Repository registered: repo');
|
|
expect(logSpy).toHaveBeenCalledWith(' 10 nodes | 20 edges');
|
|
});
|
|
});
|