🧪 Testes Automatizados
O PlayerHUD inclui uma suíte de testes extensa que cobre tanto a lógica de negócio (PHPUnit) quanto a aceitação em navegador (Behat). Todo push de CI executa a matriz completa (Moodle 4.5 → 5.x, PostgreSQL e MariaDB).
PHPUnit — Testes Unitários e de Integração
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
ai/generator_test.php |
2 | save_item() (via reflection, sem rede): limita um nome gerado por IA acima do tamanho; converte campos não-string antes de persistir |
backup_restore_test.php |
3 | Definições de backup/restore cobrem todas as tabelas RPG; round-trip completo de curso (incl. uma atividade real) preserva dados de classe/capítulo/história RPG, poderes de item (action_type/action_value), tiers de emoji da classe, e o requisito de uma quest TYPE_SPECIFIC_TRADE remapeado contra a troca restaurada em vez do mapeamento de item; um cmid fixado no deadline_extension e o requisito de uma quest TYPE_ACTIVITY são ambos remapeados para a atividade do curso restaurado |
collection_tab_test.php |
8 | Aba Coleção: mapeamento de filter_type (avatar/prazo/nenhum), power_hint_avatar exibido para item não-secreto não possuído e oculto para secreto, flag is_equipped; classificação de origem para a fonte de uma linha de inventário (map é reconhecida como própria do PlayerHUD; qualquer coisa fora das 4 fontes conhecidas cai para uma origem genérica “game”) |
content_crud_test.php |
13 | CRUD de itens, capítulos e trocas: criação persiste todos os campos, atualização altera campos, exclusão remove registro, listagem escoped por instância |
cross_instance_security_test.php |
12 | Isolamento cross-instance: guardas de item, quest, capítulo e troca aceitam IDs da própria instância e rejeitam IDs alheios sem modificar o registro alvo |
drop_guard_test.php |
7 | Limites de coleta, itens consumidos por troca, aplicação de cooldown |
game_test.php |
36 | get_game_stats() totaliza XP/nível mais a inclusão de XP de quests (e exclusão quando a quest está desabilitada), verificado contra o próprio total de analytics::economy_health(); anti-farm de coleta e cooldown; get_avatar_item (habilitado, desabilitado, instância estrangeira, não encontrado); XP concedido ao coletar drop com uso finito; exclusão de gerentes do ranking; flags de milestone de level-up, vitória no jogo e primeira PlayerCoin na coleta; xp_to_level; criação automática de jogador, alternância de gamificação e visibilidade no ranking, inventário (exclui revogados/consumidos), has_item; get_user_rank ordem por XP, desempate por chegada, exclusão de gerentes e de não matriculados; hidratação de requisitos/recompensas em get_full_trades, caso vazio, e bloqueio de disponibilidade quando o item de qualquer um dos lados está desabilitado; heurística de sugestões de troca (avatares com desconto, pulo de avatar já coberto, pré-requisitos) e persistência; change_xp emite o evento xp_changed ao conceder, ao deduzir (piso em zero) e fica em silêncio num no-op de verdade |
gamemaster_test.php |
6 | Conceder/revogar/excluir item e quest preservando timestamps do ranking; XP mínimo em zero |
instance_delete_test.php |
1 | Excluir uma instância do bloco limpa todas as tabelas próprias do plugin (instance_cleanup) |
item_delete_cascade_test.php |
17 | Detecção de trocas órfãs ao excluir item (único req, um de dois, único reward, combinado req+reward); verificações em lote; isolamento cross-instance; exclusão remove o item e cascateia trocas órfãs sem afetar as não-órfãs; excluir um item (único ou em lote) reverte XP só das cópias que realmente ganharam XP, deixando intactas as cópias de drop infinito (XP zero) |
karma_test.php |
11 | Leitura/escrita de karma, deltas positivos/negativos, clamping nos limites ±999, acumulação sucessiva |
privacy_provider_test.php |
10 | LGPD com cobertura completa: descoberta de contexto/usuário (get_contexts_for_userid, get_users_in_context); export_user_data nas seis subárvores (perfil, RPG, inventário, missões, trocas, logs de IA); exclusão por usuário, multiusuário e de contexto inteiro com garantia de isolamento; exportação/exclusão de toda chave de API e preferência de avatar; declaração de metadados; guardas de contexto não-bloco como no-ops |
quest_test.php |
34 | Verificações de conclusão (nível, XP, itens, trocas, conclusão de atividade); reivindicar recompensas; quest desabilitada; idempotência; flags de comemoração de level-up e vitória no jogo ao reivindicar recompensa; has_claimable_quests em todos os tipos de requisito incl. conclusão de atividade, com curto-circuito de reivindicadas/não reivindicadas; mapeamento de build_record_from_suggestion, transporte de item-ids e piso do override de XP; get_heuristic_suggestions milestones de nível/coleção/economia/atividade com pulo de duplicatas; uma atividade com acompanhamento de conclusão oferecida como missão heurística é detectada como cumprida assim que a atividade é realmente concluída |
rpg_classes_test.php |
7 | Atribuição de classe, proteção contra duplicatas, inicialização de karma, limites de tier de retrato |
story_manager_test.php |
15 | Carregamento de cena, persistência de progresso, navegação de escolhas, delta de karma, conclusão de capítulo, casos de erro |
suggest_trades_state_test.php |
4 | Botão Sugerir Trocas: desabilitado sem pré-requisitos, desabilitado só com moeda, desabilitado quando todos os avatares cobertos, habilitado com cobertura parcial |
trade_test.php |
8 | Montagem de trocas, fundos insuficientes, sucesso atômico, limite único, restrição por grupo; uma troca que referencia um item de recompensa desabilitado é rejeitada de imediato mesmo com saldo suficiente |
utils_test.php |
4 | get_avatar_html: emoji gera div ph-avatar-emoji com span aria-hidden; URL HTTP gera tag img ph-avatar-img; imagem nula não lança exceção em get_avatar_html nem em get_items_display_data |
| Subtotal | 198 |
Testes de Lógica de Negócio Compartilhada (tests/local/)
Lógica reutilizada por mais de um ponto de entrada (as próprias web services do assistente, a tela manual de “Distribuir Drops”, o painel Economy Health), testada diretamente em vez de só indiretamente através de quem quer que a chame.
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
analytics_test.php |
11 | Economy Health: razão entre XP total ganhável e o teto (vazio/difícil/perfeito/fácil), recompensas de quest e itens infinitos/sem drop no detalhamento, guarda de teto zero; histograma de distribuição de níveis, ordenação do overflow do cap (N+), percentual da barra mais alta, guarda de XP-por-nível zero, conjunto de jogadores vazio não produz linhas; o XP atual de balance_context() sempre bate com o total do próprio economy_health() |
audit_log_test.php |
5 | Consulta compartilhada de log de auditoria (get_logs()) usada pela aba Relatórios do professor e pela aba Histórico do estudante: o xp_gained de um item reflete o valor xpawarded registrado no momento da concessão, não o XP atual do item (e bate quando nunca editado); um item concedido por quest reporta xp_gained zero, já que seu próprio XP nunca é pago por esse caminho; uma linha revogada reporta o negativo do valor originalmente registrado, não o XP atual do item; a reivindicação de uma quest reflete o valor registrado, não o reward_xp atual da quest |
drop_distribution_test.php |
12 | Descoberta de módulos elegíveis: inclui fóruns, exclui módulos em exclusão e o fórum de avisos do curso (reservado para PlayerCoin/Item Secreto), vazio para curso sem atividades; sugestão por melhor correspondência de nome incl. caso sem correspondência; busca de cmid por shortcode já inserido incl. não encontrado e entrada vazia; divisão de cotas por atividade sempre soma o alvo, limita ao número de atividades, casos de borda |
external_items_test.php |
18 | API de itens entre plugins usada por outros plugins da família Player (ex.: PlayerWords): belongs_to_instance() aceita a própria instância de um item (habilitado ou desabilitado) e rejeita instância estrangeira, id inexistente, ou ids zero/negativos sem consultar o banco; grant() insere uma linha de inventário por unidade com seu próprio xpawarded e credita o XP total uma vez, retém XP quando quem chama sinaliza a origem como sem limite, e é no-op para item de instância estrangeira ou desabilitado; consume() marca as linhas mais antigas como consumidas em caso de sucesso, retorna false quando o saldo é insuficiente, e retorna null (não false) para item de instância estrangeira, para que quem chama dispense o custo em vez de bloquear o estudante para sempre; get_name()/get_xp() resolvem para a própria instância do item e retornam vazio/zero para uma estrangeira; get_available_quantity() conta só linhas ativas (não revogadas/não consumidas) e é zero para item de instância estrangeira mesmo que o usuário possua unidades dele |
wizard_test.php |
17 | Manifesto da rodada: status de início/fim; desfazer exclui objetos registrados em todas as tabelas, remove o shortcode registrado, reverte XP e limpa o histórico de jogo, rejeita instância incompatível; listagem de rodadas ativas com contagens e limite; detecção de “já gerado” por mecânica incl. rodadas obsoletas sem conteúdo, itens só no manifesto, itens só logados pela IA e a checagem só-de-config do Ranking; ensure_config_flag liga uma flag sem tocar em config irmã e não faz nada quando já está ligada |
xp_budget_test.php |
15 | Contagens de item/missão/capítulo por tamanho de jornada incl. fallback pra curta; distribute_share divide a folga igualmente, espalha o resto nos primeiros elementos, limita à folga quando há mais elementos que ela, casos de borda; mapeamento de níveis-máximos sugeridos; rodízio balanceado de missões entre tipos, preservação de ordem dentro de um tipo, todas selecionadas quando o limite as cobre, casos de borda |
| Subtotal | 78 |
Testes de Web Services (tests/external/)
Uma classe de teste por função de web service, validando o contrato da API externa, conformidade de parâmetros e estrutura de retorno (external_api::clean_returnvalue), e guardas de capability. As funções de IA são testadas sem rede — sem chave de API configurada, o bloco try/catch retorna success=false, que é assegurado diretamente.
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
chat_message_test.php |
2 | Sem chave de API → moodle_exception; guarda de capability (manage) |
collect_item_test.php |
4 | Item coletado + registro de inventário criado; drop inválido → success=false; limite atingido → success=false; guarda de capability (view) |
create_avatar_pack_test.php |
6 | 17 itens criados; ids e nomes retornados em lockstep; todos com action_type=avatar; deduplicação por emoji; segunda chamada cria 0 (idempotência); guarda de capability |
create_class_pack_test.php |
7 | Cria 3 classes; tiers de HP base conforme esperado; pula nome já existente; segunda chamada cria 0 (idempotência); tons diferentes geram nomes diferentes; tom desconhecido cai no fallback fantasia; guarda de capability |
create_playercoin_test.php |
3 | Novo item criado; segunda chamada retorna existente (idempotência); guarda de capability |
execute_chat_action_test.php |
4 | action_open_tab retorna URL de redirect (determinístico, sem IA); tipo de ação desconhecido → success=false; parâmetros inválidos → success=false; guarda de capability |
generate_ai_content_test.php |
2 | Sem chave de API → success=false; guarda de capability (manage) |
generate_class_oracle_test.php |
2 | Sem chave de API → success=false; guarda de capability (manage) |
generate_story_test.php |
2 | Sem chave de API → success=false; guarda de capability (manage) |
insert_drop_shortcode_test.php |
7 | Shortcode inserido no campo de conteúdo do módulo; inserção duplicada rejeitada; drop de outra instância rejeitado; drop renomeado pra atividade em que caiu; mode=text com rótulo customizado; modo desconhecido cai pra card; guarda de capability |
load_recap_test.php |
3 | HTML de recap gerado após visita à cena; sem histórico → exceção; guarda de capability (view) |
load_scene_test.php |
3 | Nó inicial e escolhas retornados; capítulo inválido → exceção; guarda de capability (view) |
make_choice_test.php |
3 | Avança a história até o nó de destino; escolha inválida → exceção; guarda de capability (view) |
remove_drop_shortcode_test.php |
5 | Shortcode existente removido; shortcode separado por <br> removido; shortcode com atributos mode=/text= removido; ausência de shortcode é noop sem erro; guarda de capability |
setup_playercoin_drop_test.php |
6 | Sucesso; sem fórum → success=false; item de outra instância rejeitado; curso que não é dono da instância rejeitado; shortcode anteposto ao intro existente; guarda de capability |
use_item_test.php |
6 | Guarda de capability (view); item não possuído → exceção; poder de prazo: sem atividade, sem regra, cria override e consome item, atualiza override existente |
wizard_apply_suggested_levels_test.php |
3 | Aplica a sugestão quando a config está nos padrões; ainda aplica quando a config já foi customizada; preserva todo outro campo de config intocado |
wizard_generate_helpers_test.php |
10 | build_step_types() bate com os módulos selecionados na ordem, pula auto_distribute quando o distribuir de Itens está desligado, vazio quando nada selecionado; compute_shared_xp_shares() vazio sem Itens/Missões, Pill/Extensão de Prazo usam seus próprios padrões sozinhos, dividem o orçamento com Itens quando combinados; resolve_or_create_progress_item() idempotente e cria um item completo quando falta; resolve_previous_chapter_context() lê o capítulo mais recente; distribute_drops() limita cada atividade à sua cota calculada em vez de deixar só a correspondência de nome empilhar todo drop numa única atividade |
wizard_list_runs_test.php |
4 | Resumo de uma rodada ativa; rodada de RPG resumida; rodadas desfeitas excluídas; guarda de capability |
wizard_run_step_test.php |
56 | Um passo de progresso ao vivo por vez, por mecânica (PlayerCoin, Avatares, Missões, Comércio, Colecionável de Conhecimento, Item Secreto, Ranking, Extensão de Prazo, RPG, Item RPG, auto-distribuir): criação de item/quest/troca com registro no manifesto, retentativas idempotentes, desfazer por mecânica, controle pela flag de distribuir, tom/tamanho de jornada influenciando o conteúdo, e a inserção exclusiva no fórum de avisos pra PlayerCoin e Item Secreto (incl. no-op sem fórum de avisos); tipo de passo desconhecido, guarda de capability, rejeição de runid de outra instância, passo com falha não finaliza a rodada, passo final reporta a economia só quando solicitado |
wizard_start_test.php |
8 | Um passo de plano por módulo selecionado; a flag de “passo lento” reflete se Próximo Capítulo foi selecionado; a divisão de cotas de XP bate com os módulos selecionados; o XP bônus da Pill presente quando selecionada sozinha; o módulo de arco da história se expande num outline + um passo por capítulo, a quantidade de passos cresce com o tamanho da jornada, o manifesto mantém o nome lógico do módulo; guarda de capability |
| Subtotal | 146 |
Testes de Controlador (tests/controller/)
Cobrem a lógica de negócio extraída do manage.php para os controladores (refatoração MVC), cada um exercitado com entradas explícitas e isolamento de instância.
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
aikeys_test.php |
4 | Armazenamento de chaves de IA: chaves aparadas e salvas como preferências do usuário, padrão vazio para campo ausente, chaves legadas removidas do config do bloco, config limpo intocado |
chapters_test.php |
13 | Persistência e ordenação de capítulos: salvar (inserir, atualizar, padrões, isolamento), excluir em cascata cenas/escolhas, mover/reordenar com renumeração da lista completa, no-op na borda |
classes_test.php |
7 | Persistência de classe RPG: inserção (HP base, vínculo de instância, emojis por tier), atualização preserva HP base, trim de emoji, isolamento; exclusão remove registro e retratos por tier, isolamento, irmãos preservados |
collect_test.php |
3 | Transação de coleta de item: drop finito concede XP, drop infinito concede 0 XP (regra de ouro), item de 0 XP armazenado sem alterar XP |
drops_test.php |
11 | Persistência de drop: salvar (inserir + código, ilimitado, atualizar preserva propriedade, isolamento, item estrangeiro); excluir único e no-op estrangeiro; exclusão em massa só dos próprios com contagem, entrada vazia; get_owned_item retorna para a instância dona e rejeita instância estrangeira |
export_test.php |
7 | Construtor da exportação de notas: campos da linha e nível derivado, ordenação por XP, teto de nível, exclusão de professores/gerentes, colunas localizadas sem jogadores, exclusão de não matriculados, desempate por última ação |
items_test.php |
15 | Ciclo de vida do item: toggle de ativação e no-op estrangeiro; conceder adiciona inventário + XP, 0 XP, rejeição estrangeira; revogar desconta XP, preserva drop infinito, no-op estrangeiro; revogar desconta o XP realmente registrado no momento da concessão, não o XP atual do item; detecção de trocas sobreviventes (troca aparada, órfã excluída, não relacionada ignorada); find_xp_impact soma só as cópias que realmente ganharam XP entre todos os detentores, vazio para item nunca possuído, e no-op para lista de ids vazia |
quests_test.php |
12 | Ciclo de vida da missão: toggle e no-op estrangeiro; excluir reverte XP por conclusão, sem recompensa, no-op estrangeiro; excluir e excluir em lote revertem o XP realmente registrado por conclusão, não a recompensa atual da quest; massa só dos próprios com reversão agregada de XP e contagem, entrada vazia; find_xp_impact soma só as conclusões que realmente ganharam XP entre todos os reivindicantes, vazio para quest nunca reivindicada, e no-op para lista de ids vazia |
scenes_test.php |
6 | Persistência de cena/escolha da história: salvar escolhas, atribuição de classe com normalização de ID string/int (regressão set_class_id), classe requerida, próximo nó, custo de item, criação de nó de continuação |
suggestions_test.php |
4 | Persistência de sugestões: só as missões marcadas são inseridas (e nenhuma selecionada), só as trocas marcadas são criadas com reqs/recompensas (e nenhuma selecionada) |
trades_test.php |
7 | Persistência de troca: salvar (inserir com reqs + recompensas, atualizar substitui, isolamento, item estrangeiro filtrado); excluir em cascata reqs/recompensas/log, isolamento, irmãos preservados |
| Subtotal | 89 |
Testes de Saída / Renderer (tests/output/)
| Arquivo de teste | Casos | O que é coberto |
|---|---|---|
manage/item_delete_confirm_test.php |
9 | Contexto da confirmação de exclusão de item: ação única vs massa e payload de IDs, rótulos de confirmação singular/plural/simples, seções só-sobreviventes e órfãs+sobreviventes; aviso de impacto de XP exibido numa exclusão única com link de desabilitar-em-vez-de, nunca exibido numa exclusão em lote mesmo com URL de alternância fornecida, e omitido por completo quando não há impacto de XP |
manage/quest_delete_confirm_test.php |
3 | Contexto de confirmação de exclusão de quest: exclusão única produz a ação delete_quest_force com o aviso de impacto de XP e o link de desabilitar-em-vez-de; exclusão em lote produz bulk_delete_quests_force com a lista de ids e nunca mostra o link de desabilitar mesmo com uma URL de alternância fornecida; sem impacto de XP omite tanto o aviso quanto o link |
manage/tab_chapters_test.php |
4 | Avisos de visibilidade do card de capítulo: sinalização de cena inicial ausente, texto e limites do aviso de nível acima do máximo |
| Subtotal | 16 |
| Total geral | 527 |
vendor/bin/phpunit --testsuite block_playerhud
Cobertura de linhas por classe (PHPUnit + Xdebug):
| Classe | Cobertura de linhas |
|---|---|
ai\generator |
6% |
controller\aikeys |
100% |
controller\chapters |
40% |
controller\classes |
41% |
controller\collect |
13% |
controller\drops |
20% |
controller\export |
90% |
controller\items |
99% |
controller\quests |
76% |
controller\scenes |
13% |
controller\suggestions |
100% |
controller\trades |
39% |
drop_guard |
100% |
event\xp_changed |
43% |
external\chat_message |
67% |
external\collect_item |
100% |
external\create_avatar_pack |
84% |
external\create_class_pack |
79% |
external\create_playercoin |
91% |
external\execute_chat_action |
27% |
external\generate_ai_content |
77% |
external\generate_class_oracle |
67% |
external\generate_story |
75% |
external\insert_drop_shortcode |
87% |
external\load_recap |
100% |
external\load_scene |
79% |
external\make_choice |
79% |
external\remove_drop_shortcode |
84% |
external\setup_playercoin_drop |
90% |
external\use_item |
75% |
external\wizard_apply_suggested_levels |
83% |
external\wizard_generate |
85% |
external\wizard_list_runs |
100% |
external\wizard_run_step |
86% |
external\wizard_start |
99% |
game |
84% |
instance_cleanup |
100% |
local\analytics |
90% |
local\audit_log |
78% |
local\drop_distribution |
97% |
local\external_items |
97% |
local\wizard |
76% |
local\xp_budget |
98% |
output\manage\item_delete_confirm |
100% |
output\manage\quest_delete_confirm |
100% |
output\manage\tab_chapters |
7% |
output\view\tab_collection |
68% |
privacy\provider |
96% |
quest |
90% |
story_manager |
37% |
trade_manager |
90% |
utils |
35% |
| Total | 42% |
49 das 82 classes do plugin aparecem acima — as demais (majoritariamente classes de exceção,
observadores de evento e wrappers finos de output nunca carregados via require durante a
execução desta suíte) não têm nenhum dado de cobertura e são omitidas em vez de aparecerem
como um 0% enganoso.
Behat — Testes de Aceitação
| Arquivo de feature | Cenários | O que é coberto |
|---|---|---|
block_playerhud_access.feature |
3 | Visibilidade do bloco por perfil (professor adiciona bloco, aluno vê HUD, não matriculado não vê) |
block_playerhud_student.feature |
4 | HUD ativo na primeira visita, desativar/reativar gamificação, dispensar confirmação |
block_playerhud_teacher.feature |
7 | Botão do Painel do Mestre, acesso ao painel de gerenciamento, navegação entre abas, retorno ao curso; abrir o log de auditoria de um aluno em Relatórios não dá erro |
block_playerhud_modals.feature |
5 | Abrir/fechar modal de detalhes do item, proteção contra abertura duplicada, coleta AJAX sem redirecionamento, sem placeholders brutos |
block_playerhud_celebrations.feature |
2 | Introdução do Huddy exibida uma única vez no painel; aviso de primeira quest exibido uma única vez quando há recompensa a reivindicar |
block_playerhud_wizard.feature |
6 | Assistente abre mostrando o formulário de geração; abas laterais de Ajuda e Recomendações externas; gerar PlayerCoin de ponta a ponta mostra o relatório de sucesso; o card do PlayerCoin trava depois de gerado; desfazer uma rodada pelo Histórico destrava de novo |
| Total | 27 |
php admin/tool/behat/cli/init.php
vendor/bin/behat --tags=@block_playerhud --profile=chrome