"""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 ErroDePlanoInvalido 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_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()