feat: création de projet (nouveau dossier + session) & en-tête de session enrichi
- POST /api/v1/projects : crée <root>/<name> (mkdir non récursif, 409 si existant), git init optionnel, puis lance une session dedans. Anti-traversal sur le nom (core/project.ts). - Web : NewProjectModal (racine préremplie scanRoots[0], case git init) + bouton dans SessionsListView. - SessionContextBar : badge du groupe (cliquable) + repos couverts avec leur branche dans l'en-tête de session ; session simple = repo+branche déduits du cwd. - 14 tests (unitaires + route).
This commit is contained in:
45
packages/server/test/project.test.ts
Normal file
45
packages/server/test/project.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isSafeProjectName, resolveProjectDir } from '../src/core/project.js';
|
||||
|
||||
describe('isSafeProjectName', () => {
|
||||
it('accepte un segment simple', () => {
|
||||
expect(isSafeProjectName('mon-projet')).toBe(true);
|
||||
expect(isSafeProjectName('Projet_42.v2')).toBe(true);
|
||||
expect(isSafeProjectName(' espaces-trim ')).toBe(true); // trim interne
|
||||
});
|
||||
|
||||
it('refuse vide / espaces seuls', () => {
|
||||
expect(isSafeProjectName('')).toBe(false);
|
||||
expect(isSafeProjectName(' ')).toBe(false);
|
||||
});
|
||||
|
||||
it('refuse `.` et `..`', () => {
|
||||
expect(isSafeProjectName('.')).toBe(false);
|
||||
expect(isSafeProjectName('..')).toBe(false);
|
||||
});
|
||||
|
||||
it('refuse les séparateurs et le NUL (anti-traversal)', () => {
|
||||
expect(isSafeProjectName('a/b')).toBe(false);
|
||||
expect(isSafeProjectName('../evil')).toBe(false);
|
||||
expect(isSafeProjectName('a\\b')).toBe(false);
|
||||
expect(isSafeProjectName('a\0b')).toBe(false);
|
||||
});
|
||||
|
||||
it('refuse un nom trop long (> 255)', () => {
|
||||
expect(isSafeProjectName('x'.repeat(256))).toBe(false);
|
||||
expect(isSafeProjectName('x'.repeat(255))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveProjectDir', () => {
|
||||
it('joint root + name (trim) en chemin absolu', () => {
|
||||
expect(resolveProjectDir('/home/johan/dev', 'mon-projet')).toBe('/home/johan/dev/mon-projet');
|
||||
expect(resolveProjectDir('/home/johan/dev', ' mon-projet ')).toBe('/home/johan/dev/mon-projet');
|
||||
});
|
||||
|
||||
it('lève (400) sur un nom invalide', () => {
|
||||
expect(() => resolveProjectDir('/home/johan/dev', '..')).toThrow();
|
||||
expect(() => resolveProjectDir('/home/johan/dev', 'a/b')).toThrow();
|
||||
expect(() => resolveProjectDir('/home/johan/dev', '')).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user