Gitea - lien « Code source » hardcodé (REPO_SOURCE_URL) vers le dépôt, toujours visible - retrait complet du réglage configurable (api.ts, route+store settings, SettingsView, i18n, help, tests) Découverte des dépôts - aucune racine de scan par défaut → pas de scan au premier démarrage (clean install) - corrige la découverte des dépôts à l'INTÉRIEUR d'une racine qui est elle-même un repo (depth 0 = conteneur de scan, on descend ; depth > 0 = feuille) Sécurité « enterprise-deployable » - en-têtes HTTP durcis (CSP, X-Frame-Options, nosniff, Referrer-Policy, HSTS conditionnel, no-store API), header Server retiré - permissions DB 0o600 / dossier de données 0o700 ; error handler sanitisé ; garde Content-Type sur les mutations - chiffrement au repos AES-256-GCM des secrets (server_secret, clé privée VAPID) via SecretBox - journal d'audit (migration #7) + endpoint /audit-logs + RGPD export/effacement + UI Réglages - SECURITY.md, docs/ENTERPRISE_DEPLOYMENT.md, sections README EN/FR, SBOM CycloneDX en CI CI - pack-smoke packe depuis le contexte du package (cd packages/server) au lieu de « npm pack -w » : corrige l'embarquement de @arboretum/shared dans le tarball Purge des données personnelles du dépôt public - suppression de spikes/s4-discovery/result.json, reformulation du VERDICT - chemin de test générique, anonymisation des 5 fixtures de dialogues Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
import { describe, expect, it, afterEach } from 'vitest';
|
|
import { mkdtempSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { scanForRepos } from '../src/core/repo-scanner.js';
|
|
|
|
const dirs: string[] = [];
|
|
afterEach(() => {
|
|
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
});
|
|
|
|
function tmpRoot(): string {
|
|
const d = mkdtempSync(join(tmpdir(), 'arb-scan-'));
|
|
dirs.push(d);
|
|
return d;
|
|
}
|
|
// un « repo » pour le scanner = un dossier contenant `.git` (le scanner ne lance jamais git).
|
|
function makeRepo(...segs: string[]): void {
|
|
mkdirSync(join(...segs, '.git'), { recursive: true });
|
|
}
|
|
|
|
const limits = { maxDepth: 6, maxRepos: 2000 };
|
|
|
|
describe('scanForRepos', () => {
|
|
it('trouve un repo simple', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'proj');
|
|
const { paths, truncated } = await scanForRepos([root], limits);
|
|
expect(paths).toEqual([join(root, 'proj')]);
|
|
expect(truncated).toBe(false);
|
|
});
|
|
|
|
it('ne descend pas dans un repo trouvé (sous-repo ignoré)', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'a');
|
|
makeRepo(root, 'a', 'sub'); // imbriqué : doit être ignoré
|
|
const { paths } = await scanForRepos([root], limits);
|
|
expect(paths).toEqual([join(root, 'a')]);
|
|
});
|
|
|
|
it('descend dans une racine qui est elle-même un repo (workspace + sous-repos)', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root); // la racine fournie contient elle-même un .git (cas WebstormProjects)
|
|
makeRepo(root, 'a');
|
|
makeRepo(root, 'b');
|
|
const { paths } = await scanForRepos([root], limits);
|
|
// la racine ET ses dépôts internes sont découverts (la racine n'arrête pas le scan)
|
|
expect([...paths].sort()).toEqual([root, join(root, 'a'), join(root, 'b')].sort());
|
|
});
|
|
|
|
it('ignore node_modules et les dotdirs', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'node_modules', 'pkg');
|
|
makeRepo(root, '.hidden', 'x');
|
|
makeRepo(root, 'real');
|
|
const { paths } = await scanForRepos([root], limits);
|
|
expect(paths).toEqual([join(root, 'real')]);
|
|
});
|
|
|
|
it('respecte maxDepth', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'a', 'b', 'c', 'deep'); // repo à profondeur 4
|
|
const shallow = await scanForRepos([root], { maxDepth: 2, maxRepos: 2000 });
|
|
expect(shallow.paths).toEqual([]);
|
|
const deep = await scanForRepos([root], { maxDepth: 4, maxRepos: 2000 });
|
|
expect(deep.paths).toEqual([join(root, 'a', 'b', 'c', 'deep')]);
|
|
});
|
|
|
|
it('ne suit pas les symlinks (cycle terminé, pas de doublon)', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'proj');
|
|
try {
|
|
symlinkSync(root, join(root, 'loop')); // cycle vers la racine
|
|
} catch {
|
|
/* symlink non autorisé sous certains CI : le test reste valide sans le lien */
|
|
}
|
|
const { paths } = await scanForRepos([root], limits);
|
|
expect(paths).toEqual([join(root, 'proj')]);
|
|
});
|
|
|
|
it('tronque à maxRepos', async () => {
|
|
const root = tmpRoot();
|
|
makeRepo(root, 'r1');
|
|
makeRepo(root, 'r2');
|
|
makeRepo(root, 'r3');
|
|
const { paths, truncated } = await scanForRepos([root], { maxDepth: 6, maxRepos: 2 });
|
|
expect(truncated).toBe(true);
|
|
expect(paths.length).toBeLessThanOrEqual(2);
|
|
});
|
|
|
|
it('ignore une racine inexistante sans lever', async () => {
|
|
const { paths } = await scanForRepos(['/nope/does/not/exist'], limits);
|
|
expect(paths).toEqual([]);
|
|
});
|
|
|
|
it('scanne plusieurs racines', async () => {
|
|
const r1 = tmpRoot();
|
|
const r2 = tmpRoot();
|
|
makeRepo(r1, 'one');
|
|
makeRepo(r2, 'two');
|
|
const { paths } = await scanForRepos([r1, r2], limits);
|
|
expect([...paths].sort()).toEqual([join(r1, 'one'), join(r2, 'two')].sort());
|
|
});
|
|
});
|