"""Testes da escrita de mutações no Premiere, sem o app aberto. Cobrem a distinção — que já custou um corte silenciosamente perdido — entre um ``split_clip`` que não fez nada porque o ponto já era borda de clipe (um no-op legítimo) e um ``split_clip`` que falhou de verdade no bridge. """ import unittest from typing import Any from engine.editor.erros import ErroDeEscritaNoEditor from engine.editor.escrita import EscritaNoEditor from engine.integracoes.premiere.cliente_mcp import ClienteMCP from engine.integracoes.premiere.erros_mcp import ErroDeFerramentaMCP class _ClienteQueRecusa(ClienteMCP): """Dublê que recusa toda chamada com a mensagem informada no construtor.""" def __init__(self, mensagem_de_erro: str) -> None: self.mensagem_de_erro = mensagem_de_erro self.chamadas: list[tuple[str, dict[str, Any]]] = [] def conectar(self) -> None: """Não há conexão real neste dublê.""" def desconectar(self) -> None: """Não há conexão real neste dublê.""" def esta_conectado(self) -> bool: """Sempre conectado: este dublê não simula falha de conexão.""" return True def chamar(self, nome_da_ferramenta: str, argumentos: dict[str, Any] | None = None) -> dict[str, Any]: """Registra a chamada e recusa com :class:`ErroDeFerramentaMCP`.""" self.chamadas.append((nome_da_ferramenta, argumentos or {})) raise ErroDeFerramentaMCP(self.mensagem_de_erro) class TesteDividirClipeSeNecessario(unittest.TestCase): """Separa o no-op esperado do erro real de bridge.""" def _dividir(self, mensagem_de_erro: str) -> bool: escrita = EscritaNoEditor(_ClienteQueRecusa(mensagem_de_erro)) return escrita.dividir_clipe_se_necessario(10.0, 0, "video") def test_ponto_que_ja_era_borda_e_no_op_e_nao_erro(self): # O Premiere recusa dividir onde nenhum clipe atravessa o instante: # é o resultado esperado quando o corte pedido coincide com uma borda # já existente (inclusive ao reexecutar um plano parcialmente aplicado). self.assertFalse(self._dividir("No clip strictly spans 10.0s on track video_0.")) def test_contagem_de_clipes_inalterada_e_no_op_e_nao_erro(self): # Segunda forma observada do mesmo no-op: o razor roda, mas o ponto já # coincide por arredondamento de quadro com uma borda existente, então # a contagem de clipes da faixa não muda. self.assertFalse(self._dividir("razor changed the track clip count from 4 to 4, expected 5")) def test_erro_real_do_bridge_nao_e_confundido_com_no_op(self): # Bug real: qualquer ErroDeFerramentaMCP era engolido como "já era # borda", então uma falha de conexão ou uma faixa bloqueada passava # por corte bem-sucedido e o trecho seguia na timeline sem aviso. with self.assertRaises(ErroDeEscritaNoEditor) as capturado: self._dividir("Track video_0 is locked.") self.assertIn("locked", str(capturado.exception)) def test_contagem_de_clipes_que_mudou_errado_nao_e_no_op(self): # A contagem mudou, mas não para o esperado: o razor fez algo, e algo # diferente do pedido — isso é erro, não no-op. with self.assertRaises(ErroDeEscritaNoEditor): self._dividir("razor changed the track clip count from 4 to 7, expected 5") class TesteDuplicarTrechosVinculados(unittest.TestCase): """Cobre o contrato estreito da escrita do gancho editorial.""" def test_exige_exatamente_dois_identificadores(self): escrita = EscritaNoEditor(_ClienteQueRecusa("não deveria chamar")) with self.assertRaisesRegex(ErroDeEscritaNoEditor, "exatamente"): escrita.duplicar_trecho_vinculado_para_o_inicio(["somente-video"], 10.0, 15.0) def test_converte_falha_da_ferramenta_em_erro_de_escrita(self): escrita = EscritaNoEditor(_ClienteQueRecusa("faixa adicional ocupada")) with self.assertRaisesRegex(ErroDeEscritaNoEditor, "faixa adicional ocupada"): escrita.duplicar_trecho_vinculado_para_o_inicio(["video", "audio"], 10.0, 15.0) if __name__ == "__main__": unittest.main()