- criado monitoramento contínuo de trilhas com varredura recursiva e baixa prioridade - gerado e validado plano editorial revisável do depoimento da Erika sem aplicação na timeline - adicionada leitura e comparação estrutural da timeline após aplicar planos de edição - limitadas as classificações de trilhas às duas maiores probabilidades de cada categoria - limpo o catálogo musical para reiniciar as análises sem remover dados de vídeo Resumo: - 34 arquivos alterados - 6 novos - 28 modificados - 0 removidos 28 files changed, 833 insertions(+), 75 deletions(-) Arquivos: - .jhonny/analises.db - code/cep-plugin/index.html - code/cep-plugin/main.js - code/engine/analisar_trilhas.py - code/engine/aplicar_plano_de_edicao.py - code/engine/consultar_detalhes_de_trilha.py - code/engine/editor/__init__.py - code/engine/editor/aplicador_de_plano_de_edicao.py - code/engine/editor/backup_de_sequencia.py - code/engine/editor/escrita/escrita_no_editor.py - code/engine/editor/modelos.py - code/engine/editor/validacao_semantica.py - code/engine/integracoes/audio/classificador_de_musica_essentia.py - code/engine/integracoes/audio/modelos_de_analise_musical.py - code/engine/integracoes/audio/provider_de_analise_musical_essentia.py - code/engine/persistencia/esquema.py - code/engine/persistencia/repositorio_de_trilhas.py - code/engine/testes/duplos_de_premiere.py - code/engine/testes/test_aplicador_de_plano_de_edicao.py - code/engine/testes/test_aplicar_plano_de_edicao.py - code/engine/testes/test_escrita_no_editor.py - code/engine/testes/test_leitor_de_plano_de_edicao.py - code/engine/testes/test_validador_semantico_de_plano.py - code/plugins/premiere-pro/skills/editar-por-voz/SKILL.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/08-formato-de-saida.md - code/plugins/premiere-pro/skills/editar-por-voz/criterios/10-revisao-humana.md - code/src/tools/timeline.ts - code/tests/tools/tool-modules.test.ts - .jhonny/planos/ - code/.jhonny/analises.db-shm - code/.jhonny/analises.db-wal - code/engine/editor/verificador_da_aplicacao.py - code/engine/testes/test_verificador_da_aplicacao.py - code/plugins/premiere-pro/skills/editar-por-voz/criterios/11-gancho.md
105 lines
4.9 KiB
Python
105 lines
4.9 KiB
Python
"""Testes da resolução do executável Node usada pelo aplicador de planos."""
|
|
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from engine import aplicar_plano_de_edicao
|
|
from engine.editor import AcaoDeEdicao, ErroDePlanoInvalido, PlanoDeEdicao, TipoDeAcao
|
|
|
|
|
|
class TesteResolucaoDoNode(unittest.TestCase):
|
|
"""Garante que a engine não dependa do PATH herdado pelo painel CEP."""
|
|
|
|
def test_prioriza_executavel_configurado_no_ambiente(self):
|
|
"""Usa o caminho explícito quando o painel ou sistema o fornece."""
|
|
with patch.dict(os.environ, {"PREMIERE_NODE_BIN": "/custom/node"}, clear=False), patch(
|
|
"engine.aplicar_plano_de_edicao.os.path.isfile", return_value=True
|
|
), patch("engine.aplicar_plano_de_edicao.os.access", return_value=True):
|
|
self.assertEqual(aplicar_plano_de_edicao.resolver_caminho_do_node(), "/custom/node")
|
|
|
|
def test_usa_caminho_conhecido_quando_node_nao_esta_no_path(self):
|
|
"""Encontra a instalação Homebrew mesmo com PATH incompleto."""
|
|
with patch.dict(os.environ, {}, clear=True), patch(
|
|
"engine.aplicar_plano_de_edicao.shutil.which", return_value=None
|
|
), patch("engine.aplicar_plano_de_edicao.os.path.isfile", side_effect=lambda caminho: caminho == "/opt/homebrew/bin/node"), patch(
|
|
"engine.aplicar_plano_de_edicao.os.access", return_value=True
|
|
):
|
|
self.assertEqual(aplicar_plano_de_edicao.resolver_caminho_do_node(), "/opt/homebrew/bin/node")
|
|
|
|
def test_informa_instalacao_ausente(self):
|
|
"""Levanta um erro explicativo quando nenhum executável existe."""
|
|
with patch.dict(os.environ, {}, clear=True), patch(
|
|
"engine.aplicar_plano_de_edicao.shutil.which", return_value=None
|
|
), patch("engine.aplicar_plano_de_edicao.os.path.isfile", return_value=False):
|
|
with self.assertRaisesRegex(FileNotFoundError, "Node.js não encontrado"):
|
|
aplicar_plano_de_edicao.resolver_caminho_do_node()
|
|
|
|
|
|
class TestePreValidacaoDoPlano(unittest.TestCase):
|
|
"""Garante que planos semanticamente perigosos não alcancem o Premiere."""
|
|
|
|
def test_interrompe_antes_de_resolver_node_e_criar_backup(self) -> None:
|
|
"""A validação semântica ocorre antes de abrir a sessão do editor."""
|
|
with tempfile.TemporaryDirectory() as pasta:
|
|
caminho_do_plano = Path(pasta) / "plano.json"
|
|
caminho_do_plano.write_text(
|
|
'{"source":"video.mp4","actions":['
|
|
'{"kind":"cut","start":1.0,"end":2.0,"reason":"teste"}]}',
|
|
encoding="utf-8",
|
|
)
|
|
with patch(
|
|
"engine.aplicar_plano_de_edicao._validar_integridade_semantica",
|
|
side_effect=ErroDePlanoInvalido("frase interrompida"),
|
|
) as validar, patch(
|
|
"engine.aplicar_plano_de_edicao.resolver_caminho_do_node"
|
|
) as resolver_node:
|
|
with self.assertRaisesRegex(ErroDePlanoInvalido, "frase interrompida"):
|
|
aplicar_plano_de_edicao.executar(caminho_do_plano, plano_id=7)
|
|
|
|
validar.assert_called_once()
|
|
resolver_node.assert_not_called()
|
|
|
|
def test_gancho_exige_backup_confirmado(self) -> None:
|
|
"""A operação composta não começa quando a recuperação não foi criada."""
|
|
plano = PlanoDeEdicao("video.mp4", (
|
|
AcaoDeEdicao(TipoDeAcao.GANCHO, 10.0, 15.0, "gancho"),
|
|
))
|
|
|
|
with self.assertRaisesRegex(ErroDePlanoInvalido, "não confirmou o backup"):
|
|
aplicar_plano_de_edicao._garantir_backup_para_ganchos(plano, False)
|
|
|
|
def test_corte_preserva_comportamento_legado_quando_backup_falha(self) -> None:
|
|
"""A exigência adicional fica restrita ao gancho estrutural."""
|
|
plano = PlanoDeEdicao("video.mp4", (
|
|
AcaoDeEdicao(TipoDeAcao.CORTE, 10.0, 15.0, "retirada"),
|
|
))
|
|
|
|
aplicar_plano_de_edicao._garantir_backup_para_ganchos(plano, False)
|
|
|
|
def test_painel_rejeita_corte_sem_plano_associado_ao_banco(self) -> None:
|
|
"""Não permite contornar a validação colando um corte sem transcrição vinculada."""
|
|
with tempfile.TemporaryDirectory() as pasta:
|
|
caminho_do_plano = Path(pasta) / "plano.json"
|
|
caminho_do_plano.write_text(
|
|
'{"source":"video.mp4","actions":['
|
|
'{"kind":"cut","start":1.0,"end":2.0,"reason":"teste"}]}',
|
|
encoding="utf-8",
|
|
)
|
|
with patch(
|
|
"engine.aplicar_plano_de_edicao.resolver_caminho_do_node"
|
|
) as resolver_node:
|
|
with self.assertRaisesRegex(ErroDePlanoInvalido, "Salve ou carregue"):
|
|
aplicar_plano_de_edicao.executar(
|
|
caminho_do_plano,
|
|
exigir_validacao_semantica=True,
|
|
)
|
|
|
|
resolver_node.assert_not_called()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|