Download testes/test_cache_analise.py from AIDAON/aida-api: direct link, hf CLI and curl.
- Browser
- Download file 5.41 kB
-
https://huggingface.co/spaces/AIDAON/aida-api/resolve/main/testes/test_cache_analise.py
- Command line
-
hf download hf://spaces/AIDAON/aida-api/testes/test_cache_analise.py
-
curl -L -o test_cache_analise.py https://huggingface.co/spaces/AIDAON/aida-api/resolve/main/testes/test_cache_analise.py
5.41 kB
| """Cache de análises: gravação, leitura, invalidação e o que NÃO é persistido. | |
| O cache é a peça de que o AIDA Forensics depende inteiramente. Se ele guardar | |
| demais, o disco cresce sem controle; se guardar de menos ou entregar dados de | |
| outra versão do modelo, o dossiê passa a contradizer o resultado que o originou. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import time | |
| import pytest | |
| def test_guarda_e_recupera(cache, payload_analise): | |
| cache.guardar(payload_analise("ABC-1")) | |
| lido = cache.recuperar_analise("ABC-1") | |
| assert lido is not None | |
| assert lido["resultado"] == "INCONCLUSIVO" | |
| assert lido["scores_modulos"] == {"forense": 0.31, "clip": 0.55} | |
| def test_nao_persiste_o_vetor_de_caracteristicas(cache, payload_analise): | |
| """659 colunas que nenhuma tela usa não podem ir para o disco.""" | |
| cache.guardar(payload_analise("ABC-2")) | |
| lido = cache.recuperar_analise("ABC-2") | |
| assert "principais_metricas" not in lido | |
| # O que a tela usa continua lá. | |
| assert lido["qualidade"]["qualidade_score"] == pytest.approx(0.62) | |
| def test_nao_persiste_caminho_do_servidor(cache, payload_analise): | |
| """Caminho absoluto expõe a estrutura de diretórios da implantação.""" | |
| cache.guardar(payload_analise("ABC-3")) | |
| lido = cache.recuperar_analise("ABC-3") | |
| assert lido["caminho_imagem"] == "" | |
| # Dos mapas sobra o nome do arquivo, não o caminho. | |
| for nome in lido["evidencias"]["arquivos"].values(): | |
| assert "/" not in nome and "\\" not in nome | |
| def test_tipos_do_numpy_sao_serializados(cache, payload_analise): | |
| """`np.float32` no payload não pode derrubar a gravação inteira.""" | |
| numpy = pytest.importorskip("numpy") | |
| payload = payload_analise("ABC-4") | |
| payload["scores_modulos"] = {"forense": numpy.float32(0.42)} | |
| payload["duracao_s"] = numpy.float64(2.5) | |
| payload["evidencias"]["perfil_radial"] = numpy.array([1.0, 2.0]) | |
| assert cache.guardar(payload) is not None | |
| lido = cache.recuperar_analise("ABC-4") | |
| assert lido["scores_modulos"]["forense"] == pytest.approx(0.42, abs=1e-6) | |
| def test_identificador_invalido_nao_grava(cache, payload_analise): | |
| """`../` na chave escaparia do diretório de cache.""" | |
| assert cache.guardar(payload_analise("ABC-5"), id_analise="../fuga") is None | |
| assert cache.recuperar_analise("../fuga") is None | |
| def test_expira_por_tempo(cache, payload_analise): | |
| cache.guardar(payload_analise("ABC-6")) | |
| caminho = cache.caminho_da_analise("ABC-6") | |
| registro = json.loads(caminho.read_text(encoding="utf-8")) | |
| registro["gravado_em"] = time.time() - 3600 | |
| caminho.write_text(json.dumps(registro), encoding="utf-8") | |
| assert cache.recuperar_analise("ABC-6", ttl_s=60) is None | |
| assert cache.recuperar_analise("ABC-6", ttl_s=7200) is not None | |
| def test_ttl_zero_desliga_expiracao(cache, payload_analise, monkeypatch): | |
| monkeypatch.setenv("AIDA_CACHE_TTL_H", "0") | |
| cache.guardar(payload_analise("ABC-7")) | |
| caminho = cache.caminho_da_analise("ABC-7") | |
| registro = json.loads(caminho.read_text(encoding="utf-8")) | |
| registro["gravado_em"] = 0.0 | |
| caminho.write_text(json.dumps(registro), encoding="utf-8") | |
| assert cache.recuperar_analise("ABC-7") is not None | |
| def test_versao_de_modelo_diferente_invalida(cache, payload_analise): | |
| """Dossiê precisa corresponder ao modelo que produziu a decisão.""" | |
| cache.guardar(payload_analise("ABC-8")) | |
| assert cache.recuperar_analise("ABC-8", versao_modelo="v-outra") is None | |
| assert cache.recuperar_analise("ABC-8") is not None | |
| def test_invalidacao_manual_remove_tambem_o_indice(cache, payload_analise): | |
| cache.guardar(payload_analise("ABC-9")) | |
| assert cache.recuperar_por_conteudo("a" * 64) is not None | |
| assert cache.invalidar("ABC-9") is True | |
| assert cache.recuperar_analise("ABC-9") is None | |
| # Ponteiro órfão faria a consulta por conteúdo devolver um id inexistente. | |
| assert cache.recuperar_por_conteudo("a" * 64) is None | |
| assert cache.invalidar("ABC-9") is False | |
| def test_indice_por_conteudo_encontra_analise_anterior(cache, payload_analise): | |
| cache.guardar(payload_analise("ABC-10", hash_sha256="b" * 64)) | |
| registro = cache.recuperar_por_conteudo("b" * 64) | |
| assert registro is not None | |
| assert registro["id_analise"] == "ABC-10" | |
| def test_arquivo_corrompido_e_descartado(cache, payload_analise): | |
| cache.guardar(payload_analise("ABC-11")) | |
| cache.caminho_da_analise("ABC-11").write_text("{ isto não é json", encoding="utf-8") | |
| assert cache.recuperar_analise("ABC-11") is None | |
| # Descartado no ato: manter o arquivo faria toda leitura futura repetir a | |
| # tentativa e o log encher com o mesmo erro. | |
| assert not cache.caminho_da_analise("ABC-11").exists() | |
| def test_limpar_remove_apenas_o_que_expirou(cache, payload_analise): | |
| cache.guardar(payload_analise("NOVA")) | |
| cache.guardar(payload_analise("VELHA")) | |
| velha = cache.caminho_da_analise("VELHA") | |
| antigo = time.time() - 10 * 24 * 3600 | |
| import os | |
| os.utime(velha, (antigo, antigo)) | |
| assert cache.limpar() == 1 | |
| assert cache.recuperar_analise("NOVA") is not None | |
| assert cache.recuperar_analise("VELHA") is None | |
| def test_listar_devolve_apenas_validas(cache, payload_analise): | |
| cache.guardar(payload_analise("L-1")) | |
| cache.guardar(payload_analise("L-2")) | |
| assert sorted(cache.listar()) == ["L-1", "L-2"] | |