"""Aprendizado com o dia a dia: gosto pelos atos, rotina da casa, sono e o fechamento do dia.""" import http.client import importlib.util import json import random import sys import tempfile import threading import time import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from aprendizado import Aprendizado # noqa: E402 from vida import Vida # noqa: E402 spec = importlib.util.spec_from_file_location('kiosk_aprendizado', ROOT / 'kiosk-server.py') kiosk = importlib.util.module_from_spec(spec) spec.loader.exec_module(kiosk) def em(dia, hora, minuto=0, segundo=0): return time.mktime((2026, 10, dia, hora, minuto, segundo, 0, 0, -1)) class AprendizadoTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.caminho = Path(self.tmp.name) / 'aprendizado.json' self.a = Aprendizado(self.caminho) self.a.hoje = self.a._dia_vazio('2026-10-06') self.diario = [] self.a.anotar = lambda tipo, texto, agora=None: self.diario.append((tipo, texto)) def tearDown(self): self.tmp.cleanup() def fazer(self, nome, vezes, resposta=None, inicio=None): t = inicio or em(6, 15) for i in range(vezes): self.a.ato(nome, t + i * 120, perto=True) if resposta: self.a.resposta(resposta, t + i * 120 + 20) def test_gosto_sobe_com_carinho_e_desce_quando_ignorado(self): self.fazer('rir', 5, 'carinho') self.fazer('bocejar', 5, inicio=em(6, 16)) self.assertGreater(self.a.peso('rir'), 1.0) self.assertLess(self.a.peso('bocejar'), 1.0) self.assertLessEqual(self.a.peso('rir'), 2.0) self.assertGreaterEqual(self.a.peso('bocejar'), 0.5) def test_sem_opiniao_com_poucas_tentativas_ou_sozinho(self): self.fazer('rir', 2, 'carinho') self.assertEqual(self.a.peso('rir'), 1.0) for i in range(10): self.a.ato('pensar', em(6, 15) + i, perto=False) # sem plateia ninguém pode gostar self.assertEqual(self.a.peso('pensar'), 1.0) self.assertNotIn('pensar', self.a.itens) def test_resposta_fora_da_janela_nao_conta(self): for i in range(4): self.a.ato('acenar', em(6, 15) + i * 300, perto=True) self.a.resposta('carinho', em(6, 15) + i * 300 + 200) # 200 s depois: já era outra coisa self.assertEqual(self.a.itens['acenar']['s'], 0) def test_cada_ato_agrada_uma_vez(self): self.a.ato('rir', em(6, 15), perto=True) for _ in range(5): self.a.resposta('carinho', em(6, 15) + 10) self.assertEqual(self.a.itens['rir']['s'], 1.0) def test_fechar_o_dia_gera_licoes_e_esquece_um_pouco(self): self.fazer('rir', 4, 'carinho') self.fazer('bocejar', 5, inicio=em(6, 17)) t_antes = self.a.itens['rir']['t'] self.a.observar(em(7, 0, 1), perto=False) # meia-noite: fecha o dia 6 textos = [t for _, t in self.diario] self.assertTrue(any('gostam de me ver dar risada' in t for t in textos), textos) self.assertTrue(any('Tentei bocejar 5 vezes e ninguém ligou' in t for t in textos), textos) self.assertAlmostEqual(self.a.itens['rir']['t'], t_antes * 0.85, places=2) self.assertEqual(self.a.hoje['dia'], '2026-10-07') self.assertTrue(self.caminho.exists()) def test_rotina_da_casa_e_acordar_antes_de_chegarem(self): for dia in (6, 7, 8): for minuto in range(60): self.a.observar(em(dia, 17, minuto), perto=False) # 17h: casa vazia self.a.observar(em(dia, 18, minuto), perto=True) # 18h: chegou gente self.a.observar(em(9, 0, 1), perto=False) self.assertGreaterEqual(self.a.rotina[18], 0.5) self.assertLess(self.a.rotina[17], 0.3) self.assertTrue(any('costumam estar por perto das 18h às 19h' in t for _, t in self.diario), self.diario) self.assertTrue(self.a.chega_logo(em(9, 17, 50))) self.assertFalse(self.a.chega_logo(em(9, 17, 10))) # ainda falta muito self.assertFalse(self.a.chega_logo(em(9, 12, 50))) def test_acordar_a_toa_pede_mais_movimento_naquela_hora(self): for dia in (6, 7, 8, 9): for vez in range(2): t = em(dia, 22, vez * 20) self.a.acordou_pelo_sensor(t) for s in range(0, 200, 5): self.a.observar(t + s, perto=s < 10) # mexeu e sumiu: foi à toa self.a.observar(em(dia + 1, 0, 1), perto=False) self.assertGreaterEqual(self.a.extra_pir(em(10, 22, 30)), 1) self.assertEqual(self.a.extra_pir(em(10, 15)), 0) self.assertTrue(any('Acordei à toa 2 vezes por volta das 22h' in t for _, t in self.diario), self.diario) def test_acordar_com_carinho_valeu_a_pena(self): t = em(6, 22) self.a.acordou_pelo_sensor(t) self.a.observar(t + 5, perto=True) self.a.resposta('carinho', t + 30) for s in range(35, 200, 5): self.a.observar(t + s, perto=False) self.assertEqual(self.a.hoje['falsos'][22], 0) def test_desligado_nao_aprende_nem_muda_nada(self): self.a.configurar(dict(ligado=False)) self.fazer('rir', 6, 'carinho') self.assertEqual(self.a.peso('rir'), 1.0) self.assertEqual(self.a.extra_pir(em(6, 22)), 0) self.assertFalse(self.a.chega_logo(em(6, 17, 50))) def test_esquecer_e_persistencia(self): self.fazer('rir', 5, 'carinho') self.fazer('bocejar', 5, inicio=em(6, 17)) # o gosto é relativo: precisa de algo menos querido self.a.salvar() outro = Aprendizado(self.caminho) self.assertGreater(outro.peso('rir'), 1.0) outro.anotar = lambda *a, **k: None outro.esquecer() self.assertEqual(outro.peso('rir'), 1.0) self.assertEqual(Aprendizado(self.caminho).itens, {}) def test_validar(self): with self.assertRaises(ValueError): self.a.configurar(dict(ligado='sim')) with self.assertRaises(ValueError): self.a.configurar(dict(outro=True)) class VidaComAprendizadoTest(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() self.a = Aprendizado(Path(self.tmp.name) / 'aprendizado.json') self.a.hoje = self.a._dia_vazio('2026-10-06') self.v = Vida(Path(self.tmp.name) / 'vida.json', iniciar=False, rng=random.Random(3)) self.v.aprendizado = self.a self.ordens = [] self.v.enfileirar = self.ordens.append self.v.ultimo_tick = em(6, 15) def tearDown(self): self.tmp.cleanup() def test_gesto_que_agrada_sai_mais(self): for i in range(8): self.a.ato('rir', em(6, 15) + i * 100, perto=True) self.a.resposta('carinho', em(6, 15) + i * 100 + 5) self.a.ato('feliz', em(6, 15) + i * 100 + 50, perto=True) opcoes = [('rir', 1, ''), ('feliz', 1, '')] saiu = [self.v.escolher(opcoes)[0] for _ in range(400)] self.assertGreater(saiu.count('rir'), saiu.count('feliz') * 2) def test_carinho_e_atos_com_plateia_chegam_ao_aprendizado(self): self.v.alguem_perto = lambda: True self.v.celular_online = lambda: True self.v.proximo_ato = 0 self.v.necessidades.update(energia=90, social=90, diversao=90, alegria=90) ato = None for i in range(30): ato = self.v.tick(em(6, 15) + i * 200) if ato in self.a.itens: break self.assertIn(ato, self.a.itens) self.v.estimulo('carinho', em(6, 15) + i * 200 + 10) self.assertGreater(self.a.itens[ato]['s'], 0) def test_pir_pede_mais_quando_acordou_a_toa_naquela_hora(self): self.a.falsos[22] = 1.0 self.assertFalse(self.v.pir_acorda(em(6, 22))) self.assertTrue(self.v.pir_acorda(em(6, 22) + 10)) self.assertEqual(self.a.acordou_em, em(6, 22) + 10) self.assertTrue(self.v.pir_acorda(em(6, 15))) def test_acorda_do_cochilo_antes_de_chegarem(self): self.a.rotina[18], self.a.rotina[17] = 0.8, 0.1 self.v.dormindo_proprio = True self.v.necessidades['energia'] = 70 self.assertEqual(self.v.agir(em(6, 17, 50)), 'acordar') self.assertIn('acordar', self.ordens) def test_sono_profundo_nao_e_interrompido_pela_rotina(self): self.a.rotina[18], self.a.rotina[17] = 0.8, 0.1 self.v.dormindo_proprio = True self.v.sono_profundo = True self.v.necessidades['energia'] = 70 self.assertIsNone(self.v.agir(em(6, 17, 50))) class RotasTest(unittest.TestCase): def test_status_config_e_esquecer(self): with tempfile.TemporaryDirectory() as tmp: kiosk.ARQUIVO_APARENCIA = str(Path(tmp) / 'a.json') s = kiosk.Servidor(('127.0.0.1', 0), kiosk.Handler) s.aprendizado = Aprendizado(Path(tmp) / 'ap.json') kiosk.ligar_eventos(s) threading.Thread(target=s.serve_forever, daemon=True).start() try: def pedir(metodo, rota, corpo=None): c = http.client.HTTPConnection('127.0.0.1', s.server_port, timeout=5) dados = json.dumps(corpo).encode() if corpo is not None else None c.request(metodo, rota, body=dados, headers={'Content-Type': 'application/json'} if dados else {}) r = c.getresponse() return r.status, json.loads(r.read()) cod, st = pedir('GET', '/aprendizado/status') self.assertEqual(cod, 200) self.assertEqual(len(st['rotina']), 24) cod, st = pedir('POST', '/aprendizado/config', dict(ligado=False)) self.assertEqual((cod, st['config']['ligado']), (200, False)) cod, st = pedir('POST', '/aprendizado/config', dict(ligado='x')) self.assertEqual(cod, 400) cod, st = pedir('POST', '/aprendizado/esquecer', {}) self.assertEqual((cod, st['dias']), (200, 0)) cod, est = pedir('GET', '/estado') self.assertIn('aprendizado', est) finally: s.shutdown() kiosk.EVENTOS.fechar() if __name__ == '__main__': unittest.main()