Files
factory/lib/projects-meta-mcp/tests/lib/domain-detector.test.ts
vitya ca669d96e1 feat(lib): bundle MCP servers from .common/lib into factory/lib/
Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for:
- projects-meta-mcp v2.25.0 (TypeScript/Node)
- wiki-graph v0.3.1 (TypeScript/Node)
- interns-mcp v0.3.3 (Python/FastMCP)

.gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc)
bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:17:29 +03:00

188 lines
5.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { mkdtemp, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
detectDomain,
detectProjectIdentity,
parseRemoteUrl,
resolveSourceProject,
} from '../../src/lib/domain-detector.js';
async function tmp(): Promise<string> {
return mkdtemp(join(tmpdir(), 'dd-'));
}
describe('detectDomain', () => {
it('returns node when package.json present', async () => {
const d = await tmp();
await writeFile(join(d, 'package.json'), '{}');
expect(await detectDomain(d)).toBe('node');
});
it('returns embedded for platformio.ini', async () => {
const d = await tmp();
await writeFile(join(d, 'platformio.ini'), '[env]');
expect(await detectDomain(d)).toBe('embedded');
});
it('returns embedded for .ino', async () => {
const d = await tmp();
await writeFile(join(d, 'sketch.ino'), 'void setup() {}');
expect(await detectDomain(d)).toBe('embedded');
});
it('returns embedded for ARM CMakeLists', async () => {
const d = await tmp();
await writeFile(join(d, 'CMakeLists.txt'), 'set(CMAKE_C_COMPILER arm-none-eabi-gcc)');
expect(await detectDomain(d)).toBe('embedded');
});
it('returns web for vite config', async () => {
const d = await tmp();
await writeFile(join(d, 'vite.config.ts'), 'export default {}');
expect(await detectDomain(d)).toBe('web');
});
it('returns web for static index.html without package.json', async () => {
const d = await tmp();
await writeFile(join(d, 'index.html'), '<html></html>');
expect(await detectDomain(d)).toBe('web');
});
it('returns unknown for empty dir', async () => {
const d = await tmp();
expect(await detectDomain(d)).toBe('unknown');
});
it('package.json wins over index.html', async () => {
const d = await tmp();
await writeFile(join(d, 'package.json'), '{}');
await writeFile(join(d, 'index.html'), '<html></html>');
expect(await detectDomain(d)).toBe('node');
});
});
describe('parseRemoteUrl', () => {
it('parses https Gitea URL with .git suffix', () => {
expect(parseRemoteUrl('https://git.kzntsv.site/OpeItcLoc03/common.git')).toEqual({
owner: 'OpeItcLoc03',
repo: 'common',
});
});
it('parses https Gitea URL without .git suffix', () => {
expect(parseRemoteUrl('https://git.kzntsv.site/victor/books')).toEqual({
owner: 'victor',
repo: 'books',
});
});
it('parses https URL with port', () => {
expect(parseRemoteUrl('https://git.kzntsv.site:443/victor/books.git')).toEqual({
owner: 'victor',
repo: 'books',
});
});
it('parses ssh URL', () => {
expect(parseRemoteUrl('git@git.kzntsv.site:victor/books.git')).toEqual({
owner: 'victor',
repo: 'books',
});
});
it('parses ssh URL without .git suffix', () => {
expect(parseRemoteUrl('git@github.com:owner/repo')).toEqual({
owner: 'owner',
repo: 'repo',
});
});
it('strips trailing slash before parsing', () => {
expect(parseRemoteUrl('https://git.kzntsv.site/victor/books/')).toEqual({
owner: 'victor',
repo: 'books',
});
});
it('returns null for empty string', () => {
expect(parseRemoteUrl('')).toBeNull();
});
it('returns null for non-URL local path', () => {
expect(parseRemoteUrl('/path/to/local/repo')).toBeNull();
});
it('returns null for URL without owner/repo path', () => {
expect(parseRemoteUrl('https://example.com/single')).toBeNull();
});
});
describe('detectProjectIdentity', () => {
it('returns parsed identity when git remote returns Gitea https URL', async () => {
const fakeExec = async () => ({
stdout: 'https://git.kzntsv.site/OpeItcLoc03/common.git\n',
stderr: '',
});
expect(await detectProjectIdentity('/cwd', fakeExec)).toEqual({
owner: 'OpeItcLoc03',
repo: 'common',
});
});
it('returns parsed identity when git remote returns ssh URL', async () => {
const fakeExec = async () => ({
stdout: 'git@git.kzntsv.site:victor/books.git\n',
stderr: '',
});
expect(await detectProjectIdentity('/cwd', fakeExec)).toEqual({
owner: 'victor',
repo: 'books',
});
});
it('returns null when git command fails (no remote / not a repo)', async () => {
const fakeExec = async () => {
throw new Error('fatal: not a git repository');
};
expect(await detectProjectIdentity('/cwd', fakeExec)).toBeNull();
});
it('returns null when remote URL is unparseable', async () => {
const fakeExec = async () => ({ stdout: 'garbage-not-a-url\n', stderr: '' });
expect(await detectProjectIdentity('/cwd', fakeExec)).toBeNull();
});
});
describe('resolveSourceProject', () => {
it('returns qualified <owner>/<repo> with fellBack=false when remote resolves', async () => {
const fakeExec = async () => ({
stdout: 'https://git.kzntsv.site/victor/books.git\n',
stderr: '',
});
expect(await resolveSourceProject('/some/cwd/path/books', fakeExec)).toEqual({
sourceProject: 'victor/books',
fellBack: false,
});
});
it('falls back to basename(cwd) with fellBack=true when remote command fails', async () => {
const fakeExec = async () => {
throw new Error('fatal: not a git repository');
};
expect(await resolveSourceProject('/home/user/projects/.workshop', fakeExec)).toEqual({
sourceProject: '.workshop',
fellBack: true,
});
});
it('falls back to basename(cwd) with fellBack=true when remote URL is unparseable', async () => {
const fakeExec = async () => ({ stdout: 'garbage-not-a-url\n', stderr: '' });
expect(await resolveSourceProject('/tmp/loose-folder', fakeExec)).toEqual({
sourceProject: 'loose-folder',
fellBack: true,
});
});
});