GitNexus/gitnexus/test/integration/group/group-sync.test.ts
ivkond 4fed097abb feat(group): add sync pipeline, CLI, MCP tools, and monorepo fixture
Wire extractors into the sync pipeline with service boundary detection.
GroupService provides high-level API for all group operations.

- Sync pipeline: orchestrates extraction (HTTP, gRPC, topics) with
  service boundary assignment and exact matching
- GroupService: groupList, groupSync, groupContracts, groupQuery,
  groupStatus (groupImpact deferred to cross-repo follow-up PR)
- CLI: group create/add/remove/list/sync/contracts/query/status
- MCP tools: group_list, group_sync, group_contracts, group_query,
  group_status
- Monorepo fixture: 3 services (auth/orders/gateway) connected via
  gRPC + Kafka + HTTP — all intra-repo cross-links discovered
- Documentation: CLI commands and MCP tools added to both READMEs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 00:40:31 +03:00

79 lines
2.9 KiB
TypeScript

/**
* Group sync integration — uses `extractorOverride` / parsed YAML only (no LadybugDB).
* Full pipeline with indexed fixture repos is a follow-up (needs `.gitnexus/lbug`).
*/
import { describe, it, expect } from 'vitest';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parseGroupConfig } from '../../../src/core/group/config-parser.js';
import { syncGroup } from '../../../src/core/group/sync.js';
import type { StoredContract } from '../../../src/core/group/types.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FIXTURES_DIR = path.resolve(__dirname, '../../fixtures/group');
describe('Group sync integration', () => {
it('parses fixture group.yaml', () => {
const yamlContent = fs.readFileSync(path.join(FIXTURES_DIR, 'group.yaml'), 'utf-8');
const config = parseGroupConfig(yamlContent);
expect(config.name).toBe('test-group');
expect(config.repos['app/backend']).toBe('test-backend');
expect(config.repos['app/frontend']).toBe('test-frontend');
});
it('builds cross-links from fixture-shaped contracts via syncGroup', async () => {
const yamlContent = fs.readFileSync(path.join(FIXTURES_DIR, 'group.yaml'), 'utf-8');
const config = parseGroupConfig(yamlContent);
const mockContracts: StoredContract[] = [
{
contractId: 'http::GET::/api/users',
type: 'http',
role: 'provider',
symbolUid: 'uid-p1',
symbolRef: { filePath: 'src/routes/users.ts', name: 'list' },
symbolName: 'list',
confidence: 0.9,
meta: { method: 'GET', path: '/api/users' },
repo: 'app/backend',
},
{
contractId: 'http::GET::/api/users',
type: 'http',
role: 'consumer',
symbolUid: 'uid-c1',
symbolRef: { filePath: 'src/api/users.ts', name: 'fetchUsers' },
symbolName: 'fetchUsers',
confidence: 0.85,
meta: { method: 'GET', path: '/api/users' },
repo: 'app/frontend',
},
{
contractId: 'http::GET::/api/health',
type: 'http',
role: 'provider',
symbolUid: 'uid-h1',
symbolRef: { filePath: 'src/routes/health.ts', name: 'health' },
symbolName: 'health',
confidence: 0.9,
meta: { method: 'GET', path: '/api/health' },
repo: 'app/backend',
},
];
const result = await syncGroup(config, {
extractorOverride: async () => mockContracts,
skipWrite: true,
});
expect(result.crossLinks.length).toBeGreaterThanOrEqual(1);
const usersLink = result.crossLinks.find((l) => l.contractId.includes('/api/users'));
expect(usersLink).toBeDefined();
expect(usersLink!.matchType).toBe('exact');
expect(usersLink!.confidence).toBe(1.0);
const healthUnmatched = result.unmatched.some((c) => c.contractId.includes('/api/health'));
expect(healthUnmatched).toBe(true);
});
});