Files
portal/tools/observer-coverage-checker.test.mjs
T
Дмитрий 8f00449615 fix сторож наблюдателя: не кричим про пропавший хук, когда он на месте
Работа 1 промта от 01.08 — оживление сторожей текста. По ходу вскрылся
отдельный дефект в нашем собственном скрипте.

observer-coverage-checker при коммите из рабочей папки ветки печатал
".git/hooks/post-commit not installed". Файл при этом лежал на месте.
Причина: в рабочей папке ветки .git не папка, а файл-указатель, и проверка
пути <корень>/.git/hooks/post-commit смотрела мимо цели. Плюс настройка
core.hooksPath, которая в этом репозитории задана, вообще не учитывалась.

Класс беды тот же, что уже дважды записан в память: сторож, не умеющий
отличить "нет" от "лежит в другом месте", штампует ложные тревоги. Рядом
в той же строке живёт правдивая жалоба про неподключённый обработчик - и
ложная половина приучает не читать всю строку целиком.

Что сделано:
- добавлена postCommitInstalled: сначала ищет общую папку репозитория
  (для обычной копии это сам .git, для рабочей папки ветки - по указателю
  gitdir и файлу commondir рядом с ним), затем учитывает core.hooksPath
  из config и только потом смотрит наличие файла;
- чистое чтение файлов, без запуска сторонних команд - требование
  Security Guidance 40 для этого скрипта соблюдено.

Порядок работы соблюдён. Пять проверок написаны ДО правки и покраснели
на отсутствующей функции. Разобраны обе стороны каждой развилки: обычная
копия с хуком и без, рабочая папка ветки с хуком и без, перенос хуков
настройкой. Живой прогон по настоящему репозиторию: из рабочей папки
ветки ложная половина ушла, правдивая осталась.

Прогоны: набор проверок инструментов 14 файлов, 66 проверок, все зелёные.

Отдельно, без правок в git: node_modules был в состоянии оборванной
установки - 121 временная папка распаковки, 90 пакетов без главного файла,
у tinyglobby файл обрезан посреди строки. Причина обрыва - падение сборки
better-sqlite3. Пересобрано начисто из списка версий без сборочных
сценариев; package.json и package-lock.json не тронуты, проверено хешем.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:19:10 +03:00

79 lines
3.4 KiB
JavaScript

// Проверки поиска файла post-commit для C5 observer-coverage-checker.
//
// Повод: 01.08.2026 сторож печатал «.git/hooks/post-commit not installed»
// при коммите из рабочей папки ветки (git worktree). Там `.git` — не папка,
// а файл-указатель, и проверка `<корень>/.git/hooks/post-commit` мимо цели.
// Файл при этом лежит на месте, в общей папке репозитория. Ложная тревога.
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { postCommitInstalled } from './observer-coverage-checker.mjs';
let baza;
beforeEach(() => {
baza = mkdtempSync(join(tmpdir(), 'occ-'));
});
afterEach(() => {
rmSync(baza, { recursive: true, force: true });
});
describe('postCommitInstalled', () => {
it('обычная папка репозитория: файл на месте — видит его', () => {
const koren = join(baza, 'repo');
mkdirSync(join(koren, '.git', 'hooks'), { recursive: true });
writeFileSync(join(koren, '.git', 'hooks', 'post-commit'), '#!/bin/sh\n');
expect(postCommitInstalled(koren)).toBe(true);
});
it('обычная папка репозитория: файла нет — так и говорит', () => {
const koren = join(baza, 'repo');
mkdirSync(join(koren, '.git', 'hooks'), { recursive: true });
expect(postCommitInstalled(koren)).toBe(false);
});
it('рабочая папка ветки: .git — файл-указатель, а хук лежит в общей папке', () => {
const koren = join(baza, 'repo');
mkdirSync(join(koren, '.git', 'hooks'), { recursive: true });
writeFileSync(join(koren, '.git', 'hooks', 'post-commit'), '#!/bin/sh\n');
const svoya = join(koren, '.git', 'worktrees', 'vetka');
mkdirSync(svoya, { recursive: true });
writeFileSync(join(svoya, 'commondir'), '../..\n');
const vetka = join(baza, 'vetka');
mkdirSync(vetka, { recursive: true });
writeFileSync(join(vetka, '.git'), `gitdir: ${svoya}\n`);
expect(postCommitInstalled(vetka)).toBe(true);
});
it('рабочая папка ветки: хука нет и в общей папке — тревога честная', () => {
const koren = join(baza, 'repo');
mkdirSync(join(koren, '.git', 'hooks'), { recursive: true });
const svoya = join(koren, '.git', 'worktrees', 'vetka');
mkdirSync(svoya, { recursive: true });
writeFileSync(join(svoya, 'commondir'), '../..\n');
const vetka = join(baza, 'vetka');
mkdirSync(vetka, { recursive: true });
writeFileSync(join(vetka, '.git'), `gitdir: ${svoya}\n`);
expect(postCommitInstalled(vetka)).toBe(false);
});
it('хуки перенесены настройкой core.hooksPath — смотрит туда', () => {
const koren = join(baza, 'repo');
mkdirSync(join(koren, '.git', 'hooks'), { recursive: true });
const inye = join(koren, 'moi-huki');
mkdirSync(inye, { recursive: true });
writeFileSync(join(inye, 'post-commit'), '#!/bin/sh\n');
writeFileSync(join(koren, '.git', 'config'), '[core]\n\thooksPath = moi-huki\n');
expect(postCommitInstalled(koren)).toBe(true);
});
});