feat: живой цикл M2M с НРД + мастер установки ключа на флешку

Инфраструктура M2M (живой обмен с НРД через ИШ):
- обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение
  ответа; INFO → ждём Decision; идемпотентность поллера
- fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO
- сырой XML ответа НРД в карточке заявки (для пересылки в ТП)
- тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes,
  4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта
- редирект из теста сразу в карточку заявки

Мастер установки ключа Валидаты на флешку (admin/setup/keywizard):
- пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник
  сертификатов (CRL) → перезапуск+проверка ИШ → готово
- привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен,
  bj-server остаётся в песочнице
- сохранение структуры профиля архива (spr<N>), перечисление съёмных USB

Прочее:
- пакет-доказательство для ТП НРД + форма регистрации участника M2M
- эталонные образцы робота (DOC/m2m_robot_samples)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-06-19 00:03:21 +03:00
parent 6e503433d4
commit 9737c787f9
110 changed files with 10771 additions and 1690 deletions
+18 -28
View File
@@ -8,55 +8,45 @@ import (
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/cryptocli"
)
// TestStubProviderHealthOK — провайдер stub не лезет в gRPC,
// возвращает информативный Health без ошибки.
func TestStubProviderHealthOK(t *testing.T) {
cli := cryptocli.New(cryptocli.Config{Provider: cryptocli.ProviderStub})
defer cli.Close()
h, err := cli.Health(context.Background())
if err != nil {
t.Fatalf("Health: %v", err)
}
if h.Provider != string(cryptocli.ProviderStub) {
t.Errorf("Provider = %q", h.Provider)
t.Errorf("Provider = %q, ожидался stub", h.Provider)
}
if !strings.Contains(h.Message, "stub") {
t.Errorf("сообщение не содержит 'stub': %q", h.Message)
}
}
func TestModulePathMissing(t *testing.T) {
// TestValidataProviderNoSocket — провайдер validata пытается дойти до
// сайдкара, но в тестах сокета нет. gRPC-клиент создаётся лениво
// (NewClient не возвращает ошибку), а ошибка приходит при первом RPC.
func TestValidataProviderNoSocket(t *testing.T) {
cli := cryptocli.New(cryptocli.Config{
Provider: cryptocli.ProviderCryptoPro,
ModulePath: "/nonexistent/libcppkcs11.so",
Provider: cryptocli.ProviderValidata,
SocketPath: "/nonexistent/crypto.sock",
})
defer cli.Close()
_, err := cli.Health(context.Background())
if err == nil {
t.Fatal("ожидалась ошибка о ненайденном модуле")
}
if !strings.Contains(err.Error(), "не найден") {
t.Errorf("неинформативная ошибка: %v", err)
}
}
func TestEmptyModulePath(t *testing.T) {
cli := cryptocli.New(cryptocli.Config{Provider: cryptocli.ProviderCryptoPro})
_, err := cli.Health(context.Background())
if err == nil {
t.Fatal("ожидалась ошибка о пустом ModulePath")
t.Fatal("ожидалась ошибка о недоступном сокете")
}
}
// TestDefaultModulePath — информативный текст для UI.
func TestDefaultModulePath(t *testing.T) {
cases := []struct {
p cryptocli.Provider
want string
}{
{cryptocli.ProviderCryptoPro, "/opt/cprocsp/lib/amd64/libcppkcs11.so"},
{cryptocli.ProviderRutoken, "/usr/lib64/librtpkcs11ecp.so"},
{cryptocli.ProviderStub, ""},
if cryptocli.DefaultModulePath(cryptocli.ProviderStub) != "" {
t.Error("DefaultModulePath(stub) должен быть пустым")
}
for _, c := range cases {
got := cryptocli.DefaultModulePath(c.p)
if got != c.want {
t.Errorf("DefaultModulePath(%s) = %q, ожидалось %q", c.p, got, c.want)
}
v := cryptocli.DefaultModulePath(cryptocli.ProviderValidata)
if v == "" {
t.Error("DefaultModulePath(validata) не должен быть пустым")
}
}